背景
在 #5605 (给 HookContext.session 补声明 positions / preserveAudit)核对「文档教的代码能否按 (ctx: HookContext) 正常标类型」时发现:补完 positions 之后这两页示例仍然编译不过 ,原因不在 session,而在同一段示例的另一个键 —— ctx.services。它根本不在任何 hook 上下文里 。
这不是 #5605 的另一半:#5605 是「引擎在产、契约没声明」(补声明即可),本单是「文档在教、任何一端都没有生产者」,两者修法不同,故单独立单。
证据(均对 origin/main 核实)
文档在教 (标了 {/* os:check */},即声称「这段应当能编译」):
引擎不产 (代码注册的 handler 路径)—— packages/objectql/src/engine.ts:4650 起,每个 hookContext 都是逐字段构造的:
const hookContext: HookContext = {
object, event, input,
session: this.buildSession(opCtx.context),
provenance: ..., user: ..., api: this.buildHookApi(...),
transaction: ..., ql: this
};
九个键:object / event / input / session / provenance / user / api / transaction / ql。没有 services,其余 triggerHooks 调用点同构。
沙箱也不产 (metadata hook body 路径)—— packages/runtime/src/sandbox/body-runner.ts:310-324 的 buildHookSandboxContext 返回 input / previous / user / session / event / object / result / api / log / crypto。同样没有 services。注意对照组 :紧挨着的 buildActionSandboxContext(同文件 :326)注释写明 action ctx 约定里有 services —— 即 services 是 action 面的词汇,示例把它搬到了 hook 面。
契约也没有 —— HookContextSchema 声明的键与引擎一致,无 services(见生成的 content/docs/references/data/hook.mdx 属性表)。
为什么是缺陷而不是无害笔误
照抄的 hook 会拒掉每一次写入。 ctx.services?.sharing?.canEdit(...) 因可选链短路成 undefined,ok 恒为 undefined,于是 if (!ok) throw new Error('PERMISSION_DENIED') 无条件抛出 。作者按文档写一个 beforeUpdate 授权检查,得到的是「该对象上所有更新一律 403」,而且失败方向是「看起来在正常拒绝」,极难归因到文档。这正是 Prime Directive chore: version packages #10 推论里那句「绝不宣传运行时并不交付的能力」的实例。
ctx: any 让 {/* os:check */} 变成一道空门。 check:skill-examples 会真的编译这个被标记的块(packages/spec/scripts/check-skill-examples.ts),但块内 ctx 标成了 any,于是块里每一次属性访问都不被检查 —— 门是绿的,覆盖的却是零。同一个 any 同时掩盖了 [spec] HookContext.session 少声明了 positions / preserveAudit —— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 的 ctx.session?.positions(当时是 TS2339)和本单的 ctx.services。一个标了 os:check 又把入参标 any 的示例,等于声明「这段编译得过」却什么也没证明。
正确写法尚待确定,所以更该由维护者定。 hook 里访问 sharing service 的真实 受支持通道是什么,需要先答:是 ctx.api(buildHookApi)已经覆盖、还是应当像 action ctx 那样给 hook ctx 也注入 services、抑或 hook 就不该直接调 service。示例应改成哪一种,取决于这个答案。
建议修法(不预设结论)
先定「hook 访问 kernel service 的受支持通道」;
按该结论改这两处示例(examples.mdx 第 2 节;sharing-service.mdx:60-70 的 Example 块用的是裸 services.sharing,不带 ctx.,是否同病需一并核);
把示例的 ctx: any 换成 (ctx: HookContext) —— [spec] HookContext.session 少声明了 positions / preserveAudit —— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 落地后 session.positions 已可正常标类型,any 已无必要,而去掉它才能让 {/* os:check */} 真正生效;
可选:给 check:skill-examples 加一条「os:check 块内不得把入参标 any」的约束,否则这道门还会被下一个示例同样架空。
与其他单的关系
[spec] HookContext.session 少声明了 positions / preserveAudit —— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 (本单的发现来源,已在 PR):补 session.positions / preserveAudit 的声明。修完后这两页的 session 读法可正常标类型,但 ctx.services 仍编译不过 —— 两者不重叠,[spec] HookContext.session 少声明了 positions / preserveAudit —— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 不解决本单。
hooks 技能与 API 文档仍在教「批量写的行级谓词在 ctx.input.ast」——与 #5273 同一句假话,只是另外三个面 #5670 (hooks 技能与 API 文档在教「批量写的行级谓词在 ctx.input.ast」)同属「文档教了一个不存在的 hook 面」这一类,但键不同、页不同,修 hooks 技能与 API 文档仍在教「批量写的行级谓词在 ctx.input.ast」——与 #5273 同一句假话,只是另外三个面 #5670 不会碰到 ctx.services,故不作为其子单。
非阻塞:本单不依赖任何在飞的单。
发现于 #5605 的文档侧核查(Prime Directive #10 )。未加标签,交 PM 分诊定级。
背景
在 #5605(给
HookContext.session补声明positions/preserveAudit)核对「文档教的代码能否按(ctx: HookContext)正常标类型」时发现:补完positions之后这两页示例仍然编译不过,原因不在session,而在同一段示例的另一个键 ——ctx.services。它根本不在任何 hook 上下文里。这不是 #5605 的另一半:#5605 是「引擎在产、契约没声明」(补声明即可),本单是「文档在教、任何一端都没有生产者」,两者修法不同,故单独立单。
证据(均对
origin/main核实)文档在教(标了
{/* os:check */},即声称「这段应当能编译」):content/docs/kernel/runtime-services/examples.mdx:31-42—— 第 2 节 "Hook: check sharing permission before mutation":引擎不产(代码注册的 handler 路径)——
packages/objectql/src/engine.ts:4650起,每个hookContext都是逐字段构造的:九个键:
object/event/input/session/provenance/user/api/transaction/ql。没有services,其余triggerHooks调用点同构。沙箱也不产(metadata hook body 路径)——
packages/runtime/src/sandbox/body-runner.ts:310-324的buildHookSandboxContext返回input/previous/user/session/event/object/result/api/log/crypto。同样没有services。注意对照组:紧挨着的buildActionSandboxContext(同文件 :326)注释写明 action ctx 约定里有services—— 即services是 action 面的词汇,示例把它搬到了 hook 面。契约也没有 ——
HookContextSchema声明的键与引擎一致,无services(见生成的content/docs/references/data/hook.mdx属性表)。为什么是缺陷而不是无害笔误
ctx.services?.sharing?.canEdit(...)因可选链短路成undefined,ok恒为undefined,于是if (!ok) throw new Error('PERMISSION_DENIED')无条件抛出。作者按文档写一个beforeUpdate授权检查,得到的是「该对象上所有更新一律 403」,而且失败方向是「看起来在正常拒绝」,极难归因到文档。这正是 Prime Directive chore: version packages #10 推论里那句「绝不宣传运行时并不交付的能力」的实例。ctx: any让{/* os:check */}变成一道空门。check:skill-examples会真的编译这个被标记的块(packages/spec/scripts/check-skill-examples.ts),但块内ctx标成了any,于是块里每一次属性访问都不被检查 —— 门是绿的,覆盖的却是零。同一个any同时掩盖了 [spec] HookContext.session 少声明了positions/preserveAudit—— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 的ctx.session?.positions(当时是 TS2339)和本单的ctx.services。一个标了 os:check 又把入参标any的示例,等于声明「这段编译得过」却什么也没证明。ctx.api(buildHookApi)已经覆盖、还是应当像 action ctx 那样给 hook ctx 也注入services、抑或 hook 就不该直接调 service。示例应改成哪一种,取决于这个答案。建议修法(不预设结论)
examples.mdx第 2 节;sharing-service.mdx:60-70的 Example 块用的是裸services.sharing,不带ctx.,是否同病需一并核);ctx: any换成(ctx: HookContext)—— [spec] HookContext.session 少声明了positions/preserveAudit—— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 落地后session.positions已可正常标类型,any已无必要,而去掉它才能让{/* os:check */}真正生效;check:skill-examples加一条「os:check 块内不得把入参标any」的约束,否则这道门还会被下一个示例同样架空。与其他单的关系
positions/preserveAudit—— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605(本单的发现来源,已在 PR):补session.positions/preserveAudit的声明。修完后这两页的session读法可正常标类型,但ctx.services仍编译不过 —— 两者不重叠,[spec] HookContext.session 少声明了positions/preserveAudit—— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 不解决本单。ctx.input.ast」——与 #5273 同一句假话,只是另外三个面 #5670(hooks 技能与 API 文档在教「批量写的行级谓词在ctx.input.ast」)同属「文档教了一个不存在的 hook 面」这一类,但键不同、页不同,修 hooks 技能与 API 文档仍在教「批量写的行级谓词在ctx.input.ast」——与 #5273 同一句假话,只是另外三个面 #5670 不会碰到ctx.services,故不作为其子单。发现于 #5605 的文档侧核查(Prime Directive #10)。未加标签,交 PM 分诊定级。