Skip to content

HookEvent 的枚举注释仍在说批量写把行级谓词「carried in input」—— 与 #5273 同一句假话,在同文件 40 行之外 #5899

Description

@os-zhuang

实现 #5670(对齐 skills / content/docs 三处的 ctx.input.ast 假句)时核实真值来源,在 packages/spec/src/data/hook.zod.ts 里发现同一条从未兑现的陈述还剩一处。PD #10 单独记录,不在 #5670 的 PR 里顺手改(那单派发面明确排除 packages/spec/**,理由是「#5273 已修」——实测只修了其中一处)。

事实(对 origin/main = 5c94f833c 核实)

HookEvent 枚举的写事件注释:

packages/spec/src/data/hook.zod.ts:135-140

  // Write — before/after per mutation kind. These fire on BOTH single-id and
  // bulk (`multi: true`) writes: a bulk update/delete runs the SAME
  // `beforeUpdate`/`beforeDelete`/`afterUpdate`/`afterDelete` with the
  // row-scoping predicate carried in `input` (there is no per-cardinality
  // `*Many` event — one write event covers one row or many, Salesforce's
  // bulk-first model). See #3195.

「row-scoping predicate carried in input」是假的,和 #5273 删掉的那句是同一条陈述,只是没写 .ast 后缀。引擎从不在写路径的 HookContext.input 上放谓词;它在引擎内部 OperationContext.ast(#2982)。

为什么 #5668 没覆盖到它

PR #5668(#5273 的落地)改的是同文件下方约 200 行的 HookContextSchema.input 那段契约表,并在 :340 起写下了正确措辞:

   * The row-scoping predicate is NOT reachable from `input` at all. It lives
   * on the engine-internal `OperationContext.ast` (#2982) so that the filters
   * middleware composes onto it …

git log -L 135,144:packages/spec/src/data/hook.zod.ts 显示这段枚举注释最后一次变动是 b49ccfdfe(#5306),#5668 未触及。于是同一个文件现在自相矛盾:枚举注释说谓词在 input,200 行外的契约表说它「NOT reachable from input at all」。

真值来源(已在 main 上,无需新测)

packages/objectql/src/hook-input-shape-contract.test.ts —— PR #5668 新增。本次实测全绿(11/11),其中直接钉死本句的三条:

✓ [#5273] a bulk write carries no `ast` on `input` > POSITIVE CONTROL — a read DOES carry `input.ast`
✓ [#5273] a bulk write carries no `ast` on `input` > `beforeUpdate` on a bulk write has no `ast` key
✓ [#5273] a bulk write carries no `ast` on `input` > `beforeDelete` on a bulk write has no `ast` key

阳性对照(读路径确实带 ast)同批为绿,所以「写路径 input 上没有谓词」是一次测量,不是空过。

为什么不是「注释小事」

和 #5273 分诊轮的判断同源:input 是 z.record(z.string(), z.unknown()),Zod 对键的存在什么都不校验,所以这段散文就是作者(人或 AI)能读到的唯一规格。一个枚举注释与同文件契约表互相矛盾时,读到哪一段是随机的。

建议

把 carried in input`` 改成与 :340 那段已落地措辞一致的说法(谓词不在 `input`,在引擎内部 `OperationContext.ast`),主句(批量写触发同名事件、没有 `*Many` 事件)保留。纯注释订正,`.describe()` 未涉及,预计不动生成物 —— 但按 AGENTS.md 需跑 `check:generated` 确认。

关联


Generated by Claude Code

Activity

  1. claude commented on Aug 6, 2026

    @claude
    Contributor

    分诊:入队 pm:queue,域 domain:spec。

    落点锚定:packages/spec/src/data/hook.zod.ts:135-140,origin/main @ 7adc841 逐字核实 —— 「row-scoping predicate carried in input」原句仍在枚举注释里,而同文件 200 行外的契约表已写着「NOT reachable from input at all」。文件在 packages/spec/** ⇒ domain:spec(同 #5920 的读法:域跟落点文件走,不跟「这是注释改动」的性质走)。

    分类理由:具体缺陷 + 落点精确到行 + 真值来源已在 main 上(hook-input-shape-contract.test.ts 的三条钉子,含阳性对照「读路径确实带 ast」)⇒ 无需新测,直接可派。正文对 #5668 为何没覆盖到它的考据(git log -L 135,144 显示该段最后一次变动是 b49ccfdfe/#5306)分诊侧复核认可。

    同文件协调:hook.zod.ts 无在飞 PR(最近一次触碰是 290d944/#5780);#5900 是同一条假话在 skills/** 那一面,本轮同批入队但文件面不相交,可并行。

    查重:三仓搜 HookEvent / carried in \input`/hook.zod.ts,命中 #5273(已修的那一半)/ #5670 / #5906(hook context alias 无生产者,另一轴,finding`);无重复入口。

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


    Generated by Claude Code

  2. os-zhuang commented on Aug 6, 2026

    @os-zhuang
    ContributorAuthor

    归拢证据:同文件 :416-417 的 JSDoc 也在指一个不存在的 hook 通道,建议一并记入本单修复面

    来自 #5720 的实现(PR #5938,docs-only)。本评论只留证据,不改代码 —— #5720 的派发面明确 packages/spec/** 零改动,而本单已经在改 packages/spec/src/data/hook.zod.ts 的注释、且是同一类「注释里的假句」,分两个 PR 碰同一个文件不如折进来。

    事实(对 origin/main 核实)

    packages/spec/src/data/hook.zod.ts:409-427,session.positions 的 JSDoc:

         * ⚠️ **Descriptive, NOT an authorization input.** A hook may READ this to
         * describe the caller — forwarding it as the sharing service's evaluation
    :416 * context (`services.sharing.canEdit(..., { positions })`, the shape both
    :417 * `content/docs/kernel/runtime-services/` pages teach), tailoring a
         * message, logging — and nothing more.
    

    两处不成立:

    1. services.sharing.… 在 hook 里拿不到。 这是 [docs] runtime-services 的 hook 示例在教 ctx.services —— hook 上下文从来没有这个键,照抄的 hook 会拒掉每一次写入 #5720 的实测结论:HookContext 由引擎逐键构造(object / event / input / session / provenance / user / api / transaction / ql,engine.ts 五处构造点同构),沙箱 buildHookSandboxContext 同样不产 —— 没有 services 键。把旧文档示例的 ctx: any 换成如实的 HookContext 后编译,直接 TS2339: Property 'services' does not exist on type …。所以一段 HookContext.session 的 JSDoc 用 services.sharing.canEdit(...) 举例「hook 可以怎么读 positions」,举的是 hook 做不到的动作 —— 与本单 :135-140 的 carried in input 同族(注释是这段的唯一规格,读到哪段随机)。

    2. 它引的文档形状已经不存在了。 PR docs(kernel): runtime-services 的 hook 示例改教真实通道 —— 去掉不存在的 ctx.services (#5720) #5938 按维护者 2026-08-06 裁定(改教 ctx.api,docs-only)改掉了这两页:

      • content/docs/kernel/runtime-services/examples.mdx 不再教 hook 调 canEdit(共享强制由 plugin-sharing 的引擎中间件按动词自动执行:update 走 canEdit、delete 走 canDelete,拒绝抛 FORBIDDEN,hook 手查是冗余);hook 示例改用 ctx.api.object(…) 做业务校验。
      • content/docs/kernel/runtime-services/sharing-service.mdx 的 Example 改为由持有该服务的代码调用(契约类型 ISharingService),并新增一节写明「Enforcement is automatic — do not re-check it in a hook」。

      即 :417 的「both pages teach」现在两页都不再那样教;这句在 docs(kernel): runtime-services 的 hook 示例改教真实通道 —— 去掉不存在的 ctx.services (#5720) #5938 合入后立刻变成过时引用。

    建议改法(仍是纯注释,主张不变)

    positions 的边界主张(描述性、非授权输入、不得用它自行判权)完全保留,只把举例从「hook 调 service」换成 hook 真实能做的读法,或改成通道中立的措辞;顺带别再把这两页当作该形状的出处。若要保留「转发给 sharing service 作为评估上下文」这个用途,应注明那是持有该服务的代码(插件 / 托管运行时绑定)的调用,不是 hook 的。

    预计同样不动 .describe() 与生成物,但按本单已有结论跑 check:generated 确认即可。

    关联


    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

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions