Repository navigation
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 |
… commits and ADR that decided them (objectstack-ai#20816) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the fourteenth stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/services/service-automation/src/**` and nothing else. By the seat's claim (`5905919247`), it is the largest package left in the lane, and it was free once the `engine.ts` work of the previous holder landed as `c8111a575`. Later stages cover the other packages, so this PR says `Part of` and 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 objectstack-ai#19123), by the method of stages 1 to 13 (the latest is PR objectstack-ai#20789, landed as `8acdae9d8`). That is **73 sites on 73 lines in 27 files, covering 14 numbers**: - 44 census sites (every census site this package has at the base); - 24 sites in test comments, which the census defers; 3 of their numbers (`objectstack-ai#11504`, `objectstack-ai#16709`, `objectstack-ai#8778`) stand only in test files here, and each was read on its own and answers 404; - 5 sites the census grammar cannot see: the `objectstack-ai#13398-class` spelling (a hyphen after the digits), 2 in `engine.ts` and 3 in test files. Each rewritten line now cites the record in this repository that decided what the line describes, and says in its own words what was decided: **15 distinct commit shas, plus ADR-0126 §7.2** on 2 lines, per ruling C's order (the ADR first where it records the decision; see the per-number table). No number was dropped. Only comments changed. Every touched source file keeps its line count (76 lines out, 76 in, over 27 files), so no line citation into these files moves. 73 of the 76 changed lines carried a dead citation; the other three are listed under Wordings. No code token moves (see the guard below). **No citation number is added.** The only tracker numbers on added lines are the live `objectstack-ai#14095`, `objectstack-ai#14456`, `objectstack-ai#8287` and the cross-repo `hotcrm#1206`, each once and each on the line it already stood on. No number is new to the diff, no number grew, and no PR number is the citation on an added line. 17 dead sites are left on purpose: 16 test titles and 1 runtime string (see the list below). One more file: a `patch` changeset for `@objectstack/service-automation`, because the rewritten prose ships (see Changeset below). ## Census: `service-automation`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only and unchanged. The count below is its `allocated-but-absent` findings under `packages/services/service-automation/`. 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 two of the 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, 11 and 13 met the same shape). | reading | tree | board | whole-repo `allocated-but-absent` | service-automation sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `8acdae9d8`, run 2026-09-30T07:03:52Z to 07:07:25Z | enumerated, 187 pages, frontier objectstack-ai#20798 (newest objectstack-ai#20796 before, objectstack-ai#20798 after) | 796 | **44** | 44 | 11 | 11 | | after | `3511e88cc` (comments and changeset), run 07:32:02Z to 07:35:27Z | enumerated, 187 pages, frontier objectstack-ai#20803 (newest objectstack-ai#20803 before and after) | 752 | **0** | 0 | 0 | 0 | | after, final head | head `a602c4003`, run 07:58:50Z to 08:02:19Z | enumerated, 187 pages, frontier objectstack-ai#20809 (newest objectstack-ai#20807 before, objectstack-ai#20809 after) | 752 | **0** | 0 | 0 | 0 | The whole-repo drop is 44, exactly this diff's census sites. The `resolves` tally is 33,096 in all three runs, and `resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (1,003) did not move either. No run was truncated or discarded: all three enumerations read 187 pages at the newest frontier. The seat's census counted 45 here at `6bff748b`. The difference is one `objectstack-ai#10243` comment line that `36d043be1` (PR objectstack-ai#20724) removed from `engine.ts` in the meantime; the other seven commits since then add or remove no dead site in this package's non-test `src`. **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) and `namesThisRepository` over every `.ts` file under `service-automation/src` (191 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 it `allocated-but-absent`, and alive when the gate's own census-scope extraction judged it and the census did not report it. 39 numbers are covered by neither, because they stand only in test files or strings here; each was read on its own through the read-only tools (2 answer 404: `objectstack-ai#11504` and `objectstack-ai#16709`; 18 answer 200 as issues; 19 answer 200 as pull requests, 18 of them also the squash suffix of a commit on `main`). | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `8acdae9d8` | 3,019 | **85** | 44 | 24 | 1 | 16 | | after, `a602c4003` | 2,951 | **17** | 0 | 0 | 1 | 16 | Its src-comment column equals the census's 44, which is the control on the second instrument. The 2,874 live citations and 60 cross-repo citations are the same in both readings, and the drop of 68 citations is exactly the rewritten sites the gate's grammar can see. A third, raw reading (every `#` followed by 2 to 6 digits, whatever surrounds it) finds 3,078 occurrences before and 3,005 after: the 68, plus the 5 `objectstack-ai#13398-class` sites only this reading sees. ## Per-number table Sites and files count every dead occurrence in scope at the base (comments and strings, tests included, and the 5 hyphen-joined sites). `rewritten / left` counts the sites rewritten and the sites left. Each anchor was read in its message and diff, not only its subject. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#11060` | 17/4 | 12/5 | `815585513` (PR objectstack-ai#11347): flow value expressions gain exactly `round` / `floor` / `ceil` / `abs` / `min` / `max`, mirrored 1:1 from the CEL stdlib, and an unknown name in call position becomes the loud `FlowExpressionFunctionError` instead of a silent `null`. Its message records the maintainer ruling on `objectstack-ai#11060` (2026-08-23, option A) and its diff names the number 18 times; `git blame` puts 11 of the 12 lines in it, and the twelfth (`end-node-refused-outcome.test.ts:333`) was written later. The lint lane's anchor for the same number | | `objectstack-ai#10243` | 14/5 | 13/1 | Three rungs, per line. **ADR-0126 §7.2** on 2 lines (`engine.ts:2315`, `flow-activation-ledger.test.ts:1024`): the durable ledger row replaces the process-local `flowEnabled` map, "retiring the objectstack-ai#10243 leak's mechanism rather than refining it" (`docs/adr/0126-packaged-metadata-customization-model.md:339-340`), which is what both lines say is the point. **`02b41232d`** (PR objectstack-ai#10996) on 9 lines, the ones that say the map "measured" leaking: the recorded measurement that tenant A's toggle answered 200 and tenant B and the platform admin read the flow back off. **`266436a7f`** (PR objectstack-ai#11660) on 2 lines: it implements the maintainer ruling on `objectstack-ai#10243` (option A, toggle joins the `manage_metadata` write set), which is the gate `flow-activation-store.ts:67` says the difference "turned on", and it wrote the "mitigating but not exculpating" record that `flow-activation-ledger.test.ts:272` quotes. The runtime lane's anchors for the same number | | `objectstack-ai#14419` | 14/4 | 11/3 | `c5a7448d5` (PR objectstack-ai#14948): `create_record` surfaces `engine.insert`'s classified `DUPLICATE_RECORD` code on the node result, the engine copies it onto `$error`, and `try_catch` preserves it across its own binding; deliberately scoped to `create_record`. Its message's last line names `objectstack-ai#14419` as its card, its diff names the number 13 times, and `git blame` puts 8 of the 11 lines in it (the other 3 were written by later commits it precedes). The spec lane's anchor for the same number | | `objectstack-ai#13398` | 9/4 | 9/0 | `e238c79f0` (PR objectstack-ai#13592): the earliest text in this repository that records the maintainer's published-sink ruling as made — raising a log level by widening a published sink that declares no `error` is "refused as actively harmful". Every line here states that ruling ("forbids raising a site to `error` where doing so means GROWING `error?` onto a published sink"). Stage 3's anchor, and the one the stage-3 review recommended for this package | | `objectstack-ai#16659` | 9/3 | 9/0 | `ecdfc9411` (PR objectstack-ai#17334): a time-triggered flow declares its acting organization on its start node, the engine lifts it onto the binding, and the triggers run the flow as it. `git blame` puts the 4 `engine.ts` / `suspended-run-store.ts` lines in it. The 5 lines in `notify-zero-delivery-visibility.integration.test.ts` were written by `ae6dcf6a4`, an ancestor of `ecdfc9411`, in the future tense ("once objectstack-ai#16659 lands"); they now name the landed commit (see Wordings). Stage 12's anchor | | `objectstack-ai#13648` | 9/3 | 6/3 | `7307191db` (PR objectstack-ai#14388): the public `resume` door normalises an absent signal to `{}`, and the chokepoints take a non-optional signal, so a signal-less resume is held to the screen contract. Its diff names `objectstack-ai#13648` 8 times; `git blame` puts 5 of the 6 lines in it | | `objectstack-ai#13681` | 6/4 | 4/2 | `18d816a50` (PR objectstack-ai#14452): the spec half of the contained-failure contract, which declares the run-level `failed`, the loop iteration through `try` / `catch`, and row identity on `$error`. Its subject names `objectstack-ai#13681`. The lines were written by the services halves (`d30ccb9bd`, `b7225477b`), both descendants. The spec lane's anchor for the same sentence ("one level up") | | `objectstack-ai#17123` | 4/2 | 2/2 | `ae6dcf6a4` (PR objectstack-ai#17339): a `notify` node reports `selected`, the recipients it addressed, so a zero-delivery run stops reading like a run with nothing to notify. Its diff writes `objectstack-ai#17123` 4 times, and `git blame` puts both lines in it. New to the sweep | | `objectstack-ai#8707` | 2/2 | 2/0 | `1408fe385` (PR objectstack-ai#8777): audit rows are stamped from the record's own organization. Stage 7's anchor | | `objectstack-ai#16709` | 2/1 | 1/1 | `8c7cca1ce` (PR objectstack-ai#16739): its item 1 pins the restore verb's drop of a stale hot consumed-suspension copy, and it created this test file. Stage 7's anchor | | `objectstack-ai#10062` | 1/1 | 1/0 | `fa5d137ab` (PR objectstack-ai#12942): published `src` may import only declared workspace dependencies; its diff moved this import to `@objectstack/metadata-core`. Subject names it | | `objectstack-ai#14390` | 1/1 | 1/0 | `9d7f7259f` (PR objectstack-ai#14603): both driver exits of `engine.update` answer a unique violation with the `DUPLICATE_RECORD` envelope. Subject names it. The runtime and rest lanes' anchor | | `objectstack-ai#11504` | 1/1 | 1/0 | `f90e82024` (PR objectstack-ai#12611): registers `FLOW_INPUT_SCHEMA_INVALID`, the line's subject; its diff names `objectstack-ai#11504` 5 times. The spec and runtime lanes' anchor | | `objectstack-ai#8778` | 1/1 | 1/0 | `7901b2dd2` (PR objectstack-ai#8905): the stamp-only `tenancy.organizationField` declaration the line names. Stage 6's anchor | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each of the 15), and all 15 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 landing `422db788a`, and the repository's root commit, which lies deeper than every anchor; the history is complete, `--is-shallow-repository` false, 15,178 commits). Each of the 14 numbers answers 404 on the issues endpoint, which serves pull requests too, read one by one. No ADR, `scripts/adr-anchors/` file or other `docs/` page records the decision of any of the 14 (a `docs/audits` census row names `objectstack-ai#10062` as a gate's origin, and `docs/qa` checklist rows name `objectstack-ai#10243`; neither is a decision record), except ADR-0126, which records the retirement `objectstack-ai#10243`'s measurement led to (§7.2) and the operator gate (§5). The `objectstack-ai#10243` lines that describe the measurement or the ruling cite those commits; the two lines that describe the retirement cite the ADR. ## Wordings to check - **Tag swaps in brackets or parentheses.** 「[objectstack-ai#16659]」 became 「[commit ecdfc94]」 on 4 lines, 「[objectstack-ai#10062]」 became 「[commit fa5d137]」, and every parenthesised `(#N)` became `(commit SHA)` in place. - **Headings.** 「objectstack-ai#14419 —」, 「objectstack-ai#17123 —」, 「objectstack-ai#11060 —」, 「objectstack-ai#16709 item 1 —」 at the head of a docblock or comment became 「Commit c5a7448 —」 and so on, the form stage 13 used. - **Two headers name the card or issue by its commit.** `notify-zero-delivery-visibility.integration.test.ts:4` now opens 「The card behind commit ae6dcf6:」, and `flow-field-expression-scale.integration.test.ts:4` 「The end-to-end oracle for the issue behind commit 8155855」. The docblocks below them go on speaking of 「the card's reading」, 「the card's ⭐」 and 「the third value-producing surface the issue names」; the headers keep that antecedent. This landed as its own commit, `a602c4003`, and every reading was re-run on it. - **`objectstack-ai#13398`.** 「a objectstack-ai#13398-class raise: that ruling forbids」 became 「a raise under the published-sink ruling (commit e238c79): that ruling forbids」; 「outside objectstack-ai#13398's class」 became 「outside the sink ruling's (commit e238c79) class」; 「(objectstack-ai#13398-class)」 became 「(the published-sink ruling, commit e238c79)」. - **`objectstack-ai#10243`.** 「the map objectstack-ai#10243 measured leaking」 became 「the map commit 02b4123 measured leaking」 (and alike on 9 lines); 「the whole point of objectstack-ai#10243」 became 「the whole point of ADR-0126 §7.2」; 「the one objectstack-ai#10243 turned on」 became 「the one the toggle ruling (commit 266436a) turned on」; 「objectstack-ai#10243 — the retired mechanism is GONE」 became 「ADR-0126 §7.2 — the retired mechanism is GONE」. - **`flow-activation-ledger.test.ts:271-272`.** 「the objectstack-ai#10243 map's "cold boot reads enabled: true again" was recorded as mitigating-but-not-exculpating」 became 「the retired map's … was recorded (commit 266436a) as mitigating-but-not-exculpating」. The anchor moved one line down, onto the verb it dates; `:272` is one of the three changed lines that carried no dead number. - **`crud-nodes.ts:439-441`, a statement that was stale when it landed.** 「`engine.update` still leaks the raw driver error (objectstack-ai#14390, not yet fixed) — those node results have nothing structured to surface yet」 became 「`engine.update` gained the same `DUPLICATE_RECORD` envelope for a unique violation (commit 9d7f725), but those node results are untouched here — this repair was scoped to `create_record` alone.」 `9d7f7259f` is an ancestor of `c5a7448d5`, the commit that wrote the sentence, so the update door already carried the envelope when "not yet fixed" landed; `c5a7448d5`'s message states the `create_record`-only scope. `:439` and `:441` are the other two changed lines with no dead number. - **`objectstack-ai#16659` in the notify test, future tense.** 「Why objectstack-ai#16659 landing does not close this」 became 「Why commit ecdfc94 does not close this」; 「the shape a scheduled flow has once objectstack-ai#16659 lands」 became 「the shape a scheduled flow takes under commit ecdfc94」; 「as it fires once objectstack-ai#16659 lands」 became 「as it fires under commit ecdfc94」. - **`objectstack-ai#13681`.** 「the measurement these tests reproduce (objectstack-ai#13681) found」 became 「the measurement behind commit 18d816a, reproduced here, found」: the measurement was the card's, and `18d816a50` is the contract that answered it. - **`objectstack-ai#11060`.** 「the LOUD half of the objectstack-ai#11060 ruling」 became 「the LOUD half of the ruling commit 8155855 records」; 「the exact silence objectstack-ai#11060 removes」 became 「the exact silence commit 8155855 removed」; 「before objectstack-ai#11060 this wrote the field as undefined」 became 「before commit 8155855 …」. - **`objectstack-ai#14419`.** 「a different door than objectstack-ai#14419's original bug」 became 「a different door than the bug commit c5a7448 fixed」; 「the whole point of objectstack-ai#14419」 became 「the whole point of commit c5a7448」. - **`objectstack-ai#16709`.** 「objectstack-ai#16709 item 1 —」 became 「Commit 8c7cca1, item 1 —」: the item numbering is that commit's own. ## The 17 sites left - **Test strings, 16 sites on 16 lines**, all `describe` / `it` titles, left as stages 1 to 13 left theirs: `contained-failure-visibility.test.ts:184` and `loop-dying-body-steps.test.ts:298` (`objectstack-ai#13681`); `create-record-duplicate-code.test.ts:51`, `:133`, `:255` (`objectstack-ai#14419`); `notify-zero-delivery-visibility.integration.test.ts:403`, `:535` (`objectstack-ai#17123`); `screen-resume-signal-less.test.ts:81`, `:134`, `:187` (`objectstack-ai#13648`); `template-functions.test.ts:44`, `:162`, `:202` and `flow-field-expression-scale.integration.test.ts:83` (`objectstack-ai#11060`); `flow-activation-ledger.test.ts:1027` (`objectstack-ai#10243`); `stale-hot-consumed-suspension.test.ts:157` (`objectstack-ai#16709`). - **One runtime string**: `builtin/template.ts:192`, the tail of the unknown-function refusal ("(Before objectstack-ai#11060 this name was silently rewritten to null …)"). A runtime string takes form D, not this card's comment-only form C, and the shrink-only `doc-authoring-prose-id` baseline already holds it (`template.ts`: `objectstack-ai#11060: 1`), so `check:doc-authoring` sees no growth. - No operator log string, assertion message, quoted maintainer ruling or generated file in this package carries a dead number. - **Outside `src`, listed and left, not edited in this stage:** the shipping `README.md` names no dead number (`objectstack-ai#4336`, `objectstack-ai#4414`, both live). `tsconfig.test.json` names the dead `objectstack-ai#13176` on 2 lines (5, 73) and the dead `objectstack-ai#14916` on 1 line (54); its other numbers are live. `vitest.config.ts` names only live numbers. The release-owned `CHANGELOG.md` names 7 of the 14 numbers on 13 lines. ## Mechanical guard: no code token moves The guard compares, base `8acdae9d8` against head, over all 27 touched `.ts` files: - **Reading 1**, the TypeScript parser's leaf nodes (a `forEachChild` walk, so comments are trivia and JSDoc nodes are never visited). String and template literals are therefore read in full. - **Reading 2**, the full token stream in parser context (a `getChildren` walk, so punctuation and keywords are included; JSDoc nodes skipped). Results: - Real run at the final head `a602c4003`: 47,982 base leaf tokens, **0 files with a token change** on either reading (exit 0). - Comment control in `engine.ts` (「which folds nothing and rejects nothing.」 to 「which folds nothing and refuses nothing.」): 0 files changed, as expected (exit 0). - Positive control, a code token renamed in `engine.ts` (`function applyResumeSignal(` to `function applyResumeSignalX(`): DIFFER on the identifier (exit 1). - Positive control, one digit changed inside a kept test title (`flow-activation-ledger.test.ts:1027`, `objectstack-ai#10243` to `objectstack-ai#10244`): 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 (`26e9be7dc174`, `e78e505fa3be`), with `git diff HEAD` empty and a clean tree afterwards. The controls ran on `3511e88cc` and again on `a602c4003`. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/service-automation` (`.changeset/20596-service-automation-provenance-anchors.md`) is included. Its body is stage 4's (`plugin-security`, the other stage with an ADR anchor), 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 (71 of 71 tasks, at `9b0d34213`, which holds every source-line change): `files[]` is `dist`, `README.md` and `CHANGELOG.md`, and the package is not private. - The declaration files `dist/index.d.ts` and `dist/index.d.cts` carry the rewritten docblocks at `engine.ts:354`, `:362`, `:529`, `:2112`, `:2119`, `:2315`, `:2331`, `:5224`, `:7984` and `flow-activation-store.ts:67` (e.g. `02b41232d` 4 times, `c5a7448d5` twice, `ecdfc9411` and `7307191db` once each). - The JS entries `dist/index.js` and `dist/index.cjs` carry the kept comments at `engine.ts:2315`, `:2331`, `:3565`, `:3581`, `:5224`, `:7984`, `notify-node.ts:449`, `suspended-run-store.ts:714` and `sys-automation-run.object.ts:112`. - The other rewrites are stripped by the bundle or sit in test files. - Positive controls, unchanged lines beside the rewrites, land exactly where their neighbours do: the line before `engine.ts:2112` and before `:2119` once in each declaration file and 0 in the JS; the `FlowActivationStore` docblock opener beside `flow-activation-store.ts:67` once in each declaration file; and the neighbours of three stripped rewrites (`crud-nodes.ts:438`, `suspended-run-store.ts:952`, `template.ts:24`) 0 everywhere. - A never-written negative phrase appears nowhere in `dist`. - None of the rewritten numbers is left in `dist`; the one `objectstack-ai#11060` in each JS entry is the kept runtime string at `template.ts:192`. The two commits after `9b0d34213` add the changeset and change two test-file comment lines, which the bundle does not include. ## Gates (final head `a602c4003`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` exits 0 (self-test, 114 cases, 8 batteries). `node scripts/check-issue-citations.mjs` exits 0: the diff-scoped run judged the 2 citations the change adds on its surface (the live `objectstack-ai#14095` and `objectstack-ai#8287`, each on the line it already stood on), and both resolve. - **Doc authoring:** `pnpm check:doc-authoring` exits 0 (the sibling-package prose-id baseline holds, no growth). - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `a602c4003` (after a fresh fetch) derived 63 commands. They are the 64 derived at dispatch less `pnpm check:error-code-casing`, which the derivation now lists as a roster family (below). - Each ran with its exit code captured before any pipe, and all 63 exit 0; none exited 3. - `--ran`, fed each command with its exit code, reports 63 run, 0 NOT MEASURED (a derived zero), 0 unrun, and exits 0. - A full `turbo run build` of `./packages/*` and `./packages/*/*` ran first under the shared verify lock (71 of 71 tasks, exit 0), so no gate hit an unbuilt workspace. - The same 63 had also all exited 0 on `3511e88cc` before the referent commit. - **Roster families the derivation lists outside its commands** (their rosters sit in directories this diff touches): `node scripts/check-changeset-fixed.mjs`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`, each exit 0. - **Tests and typecheck, under the verify lock, at `a602c4003`:** - `pnpm --filter @objectstack/service-automation test`: 157 files pass and 1,974 tests pass, every tracked test file under `src/`, the 16 touched ones included. - `pnpm --filter @objectstack/service-automation typecheck` exits 0 (`tsc --noEmit` plus the test-layer check on `tsconfig.test.json`). `tsc --listFiles` puts all 27 touched files in both programs. - **Lint, as a proven narrowing:** eslint with inline config disabled, over the 27 touched `.ts` files, gives 27 files, 0 errors and 0 warnings (its `--format json` output). All 27 are in eslint's own population (none reported ignored; a `dist` file, as the control, is ignored). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as its own lines 327-328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 28 changed files for control bytes finds none. ## Acceptance notes - **The gate-invisible spellings, grepped as the claim asked** (objectstack-ai#20636). At the base: `#N-word` 11 lines, of which the 5 `objectstack-ai#13398-class` lines were dead and are rewritten here; the 6 left are live (`objectstack-ai#5912` twice, `objectstack-ai#5048`, `objectstack-ai#5186`) or cross-repo (`hotcrm#548` twice). `#A/#B` 13 lines of the number-slash-number shape, plus 6 lines where the slash follows `ADR-0049` (`ADR-0049/objectstack-ai#1888`); every second number is live. `option #N` none. URL-spelled none. So the claim's 11 / 13 / 0 / 0 hold, and at the head the first is 6. - **A sibling of the `option #N` position, dormant.** `NON_CITATION_HEADS` also excuses a number after 「clause」, and `suspended-run-store-consume-log-cause.test.ts:408` reads 「The consequence clause objectstack-ai#6299 asked for」, a real citation of the live `objectstack-ai#6299`. Across `packages/**/src` there are 4 such sites, all in test files, a surface the gate defers anyway, so nothing dead hides there today. Recorded for objectstack-ai#20636's family, not filed. - **Two drifts left as they are, wording only.** `notify-zero-delivery-visibility.integration.test.ts:72` still calls the organization-less cron tick 「the shape production builds now」, written before `ecdfc9411` landed; it carries no number and lost no referent here. `suspended-run-store.test.ts:904` names `tenancy.organizationField`, which `502f179cc` has since retired from the authorable surface (stage 7 recorded the same drift in `plugin-approvals`). - **`update_record` still surfaces no `code`.** The corrected sentence at `crud-nodes.ts:439-441` is now true that `engine.update` carries the `DUPLICATE_RECORD` envelope while `update_record` does not surface it. That is an observation with no reported pull, recorded here. - **「The card」 phrases.** 134 comment lines in 50 files of this package speak of 「the card」 or 「this card」. They carry no number and neither instrument sees them. The two whose antecedent this diff would have removed are handled above; the rest are unchanged, as in stages 8 to 13. - **The census instrument did not truncate in this stage.** All three enumerations read 187 pages at the newest frontier. - **Anchors the next stages can reuse**, each checked here: `objectstack-ai#11060` → `815585513`; `objectstack-ai#10243` → `02b41232d` (the measurement), `266436a7f` (the ruling) or ADR-0126 §7.2 (the retirement), per line; `objectstack-ai#14419` → `c5a7448d5`; `objectstack-ai#13648` → `7307191db`; `objectstack-ai#13681` → `18d816a50`; `objectstack-ai#17123` → `ae6dcf6a4`; `objectstack-ai#14390` → `9d7f7259f`; `objectstack-ai#11504` → `f90e82024`; `objectstack-ai#10062` → `fa5d137ab`; and the reused `objectstack-ai#13398` → `e238c79f0`, `objectstack-ai#16659` → `ecdfc9411`, `objectstack-ai#16709` → `8c7cca1ce`, `objectstack-ai#8778` → `7901b2dd2`, `objectstack-ai#8707` → `1408fe385`. - **Base.** The branch is on `main` at `8acdae9d8`. `main` has since moved five commits (`41dcf1188`, `96e724475`, `df67985b0`, `cfa931535`, `5bed1f6ca`). None touches `packages/services/service-automation/src`, `scripts/check-issue-citations.mjs` or `.changeset/config.json`, and none is a path in this diff. Two of them move gate inputs (`scripts/doc-authoring-prose-id.baseline.json`, whose change is `driver-sql` rows only, and one `scripts/adr-anchors/` file for `packages/spec`), so those families ran here against the base's copies; this diff moves no code token and no runtime string, so nothing here can interact with them. No merge was taken; the merge queue rebuilds on the merged generation. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
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