Repository navigation
docs(spec): re-anchor the dead tracker citations in ui/ and two freed sites to the commits that decided them (stage 5) - #20576
Conversation
… sites to the commits that decided them (stage 5) Comment and docblock lines only. Each line that cited a tracker number answering 404 now cites the commit on main that decided what the line describes, or ADR-0087's own amendment where the line quotes it. Two sibling pairs are respelled so each number carries its own qualifier (objectui#6110 + objectui#6111, objectui#8221's PR objectui#8758). No code token moves; every file keeps its line count. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…e-anchored docblock Generated by `pnpm --filter @objectstack/spec check:generated --fix`, which proved only this page stale; never hand-edited. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
Brings in the landed citation-qualifier grammar (scripts/check-issue-citations.mjs) so the derived gates run against it; no file of this diff moved on main. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 2 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 6 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 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 5282ad8d830a1f5006264885f9baff5802cc5a5a && git checkout 5282ad8d830a1f5006264885f9baff5802cc5a5a
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin f572a7eb3c272a5be85e3ec64d2516c944c3b243 1b885d3c277b668ac4d9c1533b65dc436204ed2a && git checkout -B drift-repro f572a7eb3c272a5be85e3ec64d2516c944c3b243 && git merge --no-ff 1b885d3c277b668ac4d9c1533b65dc436204ed2a
node scripts/docs-audit/affected-docs.mjs --json f572a7eb3c272a5be85e3ec64d2516c944c3b243
|
Contract reviewServed-tier: Read: card #20234 (body; every comment: triage ① Derived judgments(a) Scope and file surface: right. 27 files: 23 (b) Comment-only, no test string or code token moves: right. (c) Numbers: right. Over the whole diff, added-minus-removed tracker numbers is empty for every one of the 27 files; net-removed is exactly the dev's 25 numbers, 87 sites (85 in the (d) Anchor truth: right, 23 of 23. 23 distinct sha-shaped tokens stand on added lines and none on removed lines (the
(e) (f) The three respellings: right. (g) The generated page: right. (h) Form C over the whole diff: right. Each rewrite leads with ② Semver level
③ Boundary flagsBlocking: none. Dev deviations, each answered:
Non-blocking, each true of its commit: (1) Escalated: none. CI at this head, the judging read at 2026-09-29T04:31Z: 32 check-runs, 16 success, 2 skipped ( Implemented-by: VERDICT: PASS Generated by Claude Code |
…decision in words instead of a tracker number (stage 21) (objectstack-ai#21907) Part of objectstack-ai#20749 Clause-②: no Stage 21 of this card: the next area of class (e), the test strings shipped under `packages/spec/src`, as ruled in `5902360492` on objectstack-ai#20513. This stage takes the second name-ordered `ui/` group: the test files directly under `packages/spec/src/ui/` from `component-record-blocks.test.ts` to `dashboard.test.ts`. Those files carried 103 messages and 109 tracker-shaped ids, citing 58 records. 105 of those ids now either state what their record decided, in words (form D), or are dropped where the title already says it. Four stay: they are CSS colour literals in two widget fixtures, not citations (below). Text only: no assertion, identifier, test count or code comment changes, and no file is renamed. ## Census at the base (`1e18a0735c`) Instruments: `census10.cjs` (md5 `9d08602ab972b4b8643c90d64d40fa41`), `census.cjs` (md5 `6e42a45a926d375013c32d62f16a296e`), `census-wide.cjs` (md5 `c98410a19529c439adb0afbfb00026a2`) and `dirtable.cjs` (md5 `dda605c54745b4a60cc14c9a686e4eff`), byte-identical to the copies stages 10 to 20 used. A literal counts as a test title when its folded message is argument 0 of a `describe` / `it` / `test` call, `.each` / `.skip` / `.only` chains included. Everything else is an "other" string. The base is `1e18a0735c`, stage 20's landing and the claim's base. Both instruments read **764 messages / 807 ids in 159 files**, the seat's reading and stage 20's head reading. | directory | files | messages / ids | titles | other | |:--|--:|--:|--:|--:| | `ui/` (this PR: 7 of the 53 files) | 53 | 300 / 318 | 284 / 302 | 16 / 16 | | `api/` | 40 | 189 / 201 | 181 / 193 | 8 / 8 | | `system/` | 34 | 154 / 165 | 128 / 138 | 26 / 27 | | (files directly in `src/`) | 30 | 118 / 120 | 117 / 119 | 1 / 1 | | `ai/` | 1 | 2 / 2 | 0 | 2 / 2 | | `contracts/` | 1 | 1 / 1 | 0 | 1 / 1 | | **total** | **159** | **764 / 807** | **710 / 752** | **54 / 55** | The group reads **103 messages / 109 ids in 7 files**, the seat's figures file for file: | file (under `ui/`) | messages / ids | titles | other | |:--|--:|--:|--:| | `component-record-blocks.test.ts` | 6 / 6 | 6 / 6 | 0 | | `component-reference-rail.test.ts` | 2 / 2 | 2 / 2 | 0 | | `component-type-vocabulary.test.ts` | 3 / 3 | 3 / 3 | 0 | | `component.test.ts` | 65 / 70 | 65 / 70 | 0 | | `dashboard-chart-structure-refusal.test.ts` | 2 / 2 | 0 | 2 / 2 | | `dashboard-compareto.test.ts` | 5 / 5 | 5 / 5 | 0 | | `dashboard.test.ts` | 20 / 21 | 17 / 18 | 3 / 3 | | **7 files** | **103 / 109** | **98 / 104** | **5 / 5** | `component-report-items-action-members-typed.pin.test.ts` sits in the same name range and carries no id. The five "other" strings are the four colour literals and `dashboard.test.ts:205`. - **Controls.** Lit: `ui/view.test.ts`, outside the group, reads 43 ids at the base and at the head. Dark: `component.test.ts` reads 0 at the head while 200 of its comment lines still carry a number. Planted in scratch copies of head files: an id put into a `component-record-blocks.test.ts` title reads 1 / 1, and an id put into a `dashboard-compareto.test.ts` comment reads 0. - **A wider pattern** (any `#` plus digits) reads the same as the gate pattern in 6 of the 7 files at the base. `component.test.ts` reads one more: `decision batch objectstack-ai#77`, a two-digit batch number the gate pattern does not count. It leaves with the id beside it, as stages 9, 13 and 15 did with theirs. - **At the head:** 665 messages / 702 ids in 154 files. The 7 files read 4 / 4 (the four colour literals), `ui/` reads 201 / 213, and no other file moved. ## How the area was chosen `ui/` has no subdirectory test file with an id, so it is taken in name-ordered file groups near the ~100-id bound. Stage 20's re-cut named this group at 109 ids, and this census reads 109, so no re-cut was needed. **Named for the next stages** (cut from the head census, 665 / 702): - `ui/` 213 ids. One is stage 20's kept `component-props-unknown-members.pin.test.ts:322`, four are this group's colour literals, and 208 sit in the 45 files after `dashboard.test.ts`. The next group nearest 100 runs from `dataset-filter-nested-relation-list.test.ts` to `view-inline-object-binding.test.ts`: 29 files, 96 messages / 102 ids, 7 of them "other". Cutting two files earlier gives 98. The last `ui/` group is then the 16 files from `view-item-config-type.test.ts` to `widget.test.ts`: 100 messages / 106 ids, `view.test.ts` alone 43. - `api/` 201, two stages. `system/` 165, two. The files directly in `src/`, 120, one. - The three docblock needles plus the kept `:322`, one stage, with an at-tier review. ## The five "other" strings: four kept, one rewritten - **`'objectstack-ai#111'` / `'objectstack-ai#222'` at `dashboard-chart-structure-refusal.test.ts:94` and `dashboard.test.ts:124` are kept.** They are three-digit CSS hex colours, not placeholders for a record: `colors: ['objectstack-ai#111', 'objectstack-ai#222']` in a widget's `chartConfig`, and `palette: ['objectstack-ai#111', 'objectstack-ai#222']` inside the free-form `options` bag. They cite nothing. But they are not input and expected value at once, the case stage 12 dropped. Each is input that the schema under test reads (`ChartConfigSchema.colors` is a string-array or string-map union), and the assertions read other things: the first parse must succeed, the second asserts `options.stacked`. So by the claim's rule they stay, and are reported. They match the gate pattern only because a short hex colour can be all digits. They are the only four such literals in the whole census. - **`dashboard.test.ts:205`** is the label argument of the file's `orderPin` helper, which passes it to `it()`. So it is a test title one call down, and no assertion reads it. It is rewritten and declared to the text-only tool. No string in the group is a needle (an id that is the expected value of an assertion over a docblock or another file's text). The three known needles are in `ai/` and `contracts/`. ## What each id became - **21 literals (21 ids)** now state a decision in words. - **15 literals (15 ids)** get their subject back in words, where the number stood for a thing, such as "the objectstack-ai#11661 keys". - **63 literals (69 ids)** drop a number the title already explains. Every cited record was read with its comments through REST: 54 answer 200 and 4 answer 404. Four citations are cross-repo: `ui#6206` (also spelled `ui#6206-B`), `ui#6207` and `objectui#8221` were read from objectui and answer 200. `framework#2501` was read under the repository's current name, as stage 18 read `framework#2536`. objectstack-ai#5042 is a pull request, `objectstack-ai#4001` batch 14. The four 404s were read from what landed, through the commits the stage-5 comment sweep (objectstack-ai#20576) re-anchored them to: - **objectstack-ai#6276:** `78f0be872`, which declares the record picker's flat `sort` / `limit` (maintainer ruling 2026-08-08, direction A). - **objectstack-ai#9972:** `60e0f900a`, which records the live read point of `page:tabs` `items[].icon` and its accept pin. - **objectstack-ai#11507:** `88b9d749a`, which declares `sys_activity.type` an open, author-extensible vocabulary (maintainer ruling 2026-08-24, direction 4). - **objectstack-ai#11658:** `1a6a19c31`, which opens `RecordActivityProps.types` to author-contributed kinds, executing that ruling. Each of those titles already carried its decision, so the number is dropped. `component.test.ts:3037` also gets its subject back: "the ruling" becomes "the open-vocabulary ruling". **Stated in words:** | record | literal (under `ui/`) | now reads | the decision | |:--|:--|:--|:--| | objectstack-ai#8744 | `component-record-blocks.test.ts:355` | "the `record:discussion` / `record:chatter` pair — one shared row" | `record:discussion` is wired to the chatter row on purpose: one renderer, one accept face. | | objectstack-ai#7702 | `component.test.ts:63` | "accepts a header without title — the synthesized shape, headed from the record" | Ruling A/B: `title` becomes optional, and an omitted title means the renderer derives the heading from the record. | | objectstack-ai#6776 | `component.test.ts:89`, `:262` | "PageHeaderProps recordChrome / showStar / showCopyId — declared because the header renderer reads them"; "PageAccordionProps variant — declared because the accordion renderer reads it" | Route A: declare the five author keys objectui's renderer reads. | | objectstack-ai#6776 | `component.test.ts:235` | "PageTabsProps tabStyle — renamed from `type`, which collides with the component `type`" | Route A: rename `type` to `tabStyle`, because in a flat node `type` is the component's own dispatch key. | | objectstack-ai#6946 | `component.test.ts:122` | "PageHeaderProps icon is retired — no renderer reads it" | Maintainer ruling: retire three keys with zero renderer read points, this one among them. | | objectstack-ai#5775 | `component.test.ts:406`, `:2932` | "… items[].value / items[].count — declared because the tabs renderer reads them"; "RecordPathProps stages[].terminal — declared because the path renderer reads it" | Ruling A: retire the dead keys and declare the keys the renderers honour. | | objectstack-ai#5775 | `component.test.ts:649` | "PageContainerProps — page:section / page:footer / page:sidebar compose through `children`" | Same ruling: the three containers declare `children` as their one composition key, and `body` is not declared. | | objectstack-ai#11289 | `component.test.ts:768` | "preserves the section presentation keys the renderer honours, verbatim" | Direction 1: declare `hideEmpty` / `collapsible` / `showBorder`; the renderer is unchanged. | | objectstack-ai#11661 | `component.test.ts:889` | "still refuses `title`, deliberately withheld as a second spelling of `label`" | Three more renderer-honoured keys are declared; `title` stays out because `label` already names the heading slot. | | objectstack-ai#9220, objectstack-ai#9249 | `component.test.ts:2078`, `:2142`, `:2899`, `:2908` | "Interactive Elements — element:filter (retired, no renderer)", the same for `element:form`, and "… through the kept map row (retired, no renderer)" twice | Both elements are retired at element grain: no renderer or reader in any repository. | | objectstack-ai#4001 | `component.test.ts:3325` | "batch A, unknown keys refused — the prescriptions, each backed by a measured producer" | Every authorable surface refuses unknown keys; spelled as stage 15 spelled `objectstack-ai#4001 batch D`. | | objectstack-ai#7751 | `component.test.ts:3419` | "object-* block props schemas — declared, so the props gate has a schema to dispatch" | Ruling A: the `object-*` block family enters `ComponentPropsMap`. | | `ui#6207` | `component.test.ts:3468` | "object-grid `data` takes the ViewDataSchema provider object — the two spec authorities converged" | Option A: `object-grid.data` converges on `ViewDataSchema`, and the bare array is refused. | | objectstack-ai#19228 | `component.test.ts:4499` | "row caps on the object-bound blocks — a bound view fills `limit` only when the authored one is not a usable cap" | Ruling D: one row bound per view; the component face keeps an undefaulted `limit`, which a bound view's page size fills whenever the authored one is not a usable cap (the gate's `!isUsableRowLimit(authored)`). | | objectstack-ai#4614 | `dashboard.test.ts:419` | "date-range preset vocabulary — one source, in the spec" | Ruling A: the preset names move into the spec as their one source, and a date filter's default is checked against them. | | objectstack-ai#16458 | `dashboard.test.ts:850` | "control — `columns` still declares no default … (the renderer infers it from widget spans)" | Item 4 was not landed: a `.default(12)` would retire the renderer's span inference and switch every auto-flow dashboard to the positioned grid. | **Subject back in words** (15 literals): "the objectstack-ai#5068 gate" becomes "the props gate" (`component-reference-rail.test.ts:34`); "the three objectstack-ai#18305 blocks" / "object blocks" become "object-map / object-gantt / object-tree" (`component-type-vocabulary.test.ts:88`, `component.test.ts:4295`); the four "objectstack-ai#11661 keys" titles name `defaultCollapsed` / `icon` / `description` (`component.test.ts:832`, `:848`, `:860`, `:875`), because id-free sibling titles already say "the section presentation keys"; "objectstack-ai#18639 scope fences" becomes "scope fences of the `columns` widening"; "the twin of the objectstack-ai#14406 census pin" becomes "the twin of the census pin that no door refuses the rule array"; "the objectstack-ai#7750 specimen shape" becomes "the my-work specimen shape"; the four `objectstack-ai#5011 —` prefixes in `dashboard-compareto.test.ts` become `compareTo —` where the title needs a subject, and go where it already has one (`:61`); "the words objectstack-ai#5042 measured" becomes "every word authors were measured spelling … reaches …". **Dropped where already stated** (63 literals, 69 ids). A number goes only where the title already says its decision. Examples: "ComponentPropsMap[\"record:alert\"] (objectstack-ai#8744)"; "user:profile is not author-placeable (objectstack-ai#14159, ruling B)"; "ai:chat_window is retired, refused by name (objectstack-ai#21504)"; the six "(objectstack-ai#6276)" picker titles, each already naming what the declaration does; "the four `object-*` `sort` doors — one sort orthography, the array (objectui#8221, decision batch objectstack-ai#77, option B; objectstack-ai#18305)"; the `[objectstack-ai#4876]`, `[objectstack-ai#5010]`, `[objectstack-ai#17779]`, `[objectstack-ai#20958]` and `[objectstack-ai#21293]` prefixes on `dashboard.test.ts`, each in front of the rule it names; "drill branch (objectstack-ai#5022): …", whose sibling labels already read "… branch: …". `批 17` stays as a batch label in stage 18's form, and `ADR-0021` / `ADR-0049` style citations are untouched. **No file is renamed.** None of the 7 file names carries a number. ## Readers - **Test-name filters:** none. No tracked script, workflow or package config passes `-t` / `--testNamePattern` (the hits are `mapfile -t`, `docker build -t`, `type -t`, `lsof -t`, and a preflight's option vocabulary). - **Snapshots:** none. No `__snapshots__` directory is tracked under `packages/spec`, and none of the 7 files calls a snapshot matcher. - **Projects:** all 7 files run in the `local` project; `packages/spec/vitest.repo-tests.json` lists none of them. - **By substring:** every old literal, its id-bearing fragment and a window around each id (301 needles) was searched with `git grep` at the base, across the tracked tree outside its own file. No gate, doc, filter, snapshot or `scripts/check-*.mjs` self-test reads one. The 6 hits: - a code comment in `lint/src/validate-component-props.test.ts:540` ("the objectstack-ai#7750 specimen shape"); - a QA checklist `source` entry, `docs/qa/platform-checklist/areas/dashboards.json:406`, which anchors on `dashboard-compareto.test.ts#dashboard` and describes it in its own words ("objectstack-ai#5011 — compareTo converged on …"). The checklist gate looks up the `dashboard` symbol, which this PR leaves in the file, and reads none of the titles. `pnpm check:platform-checklist` exits 0 at the head; - a sibling title in `service-analytics` (`dataset-compare-dimension-resolution.test.ts:74`, "objectstack-ai#5011 — …"); - three hits on one same-id title in this card's later `ui/` stage, `ui/view.test.ts:4236`, the visibility twin of the message-order describe, citing objectstack-ai#6416 / objectstack-ai#6619. ## Text-only proof Stage 10's scratch tool (`textonly10.cjs`, md5 `d5e4801dbb4329ab1984da91e92fc47c`) compares base and head file by file on three legs: 1. **Skeleton:** the full AST, with string pieces masked. It must be identical. 2. **Comments:** every comment, byte-equal. 3. **Strings:** each changed string leaf must sit in a test-call title position or on a declared line, must carry a tracker id before, and must carry no `#` plus digits after. This stage declares one line, `dashboard.test.ts:205`. - **Result:** 7 of 7 files SAME on all three legs, with the per-file counts predicted in writing before the run. - **Totals:** 99 changed string leaves in 99 literals: 98 titles and 1 declared. The diff's `+` and `-` lines are exactly the 99 planned lines as multisets, and every file keeps its line count. `dashboard-chart-structure-refusal.test.ts` is untouched. - **Controls (13 of 13 as predicted, on scratch copies, each anchor hit once):** identifier rename DIFF; numeric literal DIFF; comment edit COMMENT DIFF; a non-title string given an id VIOLATION; a rewritten title given a new id VIOLATION; a title that was id-free at base edited VIOLATION; one title reverted to base SAME; an `it.each` row given an id VIOLATION; an undeclared `orderPin` label changed VIOLATION; a title re-split into a `+` chain DIFF; the declared label reverted to base SAME; the declared label given a new id VIOLATION; a kept colour literal edited VIOLATION. The first run predicted VIOLATION for the declared-label revert: putting `(objectstack-ai#5022)` back where it was reproduces the base text exactly, so SAME is the right answer, and a control that gives the label a new id was added. That run read 11 of 12. - **Templates and tables:** no `.each` title, `%s` / `$name` placeholder or table row changes. **Test counts:** the 7 files were run at the base, in a separate base worktree, and at the head, with `--project local --project repo`. Both sides read 677 tests in 7 files, all passed, with the same count and status sequence per file in 7 of 7. 469 full test names change, and each changed name equals the base name with the planned replacements applied (0 mismatches). One full name repeats 5 times on both sides: an `it.each` row in `dashboard.test.ts` whose printed name is cut at the same point for 5 rows. Only its describe prefix changed. ## Changeset: `skip-changeset` Measured, not assumed: - `npm pack --dry-run` of `@objectstack/spec` lists 2068 files. 0 of the 6 touched files are in it, and no `*.test.ts` at all. The controls `src/ui/component.zod.ts` and `dist/index.mjs` are in it. - In the built `dist/`, a new phrase and an old literal each read in 0 files. The control `Unrecognized key` reads in 42. So this PR publishes nothing, and no changeset is added. ## Verification (at `ec96327761`) - `pnpm turbo run build` over all packages: 71 / 71, through the shared verify lock (`VERDICT command-exit 0`). - `@objectstack/spec`: - `vitest run --project local`: 618 files, 18450 passed, 1 todo. - `typecheck`: exit 0, including `check:test-typecheck` (52 files / 246 errors / 135 pinned signatures held). Its program holds all 7 group files, counted with `tsc --listFilesOnly -p tsconfig.test.json`. - `check:generated`: all 15 generated artifacts up to date, against the `dist/` the build above wrote. - **Gates:** `dispatch-gates --commands` derived 79 families, the same set as stage 20, and all 79 exit 0. `--ran` reconciles: 79 derived, 79 run, 0 NOT-MEASURED, 0 UNRUN, every family with its exit code recorded. The five roster families whose rosters sit under a touched directory were also run, and each exits 0: `check:meta-url-spelling`, `check:spec-changes`, `check:authz-resolver`, `check:error-code-casing` and `check:filter-alias-parity`. So does `check:platform-checklist`, for the checklist entry above. - **ESLint, a proven narrowing:** `--no-inline-config` over the 7 files reads 0 errors and 0 warnings. The population comes from ESLint's own config: 7 configured, 0 ignored. No file sets `parserOptions.project` or `projectService`, so no untouched file's verdict can move. - `check-governed-merges --test`: NOT governed, 198 changed lines (+99 / -99). - A control-byte scan over the 6 changed files finds none. - **Review round 1, at `69ea423302`:** two titles reworded, one line each: the row-cap describe (`component.test.ts:4499`) now states the gate's guard, and the offset-alias title (`dashboard-compareto.test.ts:160`) reads straight. Text-only proof against the base: 7 of 7 SAME, 99 changed leaves (98 title, 1 declared). The 7 files at base and head: 677 / 677 passed, count and status sequence identical in 7 of 7, 469 changed full names, 0 mismatches against the plan. ESLint over the 7 files: 0 errors, 0 warnings. The group census still reads 4 / 4. `typecheck` exit 0, and `check:nul-bytes` OK. The derived gate set is the same 79 families; they were not re-run for a two-literal change. ## `main` since the base Re-fetched just before this PR opened, `origin/main` was four commits past the base (`cab6396715`: objectstack-ai#21875, objectstack-ai#21896, objectstack-ai#21893, objectstack-ai#21900). None touches any of the 7 files. objectstack-ai#21893 touches 8 `packages/spec` files under `automation/` and `migrations/`, and the census over `packages/spec/src` at `866683f96f` (after objectstack-ai#21893) still reads 764 / 807 with no file moved, so the merged tree reads this PR's 665 / 702. objectstack-ai#21900 touches two more `packages/spec` files, both non-test migration entries, which this census does not count. So `main` was not merged. `git merge-tree` onto `cab6396715` is clean. ## Acceptance notes - **The four colour literals** stay, as above. They are the only digit-only hex colours in the census, so the card's end state will read them unless a later stage changes the fixture values, which would be a fixture change rather than a text change. - **Same-id test titles in this card's later stages** go with those stages: 21 lines in `packages/spec/src`, for example `system/i18n-resolver.test.ts:1982` ("(objectstack-ai#20940)"), `ui/page.test.ts:696` ("(ui#6206-B, objectstack-ai#15442)"), `ui/view-strictness-batch18.test.ts:91` ("objectstack-ai#4001 批 18 — …"), `ui/view.test.ts:4236` ("(objectstack-ai#6416 / objectstack-ai#6619)") and `ui/view.test.ts:4793` ("(objectstack-ai#19228)"). - **Same-id test titles in other packages** are their lanes' test-string shares. A search of `describe` / `it` / `test` lines outside `packages/spec/src` finds 40 lines citing ids this PR handled, in 10 packages: `lint` 25 (8 files), `service-analytics` 3 (1), `cli` 2 (1), `platform-objects` 2 (1), `plugin-audit` 2 (2), `plugin-security` 2 (2), and one each in `plugin-sharing`, `rest`, `service-automation` and `spec/scripts`. Examples: `lint/src/validate-component-props.test.ts:783` ("… are dispatched (objectstack-ai#8744)"), `service-analytics/src/__tests__/dataset-compare-dimension-resolution.test.ts:74` ("objectstack-ai#5011 — …"), `rest/src/meta-types-schema-titles.test.ts:131` ("objectstack-ai#16458 — …"). The two `[objectstack-ai#6206]` hits in `plugin-security` and `plugin-sharing` cite objectstack#6206, a different record from `ui#6206`. - **Code comments still carry ids** in these files and their sources, for example the `objectstack-ai#9198 tombstone` comment in `component.test.ts`, the `objectstack-ai#19228` header above `component.test.ts:4499`, and the `objectstack-ai#6416` / `objectstack-ai#5955` docblock above `dashboard.test.ts:163`. Comments are not this card's share, and none is touched here. --- _Generated by [Claude Code](https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #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 #20460 landed without touching it) andmeta-spelling/manifest-collection-spelling.ts(the census hand-over, comment 5882628946 on #20234).ui/view-grouping-query.tsis excluded because #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 saysPart 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 #19123). That is 87 comment sites: 84 re-anchored and 3 respelled.
origin/mainhistory 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.ui/view.zod.ts:3634now readsobjectui#6110 + objectui#6111, andui/component.zod.ts:3479and:4140now readobjectui#8221's PR objectui#8758. Each second number answers 404 here, and the sentence attributes it to objectui (objectui REST:issues/6111200,pulls/8758200, 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:13andui/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 #Nis 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/Nwithout following redirects, for every distinct number cited in the population. The population is:CITATION_REandNON_CITATION_HEADSat the base, kept when the qualifier is none,objectstack,objectstack-ai/objectstack,framework,pre-orpost-;Pre-,POST-,Framework);summonheads.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
#16862,#16847and#17698. The dead controls were#16714,#16715and#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.487a7846df, probed 2026-09-29T02:57:01Z to 02:59:36Z83e39641d0, probed 2026-09-29T03:15:00Z to 03:18:06ZThe 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 #20554 (
199002b3e4) landed the closed qualifier set while this stage ran, and it readsobjectui PR #8758as objectui's number. Re-run with that gate's ownextractCitationsandnamesThisRepository, the same population reads 108 dead sites before and 21 after, all 21 test strings. The difference is exactly the threeobjectui PR #8758prose 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.
data/filter-subtree-provenance.tsmeta-spelling/manifest-collection-spelling.tsui/action-param-carryover.test.tsui/action.test.tsui/action.zod.tsui/bulk-action.test.tsui/bulk-action.zod.tsui/component-element-navigation-17987.test.tsui/component-type-vocabulary.test.tsui/component-type-vocabulary.tsui/component.test.tsui/component.zod.tsui/dashboard.zod.tsui/expression-bindable-text-keys.test.tsui/expression-bindable-text-keys.zod.tsui/form-select-option.test.tsui/index.tsui/interaction-config-retirement.test.tsui/react-blocks.test.tsui/react-blocks.tsui/view-form-features-root.test.tsui/view-metadata-schema.test.tsui/view-submit-redirect-url.test.tsui/view.test.tsui/view.zod.tsui/widget-i18n-retirement.test.tsui/alone went from 106 dead sites in 24 files to 24. The other 107ui/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.
#5970action.zod.ts:819,action.test.ts:268,:304,:35497e7e3caa:ActionSchema.visible/disabledspeak one condition shape;visiblegains its boolean arm. Stage 1's anchor for the same number#6276component.zod.ts:34,:165,:568,:2455,:2546,:2554,component.test.ts:212678f0be872: declareselement:record_picker's flatsort/limiton the #5611 rule (maintainer ruling 2026-08-08, direction A). It wrote:34,:2455and the "enumerate by the renderer's read pattern" lesson#8794,#8836filter-subtree-provenance.ts:130,:131,:1561850ebbb0: corrects the reuse-safety claim from the survey and pins the invariant. It wrote:131itself. Stages 1 and 3 gave both numbers this anchor#9933view.zod.ts:2517,:5060,:5086,:5118,:5513,view-metadata-schema.test.ts:398,:410d5552ca13: admitscolumnStateas an explicitly runtime-only view-overlay key, rejected by name at every authoring door. It wrote "explicitly out of #9933's scope"#9972component.zod.ts:2213,component.test.ts:382,:3565; also:3612's#9881/#9972, a slash-joined spelling the grammar does not read60e0f900a: records the live read point ofpage:tabsitems[].iconand its accept-pin. It wrote the:382header#10194manifest-collection-spelling.ts:71(pre-#10194)2306a765c:/meta/themeand/meta/analytics_cubestop 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#10274component.zod.ts:2304,component.test.ts:308,:405,:3591,:3603,:3612d1ba685ec: 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#10485index.ts:51,interaction-config-retirement.test.ts:116,widget-i18n-retirement.test.ts:112,manifest-collection-spelling.ts:6735ad101bc: retires thethemescarrier andThemeSchemawhole (ruled B, 2026-08-21; ADR-0049 stays cited). Stage 1's anchor#11284react-blocks.ts:39,:94,:109,:295,react-blocks.test.ts:1395383fa670: the react tier converges on the metadata-tier vocabulary, deprecate-first. Its changeset heads "(#11284, maintainer ruling 2026-08-23)"#11350index.ts:105ece4dad31: 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'skernel/index.ts:53#11507component.zod.ts:1465,component.test.ts:272688b9d749a: declaressys_activity.typean open, author-extensible vocabulary (maintainer ruling 2026-08-24, direction 4). Stage 3's anchor#11658component.zod.ts:1464,component.test.ts:27251a6a19c31: opensRecordActivityProps.typesto author-contributed kinds, executing that ruling. Stage 3's anchor#11703action.zod.ts:399to:400,:460,action-param-carryover.test.ts:12to:135cb62d88b:clone_permission_setcarries all five copied facets; its params list had silently dropped three. The lines now name "the silent-drop shape commit 5cb62d8 fixed"#11753action.zod.ts:66,:390,:398,:409,action-param-carryover.test.ts:10e4e51b0a: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 thevisible: falsemeasurement:398names#12194view.zod.ts:4838311433f6b: declares the metadata item-name grammar (QUALIFIED_ITEM_NAME_PATTERNamong it) and refuses it at the publish door. Stage 2's anchor#12868view.zod.ts:2938,:2966,:3179,:7087,form-select-option.test.ts:4c459da6bc: narrows the per-optiondefaultkey out of the form-view options vocabulary. Its changeset records the ruled census:2966cites ("measured ZERO occurrences"). Stages 3 and 4's anchor#12950component-type-vocabulary.ts:4,component-type-vocabulary.test.ts:4,:101225e7690f: createdcomponent-type-vocabulary.ts; its message records the readiness read:101pins (global:searchandglobal:notificationsstay declared)#13156view-form-features-root.test.ts:70,view-submit-redirect-url.test.ts:110fd289be45: strips tracker ids from function-declaration-built refusal prose. It wrote both lines. Stage 3's wording ("commit fd289be's strip")#13670expression-bindable-text-keys.zod.ts:728c6a7fc0b: recordstext.valueas deliberately omitted; its message states the ruling thattext's evaluation channel iscontentalone#13672expression-bindable-text-keys.zod.ts:89,.test.ts:65,:118e854a531a: narrows thebuttonrow to the spelling its key reaches, and recordsaction:buttonandui:buttonas deliberately out#16626component.test.ts:233630b099078: the objectui pin bump to53ded82bf7a4that ships objectui#7754's array-analytics lowering, the door the family waited on. The association is PR #16788's body (it names #16626 as the card it lands), and30b099078is that PR's merge commit; neither its message nor its diff names #16626 (the stage-3 #11065 precedent)#17987component.zod.ts:10,:4014,:4062,:4153,:4193,:5068,:5226,:5457,component-element-navigation-17987.test.ts:4e233db9db: declares element-levelnavigationonobject-kanban/object-calendarand givesobject-timelineitsComponentPropsMaprow, executing the objectui#8652 ruling (verbatimB)#18003dashboard.zod.ts:686#18177bulk-action.zod.ts:51,:169,:262,bulk-action.test.ts:61,:307adabccf5f:BulkActionParamSchemais strict and declaresdependsOn, executing decision batch #146 item 4, letter A#6111view.zod.ts:3634objectui#6111(not re-anchored): it is objectui's number#8758component.zod.ts:3479,:4140PR objectui#8758(not re-anchored): objectui's PR #8758, merged 2026-09-09No ADR or ruling-record file in
docs/adr/orscripts/adr-anchors/records the decision behind any of the 25 re-anchored numbers except #18003. ADR-0126 mentions #11703 and #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:git rev-parse --disambiguate, count 1, 23 of 23);git merge-base --is-ancestor, exit 0, 23 of 23); the control lege9584681a4also exits 0, and the repository is not shallow;0e4e51b0a,5383fa670,c459da6bc,225e7690f,e854a531a, and for [finding] The filter-subtree provenance negative holds only incidentally — nothing states or tests "a vouchable filter must not outlive its request", andgetReadFilterhands hosts the exact reusable shape #8836 in1850ebbb0).30b099078is the exception explained in the table.Wordings to check, each true of its commit:
filter-subtree-provenance.ts:130and:156read 「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:1465andcomponent.test.ts:2726read 「maintainer ruling commit 88b9d74 declared」: that commit landed the ruling (direction 4) as thesys_activity.typedeclaration.manifest-collection-spelling.ts:71reads 「the store-anything branch from before commit 2306a76」: before that commit,PUT /meta/theme/:namestored any JSON as success.Mechanical guard: no code token moves
The check compares leaf tokens with comments stripped, base
487a7846dfagainst 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.tsfiles, and every control mutates the head text in memory only.ui/index.ts): 0 files changed (exit 0).ui/view.zod.ts): 1 file reads DIFFER at token 19222 (exit 1).component.test.tstest 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
#9972atcomponent.test.ts:3612.Generated page
check:generatedproved one artifact stale:content/docs/references/ui/expression-bindable-text-keys.mdx, the projection ofexpression-bindable-text-keys.zod.ts's module docblock.check:generated --fixregenerated only that page, and a re-run readAll 15 generated artifacts are up to date. Its two changed lines are the:72and:89substitutions verbatim. No other docblock here projects into a reference page, and nothing underskills/**moved.Changeset
This change ships bytes, so a
patchchangeset for@objectstack/specis 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, whichfiles[]ships verbatim. The rewritten comments also reachdist:c459da6bcappears in 32 bundled.jsfiles and 2.d.ts;adabccf5fin 24.jsand 2.d.ts;d5552ca13and0e4e51b0ain 24.jseach;e233db9dband78f0be872in 2.jsand 2.d.tseach;Gates (head
1b885d3c27)pnpm check:issue-citations && node scripts/check-issue-citations.mjs, both under the grammar PR fix(scripts): check-issue-citations reads a qualifier only when it names a known repository, so pre-#N / post-#N are judged and framework#N is this repository #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".pnpm check:doc-authoringexits 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.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsat this head derived 112 families, and all 112 exit 0.--ranreports 112 run, 0 NOT MEASURED, 0 unrun. A fullturbo run buildof./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..changeset/orpackages/, 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-casingandpnpm check:filter-alias-parity. All exit 0.pnpm --filter @objectstack/spec run check:generated(derived) readsAll 15 generated artifacts are up to date, andcheck:docsreads226 generated files in sync.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).src/uithat read the touched files' source text or pin their lines: Test Files 16 passed (16), Tests 489 passed (489). They arescripts/{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.tsandsrc/type-alias-convention.pin.test.ts. Three more suites matched the reader scan and are not run here:scripts/build-schemas-check-mode.test.tsonly importsViewItemSchema(code the guard proves unchanged) and rebuilds schemas in a temp tree;scripts/def-key-collisions.test.tsnamesui/view.zod.tsonly in a comment;scripts/published-projection-choke-point.test.tsmatched onbuild-react-blocks-contract.ts, not a touched file. They are left to CI.pnpm --filter @objectstack/spec typecheck: exit 0, includingcheck: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.eslint --no-inline-config --format jsonover the 25 touched.tsfiles gives 25 files, 0 errors and 0 warnings. All 25 are in eslint's own population (isPathIgnoredis false for each, read through eslint's API).eslint.config.mjsnever enables type-aware linting (noparserOptions.project, which its own lines 327 to 328 state), so a comment edit here cannot move the verdict on any untouched file. The repo-widepnpm lintis CI's run.merge-treeof the head ontoorigin/mainf572a7eb3c, from a bare shared clone with no merge driver registered, exits 0 (2026-09-29T04:15Z).Acceptance notes
487a7846df, one commit past the claim's stamp6154165484(PR fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551, outside the surface).origin/mainthen moved three commits, and199002b3e4(PR fix(scripts): check-issue-citations reads a qualifier only when it names a known repository, so pre-#N / post-#N are judged and framework#N is this repository #20554) changedscripts/check-issue-citations.mjs, so the gate derivation read STALE TREE.origin/maindee9b26f6cwas 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 againstdee9b26f6cis exactly its 27 files.origin/mainhas since moved one more commit,1c761c0d71(PR test(create-objectstack,metadata): make two version-sensitive tests hold across the 17.5.0 version pass #20565, tests in two other packages), which touches none of them.manifest-collection-spelling.ts:71(pre-#10194). The same comment's first line,:67, cites#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.ui/is feat(spec)!: $empty joins FILTER_OPERATORS, and is_empty / is_not_empty lower to it (#20446) #20570 (filter: flipis_empty/is_not_emptyto$emptyand add it toFILTER_OPERATORS, once every compile surface answers it (the last step of ruling A on #20399) #20446's), on the excludedview-grouping-query.ts. The in-flightClaim:comments on the 12pm:dispatchedcards were read too: only filter: flipis_empty/is_not_emptyto$emptyand add it toFILTER_OPERATORS, once every compile surface answers it (the last step of ruling A on #20399) #20446's names aui/file (view-grouping-query.ts, excluded above).ui/, 14 sibling-qualified pairs leave the second number bare: 13 on one line (+,and,,,/or's PRbetween them) andcomponent.zod.ts:3478to:3479, split across a line break. In 3 of them the second number answers 404 here and the sentence attributes it to objectui (#6111once,#8758twice); 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:256andwidget.zod.ts:34. The PR fix(scripts): check-issue-citations reads a qualifier only when it names a known repository, so pre-#N / post-#N are judged and framework#N is this repository #20554 header measured,andandpairs 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.it/describetitles in 7 test files):#6276×6,#11658×3 and#11507×1 incomponent.test.ts;#9972incomponent.test.ts:411;#17987×4 incomponent-element-navigation-17987.test.ts;#18177×2 inbulk-action.test.ts;#9933inview-metadata-schema.test.ts:406;#11284inreact-blocks.test.ts:147;#11753inaction-param-carryover.test.ts:17;objectstack#11195inview.test.ts:3396. None is a Zod.describe()text, an exported string or a migration-entry field, so no form D site arises here.objectui PR #8758):view.zod.ts:2354,:2579andview.test.ts:426. They are not pairs, and the landed grammar reads them as objectui's.liveness/**notes, thewhystrings and thePROVENANCE_WAIVERSreason,rest-server.zod.ts, the heldanalytics*anddriver/turso.*files, the 2AGGREGATION_CASESnote strings, anddata/'s test strings and deliberate markers.packages/spec/srcis [finding] dead tracker citations outsidepackages/spec/srchave no carrier: #20234 sweeps only the spec tree, and PR #20554 makes 26 more visible (pre-#N/Pre-#N) in cli, drivers, metadata, objectql, plugins, runtime and types #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#18003, and ADR-0126 cites#11703and#11753; both are governed.packages/spec/scripts/strictness-ledger.test.ts:375,:380cite#9933, andcheck-objectui-pin-citations.tscites#10274and#9972.react-blocks.test.ts:140says the 2026-08-23 ruling was "recorded on-card". The card is gone, and the changeset of5383fa670(now cited on:139) records the ruling. The line holds no number, so it is left.packages/**/*.test.ts. The 11 touched non-test files are in its judging population.Generated by Claude Code