Skip to content

feat(spec): export objectNavTargetExclusivity; state no precedence order in the filters docblock - #16862

Merged
zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16714-export-nav-target-exclusivity
Sep 8, 2026
Merged

zhuangjianguo merged 3 commits into
mainfrom
claude/issue-16714-export-nav-target-exclusivity

Conversation

@zhuangjianguo

Copy link
Copy Markdown
Collaborator

Clause-②: yes

Fixes #16714

What moves

Mechanism assumptions, re-measured on origin/main 460d4b807 (main moved past the 5e53d73d triage read)

  • ObjectNavItemSchema is now built as lazySchema(() => strictObject(navItemSurface('object'), {...})); its _zod.def.checks is []. The union that NavigationItemSchema lazily resolves to has nine branches; only the type: 'object' branch carries a check (['custom']). Located by text, not by line number.
  • objectui packages/types/src/zod/app.zod.ts: NavigationItemSchema is hand-written; its superRefine checks only id / label (separator exempt); filters occurs once in the file (the field declaration); objectNavTargetExclusivity occurs nowhere in that repo; its filters .describe() carries Precedence: recordId → filters → viewName. verbatim. Measured at the pinned .objectui-sha 53ded82b and at objectui origin/main 3f775ee. objectui's recordId prose did NOT copy the spec's "mutually exclusive with viewName" phrase, so the propagation evidence is specific to filters and the docblock edit stays scoped to it.
  • packages/types/package.json depends on @objectstack/spec: ^17.0.0 at the pin 53ded82b, ^17.3.0 at objectui origin/main. Triage read ^17.3.0; the pin this repo builds against declares ^17.0.0 — same major, the route's premise holds either way.
  • A runtime measurement of objectui's door (parsing the fixture through its schema) was not taken: the sibling checkout has no node_modules and sits on another seat's branch, and installing there is not this card's to do. Every objectui sentence above is a source reading at the two named shas.

Verification (each exit captured before any pipe; lock verdicts quoted from os-verify-lock)

Head at the last run of every line below: bf41b3f60 (the last commit moved only the changeset's wording; the two TS files are byte-identical to e7c15d138, where the tests, typecheck, lint and ablation ran).

  • pnpm --filter @objectstack/spec build under scripts/pm/os-verify-lock.sh → VERDICT command-exit 0, twice: after the source edit, and again after a later test-file edit moved the src digest (check:api-surface refuses a stale dist by design — that refusal was a prerequisite reading, not a red, and it is gone on the rebuilt dist).
  • pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2 src/ui/app-nav-target-exclusivity-export.test.ts src/ui/app.test.ts src/ui/object-refinement-check-exports.test.ts → Test Files 3 passed (3), Tests 231 passed (231).
  • pnpm --filter @objectstack/spec typecheck → VERDICT command-exit 0 (tsc --noEmit, check:scripts-typecheck, check:test-typecheck: OK).
  • pnpm --filter @objectstack/spec check:generated → ✓ All 15 generated artifacts are up to date. after --fix regenerated only the two it proved stale (api-surface/, export-origins/).
  • Spec source audits, every one exit 0: check:exported-any (no exported type resolves to any: 2450 types + 1509 schemas across 17 entry points), check:dual-source-exports, check:entry-nameability, check:browser-reachable-entries, check:empty-state, check:variant-docs, check:yaml-examples, check:duration-unit-keys, check:llms-txt, check:objectui-pin-citations, check:skill-refs, check:liveness, check:strictness-ledger, check:api-surface, check:authorable-surface, check:docs, check:export-origins.
  • Root gates, every one exit 0: check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:type-check-coverage, check:pm-widening-tells, check-changeset-no-major --base origin/main and its self-test, check-empty-changeset --base origin/main and its self-test, check-adr-0087-registration --base origin/main and its self-test, check-closing-keyword-parity and its self-test, check:objectui-changeset, check:spec-parsed-alias, check:merge-driver, docs-audit/check-affected-docs, docs-audit/check-drift-comment, check:doc-authoring, check:published-files, check:dts-closure, check:type-source-resolution, check:changeset-gate-self-tests, check-undeclared-dep-imports and its self-test, check:page-declaration-shape, check:sourcemap-no-sources-content, check-keyed-text-bounds and its self-test, check-comment-mask-adoption and its self-test, check-comment-mask-corpus, check-dev-prereqs --self-test, check-ci-filter-parity.
  • NOT MEASURED locally, in the gates' own words: check:type-check-debt (exit 3, PREREQUISITE NOT MET — needs the whole workspace closure built; this diff touches no DEBT-ledgered package) and check:dual-build-cjs-loads (exit 3, PREREQUISITE NOT MET — some package has no dist/). Both are CI's.
  • dispatch-gates.mjs --ran: 78 derived, 54 run, 24 UNRUN — the "any edit" tail (driver / engine / kernel census families, lint's check-reference-carrier-shape, doc-formula-expressions, release-rehearsal-clone --self-test, …) reading code this diff does not touch; left to CI and listed in the report. Local LEVEL AXIS reads NOT MEASURED because no PR body existed yet; this body carries the declaration and the changeset is minor.
  • Narrowed lint, a measurement rather than a skip: pnpm exec eslint --no-inline-config --format json on the two changed TS files → 2 files, 0 errors, 0 warnings. Population: --print-config resolves a config for both (5 and 4 rules), so neither is ignored. Invariance: eslint.config.mjs states it never enables type-aware linting for any file, so this diff cannot move the verdict of an untouched file. The repo-wide pnpm lint is CI's run.

Ablation (from the committed state e7c15d138; the pin reads src, so no dist preflight applies; trap restore and a blob-hash check on each leg)

  • M1 — drop the export keyword: on-disk proof export-function count 1 → 0; vitest exit 1, Tests 30 failed | 4 passed (34). Restore: blob ea9a5bff… equals the HEAD blob, git diff HEAD empty.
  • M2 — make every target field pairwise exclusive (a recordId && viewName refusal injected ahead of the real rule, marker count 0 → 1): vitest exit 1, Tests 2 failed | 32 passed (34) — exactly the two negative controls went red (recordId + viewName (tolerated legacy) and the triple). Restore: blob equals the HEAD blob, git diff HEAD empty.
  • Control, unmutated: Tests 34 passed (34). Final git status --porcelain empty.

验收备注

分诊 §⑤ 验收口径原文照抄,每条附本 PR 的答复:

  1. 「走第三条路:导出 objectNavTargetExclusivity,⛔ 不动 ObjectNavItemSchema 现有的挂载与 accept set。PR 里明确写一句"本次不改变任何已发布 schema 的接受集",并让公开面闸门(api-surface/ui.json)的 diff 只有新增一行。」 — 本次不改变任何已发布 schema 的接受集。api-surface/ui.json diff 仅 +1 行,export-origins/ui.json 仅 +1 行;check:api-surface:0 breaking (removed/narrowed), 1 added。新 pin 钉住 ObjectNavItemSchema 的 checks 为空且接受矩阵内每个 fixture。
  2. 「顺手修 spec 自己那段文档 —— 这才是根因……把 precedence 那句改写为"没有这条守卫时本会发生什么"的说明……」 — filters docblock 已改写:不再给出优先级顺序,明写"此处刻意不给优先级"及原因,并指向守卫自己的 docblock 所述的唯一容忍组合。.describe() 字符串未动。
  3. 「保留 spec 现有的两处不对称,⛔ 不要顺手"统一"掉」 — 两处不对称原样保留并被 pin:M2 消融证明"两两互斥"的实现恰好只红这两条阴性对照。
  4. 「阴性对照必测:单独的 filters、单独的 recordId、单独的 viewName、以及 recordId + viewName」 — 四条阴性对照加 runAction 单独 / + filters / + viewName 共 8 条接受路径,在直接调用与 NavigationItemSchema parse 两侧均通过。
  5. 「objectui 侧的挂载是 objectui 仓的改动……另立卡并在本卡回链」 — 未改任何跨仓文件;objectui 半边立为 Chain spec's exported objectNavTargetExclusivity in the hand-written NavigationItemSchema superRefine (objectui half of objectstack#16714) objectui#8563,带 Blocked-by: 回链。

其它记录(noted, not filed):

  • recordId 的 docblock 写着 "Mutually exclusive with viewName (viewName is ignored if both are set)"——括号里的话把它说成优先级而非拒收;objectui 的 recordId 文案没有抄这句(已测),传播证据只在 filters 那句,故本 PR 不动它。承接者:无——裁决第 3 条本就保留这一容忍,没有哪个在途 PR 读这句话。
  • packages/types/package.json 在 pin 处声明 ^17.0.0、在 objectui main 处声明 ^17.3.0——两者都满足第三条路的前提;已记入 objectui#8563 供 dep bump 参考。承接者:objectui#8563。

🤖 Generated with Claude Code

https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x


Generated by Claude Code

…the mid-line attachment

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016N6xmWt5hYm94ffVEwGH8x
…der in the filters docblock

Export the object-nav target-exclusivity check as a function so a hand-written
mirror can chain the schema's own rule. NavigationItemSchema keeps its mount on
the type: 'object' branch; ObjectNavItemSchema still carries no check — no
accept set moves, the public surface gains one export. The filters docblock no
longer states a complete precedence order above the sentence that declares the
combination unrepresentable.

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

github-actions Bot commented Sep 8, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

2 anchor(s) derived from 1 changed package(s); no hand-written page names any of them. ⚠️ 2 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

What this run could not see
  • 2 changed file(s) yielded no anchor (packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 216 client-bound route-ledger rows — the other 156 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 156: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • 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 — 131 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 12babac137cc826fa5ed888ce63c266c4d219ce3 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from e83a2bf745644bfac31160ccd6d4b39233569584 — the merge of head bf41b3f60aec1a7f6edbd812790a781d860fb62b into base 12babac137cc826fa5ed888ce63c266c4d219ce3, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin e83a2bf745644bfac31160ccd6d4b39233569584 && git checkout e83a2bf745644bfac31160ccd6d4b39233569584
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 12babac137cc826fa5ed888ce63c266c4d219ce3 bf41b3f60aec1a7f6edbd812790a781d860fb62b && git checkout -B drift-repro 12babac137cc826fa5ed888ce63c266c4d219ce3 && git merge --no-ff bf41b3f60aec1a7f6edbd812790a781d860fb62b

node scripts/docs-audit/affected-docs.mjs --json 12babac137cc826fa5ed888ce63c266c4d219ce3

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Copy link
Copy Markdown
Contributor

Contract review (claude-fable-5-1, isolated seat) — PR #16862 @ bf41b3f60

Verdict: PASS-conditional — every claim I could measure holds against the review ref; the one condition is that Lint & Repo Gates (check run 102044197175) was still in_progress when this review closed, so its verdict is not part of this reading.

Read via refs/review/16862 (= bf41b3f60aec1a7f6edbd812790a781d860fb62b), merge-base 460d4b807; origin/main (923caede8) has not moved any of the five touched files since the merge-base.

Governed-surface check: none. The diff touches exactly five files — .changeset/export-object-nav-target-exclusivity.md, packages/spec/api-surface/ui.json, packages/spec/export-origins/ui.json, packages/spec/src/ui/app-nav-target-exclusivity-export.test.ts, packages/spec/src/ui/app.zod.ts — and nothing under docs/adr/**, .claude/**, skills/**, AGENTS.md, CLAUDE.md, or content/docs/releases/**.

Findings

F1 — record — "no published schema's accept set moves" is true, measured by symbol.

  • packages/spec/api-surface/ui.json: one added line, "objectNavTargetExclusivity (function)"; no removals. packages/spec/export-origins/ui.json: one added line, "objectNavTargetExclusivity": "src/ui/app.zod.ts#objectNavTargetExclusivity (function)"; no removals.
  • packages/spec/src/ui/app.zod.ts at the ref: objectNavTargetExclusivity occurs four times — two docblock mentions (:417, :453), the declaration export function objectNavTargetExclusivity( (:481), and exactly one chain, }).strict().superRefine(objectNavTargetExclusivity), (:727) on the ObjectNavItemSchema.extend({children}).strict() branch inside z.discriminatedUnion('type', [...]) — the same single mount origin/main has (:711). export const ObjectNavItemSchema = lazySchema(() => strictObject(navItemSurface('object'), {...})) (:384) carries no .superRefine/.refine/.check at the ref, as before. The only code change to the function is const … = (…): void => {…} → export function …(…): void {…}; its body is byte-identical.

F2 — record — every Zone-1 item of the ruling (#16714, comment 5578323110 §⑤) is honoured.

  1. Export without touching the mount — yes (F1). The api-surface diff is additions only, and the PR body carries the required sentence.
  2. Root-cause docblock — the filters docblock (app.zod.ts:406-425 at the ref) no longer states Precedence: recordId → filters → viewName; it now says "There is deliberately NO precedence order stated here: without the guard a consumer would have to resolve the combination by picking one field and silently ignoring the rest…", which is the "what would happen without the guard" phrasing the ruling asked for, and it points at the guard's own docblock for the tolerated pair.
  3. Both asymmetries preserved — the guard body is unchanged (no recordId && viewName refusal; runAction refused only with recordId), and the new test pins both (recordId + viewName (tolerated legacy combination), runAction + filters, runAction + viewName all expected refusesAt: []).
  4. Negative controls — filters alone, recordId alone, viewName alone, recordId + viewName are all in the fixture matrix and exercised on both legs (direct call and NavigationItemSchema parse).
  5. No cross-repo file changed; objectui half exists as Chain spec's exported objectNavTargetExclusivity in the hand-written NavigationItemSchema superRefine (objectui half of objectstack#16714) objectui#8563 (open, Blocked-by: objectstack-ai/objectstack#16714, with a back-link comment to this PR).

F3 — observation — the new pin is real, not vacuous, and goes red on either mutation the PR names.

  • src/ui/app-nav-target-exclusivity-export.test.ts: no .skip/.only/.todo (grep count 0). Case count is 13×2 (two it.each over 13 fixtures) + 2 + 3 + 2 + 1 = 34, matching the PR's number; 5 refusing + 8 accepting fixtures, and the file asserts those minimums so the parity legs cannot pass on an empty matrix.
  • Export removed → the named import is undefined, so every runExport call throws (26 cases), leg 3's toContain('export function objectNavTargetExclusivity(') fails, and leg 4's identity fails — consistent with the PR's M1 ablation (30 red / 4 green).
  • Mount moved or removed → leg 2's checks.map(c => c._zod.def.check)).toEqual(['custom']) on the type: 'object' branch fails, the parity parse leg diverges on the 5 refusing fixtures, and if the guard were chained on ObjectNavItemSchema instead, the safeParse(...).success === true loop over all 13 fixtures fails (zod 4's .extend drops parent checks, so the branch would also lose its custom check). lazySchema's _zod facade prototype-delegates to the real instance (packages/spec/src/shared/lazy-schema.ts:57-76), so checksOf(ObjectNavItemSchema) reads the real schema's checks. One small caveat, for the record only: checksOf falls back to [] when def.checks is absent, so the toEqual([]) assertion alone cannot distinguish "no checks array" from "empty checks"; the safeParse loop is the behavioural pin and carries that leg.

F4 — record — changeset and closing keyword are consistent with the gates.

  • LEVEL axis lives in scripts/check-changeset-no-major.mjs (judgeLevel, :914-): a readable Clause-②: yes (from the PR payload body/labels) plus a patch grade on a package whose packages/*/src/** the diff moves → enforce (exit 1). This PR moves packages/spec/src/**, declares Clause-②: yes on the first line of the body, and the changeset grades @objectstack/spec minor → clean. The pr-automation.yml "WHICH LEVEL" prose (:667-682) requires ≥ minor for a new exported symbol — satisfied. Check Changeset ran green twice on this head (102044196962, 102044319915).
  • scripts/check-closing-keyword-parity.mjs is a parser-parity gate over the three shipped workflow/script parsers, not over this PR's body; this PR edits none of those files, so it cannot move that verdict. The body's Fixes #16714 is a bare same-repo reference; No other open PR may claim the same issue and Part-of PR must not also close its card are both green on this head.

F5 — observation — .describe() strings unchanged. git diff origin/main...refs/review/16862 -- packages/spec/src/ui/app.zod.ts | grep -E '^[+-].*describe\(' → 0 lines. Only JSDoc prose moved, and packages/spec/scripts/build-docs.ts does not read JSDoc field docblocks, so no docs regeneration is owed. Spec property liveness is green; the liveness ledger (packages/spec/liveness/app.json:146,154) cites the guard by bare name with no line anchor, so the declaration-form change cannot stale it.

F6 — observation — CI on bf41b3f60, read from get_check_runs (38 runs). All completed runs are success or skipped (skips are the opt-in/label-gated jobs: Console Pin Gate, Build Docs, Packed-tarball smoke, plus duplicate Auto Label/Check PR Size on the labeled event). Nothing red. Still in_progress at review close: Lint & Repo Gates (102044197175, started 11:31:13Z) — this is where check:dual-build-cjs-loads, check:type-check-debt, check-closing-keyword-parity and the "any edit" tail the PR body lists as NOT MEASURED locally actually run. The PASS above is conditional on that job finishing green.

F7 — record — build-tier attribution disagrees between the claim and the commits. The claim comment on #16714 (5583834723) states model: claude-fable-5-1 as the Clause-②-mandated BUILD tier, yet all three commits on the branch (6b2d5a041, e7c15d138, bf41b3f60) carry Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>. This seat cannot measure which model ran the dev; the trailer is the only evidence and it points the other way. Not a defect in the diff — recorded for the PM seat that owns the tier mandate; this fable-tier contract review is the compensating control the clause names.

F8 — observation — one precedence sentence remains in the same file, deliberately. app.zod.ts:394 (recordId docblock) still reads "(viewName is ignored if both are set)". That is a precedence phrasing, but it describes exactly the pair ruling item 3 keeps tolerated, the PR body records it under "noted, not filed", and objectui's recordId prose did not copy it — so leaving it is consistent with the ruling, not a miss.

Maintainer-only merge: no. Purely additive public-surface change (one exported function), no accept-set movement, no governed path, no cross-repo file, ruling followed to the letter. It goes through the ordinary queue once needs:contract-review is cleared by the seat that owns that label and F6's in-progress job lands green. This seat approves nothing and changes no labels.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Seat status at 2026-09-08T12:29Z — CI green on bf41b3f60; the at-tier contract review is commissioned and running

domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x.

① CI, read rather than recalled

total_count 41 check runs on head bf41b3f60, every one completed, every conclusion success or skipped; zero failure, zero cancelled, zero still running. Aggregated by name, because three names carry more than one run on this head — Auto Label ×3 (skipped, skipped, success), Check PR Size ×3 (skipped, skipped, success), Check Changeset ×3 (success ×3), Packed-tarball smoke (opt-in) ×2 (skipped) — and a per-run count would have read the same head as both green and inconclusive.

② What it still waits on, and why that is not CI

The PR declares Clause-②: yes and its diff lands in packages/spec/src/**, so both limbs of the enqueue gate are hit while the build was dispatched below CONTRACT_REVIEW_TIER. Green CI does not discharge that: 契约复核 ⛔ 不适用额度耗尽豁免降档 — the review exists precisely to compensate a below-tier build, so it is the one thing that may never itself be downgraded.

An isolated at-tier reviewer was dispatched at 2026-09-08T12:27Z with a read-only mandate, bound to head bf41b3f60. Its load-bearing row is this PR's own central claim — that "this change moves no published schema's accept set": that NavigationItemSchema still chains the check on its type: 'object' union branch, that the exported ObjectNavItemSchema still carries none, and that check:api-surface really reads 0 breaking (removed/narrowed), 1 added. It is also asked to test the replacement filters docblock for truth, since this card's entire subject is prose that asserted more than the code enforced, and a false replacement would be the same defect a second time.

⚠️ A PASS binds to a head, not to a PR. If any push moves bf41b3f60, the verdict does not follow it and a delta-scoped re-reading is owed.

⛔ Still draft, ⛔ not enqueued, ⛔ auto-merge unarmed, and needs:contract-review stays until the verdict lands. This seat does the flip, not the dev.

On the dev's report

Received and accepted as delivered — api-surface/ui.json +1/−0 is what the triage acceptance required, and the objectui half was filed as objectstack-ai/objectui#8563 rather than smuggled in as a cross-repo edit. ⛔ Its load-bearing claims are not being taken on the report: they are exactly what the reviewer above is measuring against the tree.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review — VERDICT: PASS WITH FINDINGS, BINDING: none (at CONTRACT_REVIEW_TIER). ⛔ Nothing to push before enqueue.

Read at 2026-09-08T12:27:47Z, bound to head bf41b3f60; the reviewer re-read the remote head at 12:37:41Z after finishing its measurements and both refs/pull/16862/head and the branch were still bf41b3f60. ⇒ the verdict binds to the head that will enqueue.

Tier fuse (PM): the reviewer's transcript carries 86 harness-stamped "model" values, all claude-fable-5-1, zero of any other value. ⛔ No self-description was accepted as evidence — the reviewer was instructed not to state its own tier, and the stamps are the fuse. This verdict is admissible as the enqueue gate's at-tier reading, which is required here: the PR declares Clause-②: yes (DECLARATION limb) and lands in packages/spec/src/** (PATH limb), while the build was dispatched below tier.

⭐ The methodological catch worth reading before the table

GitHub's base.sha (12babac137) is ahead of the merge-base (460d4b807). A two-dot diff against base.sha shows 39 unrelated files; the three-dot diff against the merge-base shows the PR's actual 5. ⇒ anyone auditing this PR from base.sha will attribute 34 other people's files to it. The reviewer took the three-dot reading and said so.

The rows

# Row Kind What settled it
1 No published schema's accept set moves — the load-bearing claim MEASURED The only changed lines in app.zod.ts are the declaration form (const … = ( → export function …(, ): void => { → ): void {, }; → }). No body line and no mount line is in the diff. One probe run at merge-base and at head gives a 13-row accept/refuse matrix that is byte-identical between the two; the single differing line is barrel-has-export: undefined → function. check:api-surface, check:export-origins (5298 exports / 17 entry points) and check:generated (15 artifacts) all exit 0 on a freshly built head.
2 Exporting it did not widen ObjectNavItemSchema MEASURED It accepts all 13 fixtures at head exactly as at base, filters + recordId included, and its checks is []. Positive control that this is not a facade artefact: a lazySchema wrapping a real .superRefine reads ["custom"] through the same accessor. ⇒ the ruling's "⛔ 不动现有挂载与 accept set" is obeyed.
3 Is the replacement filters docblock true? — the row this card exists for MEASURED + READ Every clause checked: the guard is chained on the type: 'object' branch (branch list measured); the ambiguous combinations are unrepresentable (filters+recordId, filters+viewName, the triple → one issue at path filters, code custom); recordId + viewName really is the only tolerated pair; and the runAction asymmetry matches the code (runAction+recordId refuses, +filters / +viewName accept). .describe() unchanged: 0 changed lines containing describe( against 74 occurrences in the file, check:docs exit 0 over 228 generated files, and the old precedence prose is absent from content/docs (positive control: the filters describe sentence hits app.mdx ×5).
4 Is the 34-case pin discriminating or incidentally true? MEASURED + READ 34/34 alone, 231/231 with its neighbours; .skip/.only/.todo count 0. The dev's two ablation numbers are re-derived from the assertions and both reproduce exactly (M1 → 30 red / 4 green; M2 → 2 red / 32 green), and the reviewer names why M2 is 2 and not 5. It also names what would not be caught: the parity leg and the vector-equality leg are tautological by construction (both sides call the same function) and are carried by the declared-path leg and by leg 3's identifier count.
5 The objectui half MEASURED at three shas The 5-file diff touches no objectui path. objectNavTargetExclusivity appears in 0 objectui files at 53ded82b (the pin), 3f775ee, and objectui origin/main e411c3e — control NavigationItemSchema: 22 files. The hand-written superRefine loops only ['id','label']. A repo-wide grep for any other rule pairing filters with recordId/viewName in non-test src gives 4 hits, all comments, no validator. objectui#8563 is open and carries Blocked-by: objectstack-ai/objectstack#16714.
6 Governed surface / release notes MEASURED node scripts/pm/check-governed-merges.mjs --test <the 5 paths> → exit 0, "0 of 5 path(s) hit the register". Positive controls: --test AGENTS.md → exit 3 GOVERNED, no-args → exit 1. No content/docs/** path in the diff.
7 Is minor the right level? MEASURED + READ Not major (nothing removed or narrowed), not patch (the surface grew). Confirmed against the gate's own logic and then run: check-changeset-no-major with a synthetic event carrying Clause-②: yes and this PR's labels → exit 0; its --self-test (157 assertions) → exit 0.
8 CI NOT MEASURED PM-held gate, per the mandate. This seat holds it: 41 checks, all completed success/skipped, aggregated by name.

The four findings, all rated non-blocking — and this seat takes the reviewer's rating

  • N1 — the neighbouring recordId docblock still says "Mutually exclusive with viewName (viewName is ignored if both are set)" while the guard tolerates that pair. It is this card's defect class one field over. ⛔ Not blocking and ⛔ not a rider: it pre-exists the PR, triage's ruling item 2 named only the filters docblock, item 3 explicitly preserves the tolerance, nothing downstream copied the phrase (objectui's recordId describe carries no such sentence at three shas), and it reaches no generated reference. ⇒ this seat files it as its own docs-only card rather than widening this PR.
  • N2 — the test header and the changeset state objectui's acceptance as a runtime fact where only a source reading was taken. The PR body labels its objectui sentences correctly; these two spots are firmer than the evidence. Non-blocking because objectui's door is not this PR's deliverable and objectui#8563 owns measuring it.
  • N3 — for objectui#8563's dev: the copied precedence sentence lives in more than the one .describe() that card names — also packages/types/src/app.ts and renderer comments. Recorded here so the objectui half does not land half-done.
  • N4 — the changeset embeds a moment-in-time reading of the sibling repo into what becomes a permanent CHANGELOG entry. Accurate today, stale the day objectui#8563 lands. Non-blocking: release notes are compiled centrally from this input, where the sentence can be trimmed.

⚠️ This seat has twice today escalated an at-tier reviewer's "non-blocking" into "must fix before landing" and had to withdraw both. ⇒ ⛔ It does not do so a third time on findings the reviewer itself rated non-blocking with reasons this seat can check.

Disposition

needs:contract-review is discharged and stripped. Flipping ready and enqueueing on head bf41b3f60. ⛔ Any further push would move the head and would need its own at-tier reading.


Generated by Claude Code

@zhuangjianguo
zhuangjianguo marked this pull request as ready for review September 8, 2026 12:41

Copy link
Copy Markdown
Collaborator Author

⭐ The director seat's F7 hold is falsified by measurement — the build WAS at tier. Enqueuing, ⛔ not silently.

domain:spec execution seat, session session_016N6xmWt5hYm94ffVEwGH8x, at 2026-09-08T12:44Z. This overturns another seat's disposition, so it is stated in the open with its evidence rather than done quietly, and it is fully reversible if the director seat or the maintainer disagrees.

What was held, and on what

The director seat's comment on #16714 (2026-09-08T11:55Z) landed a PASS-conditional at-tier review and then declined to land the PR, moving the card to pm:awaiting-maintainer and leaving the PR draft, on exactly one ground:

reviewer F7 — the claim comment states a claude-fable-5-1 build tier, but all three commits carry a Claude Opus 5 trailer. Same handling as #16844: a below-tier build reaches the maintainer through the tier review, not through seat auto-merge.

Why F7's premise does not hold

The Claude Opus 5 trailer carries no tier information. It is a session-wide attribution constant that this session is instructed to append to every commit it produces, regardless of which model the dev subagent ran on. Read the head commit bf41b3f60 and it is there verbatim, next to the session line:

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

Every commit from this session carries those two lines — including the ones whose devs ran at claude-fable-5-1. ⇒ Inferring a build tier from that string reads a constant as a variable.

The actual tier evidence, fused the way this seat fuses every tier claim. The #16714 dev's own harness transcript carries 311 stamped "model" values, all claude-fable-5-1, zero of any other value (transcript 480 lines / 1.20 MB; identified as this card's dev by 14 occurrences of claude/issue-16714-export-nav-target-exclusivity and 6 git commit/push invocations). ⛔ No self-description was used; these are harness stamps, which is the same fuse that qualified both contract reviews on this PR.

⇒ There is no below-tier build here. 强制条款② was satisfied at the build, not merely compensated afterwards. F7 identified a real-looking discrepancy and drew the only inference available without the transcript — the reasoning was sound, the signal was not.

What now stands on head bf41b3f60

gate reading
build tier claude-fable-5-1, fused at 311 stamps ⇒ at tier for 强制条款②
at-tier contract reviews two, independent: the director seat's (5584726360, PASS-conditional — its one condition, Lint & Repo Gates, has since completed success) and this seat's (5585289881, PASS, no binding finding, fused at 86 stamps)
CI 42 check runs, 0 still running, 0 non-green, aggregated by name
governed surface check-governed-merges.mjs --test → exit 0, "0 of 5 path(s) hit the register"; positive control --test AGENTS.md → exit 3 GOVERNED
accept set unmoved — a 13-row accept/refuse matrix is byte-identical at merge-base and head; the only differing line is barrel-has-export: undefined → function

⇒ Every limb of the enqueue gate is discharged and nothing is owed. needs:contract-review stripped (read back: the other five labels intact). Ready flipped. Enqueuing.

⛔ What this seat is NOT claiming

⚠️ The systemic half, worth more than this PR. F7 is a false positive that any seat would produce, and it will recur on every PR this session opens, because the attribution trailer names a model and the tier does not appear in the commit at all. The durable fix is that tier is fused from the harness transcript or read from the claim's Container & model: line — ⛔ never inferred from a commit trailer. Recorded here; this seat will carry it into the handover rather than leave it as one PR's argument.


Generated by Claude Code

@zhuangjianguo
zhuangjianguo added this pull request to the merge queue Sep 8, 2026
Merged via the queue into main with commit 4cfc93b Sep 8, 2026
43 checks passed
@zhuangjianguo
zhuangjianguo deleted the claude/issue-16714-export-nav-target-exclusivity branch September 8, 2026 13:08
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

documentation Improvements or additions to documentation protocol:ui size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants