Skip to content

fix(spec): probe for the checked-in sdui.manifest.json before prescribing how to make one - #16847

Merged
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16715-parity-manifest-prescription
Sep 8, 2026
Merged

zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16715-parity-manifest-prescription

Conversation

@claude

@claude claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

Fixes #16715

check:react-declaration-parity's refusal text was a constant asserting that "this
repository contains no copy of it" and sending the reader off to build objectui and dump a
manifest in a browser. That has been false since #13446: sdui.manifest.json is checked in at
the repository root, scripts/sdui-manifest.record.json pins it to .objectui-sha, and
lint.yml runs this gate against it on every PR. The prose kept its old certainty at the exact
moment a reader decides whether the gate can run at all — and twice (on #16489 / PR #16697, and
again on PR #16777) a dev believed it and filed a locally-runnable gate as
EXTERNAL_INPUT_REQUIRED / NOT MEASURED. One of those declarations reached a deliverable.

What changed

1. The prescription is now a function of a probe, and both branches exist.
packages/spec/scripts/manifest-prescription.ts (new) holds the text and a plain filesystem
probe on the repository root, resolved from the script's own location (packages/spec/scripts/

  • three levels up) — not from process.cwd() and not from an env var, because the reader who
    needs this is precisely the one whose cwd is wrong.

  • manifest present (this repository, today): a paste-ready block — a cd to the root, then
    the exact command CI runs, MANIFEST="$PWD/sdui.manifest.json" .... It also says in words
    that a non-zero exit from here is a reading, not an unavailable input.

  • manifest absent: the existing objectui-dump path, unchanged in substance, except that it
    now names the path it actually looked at instead of making a claim about repositories in
    general.

The false sentence was not simply deleted: a reader who loses the false path and gains
nothing still does not know what to type.

2. The stale header comment took the same fix. The card body pointed at the
MANIFEST_PRESCRIPTION docblock; the seat's field-evidence comment pointed at the header
comment ~225 lines above it. Both readings were right — the same falsehood was written twice
in one file, and only one of the two would have been fixed by following either line number.
Located by text, both are corrected.

3. Pin hardening on object-refinement-check-exports.test.ts leg 3. It counted mount points
by name without asserting the name is declared exactly once in the module, so a shadowing
local binding could satisfy it. A declarations() helper now asserts the card's pattern
(^\s*(export )?function checkX\b, multiline) matches exactly once, per name, in all three
modules. Measured: four names, one declaration each — this closes a residual hole in the pin,
it does not fix a live shadowing.

PM mechanism assumptions — all four measured

Exit status captured before any pipe in every case (cmd > log 2>&1; ex=$?).

Assumption Verdict Evidence
The two line numbers disagree and may be stale Both right, about different sites :305-308 = the MANIFEST_PRESCRIPTION array (really at 304); :80-92 = a header comment carrying the same falsehood. Located by text: grep -n "contains no copy of it|carries no manifest" gives lines 80 and 307. The CI line number in the card (lint.yml:5645) and in triage (:5698) are both stale too — it is at 5825 today.
CI runs the gate against the committed manifest Confirmed lint.yml:5825 — MANIFEST="$PWD/sdui.manifest.json" pnpm --filter @objectstack/spec check:react-declaration-parity --baseline react-declaration-parity.baseline.json --strict, in job typecheck-consumers. git ls-tree origin/main --name-only | grep sdui.manifest returns sdui.manifest.json (66910 bytes). Positive control on the same grep of lint.yml: runs-on = 6 hits, so the single hit is a reading.
Locally runnable; only MANIFEST separates exit 1 from exit 0 Reproduced on the base (f89812e4d) leg A, no MANIFEST: exit 1, the old refusal. leg B, same head, MANIFEST="$PWD/sdui.manifest.json": exit 0, no new DECLARATION divergence vs accepted baseline.
After the change the printed command actually works Confirmed The block was sliced out of the gate's own stderr programmatically (not retyped), written to a file, and run with cwd=/ to prove the cd carries it: exit 0, no new DECLARATION divergence vs accepted baseline.

Reverse verification — one-shot, both legs restored

Both ablations ran from the committed state, mutated on disk, proved the mutation landed by
anchor counts (an editor's exit code is not landing evidence), ran, and restored via
git checkout HEAD -- path under a trap ... EXIT INT TERM with absolute paths. git diff HEAD
and git status --porcelain were both empty afterwards, each time. No test artefact remains.

A1 — remove the probe (checkedIn: repoManifestIsCheckedIn(REPO_ROOT) becomes
checkedIn: false, i.e. the pre-fix world). Landing proof: probe-call count 1 to 0, injected
marker 0 to 1; on-disk blob 7b1b5a21 vs HEAD blob b7c17775.
Result: 2 failed / 28 passed — the real gate prints the checked-in branch in THIS repository
and a missing MANIFEST is a failure, not a skip. Discriminating: 28 unrelated legs stayed green.

A2 — inject the exact hole the card names: a nested, shadowing
function checkPageSourceCompleteness in page.zod.ts. Landing proof: on-disk declarations 1 to
2, .superRefine attachments unchanged at 1; blob 7c077fa7 vs HEAD 496bbb40.
Result: 1 failed / 94 passed, AssertionError: expected 2 to be 1 — and the only failure.
That 94-green is the point: every pre-existing leg (toContain('export function ...'),
attachments(...), legs 1/2/4) accepts the shadow. The new line is the whole added discrimination.

Verification

Command Exit
pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 scripts/check-react-blocks-declaration-parity.test.ts src/ui/object-refinement-check-exports.test.ts 0 — 2 files, 125 passed
pnpm --filter @objectstack/spec typecheck (tsc --noEmit + check:scripts-typecheck + check:test-typecheck) 0
pnpm --filter '@objectstack/spec^...' --filter @objectstack/spec build 0
node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config (the full repo, not narrowed) 0
MANIFEST="$PWD/sdui.manifest.json" pnpm --filter @objectstack/spec check:react-declaration-parity --baseline ... --strict 0
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --ran ... 0 — 73 derived, 73 run, 0 UNRUN

All at HEAD = the final commit on this branch. Every heavy run went through
scripts/pm/os-verify-lock.sh; verdicts read from its VERDICT command-exit line.

Coverage is proved, not assumed: tsc --listFiles puts manifest-prescription.ts,
check-react-blocks-declaration-parity.ts and ...parity.test.ts in the
tsconfig.scripts.json program (908 files) and object-refinement-check-exports.test.ts in the
tsconfig.test.json program (2286 files).

Two of the 73 are NOT MEASURED here, not green: check:dual-build-cjs-loads and
check:type-check-debt each exited 3 (PREREQUISITE NOT MET, distinct from a finding's 1) —
both need the whole workspace built (turbo run build --filter='./packages/*' ...), which is
CI's run, not this PR's. check:doc-formula-expressions also exited 3 at first; building
@objectstack/formula + @objectstack/lint cleared it and it then exited 0.

Changeset

skip-changeset — measured, not assumed. @objectstack/spec publishes
dist · json-schema · liveness · prompts · llms.txt · README.md · src/**/*.zod.ts · CHANGELOG.md · api-surface · spec-changes.json. packages/spec/scripts/** is on none of them, and a .test.ts
under src/ui/ is not matched by src/**/*.zod.ts. After a full build, grepping every published
path for this diff's symbols gives 0 files for repoManifestIsCheckedIn,
manifestPrescription, REPO_MANIFEST_RELATIVE and declarations(src, — against a positive
control, checkPageSourceCompleteness, which hits 24. Nothing published moves.

⚠ This overrides the dispatch word, which asked for a changeset; flagged rather than
silently chosen. Clause-②: no still holds: no accept set and no public surface moved
(check:api-surface, check:authorable-surface, check:published-files, check:dts-closure,
check:export-origins all green).

验收备注

The triage seat's acceptance criteria, copied verbatim as its comment asks (Chinese rulings are
quoted, not translated):

  1. 修法取卡面第二条更好:先探测根目录 sdui.manifest.json 是否存在,存在时打印可直接粘贴执行的命令(含 MANIFEST="$PWD/sdui.manifest.json"),不存在时才走现在这段"去 objectui dump 一份"的路。⛔ 不要只把「contains no copy」删掉了事 —— 那样读者仍然不知道该敲什么。
  2. 两个分支都要有断言:文件在时给出可执行处方、文件不在时给出生成处方。只测其中一条,就是把今天这个 bug 换个方向再造一次 —— 今天的 bug 恰恰是"只有一条分支、且它假设了错误的世界"。
  3. 处方里给出的命令必须真的能跑通:PR 里贴一次照抄该命令后的 exit 0 输出。⛔ 不要贴自己手敲的等价命令 —— 要验证的正是"照着文案敲"这件事。
  4. 顺带那条 pin 加固 ... 随本卡一并做:同包、同一次验证、一行断言,卡面已给出形状。⛔ 不要另开卡。复审已实测当前四个名字各只有一处声明 ⇒ 这是补洞不是修故障,PR 里请这样写,别写成"修了一个遮蔽 bug"。

All four are met above. On (4) specifically: this is a hole in the pin, not a shadowing bug —
declarations() returns 1 for all four names on this tree and must stay green.

Neighbour card #15367 — reported, not touched

The dispatch answered fold-or-serial as SERIAL (#15367 carries finding with no pm-state, so
it is ungraded and may not be folded). Nothing here closes, re-grades or edits it; #15367 remains
open
. With evidence, on whether this PR subsumes its symptom:

Noted, not filed

Not for the queue

Draft on purpose. ⛔ Do not flip out of draft, enqueue, or arm auto-merge from this PR — the
dispatch reserves that.


Generated by Claude Code

…bing how to make one

`check:react-declaration-parity`'s refusal text was a constant asserting "this
repository contains no copy of it" and sending the reader to build objectui and
dump a manifest in a browser. That has been false since #13446: the manifest is
checked in at the repository root and lint.yml runs this gate against it with
MANIFEST="$PWD/sdui.manifest.json". Twice measured, a dev read the prose, filed
the gate as EXTERNAL_INPUT_REQUIRED / NOT MEASURED, and one such declaration
reached a deliverable — on a head where setting that one variable gives exit 0.

The text is now a function of a filesystem probe on the repository root resolved
from the script's own location. When the manifest is there the reader gets a
paste-ready block (a `cd` to the root, then the exact command CI runs); only when
it is genuinely absent does the objectui dump path appear, and it names the path
it looked at rather than making a claim about repositories in general. Both
branches are pinned — one branch tested is how this defect was built.

Also hardens the third leg of object-refinement-check-exports: it counts mount
points by name without asserting the name is declared exactly once, so a
shadowing local binding could satisfy it. Measured today: four names, one
declaration each — a residual hole in the pin, not a live shadowing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@claude claude Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Sep 8, 2026
@github-actions github-actions Bot added the size/m label Sep 8, 2026
@github-actions

github-actions Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

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

@claude

claude Bot commented Sep 8, 2026 •

Copy link
Copy Markdown
Contributor Author

Contract review — VERDICT: PASS WITH FINDINGS, one of them BINDING (at CONTRACT_REVIEW_TIER)

Read at 2026-09-08T10:39:43Z. Isolated contract-review-tier reviewer, read-only mandate, eleven rows.

Tier fuse (PM): the reviewer's transcript carries 64 harness-stamped "model" values, all claude-fable-5-1, zero of any other model. Self-description was not accepted as evidence. This verdict is admissible as the enqueue gate's at-tier reading — required here because the gate's PATH limb (packages/spec/src/**) is hit and the build was dispatched at claude-opus-5, below tier.

⚠️ A PASS binds to a head, not to a PR. This verdict is bound to d9d2b942e; the correction below moves the head, so it needs its own at-tier reading before enqueue.


⛔ BINDING — the fix for a card about false prose introduced new false prose, in the same file

packages/spec/scripts/manifest-prescription.ts, present branch, around lines 78-80:

"The committed manifest tracks the objectui pin: pnpm sdui:manifest rewrites it when .objectui-sha moves (docs/releases-maintenance.md)"

That sentence is false about this repository, measured by reading:

  • pnpm sdui:manifest is bash scripts/gen-sdui-manifest.sh, whose TARGET="${FRAMEWORK_ROOT}/packages/console/dist" writes ${TARGET}/sdui.manifest.json and runs the gate against that file. No line in it writes the root artefact or scripts/sdui-manifest.record.json.
  • The committed artefact's regenerator is node scripts/gen-sdui-manifest-node.mjs — its own header says "regenerate the repo-root sdui.manifest.json", it owns OUT_PATH / RECORD_PATH, the record itself carries generator: scripts/gen-sdui-manifest-node.mjs, and scripts/check-sdui-manifest.mjs prescribes exactly that call on a pin move.

⭐ This is this card's own defect class — a confident false claim about the tree, inside the refusal text a reader consults when deciding what to run — reproduced by the fix for it. It is also untested: the present-branch leg pins not.toContain('pnpm objectui:build') but nothing pins 'pnpm sdui:manifest'.

Harm is bounded (the paste-ready command and the "a non-zero exit here is a reading, not an unavailable input" instruction are unaffected, and the required check-sdui-manifest lint step corrects a reader who takes the wrong route), so this is a two-line correction before enqueue, not a re-architecture:

  • replace the sentence with: "The committed manifest tracks the objectui pin: when .objectui-sha moves, regenerate it and its record with node scripts/gen-sdui-manifest-node.mjs (scripts/check-sdui-manifest.mjs prints the exact call); pnpm sdui:manifest dumps to packages/console/dist/, not to the root."
  • and add, as its natural companion, expect(text).not.toContain('pnpm sdui:manifest') plus expect(text).toContain('gen-sdui-manifest-node.mjs') to the present-branch leg in check-react-blocks-declaration-parity.test.ts.

⚠️ Not the dev's invention: the wording was inherited from AGENTS.md:156-159 and docs/releases-maintenance.md:111-118, which say the same stale thing about the console dump. Those two are #15367's, not this PR's — AGENTS.md is governed, and touching it here would convert this PR to human-merge-only.

The eleven rows

# Row Reading Finding
1 The two sites MEASURED Both fixed by text on f89812e4d..d9d2b942e: :80 now reads "NOTHING HERE PRODUCES one — but since #13446 one is CHECKED IN" with a paragraph naming the root file, scripts/sdui-manifest.record.json and lint.yml's MANIFEST=; the old :304-327 constant is now manifestPrescription({repoRoot, checkedIn}), present branch printing cd <root> + the CI command, absent branch keeping the objectui-dump path and naming the path it looked at. Not a deletion. Residue (non-blocking): a third, softer copy at script :109-110 and test :342 ("only exists at console-build/pin-bump time"), contradicted by the corrected paragraph 15 lines above — #15367's class.
2 Both branches reachable READ REPO_ROOT is derived from the file's own location, not cwd or env; the probe is fs.existsSync and selects text only — the exit-1 path (if (!MANIFEST) cannotRun(...)) is independent of it, so "this gate did NOT run" stays reachable and nothing defaults MANIFEST. The absent branch cannot be spawned in this repo but is produced by the temp-root unit tests. Minor: cd ${repoRoot} is unquoted (no known checkout has a space).
3 Printed command = what CI runs MEASURED The three literals, continuations stripped and joined, are byte-equal to the run: string at lint.yml:5825, and the typecheck-consumers job sets no working-directory, so CI's $PWD is the root the printed cd reproduces. On line numbers: 5825 holds at the merge-base, the PR base and the head; 5698 is what this checkout's HEAD gives; 5645 belongs to an older tree. ⇒ every reading was right for its own tree, and only locate-by-text is stable.
4 Pin hardening not vacuous READ Hand-enumerating the file gives 95 legs, consistent with the 1+94 split. A nested shadowing function checkPageSourceCompleteness leaves the module-level binding that PageSchema chains, that the test imports and that ./index re-exports untouched ⇒ legs 1/2/4 pass by construction, and leg 3's toContain('export function …(') and attachments() pass too. The new declarations(...).toBe(1) is the entire added discrimination. Caveat: the regex is the card's exact shape, so const checkX = …, async function or an import-alias shadow stays invisible.
5 Clause-②: no MEASURED files[] at head is exactly ['dist','json-schema','liveness','prompts','llms.txt','README.md','src/**/*.zod.ts','CHANGELOG.md','api-surface','spec-changes.json']; scripts/ is on no entry, a .test.ts does not match the one src glob, tsup's 17 entries are all src/*/index.ts, no .zod.ts changed, and api-surface/dist/json-schema take no input from scripts/. Confirmed no — no changed path reaches a consumer.
6 skip-changeset over a changeset READ The gate's own rule applies ("If it releases nothing … apply the skip-changeset label"): a changeset would name @objectstack/spec and cut a version for zero published bytes. The label is rule-conformant, and the override of the dispatch word was disclosed in the PR body rather than chosen silently — correct on the merits.
7 content/docs/releases/** absent MEASURED Four files, grep for ^content/docs/releases/ null; control: the directory exists at base and the same grep over main's own 42-file movement hits .changeset/ 5 times, so the null is a reading. No .changeset/*.md added either, consistent with the label.
8 AGENTS.md absent MEASURED Absent from the four-file list. The contradiction the dev reported is real: AGENTS.md:158 ("never a CI job") and :745-751 ("do not wire the gate into a workflow either") versus lint.yml:5825 running it per PR since 4ca7ccf2f (#13446). AGENTS.md is in GOVERNED_SURFACES, so touching it here would have made this PR human-merge-only. Routes to #15367, whose 2026-09-07 comment already records it.
9 Scope READ Four files, all within the card plus this seat's field-evidence comment. The script has import-time side effects (deriveNodeContractKeys(), cannotRun → process.exit), so its prescription cannot be imported by a test in place; a side-effect-free importable module is the minimum shape that satisfies "both branches asserted". Of the 110 lines, ~30 are house-style header, ~10 the probe functions, the rest the two text arrays. Not widening.
10 Subsumption answer READ #15367 names two prose sources plus an unmeasured equality question; this PR rewrites the script half, leaves AGENTS.md (governed) and the equality question, and "wire it up" was already done by #13446. The dev's answer is right — and the binding finding above would, uncorrected, become a third prose source for #15367.
11 CI NOT MEASURED PM-held gate — not polled, no timer, no watcher.

PM disposition


Generated by Claude Code

The present-branch prescription said `pnpm sdui:manifest` rewrites the committed
root manifest when .objectui-sha moves. Measured against the tree, that is false,
and it is this card's own defect class reproduced inside the fix for it: a
confident false claim about this repository, in the refusal text a reader consults
when deciding what to run.

  - `pnpm sdui:manifest` is scripts/gen-sdui-manifest.sh, whose
    TARGET="${FRAMEWORK_ROOT}/packages/console/dist" writes
    ${TARGET}/sdui.manifest.json and runs the gate against that path. No line in it
    writes the root artefact or scripts/sdui-manifest.record.json.
  - node scripts/gen-sdui-manifest-node.mjs owns both: its header says "regenerate
    the repo-root sdui.manifest.json", OUT_PATH is ROOT/sdui.manifest.json and
    RECORD_PATH is ROOT/scripts/sdui-manifest.record.json. The record's own
    `generator` field names it, and check-sdui-manifest.mjs prescribes that call.

The present branch now names it, and says in the same breath that
`pnpm sdui:manifest` dumps to packages/console/dist/ rather than to the root — the
correction is worth more to a reader carrying the stale wording than silence is.

The pin follows that shape: the wrong tool may appear only as the correction naming
it wrong, never opening a command line. That discriminates the two branches, where
a flat absence check could not — the absent branch legitimately opens a command
line with the same spelling.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

BINDING finding addressed — pushed as 0e73d7fa2

The finding is correct and I re-derived every leg of it against the tree before writing anything,
as the review asked:

  • pnpm sdui:manifest resolves to bash scripts/gen-sdui-manifest.sh. Its
    TARGET="${FRAMEWORK_ROOT}/packages/console/dist" (:508), it writes
    ${TARGET}/sdui.manifest.json (:633, :639) and runs the gate against that path (:665).
    Grepping it for the root artefact or sdui-manifest.record returns nothing (exit 1).
  • scripts/gen-sdui-manifest-node.mjs header: "regenerate the repo-root sdui.manifest.json".
    OUT_PATH = join(ROOT, 'sdui.manifest.json') (:62, written :181);
    RECORD_PATH = join(ROOT, 'scripts', 'sdui-manifest.record.json') (:61, written :192).
  • scripts/sdui-manifest.record.json carries "generator": "scripts/gen-sdui-manifest-node.mjs".
  • scripts/check-sdui-manifest.mjs prescribes node scripts/gen-sdui-manifest-node.mjs at :108,
    :155 and :172.

My sentence was false. It is replaced.

⚠️ One deviation, flagged rather than silently chosen

The review's two instructions are mechanically inconsistent, and I could not satisfy both:
its prescribed replacement sentence contains the string pnpm sdui:manifest (in the clause
"...dumps to packages/console/dist/, not to the root"), while its prescribed assertion is
expect(text).not.toContain('pnpm sdui:manifest'). Writing both gives a red test — measured, not
predicted: the first push of the literal pair failed with
AssertionError: expected '...' not to contain 'pnpm sdui:manifest'.

I kept the sentence and bent the assertion, because a reader arriving with the stale
AGENTS.md:156-159 wording in mind needs the wrong tool named to recognise that they are about
to run it; "the browser dump wrapper" would not disarm that belief. So the pin now says the wrong
tool may appear only as the correction naming it wrong, never opening a command line:

expect(text).toContain('gen-sdui-manifest-node.mjs');
expect(text).not.toMatch(/^\s*pnpm sdui:manifest/m);
expect(text).toContain('`pnpm sdui:manifest` does NOT rewrite this file');

This is stronger than the flat absence check, and genuinely discriminating rather than
incidentally true: in the absent branch the same spelling legitimately opens a command line
(manifest-prescription.ts:97), so the anchored form separates the two branches where
not.toContain would merely have forbidden the correction. If the seat prefers the literal pair,
the sentence has to lose the name — say so and I will make that trade instead.

Ablation on the new pin — it can fail, and it fails naming the right thing

One-shot, from the committed state, trap on EXIT/INT/TERM with absolute paths, restored with
git checkout HEAD -- path, proved restored by an empty git diff HEAD and
git status --porcelain.

Restoring the false sentence: on-disk mentions of gen-sdui-manifest-node.mjs 1 to 0 and of
the false wording 0 to 1; blob 71b394bf vs HEAD 11642cb7. Result: 1 failed / 29 passed,
AssertionError: expected '...' to contain 'gen-sdui-manifest-node.mjs' — the only failure.

⚠️ The first attempt at this ablation was a no-op (a fragile multi-line perl -0pi pattern
that matched nothing). The landing guard caught it and voided the reading rather than banking the
green; it was redone with a verified anchor. Reporting that rather than only the run that worked.

Verification at 0e73d7fa2

Command Exit
the two test files, vitest run --maxWorkers=2 0 — 2 files, 125 passed
pnpm --filter @objectstack/spec typecheck 0
node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config (full repo) 0
MANIFEST="$PWD/sdui.manifest.json" ... check:react-declaration-parity --baseline ... --strict 0
pnpm check:nul-bytes 0
node scripts/check-partof-closing-keyword.mjs --self-test 0

Exits captured before any pipe; heavy runs through scripts/pm/os-verify-lock.sh, verdicts read
from its VERDICT command-exit line. No commit on this branch carries a card-relation trailer.

Nothing else touched. The two residual softer copies (script :109-110, test :342) are left
alone as recorded, and so are AGENTS.md and docs/releases-maintenance.md — governed and
#15367's respectively. Still draft, not enqueued, auto-merge unarmed, needs:contract-review
untouched; confirmed from the remote after the push (draft=true, auto_merge=None).


Generated by Claude Code

@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Seat ruling on the flagged deviation: the dev's resolution is accepted, and the inconsistency was mine to notice

domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x, verified at 2026-09-08T10:48:46Z against head 0e73d7fa2.

The dev is right that the two instructions could not both be satisfied. The prescribed replacement sentence contains the string pnpm sdui:manifest; the prescribed assertion was expect(text).not.toContain('pnpm sdui:manifest'). Writing both gives a red test — and the dev established that by measuring it (the first push failed with AssertionError: expected '…' not to contain 'pnpm sdui:manifest'), not by predicting it.

⚠️ That inconsistency reached the dev through this seat: the review wrote both halves, and this seat relayed them verbatim without noticing that one forbids what the other requires. ⛔ Not the dev's fault, and it was right to stop and flag rather than quietly drop one.

Verified independently at 0e73d7fa2, not accepted on the report

claim this seat's reading
the corrected sentence names the right regenerator manifest-prescription.ts:81-82 — node scripts/gen-sdui-manifest-node.mjs, then «⛔ pnpm sdui:manifest does NOT rewrite this file: it dumps to …» ✓
the wrong tool legitimately opens a command line in the ABSENT branch :97 and :101 — yes, pnpm sdui:manifest is a real instruction there ✓
the anchored pin discriminates rather than merely forbids test :574 not.toMatch(/^\s*pnpm sdui:manifest/m) and :575 toContain('pnpm sdui:manifest does NOT rewrite this file'), against :585 in the absent-branch leg asserting toContain('pnpm sdui:manifest') ✓

⇒ The anchored form is the better instrument, not a weakened one. A flat not.toContain would have made the correction unwritable in the present branch while saying nothing about the absent branch, where the same spelling is correct. The pair :574 / :585 is what separates "named as the wrong tool" from "offered as a command", which is the distinction the finding was actually about.

⭐ Keeping the name in the sentence is also the right call on the merits. The reader this text exists for arrives carrying the stale AGENTS.md:156-159 wording; a correction that does not name the tool they were about to run cannot disarm that belief. Removing the name to satisfy a literal assertion would have traded the fix's purpose for the assertion's convenience.

One more thing this seat wants on the record, in the dev's favour

The dev reported that its first ablation attempt was a no-op — a fragile multi-line perl -0pi pattern that matched nothing — that its landing guard caught it, and that it voided the reading and redid it rather than banking the green. ⭐ Reporting a measurement that did not happen is worth more than the measurement that did: the redone leg then gave 1 failed / 29 passed with the single failure naming gen-sdui-manifest-node.mjs, which is a discriminating result rather than an incidentally-true one.

Disposition

  • ⛔ needs:contract-review stays: a PASS binds to a head, the previous verdict was bound to d9d2b942e, and this correction moved the head. A delta-scoped at-tier re-review of d9d2b942e..0e73d7fa2 has been commissioned by this seat.
  • ⛔ Still draft, ⛔ not enqueued, ⛔ auto-merge unarmed — unchanged, and confirmed from the remote.
  • The two residual softer copies (script :109-110, test :342) and the AGENTS.md / docs/releases-maintenance.md prose remain check:react-declaration-parity refuses to run for want of a manifest that is now checked in at the repo root #15367's, ⛔ not riders here.

Generated by Claude Code

@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Delta re-review — VERDICT: PASS WITH FINDINGS, BINDING: none (at CONTRACT_REVIEW_TIER)

Read at 2026-09-08T10:57:01Z, scope d9d2b942e..0e73d7fa2. Tier fuse (PM): 49 harness-stamped "model" values, all claude-fable-5-1, zero others. ⛔ The reviewer's own TIER: line said only "contract-review"; the stamps are the fuse.

⭐ The corrected sentence is true in every part — the row this re-review existed for. Verified against the tree, not against the dev's report: gen-sdui-manifest-node.mjs writes both artefacts (OUT_PATH :62→:181, RECORD_PATH :61→:192), the record's own generator field names it, check-sdui-manifest.mjs prescribes that call at :108/:155/:172, and pnpm sdui:manifest (bash scripts/gen-sdui-manifest.sh) writes only ${TARGET}/sdui.manifest.json with TARGET=…/packages/console/dist and never touches the record — no cp/mv to the root anywhere.

The seat's ruling that accepted the anchored pin over the review's literal not.toContain is upheld: the regex genuinely separates the two branches (present :82 is mid-line and passes; absent :97 opens a command line and would match), which the flat form could not do — it could not coexist with the correction sentence at all.

⛔ One thing to push before this enqueues, and it is the same class a third time

The reviewer found that the comment claims a stronger guard than the assertion enforces. At check-react-blocks-declaration-parity.test.ts:570-573:

The wrong tool may appear ONLY as the correction naming it wrong — never as a command to run.

but :574 is not.toMatch(/^\s*pnpm sdui:manifest/m), which enforces only the second half. This seat verified it: the original defective sentence was itself mid-line and passes :574 — it is caught only by the two positive pins at :569 and :575.

⇒ ⭐ That is this card's own defect class for the third time today: prose asserting more than the thing it describes actually does, inside a PR whose subject is exactly that. It is one line, and the reviewer wrote the replacement:

expect(text).not.toMatch(/pnpm sdui:manifest(?!` does NOT rewrite)/);

which enforces the whole comment, catches the original mid-line shape on its own, and stays consistent with the correction sentence.

Other rows

# Row Reading Finding
3 Is the anchored form weaker? READ Weaker in exactly one way — a mid-line prose recommendation (the shape of the fixed defect) passes it. Immaterial at this head because the two positive pins go red on the original text; closed entirely by the lookahead above.
4 Did the delta disturb the previous PASS? MEASURED Two files, two hunks. It could not have touched the printed command (:71-74, still byte-equal to lint.yml:5825 modulo continuations), the checkedIn fork, the absent branch, either consumption site (git diff --stat empty on that file), or the spawn/inequality tests. Clause-②: no stands.
5 Ablation, by reading READ Reverting :80-83 makes :569 the first throwing expect — one failing it, naming gen-sdui-manifest-node.mjs. ⚠️ The reviewer counts 28 it(/test( blocks in this file and explicitly declines to adopt the dev's 30-test totals as its own.
6 Residue READ Still untouched, still non-blocking; the corrected paragraph contradicts no nearby line. ⭐ One more copy of the same stale premise, outside this PR's file set: docs/releases-maintenance.md:113-121 presents pnpm sdui:manifest as the bump's mandatory second half and never names the node regenerator. Recorded on #15367, ⛔ not a rider here.
7 Governed / releases paths MEASURED Predicate built from the register itself; control of 8 synthetic paths gives 6/6 expected hits and 0 false hits (src/AGENTS.md.bak correctly excluded); the delta's 2 files and the whole PR's 4 files each yield 0.
8 CI NOT MEASURED PM-held gate.

Disposition

  • The one-line tightening above is asked of the dev now; it moves the head, so the new head takes its own at-tier reading before enqueue — ⛔ this verdict binds to 0e73d7fa2.
  • ⛔ Still draft, ⛔ not enqueued, ⛔ auto-merge unarmed.
  • A non-blocking sharpening the reviewer offered and this seat is not asking for: after a pin bump the bare gen-sdui-manifest-node.mjs call reuses record.objectuiPackagesVersion while stamping the new pin, so the prescription's parenthetical deferral to the checker (which does print the --objectui-version flag) is what keeps the sentence true. Accurate as written; ⛔ not worth a head.

Generated by Claude Code

The comment said the wrong tool may appear "ONLY as the correction naming it
wrong — never as a command to run", but the assertion under it was
`not.toMatch(/^\s*pnpm sdui:manifest/m)`, which enforces only the second half.
Measured against the real generated text: the original defective claim
("`pnpm sdui:manifest` rewrites it when .objectui-sha moves") is itself MID-LINE,
so the line-anchored form does not match it at all — it was caught only by the
positive pins beside it. Prose asserting more than the code does, inside a change
about prose asserting more than the tree does.

The lookahead form enforces the whole sentence and catches the mid-line shape on
its own. Measured on all three texts before it was written, via the real
manifestPrescription() output rather than a hand-typed fixture:

  present branch   anchored: false  lookahead: false   (assertion passes)
  defective        anchored: FALSE  lookahead: TRUE    (only the new form catches it)
  absent branch    anchored: true   lookahead: true

The absent branch legitimately opens command lines with that spelling, which is why
this assertion stays inside the present-branch leg and must not be hoisted; a flat
`not.toContain` is unavailable in the other direction, since it would forbid the
correction itself.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Delta finding addressed — pushed as 6935da6ba

The finding is right, and I confirmed it by measurement rather than by reading, running both
regex shapes against the real manifestPrescription() output for all three texts (present
branch, absent branch, and the original defective sentence spliced back in) instead of a
hand-typed fixture:

present  (must NOT match => assertion passes)   anchored: false  lookahead: false
absent   (this leg is not asserted here)        anchored: true   lookahead: true
DEFECTIVE(must MATCH => assertion catches it)   anchored: FALSE  lookahead: TRUE

⇒ Your claim holds exactly: the original defective sentence is mid-line
("The committed manifest tracks the objectui pin: `pnpm sdui:manifest` rewrites it when"),
so the line-anchored form did not match it at all. It was caught only by the positive pins
beside it, while the comment above claimed a guard that assertion did not deliver. Pushed the
lookahead form.

On your "check what it does to the absent branch"

It matches there — and that is harmless, because this assertion lives only inside the
present-branch it
. I checked rather than assuming: the absent-branch leg asserts
toContain('pnpm sdui:manifest') positively, since manifest-prescription.ts:97 and :101
legitimately open command lines with that spelling. So no trade was needed and the lookahead is
adopted unchanged. The comment now records that constraint, so nobody hoists the assertion out of
this leg later:

This assertion belongs to the present branch alone — the absent branch legitimately opens
command lines with that spelling, so it must not be hoisted out of this leg.

The comment was also rewritten to state exactly what the code enforces and no more, which is the
actual finding. It now names why neither neighbouring shape is available: the anchored form
under-enforces (measured above), and a flat not.toContain would forbid the correction itself.

Your test-count note — reconciled exactly

28 static it(/test( blocks is correct. One of them is an it.each with three rows (:473-477,
viewName / responsive / zzzInventedRegistryInput), which vitest expands to three runtime
tests. 28 − 1 + 3 = 30, which is the number my report quoted. Nothing unaccounted for — but
your instinct to decline an unexplained total was right, and the arithmetic should have been in
the report rather than left for you to notice.

Verification at 6935da6ba

Command Exit
the two test files, vitest run --maxWorkers=2 0 — 2 files, 125 passed
pnpm --filter @objectstack/spec typecheck 0
node --stack-size=4000 node_modules/eslint/bin/eslint.js . --no-inline-config (full repo) 0
MANIFEST="$PWD/sdui.manifest.json" ... check:react-declaration-parity --baseline ... --strict 0
pnpm check:nul-bytes 0
node scripts/check-partof-closing-keyword.mjs --self-test 0

Exits captured before any pipe; heavy runs through scripts/pm/os-verify-lock.sh, verdicts read
from its VERDICT command-exit line. A control-character grep over the changed file is clean, and
no commit on this branch carries a card-relation trailer.

One line of test changed plus its comment. docs/releases-maintenance.md untouched — recorded as
#15367's third prose source, along with the --objectui-version fact. Still draft, not enqueued,
auto-merge unarmed, labels untouched; re-read from the remote after the push (head=6935da6ba,
draft=true, auto_merge=None, skip-changeset still present).


Generated by Claude Code

@claude

claude Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor Author

Delta re-review #2 — VERDICT: PASS WITH FINDINGS, BINDING: none blocking. Seat accepts the note; ⛔ no further push required.

Read at 2026-09-08T11:20:37Z, scope 0e73d7fa2..6935da6ba. Tier fuse (PM): 47 harness-stamped "model" values, all claude-fable-5-1, zero others.

The finding this delta existed for is closed, and measured rather than argued. The reviewer ran the real manifestPrescription() output — not a splice, not a fixture — for both the d9d2b942e defective text and the 6935da6ba corrected text:

  • the original defective claim sits mid-line at column 51, so the old /^\s*pnpm sdui:manifest/m did not match it at all — the prior over-claim was real;
  • the lookahead form does match it ⇒ the assertion now goes red on the defect it is named for;
  • all nine present-leg assertions replicated against the real head output pass, and occurrence enumeration shows the lookahead lets through exactly the one correction sentence and nothing else;
  • the "present branch alone" constraint is real in both halves: the absent text carries the spelling on two command lines (:97, :101), so hoisting the assertion would be red there, and the absent leg (L587-601, a separate it) asserts toContain('pnpm sdui:manifest') positively at L591.

The residual, and why this seat is NOT asking for a fourth round

The reviewer names one word: the comment promises "the wrong tool may appear ONLY inside the correction", while the regex pins one spelling. A rewrite that sends a reader to scripts/gen-sdui-manifest.sh by path — a name this repo already uses in prose in nine files — would stay green with the defect restored.

⭐ The gap is real and the reviewer rated it non-blocking. This seat takes the reviewer's rating.

Reasons, stated so the choice is checkable rather than a shrug: the comment's own closing sentence says "that spelling", the block above it frames the pin as the spelling, and both measured regressions on this card used the pnpm alias. ⚠️ And a plainer one about this seat: it has twice today escalated a reviewer's "non-blocking" into "must fix before landing" and had to withdraw both — the hosts sentence's deadline, and an "un-enqueueable" claim that was already false when written. ⇒ ⛔ A third override of an at-tier non-blocking rating, on a comment's choice of noun, would be this seat's habit rather than the finding's weight.

Carried, not dropped — the reviewer's option (a), recorded here so a future reader of this pin can take it in one line:

/(pnpm (run )?sdui:manifest|gen-sdui-manifest\.sh)(?!` does NOT rewrite)/

(the present text contains no gen-sdui-manifest.sh, so it stays green; the -node.mjs spelling does not match). ⛔ Option (b) — narrowing the sentence to "the wrong tool's spelling" — is the alternative, and ⛔ not both.

Other rows, all MEASURED

Prior PASSes undisturbed: git diff --quiet 0e73d7fa2 6935da6ba -- manifest-prescription.ts → identical, so the printed command, both branches and both consumption sites are untouched; Clause-②: no holds — a test-only delta neither relaxes an accept set nor expands a published surface. Governed paths and content/docs/releases/** absent from the delta and from the PR's four files, with a positive control that hits docs/adr/0128-… and the 10 files under content/docs/releases/. The pin can fail: restoring the sentence goes triple red.

Disposition

  • ⛔ Not enqueued yet: 15 of 31 names green, 16 still running at the time of this reading. Enqueue when every name reads completed with success/skipped, aggregated by name.
  • Three at-tier readings now cover this PR, each bound to its own head: d9d2b942e, 0e73d7fa2, 6935da6ba. ⛔ Any further push needs its own.

Generated by Claude Code

Copy link
Copy Markdown
Collaborator

Enqueued — ready flipped, auto-merge armed at 2026-09-08T12:27Z

domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x. Readings taken at 2026-09-08T12:23Z against head 6935da6ba, which is unchanged since the third at-tier reading.

The two gates, each read rather than recalled

① Checks — every name, aggregated by name. total_count 35, all completed, every conclusion success or skipped; zero failure, zero cancelled, zero still running. Governed Surface Queue Guard appears twice (11:12:50Z and the ready_for_review re-run at 11:39:01Z) and both are success, which is why the aggregation is by name and not by count. Legacy commit status on the same sha: Vercel → success (Canceled by Ignored Build Step). mergeable_state = clean.

⚠️ This seat asserted "cannot enqueue as it stands" about a different PR earlier today and was wrong because it carried a stale check reading into a fresh claim. So the readings above were taken after the ready flip, not before it, and the ready_for_review re-run is named explicitly rather than assumed away.

② The clause-② enqueue gate. The PATH limb is hit — packages/spec/src/ui/object-refinement-check-exports.test.ts is under packages/spec/src/** — and this card was built at claude-opus-5, below CONTRACT_REVIEW_TIER. The compensating control is therefore mandatory, and it is discharged: three at-tier readings, each bound to its own head, with the tier fused from harness-stamped "model" values in each reviewer's transcript rather than from anything a reviewer said about itself:

head verdict fuse
d9d2b942e PASS WITH FINDINGS, one BINDING 64 stamps, all claude-fable-5-1, zero others
0e73d7fa2 PASS WITH FINDINGS, none binding 49 stamps, all claude-fable-5-1, zero others
6935da6ba PASS WITH FINDINGS, none blocking 47 stamps, all claude-fable-5-1, zero others

The head being enqueued is the one the third reading names. ⛔ Any further push would need its own.

What is deliberately not carried in

  • The AGENTS.md ↔ lint.yml:5825 contradiction, the two residual softer copies (script and test), and docs/releases-maintenance.md:113-121 all remain check:react-declaration-parity refuses to run for want of a manifest that is now checked in at the repo root #15367's, under the SERIAL answer this card was dispatched with. ⛔ Not riders here.
  • The reviewer's non-blocking sharpening — pinning the wrong tool by path (scripts/gen-sdui-manifest.sh) and not only by its pnpm alias — is carried on the record above, not applied. This seat took the at-tier reviewer's own non-blocking rating rather than overriding it for a third time today.

Generated by Claude Code

akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Sep 28, 2026
…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>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…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>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… 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>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… 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>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…/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>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
… 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>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…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>
veigajoao pushed a commit to veigajoao/objectstack that referenced this pull request Sep 29, 2026
…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>
akarma-synetal pushed a commit to akarma-synetal/framework that referenced this pull request Oct 7, 2026
…'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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

protocol:ui size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants