实现 #5670 时在同一文件(skills/objectstack-data/references/data-hooks.md)另一节发现的另一条已过期陈述。PD #10 单独记录:#5670 的派发面是 ctx.input.ast 那半句,本条是不同的断言(讲 hook condition 的 CEL 绑定),不搭车。
事实(对 origin/main = 5c94f833c 核实)
skills/objectstack-data/references/data-hooks.md,condition 的 CEL 绑定一节:
:254-257
- **`previous` is UNBOUND where there is no prior state**, and a reference to an
unbound root makes the whole condition unevaluable. That means: insert events
(`beforeInsert` / `afterInsert`) — write those over `record` alone — and
predicate (`multi: true`) bulk updates, where one write matches N rows and the
hook fires once, so there is no single prior record to bind.
:274-278
Practical consequence when authoring: spell keys against the object's **declared**
fields, and never reach for `previous` in a hook that can fire on insert or on a
`multi: true` write — that mistake used to cost you a hook that quietly never
ran, and now costs you every write the hook is attached to.
这两处对 before* 成立,对 after* 不成立 —— 它们正是 #5038 修掉的那个行为。
真值来源(已在 main 上)
packages/objectql/src/bulk-write-per-row-hooks.test.ts 的文件头把旧行为写成了历史:
* What used to happen instead, measured on #4862: `driver.updateMany` resolves
* an affected COUNT, the lifecycle hook fired ONCE, `hookContext.previous` was
* never assigned, and `record` degraded to the write's bare payload. So the
* transition condition the docs and ten showcase flows teach
* (`status == "done" && previous.status != "done"`) could not be evaluated on a
* bulk write …
以及它钉死的现行契约:
* 1. firing GRANULARITY — N matched rows ⇒ N dispatches, uniformly for every
* after-hook, never keyed on whether the condition text says `previous`;
* 2. the per-row BINDINGS — `previous` is that row's pre-image, `record` is
* that row's real state (not the bare payload), `input.id` names the row;
测试里的用例常量就是 skill 明令禁止的那种写法,并且是按行求值通过的:
const TRANSITION = 'record.status == "done" && previous.status != "done"';
describe('[#5038] each dispatch carries THAT row's previous / record')
it('`previous` is the row's own pre-write state, not a shared one')
it('is UNIFORM — a condition that never mentions `previous` fires per row too')
packages/spec/src/data/hook.zod.ts:352-357(PR #5668 落地文本)同样说明:
* Since #5038 (ADR-0058's bulk-write addendum) the `after*` events on a bulk
* write dispatch ONCE PER MATCHED ROW, each on a single-record-shaped
* context — `input.id` names that row, `previous` is its pre-image and
* `result` its post-state …
#5670 那句是让作者去读一个恒为 undefined 的字段;本条是反过来——skill 明令禁止平台现在已经支持、并且专门写了 pin 测试的那个模式(previous 的 transition condition)。按 skill 写的 AI 作者会为批量写路径手工绕开 transition 条件,或者干脆不给批量写挂 after* 审计/通知 hook —— 而 #5038 的原始动机恰恰是那类 automation「静默不发生」。
建议
把两处按 before / after 拆开重述:
严重程度请分诊轮判,我按发现原样提交。
关联
Generated by Claude Code
实现 #5670 时在同一文件(
skills/objectstack-data/references/data-hooks.md)另一节发现的另一条已过期陈述。PD #10 单独记录:#5670 的派发面是ctx.input.ast那半句,本条是不同的断言(讲 hookcondition的 CEL 绑定),不搭车。事实(对
origin/main=5c94f833c核实)skills/objectstack-data/references/data-hooks.md,condition的 CEL 绑定一节::254-257
:274-278
这两处对
before*成立,对after*不成立 —— 它们正是 #5038 修掉的那个行为。真值来源(已在 main 上)
packages/objectql/src/bulk-write-per-row-hooks.test.ts的文件头把旧行为写成了历史:以及它钉死的现行契约:
测试里的用例常量就是 skill 明令禁止的那种写法,并且是按行求值通过的:
packages/spec/src/data/hook.zod.ts:352-357(PR #5668 落地文本)同样说明:危害方向与 #5670 相反
#5670 那句是让作者去读一个恒为
undefined的字段;本条是反过来——skill 明令禁止平台现在已经支持、并且专门写了 pin 测试的那个模式(previous的 transition condition)。按 skill 写的 AI 作者会为批量写路径手工绕开 transition 条件,或者干脆不给批量写挂after*审计/通知 hook —— 而 #5038 的原始动机恰恰是那类 automation「静默不发生」。建议
把两处按 before / after 拆开重述:
before*(含批量)整批触发一次,previous无从绑定 —— 保留。after*在批量写上按匹配行派发,previous是该行 pre-image,record是该行真实状态,input.id命名该行([17.x] 批量写按行语义实现:hook 按行触发 + record-change trigger 按行绑定 previous/record(#4800/#4862 拍板 A) #5038)—— 因此 transition condition 在after*上是可用的。beforeInsert/afterInsert)previous无从绑定 —— 保留,与批量无关。严重程度请分诊轮判,我按发现原样提交。
关联
ctx.input.ast」——与 #5273 同一句假话,只是另外三个面 #5670(ctx.input.ast,已有 PR)previous不绑定、record只是裸 payload —— 官方文档与 showcase 正在教的 transition 起始条件在批量写上不成立 #4862 / 批量写上 hook 的 previous:B1 已拍板(fail loud 无例外,并入 #4775);本条转为「批量写 hook 按行触发」设计卡孵化点 #4800(旧行为实测)、Validation rules, requiredWhen and option visibleWhen are silently skipped on multi-row updates (options.multi) #3106(validation 谓词早于 hook 走按行)condition求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775(不可求值的 condition 会中止写入 —— 与本条同一节的措辞)Generated by Claude Code