Skip to content

[docs] runtime-services 的 hook 示例在教 ctx.services —— hook 上下文从来没有这个键,照抄的 hook 会拒掉每一次写入 #5720

Closed
1 of 1 issue completed

Description

@os-zhuang

背景

在 #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":

    export async function beforeUpdate(ctx: any) {
      const ok = await ctx.services?.sharing?.canEdit('contract', ctx.input.id, { ... });
      if (!ok) {
        throw new Error('PERMISSION_DENIED');
      }
    }
    

引擎不产(代码注册的 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 属性表)。

为什么是缺陷而不是无害笔误

  1. 照抄的 hook 会拒掉每一次写入。 ctx.services?.sharing?.canEdit(...) 因可选链短路成 undefined,ok 恒为 undefined,于是 if (!ok) throw new Error('PERMISSION_DENIED') 无条件抛出。作者按文档写一个 beforeUpdate 授权检查,得到的是「该对象上所有更新一律 403」,而且失败方向是「看起来在正常拒绝」,极难归因到文档。这正是 Prime Directive chore: version packages #10 推论里那句「绝不宣传运行时并不交付的能力」的实例。
  2. 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 的示例,等于声明「这段编译得过」却什么也没证明。
  3. 正确写法尚待确定,所以更该由维护者定。 hook 里访问 sharing service 的真实受支持通道是什么,需要先答:是 ctx.api(buildHookApi)已经覆盖、还是应当像 action ctx 那样给 hook ctx 也注入 services、抑或 hook 就不该直接调 service。示例应改成哪一种,取决于这个答案。

建议修法(不预设结论)

  1. 先定「hook 访问 kernel service 的受支持通道」;
  2. 按该结论改这两处示例(examples.mdx 第 2 节;sharing-service.mdx:60-70 的 Example 块用的是裸 services.sharing,不带 ctx.,是否同病需一并核);
  3. 把示例的 ctx: any 换成 (ctx: HookContext) —— [spec] HookContext.session 少声明了 positions / preserveAudit —— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 落地后 session.positions 已可正常标类型,any 已无必要,而去掉它才能让 {/* os:check */} 真正生效;
  4. 可选:给 check:skill-examples 加一条「os:check 块内不得把入参标 any」的约束,否则这道门还会被下一个示例同样架空。

与其他单的关系

发现于 #5605 的文档侧核查(Prime Directive #10)。未加标签,交 PM 分诊定级。

Activity

  1. added
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    domain:devxDomain lane — seat card indexed by label:pm:seat
    on Aug 6, 2026
  2. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    分诊:决策箱(needs-user-decision),预挂 domain:devx。

    • 过时前提检查(origin/main):content/docs/kernel/runtime-services/examples.mdx:13-14/:32 仍在教 ctx.services.data / ctx.services?.sharing(第 1 节的 ctx.services.data 也是同病,不止正文点名的第 2 节);engine.ts 各 hookContext 构造点与 sandbox buildHookSandboxContext 均不产 services,hook 面只有 api: buildHookApi(...)(ScopedContext)。[spec] HookContext.session 少声明了 positions / preserveAudit —— 引擎在生产、消费方在读、文档在教,契约里没有(#5050 的镜像方向) #5605 已随 PR feat(spec): HookContext.session 补声明 positions / preserveAudit(#5605) #5722 合入 main,session.positions/preserveAudit 半边已解;本单余量与其不重叠,前提成立。
    • 为什么进决策箱:修向取决于「hook 访问 kernel service 的受支持通道」——改教 ctx.api、给 hook ctx 注入 services(公开契约新增,涉 spec + engine),或裁定 hook 不得直调 service。这是公共契约拍板,不是文档措辞细节。裁「注入」则分诊届时按 contract-first 拆分(spec/engine 先行、docs 收尾);裁「ctx.api / 禁止」则为 docs-only,归 devx 车道直派。
    • 域锚定:最小落点 content/docs/kernel/runtime-services/examples.mdx + sharing-service.mdx(devx 域表 content/docs/**),故预挂 devx;正文第 4 点(check:skill-examples 加「os:check 块禁 ctx: any」)落 packages/spec/scripts/,属 spec 面,建议随裁决一并定去留、届时单独转 spec 座位。

    本评论来自分诊座位 Routine(#5474 试点),不构成认领。


    Generated by Claude Code

  3. claude commented on Aug 6, 2026

    @claude
    Contributor

    处置(2026-08-06):退回派发队列(docs-only)。

    复核结论:全仓无任何执行上下文生产 ctx.services(engine 四处 hookContext 构造、sandbox、service-automation flow node 面均查无),照抄示例的 hook 会 403 所有写入——docs 纠错 + 恢复既有契约,不触升级门槛。

    修法:runtime-services 两页 + sharing-service.mdx 示例改写为受支持能力(sharing 强制本由中间件自动执行,hook 手查 canEdit 是冗余教学;业务校验示例用 ctx.api.object());hook.zod.ts:416 JSDoc 同修。附带拆一个 spec scripts 子单:check:skill-examples 禁 os:check 块内 ctx: any(把同类空门一次焊死)。

    「hook ctx 注入 services」若日后有真实需求,另立提案走 contract-first,不由本 docs 缺陷单捎带。量级 S。

    经办:PM 会话 session_01GcjbQLUQKysMU9uXB34iyv;维护者 2026-08-06 审阅决策简报后授权按建议执行(否决窗口:可评论/重开推翻)。


    Generated by Claude Code

  4. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionBlocked on a maintainer decision — do not dispatch; decision cards only, never on a PR
    on Aug 6, 2026
  5. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    【裁决落地】维护者 2026-08-06 批复全舰队决策箱评估报告(批复「同意」),本单裁定:

    裁「改教 ctx.api」——docs-only:runtime-services 与 examples.mdx 第 1 节的 hook 示例全部改为受支持通道(ctx.api),⛔ 不新增「hook ctx 注入 services」公共契约(不动 spec/engine)。附带批准:os:check 块禁 ctx: any 的门禁约束立项(落 spec scripts,可同 PR 或由承接方拆单)。

    流转:摘 needs-user-decision → pm:queue,归 devx 车道直派(docs-only)。

    评估与落地会话:session_01N3uGFF8teXbpgtbEJ1aYXu


    Generated by Claude Code

  6. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 6, 2026
  7. self-assigned this
    on Aug 6, 2026
  8. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    Claim(PM devx 车道):本单由本 PM 会话派发实现。


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

domain:devxDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions