docs: correct before* bulk dispatch to the per-row model on two customer pages - #18220
Merged
Merged
Conversation
…tomer pages Both pages still described the batch-dispatch model ADR-0058 Addendum II retired: `before*` hooks dispatch once per matched row and `previous` is bound per row, the premise the #16074 ruling was written on. Five sites, not the two quoted on the card — the neighbouring paragraphs carried the same retirement: - formulas.mdx scope table: `previous` was documented as unbound in `before*`. - formulas.mdx "Write it once": described the per-row model as an `after*` property only. - formulas.mdx ceiling paragraph: attributed the 10 000-row refusal to objects with `after*` hooks; one ceiling covers both phases and is checked before the first dispatch. - formulas.mdx warn Callout: stated `beforeUpdate`/`beforeDelete` fire once for the whole batch, that `previous` is unbound there, and that a `previous`-reading `before*` condition fails the write with a diagnostic naming the batch — that diagnosis is retired and has had no producer since the per-row dispatch landed. Replaced with the rule that is now load-bearing: the payload stays batch-scoped, so only a row-invariant-in-effect, in-place rewrite is in contract, backed by the engine's key-divergence refusal. - data-flow.mdx hook-order prose: "The `after*` events instead dispatch once per matched row" implied `before*` does not. Docs-only; `content/` is outside every package's `files[]`, so nothing published moves. Claude-Session: https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk Co-authored-by: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #17976
Clause-②: noProse-only realignment onto the already-declared contract: no accept/reject behaviour moves, no public surface widens, no schema or runtime code is touched.
What was wrong
Both pages still taught the batch-dispatch model that ADR-0058 Addendum II (#5574) retired.
before*hooks on a predicate (multi: true) write dispatch once per matched row, each on a single-record-shaped context carrying that row'sidandprevious— the premise the #16074 ruling was written on and PR #17249 landed into the contract.Verified on
origin/mainbefore rewriting, not taken from the card:packages/objectql/src/engine.ts—dispatchPerRowBeforeHooks, docblock clauses D1/D2: "one dispatch per matched row, on the SINGLE-RECORD shape …previousis that row's pre-image".packages/spec/src/data/hook.zod.ts— "Since beforeUpdate hook 在 multi:true 批量更新上拿不到 ctx.previous —— sys_fetch_previous_update 依赖 input.id;引擎已为校验取 priorRows 却不喂 hook(17.0.0-rc.2) #5574's engine half it fires them once PER MATCHED ROW, each on a single-record-shaped context carrying that row'sidandprevious".packages/objectql/src/bulk-write-per-row-hooks.test.ts—[#5574 / D1] a bulk write fires before-hooks once per matched row,[#5574 / D2] … binds THAT row's previous.docs/adr/0058-expression-and-predicate-surface.md— Amendment II.3 (hook.zod.tssays per-rowpreviouson a predicate write is for a guard to REFUSE with, not for a rewrite to aim by — three shipped provenance stamps aim by it, kept safe only by the engine's divergence refusal #16074), the row-invariant-in-effect rule.Five sites, not the two on the card
The card's repair asked for the neighbouring paragraphs. The sweep found three more, all in the same retirement:
formulas.mdxscope tableprevious"unbound inbefore*hooks, which fire once for the batch"formulas.mdx"Write it once"after*property onlyformulas.mdxceiling paragraphafter*hooks"formulas.mdxwarn CalloutbeforeUpdate/beforeDelete"fire once for the whole batch",previousunbound, and aprevious-readingbefore*condition "fails the write with an error naming the batch"data-flow.mdxhook-order proseafter*events instead dispatch once per matched row"The Callout was the worst of the five: besides the dispatch claim it advertised a diagnostic that no longer has a producer.
HookConditionLimitationwas retired at #5574 (packages/objectql/src/index.ts: "was exported here until #5574 and is RETIRED";hook-wrappers.ts: "its twoHookConditionLimitationmembers had no producer — retired under ADR-0049"). An author was being promised a guard-rail that was removed. It now carries the rule that actually binds today: the payload stays batch-scoped, so only a row-invariant-in-effect, in-place rewrite is in contract, backed by the engine'sMULTI_UPDATE_HOOK_KEY_DIVERGENCErefusal — including the two shapes that slip past that guard and stay out of contract.Sweep method and hit counts
Ran over both pages in full, zero results given a firing control:
grep -ni "batch"read once for the whole batch, a true statement about the row read)Query Optimizer … batch strategies)grep -nEi "unbound|not bound|once per|per matched row|before\*|beforeUpdate|beforeDelete|after\*"grep -nEi "previous"skills/and 1 incontent/docs/releases/, so the zero is a measurement, not a dead patternVerification
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 40 commands from the committed change set. All 40 run, 0 NOT-MEASURED, 0 UNRUN (--ranreconciliation). Four first refused with an unmet prerequisite (threeexit 3, oneexit 1whose text is a prerequisite refusal:client-reactnot built) — prerequisites built, then each re-run whole, not patched around:Representative verdict lines:
No package is in the diff, so there is no dependency-closure build to owe and no package test suite is affected. Control-character self-scan over both files: clean.
Changeset: skip
Measured, not assumed. Across the 70 packages declaring a
files[], neither edited path falls inside any of them;content/sits at the repo root, outside every package directory, and the root manifest isprivate: true. Positive control on the same matcher:packages/spec/dist/index.jsresolves to@objectstack/specviadist, andpackages/spec/README.mdviaREADME.md. Nothing published moves.Acceptance notes
To file — the same retirement on a PUBLISHED, governed surface, outside this card's file face. Two customer-shipped skill files carry the identical false claim, and both additionally restate the retired rejection-by-name diagnostic:
skills/objectstack-formula/SKILL.md:308— thepreviousbinding table: "unbound — abefore*hook fires ONCE for the whole batch … so there is no single prior record".skills/objectstack-data/references/data-hooks.md:240-248— the same, plus "Readingpreviouson that dispatch is rejected by name, and the rejection points you at the after-type event", which has had no producer since beforeUpdate hook 在 multi:true 批量更新上拿不到 ctx.previous —— sys_fetch_previous_update 依赖 input.id;引擎已为校验取 priorRows 却不喂 hook(17.0.0-rc.2) #5574.These contradict the declared contract in
packages/spec/src/data/hook.zod.ts(class b). They are strictly worse than the two pages fixed here —skills/**ships to customers and is read by AI authoring agents — but the file face for this card is the twocontent/docspages, so they are reported rather than touched. Dedupe words:skills before hook batch dispatch,objectstack-formula previous unbound,data-hooks.md per-row before,HookConditionLimitation retired skill,skill previous bulk write.Noted, not filed — already carried. #17975 covers the sibling residue in
packages/objectqlsource (engine.tsdocblock,bulk-write-per-row-hooks.test.ts:570), a different retired rule (the "not so a rewrite can be aimed" sentence) on a different lane. #17975 remains open; carrier named, nothing to add here.Noted, not filed.
formulas.mdxline 388's "read once for the whole batch" is correct as written (it describes the D7 single row read, not hook dispatch) and was deliberately left alone; it is the benign hit the sweep above accounts for. Carrier: none needed — no defect.Dedupe
The card's "no existing card found" rested on a semantic search that returned
total: Noneon its own control. Re-checked here:GET /search/issuesis proxy-blocked in this environment (HTTP 403, "sessions are bound to their configured repositories"), so that endpoint can never have produced a proven zero. Re-ran via repository-scoped endpoints instead — all 522 open issues and 7 open PRs enumerated and title-matched against the family; the only hits were #17976 itself and #17975 (disposed above).git ls-remote --heads originshows no other branch on either page.🤖 Generated with Claude Code
https://claude.ai/code/session_017ef78bLdybu3AffehKkhfk
Generated by Claude Code