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
Conversation
…mes a repository Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…elling both ways Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…h number of its objectui pair Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift Check1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. 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 39e7f27f0ab383e155d6ccb5f98ad2d5d83e94d3 && git checkout 39e7f27f0ab383e155d6ccb5f98ad2d5d83e94d3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b05743433b7dfac9edb5e4cb92d6998d5aecff09 a3c14755f844c7989082ab67305c8a26620e2c78 && git checkout -B drift-repro b05743433b7dfac9edb5e4cb92d6998d5aecff09 && git merge --no-ff a3c14755f844c7989082ab67305c8a26620e2c78
node scripts/docs-audit/affected-docs.mjs --json b05743433b7dfac9edb5e4cb92d6998d5aecff09 |
Contract reviewServed-tier: Inputs read: card #20330 (body; comments ① Derived judgmentsThe accept set of
② Semver level
Clause-②: no. No authorable key is widened or narrowed; nothing an author writes changes. ③ Boundary flags
Deviations, each answered:
Out-of-scope findings, each answered or escalated:
Gate coverage on this head, read once (33 check-runs: 15 success, 3 skipped, 15 in_progress, 0 failure):
This record judges the contract. The landing precondition (every required context green) is the seat's read at enqueue time and is NOT presumed here: Implemented-by: VERDICT: PASS Generated by Claude Code |
… 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>
Fixes #20330
Clause-②: no
What changed
scripts/check-issue-citations.mjsread ANYword#Nas a repository reference, sopre-#12248,post-#6640,Pre-#N,POST-#NandFramework#Nwere classed cross-repo and never judged. Its qualifier is now a closed set. A token joined to#Nnames a repository only when it is anowner/repoform or a name in the newKNOWN_REPOSITORIEStable, matched case-insensitively. Any other prefix is prose, and the number after it is this repository's and is judged.One recogniser,
repositoryOf, is asked in all three places: by extraction, by the board's probe set (boardWanted) and by the classifier (namesThisRepository). The three can no longer disagree about a token.KNOWN_REPOSITORIESrows:objectstackandframework(both THIS repository),objectui,ui(an objectui alias, joined form only),cloud,hotcrm,hotcrm-heimao,os-tianshun-mtcandos-project-titanwind-ehr. Each row carries the measurement that put it in.objectui PR #10264,objectui #2670,cloud #2937andframework #2679read as their repository. The form is a known repository name, whitespace, an optionalPRorissue, then#N. The aliasuiis excluded from it, becauseUI #Nis ordinary English.objectui#6110 + #6111the second number stays this repository's. The convention is to qualify each number, and the one live site in the claim's surface is respelled:packages/spec/src/data/field.zod.ts:370now readsobjectui#6110 + objectui#6111. This is comment-only, with apatchchangeset.NON_CITATION_HEADSgainsOQ(ADR-0076 OQ#10, 10 sites) andPKCS(PKCS#11, 1 site). A hyphen joining a head to its#is now read as the same head (Prime-Directive-#12, 2 sites).PD#12(8 sites) was already covered by the existingpdhead once its candidate is refused.REMEDYtext now states the grammar that judged the author.A false red in the same seam, fixed because this change would have widened it
buildBoardprobed only UNQUALIFIED numbers, so a diff addingobjectstack#Nwas classified against a board that never asked about N. Reproduced on unmodified288611e3e5: I appendedobjectstack#20330(this live card) to a swept file and rannode scripts/check-issue-citations.mjs. It exited 2, readingboard: probed (0 citations)and[allocated-but-absent] ... objectstack#20330. Readingframework#Nas this repository would have inherited that false red on every site. The probe set is nowboardWanted, meaning every citation judged here. The self-test pins it throughprobeBoardover a stub. The blocking rule is unchanged: findings still exit 2.Measurements the design rests on
frameworknames this repository.git ls-remote https://github.com/objectstack-ai/frameworkanswered HEAD288611e3e5, identical toobjectstack-ai/objectstack. The controls diverged: a nonexistent name under the same owner exited 128, andobjectstack-ai/objectuianswered its own HEAD0eb9f36aca. The REST and web routes toframeworkanswered 403 from this session's proxy (bound to configured repositories), so git's rename redirect was the readable instrument.framework#N/Framework#Ntherefore read as THIS repository.288611e3e5, over the declared surfaces. There were 27 distinct candidates behind 1,655 sites:framework255,objectstack111,objectstack-ai/objectstack9,Framework2.objectui672,cloud213,hotcrm24,objectstack-ai/objectui12,ui11,objectstack-ai/cloud9,better-auth/better-auth3,os-tianshun-mtc2, and 1 each ofhotcrm-heimao,os-project-titanwind-ehr,objectstack-ai/objectos,objectstack-ai/atsandobjectstack-ai/hotcrm.pre-284,post-12,Pre-4,Post-4,PRE-1 andPOST-1.OQ10,PD8,Prime-Directive-2,PKCS1.uiis objectui.objectstack-ai/uidoes not exist, andui#6837,ui#6206andui#6207are objectui's records on its board (objectstack#6206answers 404, so it would have been a false death).objectui #2670is "Flow designer: render loop / parallel / try_catch as nested", cited fromloop-node.ts, andobjectui PR #4264diagnoses a path on the right side of==, cited besidePATH_SHAPED_LITERAL. This repository's same number is unrelated on every site. Thecloudsites could not be read (private) and follow their context. There were zero false positives.,andand: every cross-repo pair I could judge names THIS repository's second number.cloud#1013 and #10645is this repository's cliserveissue (4 sites),cloud#1020, #5233its org gate issue (6 sites),objectui#2561, #3021its lazySchema PR, andobjectui#3136 and #14492answers 404 on objectui./: mostly carries, but not always.objectui#3226 / #4827is this repository's [移交自 objectui] 登记 ADR-0087 D2 conversion 条目page-header-subtitle-alias(description→subtitle) #4827, a conversion entry handed over from objectui and cited fromconversions/registry.ts.+: exactly one distinct pair exists in the corpus, which is too thin to establish a convention.Census of the newly judged spellings
Taken with the gate's own
--census --jsonata3c14755f8against an enumerated board (184 pages, frontier #20551). The per-site transition comes from the gate's--listbefore and after the change. Dead meansallocated-but-absent. Four of the numbers (14657, 12998, 10194 and 8692) were re-probed directly and answered 404.pre-#Nframework#Npost-#NPre-#NPost-#NFramework#NPRE-#NPOST-#NOther readings, same run:
field.zod.ts:370. Four of them were base census deaths that were never deaths:objectui PR #8758three times, and the respelled#6111.OQ#10×10,PD#12/PD#10×8,Prime-Directive-#10/#12×2 andPKCS#11×1.288611e3e5: 38,109 judged. resolves 32,202 · resolves-as-pull 1,863 · cross-repo-unjudged 1,535 · allocated-but-absent 2,509.a3c14755f8: 38,088 judged. resolves 32,689 · resolves-as-pull 1,891 · cross-repo-unjudged 976 · allocated-but-absent 2,532.Verification
All gates below ran at
a3c14755f8, the branch head.node scripts/check-issue-citations.mjs --self-testexits 0 with 114 cases across 8 batteries (base: 73 cases across 7). The new batteryqualifier(floor 40) pins every spelling both ways: lit on a live number and a FINDING on a dead one forpre-post-Pre-Post-PRE-POST-, and forframeworkFrameworkobjectstack-ai/frameworkobjectstack. It also pins cross-repo even when dead forobjectuiOBJECTUIuicloudhotcrmobjectstack-ai/objectuibetter-auth/better-auth, and covers:PR #NandUI #NNOT being the prose form;boardWantedand a probed-board resolution ofobjectstack#20330;live-corpusgains a pin that every qualifier the live corpus keeps names a repository.scripts/ablation-replace.mjs, each landing on disk with the anchor count 1 → 0 and the blob changed, each red on its own case, and each restored with blob equal to HEAD8b6cf12653ddandgit diff HEADempty:pre-is prose".frameworkrow is renamed. It reds on "Frameworkis THIS repository".Prime-Directive-#12".objectui PR #Nnames objectui".OQhead is removed. It reds on "ADR-0076 OQ#10".node scripts/check-issue-citations.mjs, as CI runs it): exit 0 on this branch. It judged the respelled line's 2 citations, bothcross-repo-unjudged.git diff HEADempty). I appendedpre-#12248(dead) andframework#20330(live) topackages/cli/src/commands/generate.tsand ran the diff verdict twice:pre-#12248isallocated-but-absent,framework#20330resolves on a boardprobed (2 citations), and the respelled pair stays cross-repo.288611e3e5gate, from a temporary copy, exits 0 with all 4cross-repo-unjudged. That is the hole this closes.--census --json): exit 0. It is report-only and never fails.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 97 commands, and all 97 ran. 96 exit 0, includingpnpm check:pm-dispatch-gates, whosedispatch-gates.mjs --self-testpasses 1,976 cases. That self-test pins this file's:204 local-envdeclaration line, and every edit here stays below it or is line-neutral.--ranreconciliation reads "97 derived famil(ies) accounted for — 96 run, 1 NOT-MEASURED (1 DERIVED from a recorded exit 3)".check:dual-build-cjs-loads. Reason: it loads every package's built CJS entry, and this box holds no dist for 80+ packages. The diff changes no emitted code.@objectstack/specwas rebuilt, and its entry gates (check:browser-reachable-entries,check:entry-nameability) exit 0. CI's Lint and Repo Gates owns this one.pnpm --filter @objectstack/spec typecheckreturned VERDICT command-exit 0. See the report comment for the test run.Acceptance notes
packages/spec/src/**and is input for the staged sweep on packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234:packages/spec/src/meta-spelling/manifest-collection-spelling.ts:71pre-#10194. The other 26 are outsidepackages/spec/src/**, and no card names them:packages/cli/src/commands/generate.ts:1842pre-#14657packages/cli/src/utils/storage-driver.ts:206pre-#6345packages/drivers/driver-sql/src/schema-drift.ts:2458,:2474pre-#12998packages/drivers/driver-sql/src/sql-driver.ts:3872,:16545pre-#17590packages/drivers/driver-sql/src/sql-driver.ts:18285,:18324pre-#12998packages/drivers/driver-sql/src/sql-driver.ts:20095Pre-#12380packages/drivers/driver-turso/src/remote-transport.ts:2624pre-#12380packages/lint/src/validate-searchable-fields.ts:312pre-#8404packages/metadata-protocol/src/protocol.ts:2793pre-#10888packages/metadata-protocol/src/seed-loader.ts:1947pre-#11674packages/objectql/src/action-governance.ts:339pre-#14423packages/plugins/plugin-auth/src/auth-manager.ts:5629pre-#14762packages/plugins/plugin-security/src/bootstrap-platform-admin.ts:266,:630pre-#8692packages/plugins/plugin-security/src/per-organization-catalog.ts:314pre-#8692packages/plugins/plugin-security/src/permission-set-projection.ts:482pre-#6483packages/plugins/plugin-sharing/src/backfill-sys-record-share-organizations.ts:5pre-#14484packages/runtime/src/domains/mcp.ts:364pre-#8726packages/runtime/src/sandbox/body-runner.ts:548,:735pre-#14758packages/runtime/src/sandbox/script-runner.ts:440pre-#14758packages/types/src/driver-error-classification.ts:608pre-#13324packages/types/src/node.ts:1428pre-#10943objectui#6110 + #6111pair outside the claim's surface. It still reads#6111as this repository's (404 here), so these are existing census deaths:packages/spec/src/ui/view.zod.ts:3634(for the packages/spec/src: 1,277 comment lines still cite 170 deleted tracker numbers (1,295 sites) — the staged remainder of ruling C+D on #19123, measured by PR #20226 #20234 sweep),packages/metadata-core/src/form-predicate-root-policy.ts:14,:120and:205, andpackages/metadata/src/plugin.ts:910. Each respells toobjectui#6110 + objectui#6111.REPO#N / #Mpairs name the qualifier's own second number, which resolves here as an unrelated record. Examples:hotcrm-heimao#35/#40/#59,objectui#2715/#2717,objectui#2711/#2722,objectui#4648/#4901,objectui#5018 / #6469,cloud#957 / #962andcloud#930/#944. No gate can see these, because they resolve. The convention in the refusal text (qualify each number) is the remedy when someone next touches the line.objectui PR #10264specimen.packages/spec/src/api/export-job-family-retirement.test.ts:25sits on a DEFERRED surface (packages/**/*.test.ts), andsurfaceForanswersnullfor it. The census never judged that site. The prose form it names is now read correctly wherever the census does look.--censusreadenumerated (126 pages)with frontier runtime publish gate:CONTEXT_STACK_KEYSis a hand-written literal, not the derivation its docblock claims -- completeness is still forgettable #13977, against 184 pages and fix(trigger-api,service-automation): refuse an api flow with no per-flow secret, at arm time and at registration (#20529) #20551 on the re-run minutes later, and reported 9,160never-issuedphantoms.enumerateBoardstops at the first page withoutrel="next"and trusts the maximum it saw as the frontier. This diff does not touch that code. The diff-scoped verdict enumerates only past 400 distinct numbers. Recorded, not filed; the seat decides.NON_CITATION_HEADS(two rows) andnonCitationHead(the hyphen) are grammar next to the qualifier, not the qualifier itself. They are here because closing the qualifier made those 13 ordinal sites judged citations of this repository's chore: version packages #10, Migrate documentation site to Fumadocs with monorepo structure and shared content #11 and Add comprehensive test suite for Zod schema validation #12, which is false. No gate was added, and the diff-scoped blocking rule is unchanged.Generated by Claude Code