Repository navigation
HookContext.input 的契约注释声明批量写携带 input.ast,引擎从不设置它(AST 只在 opCtx 上);同一张表也未描述 #5038 后 after 事件的按行形状 #5273
Description
Activity
发现分诊轮:晋级
pm:queue(摘finding,domain:spec沿用)。理由:一条明确写下、从未兑现且主动误导 hook 作者(含 AI 作者)的契约陈述,正是 ADR-0049 declared=enforced 要清的形状;修向采正文倾向 —— 改注释不改引擎(把批量写input描述改成引擎实际形状、说明 #5038 后 after 事件按行携带id、删掉input.ast那句;把过滤后 AST 交给 mutableinput的安全评估不在本单)。落点packages/spec/src/data/hook.zod.ts注释面,零 schema 变更,spec 车道串行队列消化;hook-wrappers.ts对ast的 wrapper 特判是否顺带清理由 dev 按 main 现状判。本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
分诊轮判级(spec 车道 PM,
session_018fxLGQdatPbBUvCgiVxg6D,2026-08-05):晋级(摘finding挂pm:queue)。理由:契约注释声明引擎从不兑现的形状(input.ast)+ #5038 后 after 事件形状缺描述 —— 契约散文对 AI 作者就是规格,假散文即缺陷;修正面小(注释/TSDoc + 可行处补 pin)。排存量批次。
Generated by Claude Code
PM 认领(spec 车道 PM,
session_018fxLGQdatPbBUvCgiVxg6D,2026-08-05)- 分支:
claude/issue-5273-hook-input-contract-prose;工作树:../objectstack-5273-hook-input(基于最新origin/main);域:domain:spec - 文件面:
packages/spec/src/data/hook.zod.ts(契约注释两处漂移:批量写input.ast虚假陈述、[17.x] 批量写按行语义实现:hook 按行触发 + record-change trigger 按行绑定 previous/record(#4800/#4862 拍板 A) #5038 后 after 事件按行形状)+ 配套 pin;⚠️ 该文件刚被 refactor(spec)!: 退役 HookContext.session.roles —— 声明过、被两条死分支读过、从未被生产 (#5050) #5621(session.roles 退役)改过,基于合并后代码工作 - 方向:按 issue 论证取「改注释」而非「改引擎」(把安全过滤后的 mutable AST 交给 hook 需单独裁,不顺手做);若实测发现
ctx.input.ast出现写路径消费者,停手回报 - 前置已解:refactor(spec)!: 退役 HookContext.session.roles —— 声明过、被两条死分支读过、从未被生产 (#5050) #5621 已 MERGED,hook.zod.ts 串行链释放
- 同批不相交:ADR-0105 D13's "scoping field" has no metadata home — the 0105↔0117 linkage gap #4611、两个 discovery 生产者都在线上返回 schema 未声明的顶层字段(scoping / features / endpoints),且 REST 形状永远无法通过 DiscoverySchema #4828、flow.test.ts 剩下的五类跑不通的 fixture 形状:
{节点id.字段}输出引用、assignment 无assignments包裹、legacy loop、字符串filter、brace-CEL 出边条件 #5500、ui/app.zod.ts导航项:4 条expanded别名在另外 8 个变体上把作者指向该变体同样拒绝的键(二次拒绝) #5555 - 派发:os-dev(opus)。更早不同 session 认领者优先,本认领作废。
Generated by Claude Code
- 分支:
验收通过(spec 车道 PM,
session_018fxLGQdatPbBUvCgiVxg6D,2026-08-06)—— PR #5668。- 契约表改为引擎真实形状:批量 before
{ id: undefined, data?, options }(键在值空——比 issue 说的「没有 id」更精确)、[17.x] 批量写按行语义实现:hook 按行触发 + record-change trigger 按行绑定 previous/record(#4800/#4862 拍板 A) #5038 后 after 按行携id、input.ast假句删除;insert: { doc }假行超范围一并修正(同表已知假行,留下即不可辩护,PR 正文显著标记)。 - pin 落 objectql(依赖方向正确),raw context + 声明式作者双视角,先证红三条 +
beforeFind阳性对照;hook-wrappers的ast特判实测保留(读路径活的)。 - 互锁返工(feat(lint): 禁止把引擎查询选项擦成
any,存量入计数 baseline 只减不增 (#4918) #5600 棘轮)按门禁处方 1 修复,基线未动;skip-changeset 生效(首跑红为 15 秒标签竞态,重跑即绿)。 - 跟进:hooks 技能与 API 文档仍在教「批量写的行级谓词在
ctx.input.ast」——与 #5273 同一句假话,只是另外三个面 #5670(同一假句在 skills/objectstack-data 与 data-flow.mdx 三处,已交 devx 面分诊)、trigger-record-change的input.doc防御性 alias 读的是一个没有生产者的键(引擎只建input.data) #5671(finding)。
转 ready + auto-merge,队列落地。
Generated by Claude Code
- 契约表改为引擎真实形状:批量 before
在 #5038(批量写按行语义)实现过程中发现,PD #10 单独记录。落在
packages/spec/**(spec 车道所有),#5038 未触碰该文件。事实(对
origin/main核实)packages/spec/src/data/hook.zod.ts里HookContext.input的契约注释逐条列出每种操作的input形状:并在下方重申「the row-scoping predicate is carried in
input.ast」。packages/objectql/src/engine.ts里input: { ast: ... }只出现两处,都是读路径(beforeFind/afterFind)。写路径构造的是:update():input: { id, data: opCtx.data, options: opCtx.options }delete():input: { id, options: opCtx.options }批量分支消费的 AST 是
opCtx.ast(#2982 为让中间件合成的行级过滤真正绑定 driver 而设的那条缝),它从不进hookContext.input。所以照契约写ctx.input.ast的 hook 作者在批量写上拿到的是undefined—— 而这恰恰是注释点名让人去读的那个字段。hook-wrappers.ts的installFlatInput把ast当作 wrapper key 特判(不下放到data),说明这个形状曾被当真过;今天在写路径上它是一个没有生产者的消费口。第二处漂移(#5038 之后)
同一张表把
update (bulk, multi:true)描述为单一形状。#5038 落地后,批量写的 after 型事件按匹配行派发,每行的input是单记录形状({ id, data, options });仍然整批触发一次的只有before*。下方那句「A bulk (multi: true) update/delete fires the SAMEbeforeUpdate/beforeDeleteevents as a single-id write」本身仍然成立(它只谈 before),但这张表读起来会让人以为 after 事件在批量写上也没有 per-rowid。Zod 契约本身不受影响 ——
input是z.record(z.string(), z.unknown()),开放形状,#5038 因此无需改 spec 即可实现按行语义(这一点已在 PR #5270 里核实过)。纯属注释与实现的漂移,不是 schema 变更。分级说明
标
finding:今天没有用户会撞上它——写路径 hook 作者拿 AST 的实际写法是ctx.input.options/ 引擎内部,仓内没有任何ctx.input.ast的写路径消费者(已 grep 确认)。但它是一条明确写下来、却从未兑现的契约陈述,正是 ADR-0049 「declared = enforced」路线要清掉的那一类;而且它会主动误导下一个照文档写 hook 的人(含 AI 作者)。建议
由 spec 车道一次改掉两处:把批量写的
input描述改成引擎实际给的形状({ id?, data?, options },并说明 after 事件在批量写上按行携带id),删掉「the row-scoping predicate is carried ininput.ast」这句,或者反过来让引擎真的把opCtx.ast放进批量写的input(后者要评估是否有人会改它 ——input是 mutable 的,把安全过滤后的 AST 交给 hook 改写需要单独想清楚,因此倾向改注释而不是改引擎)。关联
opCtx.ast的原因)packages/spec/src/data/hook.zod.ts(spec 车道:控制台保存筛选条件会 422:ViewFilterRuleSchema拒绝 filter-builder 盖的id,而 wire 成员的.strip()救不到嵌套块 #5114 / 重试策略在仓里有三份形状、两种拼法:#4661 收敛了共享同一导出名的两份,flow.errorHandling是匿名内联块,检查照不到它 #4964 / View labels never resolve a translation —resolveViewLabelreads two fields the served view document does not have #4854 / 未知键静默剥离仍是全仓默认:把 #3405 的 strict 收紧从一个 schema 推广到整个可授权面(ADR-0078 完整性闸门) #4001)