docs(trigger-record-change): re-anchor the dead tracker citations to the commits that decided them - #20789
Conversation
…the commits that decided them 29 comment and docblock lines in 5 files under packages/triggers/trigger-record-change/src cited tracker numbers that answer 404. Each now cites the commit that decided what the line describes and says so in its own words: - the flow-facing record decoupled from the batch payload -> 4f85e4d - the census of same-key / per-row-value beforeUpdate rewrites -> 03c1b0f - the post-hook half of the declared-field door -> b003cf2 - the shared expected read-refusal noise capture -> c28e4cf Comments only: every file keeps its line count and no code token moves. Test titles that carry a dead number are strings and are left. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
…nchoring The rewritten buildContext docblock and one inline comment ship in dist, so the package takes a patch changeset. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
The paragraph that now opens with the census commit said the source reading was recorded "on that card"; that census record carries the reading itself, so the line now says so. Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 1 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 263425d70bfdae2a07837418e10d63b018804a09 && git checkout 263425d70bfdae2a07837418e10d63b018804a09
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4b4ee88fbc530a155ce3ea0570ad81d905d3afbb bbfe7cb244fb2ec1bf78aa15eb437d6b8093f9f8 && git checkout -B drift-repro 4b4ee88fbc530a155ce3ea0570ad81d905d3afbb && git merge --no-ff bbfe7cb244fb2ec1bf78aa15eb437d6b8093f9f8
node scripts/docs-audit/affected-docs.mjs --json 4b4ee88fbc530a155ce3ea0570ad81d905d3afbb
|
Contract reviewServed-tier: ① Derived judgmentsRead against
② Semver level
③ Boundary flagsThe dev report (
Nothing else is escalated. Implemented-by: VERDICT: PASS |
Part of #20596
Clause-②: no
What changed
This is the thirteenth stage of the
domain:serviceslane of the dead-citation sweep. It coverspackages/triggers/trigger-record-change/src/**and nothing else. By the seat's claim (5904332626), it is the largest package in the lane that no in-flight work holds, whileservice-automationstays held behind #20726. Later stages cover the other packages, so this PR saysPart ofand the card stays open.Every comment or docblock site in scope that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on #19123), by the method of stages 1 to 12 (PR #20609 as
422db788a, PR #20626 asb80ab579d, PR #20634 as4d04b6be3, PR #20658 as9a4b2bb38, PR #20693 as0e9ad74fb, PR #20708 as9b384f63a, PR #20717 ascbaf04c1f, PR #20729 asd2820876f, PR #20737 as4dfff176b, PR #20742 as697845d19, PR #20757 ascba417a8f, PR #20775 as91e8fa194). That is 29 sites on 29 lines in 5 files, covering 3 numbers:#14744);#14744, 1 of#13657, and 5 of#11081.#11081stands only in a test file here, so the census never judged it; it was read on its own and answers 404.Each rewritten line now cites the commit in
origin/mainhistory that decided what the line describes, and says in its own words what was decided: 4 distinct shas. None of the three numbers has an ADR or ruling record of its own, so every anchor is a commit, per ruling C's order (see the per-number table). No number was dropped.Only comments changed. Every touched source file keeps its line count (30 lines out, 30 in, over 5 files), so no line citation into these files moves. 29 of the 30 changed lines carried a dead citation; the thirtieth keeps a referent the rewrite would otherwise have removed (see Wordings). No code token moves (see the guard below).
No citation number is added. The only tracker numbers on added lines are the live
#15356(3 times) and#8738(once), each on the line it already stood on. Added minus removed is negative for the three dead numbers and zero for every other number, and no number is new to the diff. No PR number is the citation on an added line.4 dead sites are left on purpose, all test titles (see the list below).
One more file: a
patchchangeset for@objectstack/trigger-record-change, because the rewritten prose ships (see Changeset below).Census:
trigger-record-change, before and afterInstrument (A1). The gate's own
node scripts/check-issue-citations.mjs --census --json, read-only and unchanged. The count below is itsallocated-but-absentfindings underpackages/triggers/trigger-record-change/. Each run counts as a reading only because its board frontier equals the newest issue or pull-request number, read by a separate request just before and just after the run. In all three runs a new number was opened while the run was enumerating; each frontier equals the newest number at the run's end, which is the criterion (stages 7 and 11 met the same shape).allocated-but-absent91e8fa194, run 2026-09-30T04:58:08Z to 05:01:28Zbb9d39a87(the comments commit), run 05:07:54Z to 05:11:48Zbbfe7cb24, run 05:39:10Z to 05:42:26ZThe before count matches the seat's census and A1 (6 sites, all
#14744:decouple-flow-record.ts×1 andrecord-change-trigger.ts×5). The whole-repo drop is 6, exactly this diff's census sites. Theresolvestally is 33,055 in all three runs, andresolves-as-pull-request(1,984) andcross-repo-unjudged(995) did not move either. No run was truncated or discarded: all three enumerations read 187 pages at the newest frontier.Supplementary instrument, the whole scope. The census does not read test files or strings, and this stage's scope includes test comments. So a second reading runs the gate's own exported
extractCitations(whole-file and comment-prose projections) andnamesThisRepositoryover every.tsfile undertrigger-record-change/src(14 files). It takes its verdicts from the before census's own board reading rather than from a second enumeration: a number is dead when that census reported itallocated-but-absent, and alive when the gate's own census-scope extraction (36,836 citations over 2,617 files) judged it and the census did not report it. Five numbers are covered by neither, because they stand only in test files: each was read on its own.#11081answers 404;#5715and#17982answer 200 as pull requests;#5785and#17985answer 200 as issues. The three dead numbers were also read one by one, and each answers 404.91e8fa194bbfe7cb24Its src-comment column equals the census's 6, which is the control on the second instrument. The 154 live citations are the same in both readings, and the drop of 29 citations is exactly the rewritten sites. A third, raw reading (every
#followed by 2 to 6 digits, whatever surrounds it) finds 195 occurrences before and 166 after. Beyond the gate's grammar it sees 9 tokens, the same at base and head: the second number of five#A/#Bpairs (only one is dead, the kept title atbefore-update-flow-payload-reach.test.ts:872), two/#3457/regex literals in assertions (live), and twoPD #12ordinals.Per-number table
Sites and files count every dead occurrence in scope at the base (comments and strings, tests included).
rewritten / leftcounts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject.#147444f85e4d11(PR #15475): the flow-facingrecord(and itsparamsalias) andpreviousare decoupled from the engine's own objects before a flow runs (decoupleFromEngineState: arrays, plain objects,Date,RegExp,MapandSetare copied, primitives, functions and other class instances shared), so a flow mutating a nested value in place no longer writes the batch payload that ADR-0058 Addendum II D3 shares across every row of amulti: trueupdate. A COPY rather than a FREEZE, becauseexpandDeclaredLookupswrites into the record it is handed. The engine's write shape is unchanged, and the same-key per-row-value residue is deliberately left unguarded. Its changeset records the maintainer's option-A ruling on#14744in its own words, its diff names#14744on 29 added lines, and it createddecouple-flow-record.tsand both of this package's pin files.git blameat the base puts every one of the 22 lines in this commit. New to the sweep#14744(the census line)03c1b0f6f(PR #15301): the census of same-key / per-row-VALUEbeforeUpdaterewrites, which found ZERO across 23 production registration sites and recorded thebuildContextoverlay conclusion as a source reading, not a measurement. Its message names#14744four times and states that result word for word.before-update-flow-payload-reach.test.ts:29describes this census, not the fix, so it cites the census commit, by the per-arm precedent of stages 5 and 9. The line was written by4f85e4d11, which descends from03c1b0f6f(merge-base --is-ancestorexit 0). New to the sweep#13657b003cf2e8(PR #13864): the post-hook half of the declared-field door, which refuses an undeclared field a before-hook writes, with one envelope on every driver. Its message names#13657seven times. The runtime and lint stages' anchor for the same number. The line was written by4f85e4d11, which descends from it (exit 0)#11081c28e4cfae(PR #11570): the two SqlDriver-backed fixtures stop blanket-silencing their kernel and carry@objectstack/runtime's shared expected-noise capture, which withholds only a declared table's ownno such tableline, forwards every other driver fault, and letsafterAllassert each channel fired. Its message names#11081, and its diff writes the five[#11081]tags in this very file;git blameat the base puts all five lines in it. Stage 7's anchor for the same numberEvery cited sha matches exactly one commit (
git rev-parse --disambiguate, count 1 for each of the 4), and all 4 are ancestors of the base (merge-base --is-ancestor, exit 0 for each; reverse leg, base against each anchor, exit 1 for each; control legs exit 0: stage 1's landing422db788a, and the repository's root commit, which lies deeper than every anchor; the history is complete,--is-shallow-repositoryfalse, 15,167 commits; the anchors lie 2,516, 2,585, 3,082 and 4,207 commits behind the base). Each of the 3 numbers answers 404 on the issues endpoint, which serves pull requests too.No ADR,
scripts/adr-anchors/file or otherdocs/page records any of the three as its decision.docs/audits/2026-09-multi-update-per-row-value-census.mdnames#14744, but it states that it is "measurement only — ships nothing … implements no guard", the input to a decision rather than its record, so the census line cites the commit that landed it.Wordings to check
multi: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744]」 became 「[commit 4f85e4d]」 atdecouple-flow-record.test.ts:4andbefore-update-flow-payload-reach.test.ts:805. 「[[finding] Five fixtures now blanket-silence their kernel, hiding unexpected ERROR output — the expected-noise capture pin is the strictly better shape #11081]」 became 「[commit c28e4cf]」 on 5 lines. 「(Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744, measured by Measure whether arecord-before-updateflow can reach the batch payload of amulti: trueupdate — the one in-repo door #14744's zero does not cover #15356)」 became 「(commit 4f85e4d, measured by Measure whether arecord-before-updateflow can reach the batch payload of amulti: trueupdate — the one in-repo door #14744's zero does not cover #15356)」 atdecouple-flow-record.ts:5. 「(Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744)」 became 「(commit 4f85e4d)」 atrecord-change-trigger.ts:340. 「(engine.update() has no declared-field door either — an undeclared key still reaches the driver, after the beforeUpdate hooks have run #8738 pre-hook / The undeclared-field door sits in FRONT of the hooks, so a key a beforeInsert hook writes has no door at all — and the drivers then disagree (memory stores it, SQL throws a raw statement error) #13657 post-hook)」 became 「(engine.update() has no declared-field door either — an undeclared key still reaches the driver, after the beforeUpdate hooks have run #8738 pre-hook / commit b003cf2 post-hook)」.:4and:859. 「[Measure whether arecord-before-updateflow can reach the batch payload of amulti: trueupdate — the one in-repo door #14744's zero does not cover #15356 measured, Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 closed]」 and 「[Measure whether arecord-before-updateflow can reach the batch payload of amulti: trueupdate — the one in-repo door #14744's zero does not cover #15356 measured it, Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 closed it]」 keep the live#15356and put the sha where the dead number stood.before-update-flow-payload-reach.test.ts:10. 「Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 then ruled the door closed」 became 「The option-A ruling (commit 4f85e4d) then closed the door」: the ruling is named in words beside the commit that carried it, whose changeset records it, the form stages 2, 6 and 7 used for a ruling.:22and:87. 「the Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 residue shape」 and 「the Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 pinned residue shape」 became 「the residue shape commit 4f85e4d pins」: the positive control that pins it is in that commit's diff.:23. 「because Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744's fix is about aliasing」 became 「because commit 4f85e4d fixes aliasing」: a commit fixes something, it does not have a fix.:29and:34, the census paragraph. 「Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744's census found」 became 「The census in commit 03c1b0f found」. That removed the referent of 「The conclusion recorded on that card」 five lines down, so:34became 「The conclusion recorded in that census」. This is the one changed line that carried no dead number. It is true as written: the census record03c1b0f6flanded carries that very conclusion, "On a source reading,buildContextmaterialises a new record object by overlay … a reading, not a measurement" (docs/audits/2026-09-multi-update-per-row-value-census.md:308-311).:455. 「that is precisely the blind spot Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 is weighing」 became 「… the blind spot commit 4f85e4d left unguarded」. The present tense described a card still being weighed; that commit's changeset says the key-set refusal "is untouched and is not widened — a hook that assigns the same key with per-row values still passes it".multi: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744」 / 「before Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744」 at:686,:705,:738,:924(the word 「Before」 sits at the end of the line above at:685and:704) became 「before commit 4f85e4d」: before that commit the flow-facing record shared its nested values with the payload, which is the reading each sentence quotes.multi: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 made」, 「Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 carries the fix」, 「Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 closed the door」 at:47,:95,:642, 「Until Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744」 atrecord-change-trigger.ts:341, 「and Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744.」 at:124, 「Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 — DECOUPLE」 at:453, 「(unchanged by Amulti: truehook that writes the SAME key with per-row VALUES still applies one row's value to every matched row — the residue #14099's key-set refusal deliberately leaves open #14744 —」 at:496: the number became the commit, and each sentence already states what the commit did.The 4 sites left
describe/ittitles carrying#14744, left as stages 1 to 12 left theirs:before-update-flow-payload-reach.test.ts:825and:872(the second number of[#15356/#14744], a spelling the gate's grammar cannot see),decouple-flow-record.test.ts:78and:136.src, the package'sCHANGELOG.mdnames#14744on 2 lines (467, 478). It is release-owned and deliberately not edited here (see Acceptance notes). The packageREADME.md, which also ships, names none of the three.Mechanical guard: no code token moves
The guard compares, base
91e8fa194against head, over all 5 touched.tsfiles:forEachChildwalk, so comments are trivia and JSDoc nodes are never visited). String and template literals are therefore read in full.getChildrenwalk, so punctuation and keywords are included; JSDoc nodes skipped).Results:
bbfe7cb24: 6,110 base leaf tokens, 0 files with a token change on either reading (exit 0).record-change-trigger.ts(「reach nothing outside its own run.」 to 「reach nothing beyond its own run.」): 0 files changed, as expected (exit 0).record-change-trigger.ts(params: isolatedRecord,givenas typeof isolatedRecord): DIFFER, 953 to 954 leaf tokens and 2,130 to 2,133 full tokens (exit 1).decouple-flow-record.test.ts:78,#14744to#14745): DIFFER on the string literal (exit 1).Every mutation went through
scripts/ablation-replace.mjs(wrap mode) under a shell trap that restores by absolute path, and each landed (anchor 1 to 0, blob changed). Each restore was proven byte-identical to the HEAD blob (f3235a962fc5,9a8bf70abbcc), withgit diff HEADempty and a clean tree afterwards.Changeset
This change ships bytes, so a
patchchangeset for@objectstack/trigger-record-change(.changeset/20596-trigger-record-change-provenance-anchors.md) is included. Its body is stage 12's, word for word, with the package name changed.Measured on the built package (A3), after a full workspace build in which this package was a cache miss:
files[]isdist,README.mdandCHANGELOG.md, and the package is not private.4f85e4d11appears 3 times in each ofdist/index.jsanddist/index.mjs: thebuildContextdocblock (record-change-trigger.ts:340and:341) and the inline comment at:496, which the bundle keeps.dist/index.d.tsanddist/index.d.mts: the samebuildContextdocblock.dist: their lines are in test files. The rewrites atrecord-change-trigger.ts:124and:453anddecouple-flow-record.ts:5are stripped by the bundle.:341once in all four files, the line before:496once in each JS file and 0 in the declaration files, and the neighbours of the three stripped rewrites 0 everywhere.dist.dist.Gates (final head
bbfe7cb24)pnpm check:issue-citationsexits 0 (self-test, 114 cases, 8 batteries).node scripts/check-issue-citations.mjsexits 0: the diff-scoped run judged 1 added citation across 2 files, the live#15356atdecouple-flow-record.ts:5, and it resolves.pnpm check:doc-authoringexits 0 (the sibling-package prose-id baseline holds, no growth).node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackatbbfe7cb24(after a fresh fetch) derived 59 commands. They are all 53 derived at dispatch, pluscheck:engine-double-contract,check:objectql-double-limit,check:query-options-erasure,check:type-check-coverage,check:type-check-debtandcheck:where-matcher.--ran, fed each command with its exit code, reports 59 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0.turbo run buildof./packages/*and./packages/*/*ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace.node scripts/check-changeset-fixed.mjs,pnpm check:authz-resolver,pnpm check:error-code-casingandpnpm check:filter-alias-parity, each exit 0.bbfe7cb24:pnpm --filter @objectstack/trigger-record-change test: 10 files pass and 101 tests pass.vitest list --filesOnlynames 10 files, all the tracked test files, the 3 touched ones included.pnpm --filter @objectstack/trigger-record-change typecheckexits 0.tsc --listFilesontsconfig.test.jsonholds all 14 files undersrc/, and ontsconfig.jsonthe 4 non-test files, so all 5 touched files are compiled..tsfiles, gives 5 files, 0 errors and 0 warnings (its--format jsonoutput). All 5 are in eslint's own population (isPathIgnoredis false for each; adistfile, as the control, is ignored).eslint.config.mjsnever enables type-aware linting (noparserOptions.project, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-widepnpm lintis CI's run.pnpm check:nul-bytesexits 0, and a raw scan of the 6 changed files for control bytes finds none.Acceptance notes
CITATION_RErefuses a hyphen after the digits and a/before the#,NON_CITATION_HEADSexcuses a number after the word 「option」, and a URL-spelled link carries no#at all (check-issue-citations closeout (extractor spellings):CITATION_RErefuses a hyphen after the digits, so a dead#N-wordcitation (#13398-class) is invisible to the diff gate and to the census #20636). In this package, at the base and at the head:#N-wordnone,#A/#B5 lines,option #Nnone, URL-spelled none, which is the claim's 0 / 5 / 0 / 0. Of the five#A/#Bsecond numbers (#4251twice,#5038,#4649,#14744), only#14744is dead, and it stands in a kept test title.CHANGELOG.mdis left.packages/triggers/trigger-record-change/CHANGELOG.mdnames#14744on 2 lines. It is release-owned (AGENTS.md, Documentation Guardrails), a deferred surface of the citation gate, and ⛔ not part of this stage.record-change-trigger.ts:239's operatorwarnfor an array-form trigger event ends with the live#3457, and two tests assert the message carries it. That is form D, not this card's comment-only form C, and the shrink-onlydoc-authoring-prose-idbaseline already holds it (record-change-trigger.ts:#3457: 1), socheck:doc-authoringsees no growth.#14744→4f85e4d11(the decoupling) or03c1b0f6f(its census), both new to the sweep;#13657→b003cf2e8and#11081→c28e4cfaereuse the runtime and lint stages' anchor and stage 7's.mainat91e8fa194.mainhas since moved six commits (cd6d8a5ff,1bcba27d2,a3d7588b5,9ad654487,274e16271,085ca6bc1). Their 50 files touch nothing undertrigger-record-change, norscripts/check-issue-citations.mjs,.changeset/config.jsonor thedoc-authoring-prose-idbaseline, and none is a path in this diff. Three of them are gate inputs (scripts/engine-double-contract.pinned.json,scripts/objectql-double-limit.baseline.json,scripts/sdui-manifest.record.json), so those families ran here against the base's copies; this diff moves no code token, so nothing here can interact with them. No merge was taken; the merge queue rebuilds on the merged generation.Generated by Claude Code