Repository navigation
feat(spec): export objectNavTargetExclusivity; state no precedence order in the filters docblock - #16862
Conversation
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
…the mid-line attachment Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
…der in the filters docblock Export the object-nav target-exclusivity check as a function so a hand-written mirror can chain the schema's own rule. NavigationItemSchema keeps its mount on the type: 'object' branch; ObjectNavItemSchema still carries no check — no accept set moves, the public surface gains one export. The filters docblock no longer states a complete precedence order above the sentence that declares the combination unrepresentable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
📓 Docs Drift Check2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 131 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e83a2bf745644bfac31160ccd6d4b39233569584 && git checkout e83a2bf745644bfac31160ccd6d4b39233569584
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 12babac137cc826fa5ed888ce63c266c4d219ce3 bf41b3f60aec1a7f6edbd812790a781d860fb62b && git checkout -B drift-repro 12babac137cc826fa5ed888ce63c266c4d219ce3 && git merge --no-ff bf41b3f60aec1a7f6edbd812790a781d860fb62b
node scripts/docs-audit/affected-docs.mjs --json 12babac137cc826fa5ed888ce63c266c4d219ce3 |
Contract review (
|
Seat status at 2026-09-08T12:29Z — CI green on
|
Contract review — VERDICT: PASS WITH FINDINGS, BINDING: none (at
|
| # | Row | Kind | What settled it |
|---|---|---|---|
| 1 | No published schema's accept set moves — the load-bearing claim | MEASURED | The only changed lines in app.zod.ts are the declaration form (const … = ( → export function …(, ): void => { → ): void {, }; → }). No body line and no mount line is in the diff. One probe run at merge-base and at head gives a 13-row accept/refuse matrix that is byte-identical between the two; the single differing line is barrel-has-export: undefined → function. check:api-surface, check:export-origins (5298 exports / 17 entry points) and check:generated (15 artifacts) all exit 0 on a freshly built head. |
| 2 | Exporting it did not widen ObjectNavItemSchema |
MEASURED | It accepts all 13 fixtures at head exactly as at base, filters + recordId included, and its checks is []. Positive control that this is not a facade artefact: a lazySchema wrapping a real .superRefine reads ["custom"] through the same accessor. ⇒ the ruling's "⛔ 不动现有挂载与 accept set" is obeyed. |
| 3 | Is the replacement filters docblock true? — the row this card exists for |
MEASURED + READ | Every clause checked: the guard is chained on the type: 'object' branch (branch list measured); the ambiguous combinations are unrepresentable (filters+recordId, filters+viewName, the triple → one issue at path filters, code custom); recordId + viewName really is the only tolerated pair; and the runAction asymmetry matches the code (runAction+recordId refuses, +filters / +viewName accept). .describe() unchanged: 0 changed lines containing describe( against 74 occurrences in the file, check:docs exit 0 over 228 generated files, and the old precedence prose is absent from content/docs (positive control: the filters describe sentence hits app.mdx ×5). |
| 4 | Is the 34-case pin discriminating or incidentally true? | MEASURED + READ | 34/34 alone, 231/231 with its neighbours; .skip/.only/.todo count 0. The dev's two ablation numbers are re-derived from the assertions and both reproduce exactly (M1 → 30 red / 4 green; M2 → 2 red / 32 green), and the reviewer names why M2 is 2 and not 5. It also names what would not be caught: the parity leg and the vector-equality leg are tautological by construction (both sides call the same function) and are carried by the declared-path leg and by leg 3's identifier count. |
| 5 | The objectui half | MEASURED at three shas | The 5-file diff touches no objectui path. objectNavTargetExclusivity appears in 0 objectui files at 53ded82b (the pin), 3f775ee, and objectui origin/main e411c3e — control NavigationItemSchema: 22 files. The hand-written superRefine loops only ['id','label']. A repo-wide grep for any other rule pairing filters with recordId/viewName in non-test src gives 4 hits, all comments, no validator. objectui#8563 is open and carries Blocked-by: objectstack-ai/objectstack#16714. |
| 6 | Governed surface / release notes | MEASURED | node scripts/pm/check-governed-merges.mjs --test <the 5 paths> → exit 0, "0 of 5 path(s) hit the register". Positive controls: --test AGENTS.md → exit 3 GOVERNED, no-args → exit 1. No content/docs/** path in the diff. |
| 7 | Is minor the right level? |
MEASURED + READ | Not major (nothing removed or narrowed), not patch (the surface grew). Confirmed against the gate's own logic and then run: check-changeset-no-major with a synthetic event carrying Clause-②: yes and this PR's labels → exit 0; its --self-test (157 assertions) → exit 0. |
| 8 | CI | NOT MEASURED | PM-held gate, per the mandate. This seat holds it: 41 checks, all completed success/skipped, aggregated by name. |
The four findings, all rated non-blocking — and this seat takes the reviewer's rating
- N1 — the neighbouring
recordIddocblock still says "Mutually exclusive withviewName(viewName is ignored if both are set)" while the guard tolerates that pair. It is this card's defect class one field over. ⛔ Not blocking and ⛔ not a rider: it pre-exists the PR, triage's ruling item 2 named only thefiltersdocblock, item 3 explicitly preserves the tolerance, nothing downstream copied the phrase (objectui'srecordIddescribe carries no such sentence at three shas), and it reaches no generated reference. ⇒ this seat files it as its own docs-only card rather than widening this PR. - N2 — the test header and the changeset state objectui's acceptance as a runtime fact where only a source reading was taken. The PR body labels its objectui sentences correctly; these two spots are firmer than the evidence. Non-blocking because objectui's door is not this PR's deliverable and objectui#8563 owns measuring it.
- N3 — for objectui#8563's dev: the copied precedence sentence lives in more than the one
.describe()that card names — alsopackages/types/src/app.tsand renderer comments. Recorded here so the objectui half does not land half-done. - N4 — the changeset embeds a moment-in-time reading of the sibling repo into what becomes a permanent CHANGELOG entry. Accurate today, stale the day objectui#8563 lands. Non-blocking: release notes are compiled centrally from this input, where the sentence can be trimmed.
Disposition
needs:contract-review is discharged and stripped. Flipping ready and enqueueing on head bf41b3f60. ⛔ Any further push would move the head and would need its own at-tier reading.
Generated by Claude Code
⭐ The director seat's F7 hold is falsified by measurement — the build WAS at tier. Enqueuing, ⛔ not silently.
What was held, and on whatThe director seat's comment on #16714 (2026-09-08T11:55Z) landed a PASS-conditional at-tier review and then declined to land the PR, moving the card to
Why F7's premise does not holdThe Every commit from this session carries those two lines — including the ones whose devs ran at The actual tier evidence, fused the way this seat fuses every tier claim. The #16714 dev's own harness transcript carries 311 stamped ⇒ There is no below-tier build here. 强制条款② was satisfied at the build, not merely compensated afterwards. F7 identified a real-looking discrepancy and drew the only inference available without the transcript — the reasoning was sound, the signal was not. What now stands on head
|
| gate | reading |
|---|---|
| build tier | claude-fable-5-1, fused at 311 stamps ⇒ at tier for 强制条款② |
| at-tier contract reviews | two, independent: the director seat's (5584726360, PASS-conditional — its one condition, Lint & Repo Gates, has since completed success) and this seat's (5585289881, PASS, no binding finding, fused at 86 stamps) |
| CI | 42 check runs, 0 still running, 0 non-green, aggregated by name |
| governed surface | check-governed-merges.mjs --test → exit 0, "0 of 5 path(s) hit the register"; positive control --test AGENTS.md → exit 3 GOVERNED |
| accept set | unmoved — a 13-row accept/refuse matrix is byte-identical at merge-base and head; the only differing line is barrel-has-export: undefined → function |
⇒ Every limb of the enqueue gate is discharged and nothing is owed. needs:contract-review stripped (read back: the other five labels intact). Ready flipped. Enqueuing.
⛔ What this seat is NOT claiming
- ⛔ Not that the director seat was careless — its handling matched fix(spec): list the five service-ai-studio tools the AI tool registry was omitting #16844's, and fix(spec): list the five service-ai-studio tools the AI tool registry was omitting #16844's hold stands for a different and still-live reason (POST-MERGE (the gate it guarded has passed): re-read cloud's service-ai-studio plugin.ts and confirm the five tool names PR #16844 adds are still the whole static set #16852's cloud read, which this seat cannot take:
add_repoforobjectstack-ai/cloudwas denied by the permission classifier at 12:31Z). - ⛔ Not that its review is superseded — it is one of the two at-tier PASSes counted above.
- ⛔ Not irreversible: if the director seat or the maintainer still wants this to reach the maintainer by hand, say so and this seat will re-draft it. Under 「可逆且有推荐默认的事项按否决窗口」 this is declare-and-execute, with the veto open.
Container & model: line — ⛔ never inferred from a commit trailer. Recorded here; this seat will carry it into the handover rather than leave it as one PR's argument.
Generated by Claude Code
…e the true clean-up scale (objectstack-ai#20226) Fixes objectstack-ai#19123 Clause-②: no ## What changed Five lines in `packages/spec/src/ui` cited two tracker numbers GitHub no longer serves (`objectstack-ai#16714`, `objectstack-ai#16715`: REST 404, no 301, re-probed today against lit controls). They are rewritten on ruling C+D (comment 5749154545 on this card): each line now anchors to the commit in this repository's history that decided it, and says in its own words what that commit decided, so a reader never needs the link. | Line (base `4df101c38`) | Was | Anchored to | What the line now says | |---|---|---|---| | `app.zod.ts:457` (shipped as source) | `(objectstack-ai#16714)` | commit `4cfc93b802` | the `filters` docblock states no precedence order because objectui's hand-written mirror copied one and accepted a combination the schema refuses | | `app.zod.ts:504` (shipped as source) | `EXPORTED (objectstack-ai#16714)` | commit `4cfc93b802` | the guard is exported so a mirror chains the schema's own rule; no accept set moves | | `app-nav-target-exclusivity-export.test.ts:4` | `[objectstack-ai#16714]` header tag | commit `4cfc93b802`, with PR objectstack-ai#16862 beside it as a convenience link only | the three things that commit decided: export the guard, state no order in the `filters` docblock, move no accept set | | `app-nav-target-exclusivity-export.test.ts:243` | test title `(objectstack-ai#16714 ruling)` | the title now carries the lesson; the comment under it anchors to `4cfc93b802` | "the export deliberately moved no accept set" | | `object-refinement-check-exports.test.ts:469` | `(objectstack-ai#16715)` | commit `b37f0b1767`, the diff that added the line | the declaration-count hardening closes a residual hole in the pin; no live shadowing existed | No ADR, ruling docblock or `scripts/adr-anchors/` entry records either decision (`objectNavTargetExclusivity` in `docs/adr` and `scripts/adr-anchors`: 0 files), so both take the commit-sha rung of C. The two `app.zod.ts` lines are the two shipped-tarball lines the ruling puts under D first: they now carry no tracker number, only the lesson and a sha. The `patch` changeset for `@objectstack/spec` says a docblock citation changed and nothing else. Prose, comments and one test title only. No assertion changes meaning and no accept set moves. ## Premise, re-measured on `4df101c38` - The ruling's first D act, the refusal string in `packages/spec/scripts/manifest-prescription.ts:78`, has no live target. That file and `check-react-blocks-declaration-parity.ts` were retired by `28ce61260`. `git grep` over the whole tree finds the three numbers on 5 lines, all in the table above, and `objectstack-ai#16697` on 0. - `objectstack-ai#16715` has a durable in-repo object. Commit `b37f0b1767` added the very line that cited it, and its message records the lesson: "a residual hole in the pin, not a live shadowing". ## Census: the true clean-up scale (read-only, nothing swept) **Population.** Every tracker-shaped token in `packages/spec/src/**` (1,594 tracked files) that names this repository: bare `#N` and `objectstack-ai/objectstack#N`, with N of 100 or more. Ordinals (`batch #`, `summon #`, `Directive #`, `item #` and similar) and 3- or 6-digit colour literals are excluded. Cross-repo tokens (`objectui#`, `cloud#`, `ui#`) are counted separately. ### Instrument 1: the REST status of every number (a full census, not a sample) `GET /repos/objectstack-ai/objectstack/issues/N` without following redirects, 2026-09-27T11:13Z to 11:16Z, plus 5 fully-qualified numbers at about 12:05Z. | status | numbers | |---|---| | probed | 1,958 (every distinct in-repo number) | | 200 | 1,786 | | **404** | **172 (8.8%)** | | 301 | 0 | **Controls.** They were re-probed at the start, after every 250 probes and at the end, 9 checkpoints in all. The lit controls `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698` answered 200 at 9 of 9. The dead controls `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697` answered 404 at 9 of 9. So the channel stayed up for the whole run, and the 404s are real readings. **The dead rate depends on the number's era** (bare numbers): | numbers | dead | |---|---| | below 5000 | 0% | | 6000s | 21.2% | | 7000s | 0% | | 8000s | 14.0% | | 9000s | 10.6% | | 10000s | 40.0% | | 11000s | 26.7% | | 12000s | 18.5% | | 13000s | 16.4% | | 14000s | 22.5% | | 15000s | 0% | | 16000s | 14.3% | | 17000s | 10.5% | | 18000s | 7.7% | | 19000s | 3.5% | **Contamination bound.** Five numbers are cited only as sibling-repo PRs, written bare (for example `objectui PR` followed by the number). One of them, `8887`, is among the 404s, and `8758` is cited both ways. So the in-repo dead count is 170 to 172 out of 1,953 to 1,958. **Cross-repo tokens.** `objectui` (183 distinct) and `cloud` (36) are NOT MEASURED: this session's REST gate answers 403 for both repositories. `hotcrm` answered 200 for 7 of 7 (lit control `hotcrm#1961`: 200). ### Instrument 2: who owns the squash commit `GET /repos/objectstack-ai/objectstack/commits/SHA/pulls` for every dead number that appears as a squash suffix in `origin/main` history (8,715 commits, back to 2026-06-22). 11 of the 172 do, and all 11 answer `[]`: the PR object is gone. Controls: `b37f0b1767` answers `[16847]` and `1124c45641` answers `[19107]` (lit); `7936b29ce5`, the card's known-dead `objectstack-ai#16697`, answers `[]`. The other 161 dead numbers carry no squash suffix. They were cards, not PRs, and this instrument does not reach them. ### Independent cross-check: `check-issue-citations --census` I ran the repository's own citation gate in census mode at about 12:02Z. The board frontier is 20,223 and the board size is 18,050, so 2,173 minted numbers (10.7%) are holes. Tree-wide there are 2,779 unresolvable sites, 664 of them in `packages/spec/src`. On the gate's surface (non-test comment prose), the two instruments agree on 661 of those 664 sites. The 3 sites only the gate found use the `objectstack-ai/objectstack#N` spelling, which my first extraction pass missed; I probed them afterwards and they are included above. My 297 extra comment sites break down as 271 in test files (the gate's declared deferred surface) and 26 `re-charter objectstack-ai#13135` sites that the gate reads as an ordinal. ### What remains after this PR On this head, `packages/spec/src/**` still has **1,295 dead-cited sites on 1,277 lines in 327 files, citing 170 numbers**. On the base it was 1,300 sites, 1,282 lines, 330 files and 172 numbers. | class | sites | lines | files | |---|---|---|---| | non-test comment | 691 | 683 | 180 | | non-test string or code (text an author can be shown) | 186 | 184 | 48 | | test comment | 271 | 266 | 103 | | test string or code (titles, fixtures) | 147 | 144 | 54 | By area: migrations 438, data 239, api 153, ui 107, kernel 105, contracts 81, system 38, conversions 24, shared 20, other 90. **Load-bearing share.** I read a seeded random sample of 40 dead-cited lines. 37 of the 40 cite a ruling, a deliberate tolerance or exclusion, a retirement, or a cautionary measurement, which is the dispatch's definition of load-bearing. The other 3 are incidental: a bare `Refs`, a label, a process note. Applied to 1,277 lines, that is roughly 1,100 to 1,200 load-bearing lines, a sweep the ruling keeps out of a single PR. **None of them is rewritten here.** **The runtime-text slice is the D half.** `os migrate meta` prints each migration entry's `replacement`, `why:` (`reason`) and `verify:` (`acceptanceCriteria`) to the author (`packages/cli/src/commands/migrate/meta.ts:523-525`). Across `packages/spec/src/migrations/**`, those strings hold 2,060 tracker-id sites (478 distinct numbers; 178 sites are dead). `pnpm check:doc-authoring` is green over all of them, for two reasons: its Rule 3 does not recognise those property positions, and its cross-package leg excludes `packages/spec/src`. I report this to the seat as a finding and do not fix it here. **Every dead number still cited**, as number and sites/files, in numeric order. Any line is recoverable with `git grep -n` on the number under `packages/spec/src`. - `objectstack-ai#5970` 6/3 · `objectstack-ai#6037` 11/5 · `objectstack-ai#6048` 8/3 · `objectstack-ai#6072` 1/1 · `objectstack-ai#6075` 2/2 · `objectstack-ai#6083` 10/7 · `objectstack-ai#6085` 2/1 · `objectstack-ai#6111` 3/3 - `objectstack-ai#6148` 10/6 · `objectstack-ai#6206` 14/7 · `objectstack-ai#6216` 2/2 · `objectstack-ai#6238` 1/1 · `objectstack-ai#6239` 10/6 · `objectstack-ai#6259` 5/2 · `objectstack-ai#6276` 13/2 · `objectstack-ai#6279` 2/2 - `objectstack-ai#6287` 18/2 · `objectstack-ai#6300` 2/1 · `objectstack-ai#6306` 1/1 · `objectstack-ai#6345` 32/11 · `objectstack-ai#6361` 20/11 · `objectstack-ai#6362` 9/5 · `objectstack-ai#6363` 12/6 · `objectstack-ai#6483` 6/1 - `objectstack-ai#6511` 7/3 · `objectstack-ai#6523` 11/5 · `objectstack-ai#6527` 1/1 · `objectstack-ai#6571` 10/2 · `objectstack-ai#6573` 2/2 · `objectstack-ai#6604` 3/2 · `objectstack-ai#6605` 3/1 · `objectstack-ai#6640` 2/2 - `objectstack-ai#6704` 6/3 · `objectstack-ai#6723` 1/1 · `objectstack-ai#6725` 5/2 · `objectstack-ai#6745` 2/1 · `objectstack-ai#8460` 2/1 · `objectstack-ai#8495` 59/27 · `objectstack-ai#8656` 1/1 · `objectstack-ai#8696` 34/13 - `objectstack-ai#8714` 4/2 · `objectstack-ai#8715` 47/24 · `objectstack-ai#8758` 9/6 · `objectstack-ai#8772` 11/5 · `objectstack-ai#8778` 1/1 · `objectstack-ai#8794` 5/3 · `objectstack-ai#8836` 4/3 · `objectstack-ai#8873` 10/6 - `objectstack-ai#8874` 1/1 · `objectstack-ai#8876` 11/7 · `objectstack-ai#8885` 2/1 · `objectstack-ai#8887` 2/2 · `objectstack-ai#9040` 20/6 · `objectstack-ai#9041` 28/5 · `objectstack-ai#9740` 3/1 · `objectstack-ai#9741` 23/2 - `objectstack-ai#9933` 9/2 · `objectstack-ai#9934` 4/4 · `objectstack-ai#9972` 4/2 · `objectstack-ai#10165` 7/2 · `objectstack-ai#10194` 12/4 · `objectstack-ai#10238` 1/1 · `objectstack-ai#10264` 1/1 · `objectstack-ai#10274` 7/3 - `objectstack-ai#10329` 10/4 · `objectstack-ai#10330` 3/1 · `objectstack-ai#10338` 6/3 · `objectstack-ai#10347` 5/2 · `objectstack-ai#10485` 43/23 · `objectstack-ai#10527` 3/2 · `objectstack-ai#10627` 12/7 · `objectstack-ai#10724` 47/20 - `objectstack-ai#10726` 15/9 · `objectstack-ai#10812` 6/5 · `objectstack-ai#10926` 11/6 · `objectstack-ai#11006` 15/3 · `objectstack-ai#11065` 7/3 · `objectstack-ai#11071` 1/1 · `objectstack-ai#11166` 2/1 · `objectstack-ai#11195` 5/3 - `objectstack-ai#11215` 1/1 · `objectstack-ai#11284` 10/4 · `objectstack-ai#11328` 2/2 · `objectstack-ai#11330` 2/1 · `objectstack-ai#11331` 2/1 · `objectstack-ai#11332` 19/7 · `objectstack-ai#11333` 3/3 · `objectstack-ai#11350` 9/5 - `objectstack-ai#11408` 3/2 · `objectstack-ai#11453` 1/1 · `objectstack-ai#11504` 5/3 · `objectstack-ai#11507` 8/4 · `objectstack-ai#11513` 2/2 · `objectstack-ai#11658` 6/3 · `objectstack-ai#11693` 2/2 · `objectstack-ai#11703` 3/2 - `objectstack-ai#11741` 9/4 · `objectstack-ai#11753` 6/2 · `objectstack-ai#11757` 4/2 · `objectstack-ai#11846` 30/10 · `objectstack-ai#11858` 1/1 · `objectstack-ai#11859` 2/1 · `objectstack-ai#12010` 5/2 · `objectstack-ai#12144` 2/1 - `objectstack-ai#12165` 1/1 · `objectstack-ai#12176` 2/1 · `objectstack-ai#12194` 4/4 · `objectstack-ai#12245` 2/1 · `objectstack-ai#12248` 17/4 · `objectstack-ai#12380` 4/2 · `objectstack-ai#12493` 6/2 · `objectstack-ai#12868` 9/6 - `objectstack-ai#12950` 3/2 · `objectstack-ai#12961` 10/2 · `objectstack-ai#13031` 3/2 · `objectstack-ai#13109` 1/1 · `objectstack-ai#13135` 70/25 · `objectstack-ai#13156` 4/4 · `objectstack-ai#13195` 3/1 · `objectstack-ai#13197` 1/1 - `objectstack-ai#13218` 7/2 · `objectstack-ai#13608` 5/2 · `objectstack-ai#13644` 3/2 · `objectstack-ai#13670` 1/1 · `objectstack-ai#13672` 3/2 · `objectstack-ai#13681` 1/1 · `objectstack-ai#14143` 2/2 · `objectstack-ai#14162` 1/1 - `objectstack-ai#14192` 5/2 · `objectstack-ai#14312` 1/1 · `objectstack-ai#14365` 4/3 · `objectstack-ai#14369` 24/13 · `objectstack-ai#14419` 2/2 · `objectstack-ai#14426` 2/2 · `objectstack-ai#14474` 1/1 · `objectstack-ai#14526` 2/2 - `objectstack-ai#14662` 4/2 · `objectstack-ai#14676` 22/10 · `objectstack-ai#14686` 7/2 · `objectstack-ai#14691` 50/16 · `objectstack-ai#14722` 4/2 · `objectstack-ai#14723` 1/1 · `objectstack-ai#14748` 2/2 · `objectstack-ai#14902` 2/2 - `objectstack-ai#14919` 2/2 · `objectstack-ai#14996` 1/1 · `objectstack-ai#16126` 2/2 · `objectstack-ai#16559` 2/1 · `objectstack-ai#16626` 3/3 · `objectstack-ai#16649` 12/2 · `objectstack-ai#16659` 5/4 · `objectstack-ai#16685` 4/2 - `objectstack-ai#16786` 3/2 · `objectstack-ai#16864` 2/2 · `objectstack-ai#16867` 3/2 · `objectstack-ai#17014` 3/2 · `objectstack-ai#17058` 1/1 · `objectstack-ai#17081` 1/1 · `objectstack-ai#17124` 3/3 · `objectstack-ai#17147` 5/2 - `objectstack-ai#17286` 1/1 · `objectstack-ai#17348` 1/1 · `objectstack-ai#17590` 1/1 · `objectstack-ai#17987` 13/2 · `objectstack-ai#18003` 1/1 · `objectstack-ai#18012` 10/5 · `objectstack-ai#18177` 7/2 · `objectstack-ai#18335` 1/1 - `objectstack-ai#19307` 1/1 · `objectstack-ai#19377` 8/4 ## Gates (head `98edadd03`) - **Derived gate families.** `node scripts/pm/dispatch-gates.mjs --ran` reports 83 derived, 79 run with exit 0, 4 NOT MEASURED and 0 unrun. The four exited 3 (PREREQUISITE NOT MET): `check:doc-formula-expressions`, `check:dual-build-cjs-loads`, `check:lean-entry-closure` and `check:type-check-debt`. Each needs other packages' `dist/`, and both attempts to take the shared verify lock for that build ended in queue-timeout (99). This is a declared narrowing: the diff changes comment and test-title text in one package and nothing those four gates read, and CI's `Lint & Repo Gates` runs them. - **Roster gates.** The six artifact-roster gates whose roster sits under a changed path (`check-changeset-fixed`, `check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing`, `check:filter-alias-parity`) all exit 0. - **Build, tests, typecheck.** `pnpm --filter @objectstack/spec build` exits 0. The two edited test files pass: 2 files, 141 tests. `pnpm --filter @objectstack/spec typecheck` exits 0. - **Citation verdict.** `node scripts/check-issue-citations.mjs --base 4df101c` (the diff-scoped verdict) exits 0. The change adds one citation, `objectstack-ai#16862`, and it answers 200. ## Acceptance notes - This change adds one tracker number: PR objectstack-ai#16862, once, in a test docblock, as a convenience link beside the sha it cites. The ruling allows that, and nothing else in the change cites a number. - **Where this departs from the dispatch.** The dispatch said to rewrite any load-bearing dead-cited line found in `packages/spec/src` beyond the five, in this PR. At the measured 1,277 lines, about 92% of them load-bearing, that is the sweep the ruling keeps out of one PR, so this PR measures and lists them and rewrites none. - D's first named act, the refusal string, was retired upstream by `28ce61260`. The live D population is the migration-entry text above. - **Instrument notes**, reconciled above and not filed: the gate's ordinal list reads `re-charter #N` as a non-citation, which hides 26 real citations of `objectstack-ai#13135`. My own first extraction pass missed the `owner/repo#N` spelling (3 sites). --- _Generated by [Claude Code](https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ)_ Co-authored-by: Claude <noreply@anthropic.com>
…acts/ to the commits and ADRs that decided them (stage 1) (objectstack-ai#20326) Part of objectstack-ai#20234 Clause-②: no ## What changed This is stage 1 of the staged sweep, covering `packages/spec/src/kernel/**` and `packages/spec/src/contracts/**` and nothing else. Later stages cover the other areas, so this PR carries `Part of`. Every comment or docblock site in these two areas that cited a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123): **160 sites on 151 lines in 45 files, covering 47 numbers (152 lines rewritten)**. Each rewritten line now cites the object this repository controls that decided the matter: - an ADR or ruling record where one exists; - otherwise the commit in `origin/main` history that decided it. Each line also says in its own words what that object decided. Where nothing answers, the sentence keeps its reason in words and the number is gone: that happened for four numbers. Only comments changed. Every file keeps its line count (152 lines out, 152 in), so no line citation into these files moves. No code token moves (see the guard below). String literals carrying a dead number are tokens: 30 such sites are left as they were and listed below. **No citation number is added.** Every tracker number on an added line was already on the line it replaces. That includes the two PR numbers now standing beside their shas as convenience links: PR objectstack-ai#6900 beside `b5404f496`, and PR objectstack-ai#7211 beside `1507ba356`. ## Census: this stage's two areas, before and after **Instrument.** This is PR objectstack-ai#20226's instrument: REST `GET /repos/objectstack-ai/objectstack/issues/N` without following redirects, over every distinct in-repo number cited in the two areas. The population is: - bare `#N`, `objectstack#N`, `framework#N`, and the `pre-#N` / `post-#N` spellings, with N of 100 or more; - excluding the ordinal heads the citation gate declares (Prime Directive, `PD`, decision `batch`) and `summon`. **Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at the start, after every 100 numbers and at the end: 6 checkpoints per run. They read 18 of 18 lit (200) and 18 of 18 dead (404) in both runs. | reading | tree | numbers probed | 200 | 404 | 301 or other | dead sites | kernel | contracts | lines | files | dead numbers | |---|---|---|---|---|---|---|---|---|---|---|---| | before | base `6a6a17b62`, probed 2026-09-27T20:14Z to 20:16Z | 445 | 398 | 47 | 0 | **190** | 106 | 84 | 180 | 45 | 47 | | after | head (probe at `eda5c6b27`, 2026-09-27T21:24Z to 21:26Z; source identical at the final head) | 415 | 398 | 17 | 0 | **30** | 18 | 12 | 29 | 14 | 17 | **Before, by class.** - 73 non-test docblock sites and 26 non-test line comments. - 22 test docblock sites and 39 test line comments. - 28 test string sites (describe and it titles, one assertion message). - 2 non-test strings. **After.** Only the 30 string sites remain. There are 0 comment sites. The head probe found no number newly dead since the base probe. PR objectstack-ai#20226's area table read kernel 105 and contracts 81 at an earlier base. This census reads 106 and 84 because it also counts the `pre-#N` / `post-#N` spelling: 3 dead sites. ## Per-number table The site counts give comments rewritten and strings left. The kind column says what each line now cites: - commit: the commit whose diff made the decision the line describes (read in each diff, not only in the subject); - ADR: the recorded decision; - dropped: the reason is kept in words and the number removed. | number | sites / files | rewritten / left (string) | anchor | kind | |---|---|---|---|---| | `objectstack-ai#5970` | 2/1 | 2/0 | `97e7e3caa`: `ActionSchema.visible` gains the boolean arm | commit | | `objectstack-ai#6083` | 1/1 | 1/0 | ADR-0122 phase 2, `53068c130`: find and findOne pinned to the parsed state | commit (ADR named) | | `objectstack-ai#6206` | 12/5 | 12/0 | `d7e0b4212`: maintainer ruling 2026-08-07, enforcement takes the full envelope with no per-site subset. One site cites `8e13ca876`, the route half that restored the five dropped fields. Two name the ruling in words where the same block already cites the sha | commit | | `objectstack-ai#6216` | 2/2 | 2/0 | `f586f1a89`: one ExecutionContext assembler, closed field-set pin | commit | | `objectstack-ai#6300` | 2/1 | 2/0 | `74155c735`: find and findOne accept the author state | commit | | `objectstack-ai#6361` | 1/1 | 1/0 | `90bbf2510`: notification-list `cursor` retired on both halves | commit | | `objectstack-ai#6362` | 3/2 | 3/0 | `b5404f496` (PR objectstack-ai#6900 beside it): `connector` keeps the ADR-0010 envelope | commit | | `objectstack-ai#6363` | 3/1 | 3/0 | `17d095413`: `unreadCount` counts the whole inbox, not the window | commit | | `objectstack-ai#6483` | 6/1 | 6/0 | ADR-0005 whitelist, executed by `ee58392e1`: nine unratified `allowOrgOverride: true` rolled back | commit (ADR named) | | `objectstack-ai#6511` (a PR) | 5/1 | 5/0 | `d7e0b4212`, its squash commit | commit | | `objectstack-ai#6523` | 7/3 | 6/1 | `aa4b90d9a`: sharing and approval enforcement take the full ExecutionContext | commit | | `objectstack-ai#6640` | 2/2 | 2/0 | `2ab1257c9`: `preserveAudit` is UPDATE-only, with a loud INSERT warn | commit | | `objectstack-ai#6723` (a PR) | 1/1 | 1/0 | `8ad609c69`, its squash commit: getObject's declared answer | commit | | `objectstack-ai#6725` | 6/2 | 4/2 | `1507ba356` (PR objectstack-ai#7211 beside it): facade object writes reach the map its reads use | commit | | `objectstack-ai#6745` | 2/1 | 2/0 | `7a5ef0008`: the getObject-equals-get conformance pin | commit | | `objectstack-ai#8715` | 3/3 | 3/0 | `2c86fe3ea`: the retirement pin form over `export-origins/` | commit | | `objectstack-ai#8794`, `objectstack-ai#8836` | 1/1 each | 1/0 each | `1850ebbb0`: measured the filter-reuse invariant and pinned it | commit | | `objectstack-ai#10194` | 7/2 | 6/1 | `2306a765c`: theme and analytics_cube bound at the /meta door | commit | | `objectstack-ai#10238` | 1/1 | 1/0 | none: whether cube authoring is live end to end is still its own measurement (`559041d39` leaves it open) | dropped | | `objectstack-ai#10338` | 2/1 | 1/1 | `d2619fd0c`: `ApiEndpoint.target` optional, the publish gate holds the requirement | commit | | `objectstack-ai#10485` | 5/2 | 5/0 | `35ad101bc`: `themes` carrier and ThemeSchema retired | commit | | `objectstack-ai#10627` | 3/2 | 3/0 | `be21955ba`, whose message records that controlled census | commit | | `objectstack-ai#10724` | 16/5 | 15/1 | `be21955ba`: nine dead `contributes` members tombstoned | commit | | `objectstack-ai#10726` | 6/3 | 4/2 | `bc56e1881`: `contributes.routes` retired, ruled Option B | commit | | `objectstack-ai#10812` | 2/2 | 2/0 | none: the cloud leg's clean close is kept as its date (2026-08-24, which `be21955ba` records) | dropped | | `objectstack-ai#11071` | 1/1 | 1/0 | `50fb191dc`: `os generate` file names derived from the registry, with the parity pin | commit | | `objectstack-ai#11330` | 2/1 | 1/1 | `a9ee98992`: trust-tier text states publish-gate-only enforcement | commit | | `objectstack-ai#11331` | 2/1 | 2/0 | none: an open "tracked on" pointer, and the enforce leg is unbuilt. The lines now say so | dropped | | `objectstack-ai#11332` | 9/2 | 8/1 | `dce5cd4f0`: three dead manifest containers retired | commit | | `objectstack-ai#11333` | 1/1 | 1/0 | `aaacf1d5c`: the commit that corrected the permissions half | commit | | `objectstack-ai#11350` | 1/1 | 1/0 | `ece4dad31`, which records the 2026-08-23 entry-nameability ruling | commit | | `objectstack-ai#11504` | 1/1 | 1/0 | `f90e82024`: `FLOW_INPUT_SCHEMA_INVALID` registered | commit | | `objectstack-ai#11741` | 5/2 | 4/1 | `b706af987`: `SendEmailInput.organizationId` | commit | | `objectstack-ai#11846` | 11/3 | 7/4 | `0c2334f6c`: preview mode retired | commit | | `objectstack-ai#12010` | 5/2 | 3/2 | none: that `ConnectionEngineLike` inventory is described in words | dropped | | `objectstack-ai#12165` | 1/1 | 1/0 | `b307bfd2a`: the glob-discovery disposition recorded beside `filePatterns` | commit | | `objectstack-ai#12248` | 19/4 | 15/4 | `8425c17cc`: the five ruled engine members adopted, getObject typed | commit | | `objectstack-ai#13135` | 9/7 | 8/1 | `9e0ba21a1`: paper customization protocol retired. ADR-0126 section 6 wall 4 stays cited where the line had it | commit (ADR named) | | `objectstack-ai#13608` | 2/1 | 2/0 | `fc9ba76a5`: eligibility held at redemption | commit | | `objectstack-ai#14143` | 2/2 | 2/0 | `f19475c0a`: the handler-face `ctx.recordLoadDenied` signal | commit | | `objectstack-ai#14192` | 4/1 | 0/4 | none rewritten: test titles only | (strings) | | `objectstack-ai#14722` | 2/1 | 2/0 | `23c72be3c`: pinned the measurement that refuted that card's premise | commit | | `objectstack-ai#16559` | 2/1 | 1/1 | `c7aca0dce`: ResumeFailureReport declared once | commit | | `objectstack-ai#16786` | 3/2 | 2/1 | `6059b29c0`: `updateById` declares its answer | commit | | `objectstack-ai#17147` | 5/2 | 3/2 | `aaacf1d5c`: the granted permission set is registered and refuses nothing | commit | | `objectstack-ai#18335` | 1/1 | 1/0 | ADR-0090 D10's 2026-09-16 note: an API key is a credential | ADR | Every cited sha resolves to exactly one commit, and every one is an ancestor of `origin/main` (38 shas, each `merge-base --is-ancestor` exit 0). ## The 30 string sites left as tokens - **Test titles and one assertion message (28 sites).** - contracts: `data-engine.test.ts` (4), `objectql-engine.test.ts` (2), `email-service.test.ts`, `resume-failure-report.pin.test.ts`, `scoped-context.test.ts` and `sharing-service.test.ts` (1 each). - kernel: `manifest-unknown-keys.test.ts` (4), `manifest.test.ts` (4), `preview-mode-retirement.test.ts` (4, one of them an assertion message), `plugin-runtime-tier-truthful-text.test.ts` (3), and 1 each in `metadata-customization-retirement.test.ts`, `metadata-type-api-registration.test.ts` and `metadata-type-schemas.test.ts`. - **Non-test strings (2 sites).** Two `why:` strings on rows of the exported `METADATA_ROUNDTRIP_CASES` table in `contracts/metadata-service-roundtrip-conformance.ts` (lines 194 and 202). They ship in the package as data. No runtime path prints them to an author: both test drivers title each case by its `id`. So they are not author-shown text in the form D sense, and they are not rewritten here. ## Mechanical guard: no code token moves The check is a comments-stripped token comparison, base `6a6a17b62` against the head. It uses the TypeScript parser's leaf tokens, so template literals are scanned in context, and it excludes JSDoc nodes. It ran over all 45 touched `.ts` files. - Real run: 60,827 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control: 0 changes, as expected (exit 0). - Positive control (a declaration inserted): 1 file flagged (exit 1). - Positive control (one digit changed inside a test-title string): 1 file flagged (exit 1). ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/spec` is included; it says only that provenance comments were re-anchored. Measured on the built package: rewritten docblocks reach `dist/**/*.d.ts`. For example, `8425c17cc`, `17d095413`, `aa4b90d9a` and `fc9ba76a5` each appear in 1 declaration file, and the positive control, a pre-existing `notification-service` docblock sentence, appears in `dist/contracts/index.d.ts`. Rewritten comments also reach the bundled `.js`: `be21955ba` appears in 20 files. The `src/**/*.zod.ts` sources ship verbatim through `files[]`. ## Gates (head `ebbea0b6e`) - **Citation judging pass, run as CI runs it:** `pnpm check:issue-citations && node scripts/check-issue-citations.mjs` exits 0. It judged 23 citations: 21 resolve and 2 resolve as pull requests (the two convenience links). - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` derived 86 families, and all 86 exit 0. `--ran` reports 86 run, 0 NOT MEASURED, 0 unrun, and exits 0. Five of them (`check:doc-formula-expressions`, `check:dual-build-cjs-loads`, `check:i18n`, `check:lean-entry-closure`, `check:type-check-debt`) first exited 3, PREREQUISITE NOT MET. They exited 0 after a full `turbo run build` of `./packages/*` (71 tasks, exit 0, under the shared verify lock). - **Build, tests, typecheck:** - `pnpm --filter @objectstack/spec build` exits 0. - `vitest run src/kernel src/contracts` in `packages/spec`: 99 files and 1,608 tests pass. That covers all 24 touched test files and every test here that reads contract source text. - `pnpm --filter @objectstack/spec typecheck` exits 0, including `check:test-typecheck`. ## Acceptance notes - **Base.** The branch is 5 commits behind `origin/main` (`4e0f72e8d`). None of those commits touches `kernel/`, `contracts/` or any file here, so there was no merge. - **No author-shown string in scope.** Nothing in these two areas is form D's; the migration-entry fields belong to objectstack-ai#20233. - **Instrument note, not filed.** The citation gate reads `pre-#N` and `post-#N` as a cross-repository qualifier (`pre-`), so it never judges them. In these two areas that is 18 sites at base, 3 of them dead. This census counted them by hand. - **`framework#N` is NOT MEASURED as a repository.** `objectstack-ai/framework` answers 403 to this session's REST gate. The five numbers cited that way resolve 200 in this repository. None of them is in the dead set. - **Seat 2's pointer.** The 21 `objectstack-ai#13003` notes in `packages/spec/liveness/permission.json` are outside this stage, as the claim records. --- _Generated by [Claude Code](https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…ts that decided them (stage 2) (objectstack-ai#20342) Part of objectstack-ai#20234 Clause-②: no ## What changed This is stage 2 of the staged sweep. It covers `packages/spec/src/api/**` and nothing else, and it leaves out `packages/spec/src/api/rest-server.zod.ts`, which seat 2's objectstack-ai#20295 claim holds. Later stages cover the other areas, so this PR says `Part of`. 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). That is **107 sites on 105 lines in 22 files, covering 30 numbers**. 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 in `docs/adr/` or `scripts/adr-anchors/` names any of the 32 dead numbers, so every anchor is a commit. Nothing was dropped: every number in scope had a deciding commit. One more comment site is left on purpose, because its number belongs to another repository (see Acceptance notes). Only comments changed. Every source file keeps its line count (112 lines out, 112 in), so no line citation into these files moves. Seven of those 112 lines carry no number; they are the second half of a sentence that had to be reflowed. No code token moves (see the guard below). The 34 string-literal sites that carry a dead number are tokens, so they are left as they were and listed below. **No citation number is added.** Every tracker number on an added line was already on the line it replaces. No PR number stands beside a sha. Two more kinds of file change, both mechanical: - **Regenerated reference pages.** Five of the rewritten docblock lines project into `content/docs/references/api/` (`dispatcher.mdx`, `error-code-ledger.mdx`, `plugin-rest-api.mdx`). `check:docs` proved those three pages stale, and `pnpm --filter @objectstack/spec check:generated --fix` regenerated only them. The diff is five lines, each the same substitution as its source line. - **A `patch` changeset** for `@objectstack/spec` (see Changeset below). ## Census: `api/`, before and after **Instrument.** This is stage 1's instrument. It sends REST `GET /repos/objectstack-ai/objectstack/issues/N` without following redirects, for every distinct in-repo number cited in `packages/spec/src/api`. The population is: - the citation gate's own `CITATION_RE`, kept when the qualifier is none, `objectstack`, `objectstack-ai/objectstack`, `framework`, `pre-` or `post-`; - N of 100 or more; - excluding the ordinal heads the gate declares, and `summon`. Each site is classified by the TypeScript parser as a line comment, a docblock or a string. **Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at the start, after every 100 numbers and at the end, which is 5 checkpoints per run. They read 15 of 15 lit (200) and 15 of 15 dead (404) in both runs. | reading | tree | numbers probed | 200 | 404 | 301 or other | dead sites, all of `api/` | in scope | `rest-server.zod.ts` | in-scope lines | in-scope files | dead numbers in scope | |---|---|---|---|---|---|---|---|---|---|---|---| | before | base `21ab41041`, probed 2026-09-27T22:56Z to 22:58Z | 351 | 319 | 32 | 0 | **153** | 142 | 11 | 140 | 23 | 31 | | after | head `22a00c42d`, probed 2026-09-27T23:10Z to 23:12Z; `.ts` sources identical at the final head | 334 | 319 | 15 | 0 | **46** | 35 | 11 | 35 | 9 | 14 | **Before, in scope, by class.** 34 non-test docblock sites and 30 non-test line comments. 7 test docblock sites and 37 test line comments. 33 test string sites (describe and it titles). 1 non-test string. **After, in scope.** 34 string sites and 1 comment site remain. The comment site is `export-job-family-retirement.test.ts:25`, which names an objectui PR (see Acceptance notes). The head probe found no number newly dead since the base probe: 319 numbers answer 200 in both runs. PR objectstack-ai#20226's area table read `api` 153 at an earlier base, and this census reads the same 153 at `21ab41041`. Of those, 11 sites sit in `rest-server.zod.ts` (`objectstack-ai#14369` twice and `objectstack-ai#14691` nine times, all comments). They wait for objectstack-ai#20295 to land. `pre-#N` spellings do occur in `api/`: the at-tier review measured 15 sites on 15 lines in 13 files at the head, for example `discovery.zod.ts:913` `pre-objectstack-ai#4828`. All 15 numbers answer 200, so the gate's blind spot for that spelling (objectstack-ai#20330) hides no dead site in this stage. _(Corrected by the `domain:spec` seat 4 at 2026-09-28T00:48Z, from the at-tier review record 5861396181. The draft said that no such spelling occurs.)_ ## Per-number table The counts are in-scope sites and files. `rewritten / left` gives comment sites rewritten and string sites left. Every anchor was read in its diff or message, not only in its subject: it is the commit that made the change the line now describes, never a later refactor or a commit that merely mentions the number. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#6037` | 5/2 | 3/2 | `18189983d`: declares `DataProtocol.validateData`, the validate-only operation, under objectstack-ai#4633 ruling D | | `objectstack-ai#6239` | 4/2 | 3/1 | `f549a0d4a`: retires `ViewProtocol` and its ten request/response schemas (ADR-0049, route 3) | | `objectstack-ai#6287` | 18/2 | 17/1 | `84c86fb45`: `preview` and `trial` fold to `sandbox` by declaration, and the fold table is typed total over `EnvironmentType` | | `objectstack-ai#6306` | 1/1 | 1/0 | `fec784863`: the direct-mount routes read the one API base | | `objectstack-ai#6361` | 5/3 | 4/1 | `90bbf2510`: the notification-list `cursor` is retired on both halves (maintainer ruling 2026-08-07, Option A) | | `objectstack-ai#6363` | 3/2 | 3/0 | `17d095413`: `unreadCount` counts the whole inbox, not the window | | `objectstack-ai#6704` | 4/1 | 3/1 | `c3f491626`: `runAutomations` declares the default the import route applies, with the agreement pin | | `objectstack-ai#8885` | 2/1 | 2/0 | `30b1c636a`: registers the nine REST wire codes that sweep found | | `objectstack-ai#9740` | 3/1 | 2/1 | `11b779e0f`: declares `getMetaItemLayered`. The two cross-references to its test block now name the block by member | | `objectstack-ai#9741` | 23/2 | 16/7 | `2a29caa53`: records the 2026-08-18 maintainer ruling. `previewDrafts` and `state` are declared, and `environmentId` stays transport-level | | `objectstack-ai#9934` | 4/4 | 4/0 | `79c46da90`: the producer-side `userMessage` channel | | `objectstack-ai#10264` (objectui) | 1/1 | 0/0 | not this repository's number: see Acceptance notes | | `objectstack-ai#10330` | 3/1 | 1/2 | `b9e9227e3`: declares `mappingName` on `ImportRequestSchema` | | `objectstack-ai#10338` | 4/2 | 3/1 | `d2619fd0c`: `ApiEndpoint.target` is optional, and the publish gate holds the flow requirement | | `objectstack-ai#10726` | 2/2 | 2/0 | `bc56e1881`: retires `contributes.routes`, ruled Option B (stage 1's anchor too) | | `objectstack-ai#11006` | 12/2 | 10/2 | `cccbe51bf`: declares `publishMetaItem` under the 2026-08-22 maintainer ruling, option B | | `objectstack-ai#11453` | 1/1 | 1/0 | `1a47a5368`: `ack()` refuses a row that is not `in_flight` | | `objectstack-ai#11504` | 4/2 | 2/2 | `f90e82024`: registers `FLOW_INPUT_SCHEMA_INVALID`, the contract half (stage 1's anchor too) | | `objectstack-ai#11846` | 1/1 | 1/0 | `0c2334f6c`: the preview-mode retirement, whose test states the same assertion-set reasoning (stage 1's anchor too) | | `objectstack-ai#11858` (a PR) | 1/1 | 1/0 | `1a47a5368`, its squash commit: `ack()` takes the shared `DELIVERY_NOT_ELIGIBLE` spelling | | `objectstack-ai#11859` | 2/1 | 2/0 | `d9cf78eaa`: `ack()` binds the claim credential in its compare-and-set | | `objectstack-ai#12194` | 1/1 | 1/0 | `311433f6b`: declares the item-name grammar and refuses it loudly at the publish door | | `objectstack-ai#13135` | 3/2 | 3/0 | `9e0ba21a1`: retires the paper metadata-customization protocol (stage 1's anchor too) | | `objectstack-ai#13197` | 1/1 | 1/0 | `56c093c4d`: the in-memory driver enforces field-level `unique` | | `objectstack-ai#14474` | 1/1 | 1/0 | `df657d9df`: the install-time namespace conflict refusal carries an ADR-0112 envelope | | `objectstack-ai#14691` | 16/1 | 4/12 | `b3a63d32c`: retires the ten inert `RestServerConfig` keys. All 16 sites are in `rest-server.test.ts`; the 9 in `rest-server.zod.ts` are out of scope | | `objectstack-ai#14723` | 1/1 | 1/0 | `65846bc46`: lands the 2026-09-03 maintainer ruling, so a unique-constraint refusal has one wire spelling, `UNIQUE_VIOLATION`, on every route | | `objectstack-ai#14748` | 2/2 | 2/0 | `92b5d7f00`: registers `NAMESPACE_CONFLICT` (its diff wrote the row this line glosses) | | `objectstack-ai#16649` | 12/2 | 11/1 | `613bfbd3d` for 9 sites: registers the fourteen remaining `door: 'none'` codes. `44c917a47` for 2 sites: widens the vocabulary gate's face to every published package and retires `boot-refusal` (each of its diffs wrote the line it now anchors) | | `objectstack-ai#17058` | 1/1 | 1/0 | `94c930248`: parses the dataset-query selection at the door, with its leniency sweep | | `objectstack-ai#19307` | 1/1 | 1/0 | `8f6d83147`: the `sys_permission_set` duplicate-name refusal carries `UNIQUE_VIOLATION` | Every cited sha resolves to exactly one commit (`git rev-parse --disambiguate`, count 1), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0). That is 30 distinct shas. Two wordings to check, both true of their commit: - `error-code-ledger.zod.ts:919`: 「the contract review that passed commit 1a47a53 ruled option B」. The review record sat on PR objectstack-ai#11858, which answers 404 today. The line keeps that review's ruling as stated before and names the commit that landed it. - `discovery.zod.ts:291`: 「route B toward the single API base that commit fec7848 gave the direct-mount routes」. objectstack-ai#6633 is live and stays, and the dead goal it pointed at is now named by the commit that delivered it. ## The 34 string sites left as tokens - **Test titles (33 sites).** `protocol.test.ts` 12, `rest-server.test.ts` 12, `export.test.ts` 3, `error-code-ledger.test.ts` 2, `validate-data.test.ts` 2, and 1 each in `discovery.test.ts` and `endpoint.test.ts`. - **Non-test string (1 site).** `error-code-ledger.zod.ts:1692`, the `reason` of the `FLOW_INPUT_SCHEMA_INVALID` row in the exported `PROVENANCE_WAIVERS` table (it cites `objectstack-ai#11504`). It ships in the package as data. The one reader, `scripts/check-error-code-provenance.ts`, checks the table and never prints a `reason` to an author. So it is neither a comment nor author-shown text in the form D sense, and it is not rewritten here. No author-shown text was found in `api/`: nothing here is objectstack-ai#20233's form D. ## Mechanical guard: no code token moves The check is a comments-stripped token comparison, base `21ab41041` against head `22a00c42d` (the later commits add only the changeset and the regenerated pages). It uses the TypeScript parser's leaf tokens, so template literals are scanned in context, and it excludes JSDoc nodes. It ran over all 22 touched `.ts` files. - Real run: 88,201 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control: 0 files changed, as expected (exit 0). - Positive control (a declaration appended): 1 file reads DIFFER (exit 1). - Positive control (one digit changed inside a `rest-server.test.ts` test-title string): 1 file reads DIFFER (exit 1). ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/spec` is included. It says only that the provenance comments were re-anchored. Measured on the built package: 8 of the touched sources are `src/**/*.zod.ts`, which `files[]` ships verbatim. The rewritten docblocks also reach `dist`: `2a29caa53`, `cccbe51bf`, `84c86fb45`, `613bfbd3d` and `90bbf2510` each appear in 1 declaration file, and `613bfbd3d` and `79c46da90` each appear in 6 bundled `.js` files. The positive control, a pre-existing `protocol.zod.ts` docblock sentence, appears in `dist/api/index.d.ts`. ## Gates (head `24d9fba68`) - **Citation judging pass, run as CI runs it:** `pnpm check:issue-citations && node scripts/check-issue-citations.mjs` exits 0. The self-test passes 73 cases in 7 batteries. The live run judged 13 citations across 8 files, and all 13 resolve. These are the live numbers left on changed lines, such as objectstack-ai#4633, objectstack-ai#4828, objectstack-ai#12004, objectstack-ai#11679 and objectstack-ai#16404. - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at the final head derived 111 families, and all 111 exit 0. `--ran` reports 111 run, 0 NOT MEASURED, 0 unrun, and exits 0. - At the earlier head, before the reference pages were regenerated, the list held 87 families. `check:docs` exited 1 there; that is what proved the three pages stale. - Four gates (`check:doc-formula-expressions`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`, `check:type-check-debt`) first exited 3, PREREQUISITE NOT MET. They exit 0 after a full `turbo run build` of `./packages/*` (71 tasks, exit 0, under the shared verify lock). - **Build, tests, typecheck and lint:** - `pnpm --filter @objectstack/spec build` exits 0. - `vitest run --maxWorkers=2 src/api` in `packages/spec`: 47 files and 1,518 tests pass. That covers all 13 touched test files. - The three spec script suites that read `api/` source text (`check-error-code-provenance`, `file-description`, `schema-index`) pass: 3 files, 144 tests. - `pnpm --filter @objectstack/spec typecheck` exits 0, including `check:test-typecheck` (53 files, 255 errors, 142 pinned signatures held). - `eslint --no-inline-config --format json` over the 22 touched `.ts` files: 22 files, 0 errors, 0 warnings. All 22 are in eslint's own population (`isPathIgnored` false for each). The config never enables type-aware linting, so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. ## Acceptance notes - **The objectui PR number.** `export-job-family-retirement.test.ts:25` reads 「objectui retired its side first (objectui#10247, merged as objectui PR objectstack-ai#10264)」. The number belongs to objectui's board, but the citation grammar reads `objectui PR objectstack-ai#10264` as a bare number of this repository, so the census counts it as dead here (it is 404 on this board). `objectstack-ai/objectui` answers 403 to this session, on both REST and the web page, so whether that PR is live is NOT MEASURED. The line is left unchanged. This is the same grammar family as objectstack-ai#20330's `pre-` / `post-` blind spot, a qualifier the grammar does not recognise, and it is named there, not filed. - **Base.** The branch is 2 commits behind `origin/main` (`d3958bac6`, read at 2026-09-28). Neither commit touches `packages/spec/src/api` or `content/docs/references/api`, and a no-driver `merge-tree` of the head onto `d3958bac6` exits 0. So there was no merge. - **objectstack-ai#20295's branch.** It edits `rest-server.test.ts` too, in hunks at base lines 114 to 136, 152 to 185 and 663 to 679. This PR's four comment edits there are at lines 198, 206, 266 and 371. A no-driver `merge-tree` of this head with that branch's head `2e575f156` exits 0. - **Surface.** The regenerated reference pages under `content/docs/references/api/` are the one set of files outside `packages/spec/src/api/**` besides the changeset. They are generator output projected from the rewritten docblocks, and the required `TypeScript Type Check` job reds without them. --- _Generated by [Claude Code](https://claude.ai/code/session_01CiCTczDo7tGhafXjf61dUJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…its that decided them (stage 3) (objectstack-ai#20533) Part of objectstack-ai#20234 Clause-②: no ## What changed This is stage 3 of the staged sweep. It covers `packages/spec/src/data/**` and nothing else. It leaves out the files an open PR or an in-flight claim holds: `data-engine.zod.ts`, `data-engine.test.ts`, `hook.form.ts`, `analytics*.ts`, `cube-member-inner-name-retirement.test.ts`, `driver/turso.zod.ts` and `filter-subtree-provenance.ts`, as the claim names them. It also leaves out four files that open PRs started editing after the claim: `driver/turso.test.ts` (PR objectstack-ai#20504, objectstack-ai#20437's, opened 2026-09-28T20:08Z), `object.form.ts` (PR objectstack-ai#20519, objectstack-ai#20432's, 21:55Z), `object.zod.ts` (PR objectstack-ai#20521, objectstack-ai#20494's, 22:10Z) and `filter-logic-conformance.ts` (PR objectstack-ai#20523, objectstack-ai#20444's, 22:39Z). See Acceptance notes. Later stages cover the other areas, so this PR says `Part of`. 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). That is **163 sites on 161 lines in 44 files, covering 40 numbers**. 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 in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of the 43 dead numbers in scope. ADR-0104 names objectstack-ai#12380 only as a reference, and ADR-0055 states the rule that objectstack-ai#8772's ruling enforced, not the ruling itself. So every anchor is a commit: **38 distinct shas**. One number was dropped rather than anchored: objectstack-ai#17286, a tracking card that recorded an axis as undecided, under which no commit landed. The sentence keeps its reason in words. Three comment sites in scope are left on purpose (see Acceptance notes). Two are the `[objectstack-ai#6259]` marker in `api-derivation.ts:163`, which a test string reads, and the test comment that names that marker. The third is `field.zod.ts:370`, whose `objectstack-ai#6111` is objectui's number. Only comments changed. Every source file keeps its line count (174 lines out, 174 in, over 45 files), so no line citation into these files moves. Thirteen of those 174 lines held no dead citation. Eleven are the other half of a sentence that had to be reflowed or rewritten. One is a table header (`value-roundtrip-conformance.ts:20`, 「card」 to 「card or commit」, because its row now holds a commit). One is `api-derivation.ts:164`, which now carries the `[objectstack-ai#6259]` sentence's commit. No code token moves (see the guard below). The 41 string-literal sites that carry a dead number are tokens, so they are left as they were and listed below. **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. Two more kinds of file change, both mechanical: - **One regenerated reference page.** Two of the rewritten docblock lines (`feed.zod.ts:15`, `:18`) project into `content/docs/references/data/feed.mdx`. `check:docs` proved that page stale, and `pnpm --filter @objectstack/spec check:generated --fix` regenerated only it. The diff is two lines, each the same substitution as its source line. No page a held file projects into (`analytics.mdx`, `data-engine.mdx`, `hook.mdx`, `driver-turso.mdx`) moved. - **A `patch` changeset** for `@objectstack/spec` (see Changeset below). ## Census: `data/`, before and after **Instrument.** This is the instrument of stages 1 and 2. It sends REST `GET /repos/objectstack-ai/objectstack/issues/N` without following redirects, for every distinct number cited in `packages/spec/src/data`. The population is: - the citation gate's own exported `CITATION_RE` and `NON_CITATION_HEADS`, kept when the qualifier is none, `objectstack`, `objectstack-ai/objectstack`, `framework`, `pre-` or `post-`; - widened here to the capitalised spellings of those qualifiers (`Pre-`, `POST-`, `Framework`: 7 sites, one of them dead), which stage 2's case-sensitive set did not read; - N of 100 or more, excluding `summon` heads. Each site is classified by the TypeScript parser as a line comment, a docblock, a block comment or a string. **Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at the start, after every 100 numbers and at the end. They read 24 of 24 lit (200) and 24 of 24 dead (404) over 8 checkpoints in both runs. | reading | tree | numbers probed | 200 | 404 | 301 or other | dead sites, all of `data/` | in scope | excluded (held files) | in-scope lines | in-scope files | dead numbers in scope | |---|---|---|---|---|---|---|---|---|---|---|---| | before | base `9bf5e67af`, probed 2026-09-28T19:32Z to 19:36Z | 618 | 571 | 47 | 0 | **240** | 207 | 33 | 204 | 47 | 43 | | after | head `96fd49caa2`, probed 2026-09-28T23:19Z to 23:23Z | 600 | 571 | 29 | 0 | **77** | 44 | 33 | 43 | 16 | 21 | **Before, in scope, by class.** 92 non-test docblock sites and 13 non-test line comments. 16 test docblock sites and 45 test line comments. 39 test string sites. 2 non-test string sites. **After, in scope.** 41 string sites and 3 comment sites remain, all three deliberate. The head probe found no number newly dead since the base probe: the same 571 numbers answer 200. PR objectstack-ai#20226's area table read `data` 239 at an earlier base; this census reads 240 at `9bf5e67af`. The 33 excluded sites sit in `object.zod.ts` (15), `analytics.zod.ts` (3), `analytics-strictness-batchd.test.ts` (2), `analytics-date-range-two-bound-window.test.ts` (1), `driver/turso.zod.ts` (2), `driver/turso.test.ts` (3), `filter-subtree-provenance.ts` (3), `filter-logic-conformance.ts` (3) and `object.form.ts` (1). `data-engine.*` and `hook.form.ts` carry none. ## Per-number table The counts are in-scope sites and files at the base. `rewritten / left` gives comment sites rewritten and sites left. Every anchor was read in its diff or message, not only in its subject: it is the commit that made the change the line now describes, and its own diff or message names the number it replaces. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#6111` (objectui) | 1/1 | 0/1 | objectui's number, left: see Acceptance notes | | `objectstack-ai#6259` | 5/2 | 1/4 | `6968885ef`: retires the producer-less `batch: 'bulk'` row of `DATA_ACTION_TO_API_OPERATION` and the prose calling `batch` a runtime action. The marker and 2 test strings stay (see Acceptance notes) | | `objectstack-ai#6345` | 18/5 | 17/1 | `e2798fab7`: one driver vocabulary; both boot hosts read the shared table; `mongo` to `mongodb`; turso a builtin; the fork-1 and fork-2 refusals | | `objectstack-ai#6571` | 10/2 | 8/2 | `2f3e79351`: `$between` endpoints accept the ISO/clock strings the platform produces, as a bare string (rider ①) | | `objectstack-ai#8495` | 9/2 | 6/3 | `4bfe1a539`: refuses `${…}` placeholders in memory `persistence.path` / `persistence.key` at publish | | `objectstack-ai#8656` | 1/1 | 0/1 | a test title only | | `objectstack-ai#8696` | 20/8 | 17/3 | `90a12fb18`, the card's mongodb arm: a bound secret rides beside an unmodified url as MongoClient `auth`. Its own pins carry the multi-host form `new URL()` cannot parse and the bound secret outranking `options.auth` | | `objectstack-ai#8772` | 3/2 | 3/0 | `75b7c240a`: Direction 2 of the 2026-08-16 maintainer ruling. The builder forces `required: true` on a `master_detail` under `controlled_by_parent`, and raw parse stays tolerant. ADR-0055 stays cited beside it | | `objectstack-ai#8778` | 1/1 | 1/0 | `7901b2dd2`: stamp-only `tenancy.organizationField`, declared by `sys_api_key` | | `objectstack-ai#8794` | 2/1 | 2/0 | `1850ebbb0`: corrects the reuse-safety claim on the filter-subtree mark from the survey's measurement, and routes a mechanism change to a spec-seat ruling (stage 1's anchor too) | | `objectstack-ai#8836` | 2/1 | 2/0 | `1850ebbb0`: the same commit, which pins the invariant (one line carries both numbers) | | `objectstack-ai#8873` | 6/3 | 6/0 | `096106522`: a bound `credentialsRef` reaches the postgres server on the DSN branch. Its diff records that `pg` sends a password only when the server asks | | `objectstack-ai#8874` | 1/1 | 1/0 | `d70428ae7`: a declared mysql `ssl` reaches `mysql2` as its own TLS options object, because `mysql2` rejects a bare boolean | | `objectstack-ai#8876` | 9/5 | 6/3 | `d634e665b`: exports `urlUserinfoUsername`, and its diff states the asymmetry that a username is not credential material | | `objectstack-ai#9040` | 20/6 | 14/6 | `24206416a`: refuses a credential in the mongo options passthrough at publish, and redacts the passthrough secret paths on read | | `objectstack-ai#9041` | 22/2 | 17/5 | `d491625c1`: refuses a bound `credentialsRef` with a user-less mongo `config.url`, with the triage's fences | | `objectstack-ai#10165` | 5/1 | 1/4 | `801296050`: `ttl.onlyWhen` with the canonical null predicate (maintainer ruling 2026-08-20, option A) | | `objectstack-ai#10274` | 1/1 | 1/0 | `d1ba685ec`: re-measures the objectui pin citations and gates the class | | `objectstack-ai#10329` | 6/2 | 6/0 | `15d58dbf1`: retires the import lookup transform's steering params (ADR-0049) | | `objectstack-ai#10347` | 2/1 | 2/0 | `530c1df65`: the Archiver honours a declared `ttl` (maintainer ruling 2026-08-20) | | `objectstack-ai#10527` | 2/1 | 1/1 | `5649efbf9`: refuses a diverging retention + ttl + archive triple at parse time | | `objectstack-ai#11065` | 7/3 | 5/2 | `20950404c`: a boolean aggregand counts as 1 or 0 in `avg` and `sum`, the first face aligned. No commit message names the card; this is where the number first entered the tree | | `objectstack-ai#11195` | 3/1 | 2/1 | `b37231883`: `UserActionsConfigSchema` adopts `group` / `hideFields` / `rowColor` | | `objectstack-ai#11215` | 1/1 | 1/0 | `42a117b88`: documents `NoSQLIndexSchema.unique`'s deliberate scope-vocabulary omission | | `objectstack-ai#11350` | 1/1 | 1/0 | `ece4dad31`: records the 2026-08-23 maintainer ruling on entry nameability (stage 1's anchor too) | | `objectstack-ai#11408` | 2/1 | 1/1 | `f11fc61c5`: declares `editMode` (maintainer ruling 2026-08-24) | | `objectstack-ai#11507` | 5/2 | 5/0 | `88b9d749a`: declares `sys_activity.type` an open, author-extensible vocabulary (maintainer ruling 2026-08-24, direction 4) | | `objectstack-ai#11658` | 1/1 | 1/0 | `1a6a19c31`: opens `RecordActivityProps.types` to author-contributed kinds | | `objectstack-ai#12380` | 4/2 | 4/0 | `4045b954d`: makes the SQLite `Field.json` codec injective; its message carries the measured boundary | | `objectstack-ai#12868` | 1/1 | 0/1 | a test title only. Its comment site sits in `object.form.ts`, now held by PR objectstack-ai#20519; its deciding commit is `c459da6bc` (see Acceptance notes) | | `objectstack-ai#13156` | 1/1 | 1/0 | `fd289be45`: strips tracker ids from function-declaration-built refusal prose (the card's A half) | | `objectstack-ai#13644` | 3/2 | 2/1 | `34ce8e7db`: declares `ctx.referentialFieldClear` on `HookContextSchema` | | `objectstack-ai#14426` | 2/2 | 1/1 | `40a44b91b`: the undefined-comparand refusal prescribes the null predicate by its ruled spellings, position-safe | | `objectstack-ai#14676` | 1/1 | 1/0 | `13c48c2a5`: retires `connector.errorMapping`; its test states the same assertion-set reasoning | | `objectstack-ai#16126` | 2/2 | 2/0 | `859ded3ec`: refuses a whitespace-only `reference` on lookup / master_detail | | `objectstack-ai#16685` | 4/2 | 4/0 | `ed7243d52`: accepts boolean / toggle for sum / avg / min / max (decision batch objectstack-ai#80) | | `objectstack-ai#16867` | 3/2 | 2/1 | `0ee32edef`: `notNull` / `not_null` prescribe `storage.notNull`, not `required` | | `objectstack-ai#17014` | 3/2 | 2/1 | `80aef8032`: the one-day date-range presets prescribe a one-day window, and the table states its end-token convention | | `objectstack-ai#17286` | 1/1 | 1/0 | dropped: a tracking card with no landing. The sentence now says the card is gone and to measure `driver-memory` for the open set | | `objectstack-ai#17348` | 1/1 | 1/0 | `51efbf116`: pins the `driver-memory` temporal text-operator divergence by name in that driver's conformance suite | | `objectstack-ai#17590` | 1/1 | 1/0 | `e04a0aff2`: `$contains` on a JSON column is a per-dialect membership test (director-seat ruling 2026-09-12) | | `objectstack-ai#18012` | 8/3 | 7/1 | `176b03582`: `$between` requires two non-blank endpoints (decision batch objectstack-ai#146 item 5, letter A) | | `objectstack-ai#19377` | 6/2 | 6/0 | `a60c913de`: refuses a `{ $field }` reference as a `$between` endpoint at the runtime filter door | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0). That is 38 distinct shas. Wordings to check, each true of its commit: - `datasource.zod.ts:352` names only the card's mongo arm (`90a12fb18`) for "the defect class … closed", because the paragraph is about mongo. The card's mysql arm (`72050cc47`) is not cited anywhere in this stage. - `datasource.zod.ts:354`: 「the triage's, as commit d491625 landed them」. `d491625c1`'s message lists the fences as "per triage". - `filter.zod.ts:1021-1025`: the `objectstack-ai#17286` pointer becomes 「was measured on a tracking card … That card is gone: measure `driver-memory` for the open set, ⛔ not this text.」 The warning that this paragraph is not the authority is kept. ## The 41 string sites left as tokens - **Test titles and test-code strings (39 sites).** `driver/driver-credential-refusal.test.ts` 14, `object.test.ts` 6, `datasource-credential-redaction.test.ts` 3, `driver/driver-placeholder-refusal.test.ts` 3, `filter.test.ts` 3, `api-derivation.test.ts` 2 (the `split('[objectstack-ai#6259]')` literal and its message), `field.test.ts` 2, and 1 each in `date-range-presets.test.ts`, `driver/postgres.test.ts`, `field-rows-option-description.test.ts`, `filter-comparand-type.test.ts`, `hook.test.ts` and `object-strictness-batch20.test.ts`. - **Non-test strings (2 sites).** `aggregation-conformance.ts:398` and `:407`, the `note` of two exported `AGGREGATION_CASES` rows (`objectstack-ai#11065`, `objectstack-ai#11151`). They ship as data. Their only readers are driver conformance suites, which print a `note` as the assertion message when a case fails, to a driver developer and never to a metadata author. So they are neither comments nor form D author-shown text. This is the same disposition stage 1 gave the two `why` strings and stage 2 the `PROVENANCE_WAIVERS` reason. No author-shown text in `data/` carries a dead number, so nothing here is objectstack-ai#20233's form D. ## Mechanical guard: no code token moves The check compares leaf tokens with comments stripped, base `9bf5e67af` against head `96fd49caa2`. It uses the TypeScript parser's leaf tokens, so template literals are scanned in context, and it excludes JSDoc nodes. It ran over all 45 touched `.ts` files. - Real run: 140,379 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control: 0 files changed, as expected (exit 0). - Positive control (a declaration inserted into `feed.zod.ts`): 1 file reads DIFFER (exit 1). - Positive control (one digit changed inside the `split('[objectstack-ai#6259]')` string in `api-derivation.test.ts`): 1 file reads DIFFER (exit 1). ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/spec` is included. It says only that the provenance comments were re-anchored. Measured on the built package: 14 of the touched sources are `src/**/*.zod.ts`, which `files[]` ships verbatim. The rewritten docblocks also reach `dist`. `88b9d749a`, `e2798fab7` and `24206416a` each appear in 1 declaration file. `24206416a` appears in 20 bundled `.js` files and `2f3e79351` in 28. The positive control, a pre-existing `feed.zod.ts` docblock sentence, appears in `dist/data/index.d.ts`. ## Gates (head `96fd49caa2`) - **Citation judging pass, run as CI runs it:** `pnpm check:issue-citations && node scripts/check-issue-citations.mjs` exits 0. The self-test passes 73 cases in 7 batteries. The live run judged 11 citations across 25 files, and all 11 resolve. - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at the final head derived 108 families, and all 108 exit 0. `--ran` reports 108 run, 0 NOT MEASURED, 0 unrun, and exits 0. (`check:i18n` was derived at the earlier heads from `object.form.ts`, and left the set when that file went back to base.) - At an earlier head, four gates first exited 3 (PREREQUISITE NOT MET) because the workspace was unbuilt: `check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples` and `check:docs-transcript-drift`. At the final head a full `turbo run build` of `./packages/*` ran first (71 tasks, exit 0, under the shared verify lock), and every gate exited 0 on its first run. - `check:generated` was run under the lock against that build: all 15 artifacts are up to date. - **Build, tests, typecheck and lint:** - `pnpm --filter @objectstack/spec build` exits 0. - `vitest run --maxWorkers=2 src/data` in `packages/spec` at the final head: 107 files and 3,517 tests pass (1 todo), covering every touched test file. - The 12 spec suites outside `src/data` that read `data/` source text pass at the final head: 12 files, 503 tests. These are `scripts/{file-description,root-index,skill-map-guards,strictness-ledger}.test.ts`, `src/api/api-entry-graph.pin.test.ts`, `src/contracts/scoped-context.test.ts`, `src/shared/{alias-integrity,evaluated-slot-population,retired-key-migrate-sentence}.test.ts`, `src/system/constants/platform-object-names.test.ts`, `src/type-alias-convention.pin.test.ts` and `src/ui/dashboard.test.ts`. - `pnpm --filter @objectstack/spec typecheck` at the final head exits 0, including `check:test-typecheck` (53 files, 251 errors, 138 pinned signatures held). - Lint, as a proven narrowing at the final head: `eslint --no-inline-config --format json` over the 45 touched `.ts` files gives 45 files, 0 errors and 0 warnings. All 45 are in eslint's own population (`isPathIgnored` is false for each). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, which 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. ## Acceptance notes - **The `[objectstack-ai#6259]` marker.** `api-derivation.test.ts:236` splits `DATA_ACTION_TO_API_OPERATION`'s TSDoc on the literal `[objectstack-ai#6259]`, and a test string may not change here. So the marker line `api-derivation.ts:163` is byte-identical to the base, and the test comment at `:232` that names the marker stays too. The sentence's deciding commit sits on the next line instead: 「(both by commit 6968885)」. A first attempt wrote the commit onto the marker line itself. The diff-scoped `check-issue-citations` then read the kept `objectstack-ai#6259` as an added citation and exited 1, so it was moved one line down (commit `b93f08f8d0`). - **objectui's `objectstack-ai#6111`.** `field.zod.ts:370` reads 「objectui#6110 + objectstack-ai#6111 (section)」. The qualifier covers only the first number, so the citation grammar reads `objectstack-ai#6111` as this repository's (404 here). It is objectui's number: its introducing commit `f887e5249` writes `(objectui#6111)` in the same diff, and `objectstack-ai/objectui` answers REST 200 for objectstack-ai#6111 to this session (and for objectstack-ai#6110 and objectstack-ai#10264). objectui has no `refs/pull/6111/head`, so it is an issue there, not a PR. The line is left unchanged. This is objectstack-ai#20330's grammar family, the same as stage 2's `objectui PR objectstack-ai#10264`, and it is noted there, not filed. - **Capitalised qualifiers.** `CITATION_RE` classes `Pre-#N`, `POST-#N` and `Framework#N` (7 sites in `data/`) as cross-repo and never judges them. This census read them as this repository's. One was dead and is rewritten here (`object.test.ts:223`, `POST-objectstack-ai#10347`). This is the same objectstack-ai#20330 family as stage 1's `pre-` / `post-` finding. - **Four files held after the claim.** Each joined the exclusions and went back to the base bytes (hypothesis 2 of the dispatch). Each PR's hunks were disjoint from this PR's lines, but the dispatch's rule is file-level. - `driver/turso.test.ts`: PR objectstack-ai#20504 (objectstack-ai#20437's) opened at 2026-09-28T20:08Z and edits it. Its two comment sites (`:4`, `:58`, both `objectstack-ai#6345`) went back to blob `7fe99ebf9` in commit `86463ed0a1`. A no-driver `merge-tree` of that head with PR objectstack-ai#20504's head `5dfa45e9f` exits 0. - `object.form.ts`: PR objectstack-ai#20519 (objectstack-ai#20432's) opened at 21:55Z and edits it. Its one comment site (`:256`, `objectstack-ai#12868`, whose deciding commit is `c459da6bc`) went back to blob `60713e06f` in commit `3479600dda`. - `object.zod.ts`: PR objectstack-ai#20521 (objectstack-ai#20494's) opened at 22:10Z and edits one line at `:2123`. Its 15 comment sites (`objectstack-ai#8772`, `objectstack-ai#10165`, `objectstack-ai#10347`, `objectstack-ai#10527`, `objectstack-ai#11195`, `objectstack-ai#11408`, `objectstack-ai#13608`) went back to blob `befde04ca` in commit `96fd49caa2`. Their deciding commits are `75b7c240a`, `801296050`, `530c1df65`, `5649efbf9`, `b37231883`, `f11fc61c5` and `fc9ba76a5`, all read for this stage. - `filter-logic-conformance.ts`: PR objectstack-ai#20523 (objectstack-ai#20444's) opened at 22:39Z. Its 3 comment sites (`objectstack-ai#13195`) went back to blob `c9b32acba` in the same commit. Their deciding commit is `9dac1ae01`, with `PR objectstack-ai#13529` as the link. - **What stays for later stages.** - The 33 dead sites in the held files listed above. The later stage can reuse the deciding commits named for them here. - The 41 string sites and the 3 deliberate comment sites above. - The `data/` numbers that also appear in `packages/spec/src/migrations/**`. Those are objectstack-ai#20233's form D, or the migrations stage. - **The rung.** Several anchored changes also have ADR-0087 entries in `packages/spec/src/migrations`. Examples are `cbp-master-detail-required-forced` for objectstack-ai#8772, `filter-between-blank-endpoint-refused` for objectstack-ai#18012, the `datasource-*` entries for objectstack-ai#9040, objectstack-ai#9041 and objectstack-ai#8873, and the `mapping-lookup-params-removed` conversion for objectstack-ai#10329. This PR takes the commit rung, as stages 1 and 2 did, so it is precedent-consistent. The D3 id is the more durable in-repo record, if the ruling's first rung is later read to include those entries. - **The citation gate's reach.** It defers `packages/**/*.test.ts`, so 20 of the 45 touched `.ts` files never enter its judging population. The added-minus-removed count over the whole diff covers them: 0 numbers added. - **Base.** The branch is 22 commits behind `origin/main` (`1378ec7c0c`, read at 2026-09-29T00:18Z). Four of those commits touch `data/`, all in excluded files: objectstack-ai#20475's `hook.form.ts`, objectstack-ai#20487's `data-engine.*`, and, since this stage excluded them, PR objectstack-ai#20521's `object.zod.ts` (`9e1689f8e2`) and objectstack-ai#20444's `filter-logic-conformance.ts` (`fb386074f5`). None touches a file in this diff, and a no-driver `merge-tree` of the head onto `1378ec7c0c` exits 0. So there was no merge. The open-PR file lists were re-read at 00:18Z: 11 open PRs, none touching a file in this diff. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… commits that decided them (stage 4) (objectstack-ai#20548) Part of objectstack-ai#20234 Clause-②: no ## What changed This is stage 4 of the staged sweep: the `data/` remainder. It covers the six `packages/spec/src/data/` files stage 3 (PR objectstack-ai#20533, landed `03b19d9cfd`) left out because an open PR held them, and nothing else. They are `object.zod.ts`, `filter-logic-conformance.ts`, `object.form.ts`, `data-engine.zod.ts`, `data-engine.test.ts` and `hook.form.ts`. Later stages cover the other areas, so this PR says `Part of`. The census below measured all six. Three of them carry comment or docblock sites that cite a tracker number answering 404. `data-engine.zod.ts`, `data-engine.test.ts` and `hook.form.ts` carry none, so they are not in the diff. Every such site has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123). That is **19 sites on 19 lines in 3 files, covering 9 numbers**. 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. Where a PR number was already on the line (`PR objectstack-ai#13529`), it stays beside the commit as the link. No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of the 9 numbers: a search for each number, with and without `#`, finds nothing there. So every anchor is a commit: **9 distinct shas**. Stage 3 had already read these commits and recorded them in PR objectstack-ai#20533's body. They were not copied from there. Each one was re-read against the current line it anchors: its own message or diff names the number it replaces, and it made the change the line describes. `object.zod.ts` and `filter-logic-conformance.ts` moved on `main` after stage 3 read them (PRs objectstack-ai#20521 and objectstack-ai#20523). Each site was therefore re-read at this base, `03b19d9cfd`. Only comments changed. Every source file keeps its line count (20 lines out, 20 in, over 3 files), so no line citation into these files moves. One of the 20 lines held no dead citation: `filter-logic-conformance.ts:249`, the first half of a sentence reflowed onto `:250`. No code token moves (see the guard below). **No tracker number is added.** Every tracker number on an added line was already in the hunk it replaces. `PR objectstack-ai#13529` stands on three added lines, and on the three removed lines of the same hunks. It is the link beside commit `9dac1ae01`, which stage 3 recorded the same way. No reference page under `content/docs/references/` moved: none of the rewritten docblocks projects into one (`check:docs` at the head: `226 generated files in sync`). The PR adds one `patch` changeset for `@objectstack/spec` (see Changeset below). ## Census: the six files, before and after **Instrument.** This is the instrument of stages 1 to 3. It sends REST `GET /repos/objectstack-ai/objectstack/issues/N` without following redirects, for every distinct number cited in `packages/spec/src/data`. The population is: - the citation gate's own exported `CITATION_RE` and `NON_CITATION_HEADS`, kept when the qualifier is none, `objectstack`, `objectstack-ai/objectstack`, `framework`, `pre-` or `post-`; - widened case-insensitively to `Pre-`, `POST-` and `Framework`, as in stage 3; - N of 100 or more, excluding `summon` heads. Each site is classified by the TypeScript parser as a line comment, a docblock, a block comment or a string. Two cross-checks close the population. First, a raw `#N` count in each of the six files equals the census rows plus the cross-repo rows in five files. In the other two it is one higher, and the extra is a second number after a slash inside a string (`objectstack-ai#5322/objectstack-ai#5134` in a `note`, `objectstack-ai#6262/objectstack-ai#6433` in a test title). Both answer 200. Second, no spelled citation (`issue N`, `PR N`, `card N`) occurs in any of the six. **Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at the start, after every 100 numbers and at the end: 24 of 24 lit (200) and 24 of 24 dead (404) over 8 checkpoints in the base run, and 21 of 21 lit and 21 of 21 dead over 7 checkpoints in the head run. | reading | tree | numbers probed | 200 | 404 | 301 or other | dead sites, all of `data/` | dead sites, the six files | lines | files | numbers | |---|---|---|---|---|---|---|---|---|---|---| | before | base `03b19d9cfd`, probed 2026-09-29T01:11:59Z to 01:15:49Z | 601 | 572 | 29 | 0 | **77** | 19 | 19 | 3 | 9 | | after | head `53c9070dfd`, probed 2026-09-29T01:25:55Z to 01:29:35Z | 597 | 572 | 25 | 0 | **58** | 0 | 0 | 0 | 0 | The head probe found no number newly dead since the base probe: the same 572 numbers answer 200. The base reading of 77 equals stage 3's after reading at `96fd49caa2`. **Per file.** Cited sites here are every in-repo citation the population reads, live or dead. | file | cited sites (base) | dead sites before | by class | dead sites after | |---|---|---|---|---| | `object.zod.ts` | 120 | 15 | 8 docblock, 7 line comment | 0 | | `filter-logic-conformance.ts` | 97 | 3 | 2 docblock, 1 line comment | 0 | | `object.form.ts` | 31 | 1 | 1 line comment | 0 | | `data-engine.zod.ts` | 48 | 0 | | 0 | | `data-engine.test.ts` | 29 | 0 | | 0 | | `hook.form.ts` | 0 | 0 | | 0 | None of the 19 sites is a string, so this stage leaves no string token behind. ## Per-number table | number | sites / lines | anchor: what it decided | |---|---|---| | `objectstack-ai#8772` | 4 / 4, `object.zod.ts:2718`, `:2731`, `:2744`, `:2910` | `75b7c240a`: Direction 2 of the 2026-08-16 maintainer ruling. `ObjectSchema.create()` forces `required: true` on a `master_detail` reference under `controlled_by_parent` and refuses an explicit `required: false`. Raw parse stays tolerant, and runtime tolerance is the ruling's other half. Its changeset records the measurement that only the security gate closed that shape while the declaration surface accepted it (`:2731`). ADR-0055 stays cited beside it. It is the same anchor stage 3 gave `object.test.ts` | | `objectstack-ai#10165` | 2 / 2, `object.zod.ts:818`, `:1036` | `801296050`: `ttl.onlyWhen` with the canonical null predicate (maintainer ruling 2026-08-20, option A). One shared `onlyWhen` union, and both of `retention.onlyWhen`'s conflicts mirrored. Its diff wrote both `[objectstack-ai#10165]` blocks | | `objectstack-ai#10347` | 3 / 3, `object.zod.ts:1006`, `:1042`, `:1049` | `530c1df65`: the Archiver honours a declared `ttl`. It selects by the ttl cutoff on `ttl.field` when `ttl` is declared, and by `created_at` / `archive.after` otherwise (maintainer ruling 2026-08-20) | | `objectstack-ai#10527` | 1 / 1, `object.zod.ts:1005` | `5649efbf9`: refuses a diverging retention + ttl + archive triple at parse time. Its diff wrote this very paragraph | | `objectstack-ai#11195` | 1 / 1, `object.zod.ts:1791` | `b37231883`: `UserActionsConfigSchema` adopts `group` / `hideFields` / `rowColor` (the "last three" the line names) | | `objectstack-ai#11408` | 1 / 1, `object.zod.ts:2189` | `f11fc61c5`: declares `editMode` on the object document (maintainer ruling 2026-08-24, the `objectstack-ai#10144` declare-or-rule-out family, which stays cited) | | `objectstack-ai#13608` | 3 / 3, `object.zod.ts:2317`, `:2354`, `:2366` | `fc9ba76a5`: `publicSharing.eligibility` is held at redemption, not only at mint, fail-closed, with the undifferentiated `null` refusal. Its changeset heads with objectstack-ai#13608. It is the same anchor stage 1 gave `contracts/share-link-service.ts` | | `objectstack-ai#13195` | 3 / 3, `filter-logic-conformance.ts:190`, `:250`, `:525` | `9dac1ae01`, PR objectstack-ai#13529's squash commit, which stays as the link: `$exists` means has-a-value on driver-memory's live mingo path, its analytics face and driver-mongodb's `translateFilter` (the "last three key-presence exits") | | `objectstack-ai#12868` | 1 / 1, `object.form.ts:256` | `c459da6bc`: narrows the per-option `default` key out of the form-view options vocabulary, which offered a key nothing on that surface read. Commit `e808890958`, which wrote this line, names objectstack-ai#12868 as the same offer-vs-door class | The shas were checked at the base and again at `origin/main` `288611e3e5`. Every one matches exactly one commit (`git rev-parse --disambiguate`, count 1). Every one is an ancestor (`git merge-base --is-ancestor`, exit 0 for 9 of 9). The control leg `e9584681a4` also exits 0, and the repository is not shallow. For each commit, a grep of its own message or diff finds the number it replaces. Seven of the nine name it in the message. `fc9ba76a5` names it in its diff (20 lines, including its changeset heading), and so does `c459da6bc` (8 lines, including its changeset heading). Wordings to check, each true of its commit: - `object.zod.ts:2731` now reads 「closes that shape, and commit 75b7c24 records that the declaration and the enforcement disagree」. The measurement was the card's. The commit's changeset records it: "only the security gate closed that shape while the declaration surface accepted it". - `object.zod.ts:2189` reads 「Declared here by commit f11fc61's maintainer ruling」, and `:2744` reads 「the other half of commit 75b7c24's ruling」. This is stage 3's wording for the same relation (`object.test.ts`, 「the other half of commit 75b7c24's ruling」): the commit that landed the ruling and quotes it. - `object.zod.ts:1049` reads 「That is the whole of what [commit 530c1df] changed here」. Commit `52db1d1f2a` wrote the paragraph. `530c1df65` is the change it describes. ## Mechanical guard: no code token moves The check compares leaf tokens with comments stripped, base `03b19d9cfd` against head `53c9070dfd`. It uses the TypeScript parser's leaf tokens (TypeScript from the head's lockfile), so template literals are scanned in context, and it excludes JSDoc nodes. It ran over all 3 touched `.ts` files. It is the stage-3 instrument, unchanged. - Real run: 13,624 base tokens (object.zod.ts 8,774, object.form.ts 3,226, filter-logic-conformance.ts 1,624), **0 files with a token change** (exit 0). - Comment-insertion control (`object.form.ts`): 0 files changed, as expected (exit 0). - Positive control (a declaration inserted into `object.zod.ts`): 1 file reads DIFFER at token 1629 (exit 1). - Positive control (one digit changed inside the `objectstack-ai#5322/objectstack-ai#5134` `note` string in `filter-logic-conformance.ts`): 1 file reads DIFFER at token 889 (exit 1). Line balance: `object.zod.ts` +15 / -15, `filter-logic-conformance.ts` +4 / -4, `object.form.ts` +1 / -1. Line counts are equal at base and head: 3,240, 621 and 751. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/spec` is included. It says only that the provenance comments were re-anchored. `Clause-②: no`: no export, key, value or type moves (the guard above). Measured on the head's built package: `object.zod.ts` is `src/**/*.zod.ts`, which `files[]` ships verbatim. The rewritten comments also reach `dist`: - `9dac1ae01` appears in `dist/data/index.d.ts` (the `filter-logic-conformance.ts` docblock) and in 4 bundled `.js` files; - `fc9ba76a5`, `f11fc61c5` and `b37231883` each appear in 22 bundled `.js` files, and `c459da6bc` in 12; - the positive control, the pre-existing `object.zod.ts` sentence 「Fail-CLOSED at both points」, appears in 11 bundled `.js` files. ## Gates (head `53c9070dfd`) - **Citation judging pass, run as CI runs it:** `pnpm check:issue-citations && node scripts/check-issue-citations.mjs` exits 0. The self-test passes 73 cases in 7 batteries. The live run judged 6 citations across 3 files: 3 resolve (`objectstack-ai#9138` twice, `objectstack-ai#11410`) and 3 resolve as a pull request (`objectstack-ai#13529`, the link). - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at the head derived 79 families, and all 79 exit 0. `--ran` reports 79 run, 0 NOT MEASURED, 0 unrun, and exits 0. A full `turbo run build` of `./packages/*` ran first, under the shared verify lock: 71 of 71 tasks, VERDICT command-exit 0. So no gate met an unbuilt prerequisite. - `pnpm --filter @objectstack/spec run check:generated`: under the lock against that build, `All 15 generated artifacts are up to date`, VERDICT command-exit 0. - **Tests and typecheck:** - `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/data` under the lock: Test Files 107 passed (107), Tests 3527 passed, 1 todo (3528), VERDICT command-exit 0. It covers every test in `data/`, among them `object.test.ts`, which reads these schemas. - The 13 spec suites outside `src/data` that read the touched files' source text or pin their line numbers, under the lock: Test Files 13 passed (13), Tests 544 passed (544). They are stage 3's 12 (`scripts/{file-description,root-index,skill-map-guards,strictness-ledger}.test.ts`, `src/api/api-entry-graph.pin.test.ts`, `src/contracts/scoped-context.test.ts`, `src/shared/{alias-integrity,evaluated-slot-population,retired-key-migrate-sentence}.test.ts`, `src/system/constants/platform-object-names.test.ts`, `src/type-alias-convention.pin.test.ts`, `src/ui/dashboard.test.ts`) plus `src/shared/union-author-message-pins.test.ts`, which pins `data/object.zod.ts:855`. - `pnpm --filter @objectstack/spec typecheck` under the lock exits 0, including `check:test-typecheck` (53 files, 251 errors, 138 pinned signatures held). - **Lint, as a proven narrowing at the head:** `eslint --no-inline-config --format json` over the 3 touched `.ts` files gives 3 files, 0 errors and 0 warnings. All 3 are in eslint's own population (`isPathIgnored` is false for each). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, which 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. ## Acceptance notes - **Base.** The branch forked from `03b19d9cfd`, stage 3's landing. `origin/main` then moved two commits (`05077d4c26`, PR objectstack-ai#20532, and `288611e3e5`, PR objectstack-ai#20536), and neither touches `data/`. `dispatch-gates` flagged its derivation as stale because `scripts/regen-artifacts.mjs` had moved, so `origin/main` was merged in (`53c9070dfd`, a clean merge with no driver-deferred path) before the gates ran. The PR's delta against `origin/main` is exactly its 4 files. `origin/main` has since moved two more commits: `7e36a3cd7c` (PR objectstack-ai#20531) and `ba5927f714` (PR objectstack-ai#20460). Neither touches `data/` or anything the gate derivation reads, and a re-derivation prints the same 79 commands. A no-driver `merge-tree` of the head onto `ba5927f714`, from a bare shared clone, exits 0. So there is no second merge. - **Open PRs, re-read at 2026-09-29T02:01Z:** 9 open PRs, and none touches any of the six files. The `data/` files open PRs touch are objectstack-ai#20458's `analytics*` files, objectstack-ai#20504's `driver/turso.*`, and objectstack-ai#20545's `filter-number-comparand-declared-type.*`, which is disjoint. Since the claim, PR objectstack-ai#20460 has landed (`ba5927f714`) without touching `filter-subtree-provenance.ts`. That file's 3 dead sites are outside this claim's fence, so they are left for a later stage. - **The rung.** Two anchored changes also have ADR-0087 entries in `packages/spec/src/migrations`: `cbp-master-detail-required-forced` for objectstack-ai#8772, and `form-view-option-default-retired` for objectstack-ai#12868. The second entry's own header names commit `c459da6bc`. This PR takes the commit rung, as stages 1 to 3 did. The D3 id is the more durable in-repo record, if the ruling's first rung is later read to include those entries. - **What stays in `data/` after this stage: 58 dead sites.** - **12 comment sites in files other open work still holds.** `analytics.zod.ts`, `analytics-strictness-batchd.test.ts` and `analytics-date-range-two-bound-window.test.ts` hold 5 (objectstack-ai#20300, PR objectstack-ai#20458). `driver/turso.zod.ts` and `driver/turso.test.ts` hold 4 (objectstack-ai#20437, PR objectstack-ai#20504). `filter-subtree-provenance.ts` holds 3. It was held by objectstack-ai#20367 and is now free (see above). - **3 comment sites stage 3 left on purpose.** They are the test-read `[objectstack-ai#6259]` marker at `api-derivation.ts:163`, the test comment at `api-derivation.test.ts:232` that names it, and `field.zod.ts:370`, whose `objectstack-ai#6111` is objectui's number. - **43 string sites**, left as tokens: 41 test strings (2 of them in the held analytics and turso test files) and the 2 exported `AGGREGATION_CASES` note strings in `aggregation-conformance.ts` (`:398`, `:407`, objectstack-ai#11065), which objectstack-ai#20489's claim holds. - **Outside `data/`,** the card's other remaining items are unchanged: the migrations and ui areas, the `liveness/**` notes, the `why` strings, the `PROVENANCE_WAIVERS` reason, and `rest-server.zod.ts`. - **The citation gate's reach.** It defers `packages/**/*.test.ts`. No test file is touched here, so all 3 touched files are in its judging population. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… sites to the commits that decided them (stage 5) (objectstack-ai#20576) Part of objectstack-ai#20234 Clause-②: no ## What changed This is stage 5 of the staged sweep: the `ui/` area (`packages/spec/src/ui/**`, 133 files), plus two sites freed since stage 4: `data/filter-subtree-provenance.ts` (PR objectstack-ai#20460 landed without touching it) and `meta-spelling/manifest-collection-spelling.ts` (the census hand-over, comment 5882628946 on objectstack-ai#20234). `ui/view-grouping-query.ts` is excluded because objectstack-ai#20446's claim holds it; it carries 4 citations and none of them is dead, so the exclusion removes nothing. Later stages cover the other areas, so this PR says `Part of`. Every comment and docblock site in that population that cites a tracker number answering 404 has been rewritten in ruling C+D's form C (comment 5749154545 on objectstack-ai#19123). That is **87 comment sites**: 84 re-anchored and 3 respelled. - **84 re-anchored, over 25 numbers.** Each rewritten line now cites the commit in `origin/main` history that decided what the line describes, and says in its own words what that commit decided. One line (`ui/dashboard.zod.ts:686`) quotes ADR-0087 itself; it keeps ADR-0087 as its citation and paraphrases the amendment heading instead of quoting its number. - **3 respelled**, so each number of a sibling pair carries its own qualifier: `ui/view.zod.ts:3634` now reads `objectui#6110 + objectui#6111`, and `ui/component.zod.ts:3479` and `:4140` now read `objectui#8221's PR objectui#8758`. Each second number answers 404 here, and the sentence attributes it to objectui (objectui REST: `issues/6111` 200, `pulls/8758` 200, merged 2026-09-09). Only comments changed, plus the one generated reference page they project into and a patch changeset. Every source file keeps its line count (90 lines out, 90 in, over 25 files). Three of the 90 lines held no dead number; each is the other half of a rewritten sentence: `ui/action.zod.ts:400`, `ui/action-param-carryover.test.ts:13` and `ui/expression-bindable-text-keys.test.ts:119`. No code token moves (see the guard below). **No tracker number is added.** Every tracker number on an added line was already on the lines it replaces, and no `PR #N` is added. ## Census: before and after **Instrument.** This is the instrument of stages 1 to 4, rebuilt for this stage. It sends REST `GET /repos/objectstack-ai/objectstack/issues/N` without following redirects, for every distinct number cited in the population. The population is: - the citation gate's own exported `CITATION_RE` and `NON_CITATION_HEADS` at the base, kept when the qualifier is none, `objectstack`, `objectstack-ai/objectstack`, `framework`, `pre-` or `post-`; - matched case-insensitively (`Pre-`, `POST-`, `Framework`); - N of 100 or more, excluding `summon` heads. A qualifier covers only the number it is joined to. Each site is classified by the TypeScript parser as a line comment, a docblock, a block comment or a string. **Controls.** The lit controls were `objectstack-ai#16862`, `objectstack-ai#16847` and `objectstack-ai#17698`. The dead controls were `objectstack-ai#16714`, `objectstack-ai#16715` and `objectstack-ai#16697`. They were probed at the start, after every 100 numbers and at the end: 18 of 18 lit (200) and 18 of 18 dead (404) over 6 checkpoints in the base run, and 15 of 15 and 15 of 15 over 5 checkpoints in the head run. | reading | tree | numbers probed | 200 | 404 | 301 or other | dead sites | lines | files | of which comments | of which strings | |---|---|---|---|---|---|---|---|---|---|---| | before | base `487a7846df`, probed 2026-09-29T02:57:01Z to 02:59:36Z | 400 | 372 | 28 | 0 | **111** | 110 | 26 | 90 | 21 | | after | head `83e39641d0`, probed 2026-09-29T03:15:00Z to 03:18:06Z | 383 | 372 | 11 | 0 | **24** | 23 | 8 | 3 | 21 | The head probe found no number newly dead since the base probe: the same 372 numbers answer 200. The head was probed at `83e39641d0`; every census file is byte-identical at the final head. **Cross-check under the grammar that landed during this stage.** PR objectstack-ai#20554 (`199002b3e4`) landed the closed qualifier set while this stage ran, and it reads `objectui PR objectstack-ai#8758` as objectui's number. Re-run with that gate's own `extractCitations` and `namesThisRepository`, the same population reads **108** dead sites before and **21** after, all 21 test strings. The difference is exactly the three `objectui PR objectstack-ai#8758` prose sites below, which that PR's own header measured as "census deaths here that are not deaths at all". **Per file.** Cited sites are every in-repo citation the population reads, live or dead. | file | cited sites (base) | dead before | by class | dead after | |---|---|---|---|---| | `data/filter-subtree-provenance.ts` | 9 | 3 | 3 docblock | 0 | | `meta-spelling/manifest-collection-spelling.ts` | 8 | 2 | 2 line comment | 0 | | `ui/action-param-carryover.test.ts` | 5 | 3 | 2 line comment, 1 string | 1 | | `ui/action.test.ts` | 39 | 3 | 3 line comment | 0 | | `ui/action.zod.ts` | 90 | 7 | 5 docblock, 2 line comment | 0 | | `ui/bulk-action.test.ts` | 9 | 4 | 2 line comment, 2 string | 2 | | `ui/bulk-action.zod.ts` | 9 | 3 | 2 docblock, 1 line comment | 0 | | `ui/component-element-navigation-17987.test.ts` | 8 | 5 | 1 docblock, 4 string | 4 | | `ui/component-type-vocabulary.test.ts` | 8 | 2 | 2 docblock | 0 | | `ui/component-type-vocabulary.ts` | 2 | 1 | 1 docblock | 0 | | `ui/component.test.ts` | 190 | 22 | 11 line comment, 11 string | 11 | | `ui/component.zod.ts` | 265 | 20 | 16 docblock, 4 line comment | 0 | | `ui/dashboard.zod.ts` | 52 | 1 | 1 docblock | 0 | | `ui/expression-bindable-text-keys.test.ts` | 3 | 2 | 2 line comment | 0 | | `ui/expression-bindable-text-keys.zod.ts` | 4 | 2 | 2 docblock | 0 | | `ui/form-select-option.test.ts` | 3 | 1 | 1 docblock | 0 | | `ui/index.ts` | 15 | 2 | 2 line comment | 0 | | `ui/interaction-config-retirement.test.ts` | 19 | 1 | 1 docblock | 0 | | `ui/react-blocks.test.ts` | 9 | 2 | 1 docblock, 1 string | 1 | | `ui/react-blocks.ts` | 17 | 4 | 2 docblock, 2 line comment | 0 | | `ui/view-form-features-root.test.ts` | 4 | 1 | 1 line comment | 0 | | `ui/view-metadata-schema.test.ts` | 31 | 3 | 2 line comment, 1 string | 1 | | `ui/view-submit-redirect-url.test.ts` | 8 | 1 | 1 line comment | 0 | | `ui/view.test.ts` | 136 | 2 | 1 docblock, 1 string | 2 | | `ui/view.zod.ts` | 331 | 13 | 10 docblock, 3 line comment | 2 | | `ui/widget-i18n-retirement.test.ts` | 17 | 1 | 1 line comment | 0 | `ui/` alone went from 106 dead sites in 24 files to 24. The other 107 `ui/` files carry no dead site. ## Per-number table Anchors are 9-hex commit abbreviations. "Wrote" means the commit's own diff added the line being rewritten. | number | comment sites / files | anchor: what it decided | |---|---|---| | `objectstack-ai#5970` | 4 / 2, `action.zod.ts:819`, `action.test.ts:268`, `:304`, `:354` | `97e7e3caa`: `ActionSchema.visible` / `disabled` speak one condition shape; `visible` gains its boolean arm. Stage 1's anchor for the same number | | `objectstack-ai#6276` | 7 / 1, `component.zod.ts:34`, `:165`, `:568`, `:2455`, `:2546`, `:2554`, `component.test.ts:2126` | `78f0be872`: declares `element:record_picker`'s flat `sort` / `limit` on the objectstack-ai#5611 rule (maintainer ruling 2026-08-08, direction A). It wrote `:34`, `:2455` and the "enumerate by the renderer's read pattern" lesson | | `objectstack-ai#8794`, `objectstack-ai#8836` | 3 / 1, `filter-subtree-provenance.ts:130`, `:131`, `:156` | `1850ebbb0`: corrects the reuse-safety claim from the survey and pins the invariant. It wrote `:131` itself. Stages 1 and 3 gave both numbers this anchor | | `objectstack-ai#9933` | 7 / 2, `view.zod.ts:2517`, `:5060`, `:5086`, `:5118`, `:5513`, `view-metadata-schema.test.ts:398`, `:410` | `d5552ca13`: admits `columnState` as an explicitly runtime-only view-overlay key, rejected by name at every authoring door. It wrote "explicitly out of objectstack-ai#9933's scope" | | `objectstack-ai#9972` | 3 / 2 (+1 unread), `component.zod.ts:2213`, `component.test.ts:382`, `:3565`; also `:3612`'s `objectstack-ai#9881/objectstack-ai#9972`, a slash-joined spelling the grammar does not read | `60e0f900a`: records the live read point of `page:tabs` `items[].icon` and its accept-pin. It wrote the `:382` header | | `objectstack-ai#10194` | 1 / 1, `manifest-collection-spelling.ts:71` (`pre-objectstack-ai#10194`) | `2306a765c`: `/meta/theme` and `/meta/analytics_cube` stop storing any JSON as success and validate at the write door. The line now says "the store-anything branch from before commit 2306a76". Stage 1's anchor | | `objectstack-ai#10274` | 6 / 2, `component.zod.ts:2304`, `component.test.ts:308`, `:405`, `:3591`, `:3603`, `:3612` | `d1ba685ec`: re-measures the four pin citations and gates the class. Its gate header records that the re-measure found two anchors wrong since they were written, which is why a refresh re-reads. Stage 3's anchor | | `objectstack-ai#10485` | 4 / 4, `index.ts:51`, `interaction-config-retirement.test.ts:116`, `widget-i18n-retirement.test.ts:112`, `manifest-collection-spelling.ts:67` | `35ad101bc`: retires the `themes` carrier and `ThemeSchema` whole (ruled B, 2026-08-21; ADR-0049 stays cited). Stage 1's anchor | | `objectstack-ai#11284` | 5 / 2, `react-blocks.ts:39`, `:94`, `:109`, `:295`, `react-blocks.test.ts:139` | `5383fa670`: the react tier converges on the metadata-tier vocabulary, deprecate-first. Its changeset heads "(objectstack-ai#11284, maintainer ruling 2026-08-23)" | | `objectstack-ai#11350` | 1 / 1, `index.ts:105` | `ece4dad31`: records the maintainer ruling of 2026-08-23 that a type in an entry's public declarations must be nameable from that entry. Same wording as stage 1's `kernel/index.ts:53` | | `objectstack-ai#11507` | 2 / 2, `component.zod.ts:1465`, `component.test.ts:2726` | `88b9d749a`: declares `sys_activity.type` an open, author-extensible vocabulary (maintainer ruling 2026-08-24, direction 4). Stage 3's anchor | | `objectstack-ai#11658` | 2 / 2, `component.zod.ts:1464`, `component.test.ts:2725` | `1a6a19c31`: opens `RecordActivityProps.types` to author-contributed kinds, executing that ruling. Stage 3's anchor | | `objectstack-ai#11703` | 3 / 2, `action.zod.ts:399` to `:400`, `:460`, `action-param-carryover.test.ts:12` to `:13` | `5cb62d88b`: `clone_permission_set` carries all five copied facets; its params list had silently dropped three. The lines now name "the silent-drop shape commit 5cb62d8 fixed" | | `objectstack-ai#11753` | 5 / 2, `action.zod.ts:66`, `:390`, `:398`, `:409`, `action-param-carryover.test.ts:1` | `0e4e51b0a`: `ActionParamSchema.carryOver`, the spec half of the 2026-08-25 maintainer ruling (recommendation A). It wrote every one of these lines, and its changeset records the `visible: false` measurement `:398` names | | `objectstack-ai#12194` | 1 / 1, `view.zod.ts:4838` | `311433f6b`: declares the metadata item-name grammar (`QUALIFIED_ITEM_NAME_PATTERN` among it) and refuses it at the publish door. Stage 2's anchor | | `objectstack-ai#12868` | 5 / 2, `view.zod.ts:2938`, `:2966`, `:3179`, `:7087`, `form-select-option.test.ts:4` | `c459da6bc`: narrows the per-option `default` key out of the form-view options vocabulary. Its changeset records the ruled census `:2966` cites ("measured ZERO occurrences"). Stages 3 and 4's anchor | | `objectstack-ai#12950` | 3 / 2, `component-type-vocabulary.ts:4`, `component-type-vocabulary.test.ts:4`, `:101` | `225e7690f`: created `component-type-vocabulary.ts`; its message records the readiness read `:101` pins (`global:search` and `global:notifications` stay declared) | | `objectstack-ai#13156` | 2 / 2, `view-form-features-root.test.ts:70`, `view-submit-redirect-url.test.ts:110` | `fd289be45`: strips tracker ids from function-declaration-built refusal prose. It wrote both lines. Stage 3's wording ("commit fd289be's strip") | | `objectstack-ai#13670` | 1 / 1, `expression-bindable-text-keys.zod.ts:72` | `8c6a7fc0b`: records `text.value` as deliberately omitted; its message states the ruling that `text`'s evaluation channel is `content` alone | | `objectstack-ai#13672` | 3 / 2, `expression-bindable-text-keys.zod.ts:89`, `.test.ts:65`, `:118` | `e854a531a`: narrows the `button` row to the spelling its key reaches, and records `action:button` and `ui:button` as deliberately out | | `objectstack-ai#16626` | 1 / 1, `component.test.ts:2336` | `30b099078`: the objectui pin bump to `53ded82bf7a4` that ships objectui#7754's array-analytics lowering, the door the family waited on. The association is PR objectstack-ai#16788's body (it names objectstack-ai#16626 as the card it lands), and `30b099078` is that PR's merge commit; neither its message nor its diff names objectstack-ai#16626 (the stage-3 objectstack-ai#11065 precedent) | | `objectstack-ai#17987` | 9 / 2, `component.zod.ts:10`, `:4014`, `:4062`, `:4153`, `:4193`, `:5068`, `:5226`, `:5457`, `component-element-navigation-17987.test.ts:4` | `e233db9db`: declares element-level `navigation` on `object-kanban` / `object-calendar` and gives `object-timeline` its `ComponentPropsMap` row, executing the objectui#8652 ruling (verbatim `B`) | | `objectstack-ai#18003` | 1 / 1, `dashboard.zod.ts:686` | **ADR-0087**, the rung above a commit. The line quoted the ADR's own amendment heading, number included. It now reads "(ADR-0087, its 2026-09-13 amendment, 「the level half」)": the ADR stays the citation and the fragment it quotes is verbatim | | `objectstack-ai#18177` | 5 / 2, `bulk-action.zod.ts:51`, `:169`, `:262`, `bulk-action.test.ts:61`, `:307` | `adabccf5f`: `BulkActionParamSchema` is strict and declares `dependsOn`, executing decision batch objectstack-ai#146 item 4, letter A | | `objectstack-ai#6111` | 1 / 1, `view.zod.ts:3634` | respelled `objectui#6111` (not re-anchored): it is objectui's number | | `objectstack-ai#8758` | 2 / 1, `component.zod.ts:3479`, `:4140` | respelled `PR objectui#8758` (not re-anchored): objectui's PR objectstack-ai#8758, merged 2026-09-09 | No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of the 25 re-anchored numbers except objectstack-ai#18003. ADR-0126 mentions objectstack-ai#11703 and objectstack-ai#11753 only as references ("permission-set precedent"), not as the record of either decision. **Anchor checks.** Every sha on an added line is one of 23, and none is on a removed line. At the base `487a7846df`: - each matches exactly one object (`git rev-parse --disambiguate`, count 1, 23 of 23); - each is an ancestor (`git merge-base --is-ancestor`, exit 0, 23 of 23); the control leg `e9584681a4` also exits 0, and the repository is not shallow; - for 22 of the 23, a grep of the commit's own message or diff finds the number it replaces (the message for 16; the diff for `0e4e51b0a`, `5383fa670`, `c459da6bc`, `225e7690f`, `e854a531a`, and for objectstack-ai#8836 in `1850ebbb0`). `30b099078` is the exception explained in the table. - Each commit was read for the rule its line states, not only for the number. In most cases the commit wrote the very line it now anchors. Wordings to check, each true of its commit: - `filter-subtree-provenance.ts:130` and `:156` read 「survey commit 1850ebb records」: the survey was the card's, and the commit's message records its measurement. It is stage 3's wording for the same relation (「from the survey it records」). - `component.zod.ts:1465` and `component.test.ts:2726` read 「maintainer ruling commit 88b9d74 declared」: that commit landed the ruling (direction 4) as the `sys_activity.type` declaration. - `manifest-collection-spelling.ts:71` reads 「the store-anything branch from before commit 2306a76」: before that commit, `PUT /meta/theme/:name` stored any JSON as success. ## Mechanical guard: no code token moves The check compares leaf tokens with comments stripped, base `487a7846df` against the head. It uses the TypeScript parser's leaf tokens from the head's lockfile, so template literals are scanned in context, and it excludes JSDoc nodes. It ran over all 25 touched `.ts` files, and every control mutates the head text in memory only. - Real run: 101,836 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control (`ui/index.ts`): 0 files changed (exit 0). - Positive control (a declaration inserted into `ui/view.zod.ts`): 1 file reads DIFFER at token 19222 (exit 1). - Positive control (one digit changed in a `component.test.ts` test title): 1 file reads DIFFER at token 14972 (exit 1). Line balance holds in every file, 90 out and 90 in over the 25, and every line count is equal at base and head. Tracker numbers: added-not-removed is empty in every file. The net-removed numbers are the 25 in the table, 85 sites: the census's 84 comment sites, plus the slash-joined `objectstack-ai#9972` at `component.test.ts:3612`. ## Generated page `check:generated` proved one artifact stale: `content/docs/references/ui/expression-bindable-text-keys.mdx`, the projection of `expression-bindable-text-keys.zod.ts`'s module docblock. `check:generated --fix` regenerated only that page, and a re-run read `All 15 generated artifacts are up to date`. Its two changed lines are the `:72` and `:89` substitutions verbatim. No other docblock here projects into a reference page, and nothing under `skills/**` moved. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/spec` is included. It says only that the provenance comments were re-anchored. `Clause-②: no`: no export, key, value or type moves (the guard above). Measured on the head's built package: 6 touched sources are `src/**/*.zod.ts`, which `files[]` ships verbatim. The rewritten comments also reach `dist`: - `c459da6bc` appears in 32 bundled `.js` files and 2 `.d.ts`; - `adabccf5f` in 24 `.js` and 2 `.d.ts`; `d5552ca13` and `0e4e51b0a` in 24 `.js` each; `e233db9db` and `78f0be872` in 2 `.js` and 2 `.d.ts` each; - the positive control, the pre-existing sentence 「the object-field face enforces」, appears in 32 files. ## Gates (head `1b885d3c27`) - **Citation judging pass, run as CI runs it:** `pnpm check:issue-citations && node scripts/check-issue-citations.mjs`, both under the grammar PR objectstack-ai#20554 landed, exit 0. The self-test passes 114 cases in 8 batteries. The live, diff-scoped run judged 13 citations across 11 files: 3 resolve and 10 are declared cross-repo references. It reads "every citation this change adds resolves". - **Doc authoring:** `pnpm check:doc-authoring` exits 0. Its 16,759 customer-facing strings across 1,174 spec sources carry no internal issue id, and the sibling-package prose-id baseline holds with no growth. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at this head derived 112 families, and all 112 exit 0. `--ran` reports 112 run, 0 NOT MEASURED, 0 unrun. A full `turbo run build` of `./packages/*` at this head ran first, under the shared verify lock: 71 of 71 tasks, VERDICT command-exit 0. So no gate met an unbuilt prerequisite. - **Five roster gates the derivation flags for this diff** (their rosters sit in `.changeset/` or `packages/`, so their silence proves nothing): `node scripts/check-changeset-fixed.mjs`, `pnpm --filter @objectstack/spec run check:spec-changes`, `pnpm check:authz-resolver`, `pnpm check:error-code-casing` and `pnpm check:filter-alias-parity`. All exit 0. - `pnpm --filter @objectstack/spec run check:generated` (derived) reads `All 15 generated artifacts are up to date`, and `check:docs` reads `226 generated files in sync`. - **Tests and typecheck, under the lock:** - `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 src/ui src/meta-spelling`: Test Files 98 passed (98), Tests 3452 passed (3452), VERDICT command-exit 0 (the chain held the lock 142s on a shared box). - The 16 spec suites outside `src/ui` that read the touched files' source text or pin their lines: Test Files 16 passed (16), Tests 489 passed (489). They are `scripts/{export-origins,file-description,root-index,schema-closure,skill-map-guards,strictness-ledger}.test.ts`, `src/ai/tool-confirmation-prescription-tense.pin.test.ts`, `src/api/api-entry-graph.pin.test.ts`, `src/contracts/scoped-context.test.ts`, `src/data/filter-subtree-provenance.test.ts`, `src/shared/{alias-integrity,evaluated-slot-population,retired-key-migrate-sentence,union-author-message-pins}.test.ts`, `src/system/constants/platform-object-names.test.ts` and `src/type-alias-convention.pin.test.ts`. Three more suites matched the reader scan and are not run here: `scripts/build-schemas-check-mode.test.ts` only imports `ViewItemSchema` (code the guard proves unchanged) and rebuilds schemas in a temp tree; `scripts/def-key-collisions.test.ts` names `ui/view.zod.ts` only in a comment; `scripts/published-projection-choke-point.test.ts` matched on `build-react-blocks-contract.ts`, not a touched file. They are left to CI. - `pnpm --filter @objectstack/spec typecheck`: exit 0, including `check:test-typecheck` (53 files, 251 errors, 138 pinned signatures held). The same three runs also passed, with the same counts, on the pre-merge tree. - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 25 touched `.ts` files gives 25 files, 0 errors and 0 warnings. All 25 are in eslint's own population (`isPathIgnored` is false for each, read through eslint's API). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, which its own lines 327 to 328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-wide `pnpm lint` is CI's run. - **Merge probe:** a `merge-tree` of the head onto `origin/main` `f572a7eb3c`, from a bare shared clone with no merge driver registered, exits 0 (2026-09-29T04:15Z). ## Acceptance notes - **Base and merge.** The branch forked from `487a7846df`, one commit past the claim's stamp `6154165484` (PR objectstack-ai#20551, outside the surface). `origin/main` then moved three commits, and `199002b3e4` (PR objectstack-ai#20554) changed `scripts/check-issue-citations.mjs`, so the gate derivation read STALE TREE. `origin/main` `dee9b26f6c` was merged in (`1b885d3c27`): a clean merge with no driver-routed path and no lockfile change, touching none of this diff's files. The PR's delta against `dee9b26f6c` is exactly its 27 files. `origin/main` has since moved one more commit, `1c761c0d71` (PR objectstack-ai#20565, tests in two other packages), which touches none of them. - **One site beyond the hand-over's list.** The hand-over named `manifest-collection-spelling.ts:71` (`pre-objectstack-ai#10194`). The same comment's first line, `:67`, cites `objectstack-ai#10485`, which also answers 404, and it is rewritten too. The claim names this file; the fix is the same defect class, mechanical in the form stages 1 to 4 fixed, in a file no other claim holds, under the same gates. Reverting it would be one line. - **Open PRs, re-read at 2026-09-29T04:22Z:** 8 open PRs, and none touches any file in this diff. The one that touches `ui/` is objectstack-ai#20570 (objectstack-ai#20446's), on the excluded `view-grouping-query.ts`. The in-flight `Claim:` comments on the 12 `pm:dispatched` cards were read too: only objectstack-ai#20446's names a `ui/` file (`view-grouping-query.ts`, excluded above). - **Hypothesis 2, measured.** In `ui/`, 14 sibling-qualified pairs leave the second number bare: 13 on one line (`+`, `and`, `,`, `/` or `'s PR` between them) and `component.zod.ts:3478` to `:3479`, split across a line break. In 3 of them the second number answers 404 here and the sentence attributes it to objectui (`objectstack-ai#6111` once, `objectstack-ai#8758` twice); they are respelled above. In the other 11 the second number answers 200 here, so it is judged as this repository's and left: `action.zod.ts:1603`, `component.test.ts:2052`, `:2199`, `:2315`, `component.zod.ts:3276`, `:3793`, `:4812`, `expression-bindable-text-keys.zod.ts:33`, `page.test.ts:696`, `react-blocks.ts:256` and `widget.zod.ts:34`. The PR objectstack-ai#20554 header measured `,` and `and` pairs as naming this repository's number and `/` pairs as mostly, but not always, the qualifier's. Whether any `/` pair here names objectui's number is not measured; a 200 here cannot tell. - **What stays in this population: 24 dead sites** (21 under the landed grammar). - **21 test strings**, left as tokens (vitest `it` / `describe` titles in 7 test files): `objectstack-ai#6276` ×6, `objectstack-ai#11658` ×3 and `objectstack-ai#11507` ×1 in `component.test.ts`; `objectstack-ai#9972` in `component.test.ts:411`; `objectstack-ai#17987` ×4 in `component-element-navigation-17987.test.ts`; `objectstack-ai#18177` ×2 in `bulk-action.test.ts`; `objectstack-ai#9933` in `view-metadata-schema.test.ts:406`; `objectstack-ai#11284` in `react-blocks.test.ts:147`; `objectstack-ai#11753` in `action-param-carryover.test.ts:17`; `objectstack#11195` in `view.test.ts:3396`. None is a Zod `.describe()` text, an exported string or a migration-entry field, so no form D site arises here. - **3 comments that name objectui's live PR objectstack-ai#8758 in prose** (`objectui PR objectstack-ai#8758`): `view.zod.ts:2354`, `:2579` and `view.test.ts:426`. They are not pairs, and the landed grammar reads them as objectui's. - **Left for later stages of objectstack-ai#20234** (the stage-4 landing comment 5882686893's list, unchanged): the migrations area, the `liveness/**` notes, the `why` strings and the `PROVENANCE_WAIVERS` reason, `rest-server.zod.ts`, the held `analytics*` and `driver/turso.*` files, the 2 `AGGREGATION_CASES` note strings, and `data/`'s test strings and deliberate markers. - **The same rot outside `packages/spec/src`** is objectstack-ai#20556's, not this card's. Examples met here: ADR-0087's own amendment heading (`docs/adr/0087-metadata-protocol-upgrade-contract.md:390`, `:397`) cites the dead `objectstack-ai#18003`, and ADR-0126 cites `objectstack-ai#11703` and `objectstack-ai#11753`; both are governed. `packages/spec/scripts/strictness-ledger.test.ts:375`, `:380` cite `objectstack-ai#9933`, and `check-objectui-pin-citations.ts` cites `objectstack-ai#10274` and `objectstack-ai#9972`. - **Unchanged wording.** `react-blocks.test.ts:140` says the 2026-08-23 ruling was "recorded on-card". The card is gone, and the changeset of `5383fa670` (now cited on `:139`) records the ruling. The line holds no number, so it is left. - **The citation gate's reach.** It defers `packages/**/*.test.ts`. The 11 touched non-test files are in its judging population. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…/src remainder to the commits and ADRs that decided them (stage 6) (objectstack-ai#20606) Part of objectstack-ai#20234 Clause-②: no Stage 6 of the staged sweep: the `packages/spec/src` remainder outside `migrations/` and the held files. Its claim is `5884233505`, with 22 files named there. Every comment or docblock line in those files that cited a tracker number answering 404 now cites what decided its rule, in ruling C+D's form C. That is the commit on `main` that decided the rule, or, for one number, the ADR amendment that records the ruling. Each line says in its own words what was decided. Comments only: 66 lines out, 66 in, across 21 files. No code token, string literal, `describe()` text or message-catalog string moves. Two dead sites stay byte-identical, because a test reads each one by literal. The census is the gate's own `node scripts/check-issue-citations.mjs --census --json`, filtered to the 22 paths. Before: base `c876a7426d`, board enumerated (185 pages, frontier objectstack-ai#20590). After: head `9d63cb6548`, frontier objectstack-ai#20604. ## Measurement | file (under `packages/spec/src/`) | dead before | after | numbers, then anchor | |---|---:|---:|---| | `api/rest-server.zod.ts` | 11 | 0 | objectstack-ai#14691 ×9 to `b3a63d32c`; objectstack-ai#14369 ×2 to `a3d5724c8` | | `system/i18n-resolver.ts` | 14 | 0 | objectstack-ai#12961 ×6 to `901355c3b`; objectstack-ai#13218 ×4 to `c45d8e6b4`; objectstack-ai#8460 ×2 to ADR-0029 D9.2a; objectstack-ai#10926 to `d173125fb`; objectstack-ai#13109 to `8b236c826` | | `system/operation-message.ts` | 4 | 0 | objectstack-ai#12493 ×4 to `aa5994e17` (docblock lines only) | | `system/translation.zod.ts` | 2 | 0 | objectstack-ai#10926 ×2 to `d173125fb` | | `system/core-services.zod.ts` | 1 | 0 | objectstack-ai#6604 to `d127ff002` | | `system/dev-login.zod.ts` | 1 | 0 | objectstack-ai#17081 to `24d622b94` (objectstack-ai#17556 stays, 200) | | `system/environment-artifact.zod.ts` | 1 | 0 | objectstack-ai#11333 to `e58ea8b38` (objectstack-ai#14865 and objectstack-ai#13457 stay, 200) | | `shared/identifiers.zod.ts` | 7 | 0 | objectstack-ai#12245 ×2 to `c41b42e8d`; objectstack-ai#12144 ×2 to `3a04b0125`; objectstack-ai#12194 and objectstack-ai#12176 (:140-141) to `311433f6b`; objectstack-ai#12176 (:189) to `7986d973f` | | `shared/metadata-collection.zod.ts` | 1 | 0 | objectstack-ai#10485 to `35ad101bc` | | `index.ts` (package root) | 6 | 0 | objectstack-ai#11350 ×5 to `ece4dad31` (objectstack-ai#11709 stays, 200); objectstack-ai#10485 to `35ad101bc` | | `automation/control-flow.zod.ts` | 1 | 0 | objectstack-ai#14419 to `c5a7448d5` (objectstack-ai#14954 stays, 200) | | `automation/execution.zod.ts` | 1 | 0 | objectstack-ai#13681 to `18d816a50` | | `automation/index.ts` | 1 | 0 | objectstack-ai#16659 to `ecdfc9411` | | `automation/schedule-organization.zod.ts` | 1 | 0 | objectstack-ai#16659 to `ecdfc9411` | | `ai/index.ts` | 1 | 0 | objectstack-ai#11350 to `ece4dad31` | | `identity/identity.zod.ts` | 1 | **1** | objectstack-ai#8715 kept at :230 (a test reads it); `2c86fe3ea` added on :231 | | `security/explain.zod.ts` | 1 | 0 | objectstack-ai#8714 to `42b05af89` | | `security/public-form.ts` | 1 | 0 | objectstack-ai#6640 to `2ab1257c9` | | `data/api-derivation.ts` | 1 | **1** | objectstack-ai#6259 kept at :163 (two tests read it); `6968885ef` already on :164; file untouched | | `data/driver/turso.zod.ts` | 2 | 0 | objectstack-ai#6345 ×2 to `e2798fab7` | | `conversions/walk.ts` | 1 | 0 | objectstack-ai#13031 to `b799ac553` | | `meta-spelling/metadata-url-spelling.ts` | 2 | 0 | objectstack-ai#10485 ×2 to `35ad101bc` | | **22 files** | **62** | **2** | 26 numbers, 24 removed: 24 distinct shas and 1 ADR | Per-file counts at base equal the claim's (census `5884031174` at `f11b5f20a2`) in all 22 files. A second instrument agrees site for site: every `#N` in the 22 files, classified by the TypeScript parser, and each of 201 distinct numbers probed by REST `issues/N` without redirects. It found 484 sites, all in comments and none in a string, 26 dead numbers and 62 dead sites. Its string-class positive control found 19 string sites in `api/rest-server.test.ts`. Head: 424 sites and 177 numbers, 175 answer 200 (the same 175), and 2 answer 404 (the two kept sites). Lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 at every checkpoint (4 at base, 3 at head); dead controls objectstack-ai#16714, objectstack-ai#16715 and objectstack-ai#16697 answered 404 at every checkpoint. ## Why each anchor decides its line Each sha resolves uniquely, is an ancestor of `origin/main` (and of the base), has one parent, and names the number it replaces in its own message or diff. Each was read for the rule its line states. - **objectstack-ai#14691 to `b3a63d32c`**: the retirement of the ten inert `RestServerConfig` keys under ADR-0049 enforce-or-remove. Its own `rest-server.zod.ts` diff wrote all nine `objectstack-ai#14691` lines: the tombstones, the dropped `CrudEndpointPatternSchema` and the `routes` block. - **objectstack-ai#14369 to `a3d5724c8`**: seeded the four `RestServerConfig` liveness ledgers "from the census filed with objectstack-ai#14369" (its changeset heading names the number). It records both facts the two lines state: every CRUD route is mounted from hard-coded method/path pairs, and `routes` is parsed, defaulted and normalized, then never read. - **objectstack-ai#12961 to `901355c3b`**: "Ruled 2026-08-29 (option A)". `translatePage` descends into declared `properties.children`; on an id collision a region-level component wins outright, and among nested matches document order decides. Its diff wrote the `objectstack-ai#12961` lines being replaced. - **objectstack-ai#13218 to `c45d8e6b4`**: exports `walkAddressedPageComponents` and consumes it from both sides; its changeset reads "(objectstack-ai#13218, ruled 2026-08-30)". - **objectstack-ai#13109 to `8b236c826`**: its changeset says the extractor OMITTED keys the resolver reads, and "This matches the second half". The line now says the second half went live and this commit repaired it. - **objectstack-ai#8460 to ADR-0029 D9.2a**: ruling C's first rung. The amendment "D9.2a — AMENDMENT (2026-08-13)" records the option-A ruling: an extender's scalar applies only while the fold's base still carries the packaged owner's value. It also records that the mechanism is deliberately the same comparison-based one the catalog uses one layer up. The heading marker `[objectstack-ai#8460]` becomes `[ADR-0029 D9.2a]`. - **objectstack-ai#10926 to `d173125fb`**: "Option A per the maintainer ruling on objectstack-ai#10926 (2026-08-22): drop the key", the `submitLabel` retirement all three lines describe. - **objectstack-ai#12493 to `aa5994e17`**: adds `record_write_denied` and `approval_recall_not_submitter` ahead of their emitters, with no placeholders. Its diff wrote the four docblock lines. The catalog's rendered strings are untouched, per hypothesis 5. - **objectstack-ai#6604 to `d127ff002`**: "Per the maintainer's 2026-08-08 Option-B ruling the kernel side takes the domain-specific name", matching `KernelServiceMapSchema`. - **objectstack-ai#17081 to `24d622b94`**: lands objectstack-ai#17556 as "Suggestion 1 of objectstack-ai#17081". The line keeps objectstack-ai#17556 and names the parent card in words. - **objectstack-ai#11333 to `e58ea8b38`**: declares `grantedPermissions`, described by the commit itself as "the artifact-contract half of objectstack-ai#11333 option A / the objectstack-ai#13457 batch ruling". The line keeps objectstack-ai#14865 and objectstack-ai#13457 and states the ruled option in words. - **objectstack-ai#12245 to `c41b42e8d`**: rewrote this docblock from "the per-surface census (its os-dev-report comment, measured on origin/main @ e2debee)" and carries the 1218-values measurement. The report comment lived on the deleted card, so the commit is now the record, and the line says so. - **objectstack-ai#12144 to `3a04b0125`**: wrote the storage-owned length-ceiling note and its storage-column pin; its changeset heads "(objectstack-ai#12144)". - **objectstack-ai#12194 / objectstack-ai#12176 to `311433f6b`** (:140-141): stage 1, the item-name grammar declared and refused at the publish door; its message reads "Stage 1 of objectstack-ai#12176". **objectstack-ai#12176 to `7986d973f`** (:189): "Retire compound-name metadata addressing", stage 3 of the maintainer-ruled retirement. - **objectstack-ai#10485 to `35ad101bc`**: retires the `themes` carrier, `ThemeSchema` and the `PLURAL_TO_SINGULAR` fold ("Ruled B"). Its own diff wrote all four lines. - **objectstack-ai#11350 to `ece4dad31`**: "Invariant recorded (maintainer ruling 2026-08-23)". It also points the premise-delta note at objectstack-ai#11709, which is what `index.ts:148` now says. This is stage 1's wording for `kernel/index.ts:53`. - **objectstack-ai#14419 to `c5a7448d5`**: `create_record` surfaces the engine's `DUPLICATE_RECORD` code and the engine binds it on `$error`, the founding case the line names. Its message names objectstack-ai#14419 as the card it lands. - **objectstack-ai#13681 to `18d816a50`**: declares the run-level `FlowRunSummary.failed`, the spec half of the contained-failure contract. - **objectstack-ai#16659 to `ecdfc9411`**: declares the start-node `config.organization` key and "the one refusal sentence every enforcement point says". Its sub-commits pin "the three objectstack-ai#16659 consequences", so it is also the commit that closed the defect the second line describes. - **objectstack-ai#8714 to `42b05af89`**: "ONE closed contributor-state enumeration", maintainer-ruled 2026-08-18. - **objectstack-ai#6640 to `2ab1257c9`**: `preserveAudit` is UPDATE-only (stage 1's anchor for the same rule). - **objectstack-ai#6345 to `e2798fab7`**: its own `turso.zod.ts` diff wrote both lines ("The maintainer's objectstack-ai#6345 ruling closes it…", "(objectstack-ai#6345 fork 2)"). The wording is stage 3's in `config-registry.zod.ts`. - **objectstack-ai#13031 to `b799ac553`**: adds `mapViewPayloads` to `walk.ts`, the centralized walk the heading describes. Its message names objectstack-ai#13031 as the card it lands. - **objectstack-ai#8715, kept**: `2c86fe3ea` ("Maintainer ruling 2026-08-15 (disposition B: delete)"; its diff wrote :230) now sits on :231. ## Mechanical proof - **Token guard** (my `tokcmp.mjs`: TypeScript 6.0.3 leaf tokens, JSDoc kinds excluded, controls mutate the head text in memory only). Base `c876a7426d` against the head, 21 files, 37,539 base tokens: - Real run: 0 files with a token change (exit 0). - Comment-insertion control (`ai/index.ts`): 0 (exit 0). - Code-insertion positive control (`system/i18n-resolver.ts`): DIFFER at token 14452 (exit 1). - String positive control (a real `StringLiteral` in `system/operation-message.ts`, found by the parser): DIFFER at token 5 (exit 1). - The first string-control attempt matched a quoted fragment inside a comment and did not fire. It was a vacuous control, not a measurement, and the parser-located control above replaced it. - **Line balance**: every file is +N/−N (66/66 across 21 files); every line count is equal at base and head. - **Tracker numbers**: added-not-removed is empty in every file, and no `PR #N` is on an added line. Net-removed: 60 sites, 24 numbers. - **Shas**: 24 distinct on added lines, 0 on removed lines. - `rev-parse --disambiguate` answers 1 object for each. - `merge-base --is-ancestor` exits 0 against `origin/main` `e666636fd9` and against the base. - Each is single-parent; the repository is not shallow; the control leg `e9584681a4` exits 0. - Each commit's own message (17 of 24) or diff (all 24) names the number it replaces. - **Literal readers**: every string literal in the repository that carries one of the 26 numbers was matched against the 22 files' text. Three hits read these files: - `data/api-derivation.test.ts:236` splits on `[objectstack-ai#6259]`; - `packages/runtime/src/api-exposure.test.ts:152` splits on `objectstack-ai#6259`; - `identity/api-key-retirement.test.ts:118` asserts `are NOT declared here (objectstack-ai#8715`. - Those lines are the two kept sites. No test or script matches any rewritten line by pattern. ## Tests and gates (at head `9d63cb6548`) - `pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*` under `os-verify-lock`: Tasks 71 successful, 71 total, VERDICT command-exit 0. - `pnpm --filter @objectstack/spec check:generated`: exit 1, `check:docs` stale (1 of 15). `check:generated --fix` then regenerated exactly that artifact: 2 pages, 3 lines, each its docblock line verbatim. - `content/docs/references/automation/schedule-organization.mdx` - `content/docs/references/data/driver-turso.mdx` - The re-check in the gate run below is exit 0. - `vitest run --maxWorkers=2` over the touched areas (`src/api/rest-server.test.ts`, `src/api/rest-api-config-dead-keys-retirement.test.ts`, `src/system`, `src/shared`, `src/automation`, `src/ai`, `src/identity`, `src/security`, `src/data/driver`, `src/conversions`, `src/meta-spelling`): Test Files 159 passed (159), Tests 5163 passed (5163). - The 23 spec suites outside those areas that read a touched file's source text or name it: Test Files 23 passed (23), Tests 540 passed (540). - `scripts/{tombstoned-row-status,strictness-ledger,file-description,skill-map-guards,root-index,export-origins,category-title,split-entries,dist-freshness,dist-freshness-adoption,root-entry-type-nameability.pin}` tests; - `src/type-alias-convention.pin`, `src/contracts/{automation-result-status.pin,automation-service,scoped-context}`, `src/api/{export-job-family-retirement,api-entry-graph.pin}`, `src/eager-entry-import`, `src/integration/connector-author-shape`, `src/ui/{interaction-config-retirement,notification,strictness-batch14}`, `src/migrations/migrations`. - `scripts/build-schemas-check-mode.test.ts` is left to CI: it imports rather than reads, and rebuilds schemas in a temp tree. - `pnpm --filter @objectstack/spec typecheck`: exit 0; `check:test-typecheck` OK (53 files / 251 errors / 138 pinned signatures held). - Lint, a proven narrowing: `eslint --no-inline-config --format json` over the 21 touched `.ts` files gives 21 files, 0 errors, 0 warnings. - `isPathIgnored` is false for all 21, 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`: 108 families derived and run, every one exit 0. `--ran` reads "108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN". Among them: - `pnpm check:issue-citations` plus the live diff-scoped `node scripts/check-issue-citations.mjs` judged 7 citations across 21 files, 7 resolve: the live numbers kept beside the anchors (objectstack-ai#9249, objectstack-ai#14954, objectstack-ai#17556, objectstack-ai#14865, objectstack-ai#13457, and objectstack-ai#11709 twice). - `pnpm check:doc-authoring`: 16,765 customer-facing strings across 1,175 spec sources clean; the sibling baseline holds. - Changeset: `patch` for `@objectstack/spec`. 13 of the 21 touched sources are `src/**/*.zod.ts`, which `files[]` ships verbatim, and the rewritten docblocks reach `dist`. For example, `ruled collision arbitration (commit 901355c)` is in 1 `.d.ts`, and the unchanged neighbouring sentence in the same exported docblock (the positive control) is in 1 `.d.ts`. - Merge probe: a no-driver `merge-tree` of the head onto `origin/main` `7a1faf1a5d`, from a bare shared clone, exits 0. The 3 commits `main` gained since the base touch none of this diff's files. No merge was made, as stages 1–4 did. - No ablation or reverse verification: the change is comment-only, so there is no behaviour to invert. ## Hypotheses (measured first) 1. **Holds.** The population is exactly the claim's 22 files: 62 dead sites at the tip, equal per file to the census at `f11b5f20a2`. 2. **Holds, with nothing to respell.** No sibling-qualified pair occurs among the 62 sites; no `pre-#N` spelling is dead here. 3. **Holds.** Re-read at 2026-09-29T06:23Z, after the last push and before this PR was opened: all 14 open PRs' full file lists (1,116 files in the version PR alone), and the newest `Claim:` on all 15 `pm:dispatched` cards. None names any of this PR's 24 paths. The five exclusions stay excluded. 4. **Holds.** The two projected pages were regenerated by the generator, never by hand. No other page under `content/docs/references/` carries any of the 26 numbers. 5. **Holds.** No `#N` in the 22 files is inside a string; the two literal-read sites stay as tokens. In `system/i18n-resolver.ts` and `system/operation-message.ts` only docblock and line-comment lines moved. ## Deviations - The file surface is 21 of the 22 named files. `data/api-derivation.ts` is untouched, because its one dead site is read by literal and its commit already stands on the next line (stage 3's disposition). - Six changed lines held no dead number. Each is the other half of a rewritten sentence: `rest-server.zod.ts:756`, `identifiers.zod.ts:19`, `index.ts:133`, `environment-artifact.zod.ts:137`, `schedule-organization.zod.ts:82`, and `identity.zod.ts:231` (the commit placed beside the kept :230). - Commit trailers follow AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`); the pre-push trailer check passed on every push. ## Acceptance notes **What stays for later stages.** At the tip `7a1faf1a5d` with this PR applied, `packages/spec/src` holds **260** dead sites (34 numbers). This is the gate's census on this head, with the four spec sources `main` changed since the base re-extracted and re-probed at the tip. By area: - `migrations/` **233**: objectstack-ai#20233 edits the same entry files; the author-shown fields are its form D. - `conversions/registry.ts` **12**: held by PRs objectstack-ai#20570 and objectstack-ai#20458. - `stack.zod.ts` **9**: free now; PR objectstack-ai#20579 landed as `7a1faf1a5d` at 06:02Z, after the claim, so it stayed excluded here. - `data/analytics.zod.ts` **3**: PR objectstack-ai#20458. - `integration/connector.zod.ts` **1**: objectstack-ai#20287, PR objectstack-ai#20587. - `data/api-derivation.ts:163` (objectstack-ai#6259) and `identity/identity.zod.ts:230` (objectstack-ai#8715), **1** each: kept because `api-derivation.test.ts:236`, `packages/runtime/src/api-exposure.test.ts:152` and `api-key-retirement.test.ts:118` read them by literal. Removing them is a test-string change, form D, outside this card's comment-only scope. **Carried from earlier stages, outside the gate's census** (which blanks strings and defers test files): the dead-number test-title strings, the two `why` strings, the `PROVENANCE_WAIVERS` reason, the two `AGGREGATION_CASES` notes, and the `liveness/**` notes. **Outside `packages/spec/src`** (objectstack-ai#20556's lane): `packages/spec/scripts/check-entry-nameability.ts` cites objectstack-ai#11350 in its header (:14) and PRINTS "recorded on objectstack-ai#11350" in its failure text (:727); `packages/spec/scripts/root-entry-type-nameability.pin.test.ts` cites it too. Commit `ece4dad31` is the anchor, already verified here. **Rung.** Five of the anchored retirements also have ADR-0087 D3/D2 entries: `identity-api-key-schema-retired`, `metadata-item-name-grammar-enforced`, `rest-server-config-dead-keys-retired`, `stack-themes-carrier-retired` and `translation-component-submit-label-retired`. This PR takes the commit rung, as stages 1–5 did. The D3 id is the more durable in-repo record if the ruling's first rung is later read to include those entries. **Wording, each true of its commit.** - `dev-login.zod.ts:10` names objectstack-ai#17081 in words ("suggestion 1 of its parent card"). - `environment-artifact.zod.ts:136-137` states objectstack-ai#11333's option A as "the option the objectstack-ai#13457 batch ruling chose". - `identifiers.zod.ts:18` drops "its `os-dev-report` comment is the measurement of record", since that comment went with the card, and names the commit as the record. **Observation, not filed** (a pre-existing live citation, not a tracker number; carrier: none): `environment-artifact.zod.ts:137` and `packages/runtime/src/security/artifact-granted-permissions.ts:6` cite "ADR-0025 §3.5 step 2" for the granted set. At the tip, §3.5's numbered step 2 is "Compatibility" and "Permission consent" is step 3. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… to the commits that decided them (objectstack-ai#20612) Part of objectstack-ai#20597 Clause-②: no The `packages/lint` stage of the dead-citation sweep: the `domain:spec` lane's only package (census `5884031174` on objectstack-ai#20556, claim `5885046469`). Every comment or docblock line in 22 of the 23 claimed `packages/lint/src/` files 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 describes, and says in its own words what was decided. Comments only: 81 lines out, 81 in, across 22 files. No code token, string literal, rule message, hint or rule id moves. `authoring-rules.ts` (5 sites) is excluded and left at its base blob: PR objectstack-ai#20593 (objectstack-ai#20553) edits it and was still open at the last read (2026-09-29T07:34Z). So this PR says `Part of`: those 5 sites stay for a follow-up once that PR lands, with their anchors already verified (see Acceptance notes). The census is the gate's own `node scripts/check-issue-citations.mjs --census --json`, filtered to `packages/lint/`. Before: base `7a1faf1a5d`, 2026-09-29T06:42:03Z to 06:45:24Z, board enumerated (185 pages, frontier objectstack-ai#20606). After: head `0c7b847f18`, 07:28:22Z to 07:31:52Z (185 pages, frontier objectstack-ai#20611). ## Measurement | file (under `packages/lint/src/`) | dead before | after | numbers, then anchor | |---|---:|---:|---| | `lint-flow-patterns.ts` | 9 | 0 | objectstack-ai#13681 ×9 to `8ed9c54b4` (objectstack-ai#14394 stays, 200) | | `validate-hook-body-writes.ts` | 9 | 0 | objectstack-ai#8663 ×6 to `192213f66`; objectstack-ai#13657 ×3 to `b003cf2e8` | | `runtime-gate.ts` | 8 | 0 | objectstack-ai#10064 ×5 to `def0d3e63`; objectstack-ai#19370 ×2 to `a227afa41` (objectstack-ai#19143 stays); objectstack-ai#9798 to `c7655d472` (objectstack-ai#9261 stays) | | `validate-searchable-fields.ts` | 7 | 0 | objectstack-ai#8404 ×4 to `b849e6911`, the `pre-objectstack-ai#8404` control at `:312` included; objectstack-ai#10001 ×3 to `f1b5ad39a` | | `authoring-rules.ts` | 5 | **5** | excluded: PR objectstack-ai#20593 holds the file | | `validate-expressions.ts` | 5 | 0 | objectstack-ai#6290 ×5 to `e9b526597` (objectstack-ai#6584, that commit's own PR, stays) | | `validate-react-page-props.ts` | 5 | 0 | objectstack-ai#11284 ×4 to `5383fa670`; objectstack-ai#8404 to `b849e6911` | | `validate-sortable-fields.ts` | 5 | 0 | objectstack-ai#10001 ×4 to `f1b5ad39a`; objectstack-ai#8404 to `b849e6911` | | `validate-flow-node-writes.ts` | 4 | 0 | objectstack-ai#8663 ×4 to `192213f66` | | `validate-page-field-bindings.ts` | 4 | 0 | objectstack-ai#6629 ×2 to `cd584d559` (plus the slash-joined `:208`, see Deviations); objectstack-ai#8664 ×2 to `8798cd2a6` | | `validate-translation-references.ts` | 4 | 0 | objectstack-ai#14700 ×3 to `de3c52beb` (objectstack-ai#14253 stays); objectstack-ai#6124 to `b3c1f3cd5` | | `lint-liveness-properties.ts` | 3 | 0 | objectstack-ai#10262 ×3 to `2aca1bc4c` | | `validate-action-body-writes.ts` | 3 | 0 | objectstack-ai#8663 ×3 to `192213f66` | | `validate-security-posture.ts` | 3 | 0 | objectstack-ai#19370 ×3 to `a227afa41` (objectstack-ai#8310 stays) | | `flow-template-grammar.ts` | 2 | 0 | objectstack-ai#11060 ×2 to `815585513` | | `data-model-rules.ts` | 1 | 0 | objectstack-ai#10064 to `def0d3e63` | | `reference-integrity-suite.ts` | 1 | 0 | objectstack-ai#13653 to `36d287803` | | `validate-component-types.ts` | 1 | 0 | objectstack-ai#12950 to `225e7690f` (objectstack-ai#12183 stays) | | `validate-empty-combinators.ts` | 1 | 0 | objectstack-ai#6528 to `3510e4a25` (objectstack-ai#5659 stays) | | `validate-list-view-field-refs.ts` | 1 | 0 | objectstack-ai#10001 to `f1b5ad39a` | | `validate-readonly-action-writes.ts` | 1 | 0 | objectstack-ai#13653 to `36d287803` | | `validate-readonly-flow-writes.ts` | 1 | 0 | objectstack-ai#13653 to `36d287803` | | `validate-readonly-hook-writes.ts` | 1 | 0 | objectstack-ai#13653 to `36d287803` | | **23 files** | **84** | **5** | 21 numbers; 19 removed from the 22 edited files, to 19 distinct shas | Per-file counts at base equal the claim's (census at `f11b5f20a2`) in all 23 files. A second instrument agrees site for site: every `#N` in the 23 files, classified by the TypeScript parser as comment, string or code, and each of 360 distinct numbers probed by REST `issues/N` without following redirects. At base it found 1,323 sites (1,259 comment, 64 string, 0 code); 339 numbers answer 200 and 21 answer 404, the census's 21. Its dead comment sites are the census's 84 plus one slash-joined `objectstack-ai#5775/objectstack-ai#6629` the grammar does not read, and it found one dead **string**: `validate-react-page-props.ts:1198` (see Acceptance notes). At head: 1,243 sites and 344 numbers, the same 339 answer 200, and 5 answer 404, all in `authoring-rules.ts` comments or that one string. Lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200, and dead controls objectstack-ai#16714, objectstack-ai#16715 and objectstack-ai#16697 answered 404, at every checkpoint (5 at base, 5 at head). ## Why each anchor decides its line Each sha resolves uniquely, is an ancestor of `origin/main` and of the base, has one parent, and names the number it replaces in its own message (15 of 19) or its own diff (17 of 19); every one does at least one. Each was read for the rule its line states. - **objectstack-ai#13681 to `8ed9c54b4`**: lands the per-iteration containment rule PAIR (`flow-loop-body-uncontained`, `flow-try-catch-without-catch`) and its measured minimal `catch`; its diff wrote all nine lines, and its changeset records the measurements the lines cite. objectstack-ai#14394 (the rule card, 200) stays beside it. - **objectstack-ai#8663 to `192213f66`**: "three write rules ask anchor provenance before exempting a system column"; its body names objectstack-ai#8663 and its diff wrote the `[objectstack-ai#8663]` lines in all three rule files. - **objectstack-ai#13657 to `b003cf2e8`**: the post-hook half of the declared-field door, one envelope on every driver; its diff wrote the three lines. - **objectstack-ai#10064 to `def0d3e63`**: name-keys collection-resident publish-gate finding paths; its body reads "maintainer ruling 2026-08-20: Option A" for objectstack-ai#10064. - **objectstack-ai#19370 to `a227afa41`**: `security-role-word` crosses to the runtime publish gate, whole, per ruling batch objectstack-ai#203 item 3 letter B; it maps `position` / `app` and writes the past-tense crossing lines. - **objectstack-ai#9798 to `c7655d472`**: the change that carried objectstack-ai#9798 to done (its body names it), restoring the sys_comment unscoped multi-delete refusal that could not fire through the wired engine, the declared-but-unenforced fail-open the line lists beside objectstack-ai#9261 and ADR-0110 D3. - **objectstack-ai#8404 to `b849e6911`**: warns when `searchableFields` declares an unprovisioned injected anchor, adding the optional provenance index the lines describe; the SORT twin line names it as the SEARCH wiring. - **objectstack-ai#10001 to `f1b5ad39a`**: a standalone ViewItem record's nested `config.sort` / `config.searchableFields` reach the runtime publish gate, the RECORD rung. - **objectstack-ai#6290 to `e9b526597`**: `current_user` joins `SCOPE_ROOTS`, the field-level rejection becomes its own rule, and option-level `visibleWhen` is walked for the first time. `:770` quotes `SCOPE_ROOTS`' docblock in `packages/formula`; the quote now stops at "the last one this list was missing", verbatim, with the commit outside the quotation. - **objectstack-ai#11284 to `5383fa670`**: the ListView react-tier vocabulary converges on the metadata-tier spelling, deprecate-first; its changeset reads "(objectstack-ai#11284, maintainer ruling 2026-08-23)". - **objectstack-ai#6629 to `cd584d559`**: drops the retired `displayField` / `searchFields` from the record_picker entry and adds `component-field-specs-liveness.test.ts`. - **objectstack-ai#8664 to `8798cd2a6`**: names what actually guards the `unprovisionedAnchors` wiring; its diff wrote both lines. - **objectstack-ai#14700 to `de3c52beb`**: descends into `conditional` `then` / `otherwise` when building the `_validations` universe; its diff wrote all three lines. - **objectstack-ai#6124 to `b3c1f3cd5`**: the squash commit of objectstack-ai#6124 itself, leg 1 of the `_views` key ruling (the CLI i18n extractor keyed by the runtime view identity). - **objectstack-ai#10262 to `2aca1bc4c`**: adds the package-internal test seam for `getNested`'s array fan-out; its diff wrote all three lines. - **objectstack-ai#11060 to `815585513`**: its body records "Maintainer ruling on objectstack-ai#11060 (2026-08-23): option A", the CEL-mirrored six with no second semantics, which the lines quote. - **objectstack-ai#13653 to `36d287803`**: gates a hook body's `ctx.api` write to a readonly field, and shares `buildReadonlyIndex` from the flow rule, the export `:118` describes. - **objectstack-ai#12950 to `225e7690f`**: created `validate-component-types.ts`, the author-time rejection for unknown component types in spec-reserved namespaces (stage 5's anchor for the same number). - **objectstack-ai#6528 to `3510e4a25`**: the squash commit of objectstack-ai#6528 itself, one implementation of the filter identity reduction (maintainer ruling 2026-08-06, option 1). The line read `PR objectstack-ai#6528`; it now names the commit. Rung: no ADR, `docs/NORTH-STAR.md` or `scripts/adr-anchors/` file records any of these 19 decisions (the one lint anchor file, `data-model-rules.ts`, pins ADR-0120, which none of these lines cites), so the commit rung is the right one, as in objectstack-ai#20234's stages. ## Mechanical proof - **Token guard** (scratch `tokcmp.mjs`: TypeScript 6.0.3 leaf tokens, JSDoc kinds excluded, controls mutate the head text in memory only). The merge base `c96beb2707` against the head, 22 files, 54,508 base tokens (the 22 files are byte-identical at `7a1faf1a5d` and at the merge base): - Real run: 0 files with a token change (exit 0). - Comment-insertion control (`runtime-gate.ts`): 0 (exit 0). - Code-insertion positive control (`validate-hook-body-writes.ts`): DIFFER at token 216 (exit 1). - String positive control (a parser-located `StringLiteral` in `validate-react-page-props.ts`): DIFFER at token 5 (exit 1). - **Line balance**: every file is +N/−N (81/81 across 22 files), every changed line is comment-shaped, and every line count is equal at base and head. - **Tracker numbers**: added-not-removed is empty in every file, and no `PR #N` stands on an added line. Net-removed: 80 sites (the census's 79 in these files plus the slash-joined one), 19 numbers. The numbers kept on added lines all answer 200: objectstack-ai#5659, objectstack-ai#5775, objectstack-ai#8310, objectstack-ai#8340, objectstack-ai#9261, objectstack-ai#9313, objectstack-ai#12183, objectstack-ai#13390, objectstack-ai#14253, objectstack-ai#14394, objectstack-ai#19143, and objectstack-ai#6584 (a pull request, the anchor commit's own PR). - **Shas**: 19 distinct on added lines, 0 on removed lines. `rev-parse --disambiguate` answers 1 object for each; `merge-base --is-ancestor` exits 0 against `origin/main` and against the base; each is single-parent; the repository is not shallow; the control leg `e9584681a4` exits 0. - **Literal readers**: every string or regex literal in the repository that carries one of the 21 numbers (85 literals) was matched against the 23 files' text: no reader of any rewritten line. The lint tests that read these sources as text stay green below. For example, `validate-expressions.test.ts` strips comments before it matches, and `validate-security-posture.runtime-surface.test.ts` collects the `stack.X` reads inside `validateSecurityRoleWord`, which no added line carries. ## Tests and gates (at head `0c7b847f18`) - `pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*` under `os-verify-lock`: Tasks 71 successful, 71 total, VERDICT command-exit 0. - `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2` under the lock: Test Files 115 passed (115), Tests 5363 passed (5363); then `pnpm --filter @objectstack/lint typecheck`: exit 0, `check:test-typecheck` OK (2 files / 6 errors / 2 pinned signatures held). VERDICT command-exit 0. The same two runs passed with the same counts on the pre-merge head `2ce32f6b48`. - Lint, a proven narrowing: `eslint --no-inline-config --format json` over the 22 touched `.ts` files gives 22 files, 0 errors, 0 warnings. `isPathIgnored` is false for all 22, 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`: 54 families derived, all run, every one exit 0. `--ran` reads "54 derived, 54 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero (every line carries its exit code). Among them: - `node scripts/check-issue-citations.mjs` (the live diff-scoped run) judged 18 citations across 22 files: 17 answer as issues and 1 answers as a pull request, the kept objectstack-ai#6584; `pnpm check:issue-citations` (self-test, 114 cases in 8 batteries) passes. - `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,239 tracked text files; a control-byte scan of the 23 changed files finds none. - No generated page carries a lint docblock: no page under `content/docs/references/` names any of the 19 numbers, and no generator reads `packages/lint/src`, so nothing was regenerated. - Changeset: `patch` for `@objectstack/lint`. `files[]` ships `dist`, and the rewritten comments reach it: 12 of the 19 shas appear in the built `dist` (for example `8ed9c54b4` and `def0d3e63` in `index.d.ts`, `b849e6911` in `index.js` and `index.d.ts`); the positive control, the unchanged sentence "the near-miss shape: a `try_catch` that declares no `catch`" of an exported docblock, is in `index.d.ts`. Hence patch, not `skip-changeset`. - Merge probe: a no-driver `merge-tree` of the head onto `origin/main` `0f6dcac5e9`, from a bare shared clone with no `merge.*` config, exits 0. The two commits `main` gained after the merge touch none of the 23 files. - No ablation or reverse verification: the change is comment-only, so there is no behaviour to invert. ## Hypotheses (measured first) 1. **Holds.** 84 dead sites, 21 numbers, 23 files at the tip `7a1faf1a5d`, equal per file to the claim. 2. **Holds.** Only comment and docblock lines moved. The one dead number inside a string (`validate-react-page-props.ts:1198`, a finding `message`) stays byte-identical; no test or script reads a rewritten line by literal. 3. **Holds, and conditions the card.** PR objectstack-ai#20593 was still open at 07:34Z, so `authoring-rules.ts` stays at its base blob. At that read, the 9 open PRs' full file lists and the newest `Claim:` on all 11 `pm:dispatched` cards name none of the other 23 paths. 4. **Holds.** `validate-searchable-fields.ts:312` `pre-objectstack-ai#8404` is listed dead before and is gone after. 5. **Holds, with nothing to regenerate.** No lint docblock projects into a generated page; no release page is touched. ## Deviations - Two changed lines beyond the census's sites. `validate-page-field-bindings.ts:208` carried `objectstack-ai#5775/objectstack-ai#6629`, a slash-joined dead number the citation grammar does not read; it now reads "the same objectstack-ai#5775 residue class (commit cd584d5)", stage 5's precedent for the slash-joined `objectstack-ai#9972`. `runtime-gate.ts:780` is the other half of the rewritten `:779` sentence and held no number. - `origin/main` was merged once (`0c7b847f18`, merging `c96beb2707`): the first derivation read STALE TREE because `scripts/sdui-manifest.record.json` changed on `main`. The merge was clean, no driver-routed path and no lockfile change, and it touches none of the 23 files; the build, tests and gates above ran after it. - Commit trailers follow AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`); the pre-push trailer check passed on every push. ## Acceptance notes **What stays for this card** (why it says `Part of`): `authoring-rules.ts`, 5 sites, excluded while PR objectstack-ai#20593 holds it. Anchors, verified the same way, for whoever takes it after that PR lands: `:198` objectstack-ai#10064 to `def0d3e63`; `:1117` objectstack-ai#16659 to `ecdfc9411` (it added `flow-schedule-organization-missing` to the registry); `:1687` "(PR objectstack-ai#8546)" to `ba5e957ef`, that PR's own squash commit; `:1713` and `:1733` objectstack-ai#19370 to `a227afa41`. **Form D, not touched:** `validate-react-page-props.ts:1198` is the `react-prop-deprecated` finding `message`, which ends "...is removed after the deprecation window (objectstack-ai#11284)." An author sees it, so it takes ruling D (no number), which is a string change and outside this comment-only scope. `scripts/doc-authoring-prose-id.baseline.json` pins it (`objectstack-ai#11284: 1` for this file). It needs a form-D carrier. **Outside the census's surface**, which blanks strings and defers test files (noted, not swept here): - `packages/lint/src/*.test.ts` titles and comments still cite several of these dead numbers (objectstack-ai#6290, objectstack-ai#8404, objectstack-ai#8663, objectstack-ai#10001, objectstack-ai#10064, objectstack-ai#10262, objectstack-ai#13681, objectstack-ai#19370 and others). - Hand-written docs pages cite them too: `content/docs/automation/hook-bodies.mdx` (objectstack-ai#8663, objectstack-ai#13657), `content/docs/automation/flows.mdx` (objectstack-ai#11060) and `content/docs/deployment/validating-metadata.mdx` (objectstack-ai#19370). - `packages/formula/src/cel-engine.ts` cites objectstack-ai#6290 four times, including the docblock `validate-expressions.ts:770` quotes. It is in the census, in another lane's package. **Wording, each true of its commit.** - `validate-expressions.ts:571` keeps objectstack-ai#6584 beside `e9b526597`: objectstack-ai#6584 is that commit's own PR, so "arrived in commit e9b5265, and needed that same change (objectstack-ai#6584) to be noticed" states the one act both old numbers named. - `runtime-gate.ts:362` names the objectstack-ai#9798 shape in words, as the fail-open that commit c7655d4 ended, next to objectstack-ai#9261 and ADR-0110 D3. - `lint-flow-patterns.ts:343` reads "The measured case commit 8ed9c54 records, exactly: one row with a null owner killed the sweep"; that commit wrote the sentence, and `c02f70e13` later fixed the same shape in the showcase flow. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…data/analytics.zod.ts to the commits that decided them (stage 7) (objectstack-ai#20616) Part of objectstack-ai#20234 Clause-②: no Stage 7 of the staged sweep: `packages/spec/src/stack.zod.ts` and `packages/spec/src/data/analytics.zod.ts`, both freed by landings (PR objectstack-ai#20579 and PR objectstack-ai#20458). Its claim is `5885635758`. Every comment or docblock line in those two files that cited a tracker number answering 404 now cites the commit on `main` that decided its rule, in ruling C+D's form C, and says in its own words what was decided. Comments only: 12 lines out, 12 in, across 2 files. No code token, string literal or `describe()` text moves. No dead site stays: none of the 12 is read by literal. The census is the gate's own `node scripts/check-issue-citations.mjs --census --json`, filtered to the two paths. Before: base `0f6dcac5e9`, board enumerated (185 pages, frontier objectstack-ai#20611). After: head `cc0580d404`, board enumerated (185 pages, frontier objectstack-ai#20615). ## Measurement | file (under `packages/spec/src/`) | dead before | after | numbers, then anchor | |---|---:|---:|---| | `stack.zod.ts` | 9 | 0 | objectstack-ai#10485 ×2 (`:415`, `:1023`) to `35ad101bc`; objectstack-ai#6238 (`:633`) to `c8d6f6e08`; objectstack-ai#14192 (`:1233`) to `4d0d9445a`; objectstack-ai#14686 ×2 (`:3037`, `:3194`) to `279431e7a`; objectstack-ai#14662 ×3 (`:4510`, `:5070`, `:5293`) to `35dffeace` | | `data/analytics.zod.ts` | 3 | 0 | objectstack-ai#10194 ×3 (`:404`, `:407`, `:485`) to `2306a765c` | | **2 files** | **12** | **0** | 6 numbers removed, 6 distinct shas | Per-file counts at base equal the claim's (9 and 3, from stage 6's census). A second instrument agrees site for site: every `#N` in the two files, classified by the TypeScript parser, and each of the 84 distinct numbers of 100 or more probed by REST `issues/N` without following redirects (the other 3 are the ordinals `Prime Directive objectstack-ai#12`, `batch objectstack-ai#23`, `batch objectstack-ai#57`). - Base: 244 sites, all in comments (0 strings, 0 code). 78 numbers answer 200 and 6 answer 404: the same 6 numbers and the same 12 sites as the gate. - Its string-class positive control found 11 string sites in `kernel/manifest-unknown-keys.test.ts` and `packages/cli/src/utils/lower-callables.test.ts`. - Head: 232 sites, 78 numbers, all 78 answer 200 (the same 78), none answers 404. - Lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 at every checkpoint (3 at base, 3 at head); dead controls objectstack-ai#16714, objectstack-ai#16715 and objectstack-ai#16697 answered 404 at every checkpoint. ## Why each anchor decides its line Each sha resolves uniquely, is an ancestor of `origin/main` (and of the base), and has one parent. No file under `docs/adr/**`, `docs/NORTH-STAR.md` or `scripts/adr-anchors/` names any of the six numbers or records these rules, so each takes the commit rung, as stages 1–6 did. - **objectstack-ai#10485 to `35ad101bc`** (`:415`, `:1023`): retires the `themes` carrier key and `ThemeSchema` under ADR-0049. Its message records the ruling, "Ruled B (退役授权面, 2026-08-21)", and its own `stack.zod.ts` diff wrote both lines. `:415` keeps ADR-0049 and the ruling in its words; the D3 entry `stack-themes-carrier-retired` it names on `:423` is unchanged. This is the anchor stages 1, 5 and 6 used for the same retirement. - **objectstack-ai#6238 to `c8d6f6e08`** (`:633`): widens the array member of `functions` so its `handler` also takes the lowered string ref, which is the fix for `objectstack build` refusing its own array output. Its message names objectstack-ai#6238, and its own diff wrote the line. objectstack-ai#4343 and objectstack-ai#4976 on the same line stay (both 200). - **objectstack-ai#14192 to `4d0d9445a`** (`:1233`): turns `ManifestSchema` and its nested blocks into `strictObject` and flips the assembled-body strip pin to a refusal pin; each of its sub-commits names objectstack-ai#14192. The line itself was written later by `c78c9180de`, whose own message says "objectstack-ai#14192 closed ManifestSchema with strictObject", so the commit that closed it is the anchor. - **objectstack-ai#14686 to `279431e7a`** (`:3037`, `:3194`): "defineStack refuses two actions that resolve to one scope-qualified runtime key". Its subject names objectstack-ai#14686, and its diff adds `collectDuplicateActionKeyErrors` and the changeset for that refusal. Both lines were written later by `773a99960a` (PR objectstack-ai#15022), whose message describes the same "same-key rule, which runs before the merge". - **objectstack-ai#14662 to `35dffeace`** (`:4510`, `:5070`, `:5293`): "composeStacks refuses two stacks whose actions resolve to one scope-qualified runtime key". It checks the composed set with the rule `defineStack` applies within one stack, with no `actionConflict` option (maintainer ruling 2026-09-03). Its message does not name objectstack-ai#14662; its own `stack.zod.ts` diff wrote all three `(objectstack-ai#14662)` lines. - **objectstack-ai#10194 to `2306a765c`** (`analytics.zod.ts:404`, `:407`, `:485`): binds `analytics_cube` (and `theme`) in `UNREGISTERED_KIND_SCHEMAS`, so `PUT /meta/analytics_cube/:name` parses through `CubeSchema`, and gives `CubeSchema` the `...MetadataProtectionFields` spread. Its message names objectstack-ai#10194, and its own diff wrote all three lines. The `[objectstack-ai#10194]` markers become `[commit 2306a76]`, the spelling stages 1 and 5 already use in `kernel/metadata-type-schemas.ts`. ## Mechanical proof - **Token guard** (my `tokcmp.mjs`: TypeScript 6.0.3 leaf tokens, JSDoc kinds excluded, controls mutate the head text in memory only). Base `0f6dcac5e9` against the head, 2 files, 17,249 base tokens: - Real run: 0 files with a token change (exit 0). - Comment-insertion control (`data/analytics.zod.ts`): 0 (exit 0). - Code-insertion positive control (`stack.zod.ts`, a declaration appended): DIFFER at token 15388 (exit 1). - String positive control (the first `StringLiteral` the parser locates in each file): DIFFER at token 5 (exit 1), once per file. - `describe()` positive control (the first `.describe()` string argument the parser locates: `stack.zod.ts:133`, `analytics.zod.ts:244`): DIFFER at tokens 507 and 442 (exit 1). - **Line balance**: `stack.zod.ts` +9/−9, `data/analytics.zod.ts` +3/−3; line counts equal at base and head (5344 and 853). - **Tracker numbers**: added-not-removed is empty in both files, and no `PR #N` is on an added line. Net-removed: 12 sites, 6 numbers. The only numbers on added lines are objectstack-ai#4343 and objectstack-ai#4976, which stay on `:633`. - **Shas**: 6 distinct on added lines, 0 on removed lines. - `rev-parse --disambiguate` answers 1 object for each. - `merge-base --is-ancestor` exits 0 for each, against `origin/main` `7510663c87` and against the base; each is single-parent; the repository is not shallow. - **Literal readers**: all 26 string, template and regex literals in the repository that carry one of the six numbers (42 code files) were matched against the two files' base text: 0 occur there. Each removed line was also cut into 4-word windows (96) and searched across the tree: the 9 hits inside string literals are other files' own test titles sharing a phrase ("the ADR-0010 protection envelope", "an assembled body is"), and none reads either file. The source-text readers of the two files read code, not these comments: `compose-stacks-refusal-envelopes.test.ts` counts `throw new Error(`, and `check-stack-collection-maps.mjs` and `check-skill-top-level-keys.mjs` read the declared collections and keys. ## Tests and gates (at head `cc0580d404`) - `pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*` under `os-verify-lock`: Tasks 71 successful, 71 total, VERDICT command-exit 0. - `pnpm --filter @objectstack/spec check:generated` under the lock: all 15 generated artifacts up to date, `check:docs` over `content/docs/references/**` included; VERDICT command-exit 0. No reference page projects any of the 12 lines, so none is regenerated. - `vitest run --maxWorkers=2` under the lock over the two files' own suites (`src/stack*`, `src/compose-stacks*`, `src/define-stack*`, `src/assembled-package-body`, `src/data/analytics*`, `src/data/cube*`): Test Files 35 passed (35), Tests 976 passed (976). - The 37 spec suites that read source text across `src/`, or carry one of these numbers, under the lock: Test Files 37 passed (37), Tests 759 passed (759). - `scripts/{category-title,dist-freshness,dist-freshness-adoption,file-description,strictness-ledger,strictness-ledger-doc,root-index,skill-map-guards,export-origins,split-entries,root-entry-type-nameability.pin}`, `scripts/liveness/{evidence,tombstoned-row-status}`; - `src/type-alias-convention.pin`, `src/eager-entry-import`, `src/api/{api-entry-graph.pin,auth,export-job-family-retirement}`, `src/ai/tool-confirmation-prescription-tense.pin`, `src/data/{currency-mode-family-closure.pin,external-lookup-retirement}`, `src/identity/position-delegatable-enforcer.pin`, `src/integration/{connector-connection-timeout-retirement,connector-resilience-keys-retirement}`, `src/security/rls-tags-retirement`, `src/shared/{alias-integrity,retired-key-migrate-sentence}`, `src/system/{compliance-families-retirement,constants/platform-object-names,email-template-floor-locale-parity.pin,message-queue-retirement}`, `src/ui/{action-requires-confirmation-docblock.pin,i18n,interaction-config-retirement,strictness-batch14}`, `src/kernel/{manifest-unknown-keys,metadata-type-schemas}`. - Left to CI: `scripts/{build-schemas-check-mode,def-key-collisions,openapi-self-consistency}` (each rebuilds artifacts in a temp tree) and `scripts/{check-generated-ledger,check-generated-fix-rebuild.pin}` (read the ledger and `dist`). None reads comment text. - `pnpm --filter @objectstack/spec typecheck` under the lock: exit 0; `check:test-typecheck` OK (53 files / 251 errors / 138 pinned signatures held). - Lint, a proven narrowing: `eslint --no-inline-config --format json` over the 2 files gives 2 files, 0 errors, 0 warnings. - `isPathIgnored` is false for both, 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`: 79 families derived and run, every one exit 0. `--ran` reads "79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN". Among them: - `pnpm check:issue-citations` (self-test, 114 cases in 8 batteries) and the live diff-scoped `node scripts/check-issue-citations.mjs`: it judged the 2 citations on added lines, objectstack-ai#4343 and objectstack-ai#4976, and both are live issues. - `pnpm check:doc-authoring`: 16,804 customer-facing strings across 1,179 spec sources clean; the sibling baseline holds. - `pnpm check:stack-collection-maps`: 8 enumerations reconciled against 31 declared collections. - Changeset: `patch` for `@objectstack/spec`. Both files are `src/**/*.zod.ts`, which `files[]` ships verbatim, and the rewritten docblocks reach `dist`: "posture: commit 4d0d944 closed" and "[commit 2306a76] This docblock used to say" are each in 2 `.d.ts`, their old spellings in 0. Positive control: the unchanged neighbouring sentence "BY INHERITANCE — an undeclared key on one is REFUSED" is in the same 2 `.d.ts`. - Merge probe: a no-driver `merge-tree` of the head onto `origin/main` `7510663c87`, from a bare shared clone, exits 0. The 3 commits `main` gained since the base touch neither file nor the citation or derivation scripts, and a re-derivation prints the same 79 commands. No merge was made. - No ablation or reverse verification: the change is comment-only, so there is no behaviour to invert. ## Hypotheses (measured first) 1. **Holds.** 12 dead sites at the tip, 9 in `stack.zod.ts` and 3 in `data/analytics.zod.ts`, equal per file to stage 6's census. 2. **Holds.** Read at 2026-09-29T07:36Z and again at 08:16Z, after the last push and before this PR was opened: all open PRs' full file lists (9 PRs, 166 files at the second read) and the newest `Claim:` on all 11 `pm:dispatched` cards. None names either file, except this card's own claim. 3. **Holds, with nothing to keep.** All 12 sites are comments. No test string, exported string or `describe()` text carries one, and no test or script reads any of them by literal. 4. **Holds.** No generated reference page projects these lines; `check:docs` is green with no regeneration. ## Deviations - None to the file surface: the 12 claimed lines and one changeset, no generated page needed. - Commit trailers follow AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`); the pre-push trailer check passed on every push. ## Acceptance notes **What stays for later stages.** The gate's census at this PR's head (base `0f6dcac5e9` plus this PR) reads **248** dead sites (29 numbers) in `packages/spec/src`. The only `packages/spec/src` change `main` has made since the base (objectstack-ai#20610's migrations entry and registry) adds four live numbers and removes none, so 248 also stands at the tip `7510663c87` plus this PR: - `migrations/` **233**: objectstack-ai#20233 edits the same entry files (PR objectstack-ai#20607 holds `migrations/registry.ts`). - `conversions/registry.ts` **12**: PRs objectstack-ai#20570 and objectstack-ai#20587 hold it. - `integration/connector.zod.ts` **1**: PR objectstack-ai#20587 (objectstack-ai#20287). - `data/api-derivation.ts:163` (objectstack-ai#6259) and `identity/identity.zod.ts:230` (objectstack-ai#8715), **1** each: kept because tests read them by literal, so removing them is form D. **Outside the gate's census: test files.** The gate defers `*.test.ts`. The same six dead numbers still stand at 15 comment sites and 10 test-title strings in `packages/spec/src` test files: - `data/analytics-strictness-batchd.test.ts:96` (comment, objectstack-ai#10194) and its title `:93`. This file is in the `analytics*` set stages 3 and 4 excluded while PR objectstack-ai#20458 held it; `analytics-date-range-two-bound-window.test.ts` and `cube-member-inner-name-retirement.test.ts` were in that set too and are not re-measured here. - The package root: `compose-stacks-action-echo.test.ts:20`, `:34`, `:200` (objectstack-ai#14686) and titles `:176`, `:224`; `compose-stacks-action-key-collision.test.ts:3` (objectstack-ai#14662); `stack-top-level-strict.test.ts:103` (objectstack-ai#10485) and title `:128`; `type-alias-convention.pin.test.ts:257`, `:1572`, `:1937` (objectstack-ai#10485). - `shared/`: `metadata-collection.test.ts:250`, `metadata-url-spelling.test.ts:51`, `:72`, `:168` (objectstack-ai#10485), `:257` (objectstack-ai#10194), title `:254`. `automation/sync-retirement.test.ts:207` (objectstack-ai#10485). - `kernel/`: `manifest-unknown-keys.test.ts`, four titles (objectstack-ai#14192); `metadata-type-schemas.test.ts:422`, a title (objectstack-ai#10194). - Stage 6 took the package root, `shared/` and `automation/` through the gate's census, which never lists a test file, so test-file comment lines there may carry other dead numbers as well. That wider population is not measured here. **Outside `packages/spec/src`.** The same six numbers stand at 44 more sites (`packages/{metadata-protocol,objectql,rest,runtime,cli,core,metadata,qa}`, `examples/`, `scripts/`, `packages/spec/scripts/`), and at 19 sites in `migrations/` (the objectstack-ai#20233 area). **Rung.** The objectstack-ai#10485 retirement also has the ADR-0087 D3 entry `stack-themes-carrier-retired`, which `:423` already names. This PR takes the commit rung, as stages 1–6 did. **Wording, each true of its commit.** `:3037` and `:3194` now read "commit 279431e's same-key refusal": the refusal that commit added, in lines `773a99960a` wrote. `:1233` reads "commit 4d0d944 closed `ManifestSchema`", in a line `c78c9180de` wrote. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…me/src to the commits that decided them (objectstack-ai#20624) Part of objectstack-ai#20594 Clause-②: no ## What changed This is stage 1 of the `domain:cli` lane of the dead-citation sweep: `packages/runtime/src/**`, the lane's largest package. 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#20609 the closest sibling. Later stages cover `rest`, `cli`, `types` and the rest of the lane, so this PR says `Part of` and the card stays open. That is **513 comment sites on 508 lines in 118 files, covering 96 numbers**: 194 of the census's 217 sites, and 319 more in test comments, which the census defers. Three more sites carried a slash-joined dead number the citation grammar does not read (`objectstack-ai#10629/objectstack-ai#10630`, `objectstack-ai#5811/objectstack-ai#12281`, `objectstack-ai#8421/objectstack-ai#12194`), and they are rewritten too. Each rewritten line cites one of **95 distinct commits**. No ADR or ruling-record file in `docs/adr/` or `scripts/adr-anchors/` records the decision behind any of these numbers. ADR-0126 and ADR-0131 name objectstack-ai#10243 only as the incident, ADR-0126 names objectstack-ai#11513 only for the flow-clone half, and ADR-0112 names objectstack-ai#12281 only as another card. So every anchor is a commit. The anchors the landed stages already gave the same numbers are reused (24 numbers, for example `f19475c0a` for objectstack-ai#14143, `e2798fab7` for objectstack-ai#6345 and `79c46da90` for objectstack-ai#9934), so each number carries one anchor across the tree. Only comments changed. Every touched file keeps its line count (508 lines out, 508 in, over 118 files), so no line citation into these files moves. Seven of the 508 lines held no census site. Five are the other half of a sentence that had to change: `action-governance-scope-divergence.test.ts:6` (「the card names」 to 「that diverged」, because line 4 no longer names the card), `action-record-load-denied.test.ts:560`, `dispatcher-5xx-demoted-code-withhold.test.ts:45` (「that card's change」 to 「that commit's change」), `hook-input-writeback-readonly-provenance.integration.test.ts:380` (「that card」 to 「that commit」) and `standalone-stack-seeder-declaration-copy.test.ts:88` (a trailing 「PR」 whose number wrapped onto line 89). Two carry only a slash-joined number: `dispatcher-plugin.ts:688` and `meta-compound-arity-mint-door.test.ts:4`. 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. No PR number stands on an added line, and none of the 95 shas is on a removed line. Twenty-eight dead comment sites are left on purpose: - **20 in `domains/meta.ts`.** PR objectstack-ai#20615 (objectstack-ai#20590's) opened at 2026-09-29T08:12:40Z, after this stage's claim and first read, and edits that file. So the file went back to its base blob (`b4ddb362cc`) in `a5cdfd8a46`, as PR objectstack-ai#20612 did with `authoring-rules.ts`. The anchors are verified and listed below for the follow-up. - **8 with no deciding commit, or with a literal reader.** See "The sites left" below. One more file: a `patch` changeset for `@objectstack/runtime`, because the rewritten docblocks ship (see Changeset below). ## Census: `packages/runtime`, 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/runtime/`. Both runs enumerated the whole board (185 pages), so neither read a truncated board. | reading | tree | board | whole-repo `allocated-but-absent` | runtime sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `eb4b17c346`, run 2026-09-29T07:55:51Z to 08:05:47Z | enumerated, 185 pages, frontier objectstack-ai#20614, 18,441 numbers | 2,397 | **217** | 216 | 29 | 59 | | after | head `a5cdfd8a46`, run 09:08:23Z to 09:14:11Z | enumerated, 185 pages, frontier objectstack-ai#20623, 18,450 numbers | 2,027 | **23** | 23 | 4 | 12 | The before count equals the card's 217 at `f11b5f20a2`. The 23 left are the 20 held `domains/meta.ts` sites and 3 deliberate ones (`api-exposure.ts:108`, `domains/mcp.ts:360`, `route-ledger.ts:300`). The whole-repo drop is 370: this diff's 194, plus the 97 and 79 of PR objectstack-ai#20609 and PR objectstack-ai#20612, which landed on `main` in between and came in with the merge. **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/runtime/src` (373 files), against a board probed by REST for every number cited there. The lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200 and the dead controls objectstack-ai#16714, objectstack-ai#16715 and objectstack-ai#16697 answered 404 in both runs. | reading | tree | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---|---| | before, 08:17:55Z | `eb4b17c346` | 5,364 | **641** | 217 | 324 | 5 | 95 | | after, 09:24:24Z | `a5cdfd8a46` | 4,851 | **128** | 23 | 5 | 5 | 95 | Its src-comment column equals the census's 217 and 23, which is the control on the second instrument. The 4,499 resolving citations, the 195 that resolve as pull requests and the 29 cross-repo ones are the same in both readings. The drop is 513, exactly this diff's grammar-read sites. ## Per-number table Sites and files are the dead comment sites in scope at the base, tests included. `held` is `domains/meta.ts` (see above) and `left` is a site with no deciding commit or with a literal reader. `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. | number | comment sites / files | rewritten | held | left | strings kept | anchor | |---|---|---|---|---|---|---| | `objectstack-ai#6065` | 1/1 | 1 | 0 | 0 | 0 | `026101660` | | `objectstack-ai#6123` | 1/1 | 1 | 0 | 0 | 0 | `59d1933f9` | | `objectstack-ai#6206` | 5/2 | 5 | 0 | 0 | 0 | `8e13ca876` | | `objectstack-ai#6216` | 2/1 | 2 | 0 | 0 | 1 | `f586f1a89` | | `objectstack-ai#6220` | 1/1 | 1 | 0 | 0 | 0 | `83df2fd73` | | `objectstack-ai#6238` | 2/2 | 2 | 0 | 0 | 2 | `c8d6f6e08` | | `objectstack-ai#6259` | 4/2 | 3 | 0 | 1 | 1 | `6968885ef` | | `objectstack-ai#6265` | 12/2 | 12 | 0 | 0 | 4 | `cfb549db8` | | `objectstack-ai#6268` | 9/3 | 9 | 0 | 0 | 0 | `68f5eccb1` | | `objectstack-ai#6287` | 1/1 | 1 | 0 | 0 | 0 | `84c86fb45` | | `objectstack-ai#6307` | 1/1 | 1 | 0 | 0 | 0 | `293476148` | | `objectstack-ai#6316` | 6/3 | 6 | 0 | 0 | 0 | `448ac9565` | | `objectstack-ai#6345` | 10/3 | 10 | 0 | 0 | 0 | `e2798fab7` | | `objectstack-ai#6361` | 4/2 | 4 | 0 | 0 | 6 | `90bbf2510` | | `objectstack-ai#6363` | 6/2 | 6 | 0 | 0 | 2 | `17d095413` | | `objectstack-ai#6483` | 3/2 | 3 | 0 | 0 | 0 | `ee58392e1` | | `objectstack-ai#8722` | 1/1 | 0 | 0 | 1 | 0 | — | | `objectstack-ai#8724` | 1/1 | 1 | 0 | 0 | 0 | `ff4ba6a06` | | `objectstack-ai#8726` | 8/4 | 7 | 1 | 0 | 1 | `e783e163d` | | `objectstack-ai#8796` | 13/3 | 13 | 0 | 0 | 4 | `a4331227b` | | `objectstack-ai#8848` | 3/2 | 1 | 2 | 0 | 1 | `4fc4a3c0b` | | `objectstack-ai#8919` | 1/1 | 0 | 1 | 0 | 0 | `b5378550e` (held file) | | `objectstack-ai#9934` | 17/7 | 17 | 0 | 0 | 4 | `79c46da90` | | `objectstack-ai#9967` | 1/1 | 1 | 0 | 0 | 0 | `8f266f1cd` | | `objectstack-ai#10179` | 1/1 | 0 | 0 | 1 | 2 | — | | `objectstack-ai#10243` | 40/13 | 40 | 0 | 0 | 9 | `266436a7f`, `02b41232d` | | `objectstack-ai#10293` | 3/3 | 3 | 0 | 0 | 0 | `92a69d813` | | `objectstack-ai#10338` | 1/1 | 1 | 0 | 0 | 0 | `d2619fd0c` | | `objectstack-ai#10340` | 3/2 | 2 | 1 | 0 | 1 | `26f3588fb` | | `objectstack-ai#10380` | 12/2 | 12 | 0 | 0 | 0 | `dd8172ee2` | | `objectstack-ai#10485` | 3/3 | 3 | 0 | 0 | 0 | `35ad101bc` | | `objectstack-ai#10503` | 8/2 | 3 | 5 | 0 | 1 | `67ceb9aef` | | `objectstack-ai#10537` | 2/1 | 2 | 0 | 0 | 0 | `e634ecf6a` | | `objectstack-ai#10554` | 1/1 | 1 | 0 | 0 | 0 | `6abc4df03` | | `objectstack-ai#10629` | 75/23 | 75 | 0 | 0 | 0 | `13a6cb4ad` | | `objectstack-ai#10630` | 4/1 | 4 | 0 | 0 | 0 | `dd8172ee2` | | `objectstack-ai#10789` | 2/1 | 2 | 0 | 0 | 1 | `38bc74ed1` | | `objectstack-ai#10886` | 1/1 | 1 | 0 | 0 | 1 | `809e61221` | | `objectstack-ai#10888` | 3/3 | 2 | 1 | 0 | 1 | `d806081dd` | | `objectstack-ai#10961` | 5/3 | 5 | 0 | 0 | 3 | `222d06fc1` | | `objectstack-ai#10965` | 2/1 | 2 | 0 | 0 | 1 | `ab47f6974` | | `objectstack-ai#10978` | 1/1 | 1 | 0 | 0 | 0 | `4c9780c7a` | | `objectstack-ai#10983` | 3/2 | 3 | 0 | 0 | 0 | `6a4e929f5` | | `objectstack-ai#11006` | 4/4 | 3 | 1 | 0 | 0 | `cccbe51bf` | | `objectstack-ai#11015` | 3/1 | 3 | 0 | 0 | 0 | `82cb6e849` | | `objectstack-ai#11166` | 8/3 | 8 | 0 | 0 | 4 | `735f5c709` | | `objectstack-ai#11333` | 1/1 | 1 | 0 | 0 | 0 | `ea4d16420` | | `objectstack-ai#11504` | 3/2 | 3 | 0 | 0 | 0 | `f90e82024` | | `objectstack-ai#11513` | 2/2 | 2 | 0 | 0 | 0 | `e170b0ae5` | | `objectstack-ai#11703` | 8/3 | 8 | 0 | 0 | 1 | `5cb62d88b` | | `objectstack-ai#12010` | 1/1 | 1 | 0 | 0 | 0 | `77b91bdb4` | | `objectstack-ai#12176` | 5/5 | 5 | 0 | 0 | 0 | `7986d973f` | | `objectstack-ai#12194` | 11/4 | 8 | 3 | 0 | 0 | `311433f6b` | | `objectstack-ai#12195` | 9/4 | 4 | 5 | 0 | 10 | `7986d973f` | | `objectstack-ai#12281` | 20/5 | 20 | 0 | 0 | 5 | `0783d7b80` | | `objectstack-ai#12943` | 7/3 | 7 | 0 | 0 | 0 | `090f2302e` | | `objectstack-ai#13037` | 8/2 | 8 | 0 | 0 | 5 | `e7dfb1d69` | | `objectstack-ai#13233` | 5/1 | 5 | 0 | 0 | 0 | `3800e4293` | | `objectstack-ai#13241` | 5/4 | 5 | 0 | 0 | 1 | `a21d2a9cf` | | `objectstack-ai#13273` | 11/3 | 11 | 0 | 0 | 0 | `3a86a65e7` | | `objectstack-ai#13279` | 3/2 | 3 | 0 | 0 | 0 | `6a180e42d` | | `objectstack-ai#13325` | 3/1 | 3 | 0 | 0 | 0 | `2e0b7b18f` | | `objectstack-ai#13644` | 5/4 | 5 | 0 | 0 | 1 | `34ce8e7db` | | `objectstack-ai#13657` | 13/1 | 13 | 0 | 0 | 1 | `b003cf2e8` | | `objectstack-ai#14143` | 26/8 | 26 | 0 | 0 | 4 | `f19475c0a` | | `objectstack-ai#14390` | 1/1 | 1 | 0 | 0 | 0 | `9d7f7259f` | | `objectstack-ai#14398` | 3/1 | 3 | 0 | 0 | 0 | `317132495` | | `objectstack-ai#14403` | 6/1 | 6 | 0 | 0 | 0 | `93d2d679b` | | `objectstack-ai#14421` | 2/1 | 2 | 0 | 0 | 0 | `bd8795ea1` | | `objectstack-ai#14422` | 4/2 | 4 | 0 | 0 | 4 | `dc7c226b9` | | `objectstack-ai#14423` | 3/1 | 3 | 0 | 0 | 1 | `a56baa2bd` | | `objectstack-ai#14474` | 1/1 | 1 | 0 | 0 | 0 | `df657d9df` | | `objectstack-ai#14667` | 2/1 | 2 | 0 | 0 | 0 | `dc7c226b9` | | `objectstack-ai#14678` | 2/1 | 2 | 0 | 0 | 2 | `73ad0bba7` | | `objectstack-ai#14683` | 2/2 | 2 | 0 | 0 | 0 | `96326040f` | | `objectstack-ai#14723` | 1/1 | 1 | 0 | 0 | 0 | `65846bc46` | | `objectstack-ai#14745` | 1/1 | 0 | 0 | 1 | 0 | — | | `objectstack-ai#14748` | 1/1 | 1 | 0 | 0 | 1 | `92b5d7f00` | | `objectstack-ai#14758` | 15/5 | 15 | 0 | 0 | 1 | `84199cb87` | | `objectstack-ai#14760` | 6/2 | 6 | 0 | 0 | 2 | `ee32e1cb8` | | `objectstack-ai#14864` | 3/3 | 3 | 0 | 0 | 3 | `066dd3bd0` | | `objectstack-ai#14878` | 2/1 | 2 | 0 | 0 | 1 | `29db3cd2a` | | `objectstack-ai#14908` | 3/2 | 3 | 0 | 0 | 0 | `d5cbb44f3` | | `objectstack-ai#14921` | 2/1 | 2 | 0 | 0 | 0 | `c1d274de7` | | `objectstack-ai#15063` | 2/1 | 2 | 0 | 0 | 0 | `ad35745e8` | | `objectstack-ai#15068` | 2/2 | 2 | 0 | 0 | 4 | `8744de9e9` | | `objectstack-ai#15071` | 5/2 | 5 | 0 | 0 | 0 | `cf6e0a193` | | `objectstack-ai#16610` | 3/1 | 3 | 0 | 0 | 0 | `316a20fc5` | | `objectstack-ai#16649` | 4/1 | 4 | 0 | 0 | 0 | `44c917a47`, `613bfbd3d` | | `objectstack-ai#16755` | 1/1 | 1 | 0 | 0 | 0 | `44c849c7d` | | `objectstack-ai#16758` | 1/1 | 1 | 0 | 0 | 0 | `6e9bee640` | | `objectstack-ai#16783` | 1/1 | 1 | 0 | 0 | 0 | `854639b31` | | `objectstack-ai#16919` | 1/1 | 1 | 0 | 0 | 0 | `2cd4c548e` | | `objectstack-ai#17038` | 1/1 | 0 | 0 | 1 | 0 | — | | `objectstack-ai#17039` | 1/1 | 1 | 0 | 0 | 0 | `edf59e359` | | `objectstack-ai#17041` | 2/2 | 0 | 0 | 2 | 0 | — | | `objectstack-ai#17114` | 2/2 | 2 | 0 | 0 | 2 | `4af758d47` | | `objectstack-ai#17147` | 1/1 | 1 | 0 | 0 | 0 | `aaacf1d5c` | | `objectstack-ai#17148` | 1/1 | 0 | 0 | 1 | 0 | — | | `objectstack-ai#17195` | 1/1 | 1 | 0 | 0 | 0 | `d2c1d1980` | | `objectstack-ai#17219` | 1/1 | 1 | 0 | 0 | 0 | `706ad0fcc` | | `objectstack-ai#19364` | 2/2 | 2 | 0 | 0 | 0 | `ada701220` | | `objectstack-ai#19394` | 5/2 | 5 | 0 | 0 | 0 | `0862063ba` | Every cited sha matches exactly one object (`git rev-parse --disambiguate`, count 1 for each of the 95), is a commit, has one parent, and is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 95). The checkout is not shallow (`--is-shallow-repository` false), and the control leg `13a6cb4ad` exits 0 too. **Numbers with more than one anchor, by site:** - `objectstack-ai#10243` (40 sites): `266436a7f` for the 26 sites that describe the 2026-08-23 ruling it implements (the enablement door joins the `manage_metadata` write set, with the `trigger` exclusion), and `02b41232d` for the 14 that name the leak itself (「the leak commit 02b4123 measured」). That commit recorded the measurement over HTTP and says it is part of that card. - `objectstack-ai#16649` (4 sites): `613bfbd3d` for the first half (the fourteen remaining `boot-refusal` rows registered) and `44c917a47` for the second (the face refusal widened to every published package, and `boot-refusal` retired). - `objectstack-ai#12176`, `objectstack-ai#12194`, `objectstack-ai#12195`: the stages of one ruled retirement. `311433f6b` is stage 1 (the item-name grammar refused at the publish door) and `7986d973f` is stage 3 (the compound arities un-mounted). These are the anchors the spec stages gave. **Wordings to check, each true of its commit:** - `objectstack-ai#10293` (3 sites) cited the p1 flake whose signature had the expected-noise lines lifted into it. They now read 「(a vitest teardown race, fixed by commit 92a69d8)」. `92a69d813` names that number in its subject and fixed the flake by disarming vitest's console-forwarding teardown race, which is why the noise pointed the dispatch at the wrong mechanism. - `objectstack-ai#16755` and `objectstack-ai#16783` each cited an open PR that held a file at the time. They now read 「the change that landed as commit 44c849c held that file」 and 「then held by the change that landed as commit 854639b」. Each commit's diff edits the named file (`domains/automation.ts`, `seed-loader.test.ts`). - Quoted rulings keep their words. `dispatcher-plugin.declared-5xx-prose-withhold.test.ts:13` and `dispatcher-plugin.declared-user-message.test.ts:35` quote the 2026-08-27 ruling, and `dispatcher-5xx-demoted-code-withhold.test.ts:281` quotes an older note. There the commit stands in an editorial bracket (`[commit 79c46da]`, `[commit 0783d7b]`) in place of the number. - `objectstack-ai#9934`'s 「second constraint」 and 「third constraint」 now read 「the ruling's second constraint, commit 79c46da」. That commit's own diff calls status-agnosticism 「the ruling's second constraint」. - `domains/packages.ts:841` read 「declares, since objectstack-ai#19364:」 above the `enabled` line, but that line predates `ada701220` (objectstack-ai#19364's commit). It now reads 「declares — a key commit ada7012 kept rather than retired:」. - `route-ledger.ts:288`: 「objectstack-ai#16758 filed the second kind」 now reads 「Commit 6e9bee6 gated the second kind」, because that commit added the row census after the index-slice incident the sentence goes on to describe. - `flow-clone.ts:7` and `domains/automation.ts:2403` cite `e170b0ae5` for objectstack-ai#11513: the commit that landed 「lock package-declared permission sets at the save door; clone to customize」, whose changeset names the number. ## The sites left **No deciding commit, or a literal reader (8 sites):** - `api-exposure.ts:108` (objectstack-ai#6259): `api-exposure.test.ts:152` splits this `@param` block on the literal `'objectstack-ai#6259'`, so rewriting the comment would change what the test measures. Its deciding commit is `6968885ef`, which the three test-comment sites of the same number now cite. - `domains/mcp.ts:360` (objectstack-ai#8722): a wider contract change 「archived unscheduled」. It never landed, so no commit decided it. - `domains/meta-state-plural-tolerance.test.ts:130` (objectstack-ai#10179): an untaken option on a tracking card. The only commit naming the card, `53a48c93f`, recorded the opposite state. - `package-door-namespace-conflict-code.test.ts:30` (objectstack-ai#14745): a residue item on a review card. The only commit carrying the token is the one that added this file. - `route-ledger.conformance.test.ts:33` (objectstack-ai#17038): an ablation measured on a PR whose squash commit, `6a7910abb`, neither records nor performs it. - `route-ledger.conformance.test.ts:38` and `route-ledger.ts:300` (objectstack-ai#17041): a maintainer decision the lines call open. - `security/artifact-granted-permissions.test.ts:291` (objectstack-ai#17148): a question the line itself says is unsettled. **Held with `domains/meta.ts` (20 sites), anchors verified for the follow-up:** `objectstack-ai#8726` `:116` to `e783e163d`; `objectstack-ai#8848` `:200`, `:1398` to `4fc4a3c0b`; `objectstack-ai#8919` `:1247` to `b5378550e`; `objectstack-ai#10340` `:1309` to `26f3588fb`; `objectstack-ai#10503` `:14`, `:1143`, `:1159`, `:1250`, `:1308` to `67ceb9aef`; `objectstack-ai#10888` `:1337` to `d806081dd`; `objectstack-ai#11006` `:103` to `cccbe51bf`; `objectstack-ai#12194` `:831`, `:1050`, `:1173` to `311433f6b`; `objectstack-ai#12195` `:819`, `:827`, `:1046`, `:1167`, `:1960` to `7986d973f`. PR objectstack-ai#20615's one hunk there is at `:1874`, disjoint from these lines, but the rule is file-level. **String sites kept as tokens (100).** 95 are test titles and test-code strings in 43 files. Five are non-test strings: the `route-ledger.ts` `note` fields at `:435`, `:441` and `:505`, a string at `dispatcher-error-vocabulary.ts:349`, and the enablement door's refusal text at `domains/activation-gate.ts:279`, which ends 「(objectstack-ai#10243).」 (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, so template literals are read in context) of each touched file at base `eb4b17c346` against the working tree at `a5cdfd8a46`, over all 118 touched `.ts` files. Controls mutate the head text in memory only, so nothing on disk moved for them. - Real run: 301,081 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control (`domains/activation-gate.ts`): 0 files changed (exit 0). - Code-insertion positive control (a declaration in the same file): DIFFER at token 34 (exit 1). - String positive control (`(objectstack-ai#10243)` to `(objectstack-ai#10244)` inside the kept refusal string): DIFFER at token 339 (exit 1). Line balance: every touched file is +N/−N (508/508), and every line count is equal at base and head. A raw scan of the 119 changed files for control bytes finds none. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/runtime` is included, in PR objectstack-ai#20609'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/runtime build`, the rewritten docblocks reach `dist`: for example `e2798fab7` appears 3 times and `68f5eccb1` 6 times in `dist/index.d.ts`, and `f19475c0a` 4 times in `dist/index.js`. The positive control, the unchanged sentence 「drags `@libsql/client` (native bindings included)」 of the same `turso-driver-factory.ts` docblock, is in `dist/index.d.ts`, and a negative control phrase appears nowhere. The only dead number left in `dist` is the kept refusal string's `objectstack-ai#10243`. ## Gates (head `a5cdfd8a46`) 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 5s · declare it in the PR body · pnpm --filter @objectstack/runtime 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 99s (1m39s) · declare it in the PR body · pnpm --filter @objectstack/runtime 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 6s · declare it in the PR body · pnpm --filter @objectstack/runtime 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 8s · declare it in the PR body · pnpm --filter @objectstack/runtime typecheck ``` The dependency closure and the whole workspace were built first, at the merge head `ca6d13d6ab`, the same way: `turbo run build --filter='@objectstack/runtime...'` (30 tasks, exit 0) and `turbo run build --filter='./packages/*' --filter='./packages/*/*'` (71 tasks, exit 0). `a5cdfd8a46` differs from that head only in `domains/meta.ts`, which went back to base bytes, and `@objectstack/runtime` was rebuilt at `a5cdfd8a46`. - **Tests:** `vitest run --project local`: 288 files, 4,190 tests passed, 1 skipped. `--project repo` (which holds the touched `action-owner-key-single-source.test.ts`): 3 files, 727 tests passed. Together they cover every touched test file. - **Typecheck:** `pnpm --filter @objectstack/runtime typecheck` exits 0. `tsc --listFiles` counts 82 `src` files (no tests) under `tsconfig.json` and all 291 test files under `tsconfig.test.json`, which `check:test-typecheck` judges: 27 files, 190 errors, 68 pinned signatures held. - **Lint:** the repo-wide `pnpm lint` (`eslint . --no-inline-config`) exits 0 at `a5cdfd8a46` (2026-09-29T09:23:06Z to 09:23:36Z). A narrowed run over the 118 touched `.ts` files through eslint's API agrees: 118 linted, 0 ignored, 0 errors, 0 warnings. - **Citation judging:** `node scripts/check-issue-citations.mjs --base origin/main` exits 0. The diff-scoped run judged 23 citations across 28 files, and all 23 resolve. 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 `a5cdfd8a46` derived 67 families, the same set as at the merge head. All 67 exit 0. `--ran` reads 「67 derived, 67 run, 0 NOT-MEASURED, 0 UNRUN」. - At the merge head, `check:dual-build-cjs-loads` and `check:type-check-debt` first exited 3 (PREREQUISITE NOT MET) on a partly built workspace. After the whole-workspace build both exited 0, and both exit 0 at the final head. - Among them: `check:doc-authoring` (the sibling prose-id baseline holds, 810 pinned sites, no growth), `check:nul-bytes` (9,250 files, no raw control bytes), `check:route-ledger-census`, `check:dispatcher-error-vocabulary` and `check:issue-citations` (self-test, 114 cases in 8 batteries). - **Artifact rosters:** 38 of the 41 non-self-test roster rows exit 0 at the merge head. 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, and are run against this PR and reported on the card. ## Hypotheses (measured first) - **H0 holds.** The filtered census answers 217 dead sites at `eb4b17c346` (29 files, 59 numbers), equal to the card's count at `f11b5f20a2`: no drift. - **H1 holds, with the listed exceptions.** After the rewrite the filtered census answers 23: the 20 sites held with `domains/meta.ts` for an open PR, and 3 deliberate ones (a literal reader, a card never landed, an open decision). The supplementary reading adds 5 test-comment sites of the same two kinds. - **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 controls fire. The emitted `dist` is not byte-identical, because the docblocks ship, which is why the changeset is `patch`. ## Acceptance notes - **The held file.** The claim's read (07:51Z) and this stage's first read of the open PRs' file lists (08:06:32Z, 8 open PRs) found none touching `packages/runtime/src`. PR objectstack-ai#20615 opened at 08:12:40Z and edits `domains/meta.ts`. The re-read at 09:07:08Z (7 open PRs) found it, and it is the only open PR touching the package. The file went back to its base blob in `a5cdfd8a46`, and `git hash-object` equals `b4ddb362cc`, the blob at the base and at `origin/main`. The 20 anchors above are ready for the follow-up once that PR lands. - **Form D, not touched here.** `domains/activation-gate.ts:279` is part of the enablement door's refusal message and ends 「(objectstack-ai#10243).」. An author sees it, so it is ruling D's (no number, the lesson in words), a string change outside this comment-only scope. It needs a form-D carrier. The other four non-test string sites are ledger `note` data and a gate's own string. - **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/runtime/src`, 3 such dead numbers exist (`objectstack-ai#10630`, `objectstack-ai#12281`, `objectstack-ai#12194`), and all 3 are rewritten here. The other 36 distinct slash-joined numbers there were probed by REST and answer 200. One more dead one, `objectstack-ai#17219`, stands slash-joined inside a test title, a string, and is kept. This is the same shape as PR objectstack-ai#20612's slash-joined `objectstack-ai#5775/objectstack-ai#6629`. It is noted, not filed. - **Outside the scope and the census surface.** `packages/runtime/vitest.config.ts:54` cites `objectstack-ai#17853`, which answers 404. The file is outside `src/**`, so it is left for whoever owns the package's config. The other numbers there, and those in `tsup.config.ts` and `README.md`, answer 200. - **Base.** The branch merged `origin/main` once (`ca6d13d6ab`, merging `c1d8051e0a`) before the `--base origin/main` run, as the dispatch orders. That merge brought PR objectstack-ai#20609's and PR objectstack-ai#20612's landed stages and touched none of this diff's files. `origin/main` has since moved to `ed6f7348f9`, one commit that touches only `packages/cli`, so there was no second merge. - **Anchors shared with the landed stages.** 24 numbers keep the anchor the spec, lint or service-messaging stages already gave them, for example `f19475c0a` (objectstack-ai#14143), `b003cf2e8` (objectstack-ai#13657), `311433f6b` (objectstack-ai#12194), `8e13ca876` (objectstack-ai#6206) and `17d095413` (objectstack-ai#6363). ## Deviations - Three changed lines hold only a slash-joined dead number, beyond the census's sites (see Acceptance notes). Five more are the other half of a rewritten sentence (listed under What changed). - 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>
…'s test surface to the commits that decided them (objectstack-ai#21700) Part of objectstack-ai#20234 Clause-②: no ## What this stage does The citation gate's census defers `packages/**/*.test.ts`, so the dead tracker numbers in `packages/spec/src`'s test files were never in its count. This stage measured that surface and re-anchored every dead site in its comment and docblock text, in ruling C+D form C (triage `5856637615`): each line now cites the commit on `main` that decided what it describes, and says the decision in words. Wherever an earlier stage of this card, or a sibling lane, already landed an anchor for the same number, this stage reuses it. It also takes the two source comments that tests read literally, and moves each reader onto the new text: - `src/data/api-derivation.ts:163`: the `[objectstack-ai#6259]` marker on `DATA_ACTION_TO_API_OPERATION` becomes `[commit 6968885]` and the sentence says what that commit did (it removed the producer-less `batch: 'bulk'` row and stopped the description calling `batch` a runtime action). Its reader, `src/data/api-derivation.test.ts:236`, splits on the new marker. - `src/identity/identity.zod.ts:230`: `(objectstack-ai#8715,` becomes `(commit 2c86fe3,`; the maintainer-ruled DELETE and its date stay. Its reader, `src/identity/api-key-retirement.test.ts:118`, asserts the new opening phrase. - The runtime test that stage 6's record also named (`packages/runtime/src/api-exposure.test.ts:152`) reads `packages/runtime/src/api-exposure.ts`, not the spec file, so it is not a reader of this text and is untouched. Comment and docblock text only, plus those two readers. No test title, schema, type, export or behaviour change. ## Census (base `7d0781482d`, head `0e92e882f4`) Instrument: the gate's own `extractCitations` (comment-prose projection) and `namesThisRepository` over all 596 `packages/spec/src/**/*.test.ts` files (no `.test.tsx`, `.spec.ts` or `__tests__/` files exist there), with string literals read through the shared `scanSource` literal projection. Every distinct in-repo number was probed with REST `issues/N`, redirects not followed, with lit controls objectstack-ai#16862 objectstack-ai#16847 objectstack-ai#17698 and dead controls objectstack-ai#16714 objectstack-ai#16715 objectstack-ai#16697 at the start, every 100 numbers and the end. | | base | head | |---|---|---| | distinct in-repo numbers probed | 1,397: 1,310 answer 200, 87 answer 404, 0 other | 1,377: 1,309 answer 200, 68 answer 404, 0 other | | controls | 45/45 lit = 200, 45/45 dead = 404 | 45/45 and 45/45 | | dead sites in comments and docblocks | **87 sites, 85 lines, 34 files, 40 numbers** | **0** | | dead sites in string literals | 150 sites, 147 lines, 56 files, 69 numbers | 147 sites, 144 lines, 55 files, 68 numbers | 87 is under the stage's 120-site bound, so this stage takes all of it. By area: package root 36, `shared/` 10, `system/` 9, `data/` 8, `api/` 5, `automation/` 5, `integration/` 5, `conversions/` 4, `security/` 3, `identity/` 2. The excluded files (`migrations/**`; `automation/flow-slot-refusal-codes.test.ts`; `automation/flow-write-node-stored-metadata-target.test.ts`) carry 0 dead sites, so the exclusions removed nothing. The three literal sites that left are exactly the two readers' literals. Every test title is untouched. No in-repo comment citation was added at head. The one live number that left the in-repo count is objectstack-ai#6110, which now carries its `objectui#` qualifier (below). A raw `#N` scan of the comment projection agrees with the gate's extractor: its only extra hits are three `objectui objectstack-ai#11166` sites that are objectui's. The gate's own census (`check-issue-citations.mjs --census --json`, board enumerated, 196 pages, frontier objectstack-ai#21684) reads **0** findings in `packages/spec/src` at head. The gate's extractor read 2 at base (`api-derivation.ts:163` objectstack-ai#6259, `identity.zod.ts:230` objectstack-ai#8715). ### Dead numbers left in string literals (not touched; objectstack-ai#20749's class (e)) 68 numbers at 147 sites, all test titles. `data/` 40, `api/` 33, `ui/` 21, `kernel/` 18, `contracts/` 10, `shared/` 6, `system/` 6, `integration/` 5, `(root)` 3, `identity/` 3, `conversions/` 2, `automation/` 1. One of them is not a tracker number at all: `#0000` in `shared/retired-key*.test.ts` is a fixture placeholder. ## Anchors | number | sites | anchor | where | |---|---|---|---| | objectstack-ai#6037 | 4 | `18189983d` | type-alias-convention.pin.test.ts | | objectstack-ai#6072 | 1 | `7f713b662` (the squash commit of PR objectstack-ai#6072, ADR-0122 phase 1) | type-alias-convention.pin.test.ts | | objectstack-ai#6083 | 5 | `53068c130` (ADR-0122 phase 2) | connector-author-shape, environment-artifact, type-alias-convention | | objectstack-ai#6085 | 1 | `026101660` (added this very pin file) | shared/expression-dialect-docs.pin.test.ts | | objectstack-ai#6239 | 1 | `f549a0d4a` (the sweep commit, cited on the sweep's header line) | type-alias-convention.pin.test.ts | | objectstack-ai#6259 | 1 | `6968885ef` | data/api-derivation.test.ts | | objectstack-ai#6345 | 5 | `e2798fab7` (its diff wrote all five lines) | conversions.test.ts, stored.test.ts, driver/turso.test.ts | | objectstack-ai#6362 | 3 | `b5404f496` | automation/webhook.test.ts, integration/connector.test.ts | | objectstack-ai#6527 | 1 | `259459d8b` (the squash commit of PR objectstack-ai#6527) | type-alias-convention.pin.test.ts | | objectstack-ai#6604 | 2 | `d127ff002` | type-alias-convention.pin.test.ts | | objectstack-ai#6605 | 3 | `c6b05c76a` (wrote the prose-count check) | type-alias-convention.pin.test.ts | | objectstack-ai#8714 | 3 | `42b05af89` | security/explain.test.ts | | objectstack-ai#8715 | 2 | `2c86fe3ea` | identity/api-key-retirement.test.ts | | objectstack-ai#9040 | 2 | `24206416a` | data/driver/driver-credential-refusal.test.ts | | objectstack-ai#9041 | 1 | `d491625c1` | data/driver/driver-credential-refusal.test.ts | | objectstack-ai#9741 | 2 | `2a29caa53` | api/protocol.test.ts | | objectstack-ai#10194 | 2 | `2306a765c` | analytics-strictness-batchd, metadata-url-spelling | | objectstack-ai#10485 | 9 | `35ad101bc` | sync-retirement, metadata-collection, metadata-url-spelling, stack-top-level-strict, type-alias-convention | | objectstack-ai#10926 | 2 | `d173125fb` | system/i18n-resolver.test.ts, system/translation.test.ts | | objectstack-ai#11006 | 4 | `cccbe51bf` | api/protocol.test.ts, type-alias-convention.pin.test.ts | | objectstack-ai#11166 | 1 | `735f5c709` (the runtime and service-datasource lanes' anchor) | shared/external-errors.test.ts | | objectstack-ai#11333 | 1 | `e58ea8b38` (stage 6's anchor for the same phrase) | system/environment-artifact.test.ts | | objectstack-ai#12194 | 1 | `311433f6b` | type-alias-convention.pin.test.ts | | objectstack-ai#12493 | 1 | `aa5994e17` | system/operation-message.test.ts | | objectstack-ai#12961 | 2 | `901355c3b` | system/i18n-resolver.test.ts | | objectstack-ai#13135 | 3 | `9e0ba21a1` | type-alias-convention.pin.test.ts | | objectstack-ai#13156 | 1 | `fd289be45` (the sibling files' spelling, "commit fd289be's strip") | compose-stacks-key-loss.test.ts | | objectstack-ai#13218 | 2 | `c45d8e6b4` | system/i18n-resolver.test.ts | | objectstack-ai#14162 | 1 | `c5a9a437d` (the squash commit of PR objectstack-ai#14240, whose body names objectstack-ai#14162 as the card it lands; it is the load path that judges each `packages[]` entry with `ArtifactPackageEntrySchema`) | stack-artifact-packages.test.ts | | objectstack-ai#14419 | 1 | `c5a7448d5` (its message names objectstack-ai#14419 as the card it lands) | automation/control-flow.test.ts | | objectstack-ai#14662 | 1 | `35dffeace` | compose-stacks-action-key-collision.test.ts | | objectstack-ai#14676 | 4 | `13c48c2a5` | integration/connector.test.ts, type-alias-convention.pin.test.ts | | objectstack-ai#14686 | 3 | `279431e7a` | compose-stacks-action-echo.test.ts | | objectstack-ai#14691 | 2 | `b3a63d32c` | rest-api-config-dead-keys-retirement, type-alias-convention | | objectstack-ai#14722 | 2 | `23c72be3c` (stage 1's "refuted in commit 23c72be") | shared/union-author-message-pins.test.ts | | objectstack-ai#16659 | 3 | `ecdfc9411` | schedule-organization.test.ts, type-alias-convention.pin.test.ts | | objectstack-ai#16864 | 1 | `29dd1a6dd` (it wrote this very pin line) | conversions/conversions.test.ts | | objectstack-ai#17124 | 1 | `86c505286` (the service-analytics and core lanes' anchor) | data/analytics-date-range-two-bound-window.test.ts | Every anchor is unique at nine hex digits, single-parent, and an ancestor of `main`. That was read from a full, not shallow, treeless clone of `main` at the base. For 40 of the 49 (number, anchor, file) pairs, the anchor's own diff wrote a line naming the number into that file. Where it did not, the anchor's message or its PR body names the number. Two census-dead sites are not this repository's numbers. They are respelled so the gate reads their qualifier, following stage 5's precedent, and are not re-anchored. Both numbers answer 200 on objectui. - `api/export-job-family-retirement.test.ts:24-25`: `objectui` / `PR objectstack-ai#10264` was split across a line break, so the census read objectstack-ai#10264 as bare. The line break now falls before `objectui PR objectstack-ai#10264`. - `shared/editability-boundary.test.ts:391-392`: `(objectui#6010 / objectstack-ai#6110 / objectstack-ai#6111)` becomes `(objectui#6010 / objectui#6110 / objectui#6111)`, the spelling `ui/view.zod.ts` and `data/field.zod.ts` already use for the same pair. ## Proof that only text moved - **Token streams.** A TypeScript 6.0.3 parser leaf-token comparison (JSDoc nodes skipped) and a comment-blanked comparison (the shared `maskComments`, whitespace collapsed) run over all 36 files, base vs head. 34 files are IDENTICAL on both instruments. The other two differ only in the declared reader literals: `api-derivation.test.ts` in 2 string tokens (`'[objectstack-ai#6259]'` and its assertion message), and `api-key-retirement.test.ts` in 1 (`'are NOT declared here (objectstack-ai#8715'`). `VERDICT text-only (36 files)`, exit 0. - **Controls, 8 of 8 as expected.** These ran on scratch copies. An identifier change, a test-title string, a template literal and a regex literal each read DIFFERS (exit 1). A line comment, a JSDoc edit and a block comment each read IDENTICAL (exit 0). An undeclared literal changed beside the declared ones reads DIFFERS (exit 1). - **Lines.** 206 changed lines (103 out, 103 in; every file keeps its line count). 200 are comment lines, and 6 are the three reader lines out and in. - **Test counts unchanged.** The 34 touched test files ran at base in a separate base worktree, and at head: 346 suites and 1,689 tests passed on both sides. The (fullName, status) list matched for every file. ## Reverse verification of the two readers (each leg through `scripts/ablation-replace.mjs`, restore proven against `HEAD`) | leg | mutation | result | |---|---|---| | A1 | `api-derivation.ts`: `[commit 6968885]` back to `[objectstack-ai#6259]` | red: "the `[commit 6968885]` removal note vanished from the TSDoc", 1 failed, 31 passed | | A2 | `api-derivation.test.ts`: the reader back to `split('[objectstack-ai#6259]')` | red, same assertion | | B1 | `identity.zod.ts`: back to `are NOT declared here (objectstack-ai#8715` | red: `toContain('are NOT declared here (commit 2c86fe3')`, 1 failed, 2 passed | | B2 | `api-key-retirement.test.ts`: the reader back to `(objectstack-ai#8715` | red, same assertion | Each leg's mutation landed (anchor 1 → 0, blob changed), and each restore read blob == HEAD with an empty `git diff HEAD`. Both files are green at `HEAD` (32/32, 3/3), and the tree is clean afterwards. ## Generated artifacts and the changeset - After `pnpm --filter @objectstack/spec build`, the rewritten `DATA_ACTION_TO_API_OPERATION` docblock is in `dist/data/index.d.ts` and `index.d.mts`. The positive control, the same docblock's unchanged first sentence, is in the same two files, and the old `[objectstack-ai#6259]` marker is in 0 `dist` files. - The `identity.zod.ts` block comment is in no `dist` file, and neither is the control sentence from the same block. But `files[]` ships `src/**/*.zod.ts` verbatim, so the new text publishes. - So this PR carries a `@objectstack/spec` patch changeset (`.changeset/spec-test-surface-dead-citation-anchors.md`, with the `Clause-②: no` line), and `skip-changeset` does not apply. Test files do not ship. - `check:generated`: all 15 generated artifacts are up to date, with nothing regenerated. `check:docs` is green, so no reference page renders either comment. ## Verification - `pnpm --filter @objectstack/spec build`, then `check:generated`: exit 0. - `pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2`: 610 files passed, 18,113 tests passed, 1 todo. - The two touched `repo`-project files (`export-job-family-retirement`, `rest-api-config-dead-keys-retirement`) are green in the 34-file run. - `pnpm --filter @objectstack/spec typecheck`: exit 0 (`check:test-typecheck` OK: 52 files, 246 errors, 135 pinned signatures held). - `dispatch-gates --commands` at `0e92e882f4` derives 85 families. `--ran` reports 85 accounted, 84 run (all exit 0), 1 NOT MEASURED and 0 unrun. - The NOT MEASURED one is `check:dual-build-cjs-loads`, which exits 3 (PREREQUISITE NOT MET) without a full monorepo build. It is a declared narrowing: the diff is comment-only in `packages/spec`, and spec's 19 require entries from the gate's own `--list` all load at head (a missing-entry control throws). CI runs the full gate. - Lint, a proven narrowing: `eslint --no-inline-config --format json` over the 36 touched files gives 36 files, 0 errors and 0 warnings. `isPathIgnored` is false for all 36, read from eslint's own config. `eslint.config.mjs` enables no type-aware linting (no `parserOptions.project`), so a comment edit cannot move any untouched file's verdict. The repo-wide `pnpm lint` is CI's. - These readings are at head `0e92e882f4`. `origin/main` was re-fetched before opening this PR (`83e2feeb46`, 5 commits past the base). None of those commits touches `packages/spec` or any file here, and `git merge-tree` onto it exits 0, so no merge was taken. ## Acceptance notes - **Rung choices, one line each.** objectstack-ai#16864 cites `29dd1a6dd`, the commit that wrote this exact pin line; stage 8 took ADR-0087's 2026-09-13 addendum for a different sentence about the three data-at-rest seams. objectstack-ai#6239's row in the `-7` receipt table now reads `views`, and the sweep commit `f549a0d4a` is cited on the sweep's header line beside the live `objectstack-ai#6486`. - **What remains of this card's surface.** In `packages/spec/src` there are no dead comment or docblock citations left: the gate census reads 0 and the test-surface census reads 0. What remains are the 68 dead numbers in 147 test-title literals listed above. This stage's dispatch assigns those to objectstack-ai#20749's class (e), so this PR says `Part of` and leaves the card open for that call. - **Outside the card's census surface, as earlier stages recorded.** `docs/audits/2026-07-unknown-key-strictness-ledger.md:676` (objectstack-ai#6276) has no carrier. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Clause-②: yes
Fixes #16714
What moves
objectNavTargetExclusivity(packages/spec/src/ui/app.zod.ts) is nowexport function objectNavTargetExclusivity(item, ctx): void— one function per refinement, the posture of thecheck*exports from spec: export the object-level refinement checks (superRefine/refinebodies) as named functions so zod mirrors in objectui can re-attach them (spec half of objectui#7715) #16489. It ships through the existingexport * from './app.zod'insrc/ui/index.ts, so@objectstack/spec/uicarries it with no barrel edit.NavigationItemSchemachains the check exactly where it did — itstype: 'object'union branch — and the exportedObjectNavItemSchemastill carries no object-level check. The public-surface gate's diff is additions only:api-surface/ui.json+1 line (objectNavTargetExclusivity (function)),export-origins/ui.json+1 line;check:api-surfacereports0 breaking (removed/narrowed), 1 added.filtersdocblock — the root cause, same file — no longer states the complete precedence orderrecordId → filters → viewNamea few lines above the sentence that declares the combination unrepresentable. It now says in as many words that no precedence order is stated and why, and names the one legacy combination the guard tolerates. The.describe()strings, which are what reachcontent/docs/references/, are unchanged (check:docsgreen without regeneration).recordId+viewNamestays a tolerated legacy combination; (ii)runActionis refused withrecordIdonly and still composes withfilters/viewName.src/ui/app-nav-target-exclusivity-export.test.ts(34 cases): parity between the export andNavigationItemSchema's parse over the whole matrix (5 refusal paths, 8 accepting paths including the ruling's negative controls), the mount (thetype: 'object'branch carries exactly onecustomcheck whose issue vector equals the export's;ObjectNavItemSchemacarries none and accepts every fixture), attachment by identifier (declared once, chained once, read from source), and barrel identity. It is its own file rather than a row in the spec: export the object-level refinement checks (superRefine/refinebodies) as named functions so zod mirrors in objectui can re-attach them (spec half of objectui#7715) #16489 harness because that harness keys on a schema whose owncheckscarry the export, and this mount is a union branch; PR fix(spec): probe for the checked-in sdui.manifest.json before prescribing how to make one #16847 edits that harness in flight, so the two stay disjoint.@objectstack/specminor (one new export).Mechanism assumptions, re-measured on
origin/main460d4b807(main moved past the5e53d73dtriage read)ObjectNavItemSchemais now built aslazySchema(() => strictObject(navItemSurface('object'), {...})); its_zod.def.checksis[]. The union thatNavigationItemSchemalazily resolves to has nine branches; only thetype: 'object'branch carries a check (['custom']). Located by text, not by line number.packages/types/src/zod/app.zod.ts:NavigationItemSchemais hand-written; itssuperRefinechecks onlyid/label(separator exempt);filtersoccurs once in the file (the field declaration);objectNavTargetExclusivityoccurs nowhere in that repo; itsfilters.describe()carriesPrecedence: recordId → filters → viewName.verbatim. Measured at the pinned.objectui-sha53ded82band at objectuiorigin/main3f775ee. objectui'srecordIdprose did NOT copy the spec's "mutually exclusive withviewName" phrase, so the propagation evidence is specific tofiltersand the docblock edit stays scoped to it.packages/types/package.jsondepends on@objectstack/spec:^17.0.0at the pin53ded82b,^17.3.0at objectuiorigin/main. Triage read^17.3.0; the pin this repo builds against declares^17.0.0— same major, the route's premise holds either way.node_modulesand sits on another seat's branch, and installing there is not this card's to do. Every objectui sentence above is a source reading at the two named shas.Verification (each exit captured before any pipe; lock verdicts quoted from
os-verify-lock)Head at the last run of every line below:
bf41b3f60(the last commit moved only the changeset's wording; the two TS files are byte-identical toe7c15d138, where the tests, typecheck, lint and ablation ran).pnpm --filter @objectstack/spec buildunderscripts/pm/os-verify-lock.sh→VERDICT command-exit 0, twice: after the source edit, and again after a later test-file edit moved the src digest (check:api-surfacerefuses a stale dist by design — that refusal was a prerequisite reading, not a red, and it is gone on the rebuilt dist).pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/ui/app-nav-target-exclusivity-export.test.ts src/ui/app.test.ts src/ui/object-refinement-check-exports.test.ts→Test Files 3 passed (3),Tests 231 passed (231).pnpm --filter @objectstack/spec typecheck→VERDICT command-exit 0(tsc --noEmit,check:scripts-typecheck,check:test-typecheck: OK).pnpm --filter @objectstack/spec check:generated→✓ All 15 generated artifacts are up to date.after--fixregenerated only the two it proved stale (api-surface/,export-origins/).check:exported-any(no exported type resolves to any: 2450 types + 1509 schemas across 17 entry points),check:dual-source-exports,check:entry-nameability,check:browser-reachable-entries,check:empty-state,check:variant-docs,check:yaml-examples,check:duration-unit-keys,check:llms-txt,check:objectui-pin-citations,check:skill-refs,check:liveness,check:strictness-ledger,check:api-surface,check:authorable-surface,check:docs,check:export-origins.check:nul-bytes,check:cross-package-test-inputs,check:test-source-alias,check:type-check-coverage,check:pm-widening-tells,check-changeset-no-major --base origin/mainand its self-test,check-empty-changeset --base origin/mainand its self-test,check-adr-0087-registration --base origin/mainand its self-test,check-closing-keyword-parityand its self-test,check:objectui-changeset,check:spec-parsed-alias,check:merge-driver,docs-audit/check-affected-docs,docs-audit/check-drift-comment,check:doc-authoring,check:published-files,check:dts-closure,check:type-source-resolution,check:changeset-gate-self-tests,check-undeclared-dep-importsand its self-test,check:page-declaration-shape,check:sourcemap-no-sources-content,check-keyed-text-boundsand its self-test,check-comment-mask-adoptionand its self-test,check-comment-mask-corpus,check-dev-prereqs --self-test,check-ci-filter-parity.check:type-check-debt(exit 3,PREREQUISITE NOT MET— needs the whole workspace closure built; this diff touches no DEBT-ledgered package) andcheck:dual-build-cjs-loads(exit 3,PREREQUISITE NOT MET — some package has no dist/). Both are CI's.dispatch-gates.mjs --ran: 78 derived, 54 run, 24 UNRUN — the "any edit" tail (driver / engine / kernel census families, lint'scheck-reference-carrier-shape,doc-formula-expressions,release-rehearsal-clone --self-test, …) reading code this diff does not touch; left to CI and listed in the report. Local LEVEL AXIS readsNOT MEASUREDbecause no PR body existed yet; this body carries the declaration and the changeset isminor.pnpm exec eslint --no-inline-config --format jsonon the two changed TS files → 2 files, 0 errors, 0 warnings. Population:--print-configresolves a config for both (5 and 4 rules), so neither is ignored. Invariance:eslint.config.mjsstates it never enables type-aware linting for any file, so this diff cannot move the verdict of an untouched file. The repo-widepnpm lintis CI's run.Ablation (from the committed state
e7c15d138; the pin readssrc, so no dist preflight applies;traprestore and a blob-hash check on each leg)exportkeyword: on-disk proofexport-function count 1 → 0;vitestexit 1,Tests 30 failed | 4 passed (34). Restore: blobea9a5bff…equals the HEAD blob,git diff HEADempty.recordId && viewNamerefusal injected ahead of the real rule, marker count 0 → 1):vitestexit 1,Tests 2 failed | 32 passed (34)— exactly the two negative controls went red (recordId + viewName (tolerated legacy)and the triple). Restore: blob equals the HEAD blob,git diff HEADempty.Tests 34 passed (34). Finalgit status --porcelainempty.验收备注
分诊 §⑤ 验收口径原文照抄,每条附本 PR 的答复:
objectNavTargetExclusivity,⛔ 不动ObjectNavItemSchema现有的挂载与 accept set。PR 里明确写一句"本次不改变任何已发布 schema 的接受集",并让公开面闸门(api-surface/ui.json)的 diff 只有新增一行。」 — 本次不改变任何已发布 schema 的接受集。api-surface/ui.jsondiff 仅 +1 行,export-origins/ui.json仅 +1 行;check:api-surface:0 breaking (removed/narrowed), 1 added。新 pin 钉住ObjectNavItemSchema的checks为空且接受矩阵内每个 fixture。filtersdocblock 已改写:不再给出优先级顺序,明写"此处刻意不给优先级"及原因,并指向守卫自己的 docblock 所述的唯一容忍组合。.describe()字符串未动。filters、单独的recordId、单独的viewName、以及recordId+viewName」 — 四条阴性对照加runAction单独 / +filters/ +viewName共 8 条接受路径,在直接调用与NavigationItemSchemaparse 两侧均通过。Blocked-by:回链。其它记录(noted, not filed):
recordId的 docblock 写着 "Mutually exclusive withviewName(viewName is ignored if both are set)"——括号里的话把它说成优先级而非拒收;objectui 的recordId文案没有抄这句(已测),传播证据只在filters那句,故本 PR 不动它。承接者:无——裁决第 3 条本就保留这一容忍,没有哪个在途 PR 读这句话。packages/types/package.json在 pin 处声明^17.0.0、在 objectui main 处声明^17.3.0——两者都满足第三条路的前提;已记入 objectui#8563 供 dep bump 参考。承接者:objectui#8563。🤖 Generated with Claude Code
https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
Generated by Claude Code