Skip to content

fix(spec): os migrate meta guidance for the remaining migration-entry families states each lesson in words, not tracker numbers (stage 9) - #20630

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20233-migrate-meta-tracker-free-stage-9
Sep 29, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-20233-migrate-meta-tracker-free-stage-9

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Part of #20233
Stage 9: the remaining semantic-entry families, meaning every ADR-0087 semantic entry that still cited a tracker number.

Clause-②: no

os migrate meta prints each ADR-0087 semantic entry's surface, replacement, reason and acceptanceCriteria to the author. This stage covers the 44 entries that still cited a tracker, pull-request, decision-batch or cross-repository number: 39 with a four- or five-digit id, and 5 with a decision-batch number only. In each of them, every sentence now says what the cited ruling, measurement or fix decided (form D). ADR ids stay, and so do the contributor-guide rule references, which are not tracker ids. The address-location-value-unknown-keys-refused replacement cited AGENTS.md #0.1, which names nothing in AGENTS.md today. It now states the rule it copied from the 2026-09-01 ruling: a consumer-side alias for an off-spec key stays forbidden.

  • Text only: a base-vs-head AST comparison over the 45 changed entry files and registry.ts finds no change outside replacement / reason / acceptanceCriteria. That is 48 prose fields per copy, and no surface, id, comment, import or token-skeleton change.
  • Census (AST instrument, whole tree): base 682873f201 has 76 prose sites in 39 entries (replacement 3 / reason 73 / acceptanceCriteria 0), 0 surface sites and 28 short numbers. Head has 0 / 0 / 10. The 10 short numbers left are contributor-guide rule references.
  • Pin: packages/cli/test/migrate-meta-engine-guidance.test.ts now holds every semantic entry instead of a prefix list, so an entry added later in any family is held on arrival. REWRITTEN goes from 203 to 247. Ablation: putting one removed id back into the turso- entry (a family the pin did not cover before) turned the pin red; restoring it turned the pin green.
  • Generated: registry.ts, spec-changes.json and docs/protocol-upgrade-guide.md, by their generators. patch changeset.

Verification (head 96b994e472)

  • Tests:
    • spec --project local: 575/575 files (16917 passed, 1 todo).
    • spec --project repo: 42/42 files (745 passed).
    • CLI --project unit: 234/234 files (3342 passed).
    • CLI --project integration, the three migrate-meta files: 3/3 (13 passed, 1 skipped by the default-range file's own skipIf).
    • spec and CLI typecheck: exit 0, test-layer debt held.
  • Gates: dispatch-gates.mjs --commands derives 89 families. 88 exit 0, among them check:doc-authoring, check:generated, check:migration-registry, check:spec-changes, check:upgrade-guide, check:issue-citations, check:nul-bytes, check:api-surface and check:authorable-surface.
    • check:dual-build-cjs-loads is NOT MEASURED: its prerequisite is not met, because packages outside the CLI closure have no dist/. Build Core runs it.
    • --ran reconciliation: 89 accounted, 88 run, 1 NOT MEASURED.
  • Lint: eslint over the 47 changed source files: 0 errors, 0 warnings.
  • Mergeability: a driver-free merge-tree onto origin/main b80ab579d8 is clean. Main's new commits touch none of these paths.

The census method and controls, the source of every citation, and the ablation record are in the dev report on #20233.

Acceptance notes


Generated by Claude Code

…n words, not tracker numbers (stage 9)

Each ADR-0087 semantic entry that still cited a tracker, pull-request,
decision-batch or cross-repository number in its replacement / reason /
acceptanceCriteria prose now says what the cited ruling, measurement or
fix decided. Verbatim quotes that carried a card or batch number keep only
their operative words. ADR ids and contributor-guide rule references stay;
the address entry's dangling AGENTS.md rule number is stated as the rule.

Text only: no entry id, surface, conversion or matcher changes.

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
… the upgrade guide

Generator output only (gen:migration-registry, gen:spec-changes,
gen:upgrade-guide) for the stage-9 entry prose.

Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx
Co-authored-by: Claude <noreply@anthropic.com>
…patch changeset

With the remaining families rewritten, no semantic entry's printed
guidance carries a tracker id, so the pin covers the whole directory
instead of a prefix list: an entry added later, in any family, is held on
arrival. REWRITTEN gains the 44 entries this stage rewrote (203 -> 247).

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 1 documentable anchor(s). ⚠️ 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

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

  • content/docs/kernel/services-checklist.mdx (via /api/v1/workflow (route, a path literal in reason; a path literal in semantic))

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

  • content/docs/releases/v17/17-0.mdx (via /api/v1/workflow (route, a path literal in reason; a path literal in semantic))

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
  • 1 changed file(s) yielded no anchor (packages/spec/spec-changes.json) — pages documenting those are invisible to this run
  • 3 name(s) were too generic to anchor anything (single lowercase words)
  • 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 — 137 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 b80ab579d8bdd7499805612104ce95d931b0f03e → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 619ee3e31cc279c3993f4aec32a7e1bb26781a9a — the merge of head 96b994e472ba988aa9fae42e3b3c1fde2437e591 into base b80ab579d8bdd7499805612104ce95d931b0f03e, 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 619ee3e31cc279c3993f4aec32a7e1bb26781a9a && git checkout 619ee3e31cc279c3993f4aec32a7e1bb26781a9a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b80ab579d8bdd7499805612104ce95d931b0f03e 96b994e472ba988aa9fae42e3b3c1fde2437e591 && git checkout -B drift-repro b80ab579d8bdd7499805612104ce95d931b0f03e && git merge --no-ff 96b994e472ba988aa9fae42e3b3c1fde2437e591

node scripts/docs-audit/affected-docs.mjs --json b80ab579d8bdd7499805612104ce95d931b0f03e

⚠️ 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 b80ab579d8bdd7499805612104ce95d931b0f03e → 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: 96b994e472ba988aa9fae42e3b3c1fde2437e591
Local-runs: none

Read-only review of PR #20630 (stage 9 of #20233) at the head above, against main at the merge base 682873f201 and against origin/main 542670da as read in this act. Inputs: the card's body and all 33 comments (triage 5856629820, ruling A on surface in 5858839916, stage 8's report 5886017515 and landing 5886769122, the stage-9 claim 5887268542 and report 5888773479), ruling C+D on #19123 (5749154545), the PR's body, comments, 50-file list and net diff, the cited sources, and the head's check-runs read once. Nothing from the dev's prose was adopted without a reading of my own: every count below comes from a scratch tokenizer run over git show objects (no checkout, no build, no test, no gate), and every sentence judgment from the cited record.

① Derived judgments

  1. Accept set: unchanged — RIGHT. No Zod schema, conversion, matcher, id, surface, from / to, import or comment moves. My token-skeleton comparison of the 45 entry files at base and head (string runs collapsed, every comment and every non-prose string compared by value in order) reports 0 non-prose changes and exactly 48 prose fields changed: 44 reason, 4 replacement, 0 acceptanceCriteria, 0 surface. Semantic entries carry no from / to or matcher.
  2. Public surface: author-shown text only — RIGHT. @objectstack/spec ships the registry, so the why: / verify: lines os migrate meta prints (meta.ts:523-525 prints surface, replacement, reason, acceptanceCriteria) change for 45 entries; no export is added or removed (check:api-surface unchanged per the dev; Build Core and Type Check source gates are green at this head).
  3. The three projections are exact — RIGHT. The removed and added line multisets of registry.ts's diff equal the 45 entry files' diff (85 removed / 143 added, both sides). spec-changes.json (both its aggregate and perMajor copies) and docs/protocol-upgrade-guide.md carry the head reason / replacement / surface for all nine changed protocol-17 entries byte-equal; the 36 protocol-18 entries are absent from both by design (released majors only). The 138 tracker ids still in spec-changes.json sit in conversion rows' to fields, outside this card's positions.
  4. Census — reproduced. Base 682873f201: 76 prose sites (replacement 3 / reason 73 / acceptanceCriteria 0) in 39 entries, 0 surface sites, 28 short numbers. Head: 0 / 0 / 10; the 10 are Prime Directive chore: version packages #10 (x4), Add comprehensive test suite for Zod schema validation #12 (x3), ✨ Set up Copilot instructions #2 (x1) and AGENTS.md #0.1 in the two ui-list-view-* entries. origin/main 542670da outside the PR's 45 files reads 0 tracker sites and the same 10 short numbers; the one entry added there since the base (connector-triggers-retired, PR feat(spec)!: retire the connector triggers array — the ConnectorTrigger shape nothing registered, polled or received (#20287) #20587 merged) reads 0 in all four fields. So the merged population is 0. None of the four fenced entries is in the file list.
  5. Sentence truth — RIGHT, no invented claim found. Read from source for: turso- (driver-turso: a remote url plus syncUrl is classified replica and handed a :memory: Knex connection, so every write lands in process memory and never reaches the remote #19893, driver-turso: a url whose scheme the classifier does not recognise (an uppercase LIBSQL://, a bare path) and no mode falls through to local on a :memory: Knex engine, so every write is lost on restart #19976 bodies; driver-turso: new TursoDriver accepts syncUrl / sync under mode: 'remote' and ignores them — isSyncEnabled() answers true, no sync runs, and sync() rejects SYNC_NOT_SUPPORTED #20200's constructor refusals via PR fix(driver-turso)!: new TursoDriver refuses syncUrl under a forced remote mode, and sync with no syncUrl (#20200) #20447's report), cbp- (A controlled_by_parent object may declare its master reference without required, so the master-access guard is the only thing preventing an unreachable orphan detail row #8772 is 404: read from commit 75b7c240a3 and card spec builder: force required: true on a master_detail reference under controlled_by_parent (ruled Direction 2 of #8772) #9138, which name Direction 2 as the builder forcing required: true and lint staying warning until the v18 sibling; the sentence keeps the base's Direction-1 attribution and adds nothing), autonumber- (hotcrm#1301 is 403: autonumber 业务标识字段默认不唯一,可铸重号 —— 裁定:默认唯一(租户复合形状),显式退出才不唯一 #13894's body carries the nine-field measurement, eight duplicable, and the 2026-08-31 ruling), cluster- (cloud#1626 is 403: the spec CHANGELOG entry for c85a265 carries the 2026-08-24 option-B ruling and its rider), structured- (batch 145 C 5714239196 and batch 146 禁 / A 5714974100 / 5714974443 are both dated 2026-09-17; batch 153 D 5724940095 is 2026-09-18), admin-scope- (5793356837: A, refuse blank at parse, stored rows untouched), wait-node- (objectui#9354 5651579637: batch 127 item 5, 2026-09-13, and that record itself states 「其他同意」 covers this item), address- (5494663475 item 5 is the source of AGENTS.md #0.1: consumer-side alias stays forbidden), plus approval- (C, 2026-08-27, the declared default moves to the truth), batch-row- (fix(metadata-protocol): deleteManyData has the same fake-atomic as batchData, updateManyData ignores atomic entirely #4620 fix; fix(metadata-protocol)!: batch 逐行结果迁移到 BatchOperationResultSchema 形状 —— 方案 B 已拍板,硬切 + 诚实迁移说明(Blocked-by #4620) #4793 plan B hard cut ruled 2026-08-03), standard-error-code-batch- (5328254030: 「9266 同意 A」 = retire the three codes), standard-error-code-concurrent- (A 2026-09-13, narrowed 2026-09-24), cel- (A 2026-09-24, comparand is a literal or { $field }), memory- (direction 2, 2026-08-13), schedule- (A′ 2026-09-16), logging- (A 2026-09-11, gate lands last), ai- / device- / websocket- / epoch- (B 2026-09-02 and the 2026-09-05 population ruling 5548763981), change- / incident- / training- / esignature- (A 2026-09-02 per family; batch 40 answered no roadmap on 2026-09-05), assembled- (A 2026-09-04), translation- (② 2026-09-13, B 2026-09-22), observability- / screen- / ui-bulk-action- (A 2026-09-18, A′ 2026-09-13, A 2026-09-17 via their landing PRs), auth- (2026-08-11 stop-advertising; objectui#4179 closed not_planned), and the own-card drops (send-, time-, job-, enhanced-, connector-inline-, position-, ups-, workflow-, audience-, admin-export-, branded- / event-name-) whose titles state what the sentence now says. Where a number was dropped without replacement the surrounding sentence already carried the lesson (e.g. discovery 的 workflow / graphql 槽位还声明着两条无人挂载的 route —— #4318 同款,但目前"上了膛没击发" #4451, [#14478 stack 1/6] declare the two exemption classes ON THE SCHEMA — a shared EpochMs for the 6 epoch instants and a .meta({ externalVocabulary }) marker on the 13 external-standard keys, honoured by check:duration-unit-keys and printed by the docs generator #15676/[#14478 stack 2/6] api/: the 12 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers, ApiError.retryAfter with its own BREAKING note #15677/[#14478 stack 5/6] data/ · ui/ · ai/ · integration/: the 7 remaining duration keys carry their unit in the key name — ADR-0087 conversions with readers #15680 trailing ids, [finding] SendTemplateInput.org is declared ("Tenant id for org-overlay resolution (when supported)") but no implementation reads it #11832 own card); no lesson was lost.
  6. Verbatim maintainer quotes — RIGHT. Form D (5749154545, 「保留 issue id没有意义」) forbids a number in author-shown text, so a quotation whose only content beyond assent is a card or batch number cannot stay as written. 「217 同意」 and 「146 同意」 are dropped, not rewritten, with the decision stated in words and the date kept; 「9266 同意 A」 is replaced by the option the ruling record itself spells out; the wait-node reply keeps its clause for this item 「其他同意」 untranslated, cut at the sentence boundary, exactly the reading the recording comment gives. AGENTS.md forbids translating or rewording a quoted ruling; excerpting the clause that answers this item is neither. The quotes that carry no number (「同意,其他也同意」, 「其他同意」, 「同意」, 「Breaking for authored metadata」) are untouched.
  7. Scope reading of the five batch-only entries — RIGHT. Triage's direction is 「no number, dead or alive」 in form D; stage 8 already rewrote decision-batch and summon numbers under a PASS record and left only contributor-guide rule references, and the claim's 「still carries one」 reads on that classification. A batch number sends the reader to a director-summon record outside this repository, which is the defect. Including observability-, screen-, translation-, ui-bulk-action- and wait-node- is the consistent reading; the census is 0 either way.
  8. The pin — RIGHT, non-vacuous, and no family loses its hold. FAMILY is now every semantic entry of every major, a superset of the old prefix-filtered set, so every block asserted before is still asserted; each block is asserted present in stdout before it is asserted tracker-free; REWRITTEN 203 to 247 (44 added; address- was already listed from stage 8) and the containment assertion stays. The one dropped assertion (each prefix selects at least one entry) existed to stop an emptied prefix making the loop vacuous; 247 required ids do that job now. The claim allowed the whole directory once the census read 0. Subtracting the fenced entries would have loosened filter-, held since stage 4 (COVERED_PREFIXES at base names it). PR feat(spec)!: $empty joins FILTER_OPERATORS, and is_empty / is_not_empty lower to it (#20446) #20570's filter-is-empty-lowers-to-empty-operator therefore meets the same demand before and after this diff; at its current head a1402a37 its four printed fields carry 0 tracker ids (two remain in comment lines only), so it meets it either way. Ablation not re-run here; the dev's red-then-green on turso- is consistent with the detector # plus 4-5 digits.
  9. Not governed. No .claude/**, docs/adr/**, AGENTS.md, skills/** or NORTH-STAR path in the 50 files; Governed Surface Queue Guard is green.

② Semver level

@objectstack/spec patch, Clause-②: no — RIGHT. The published package's author-shown guidance text changes, so skip-changeset would be wrong; no accept set, export or authorable key moves, so minor is not owed and no arm applies. The changeset body carries the standalone Clause-②: no line, says text only, names the families and the address- rule restatement, and states that no id, surface, from / to or matching logic changes. Check Changeset is green.

③ Boundary flags

  • Dev deviation 1 (batch numbers counted as tracker numbers): answered in ①.7 — right; no file leaves the PR.
  • Dev deviation 2 (four verbatim quotes trimmed or replaced): answered in ①.6 — right under form D; no ruling misrepresented.
  • Dev deviation 3 (pin holds the whole directory, no fence subtraction): answered in ①.8 — right.
  • Dev deviation 4 (census and astdiff rewritten): their readings are reproduced here by an independent reader (76 / 0 / 28 to 0 / 0 / 10; 48 prose fields; 0 non-prose changes).
  • Dev deviation 5 (no deletion leg): acceptable — the dispatch did not ask for one; stage 8's leg found only the tool-pin fragments, all still present in this head's text, and the full spec and CLI runs at head are the dev's; CI's Test Core is the reading of record.
  • Dev deviations 6-10 (background waits, attribution form, no label writes, main not merged, teardown): bookkeeping, nothing to rule. registry.ts moved on main under PR feat(spec)!: retire the connector triggers array — the ConnectorTrigger shape nothing registered, polled or received (#20287) #20587; GitHub reports the PR mergeable (not dirty) at read time. If the queue reports a conflict there, regenerate, never hand-merge.
  • Out-of-scope finding: ui-list-view-groupbyfield-padded-refused and ui-list-view-grouping-field-padded-refused cite AGENTS.md #0.1 in reason — ESCALATED to the seat. Not a tracker number, so outside this card's population and outside the stage-9 claim; but it is the same dangling rule reference the stage-8 review escalated and this seat took into stage 9 for address-. The consistent disposition is a two-sentence follow-up under this card before it closes, or a bare card. Not a defect of this diff.
  • Out-of-scope finding: ui-notification-action-embed-config-retired names 「its ui/ batch 14」 (no #): a sweep batch label, not a tracker number, not caught by the pin's detector, family covered since stage 2. Noted; the seat may fold it into the same follow-up.
  • Out-of-scope finding: PR feat(spec)!: $empty joins FILTER_OPERATORS, and is_empty / is_not_empty lower to it (#20446) #20570's incoming entry: resolved on its own — 0 tracker ids in its four fields at a1402a37.
  • Out-of-scope findings: packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234's comment lines (127 / 839) and the detector's 1-3-digit blind spot: theirs and not this card's; correctly left.
  • Card population and closing keyword: the population this card names (tracker numbers in the four printed fields of the semantic entries) is 0 on this head and on the merged state. Part of #20233 is the shape the claim set for a staged card and the Part-of check is green; closing the card is the seat's landing act with a closing record, after it decides the escalated AGENTS.md #0.1 pair. No body edit is required by this record.
  • Check-runs, read once: 32 on the head: 17 success, 3 skipped (Console Pin Gate, Build Docs, Packed-tarball smoke — skip-by-design at this path set), 0 failing, 12 not concluded at read time: Test Core 1/6 through 6/6 (the CLI test script is vitest run over both projects, so the pin runs there), Lint & Repo Gates (check:doc-authoring, check:migration-registry, check:spec-changes, check:upgrade-guide, check:issue-citations, check:generated), Type Check · workspace, Type Check · consumer gates, Temporal Conformance, Dogfood Regression Gate 1/3 and 3/3. Green so far: Build Core, Type Check · source gates and · debt ledger, Check Changeset, Spec property liveness, Governed Surface Queue Guard, Part-of and claim checks, single-writer path, Check Documentation Links, Dogfood Verify CLI, Dogfood Regression Gate 2/3. This verdict judges the diff; landing still waits for every check to conclude green.

Implemented-by: claude/issue-20233-migrate-meta-tracker-free-stage-9
Reviewed-by: session_014EJ1ED8X4MMrT18BhVx4tx

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review September 29, 2026 11:18
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Sep 29, 2026
Merged via the queue into main with commit a918fe7 Sep 29, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-20233-migrate-meta-tracker-free-stage-9 branch September 29, 2026 11:37
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…les.ts to the commits that decided them (objectstack-ai#20631)

Part of objectstack-ai#20597
Clause-②: no

Stage 2 of the `packages/lint` dead-citation sweep (claim `5888191846`).
Stage 1 (PR objectstack-ai#20612) left `packages/lint/src/authoring-rules.ts` at its
base blob while PR objectstack-ai#20593 held the file. That PR has landed
(`e651556e2d`). Each of the file's five comment and docblock lines that
cited a tracker number answering 404 now cites, in ruling C+D's form C,
the commit in this repository's history that decided what the line
states. Each line still says what was decided. Comments only: 5 lines
out, 5 in, in one file, plus one `@objectstack/lint` `patch` changeset.

This PR says `Part of`: the form-D finding-message string at
`validate-react-page-props.ts:1198` (`(objectstack-ai#11284)`, shown to authors) stays
on the card as a separate decision. It is byte-identical here (see
Acceptance notes).

## Measurement

The instrument is the gate's own `node scripts/check-issue-citations.mjs
--census --json`, filtered to `packages/lint/`.

- **Before:** base `1322cc72c9`, 2026-09-29T10:18:50Z to 10:22:33Z,
board enumerated (185 pages, frontier objectstack-ai#20627). Repo-wide
`allocated-but-absent` 2,209.
- **After:** head `2e1955b492`, 11:13:21Z to 11:16:47Z (185 pages,
frontier objectstack-ai#20630). Repo-wide `allocated-but-absent` 2,204, exactly 5
fewer. No finding at head is absent at base.

| site in `authoring-rules.ts` | before | after | anchor |
|---|---|---|---|
| `:201` (the `AuthoringFinding.path` docblock) | objectstack-ai#10064 | commit |
`def0d3e63` |
| `:1123` | objectstack-ai#16659 | commit | `ecdfc9411` |
| `:1722` | `(PR objectstack-ai#8546)` | commit | `ba5e957ef` |
| `:1748` | `[objectstack-ai#19370]` | commit | `a227afa41` |
| `:1768` | `[ADR-0090 D3 / objectstack-ai#8310 → objectstack-ai#19370]` | commit | `a227afa41`
(ADR-0090 D3 and objectstack-ai#8310 stay) |
| **`packages/lint` total** | **5** | **0** | 4 numbers, to 4 distinct
shas |

The five lines on `origin/main` `e651556e2d` are the same lines at the
base: `packages/lint` is byte-identical between `e651556e2d` and
`1322cc72c9`.

## Why each anchor decides its line

Each sha resolves uniquely (`rev-parse --disambiguate` gives 1 object).
Each is single-parent. `merge-base --is-ancestor` exits 0 against
`origin/main` and against the base, and the repository is not shallow.
Each commit's own diff was read for the rule its line states.

- **objectstack-ai#10064 to `def0d3e63`**: "key collection-resident publish-gate
finding paths by name, not the private snapshot index". Its body names
objectstack-ai#10064 as the card it lands, "(maintainer ruling 2026-08-20: Option A)".
Its own diff wrote this very docblock: the positional-as-rules-emit-it
sentence, the `objects.acme_invoice.sharingModel` example and the
pointer to `nameKeyFindingPath`, which the same commit introduced in
`runtime-gate.ts`.
- **objectstack-ai#16659 to `ecdfc9411`**: the squash commit that declares a
time-triggered flow's acting organization. Its diff adds
`FLOW_SCHEDULE_ORGANIZATION_MISSING`
(`flow-schedule-organization-missing`, at `warning`) to
`validate-flow-trigger-readiness.ts`. It also wrote the
`authoring-rules.ts` sentence "... added a sixth id,
`flow-schedule-organization-missing`, at `warning`" that this line
opens, and its sub-commits name objectstack-ai#16659. The live objectstack-ai#17396 retirement
beside it stays.
- **`(PR objectstack-ai#8546)` to `ba5e957ef`**: PR objectstack-ai#8546's own squash commit,
"permission/book cross the runtime publish gate; object measured dirty
stays behind". Its `authoring-rules.ts` diff changes `runtimeTypes:
['seed']` to `['seed', 'permission', 'book']`, which is objectstack-ai#8310 slice 1 as
the line states. The live objectstack-ai#8310 stays.
- **objectstack-ai#19370 to `a227afa41`** (two sites): "`security-role-word` crosses
to the runtime publish gate, whole". Its body names objectstack-ai#19370 as the card
it lands. Its own `authoring-rules.ts` diff wrote both lines: "[objectstack-ai#19370]
It has since crossed, also whole, on its own entry" and the `[ADR-0090
D3 / objectstack-ai#8310 → objectstack-ai#19370]` marker. Stage 1 cited the same commit for the same
number in `runtime-gate.ts` and `validate-security-posture.ts`.

Rung: no file under `docs/adr/**`, `docs/NORTH-STAR.md` or
`scripts/adr-anchors/` names any of the four numbers. So the commit rung
is right, as in stage 1. ADR-0090 D3 already stands on `:1768` and is
kept.

## Mechanical proof

- **Token and residue guard.** A scratch instrument on the TypeScript
6.0.3 parser compares the base blob with the head blob at two levels.
The first is leaf AST tokens, with JSDoc nodes excluded. The second is
the non-comment residue: every comment range dropped, everything else
compared byte for byte. The controls mutate the head text in memory
only.
- Real run: 3,543 tokens at base and at head, tokens EQUAL, residue
EQUAL (exit 0).
- Dark control, a whole comment line inserted: tokens EQUAL, residue
EQUAL, comment ranges 957 to 958 (exit 0).
- Lit control, a code statement inserted: DIFFER at token 0, residue
DIFFER (exit 1).
- Lit control, one character inserted into a parser-located string
literal: DIFFER at token 5, residue DIFFER (exit 1). The first string
control was a no-op and is void: it searched by text and landed in a
comment, reading EQUAL. It was re-anchored on a parser-located literal
and re-run. The mutation was confirmed landed.
- **Line balance**: +5/−5, and every changed line is comment-shaped. The
file has 2,011 lines at base and at head.
- **Tracker numbers**: removed objectstack-ai#10064, objectstack-ai#16659, objectstack-ai#8546 and objectstack-ai#19370 ×2. The
added lines carry only the live objectstack-ai#8310 ×2, which stands on both the
removed and the added side of `:1722` and `:1768`. So added-not-removed
is empty, and no `PR #N` stands on an added line. There are 215 `#N`
tokens at base and 210 at head.
- **Shas**: 4 distinct on added lines (`a227afa41` ×2), none on removed
lines.
- **Literal readers**: `scripts/doc-authoring-prose-id.baseline.json`
pins this file's string sites as objectstack-ai#4463, objectstack-ai#4716, objectstack-ai#4717, objectstack-ai#7220, objectstack-ai#8309 and
objectstack-ai#9698, none of them these four numbers. `check-docs-transcript-drift`
loads the registry module, not its comments.

## Tests and gates (at head `2e1955b492`)

- Build under `os-verify-lock`: `pnpm exec turbo run build
--concurrency=2 --filter=./packages/* --filter=./packages/*/*`. The last
run printed Tasks 71 successful, 71 total, and VERDICT command-exit 0.
It took three attempts inside a 270 s timeout on a shared box. The first
two were cut off at 39 of 46 and 66 of 68 tasks, and turbo's cache
carried their finished tasks forward.
- `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2` under
the lock: Test Files 115 passed (115), Tests 5379 passed (5379), VERDICT
command-exit 0.
- `pnpm --filter @objectstack/lint typecheck` under the lock: exit 0.
`check:test-typecheck` OK (2 files, 6 errors, 2 pinned signatures held).
VERDICT command-exit 0.
- Lint, as a proven narrowing: `eslint --no-inline-config --format json
packages/lint/src/authoring-rules.ts` reports 1 file, 0 errors, 0
warnings. `isPathIgnored` is false, read through eslint's API.
`eslint.config.mjs:327-328` says type-aware linting is never enabled, so
a comment edit cannot move an untouched file's verdict. The repo-wide
`pnpm lint` is CI's.
- `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack
--commands` derived 54 families, and all 54 ran with exit 0. `--ran`
reads "54 derived, 54 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero.
Among them:
- `node scripts/check-issue-citations.mjs`, the live diff-scoped run,
judged 2 citations in 1 file, the kept objectstack-ai#8310 ×2, and both answer as
issues. `pnpm check:issue-citations` passes its self-test (114 cases in
8 batteries).
- `pnpm check:doc-authoring`: the sibling prose-id baseline holds, 810
pinned sites across 230 files, no growth.
- `pnpm check:nul-bytes`: OK over 9,253 tracked text files. A
control-byte scan of both changed files finds none.
- Generated pages: none to regenerate. No page under
`content/docs/references/` names the four numbers, `AuthoringFinding` or
`nameKeyFindingPath`, and no generator reads `packages/lint/src`.
- Changeset: `patch` for `@objectstack/lint`, a new file (stage 1's
`lint-provenance-anchors.md` is untouched). `files[]` ships `dist`, and
the rewritten comments reach it:
- `commit def0d3e` is in the `AuthoringFinding` docblock of
`dist/runtime-*.d.ts`, beside the unchanged "Positional as RULES emit
it", the positive control.
- `commit ba5e957` (cited only here) and `commit a227afa` are in
`dist/index.js` and `dist/index.cjs`.
  - None of the four numbers remains in `dist`.
- Merge probe: a no-driver `merge-tree` of the head onto `origin/main`
`542670da6d`, from a bare shared clone with no `merge.*` config, exits
0. None of the three commits `main` gained since the base touches
`packages/lint`.
- No ablation or reverse verification: the change is comment-only, so
there is no behaviour to invert.

## Hypotheses (measured first)

1. **Holds.** At the base the census reads exactly 5 dead sites in
`packages/lint`, all in `authoring-rules.ts`, and after the change it
reads 0. The card's sixth site, the `(objectstack-ai#11284)` string at
`validate-react-page-props.ts:1198`, is outside the census because the
census blanks string literals. It was read directly: still present, and
the file is byte-identical from base to head.
2. **Holds.** The five sites read `:201` objectstack-ai#10064, `:1123` objectstack-ai#16659, `:1722`
`(PR objectstack-ai#8546)`, `:1748` and `:1768` objectstack-ai#19370, on `e651556e2d` and at the
base alike. All four anchors were re-verified above from their own
diffs, not copied.
3. **Holds.** PR objectstack-ai#20593's new lines cite objectstack-ai#20553, objectstack-ai#20611 and objectstack-ai#20552 (and
ADR-0041). All three answer 200, and the census finds no dead site on
them. None of the five lines' sentences changed in meaning. The one
adjacency is described under Acceptance notes.

## Deviations

- Commit trailers follow AGENTS.md's model-free pair (`Claude-Session`
plus `Co-authored-by: Claude`), not the model-named trailer the harness
reminder suggested. The pre-push trailer check passed on both pushes.
- The first lit string control was a no-op: it searched by text and
landed in a comment. It is reported void above and was re-run on a
parser-located literal.

## Acceptance notes

**Form D, not touched (why this PR says `Part of`):**
`validate-react-page-props.ts:1198` is the `react-prop-deprecated`
finding `message`. It ends "...is removed after the deprecation window
(objectstack-ai#11284)." An author sees it, so it takes ruling D (no number). That is
a string change, outside this comment-only claim.
`scripts/doc-authoring-prose-id.baseline.json` pins it (`objectstack-ai#11284: 1` for
that file), and that baseline is shrink-only.

**An ordinal beside PR objectstack-ai#20593's insertion, kept verbatim:** the
paragraph above `:1123` now ends "objectstack-ai#20553 made it five", counting the
rules that emit `error`. `:1123` reads "Commit ecdfc94 added a sixth
id", an ordinal that commit wrote itself. The two count different
things: rules that emit `error`, and ids in the rule file. The ordinal
is also imprecise on its own terms, because
`validate-flow-trigger-readiness.ts` exported six ids before
`ecdfc9411`, so the new one was its seventh. This PR moves only the
tracker number, so the word stays as written.

**Outside the census's surface (noted, not swept):** stage 1 notes that
lint test titles and hand-written docs still cite these numbers.
`content/docs/deployment/validating-metadata.mdx` cites objectstack-ai#19370 at
`:472`, `:483` and `:515`.

---
_Generated by [Claude
Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…s that decided them (objectstack-ai#20634)

Part of objectstack-ai#20596
Clause-②: no

## What changed

This is the third stage of the `domain:services` lane of the
dead-citation sweep. It covers `packages/plugins/plugin-auth/src/**` and
nothing else. By census, it is the largest package in the lane that no
open PR or in-flight claim holds (the claim, `5888562941`, gives the
order). 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 and 2 (PR objectstack-ai#20609 as
`422db788a`, PR objectstack-ai#20626 as `b80ab579d`). That is **95 sites on 95 lines
in 31 files, covering 16 numbers**:

- the 52 census sites (all of this package's census sites);
- 38 sites in test comments, which the census defers;
- 5 sites in the hyphen-joined spelling `objectstack-ai#13398-class`, which the gate's
extractor does not match at all (see Acceptance notes).

Each rewritten line now cites the commit in `origin/main` history that
decided what the line describes, and it says in its own words what that
commit decided. No ADR or ruling-record file records the decision behind
any of the 16 numbers, so every anchor is a commit: **15 distinct shas**
(`objectstack-ai#11477` and `objectstack-ai#12029` share one, because `objectstack-ai#12029` was the pull request
that settled `objectstack-ai#11477`). No number was dropped.

Only comments changed. Every touched source file keeps its line count
(107 lines out, 107 in, over 31 files), so no line citation into these
files moves. 12 of those 107 lines hold no dead citation; they are
reflow or a lost referent, listed under Wordings below. No code token
moves (see the guard below).

**No citation number is added.** Every tracker number on an added line
was already on the line it replaces. Over the whole diff, added minus
removed is 0 or negative for every number (the gate's own
`extractCitations` over the diff: 103 citations removed, 13 added, all
13 kept resolving numbers), and no number is new to the diff. No PR
number stands on an added line.

Twenty-one dead sites are left on purpose, all of them test titles (see
the list below).

One more file: a `patch` changeset for `@objectstack/plugin-auth`,
because the rewritten docblocks ship (see Changeset below).

## Census: `plugin-auth`, 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/plugins/plugin-auth/`. Each run counts as a reading only
because its board frontier equals the newest issue number, read by a
separate request just before and just after the run.

| reading | tree | board | whole-repo `allocated-but-absent` |
plugin-auth sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `b80ab579d`, run 2026-09-29T10:43:31Z to 10:47:03Z |
enumerated, 185 pages, frontier objectstack-ai#20629 (newest objectstack-ai#20628 before, objectstack-ai#20629
after), 18,456 numbers | 1,955 | **52** | 52 | 13 | 12 |
| after | head `5ae64e8b8`, run 11:12:05Z to 11:15:37Z | enumerated, 185
pages, frontier objectstack-ai#20630 (newest objectstack-ai#20630 before and after), 18,457 numbers
| 1,903 | **0** | 0 | 0 | 0 |

The before count matches the 52 that census `5884031174` read at
`f11b5f20`. The whole-repo drop is 52, exactly this diff's census sites.
The `resolves` tally is 32,832 in both runs, and
`resolves-as-pull-request` (1,984) and `cross-repo-unjudged` (995) did
not move either. No run was truncated or discarded: all three
enumerations in this stage (two census runs and the supplementary board
below) read 185 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) and `classifyCitation` over
every `.ts` file under `plugin-auth/src` (178 files). It uses one board,
enumerated by the gate's own `enumerateBoard` at 10:50:47Z (185 pages,
frontier objectstack-ai#20629, equal to the newest).

| reading | citations | dead | src comment | test comment | src string |
test string |
|---|---|---|---|---|---|---|
| before, `b80ab579d` | 2,150 | **111** | 52 | 38 | 0 | 21 |
| after, `9fd0ebf10` | 2,060 | **21** | 0 | 0 | 0 | 21 |

Its src-comment column equals the census's 52, which is the control on
the second instrument. The 1,966 resolving, 46 pull-request and 27
cross-repo citations are the same in both readings. Neither instrument
sees the 5 `objectstack-ai#13398-class` sites; a plain grep for the 16 numbers over
`plugin-auth/src` at the head finds only the 21 test titles (and the
digits `11477` inside test fixture e-mail addresses and a password,
which are code tokens, not citations).

## Per-number table

Sites and files count all dead sites the gate sees in scope at the base
(comments and strings, tests included). `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#8676` | 22/6 | 18/4 | `d6e80b28b`: `sys_account.password` and
`previous_password_hashes` are flagged `internal: true`, and every
reader is recovered through the engine's privileged accessor (the
adapter readback table gains `password`; plugin-auth's own raw-engine
reads get `recoverInternalFieldsForSystemRead`). Its subject names
`objectstack-ai#8676` |
| `objectstack-ai#8734` | 4/2 | 3/1 | `f8eb73601`: the last-admin guard's standing-key
lists are bound to what `resolveAuthzContext` actually reads
(`STANDING_KEYS_BY_TABLE` / `STANDING_KEY_EXCLUSIONS` and the
correspondence gate). Its subject names `objectstack-ai#8734` |
| `objectstack-ai#10165` | 1/1 | 1/0 | `801296050`: lifecycle `ttl` gains an
`onlyWhen` row filter (maintainer ruling option A on `objectstack-ai#10165`, quoted in
its message). The same anchor the spec stages gave this number |
| `objectstack-ai#10366` | 3/2 | 2/1 | `bbe643c08`: the localhost trusted-origin
substitution is gated to non-production. Its diff writes both rewritten
lines and its changeset names `objectstack-ai#10366` |
| `objectstack-ai#11343` | 19/8 | 18/1 | `c0714eb5d`: walled platform-admin elevation
requires a VERIFIED owner-email match (a fail-closed allow-list over
`email_verified`), the bootstrap replays on the verifying `sys_user`
update, and the dev-admin seed stamps its account verified. Its message
names `objectstack-ai#11343` as the card it completes |
| `objectstack-ai#11477` | 6/3 | 3/3 | `6dd3e6968`: `/admin/remove-user` gets the
raw-mount shading `/admin/ban-user` has, so authorization runs before
the break-glass guard (ruled option A on `objectstack-ai#11477`, as its message
records) |
| `objectstack-ai#11626` | 1/1 | 1/0 | `a6eca9223`: `check:engine-double-contract`
admits a single-verb engine double on the contract it DECLARES, a second
admission route beside sibling inference. Its diff names that route
`objectstack-ai#11626` |
| `objectstack-ai#11640` | 11/6 | 7/4 | `bf8d129b5`: a walled deployment whose
declared owner has no verification path gets a loud, named warning at
boot, and boot proceeds (maintainer ruling 2026-08-25, option A). Its
subject names `objectstack-ai#11640` |
| `objectstack-ai#11741` | 4/2 | 2/2 | `b706af987`: `SendEmailInput` gains an optional
`organizationId`, threaded from the producers that hold one (the
invitation among them). The same anchor stages 1 and 2 and the spec
stages gave this number |
| `objectstack-ai#11757` | 4/4 | 4/0 | `4d25d22d4`: the rc.1-era `sys_scim_provider`
platform object is retired. Every `objectstack-ai#11757` site in the tree before it
says the object "retires under objectstack-ai#11757" |
| `objectstack-ai#12029` | 2/2 | 2/0 | `6dd3e6968`: `objectstack-ai#12029` was the pull request
itself; this is its squash commit, the gate-then-delegate mount on
`/admin/remove-user` |
| `objectstack-ai#13398` | 6/2 | 3/3 | `e238c79f0`: the published-sink ruling, that
raising a log level must never widen a published sink. No record of the
ruling exists in the repo; this commit's pin is the earliest text in
history that records it (see Wordings) |
| `objectstack-ai#14762` | 21/4 | 19/2 | `35e94c96b`: auth OTP SMS and auth mail read
the recipient's own `sys_user.locale`, one rung above the request and
the deployment default, in the order ruled for `objectstack-ai#14788`. Its diff
carries `objectstack-ai#14762` 24 times |
| `objectstack-ai#14902` | 3/2 | 3/0 | `61821e54c`: a plain unique index over
duplicate rows is loud and non-fatal (the boot continues), and `os
migrate plan` stops calling it `safe`. Its message names `objectstack-ai#14902` as the
card it ends |
| `objectstack-ai#14998` | 2/1 | 2/0 | `f1e91595f`: the batch-6 admin endpoint graphs
load at module top, not inside each clocked case, which removed the
cold-import timeout flake |
| `objectstack-ai#15092` | 2/1 | 2/0 | `9e9f03abe`: `settleSelfRegistrationGrant`'s
trailing filter no longer silently DROPS a malformed permission-set row;
it refuses. The only commit in history that names `objectstack-ai#15092` |

Plus 5 `objectstack-ai#13398-class` sites the gate does not extract, anchored like the
other `objectstack-ai#13398` sites: `boot-sign-in-reachability.ts:109`, `:512`,
`boot-sign-in-reachability.test.ts:595`, `tenancy-service.ts:249`,
`:257-258`.

Every cited sha matches exactly one commit (`git rev-parse
--disambiguate`, count 1 for each), and every one is an ancestor of the
base (`merge-base --is-ancestor`, exit 0 for all 15; the history is
complete, `--is-shallow-repository` false, 15,083 commits). A
line-origin pickaxe (`git log -S` on each dead line's exact text) found
each line entering either in its anchor commit or in a later commit that
cites that commit's decision: for example `4d5b4f832` (the
operator-provisioned stamp) and `4f65837a7` (the L3 re-anchor) cite
`c0714eb5d`'s verified-owner rule, `f074616e6` (invitation locale) cites
`35e94c96b`'s stored rung, `8064e6da1` (the has-permission mount) cites
`6dd3e6968`'s seam, and `9bd4344e4` carries the
`account-identity-preflight` text that cites `61821e54c`.

## Wordings to check

- **`objectstack-ai#13398` → `e238c79f0`, and not stage 2's `953a81f4a`.** Stage 2
anchored its one `objectstack-ai#13398` site at `953a81f4a` (2026-09-02) as the
earliest application of the published-sink ruling. In this package,
`e238c79f0` (2026-08-31) already records it: its pin in
`durability-swallow-repair.test.ts` says raising the level "means
widening a published sink — refused as actively harmful by the
maintainer's" ruling. It is earlier, and it is in this package, so it is
the anchor here. Its own commit message still calls the level "objectstack-ai#13398's
question", which is why the lines say "the published-sink ruling (commit
e238c79)" rather than claiming that commit made the ruling.
- **Reflow, 11 lines with no dead site** (every file keeps its line
count):
- `auth-manager.ts:7554-7557`: 「routes that LEVEL question to the
published-sink ruling (commit e238c79) and tells this batch to fix the
SILENCE only」, the rest of the paragraph reflowed unchanged (3 lines).
- `durability-swallow-repair.test.ts:36-40` (4 lines) and `:527-529` (2
lines): the same substitution, and 「which routes that question there」
became 「which keeps that question」, because "there" pointed at the
number.
- `tenancy-service.ts:257-258`: 「exactly what the sink ruling (commit
e238c79) forbids」 (1 line).
- `find-envelope-limb-removal.test.ts:47-48`: 「also carried the
silent-DROP shape, and commit 9e9f03a fixed it in the OPPOSITE
direction」 (1 line).
- **A lost referent, 1 line.** `auth-plugin.ts:2738-2739`: 「(the objectstack-ai#12029
worked reading — a shadow is accounted for …)」 became 「(as it read
commit 6dd3e69's remove-user mount — a shadow is accounted for …)」.
`check:auth-mount-ledger` has counted a shadowing mount since
`26dea1495`; the "worked reading" was that PR's application of it to
`/admin/remove-user`, which `6dd3e6968` mounts.
- `sys-session-ttl-sweep.test.ts:230`: 「the naive policy commit
8012960 existed to make avoidable」, where `801296050` is the
`ttl.onlyWhen` filter the ablation removes.
- `durability-swallow-repair.test.ts:62`: the flake report became a
pointer to the commit that removed the flake (`f1e91595f`), with
`objectstack-ai#15603` kept beside it.
- `auth-manager.ts:5629`: 「the pre-objectstack-ai#14762 deployment-default behaviour」
became 「the deployment default, as before commit 35e94c9」.

## The 21 sites left

- **Test titles (21 sites).** `describe` / `it` titles, which are string
tokens: `admin-remove-user-gate-ordering.test.ts:207`, `:263`, `:298`
(`objectstack-ai#11477`), `auth-email-locale.test.ts:528` and
`auth-manager.test.ts:2545` (`objectstack-ai#14762`), `auth-manager.test.ts:1562`
(`objectstack-ai#10366`), `:2866`, `:2880` (`objectstack-ai#11741`), `:4105` and
`internal-field-readback.test.ts:219`, `:230`, `:286` (`objectstack-ai#8676`),
`auth-plugin-walled-owner-verification-path.test.ts:87`, `:193`, `:317`,
`:384` (`objectstack-ai#11640`), `durability-swallow-repair.test.ts:159`, `:567`,
`:670` (`objectstack-ai#13398`), `last-admin-standing-keys.test.ts:61` (`objectstack-ai#8734`) and
`walled-owner-operator-stamp.test.ts:355` (`objectstack-ai#11343`). Tokens, left as
they were, as stages 1 and 2 left theirs.
- There is no non-test string, no generated file and no quoted ruling
carrying a dead number in this package.

## Mechanical guard: no code token moves

The guard compares the TypeScript parser's leaf nodes, with comments as
trivia and JSDoc nodes excluded, base `b80ab579d` against head. Template
literals are therefore read in context. It ran over all 31 touched `.ts`
files.

- Real run: 158,646 base tokens, **0 files with a token change** (exit
0).
- Comment control in `auth-manager.ts` (`As above — the flagged column`
to `Likewise — the flagged column`): 0 files changed, as expected (exit
0).
- Positive control, a code token changed in `auth-manager.ts` (a fourth
element added to the `fields` projection of the password-reuse read):
DIFFER (exit 1).
- Positive control, one digit changed inside a kept test title
(`admin-remove-user-gate-ordering.test.ts:207`): DIFFER (exit 1).

Every mutation went through `scripts/ablation-replace.mjs`, and each
landed (anchor 1 to 0, blob changed). Each restore was proven
byte-identical to the HEAD blob (`c0bdef025a39`, `ec83f09f556e`), with
`git diff HEAD` empty and a clean tree afterwards.

## Changeset

This change ships bytes, so a `patch` changeset for
`@objectstack/plugin-auth`
(`.changeset/20596-plugin-auth-provenance-anchors.md`) is included. It
says only that the provenance comments were re-anchored.

Measured on the built package (A3): `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After the build, the rewritten comments reach `dist`:
`35e94c96b` appears 8 times in each of `dist/index.d.ts`, `index.d.mts`,
`index.js` and `index.mjs`; `f8eb73601` twice in each declaration file;
`bf8d129b5` and `e238c79f0` once in each of the four; `d6e80b28b` and
`4d25d22d4` twice in each runtime file; `c0714eb5d` and `61821e54c` once
in each declaration file; `b706af987` once in each runtime file.
Positive control: the unchanged line 「read best-effort off the identity
row.」 beside a shipped rewrite is found once in `index.d.ts` and once in
`index.js`. A never-written negative phrase appears nowhere. No dead
number of the 16 is left anywhere in `dist`.

## Gates (head `5ae64e8b8`)

- **Citation judging, as CI runs it:** `pnpm check:issue-citations`
(self-test) exits 0. `node scripts/check-issue-citations.mjs` exits 0:
the diff-scoped run judged 5 citations across 14 files, and all 5
resolve.
- **Doc authoring:** `pnpm check:doc-authoring` exits 0, with the
sibling-package prose ids at their baseline and no growth.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands
--repo objectstack-ai/objectstack` at `5ae64e8b8` derived 65 commands:
all 57 derived at dispatch, plus `check:duration-unit-keys`,
`check:engine-double-contract`, `check:logger-receiver-detach`,
`check:objectql-double-limit`, `check:query-options-erasure`,
`check:type-check-coverage`, `check:type-check-debt` and
`check:where-matcher`. It was re-derived after a fresh `git fetch`
(`origin/main` `a918fe7fd`, 2 commits ahead, neither touching
`plugin-auth`): the same 65. Each ran with its exit code captured before
any pipe, and all 65 exit 0. `--ran`, fed each command with its exit
code, reports 65 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.
- **Tests and typecheck, under the verify lock:**
- `pnpm --filter @objectstack/plugin-auth test`: 115 files and 2,464
tests pass. That is every test file in the package, the 17 touched ones
included.
- `pnpm --filter @objectstack/plugin-auth typecheck` exits 0 (`tsc`
main, `tsconfig.examples.json`, and `check:test-typecheck` held at its
ledger). The main program reads 63 non-test files; the
`tsconfig.test.json` program reads all 178 files under `src/`, the 115
test files included, and all 31 touched files are in it (`--listFiles`).
- **Lint, as a proven narrowing:** `eslint --no-inline-config --format
json` over the 31 touched `.ts` files gives 31 files, 0 errors and 0
warnings. All 31 are in eslint's own population (`isPathIgnored` is
false for each). `eslint.config.mjs` never enables type-aware linting
(no `parserOptions.project`, as its own line 328 states), 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 32 changed files for control bytes finds none.

## Acceptance notes

- **The gate's extractor does not see a hyphen-joined number.**
`CITATION_RE` ends in a lookahead that refuses a following hyphen, so
`objectstack-ai#13398-class` is not a citation to either the diff gate or the census,
dead or alive. This stage rewrote the 5 such sites in `plugin-auth`
because they are the same dead number in the same comment prose. At the
head, 10 dead `#N-word` sites remain in `packages/**/src` (a raw line
scan of `.ts` files against the cached board): `service-automation` 5
(all `objectstack-ai#13398-class`), `rest` 2, `plugin-security` 1, `runtime` 1, `spec`
1. The census cannot count them, so a later stage reaching those
packages has to look for them by hand. No instrument change here.
- **The census instrument did not truncate in this stage.** Three
enumerations read 185 pages each at the newest frontier.
- **Anchors the next stages can reuse.** These numbers stand elsewhere
on the census at the head: `objectstack-ai#11343` in `plugin-security` (6) and `types`
(2), anchor `c0714eb5d`; `objectstack-ai#14902` in `driver-sql` (7) and `cli` (1),
anchor `61821e54c`; `objectstack-ai#13398` in `service-automation` (4, plus the 5
hyphen-joined sites), anchor `e238c79f0`; `objectstack-ai#8734` in `core` (2), anchor
`f8eb73601`; `objectstack-ai#10165` in `objectql` (2) and `platform-objects` (1),
anchor `801296050`; `objectstack-ai#11757` in `platform-objects` (2), anchor
`4d25d22d4`; `objectstack-ai#11741` in `plugin-email` (2), anchor `b706af987`; `objectstack-ai#8676`
in `platform-objects` (1), anchor `d6e80b28b`.
- **Base.** The branch is 2 commits behind `origin/main` (`a918fe7fd`,
read at 11:20Z). Neither touches `plugin-auth`, this changeset or any of
these 16 numbers, so there was no merge.

---
_Generated by [Claude
Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_

---------

Co-authored-by: Claude <noreply@anthropic.com>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… to the commits that decided them (objectstack-ai#20632)

Part of objectstack-ai#20594
Clause-②: no

## What changed

This is stage 2 of the `domain:cli` lane of the dead-citation sweep:
`packages/rest/src/**`. Every comment or docblock site in scope that
cited a tracker number answering 404 now cites, in ruling C+D's form C
(comment 5749154545 on objectstack-ai#19123), the commit in this repository's history
that decided what the line describes, and says in its own words what
that commit decided. PR objectstack-ai#20533 is the method and PR objectstack-ai#20624 (stage 1,
`packages/runtime`) the precedent this follows line for line. Later
stages cover `cli`, `types` and the rest of the lane, so this PR says
`Part of` and the card stays open.

That is **457 comment sites on 445 lines in 85 files, covering 74
numbers**: the census's 191 sites, 256 more in test comments (which the
census defers), and 10 sites whose dead number is the second half of a
slash-joined pair the citation grammar does not read (`objectstack-ai#3984/objectstack-ai#6241`,
`objectstack-ai#9901/objectstack-ai#10255` four times, `objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292`, `objectstack-ai#11235/objectstack-ai#11242`
twice, `objectstack-ai#10993/objectstack-ai#11242`, `objectstack-ai#7543/objectstack-ai#15071`). Each rewritten line cites one
of **70 distinct commits**.

ADR-0076 D11 is the only ADR that records any of these numbers, and it
records objectstack-ai#8850 only as the extraction it names as landed in `8664a2c99`,
so that commit is the anchor there. No other ADR or ruling-record file
in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any
of these numbers, so every anchor is a commit. The anchors the landed
stages already gave the same numbers are reused where the rest sites
describe the same decision (30 numbers, for example `79c46da90` for
objectstack-ai#9934, `7986d973f` / `311433f6b` for the compound-name retirement,
`6a180e42d` for objectstack-ai#13279 and `cf6e0a193` for objectstack-ai#15071), so each number
carries one anchor across the tree.

Only comments changed. Every touched file keeps its line count (451
lines out, 451 in, over 85 files), so no line citation into these files
moves. Six of the 451 lines held no dead site; each is the other half of
a sentence that had to change:
- `discovery-schema-conformance.test.ts:343` (「(reaffirmed by」 to
「(which commits」, because line 344 now names the two commits that landed
the ruling),
- `package-door-16019-raw-statement-fault-code.test.ts:51` and
`error-response.ts:1485` (a trailing 「PR」 whose number wrapped onto the
next line),
- `error-response-structured-arm-door-parity.test.ts:463` (「That card
added the limb」 to 「That commit」, because line 459's tag now names the
commit),
- `rest-hook-script-fault-envelope.test.ts:331` (「both sides of that
card」 to 「that fix」),
- `rest-server.ts:908` (「(objectstack-ai#14409, landed」 to 「(landed as commit」, the
sha `3ecb7dc1a` already standing on line 909).

**No citation number is added.** Every tracker number on an added line
was already on the line it replaces. No PR number stands on an added
line. One of the 70 shas is on a removed line, and it was there before:
`rest-14078-invalid-date-total-arm.test.ts:19` read 「PR objectstack-ai#14409 (landed
`3ecb7dc1a`)」 and now reads 「Commit 3ecb7dc drove」. No code token
moves (see the guard below).

Three dead comment sites are left on purpose, listed under "The sites
left". One more file: a `patch` changeset for `@objectstack/rest`,
because the rewritten docblocks ship (see Changeset below).

## Census: `packages/rest`, before and after

**Instrument.** The gate's own `node scripts/check-issue-citations.mjs
--census --json`, read-only and unchanged, run with the fleet token. Its
surface is comment prose in `packages/**/src/**/*.ts` with string
literals blanked, and it defers `*.test.ts`. The count is its
`allocated-but-absent` findings under `packages/rest/`. Both runs
enumerated the whole board (185 pages), so neither read a truncated
board.

| reading | tree | board | whole-repo `allocated-but-absent` | rest
sites | lines | files | numbers |
|---|---|---|---|---|---|---|---|
| before | base `a186aea996`, run 2026-09-29T10:28:18Z to 10:36:06Z |
enumerated, 185 pages, frontier objectstack-ai#20628, 18,455 numbers | 2,015 | **191**
| 186 | 14 | 51 |
| after | head `93e4d69ba6`, run 11:11:30Z to 11:17:37Z | enumerated,
185 pages, frontier objectstack-ai#20630, 18,457 numbers | 1,764 | **0** | 0 | 0 | 0 |

The before count equals the card's 191 at `f11b5f20a2`. The whole-repo
drop is 251: this diff's 191, plus the 60 of PR objectstack-ai#20626
(`packages/plugins/plugin-sharing`, 63 to 3), which landed on `main` in
between and came in with the merge. No other package moved.

**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 `classifyCitation` over
every `.ts` file under `packages/rest/src` (256 files), against the
board enumerated through the gate's own `enumerateBoard`. The lit
controls objectstack-ai#20594, objectstack-ai#19123 and objectstack-ai#20624 answered 200 and are on both boards;
the dead controls objectstack-ai#13214, objectstack-ai#14541 and objectstack-ai#15071 answered 404 and are on
neither.

| reading | tree | board | citations | dead | src comment | test comment
| src string | test string |
|---|---|---|---|---|---|---|---|---|
| before, 10:29Z | `a186aea996` | 185 pages, frontier objectstack-ai#20628 | 4,620 |
**577** | 191 | 259 | 1 | 126 |
| after, 11:21Z | `93e4d69ba6` | 185 pages, frontier objectstack-ai#20631 | 4,174 |
**130** | 0 | 3 | 1 | 126 |

Its src-comment column equals the census's 191 and 0, which is the
control on the second instrument, and a site-by-site comparison of the
two before-readings is identical. Resolving comment citations move by
one (1,364 to 1,365 in src): `(objectstack-ai#10993/objectstack-ai#11235/objectstack-ai#11292)` became `(objectstack-ai#10993,
commit 376c70f, objectstack-ai#11292)`, so the grammar now reads the live `objectstack-ai#11292`
that the slash hid. The drop is 447 grammar-read sites; the other 10
rewritten sites are the slash-joined ones the grammar never read.

Separately, every one of the 77 numbers was probed on its web endpoint:
76 answer 404 (deleted) and one, #14026, answers 302 to
objectstack-ai/objectui#10102 (transferred), which is why it is left
(see below).

## Per-number table

Sites and files are the dead comment sites in scope at the base, tests
and slash-joined halves included. `left` is a site with no deciding
commit (see below). `strings kept` counts string-literal sites, which
are tokens and stay as they were. Every anchor was read in its message
or its diff, not only in its subject: it is the commit that made the
change the line describes, and its own message or diff names the number
it replaces or adds the citation the line carries.

| number | comment sites / files | rewritten | left | strings kept |
anchor |
|---|---|---|---|---|---|
| `objectstack-ai#6037` | 5/3 | 5 | 0 | 0 | `18189983d` |
| `objectstack-ai#6122` | 2/2 | 2 | 0 | 0 | `64cd01082` |
| `objectstack-ai#6206` | 1/1 | 1 | 0 | 0 | `8e13ca876` |
| `objectstack-ai#6216` | 6/2 | 6 | 0 | 2 | `f586f1a89` |
| `objectstack-ai#6241` | 10/3 (1 slash-joined) | 10 | 0 | 1 | `83a3b1f2e` |
| `objectstack-ai#6259` | 2/1 | 2 | 0 | 0 | `6968885ef` |
| `objectstack-ai#6303` | 1/1 | 1 | 0 | 0 | `465c5fc14` |
| `objectstack-ai#6306` | 9/5 | 9 | 0 | 3 | `fec784863` |
| `objectstack-ai#6307` | 4/2 | 4 | 0 | 0 | `293476148` |
| `objectstack-ai#6349` | 4/2 | 4 | 0 | 4 | `2443bb4c4` |
| `objectstack-ai#6474` | 1/1 | 1 | 0 | 0 | `18189983d` |
| `objectstack-ai#6535` | 3/2 | 3 | 0 | 0 | `a92b1793c` |
| `objectstack-ai#6640` | 1/1 | 1 | 0 | 1 | `2ab1257c9` |
| `objectstack-ai#6704` | 5/1 | 5 | 0 | 1 | `c3f491626` |
| `objectstack-ai#8641` | 1/1 | 0 | 1 | 0 | — |
| `objectstack-ai#8850` | 3/3 | 3 | 0 | 0 | `8664a2c99` |
| `objectstack-ai#8885` | 6/3 | 6 | 0 | 3 | `30b1c636a` |
| `objectstack-ai#8919` | 7/3 | 7 | 0 | 7 | `b5378550e` |
| `objectstack-ai#9741` | 12/1 | 12 | 0 | 0 | `2a29caa53` |
| `objectstack-ai#9805` | 1/1 | 1 | 0 | 0 | `45862a53d` |
| `objectstack-ai#9934` | 19/10 | 19 | 0 | 4 | `79c46da90` |
| `objectstack-ai#9967` | 2/2 | 2 | 0 | 4 | `8f266f1cd` |
| `objectstack-ai#10063` | 2/2 | 2 | 0 | 1 | `9e04c3e35` |
| `objectstack-ai#10178` | 1/1 | 1 | 0 | 0 | `38cf397ea` |
| `objectstack-ai#10179` | 0/0 | 0 | 0 | 1 |  |
| `objectstack-ai#10255` | 18/4 (4 slash-joined) | 18 | 0 | 2 | `6ce58a735` |
| `objectstack-ai#10340` | 13/3 | 13 | 0 | 2 | `26f3588fb` |
| `objectstack-ai#10345` | 13/6 | 13 | 0 | 6 | `cad8b42f0` |
| `objectstack-ai#10350` | 1/1 | 1 | 0 | 0 | `490879ad0` |
| `objectstack-ai#10485` | 2/1 | 2 | 0 | 1 | `35ad101bc` |
| `objectstack-ai#10537` | 9/3 | 9 | 0 | 1 | `e634ecf6a` |
| `objectstack-ai#10888` | 2/2 | 2 | 0 | 0 | `d806081dd` |
| `objectstack-ai#11006` | 3/1 | 3 | 0 | 0 | `cccbe51bf` |
| `objectstack-ai#11130` | 1/1 | 1 | 0 | 0 | `851909530` |
| `objectstack-ai#11235` | 4/2 (1 slash-joined) | 4 | 0 | 0 | `376c70f98` |
| `objectstack-ai#11242` | 3/2 (3 slash-joined) | 3 | 0 | 0 | `98ea3443f` |
| `objectstack-ai#12144` | 1/1 | 1 | 0 | 0 | `3a04b0125` |
| `objectstack-ai#12176` | 11/7 | 11 | 0 | 2 | `7986d973f` |
| `objectstack-ai#12194` | 15/5 | 15 | 0 | 4 | `311433f6b` |
| `objectstack-ai#12195` | 35/16 | 35 | 0 | 7 | `7986d973f` |
| `objectstack-ai#13182` | 2/2 | 2 | 0 | 0 | `5b3ff63cc` |
| `objectstack-ai#13197` | 1/1 | 1 | 0 | 0 | `56c093c4d` |
| `objectstack-ai#13213` | 2/1 | 2 | 0 | 0 | `4801296e7` |
| `objectstack-ai#13214` | 18/6 | 18 | 0 | 14 | `cc837dbfe`, `889ec5b42`, `3d10755f0`
|
| `objectstack-ai#13244` | 5/2 | 5 | 0 | 1 | `889ec5b42` |
| `objectstack-ai#13255` | 4/1 | 4 | 0 | 6 | `43028a8f8` |
| `objectstack-ai#13258` | 1/1 | 1 | 0 | 0 | `3d10755f0` |
| `objectstack-ai#13279` | 23/5 | 23 | 0 | 5 | `6a180e42d` |
| `objectstack-ai#13280` | 13/4 | 13 | 0 | 2 | `add6a1b1c` |
| `objectstack-ai#13282` | 1/1 | 1 | 0 | 0 | `43028a8f8` |
| `objectstack-ai#13377` | 3/2 | 3 | 0 | 0 | `e10cf3444` |
| `objectstack-ai#13378` | 2/1 | 2 | 0 | 0 | `82faea03f` |
| `objectstack-ai#13454` | 1/1 | 1 | 0 | 0 | `7ad57e17a` |
| `#14026` | 1/1 | 0 | 1 | 0 | — |
| `objectstack-ai#14365` | 1/1 | 0 | 1 | 0 | — |
| `objectstack-ai#14366` | 14/4 | 14 | 0 | 2 | `53cbad9f7` |
| `objectstack-ai#14369` | 3/2 | 3 | 0 | 0 | `a3d5724c8`, `53cbad9f7` |
| `objectstack-ai#14389` | 7/3 | 7 | 0 | 7 | `10220a7bf` |
| `objectstack-ai#14390` | 1/1 | 1 | 0 | 0 | `9d7f7259f` |
| `objectstack-ai#14409` | 2/2 | 2 | 0 | 0 | `3ecb7dc1a` |
| `objectstack-ai#14541` | 27/4 | 27 | 0 | 5 | `6d178a408` |
| `objectstack-ai#14613` | 2/2 | 2 | 0 | 0 | `81208086a` |
| `objectstack-ai#14677` | 1/1 | 1 | 0 | 0 | `a4e4d2d78` |
| `objectstack-ai#14683` | 8/2 | 8 | 0 | 0 | `96326040f` |
| `objectstack-ai#14691` | 15/2 | 15 | 0 | 2 | `b3a63d32c` |
| `objectstack-ai#14704` | 9/3 | 9 | 0 | 2 | `1c7adc73d` |
| `objectstack-ai#14723` | 7/4 | 7 | 0 | 4 | `65846bc46` |
| `objectstack-ai#14725` | 3/3 | 3 | 0 | 2 | `f5cc78b63` |
| `objectstack-ai#14849` | 3/1 | 3 | 0 | 0 | `226e72443` |
| `objectstack-ai#14907` | 1/1 | 1 | 0 | 0 | `e1d4f9e3f` |
| `objectstack-ai#14908` | 1/1 | 1 | 0 | 0 | `d5cbb44f3` |
| `objectstack-ai#15021` | 2/1 | 2 | 0 | 8 | `cc238db8b` |
| `objectstack-ai#15034` | 6/2 | 6 | 0 | 0 | `abf9101f1` |
| `objectstack-ai#15065` | 1/1 | 1 | 0 | 0 | `1c7adc73d` |
| `objectstack-ai#15071` | 23/4 (1 slash-joined) | 23 | 0 | 3 | `cf6e0a193` |
| `objectstack-ai#16650` | 1/1 | 1 | 0 | 0 | `001a83b04` |
| `objectstack-ai#17058` | 3/1 | 3 | 0 | 4 | `94c930248` |
| `objectstack-ai#18546` | 3/2 | 3 | 0 | 3 | `58f60e37e` |
| **total** | **460** | **457** | **3** | **127** | **70 distinct
commits** |

Every cited sha matches exactly one object (`git rev-parse
--disambiguate`, count 1 for each of the 70), is a commit, has one
parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit
0 for all 70). The checkout is not shallow (`--is-shallow-repository`
false); the control leg `13a6cb4ad` exits 0 and the negative control
(this branch's first WIP commit, not on `main`) exits 1. Several numbers
are the PR number of their own anchor commit (objectstack-ai#6122, objectstack-ai#6303, objectstack-ai#6474,
objectstack-ai#11242, objectstack-ai#13213, objectstack-ai#13244, objectstack-ai#13258, objectstack-ai#13282, objectstack-ai#14409, objectstack-ai#14677, objectstack-ai#14908, objectstack-ai#15065,
objectstack-ai#16650), so the sha is the same object the number named.

**Numbers with more than one anchor, by site:**
- `objectstack-ai#13214` (18 sites) was one card with three commits. `cc837dbfe` (the
ownership gate, the 2026-08-30 ruling) for the 11 sites that describe
the gate; `889ec5b42` for the 5 in
`ui-view-route-identity.measurement.test.ts`, the identity measurement
it created; `3d10755f0` for the tenancy file's header, the measurement
it created; and `rest-server.ts:2247`, 「Driven and reported on objectstack-ai#13214
(PRs objectstack-ai#13244, objectstack-ai#13258)」, now reads 「Measured in commits 889ec5b
(identity) and 3d10755 (tenancy)」: those PRs are exactly those two
commits.
- `objectstack-ai#14369` (3 sites): `a3d5724c8` (the liveness census it recorded) for
`rest-server.ts:1172` and `rest-sub-config-parse-not-cast.test.ts:48`.
`rest-server.ts:4092` said the zero read sites of `api.documentation` /
`api.responseFormat` came from 「the objectstack-ai#14369 census」, but `a3d5724c8`
explicitly left `api` out of that census; the zero was measured by
`53cbad9f7` (its changeset: no other read site for either key), which is
the anchor there.
- `objectstack-ai#11235` / `objectstack-ai#11242` / `objectstack-ai#10993`: `376c70f98` derives the discovery
`version` in metadata-protocol (objectstack-ai#11235), and `98ea3443f` is objectstack-ai#11242's own
squash, which landed the objectstack-ai#10993 ruling on `/health` and the dispatcher's
`/discovery`. So 「the objectstack-ai#10993 ruling … reaffirmed by objectstack-ai#11235/objectstack-ai#11242」 now
reads 「the objectstack-ai#10993 ruling, landed by commits 98ea344 and 376c70f」
(`rest-server.ts:4528`, `discovery-schema-conformance.test.ts:343-344`).
`objectstack-ai#10993`, `objectstack-ai#11292` and `objectstack-ai#11297` answer 200 and stay.
- `objectstack-ai#6037` / `objectstack-ai#6474`: one commit, `18189983d` (objectstack-ai#6474 is its PR number),
so 「(objectstack-ai#6037 / PR objectstack-ai#6474)」 became 「(commit 1818998)」.

**Wordings to check, each true of its commit:**
- A commit does not rule. Where a line said a number ruled, it now says
what the commit did with the ruling: 「the ruling commit 79c46da landed
says it does」, 「the ruling commit cf6e0a1 implemented fences it」, 「the
ruling commit 10220a7 implemented」, 「the 2026-08-20 ruling, landed as
commit 6ce58a7」, 「recorded in commit 6ce58a7's message (option A)」
(its message reads 「Ruled on objectstack-ai#10255 (2026-08-20, option A)」), and
「question was ruled on 2026-08-20 and landed as commit 6ce58a7」 where
the line said 「filed as objectstack-ai#10255」.
- `objectstack-ai#14541`'s contract review: 「the objectstack-ai#14541 contract review (condition N)」
now reads 「the contract review of commit 6d178a4 (condition N)」; that
commit's message lists the conditions it carries. 「objectstack-ai#14541's §4」 and
「objectstack-ai#14541 §5」 in
`error-response-generic-passthrough-object-parity.test.ts` are sections
of `error-response-structured-arm-door-parity.test.ts` (the file
`6d178a408` created), so they now name that file. 「measured on the
objectstack-ai#14541 branch」 reads 「on the branch that landed as commit 6d178a4」.
- A line that named a DEFECT by its number now says so: 「Before commit
9e04c3e the draft→active promotion door could not…」, 「Before commit
26f3588 the `/meta` doors decided ORGANIZATION SCOPE from the RAW
url」, 「the defect commit 2443bb4 fixed」 and 「would be the defect
commit 26f3588 fixed」.
- `objectstack-ai#13255`: 「As written for objectstack-ai#13255 this file repaired nothing」 reads 「As
first written (commit 43028a8)」, the commit that created the file and
answered the measurement; 「CONTEXT-LOST family (objectstack-ai#13255), still unruled」
reads 「first measured by commit 43028a8」 (the ruling on that family
never landed, which the line still says).
- `objectstack-ai#13214` in the identity file: 「the half objectstack-ai#13214 marks UNMEASURED」
reads 「the half left UNMEASURED until commit 889ec5b」, and 「objectstack-ai#13214
asks for an INDEPENDENT reproduction」 reads 「commit 889ec5b is an
INDEPENDENT reproduction」.
- 「the objectstack-ai#8885 sweep」 reads 「the sweep behind commit 30b1c63」, the
commit that registered the 9 codes the sweep found; 「objectstack-ai#14849 predicted」
reads 「The card behind commit 226e724 predicted」; 「the hazard objectstack-ai#13377
names」 reads 「the hazard commit e10cf34 was written to remove」; 「The
concrete harm objectstack-ai#6704 names」 reads 「removed」.
- Quoted ruling: `error-response-sandbox-arm-message.test.ts:340` sits
inside a verbatim ruling quote, so the commit stands in an editorial
bracket (「not from [commit 1c7adc7]'s list」), as PR objectstack-ai#20624 did.
- Two markdown tables in comments
(`meta-state-route-engine-outage.test.ts:76`,
`objectql-slot-consumer-census.test.ts:43`): the rewritten cell is wider
than its column, and its padding is reduced rather than widening the
four sibling rows.

## The sites left

**No deciding commit (3 sites, all in test files, so the census does not
see them):**
- `meta-object-owd-gate.test.ts:516` (objectstack-ai#8641): 「whether it should stay is
objectstack-ai#8641's question」, an open decision. The commit that added the citation
calls it a pointer to the open decision card, and no commit decides it.
- `rest-sub-config-parse-not-cast.test.ts:321` (objectstack-ai#14365): the
`z.partialRecord` question 「deferred to objectstack-ai#14365」 was never taken (`git
log -S partialRecord`); `b3a63d32c` made it moot by retiring the record,
which the other half of the same line now cites.
- `import-integration.test.ts:1043` (#14026): not deleted, TRANSFERRED.
The web endpoint answers 302 to objectstack-ai/objectui#10102, the REST
read follows the redirect, and the board enumeration does not list it,
so the census and the supplementary reading both class it
`allocated-but-absent`. The line says how an issue was raised; no commit
decides that, so form C has nothing to cite.

**String sites kept as tokens (127).** 126 are test titles and test-code
strings in 41 files. One is a non-test string: the `note` field of the
REST route ledger's `GET /api/v1/meta/object/:name/state/:field` row at
`rest-route-ledger.ts:290`, which ends 「(objectstack-ai#10179)」 (see Acceptance
notes).

## Mechanical guard: no code token moves

The check compares the TypeScript parser's leaf tokens (TypeScript
6.0.3, JSDoc nodes excluded, comments being trivia) of each touched file
at base `a186aea996` against the working tree at `93e4d69ba6`, over all
85 touched `.ts` files. Controls mutate the head text in memory only, so
nothing on disk moved for them.

- Real run: 272,653 base tokens, **0 files with a token change** (exit
0).
- Comment-insertion control (`error-response.ts`): 0 files changed (exit
0).
- Code-insertion positive control (a declaration in the same file):
DIFFER at token 0 (exit 1).
- String positive control (the first string literal past offset 2000 of
the same file, one character added inside it): DIFFER at token 26 (exit
1).

Line balance: every touched file is +N/−N (451/451), and every line
count is equal at base and head. A raw scan of the 86 changed files for
control bytes finds none (its positive control on a scratch file with a
U+0001 byte matches).

## Changeset

This change ships bytes, so a `patch` changeset for `@objectstack/rest`
is included, in PR objectstack-ai#20624's form and level. It says only that the
provenance comments were re-anchored.

Measured on the built package: `files[]` is `dist`, `README.md` and
`CHANGELOG.md`. After `pnpm --filter @objectstack/rest build`, the
rewritten docblocks reach `dist`: for example `53cbad9f7` appears 4
times in `dist/index.d.ts`, and `26f3588fb` 8 times and `b3a63d32c` 5
times in `dist/index.js`. The positive control, the unchanged sentence
「It was VALIDATE-ONLY from objectstack-ai#11637」 of the same `rest-server.ts` docblock
whose first line now reads 「[commit 53cbad9] The parsed output is
CONSUMED」, is in `dist/index.d.ts` beside it; a negative control phrase
appears nowhere.

## Gates (head `93e4d69ba6`)

This host has no `flock`, so `os-verify-lock.sh` ran in its declared
unlocked mode. Its disclosure, verbatim, from each locked run at this
head:

```text
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 47s · declare it in the PR body · pnpm --filter '@objectstack/rest...' build
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 102s (1m42s) · declare it in the PR body · pnpm exec turbo run build --filter='./packages/*' --filter='./packages/*/*' --concurrency=4
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 76s (1m16s) · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project local --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 2s · declare it in the PR body · pnpm --filter @objectstack/rest exec vitest run --project repo --maxWorkers=2
os-verify-lock: VERDICT command-exit 0 · UNLOCKED (declared) · no usable `flock` on this host, so the shared verify lock was NEVER taken and NOTHING was serialized · ran 9s · declare it in the PR body · pnpm --filter @objectstack/rest typecheck
```

The branch merged `origin/main` once (`93e4d69ba6`, merging
`542670da6d`) before these runs, as the dispatch orders; `origin/main`
has not moved since (read at 11:19Z). The merge brought PR objectstack-ai#20626 and PR
objectstack-ai#20587 and touched none of this diff's files. The dependency closure was
built first (`pnpm --filter '@objectstack/rest...' build`, 26 packages),
then the whole workspace (`turbo run build --filter='./packages/*'
--filter='./packages/*/*'`, 71 tasks, 71 successful).

- **Tests:** `vitest run --project local`: 227 files, 4,382 tests
passed, 50 skipped. `--project repo` (which holds the touched
`meta-state-route-doc-spelling.test.ts`): 1 file, 8 tests passed.
Together they are all 228 test files of the package, so every touched
test file ran.
- **Typecheck:** `pnpm --filter @objectstack/rest typecheck` exits 0.
`tsc --listFiles` counts 28 `src` files (no tests) under `tsconfig.json`
and all 228 test files under `tsconfig.test.json`, which
`check:test-typecheck` judges: 0 files, 0 errors, 0 pinned signatures in
the ledger.
- **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`)
exits 0 at `93e4d69ba6` (2026-09-29T11:19:30Z to 11:20:00Z). Not
narrowed.
- **Citation judging:** `node scripts/check-issue-citations.mjs --base
origin/main` exits 0: 19 citations judged across 14 files (18 resolve, 1
resolves as a pull request). These are the live numbers that stay on
rewritten lines. It defers `*.test.ts`, so the added-minus-removed count
over the whole diff covers the rest: 0 numbers added.
- **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at `93e4d69ba6` derived 68
families. All 68 exit 0, and `--ran` over a record carrying each exit
code reads 「68 derived, 68 run, 0 NOT-MEASURED, 0 UNRUN」 (a derived
zero).
- `check:dual-build-cjs-loads` and `check:type-check-debt` first exited
3 (PREREQUISITE NOT MET, nothing measured) on the closure-only build;
after the whole-workspace build both exited 0.
- Among them: `check:doc-authoring`, `check:nul-bytes`,
`check:rest-log-declared`, `check:route-envelope`,
`check:system-context-census` (106 elevation read sites, the page's 102
symbols held) and `check:issue-citations` (self-test).
- **Artifact rosters:** 33 of the 36 non-self-test roster rows exit 0 at
`93e4d69ba6`, `check-changeset-fixed` (the one whose roster sits under
`.changeset/`) and `check:route-ledger-census` among them. The other
three, `check-closing-target-claim`, `check-partof-closing-keyword` and
`check-single-claim-paths`, answer 「NOT WIRED」 (exit 2) without a pull
request's context; they are run against this PR once it exists and
reported on the card.

## Hypotheses (measured first)

- **H0 holds.** The filtered census answers 191 dead sites at
`a186aea996` (186 lines, 14 files, 51 numbers), equal to the card's
count at `f11b5f20a2`: no net drift, although PR objectstack-ai#20601 (merged as
`eb4b17c346`, before this base) touches four files in `packages/rest`.
- **H1 holds.** After the rewrite the filtered census answers 0. The
supplementary reading leaves 3 test-comment sites, the three listed
above: an open decision, an untaken option and a transferred issue, none
with a deciding commit. No site was held for an open PR: the claim's
read and this stage's two reads of the open PRs' file lists (10:27:35Z,
7 open PRs; 11:30:34Z, 8 open PRs) found none touching `packages/rest`.
- **H2 holds, by the token guard.** A comment-stripped comparison of
every touched file (the parser's leaf tokens, JSDoc excluded) is empty,
and its code and string controls fire. The emitted `dist` is not
byte-identical, because the docblocks ship, which is why the changeset
is `patch`.

## Acceptance notes

- **Form D, not touched here.** 127 dead numbers stand inside string
literals: 126 in test titles and test-code strings, and one in the
`note` of the REST route ledger's legal-next-state row
(`rest-route-ledger.ts:290`, 「(objectstack-ai#10179)」), which is ledger data, not an
author-shown refusal. Ruling D (no number, the lesson in words) is a
string change outside this comment-only scope; the card already carries
a form-D stage for the lane.
- **A transferred issue among the 404s.** #14026 answers 302 to
objectstack-ai/objectui#10102 on its web endpoint. The census classes it
`allocated-but-absent` (deleted and transferred are only told apart
under `--probe-cause`), and `scripts/check-issue-citations.mjs`'s header
says the `transferred` arm has no positive specimen on this tree; this
is one. Noted, not filed.
- **The grammar does not read a slash-joined number.** `CITATION_RE`
refuses a `#` preceded by `/`, so the second number of `#A/#B` is never
judged. In `packages/rest/src` six such dead numbers stood at 10 comment
sites, all rewritten here; one more, `objectstack-ai#14389` in `objectstack-ai#14095/objectstack-ai#14389`, stands
inside a string
(`error-response-structured-arm-door-parity.test.ts:187`) and is kept.
The same shape PR objectstack-ai#20624 and PR objectstack-ai#20612 reported. Noted, not filed.
- **Outside the scope and the census surface.**
`packages/rest/vitest.config.ts:21` cites objectstack-ai#17853, which answers 404;
`packages/rest/test-typecheck-debt.json`, written by
`gen:test-typecheck-debt`, carries objectstack-ai#13470, objectstack-ai#13454, objectstack-ai#13377 and objectstack-ai#13378 in
its prose, all 404. Neither is under `src/**`. The other numbers in
`vitest.config.ts`, `tsconfig.json` and `tsconfig.test.json` answer 200.
- **Two comments stale on their own, not touched.** The anchor research
found `rest-server.ts`'s `api` docblock near `:1115` and the 「zero read
sites」 sentence at `:4092` both overtaken by `80153f5a4`, whose own
acceptance notes record it. This PR re-anchors their citations and
leaves their claims alone.
- **An attribution corrected by the anchor.** `rest-server.ts:4092`
credited its zero-read-site count to 「the objectstack-ai#14369 census」, which
(`a3d5724c8`) excluded `api`; it now cites `53cbad9f7`, the commit that
measured it.
- **Base.** One merge of `origin/main` (`93e4d69ba6`) before the `--base
origin/main` run, as the dispatch orders.

## Deviations

- Ten sites beyond the census's read grammar carry a slash-joined dead
number and are rewritten; six more lines are the other half of a
rewritten sentence (listed under What changed).
- The whole-workspace build ran with `--concurrency=4`, not 2, to stay
inside the ten-minute foreground cap on this host; it took 1m42s.
- Anchor research for 33 of the 77 numbers ran in three read-only
research subagents; every proposal was verified here against the
commit's message or diff, and the wording of each changed line was
reviewed and corrected by hand in a second pass.
- Commit trailers are AGENTS.md's model-free pair (`Claude-Session` plus
`Co-authored-by: Claude`), and the pre-push trailer check passed on
every push. The merge commit carries git's default message.

---
_Generated by [Claude
Code](https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289)_

---------

Co-authored-by: Jack Zhuang <50353452+hotlong@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
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/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants