Skip to content

data-hooks skill 仍按 #5038 之前教「multi: true 批量写只触发一次、previous 无从绑定」,并明令作者不要在可能批量触发的 hook 里用 previous #5900

Closed

Description

@os-zhuang

实现 #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 相反

#5670 那句是让作者去读一个恒为 undefined 的字段;本条是反过来——skill 明令禁止平台现在已经支持、并且专门写了 pin 测试的那个模式(previous 的 transition condition)。按 skill 写的 AI 作者会为批量写路径手工绕开 transition 条件,或者干脆不给批量写挂 after* 审计/通知 hook —— 而 #5038 的原始动机恰恰是那类 automation「静默不发生」。

建议

把两处按 before / after 拆开重述:

严重程度请分诊轮判,我按发现原样提交。

关联


Generated by Claude Code

Activity

  1. added
    domain:devxDomain lane — seat card indexed by label:pm:seat
    pm:queueReady for the PM dispatch loop
    on Aug 6, 2026
  2. claude commented on Aug 6, 2026

    @claude
    Contributor

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

    落点锚定:skills/objectstack-data/references/data-hooks.md ⇒ 按域表 skills/** 属 domain:devx。

    过时前提检查(有结果,但前提成立):#5670 的 PR #5901 已合入 main(c15fcee,改的是 ctx.input.ast 那半句)。本单点名的两处措辞在 origin/main @ 7adc841 上逐字核实仍未变(previous UNBOUND 那条 bullet 与「never reach for previous in a hook that can fire on … a multi: true write」那段都在)⇒ 缺陷成立;且同文件串行冲突已随 #5901 落地自动解除,本单现在无在飞同文件 PR。

    分类理由:具体缺陷 + 落点精确到段 + 真值来源已在仓里(bulk-write-per-row-hooks.test.ts 的按行派发钉子、hook.zod.ts:352-357 的 #5038 措辞)⇒ 直接可派。正文给的 before/after 三段重述可作为验收口径。严重度:skill 明令禁止平台已支持并有 pin 测试的模式,危害方向是让 AI 作者给批量写路径绕开 transition condition 或干脆不挂 after* 审计 hook —— 与 #5038 的原始动机(automation 静默不发生)同形,按本座位判读高于普通文档漂移,但仍属 pm:queue 而非插队。

    查重:三仓搜 data-hooks.md / multi: true / previous 绑定,命中 #5670(同文件另一条,已合)、#5899(同一句假话在 hook.zod.ts 的枚举注释里,本轮同批入队,文件不相交)、#4775;无重复入口。

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


    Generated by Claude Code

  3. self-assigned this
    on Aug 6, 2026
  4. hotlong commented on Aug 6, 2026

    @hotlong
    Contributor

    认领:PM 循环第 1 轮(devx 车道)
    会话:session_01BDmDsu2575gDxeMCxXhDE3
    分支:claude/issue-5900-data-hooks-bulk-after
    Worktree:objectstack-issue-5900
    域:domain:devx
    文件面:skills/objectstack-data/references/data-hooks.md、.changeset/(越界即停,报告说明)

    同文件前单 #5670 已随 PR #5901 合入,串行冲突解除;#5899(hook.zod.ts 同族)在 spec 座位,文件不相交。


    Generated by Claude Code

  5. hotlong commented on Aug 6, 2026

    @hotlong
    Contributor

    验收(devx 车道,会话 session_01BDmDsu2575gDxeMCxXhDE3):ACCEPT → PR #5989,已转 ready 并挂入合并队列。

    交付摘要:


    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-dispatchpm:queueReady for the PM dispatch loop

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions