Repository navigation
HookEvent 的枚举注释仍在说批量写把行级谓词「carried in input」—— 与 #5273 同一句假话,在同文件 40 行之外 #5899
Description
实现 #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` 确认。
关联
- 同源:
HookContext.input的契约注释声明批量写携带input.ast,引擎从不设置它(AST 只在 opCtx 上);同一张表也未描述 #5038 后 after 事件的按行形状 #5273 / PR docs(spec): HookContext.input 契约表改成引擎真正构造的形状 (#5273) #5668(同文件契约表那份已修)、hooks 技能与 API 文档仍在教「批量写的行级谓词在ctx.input.ast」——与 #5273 同一句假话,只是另外三个面 #5670(skills 与 content/docs 三个面) - 相关:Security: bulk (multi) update/delete skips OWD owner scoping on private objects — members can modify others' rows #2982(谓词走
opCtx.ast的原因)、Hook event taxonomy is over-specified: collapse 18 events → 8, make findOne fire find hooks, formalize bulk semantics #3195(为什么没有*Many事件)
Generated by Claude Code
Activity
claude commentedon Aug 6, 2026
分诊:入队
pm:queue,域domain:spec。落点锚定:
packages/spec/src/data/hook.zod.ts:135-140,origin/main@7adc841逐字核实 —— 「row-scoping predicate carried ininput」原句仍在枚举注释里,而同文件 200 行外的契约表已写着「NOT reachable frominputat 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
os-zhuang commentedon Aug 6, 2026
归拢证据:同文件
: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:两处不成立:
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同族(注释是这段的唯一规格,读到哪段随机)。它引的文档形状已经不存在了。 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确认即可。关联
ctx.services—— hook 上下文从来没有这个键,照抄的 hook 会拒掉每一次写入 #5720 / PR docs(kernel): runtime-services 的 hook 示例改教真实通道 —— 去掉不存在的ctx.services(#5720) #5938(hook 侧ctx.services的文档面,已 docs-only 修完;packages/spec/**按派发面未动)check:skill-examples应拒绝 os:check 块内把入参标any—— 标记了却零覆盖的空门 #5943(从 [docs] runtime-services 的 hook 示例在教ctx.services—— hook 上下文从来没有这个键,照抄的 hook 会拒掉每一次写入 #5720 拆出:check:skill-examples禁 os:check 块内入参标any—— 就是让上面那句TS2339一开始就该红的门)Generated by Claude Code