Skip to content

docs(service-automation): re-anchor the dead tracker citations to the commits and ADR that decided them - #20816

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20596-service-automation-citations
Sep 30, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20596-service-automation-citations

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #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 #19123), by the method of stages 1 to 13 (the latest is PR #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 (#11504, #16709, #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 #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 #14095, #14456, #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 #20798 (newest #20796 before, #20798 after) 796 44 44 11 11
after 3511e88cc (comments and changeset), run 07:32:02Z to 07:35:27Z enumerated, 187 pages, frontier #20803 (newest #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 #20809 (newest #20807 before, #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 #10243 comment line that 36d043be1 (PR #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: #11504 and #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 #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
#11060 17/4 12/5 815585513 (PR #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 #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
#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 #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 #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 #11660) on 2 lines: it implements the maintainer ruling on #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
#14419 14/4 11/3 c5a7448d5 (PR #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 #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
#13398 9/4 9/0 e238c79f0 (PR #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
#16659 9/3 9/0 ecdfc9411 (PR #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 #16659 lands"); they now name the landed commit (see Wordings). Stage 12's anchor
#13648 9/3 6/3 7307191db (PR #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 #13648 8 times; git blame puts 5 of the 6 lines in it
#13681 6/4 4/2 18d816a50 (PR #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 #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")
#17123 4/2 2/2 ae6dcf6a4 (PR #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 #17123 4 times, and git blame puts both lines in it. New to the sweep
#8707 2/2 2/0 1408fe385 (PR #8777): audit rows are stamped from the record's own organization. Stage 7's anchor
#16709 2/1 1/1 8c7cca1ce (PR #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
#10062 1/1 1/0 fa5d137ab (PR #12942): published src may import only declared workspace dependencies; its diff moved this import to @objectstack/metadata-core. Subject names it
#14390 1/1 1/0 9d7f7259f (PR #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
#11504 1/1 1/0 f90e82024 (PR #12611): registers FLOW_INPUT_SCHEMA_INVALID, the line's subject; its diff names #11504 5 times. The spec and runtime lanes' anchor
#8778 1/1 1/0 7901b2dd2 (PR #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 #10062 as a gate's origin, and docs/qa checklist rows name #10243; neither is a decision record), except ADR-0126, which records the retirement #10243's measurement led to (§7.2) and the operator gate (§5). The #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

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 (#13681); create-record-duplicate-code.test.ts:51, :133, :255 (#14419); notify-zero-delivery-visibility.integration.test.ts:403, :535 (#17123); screen-resume-signal-less.test.ts:81, :134, :187 (#13648); template-functions.test.ts:44, :162, :202 and flow-field-expression-scale.integration.test.ts:83 (#11060); flow-activation-ledger.test.ts:1027 (#10243); stale-hot-consumed-suspension.test.ts:157 (#16709).
  • One runtime string: builtin/template.ts:192, the tail of the unknown-function refusal ("(Before Flow field expressions can call no function but NOW()/TODAY() — every other identifier is rewritten to null, so a computed money value can never be rounded to its field's declared scale #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: #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 (#4336, #4414, both live). tsconfig.test.json names the dead #13176 on 2 lines (5, 73) and the dead #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, #10243 to #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 #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 #14095 and #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 (check-issue-citations closeout (extractor spellings): CITATION_RE refuses a hyphen after the digits, so a dead #N-word citation (#13398-class) is invisible to the diff gate and to the census #20636). At the base: #N-word 11 lines, of which the 5 #13398-class lines were dead and are rewritten here; the 6 left are live (#5912 twice, #5048, #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/#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 finding(service-automation): engine.ts 还剩三处同形的 warn message 拼接 —— forgetSuspendedRun / cancelRun / listSuspendedRunsDurable,是 #5912+#6230 之后该文件的最后一批 #6299 asked for」, a real citation of the live #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 check-issue-citations closeout (extractor spellings): CITATION_RE refuses a hyphen after the digits, so a dead #N-word citation (#13398-class) is invisible to the diff gate and to the census #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: #11060 → 815585513; #10243 → 02b41232d (the measurement), 266436a7f (the ruling) or ADR-0126 §7.2 (the retirement), per line; #14419 → c5a7448d5; #13648 → 7307191db; #13681 → 18d816a50; #17123 → ae6dcf6a4; #14390 → 9d7f7259f; #11504 → f90e82024; #10062 → fa5d137ab; and the reused #13398 → e238c79f0, #16659 → ecdfc9411, #16709 → 8c7cca1ce, #8778 → 7901b2dd2, #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

… commits and ADR that decided them

Comment and docblock prose only, under packages/services/service-automation/src.
73 dead sites on 73 lines in 27 files (44 census sites, 24 test-comment
sites, and 5 hyphen-joined sites the census grammar cannot see) now cite
the commit in this repository's history that decided what the line
describes, or ADR-0126 section 7.2 where that ADR records the decision,
and say in their own words what was decided.

Anchors: c5a7448, 9d7f725, ae6dcf6, ecdfc94, 7307191,
18d816a, 8155855, f90e820, 02b4123, 266436a, e238c79,
fa5d137, 1408fe3, 7901b2d, 8c7cca1; and ADR-0126 section 7.2.

Three changed lines carry no dead number: two in crud-nodes.ts correct
a statement that was stale when it landed (the update door already
answered a unique violation with the DUPLICATE_RECORD envelope), and
one in flow-activation-ledger.test.ts carries the anchor its reflowed
neighbour lost. Every file keeps its line count; no code token moves.

Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H
Co-authored-by: Claude <noreply@anthropic.com>
…ce comments

The rewritten docblocks and inline comments ship in dist (index.js,
index.cjs, index.d.ts, index.d.cts), so the package owes a patch note.

Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H
Co-authored-by: Claude <noreply@anthropic.com>
…tten test headers named

Two test-file docblock headers that named a dead tracker number were
rewritten to a commit; later lines in the same docblock still speak of
"the card" / "the issue" that number named. Each header now names that
card or issue by the commit behind it, so the later lines keep their
antecedent. Both lines already carried a rewritten dead site; no other
line changes and no code token moves.

Claude-Session: https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 30, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/service-automation, touching 17 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/services/service-automation/src/flow-activation-store.ts, packages/services/service-automation/src/flow-precedence.ts, packages/services/service-automation/src/index.ts), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/declarative-endpoints.mdx (via /api/v1/automation/:name/trigger (route, bridged from symbol toggleFlow — its route source's handler names it))
  • content/docs/automation/flows.mdx (via /api/v1/automation/:name/trigger (route, bridged from symbol toggleFlow — its route source's handler names it))
  • content/docs/permissions/system-context.mdx (via registerCrudNodes (symbol, a top-level function))
  • content/docs/protocol/kernel/http-protocol.mdx (via /api/v1/automation/:name/trigger (route, bridged from symbol toggleFlow — its route source's handler names it))

⛔ 2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v16.mdx (via AutomationEngine (symbol, a top-level class))
  • content/docs/releases/v17/17-0.mdx (via AutomationEngine (symbol, a top-level class))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/services/service-automation/src/flow-activation-store.ts, packages/services/service-automation/src/flow-precedence.ts, packages/services/service-automation/src/index.ts) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 6 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 4cc5bcd858a71d17e8b7f3e1224b91c1331efc3f → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 6500f191b9e0b0e5d9493a501541759b084295d0 — the merge of head a602c4003cd21afb3daa1a0f20fd14eb1a53499d into base 4cc5bcd858a71d17e8b7f3e1224b91c1331efc3f, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 6500f191b9e0b0e5d9493a501541759b084295d0 && git checkout 6500f191b9e0b0e5d9493a501541759b084295d0
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4cc5bcd858a71d17e8b7f3e1224b91c1331efc3f a602c4003cd21afb3daa1a0f20fd14eb1a53499d && git checkout -B drift-repro 4cc5bcd858a71d17e8b7f3e1224b91c1331efc3f && git merge --no-ff a602c4003cd21afb3daa1a0f20fd14eb1a53499d

node scripts/docs-audit/affected-docs.mjs --json 4cc5bcd858a71d17e8b7f3e1224b91c1331efc3f

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 4cc5bcd858a71d17e8b7f3e1224b91c1331efc3f → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: a602c4003cd21afb3daa1a0f20fd14eb1a53499d
Local-runs: none

① Derived judgments

Read against main at the merge-base 8acdae9d8 (stage 13's landing); the tree and the board read 2026-09-30T08:58Z. origin/main stands ten commits past that base (at 261c529f0); none of the ten touches a path in this diff or anything under packages/services/service-automation, and the one citation-gate input they move (scripts/doc-authoring-prose-id.baseline.json, in df67985b0) changes no service-automation row, so the net diff against main is the merge-base diff: 28 files, +87/−76 — 27 source files under packages/services/service-automation/src/** (11 modules, 16 test files) and one changeset. The head a602c4003 adds two test-file header lines on top of 3511e88cc (the changeset) and 9b0d34213 (every other source line).

  • Accept-set: no change — right. No Zod schema, REST handler, query-parameter set, refusal text, log text or runtime string moves. 76 source lines out, 76 in; every one of the 152 changed source lines opens with a comment marker after whitespace (//, *, /**), 0 fall outside one. Each of the 27 touched source files has additions equal to deletions (checked file by file, base against head: 27 of 27 equal line counts), so no line citation into these files moves. The dev's parser token guard (0 files with a token change; both positive controls DIFFER) says the same and is not repeated here.
  • Public surface: no change — right. No export added, removed or renamed; no packages/spec file touched, so no generated artifact is owed.
  • Published bytes: changed — right, and it decides ②. @objectstack/service-automation (17.5.0, no private key, files = dist, README.md, CHANGELOG.md, a member of the changeset fixed group) emits declarations, and rewritten docblocks sit on declarations src/index.ts exports: NodeExecutionResult.code (engine.ts:354, :362), FlowTriggerBinding.organization (:529), the FlowActivationStore port docblock (:2112, :2119), AutomationEngine's activation and resume docblocks (:2315, :2331, :5224, :7984) and InMemoryFlowActivationStore (flow-activation-store.ts:67) — all five names are on the entry's export lines. So dist/index.d.ts changes. The dev's A3 build reading over all four dist entries, with positive and negative controls, says the same; this record does not repeat the build.
  • The 14 numbers are dead — right. Each of #8707 #8778 #10062 #10243 #11060 #11504 #13398 #13648 #13681 #14390 #14419 #16659 #16709 #17123 answers 404 on the issues endpoint, which serves pull requests too; the live controls #14095, #14456, #8287 (the three repository numbers kept on added lines) and #15617 answer 200.
  • The 15 commit anchors — each right. Each abbreviated sha resolves to exactly one commit (rev-parse --disambiguate, count 1 for all 15) and is an ancestor of the base (merge-base --is-ancestor, exit 0 for all 15; the history is complete, --is-shallow-repository false). Each names the number it replaces in its subject or message, or in its diff, and the decision the rewritten line states is the commit's. #11060 → 815585513: its message records the maintainer ruling on #11060 (2026-08-23, option A) — exactly round / floor / ceil / abs / min / max, mirrored 1:1 from the CEL stdlib, and the loud FlowExpressionFunctionError for an unknown name in call position. #14419 → c5a7448d5: "Fixes automation: create_record collapses the engine's DUPLICATE_RECORD envelope to a string, so a flow's try_catch / fault edge still cannot tell "already there" from "the store is down" #14419"; create_record surfaces DUPLICATE_RECORD, the engine copies it onto $error, try_catch preserves it, and its message states the create_record-only scope. #13648 → 7307191db: a signal-less resume held to the screen-input contract; its diff names #13648 8 times. #13681 → 18d816a50: subject names it; the spec half of contained-failure visibility (run-level failed, iteration through try / catch, $error row identity). #16659 → ecdfc9411: subject names the acting-organization declaration a time-triggered flow now carries. #17123 → ae6dcf6a4: a notify node reports selected; its diff names #17123 4 times, and it is an ancestor of ecdfc9411, which is why five of its lines were in the future tense. #8707 → 1408fe385: subject names it; audit rows stamped from the record's own organization. #16709 → 8c7cca1ce: its message opens "Three residues of the [Decision] inspectStrandedRequests now over-reports: it keys on status === 'failed' while the platform gained an authoritative strand discriminator — a cascade-failed run the engine calls NOT stranded is reported as one #15358 contract review ([finding] Three residues from the #15358 contract review: an unpinned stale-hot drop, a thrown third read that leaves stranded, and a malformed host verdict that aborts the whole scan #16709)", and its item 1 is the test-only pin of the restore verb dropping a stale hot consumed-suspension copy — the rewritten header's subject. #10062 → fa5d137ab: subject names it; its diffstat touches service-automation/src/flow-precedence.ts, the file rewritten. #14390 → 9d7f7259f: subject names it; engine.update answers a driver unique violation with the DUPLICATE_RECORD envelope on every driver. #11504 → f90e82024: subject registers FLOW_INPUT_SCHEMA_INVALID, the line's subject; its diff names #11504 5 times. #8778 → 7901b2dd2: subject names it; the stamp-only tenancy.organizationField declaration. #10243 → 02b41232d on the nine measurement lines (its message is the recorded measurement: tenant A's toggle answered 200 and tenant B and the platform admin read the flow back off) and 266436a7f on the two ruling lines (its message implements the maintainer ruling of 2026-08-23 on #10243, option A, toggle joins the manage_metadata write set; its diff writes "Mitigating but not exculpating: the override is process-local, so a cold boot reads enabled: true again" three times, so flow-activation-ledger.test.ts:272's "recorded (commit 266436a)" names the commit that wrote the phrase the line quotes). #13398 → e238c79f0: its diff carries "widening a published sink — refused as actively harmful by the maintainer's [Decision] plugin-sharing's refused-backfill report lands at warn where AGENTS.md puts it at error — and the card that was supposed to carry the level is CLOSED #13398 ruling", the earliest text in the tree that records the ruling as made, which the stage-3 record found and recommended for this package; every rewritten line says "the published-sink ruling (commit e238c79)", claiming that the commit records the ruling, not that it made it.
  • The ADR anchor — right, and the ruling's preferred rung. docs/adr/0126-packaged-metadata-customization-model.md §7.2 (:339-340) reads "The durable ledger row replaces the process-local flowEnabled map as the sanctioned off-switch for packaged flows, retiring the Decide whether POST /api/v1/automation/:name/toggle belongs in the manage_metadata write set — it mutates flow enablement with no authoring capability #10243 leak's mechanism rather than refining it", which is what the two lines that now cite it state (engine.ts:2315, the ledger-is-not-the-map distinction; flow-activation-ledger.test.ts:1024, the retired mechanism is gone). The measurement and ruling lines cite the commits, which the ADR does not record — per line, the per-arm precedent of stages 5, 9 and 13.
  • The three corrected lines with no dead number — each right. crud-nodes.ts:439-441: the removed sentence said engine.update "still leaks the raw driver error (engine.update still leaks the raw driver unique-violation error — the same defect one verb over from the insert door, with the whole UPDATE statement as its message #14390, not yet fixed)"; 9d7f7259f is an ancestor of c5a7448d5, the commit that wrote that sentence (merge-base --is-ancestor, exit 0), so it was stale when it landed. The new sentence — the update door gained the envelope, these node results are untouched, the repair was scoped to create_record alone — matches c5a7448d5's own message, and update_record's catch at the head still returns success: false with the collapsed string and no code, so "untouched" is true of the head. flow-activation-ledger.test.ts:272 carries the anchor its reflowed neighbour lost. Every file keeps its line count.
  • Citation accounting — right. Over the diff: the 76 removed source lines carry 73 dead occurrences (#10243 13, #11060 12, #14419 11, #13398 9 — five of them the hyphen-joined #13398-class spelling — #16659 9, #13648 6, #13681 4, #17123 2, #8707 2, and #10062 #11504 #14390 #16709 #8778 once each) plus the live #14095, #14456, #8287 and hotcrm#1206 once each; the 3 removed lines carrying none are exactly the three above. Added lines carry only those four live numbers, once each, on the lines they already stood on; no number is new to the diff, none grew, no PR #N stands on an added line; 15 distinct shas stand on added lines (71 occurrences: 815585513 12, c5a7448d5 11, 02b41232d 9, ecdfc9411 9, e238c79f0 9, 7307191db 6, 18d816a50 4, 266436a7f 2, 1408fe385 2, ae6dcf6a4 2, and 9d7f7259f f90e82024 fa5d137ab 8c7cca1ce 7901b2dd2 once each) plus ADR-0126 §7.2 twice, 73 in all.
  • The 17 sites left — right, and the list is exact. A grep of the 14 numbers over service-automation/src at the head returns exactly 17 lines: 16 describe / it titles (template-functions.test.ts:44, :162, :202; flow-field-expression-scale.integration.test.ts:83; flow-activation-ledger.test.ts:1027; create-record-duplicate-code.test.ts:51, :133, :255; screen-resume-signal-less.test.ts:81, :134, :187; contained-failure-visibility.test.ts:184; loop-dying-body-steps.test.ts:298; notify-zero-delivery-visibility.integration.test.ts:403, :535; stale-hot-consumed-suspension.test.ts:157) and the one runtime string, builtin/template.ts:192. Titles are string tokens, left as stages 1 to 13 left theirs; the runtime string is form D, and scripts/doc-authoring-prose-id.baseline.json holds it at the head (template.ts: #11060: 1), so check:doc-authoring sees no growth.
  • The gate-invisible spellings — right. At the head under service-automation/src: 6 #N-word lines, every number live or cross-repo (#5912 twice, #5048, #5186, each 200; hotcrm#548 twice) — the five dead #13398-class lines are gone; 13 #A/#B lines whose 10 distinct second numbers all answer 200, plus 6 ADR-0049/#1888 lines; 0 option #N; 0 URL-spelled. So the claim's 11 / 13 / 0 / 0 hold at the base, and the first is 6 at the head.
  • Form — consistent with the landed stages 1 to 13. The word commit plus the abbreviated sha in the position where the number stood, the decision carried in the sentence; ADR-0126 §7.2 where the record is an ADR; "The card behind commit …" for a header whose docblock goes on speaking of the card (stage 10's form). The reused anchors match this thread: #13398 → e238c79f0 (stage 3, and the stage-3 record's observation for this package), #16659 → ecdfc9411 (stage 12), #8707 → 1408fe385 and #16709 → 8c7cca1ce (stage 7), #8778 → 7901b2dd2 (stage 6).
  • Check-runs on the head, the gate verdicts (read 2026-09-30T08:55Z): 34 check-runs, all completed — 31 success, 3 skipped (Build Docs, Console Pin Gate, Packed-tarball smoke (opt-in): paths-filtered or opt-in, not verdicts against), 0 failure, none in progress at the read. Every one of the seven required contexts is success: Lint & Repo Gates (which carries check:issue-citations and check:doc-authoring, the two gates this diff answers to), TypeScript Type Check, Test Core, Dogfood Regression Gate, Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Check Changeset, Check PR Size, Part-of PR must not also close its card and The card this PR closes must claim this branch are success too. Nothing was built, run or re-run locally.

② Semver level

  • .changeset/20596-service-automation-provenance-anchors.md declares '@objectstack/service-automation': patch — matches what the diff publishes. The package is released and dist/index.d.ts carries the rewritten docblocks, so bytes ship; skip-changeset would be wrong (it is for a diff that publishes nothing from any released package), and the PR carries no such label. Not minor: no accept set widens and no surface is added. The body is truthful (comments only; no type, schema, export, log or refusal text, or runtime behaviour change), carries no tracker number and no model identifier, is stage 4's landed body with the package name swapped, and the filename carries the card number. No other .changeset file is touched.
  • Clause-②: no — right. It is line 2 of the PR body under Part of #20596, and the claim (5905919247) declares the same. The diff widens no accept set, so no arm is owed and no minor is owed. Nothing breaks, so no ADR-0087 marker is owed; Check Changeset on the head is success.
  • Not a governed-surface diff (no path under docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md); 163 changed lines, under the 5,000-line human-merge threshold; head repo equals base repo; Governed Surface Queue Guard on the head is success. A draft with Part of on line 1 and no closing keyword anywhere in the body, so the card stays open for the remaining stages.

③ Boundary flags

The dev report (5907350471) has open_questions: []. Its fourteen deviations, its five out-of-scope findings and the PR's acceptance notes, each answered:

  1. 24 test-comment sites beyond the census's 44, and the 5 #13398-class sites — answered, in scope. The claim's surface is comment and docblock prose under service-automation/src/**; test comments are that, stages 1 to 13 rewrote theirs, and the claim itself ordered the hyphen grep. Verified at the head: the residue is the 17 string sites above and nothing else.
  2. #10243 on three rungs per line — answered, right (① above). Ruling C's order puts the in-repo decision record first, and the ADR records only the retirement; the measurement and the ruling have no record but their commits. Form C applied per line, the per-arm precedent of stages 5, 9 and 13.
  3. Three changed lines with no dead number — answered, right (① above). The crud-nodes.ts correction was checked against both commits and against the head's update_record executor; every file keeps its line count.
  4. Two header lines re-worded as their own commit a602c4003, every reading re-run, the A3 build not — answered, right. Both files are test files (*.integration.test.ts, *.test.ts), which the bundle does not include, so the 9b0d34213 build reading still describes the head's dist; the head's check-runs are the gate verdicts this record reads either way.
  5. The void first token-guard control run — answered, immaterial. Discarded before any reading was taken; the replacement controls DIFFER as expected.
  6. The gate batch waited on by its recorded PID — answered, immaterial here. A process note; the head's Lint & Repo Gates is the verdict.
  7. Supplementary judging by stage 6's method, no board-dump script, no session-token curl — answered, right. The census instrument ran unchanged, and the residue it reports agrees with this record's own grep of the head.
  8. The model-free trailer pair over the harness reminder — answered, right. All three head commits end with Claude-Session: and Co-authored-by: Claude, no model identifier appears in any message, and the PR body's footer is the session-URL form.
  9. No pre-PR merge, main eight (now ten) commits past the base — answered, right. Verified above: none touches a path in this diff, anything under packages/services/service-automation, scripts/check-issue-citations.mjs or .changeset/config.json; the one baseline move changes no service-automation row. The queue's rebuild has nothing to reconcile by hand.
  10. The derived list is 63, not 64 — answered, immaterial. check:error-code-casing ran as a roster family, exit 0; CI's Lint & Repo Gates is the verdict.
  11. Labels — answered. documentation, size/m, tests, tooling are the labeler's; no skip-changeset, which is right.
  12. PR body read back through the MCP read, no byte comparison — answered, immaterial. The body read for this record carries every section, line 1 Part of #20596, line 2 Clause-②: no, one footer.
  13. Three census enumerations — answered, immaterial. All three read 187 pages at the newest frontier; nothing was discarded.
  14. Cleanup after posting — answered, immaterial to the head. The branch stays on the remote at a602c4003.
  15. Out-of-scope 1, NON_CITATION_HEADS' clause row excuses a real citation — answered, carrier stands. Verified at the head: suspended-run-store-consume-log-cause.test.ts:408 reads "The consequence clause finding(service-automation): engine.ts 还剩三处同形的 warn message 拼接 —— forgetSuspendedRun / cancelRun / listSuspendedRunsDurable,是 #5912+#6230 之后该文件的最后一批 #6299 asked for", and #6299 answers 200, so nothing dead hides there today. A sibling position of the folded option #N one; check-issue-citations closeout (extractor spellings): CITATION_RE refuses a hyphen after the digits, so a dead #N-word citation (#13398-class) is invisible to the diff gate and to the census #20636, the extractor-spelling family closeout, is the carrier, and the ACCEPT on the card (5907406984) records a pointer posted there. No instrument change in a stage PR of this card is right.
  16. Out-of-scope 2, builtin/template.ts:192's refusal names the dead #11060 — answered. Form D, not this card's comment-only form C; the shrink-only baseline already holds it (verified at the head), so check:doc-authoring sees no growth. Carrier: the runtime-string lane (the ACCEPT names runtime strings in the domain:services packages carry tracker numbers (168 messages in 17 packages, 263 ledgered ids): this lane's share of the #20513 A/A burn-down #20751); the anchor 815585513 is measured here for it.
  17. Out-of-scope 3, wording drift (notify-zero-delivery-visibility.integration.test.ts:72's "now", suspended-run-store.test.ts:904's retired tenancy.organizationField, "the card" on 134 lines) — answered, acceptance notes are right. No number, no runtime effect, neither instrument sees them; stage 7 recorded the same organizationField drift and stages 8 to 13 left "the card" as it stood. The two whose antecedent this diff removed were repaired in a602c4003.
  18. Out-of-scope 4, update_record surfaces no classified code — answered, acceptance note is right. Verified at the head: its catch returns the collapsed string only. c5a7448d5 scoped the repair to create_record deliberately, nothing advertises a code on update_record, and no reported pull exists, so it is neither a defect nor a contract violation under Prime Directive chore: version packages #10; a note is the right filing. The corrected crud-nodes.ts:439-441 states the gap truthfully.
  19. Out-of-scope 5, tsconfig.test.json names the dead #13176 (lines 5, 73) and #14916 (line 54) — answered, right. Verified at the head. Outside the census's declared surface and outside files[], so it ships nothing and moves no gate; listed and left, as stage 13 left its own. README.md names no dead number; the release-owned CHANGELOG.md is never edited in a code PR.
  20. The PR's acceptance notes on the gate-invisible spellings and on the base — answered, right (① above and item 9).

Nothing is escalated.

Implemented-by: claude/issue-20596-service-automation-citations
Reviewed-by: session_01XY5uCwTjZj7884yYtyur4H

VERDICT: PASS

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 30, 2026 09:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 30, 2026
Merged via the queue into main with commit 73155fe Sep 30, 2026
36 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20596-service-automation-citations branch September 30, 2026 09:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants