docs(spec): re-anchor the dead tracker citations in the packages/spec/src remainder to the commits and ADRs that decided them (stage 6) - #20606
Conversation
…/src remainder to the commits and ADRs that decided them (stage 6) Comment and docblock lines only, 66 out and 66 in across 21 files; no code token, string literal or describe() text moves. Each dead number is replaced by the commit on main that decided what its line describes, or by the ADR that records the ruling (ADR-0029 D9.2a). Two dead sites stay byte-identical because a test reads them by literal: api-derivation.ts:163's [#6259] marker, and identity.zod.ts:230, whose deciding commit now sits on the next line. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
…cks project into check:generated proved exactly one artifact stale (check:docs, content/docs/references/**) and --fix regenerated it: two pages, three lines, each the same substitution as its source docblock line. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
13 of the 21 touched sources are src/**/*.zod.ts, which files[] ships verbatim, and the rewritten docblocks reach dist .d.ts and .js, so the change publishes bytes and takes a patch. Claude-Session: https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 137 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 16a817c5134aa1cec03da062191bc7819599d2af && git checkout 16a817c5134aa1cec03da062191bc7819599d2af
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 7a1faf1a5d213cd3fb3c71320d1fb2f9f25f50d5 9d63cb65484db46a7657a435938b86222693c87e && git checkout -B drift-repro 7a1faf1a5d213cd3fb3c71320d1fb2f9f25f50d5 && git merge --no-ff 9d63cb65484db46a7657a435938b86222693c87e
node scripts/docs-audit/affected-docs.mjs --json 7a1faf1a5d213cd3fb3c71320d1fb2f9f25f50d5
|
Contract reviewServed-tier: Read (this act, 2026-09-29T06:51Z): card #20234 (body; every comment: triage ① Derived judgments(a) Scope and file surface: right. 24 files: 21 (b) Comment-only, no code token, string, test title or catalog string moves: right. (c) Numbers: right. Over the 21 (d) Anchor truth: right, 24 of 24. 24 distinct 9-hex shas stand on added lines and none on removed lines.
(e) The two kept sites: right. (f) The generated pages: right. (g) Form C over the whole diff: right. Each rewrite leads with ② Semver level
③ Boundary flagsBlocking: none. Dev deviations, each answered:
Out-of-scope findings, each judged:
Non-blocking observations: (1) the docs-drift comment Escalated: none. CI at this head, the judging read at 2026-09-29T06:38Z: 32 check-runs, 15 success, 2 skipped ( Implemented-by: VERDICT: PASS Generated by Claude Code |
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 36533810671 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
…commits that decided them (objectstack-ai#20609) Part of objectstack-ai#20596 Clause-②: no ## What changed This is the first stage of the `domain:services` lane of the dead-citation sweep. It covers `packages/services/service-messaging/src/**` and nothing else, the largest package in the lane that no open PR or in-flight claim holds (the claim, `5884863234`, gives the order). Later stages cover the other packages, so this PR says `Part of` and the card stays open. 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), the way the landed `packages/spec/src` stages apply it (PR objectstack-ai#20533 is the method). That is **127 sites on 109 lines in 28 files, covering 14 numbers**: the 97 census sites outside the generated headers, and 30 sites in test comments, which the census defers. 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 14 numbers, so every anchor is a commit: **13 distinct shas**. No number was dropped. Only comments changed. Every touched source file keeps its line count (116 lines out, 116 in, over 28 files), so no line citation into these files moves. Seven of those 116 lines held no dead citation: they are the other half of a sentence that had to be reflowed (`inbox-caller.ts:87`, `:88`, `messaging-service.test.ts:972`, `notification-keyed-text-bounds.test.ts:83`, `notification-subscription.object.ts:81`, `:82`), or a pointer that lost its referent (`sql-outbox.ts:281`, 「the race the card describes」 to 「the race that commit describes」, because line 278 now names the commit). 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 (added-minus-removed over the whole diff: 0). No PR number stands on an added line. Fifteen dead sites are left on purpose: 12 string literals and 3 generated file headers (see the list below). One more file: a `patch` changeset for `@objectstack/service-messaging`, because the rewritten docblocks ship (see Changeset below). ## Census: `service-messaging`, before and after **Instrument (A1).** The gate's own `node scripts/check-issue-citations.mjs --census --json`, read-only, unchanged. Its surface is comment prose in `packages/**/src/**/*.ts` with string literals blanked, and it defers `*.test.ts`. The count below is its `allocated-but-absent` findings under `packages/services/service-messaging/`. | reading | tree | board | whole-repo `allocated-but-absent` | service-messaging sites | lines | files | numbers | |---|---|---|---|---|---|---|---| | before | base `7a1faf1a5`, run 2026-09-29T06:31:54Z to 06:35:25Z | enumerated, 185 pages, frontier objectstack-ai#20606, 18,433 numbers | 2,457 | **100** | 82 | 22 | 13 | | after | head `685200760`, run 06:48:33Z to 06:52:17Z | enumerated, 185 pages, frontier objectstack-ai#20606, 18,433 numbers | 2,360 | **3** | 3 | 3 | 1 | The before count matches the 100 that census `5884031174` read at `f11b5f20`. The whole-repo drop is 97, exactly this diff's census sites, and the `resolves` tally is 32,744 in both runs. The 3 left are the generated headers below. `267c11562`, the final head, adds only the changeset, which is outside the census surface. **Supplementary instrument, the whole scope.** The census does not read test files or strings, and this stage's scope includes both. So a second reading runs the gate's own exported `extractCitations` (whole-file and comment-prose projections) and `classifyCitation` over every `.ts` file under `service-messaging/src` (87 files), against the same enumerated board. | reading | citations | dead | src comment | test comment | src string | test string | |---|---|---|---|---|---|---| | before, `7a1faf1a5` | 613 | **142** | 100 | 30 | 3 | 9 | | after, `685200760` | 486 | **15** | 3 | 0 | 3 | 9 | Its src-comment column equals the census's 100, which is the control on the second instrument. The 450 resolving citations and 21 pull-request citations are the same in both readings. ## Per-number table Sites and files are all dead sites in scope at the base (comments and strings, tests included). `rewritten / left` counts 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 describes, and its own diff or message names the number it replaces. | number | sites / files | rewritten / left | anchor: what it decided | |---|---|---|---| | `objectstack-ai#6206` | 1/1 | 1/0 | `8e13ca876`: the share-link routes pass the whole authz envelope into enforcement instead of a four-field trim. The line lists it as one member of the defect family behind `assembleExecutionContext` | | `objectstack-ai#6363` | 14/2 | 13/1 | `17d095413`: `listInbox`'s `unreadCount` counts the total unread, not the fetched window (maintainer ruling 2026-08-07, Option A: make the declaration true); it adds `countUnreadTotal`. The same anchor the spec stages gave this number | | `objectstack-ai#9722` | 1/1 | 1/0 | `2074b2651`: corrects the `sys_notification_subscription` index note — `role:` and `team:` resolve against `sys_member` and `sys_team_member` | | `objectstack-ai#9807` | 4/3 | 4/0 | `44738f7af`: marks the subscription-to-recipient expansion NOT WIRED and aligns `principal` with the forms `RecipientResolver.resolveOne()` accepts, email kept verbatim | | `objectstack-ai#11374` | 17/6 | 16/1 | route A of the maintainer's 2026-08-24 ruling: a keyed text column declares a `maxLength` sourced from its producer. Written as 「route A, ruling 2026-08-24」 beside `e4902d2b9`, the commit that applied it here. `scripts/check-keyed-text-bounds.mjs`'s header states route A in words | | `objectstack-ai#11452` | 6/3 | 5/1 | `3b5f0360c`: the plugin-facing `listInboxAsCaller`, scoped to the authenticated caller | | `objectstack-ai#11453` | 26/13 | 23/3 | `1a47a5368`: `ack()` refuses a row that is not `in_flight` (`NotificationAckError`, `DELIVERY_NOT_ELIGIBLE`), as a compare-and-set in the SQL outbox. The same anchor stage 2 gave it | | `objectstack-ai#11671` | 4/4 | 1/3 | `09b4f4e4e`: `os i18n extract --source-hashes` writes the per-locale provenance companion (maintainer ruling objectstack-ai#12069 Option A, which stays cited) | | `objectstack-ai#11741` | 6/2 | 5/1 | `b706af987`: `SendEmailInput` gains `organizationId`, and the email channel threads it on both arms. The same anchor stage 1 gave it | | `objectstack-ai#11859` | 29/13 | 27/2 | `d9cf78eaa`: `ack()` takes the claimed record back and binds its claim credential in the compare-and-set. The same anchor stage 2 gave it | | `objectstack-ai#12144` | 2/2 | 2/0 | `3a04b0125`: identifier ceilings are storage-owned (`sys_metadata.name` is 255) | | `objectstack-ai#12147` | 1/1 | 1/0 | `945e91a13`: the class-level `check-keyed-text-bounds` gate | | `objectstack-ai#12978` | 17/6 | 16/1 | `e4902d2b9`: declares the sourced `maxLength` on all 15 keyed text columns of the `sys_notification_*` objects. No commit message names the card; its diff is where every `[objectstack-ai#12978]` marker entered the tree | | `objectstack-ai#18424` | 14/4 | 12/2 | `879b51270`: an email or SMS channel with no transport refuses with `transport_not_configured` instead of reporting success | Every cited sha matches exactly one commit (`git rev-parse --disambiguate`, count 1 for each), and every one is an ancestor of the base (`merge-base --is-ancestor`, exit 0 for all 13). The history was unshallowed first (`git fetch --unshallow`, 15,062 commits), so no anchor was read from a truncated log. Wordings to check, each true of its commit: - `inbox-caller.ts:86-88`: 「(objectstack-ai#6071, objectstack-ai#6551, and the share-link envelope trim commit 8e13ca8 undid)」. `8e13ca876`'s message records the trim (four fields kept, five dropped) and the whole-envelope fix. - `outbox.ts:72`: 「the option-A shape commit d9cf78e's ruling refused」. `d9cf78eaa`'s message: 「The caller never supplies an identity: ownership is proven by round-tripping what claim() returned.」 - The fifteen `sys_notification_*` bound comments: `[commit e4902d2] ... (route A, ruling 2026-08-24)`. `e4902d2b9`'s message opens 「Every bound names its producer in the declaration」, and `3954fb7df`'s records the ruling's date and its A and C routes. - `notification-keyed-text-bounds.test.ts:82-83`: the `objectstack-ai#9807` pointer becomes 「Every other arm of the grammar commit 44738f7 documented is narrower」, because `44738f7af` is where the email arm of the selector grammar was written down. - `outbox-ack-claim-ownership.integration.test.ts:40`: 「the objectstack-ai#11453 file beside this one」 names the file itself, `outbox-ack-precondition.integration.test.ts`. ## The 15 sites left - **Non-test strings (3 sites, 2 lines), refusal text.** `outbox.ts:187` (「see objectstack-ai#11453」) and `outbox.ts:210` (「(objectstack-ai#11453, objectstack-ai#11859)」) are inside `notificationAckNotClaimedMessage` and `notificationAckLostClaimMessage`, the messages `NotificationAckError` carries. They are runtime strings, so they are form D, not form C. The landed objectstack-ai#20234 stages left every string site as a token and rewrote no refusal text, so these are left and listed, as PR objectstack-ai#20533 did. The form D stages that did rewrite strings (objectstack-ai#20233's) cover `os migrate meta` guidance, a different class. - **Test titles (9 sites, 8 lines).** `describe` titles in `email-channel.test.ts:90`, `:592`, `messaging-service.test.ts:809`, `:1286`, `notification-keyed-text-bounds.test.ts:38` (2 numbers), `outbox-ack-claim-ownership.integration.test.ts:109`, `outbox-ack-precondition.integration.test.ts:110` and `sms-channel.test.ts:90`. Tokens, left as they were. - **Generated headers (3 sites).** Line 8 of `es-ES`, `ja-JP` and `zh-CN` `.source-hashes.generated.ts` reads 「(objectstack-ai#11671, maintainer ruling objectstack-ai#12069 Option A, extending objectstack-ai#8765 Option B)」. `os i18n extract` writes that line from `packages/cli/src/utils/i18n-extract.ts:2294`, and 27 generated files across the repo carry it. A hand edit here would be undone by the next extract, so the fix belongs at the producer in a later stage, which regenerates every copy. The hand-written `translations/index.ts:29` is rewritten here. ## Mechanical guard: no code token moves The check compares leaf tokens with comments stripped, base `7a1faf1a5` against head. It uses the TypeScript parser's leaf nodes, so template literals are read in context, and it excludes JSDoc nodes. It ran over all 28 touched `.ts` files. - Real run: 52,337 base tokens, **0 files with a token change** (exit 0). - Comment-insertion control, in `outbox.ts`: 0 files changed, as expected (exit 0). - Positive control, a declaration inserted into `outbox.ts`: DIFFER (exit 1). The first attempt was a no-op: its anchor text was still inside the replacement, so `scripts/ablation-replace.mjs` refused it before the guard ran. It was redone with a hitting anchor. - Positive control, one digit changed inside the kept `outbox.ts:210` refusal string: DIFFER (exit 1). Every mutation went through `scripts/ablation-replace.mjs`, and each restore was proven byte-identical to the HEAD blob (`80618f8711e2`) with `git diff HEAD` empty. ## Changeset This change ships bytes, so a `patch` changeset for `@objectstack/service-messaging` is included. It says only that the provenance comments were re-anchored. Measured on the built package (A3): `files[]` is `dist`, `README.md` and `CHANGELOG.md`. After `pnpm --filter @objectstack/service-messaging build`, the rewritten comments reach both halves of `dist`. `d9cf78eaa` appears 8 times in `dist/index.d.ts`, `1a47a5368` 6 times and `17d095413` 6 times, and `e4902d2b9` appears 15 times in `dist/index.js`. The positive control, an unchanged `notification-subscription.object.ts` docblock sentence, appears in `dist/index.d.ts`, and a negative control phrase appears nowhere. The only dead numbers left in `dist` are the two kept refusal strings. ## Gates (head `267c11562`) - **Citation judging, as CI runs it:** `pnpm check:issue-citations` (self-test, 114 cases in 8 batteries) exits 0, and `node scripts/check-issue-citations.mjs` exits 0. The diff-scoped run judged 5 citations across 19 files, and all 5 resolve. - **Doc authoring:** `pnpm check:doc-authoring` exits 0. - **Derived gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` at `267c11562` derived 64 families. They include all 50 derived at dispatch, plus 14 more. All 64 exit 0. `--ran` reports 64 run, 0 NOT MEASURED, 0 unrun, and exits 0. - Three gates first exited 3 (PREREQUISITE NOT MET) because the workspace was unbuilt: `check:dual-build-cjs-loads`, `check:i18n` and `check:type-check-debt`. A full `turbo run build` of `./packages/*` and `./packages/*/*` then ran under the shared verify lock (71 tasks, exit 0). The first two exited 0 on their rerun. - `check:type-check-debt` exited 3 once more: `outbox.ts`'s mtime had moved during the guard controls, although its bytes had not, so turbo's cache hit left `dist` older than the source. A direct `pnpm --filter @objectstack/service-messaging build` then let it exit 0 (4 ledger entries re-measured, none above its number). - **Tests and typecheck:** - `pnpm --filter @objectstack/service-messaging test`: 46 files and 507 tests pass, covering every touched test file. - `pnpm --filter @objectstack/service-messaging typecheck` exits 0. Its `tsc` program lists all 46 test files and 87 files under `src/` in total (`--listFiles`). - **Lint, as a proven narrowing:** `eslint --no-inline-config --format json` over the 28 touched `.ts` files gives 28 files, 0 errors and 0 warnings. All 28 are in eslint's own population (`isPathIgnored` is false for each). `eslint.config.mjs` never enables type-aware linting (no `parserOptions.project`, as 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. - **Control bytes:** `pnpm check:nul-bytes` exits 0, and a raw scan of the 28 files for control bytes finds none. ## Acceptance notes - **The census instrument returned a truncated board once, at exit 0.** The first `--census --json` run of this stage (06:25:55Z, base `7a1faf1a5`) read `enumerated (85 pages)`, frontier objectstack-ai#8854, 8,444 numbers, when the newest number was above objectstack-ai#20600. The `Link` header of its 85th page had carried no `rel="next"`, so `enumerateBoard` stopped and classified 16,187 citations as `never-issued`. A reader counting only `allocated-but-absent`, as this stage's count does, would have got 8 service-messaging sites instead of 100, silently. The next four enumerations in this session read 185 pages and frontier objectstack-ai#20606, and the counts above come from those. Nothing in `enumerateBoard` compares its frontier with the newest issue number, which `probeBoard` does read. Reported to the seat, not changed here: this stage makes no instrument change. - **What stays for later stages.** - The 3 generated `objectstack-ai#11671` headers, whose producer is `packages/cli/src/utils/i18n-extract.ts:2294`. That line is the repo-wide carrier (27 generated files). - The 3 refusal-string sites in `outbox.ts` (form D) and the 9 test-title sites. - **Base.** The branch is 9 commits behind `origin/main` (`0f6dcac5e`, read at 07:23Z). One of them, `8c87d26a5` (the version packages release), touches `service-messaging`, but only its `CHANGELOG.md` and `package.json`, and neither is in this diff. So there was no merge. - **Anchors shared with the spec stages.** `17d095413` (objectstack-ai#6363), `1a47a5368` (objectstack-ai#11453), `d9cf78eaa` (objectstack-ai#11859) and `b706af987` (objectstack-ai#11741) are the anchors stages 1 and 2 already gave the same numbers in `packages/spec/src`, so each number carries one anchor across the tree. --- _Generated by [Claude Code](https://claude.ai/code/session_01XY5uCwTjZj7884yYtyur4H)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… to the commits that decided them (objectstack-ai#20612) Part of objectstack-ai#20597 Clause-②: no The `packages/lint` stage of the dead-citation sweep: the `domain:spec` lane's only package (census `5884031174` on objectstack-ai#20556, claim `5885046469`). Every comment or docblock line in 22 of the 23 claimed `packages/lint/src/` files that cited a tracker number answering 404 now cites, in ruling C+D's form C, the commit in this repository's history that decided what the line describes, and says in its own words what was decided. Comments only: 81 lines out, 81 in, across 22 files. No code token, string literal, rule message, hint or rule id moves. `authoring-rules.ts` (5 sites) is excluded and left at its base blob: PR objectstack-ai#20593 (objectstack-ai#20553) edits it and was still open at the last read (2026-09-29T07:34Z). So this PR says `Part of`: those 5 sites stay for a follow-up once that PR lands, with their anchors already verified (see Acceptance notes). The census is the gate's own `node scripts/check-issue-citations.mjs --census --json`, filtered to `packages/lint/`. Before: base `7a1faf1a5d`, 2026-09-29T06:42:03Z to 06:45:24Z, board enumerated (185 pages, frontier objectstack-ai#20606). After: head `0c7b847f18`, 07:28:22Z to 07:31:52Z (185 pages, frontier objectstack-ai#20611). ## Measurement | file (under `packages/lint/src/`) | dead before | after | numbers, then anchor | |---|---:|---:|---| | `lint-flow-patterns.ts` | 9 | 0 | objectstack-ai#13681 ×9 to `8ed9c54b4` (objectstack-ai#14394 stays, 200) | | `validate-hook-body-writes.ts` | 9 | 0 | objectstack-ai#8663 ×6 to `192213f66`; objectstack-ai#13657 ×3 to `b003cf2e8` | | `runtime-gate.ts` | 8 | 0 | objectstack-ai#10064 ×5 to `def0d3e63`; objectstack-ai#19370 ×2 to `a227afa41` (objectstack-ai#19143 stays); objectstack-ai#9798 to `c7655d472` (objectstack-ai#9261 stays) | | `validate-searchable-fields.ts` | 7 | 0 | objectstack-ai#8404 ×4 to `b849e6911`, the `pre-objectstack-ai#8404` control at `:312` included; objectstack-ai#10001 ×3 to `f1b5ad39a` | | `authoring-rules.ts` | 5 | **5** | excluded: PR objectstack-ai#20593 holds the file | | `validate-expressions.ts` | 5 | 0 | objectstack-ai#6290 ×5 to `e9b526597` (objectstack-ai#6584, that commit's own PR, stays) | | `validate-react-page-props.ts` | 5 | 0 | objectstack-ai#11284 ×4 to `5383fa670`; objectstack-ai#8404 to `b849e6911` | | `validate-sortable-fields.ts` | 5 | 0 | objectstack-ai#10001 ×4 to `f1b5ad39a`; objectstack-ai#8404 to `b849e6911` | | `validate-flow-node-writes.ts` | 4 | 0 | objectstack-ai#8663 ×4 to `192213f66` | | `validate-page-field-bindings.ts` | 4 | 0 | objectstack-ai#6629 ×2 to `cd584d559` (plus the slash-joined `:208`, see Deviations); objectstack-ai#8664 ×2 to `8798cd2a6` | | `validate-translation-references.ts` | 4 | 0 | objectstack-ai#14700 ×3 to `de3c52beb` (objectstack-ai#14253 stays); objectstack-ai#6124 to `b3c1f3cd5` | | `lint-liveness-properties.ts` | 3 | 0 | objectstack-ai#10262 ×3 to `2aca1bc4c` | | `validate-action-body-writes.ts` | 3 | 0 | objectstack-ai#8663 ×3 to `192213f66` | | `validate-security-posture.ts` | 3 | 0 | objectstack-ai#19370 ×3 to `a227afa41` (objectstack-ai#8310 stays) | | `flow-template-grammar.ts` | 2 | 0 | objectstack-ai#11060 ×2 to `815585513` | | `data-model-rules.ts` | 1 | 0 | objectstack-ai#10064 to `def0d3e63` | | `reference-integrity-suite.ts` | 1 | 0 | objectstack-ai#13653 to `36d287803` | | `validate-component-types.ts` | 1 | 0 | objectstack-ai#12950 to `225e7690f` (objectstack-ai#12183 stays) | | `validate-empty-combinators.ts` | 1 | 0 | objectstack-ai#6528 to `3510e4a25` (objectstack-ai#5659 stays) | | `validate-list-view-field-refs.ts` | 1 | 0 | objectstack-ai#10001 to `f1b5ad39a` | | `validate-readonly-action-writes.ts` | 1 | 0 | objectstack-ai#13653 to `36d287803` | | `validate-readonly-flow-writes.ts` | 1 | 0 | objectstack-ai#13653 to `36d287803` | | `validate-readonly-hook-writes.ts` | 1 | 0 | objectstack-ai#13653 to `36d287803` | | **23 files** | **84** | **5** | 21 numbers; 19 removed from the 22 edited files, to 19 distinct shas | Per-file counts at base equal the claim's (census at `f11b5f20a2`) in all 23 files. A second instrument agrees site for site: every `#N` in the 23 files, classified by the TypeScript parser as comment, string or code, and each of 360 distinct numbers probed by REST `issues/N` without following redirects. At base it found 1,323 sites (1,259 comment, 64 string, 0 code); 339 numbers answer 200 and 21 answer 404, the census's 21. Its dead comment sites are the census's 84 plus one slash-joined `objectstack-ai#5775/objectstack-ai#6629` the grammar does not read, and it found one dead **string**: `validate-react-page-props.ts:1198` (see Acceptance notes). At head: 1,243 sites and 344 numbers, the same 339 answer 200, and 5 answer 404, all in `authoring-rules.ts` comments or that one string. Lit controls objectstack-ai#16862, objectstack-ai#16847 and objectstack-ai#17698 answered 200, and dead controls objectstack-ai#16714, objectstack-ai#16715 and objectstack-ai#16697 answered 404, at every checkpoint (5 at base, 5 at head). ## Why each anchor decides its line Each sha resolves uniquely, is an ancestor of `origin/main` and of the base, has one parent, and names the number it replaces in its own message (15 of 19) or its own diff (17 of 19); every one does at least one. Each was read for the rule its line states. - **objectstack-ai#13681 to `8ed9c54b4`**: lands the per-iteration containment rule PAIR (`flow-loop-body-uncontained`, `flow-try-catch-without-catch`) and its measured minimal `catch`; its diff wrote all nine lines, and its changeset records the measurements the lines cite. objectstack-ai#14394 (the rule card, 200) stays beside it. - **objectstack-ai#8663 to `192213f66`**: "three write rules ask anchor provenance before exempting a system column"; its body names objectstack-ai#8663 and its diff wrote the `[objectstack-ai#8663]` lines in all three rule files. - **objectstack-ai#13657 to `b003cf2e8`**: the post-hook half of the declared-field door, one envelope on every driver; its diff wrote the three lines. - **objectstack-ai#10064 to `def0d3e63`**: name-keys collection-resident publish-gate finding paths; its body reads "maintainer ruling 2026-08-20: Option A" for objectstack-ai#10064. - **objectstack-ai#19370 to `a227afa41`**: `security-role-word` crosses to the runtime publish gate, whole, per ruling batch objectstack-ai#203 item 3 letter B; it maps `position` / `app` and writes the past-tense crossing lines. - **objectstack-ai#9798 to `c7655d472`**: the change that carried objectstack-ai#9798 to done (its body names it), restoring the sys_comment unscoped multi-delete refusal that could not fire through the wired engine, the declared-but-unenforced fail-open the line lists beside objectstack-ai#9261 and ADR-0110 D3. - **objectstack-ai#8404 to `b849e6911`**: warns when `searchableFields` declares an unprovisioned injected anchor, adding the optional provenance index the lines describe; the SORT twin line names it as the SEARCH wiring. - **objectstack-ai#10001 to `f1b5ad39a`**: a standalone ViewItem record's nested `config.sort` / `config.searchableFields` reach the runtime publish gate, the RECORD rung. - **objectstack-ai#6290 to `e9b526597`**: `current_user` joins `SCOPE_ROOTS`, the field-level rejection becomes its own rule, and option-level `visibleWhen` is walked for the first time. `:770` quotes `SCOPE_ROOTS`' docblock in `packages/formula`; the quote now stops at "the last one this list was missing", verbatim, with the commit outside the quotation. - **objectstack-ai#11284 to `5383fa670`**: the ListView react-tier vocabulary converges on the metadata-tier spelling, deprecate-first; its changeset reads "(objectstack-ai#11284, maintainer ruling 2026-08-23)". - **objectstack-ai#6629 to `cd584d559`**: drops the retired `displayField` / `searchFields` from the record_picker entry and adds `component-field-specs-liveness.test.ts`. - **objectstack-ai#8664 to `8798cd2a6`**: names what actually guards the `unprovisionedAnchors` wiring; its diff wrote both lines. - **objectstack-ai#14700 to `de3c52beb`**: descends into `conditional` `then` / `otherwise` when building the `_validations` universe; its diff wrote all three lines. - **objectstack-ai#6124 to `b3c1f3cd5`**: the squash commit of objectstack-ai#6124 itself, leg 1 of the `_views` key ruling (the CLI i18n extractor keyed by the runtime view identity). - **objectstack-ai#10262 to `2aca1bc4c`**: adds the package-internal test seam for `getNested`'s array fan-out; its diff wrote all three lines. - **objectstack-ai#11060 to `815585513`**: its body records "Maintainer ruling on objectstack-ai#11060 (2026-08-23): option A", the CEL-mirrored six with no second semantics, which the lines quote. - **objectstack-ai#13653 to `36d287803`**: gates a hook body's `ctx.api` write to a readonly field, and shares `buildReadonlyIndex` from the flow rule, the export `:118` describes. - **objectstack-ai#12950 to `225e7690f`**: created `validate-component-types.ts`, the author-time rejection for unknown component types in spec-reserved namespaces (stage 5's anchor for the same number). - **objectstack-ai#6528 to `3510e4a25`**: the squash commit of objectstack-ai#6528 itself, one implementation of the filter identity reduction (maintainer ruling 2026-08-06, option 1). The line read `PR objectstack-ai#6528`; it now names the commit. Rung: no ADR, `docs/NORTH-STAR.md` or `scripts/adr-anchors/` file records any of these 19 decisions (the one lint anchor file, `data-model-rules.ts`, pins ADR-0120, which none of these lines cites), so the commit rung is the right one, as in objectstack-ai#20234's stages. ## Mechanical proof - **Token guard** (scratch `tokcmp.mjs`: TypeScript 6.0.3 leaf tokens, JSDoc kinds excluded, controls mutate the head text in memory only). The merge base `c96beb2707` against the head, 22 files, 54,508 base tokens (the 22 files are byte-identical at `7a1faf1a5d` and at the merge base): - Real run: 0 files with a token change (exit 0). - Comment-insertion control (`runtime-gate.ts`): 0 (exit 0). - Code-insertion positive control (`validate-hook-body-writes.ts`): DIFFER at token 216 (exit 1). - String positive control (a parser-located `StringLiteral` in `validate-react-page-props.ts`): DIFFER at token 5 (exit 1). - **Line balance**: every file is +N/−N (81/81 across 22 files), every changed line is comment-shaped, and every line count is equal at base and head. - **Tracker numbers**: added-not-removed is empty in every file, and no `PR #N` stands on an added line. Net-removed: 80 sites (the census's 79 in these files plus the slash-joined one), 19 numbers. The numbers kept on added lines all answer 200: objectstack-ai#5659, objectstack-ai#5775, objectstack-ai#8310, objectstack-ai#8340, objectstack-ai#9261, objectstack-ai#9313, objectstack-ai#12183, objectstack-ai#13390, objectstack-ai#14253, objectstack-ai#14394, objectstack-ai#19143, and objectstack-ai#6584 (a pull request, the anchor commit's own PR). - **Shas**: 19 distinct on added lines, 0 on removed lines. `rev-parse --disambiguate` answers 1 object for each; `merge-base --is-ancestor` exits 0 against `origin/main` and against the base; each is single-parent; the repository is not shallow; the control leg `e9584681a4` exits 0. - **Literal readers**: every string or regex literal in the repository that carries one of the 21 numbers (85 literals) was matched against the 23 files' text: no reader of any rewritten line. The lint tests that read these sources as text stay green below. For example, `validate-expressions.test.ts` strips comments before it matches, and `validate-security-posture.runtime-surface.test.ts` collects the `stack.X` reads inside `validateSecurityRoleWord`, which no added line carries. ## Tests and gates (at head `0c7b847f18`) - `pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*` under `os-verify-lock`: Tasks 71 successful, 71 total, VERDICT command-exit 0. - `pnpm --filter @objectstack/lint exec vitest run --maxWorkers=2` under the lock: Test Files 115 passed (115), Tests 5363 passed (5363); then `pnpm --filter @objectstack/lint typecheck`: exit 0, `check:test-typecheck` OK (2 files / 6 errors / 2 pinned signatures held). VERDICT command-exit 0. The same two runs passed with the same counts on the pre-merge head `2ce32f6b48`. - Lint, a proven narrowing: `eslint --no-inline-config --format json` over the 22 touched `.ts` files gives 22 files, 0 errors, 0 warnings. `isPathIgnored` is false for all 22, read through eslint's API. `eslint.config.mjs:327-328` says type-aware linting is never enabled, so a comment edit cannot move an untouched file's verdict. The repo-wide `pnpm lint` is CI's. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands`: 54 families derived, all run, every one exit 0. `--ran` reads "54 derived, 54 run, 0 NOT-MEASURED, 0 UNRUN", a derived zero (every line carries its exit code). Among them: - `node scripts/check-issue-citations.mjs` (the live diff-scoped run) judged 18 citations across 22 files: 17 answer as issues and 1 answers as a pull request, the kept objectstack-ai#6584; `pnpm check:issue-citations` (self-test, 114 cases in 8 batteries) passes. - `pnpm check:doc-authoring`: the sibling prose-id baseline holds, 810 pinned sites across 230 files, no growth. - `pnpm check:nul-bytes`: OK over 9,239 tracked text files; a control-byte scan of the 23 changed files finds none. - No generated page carries a lint docblock: no page under `content/docs/references/` names any of the 19 numbers, and no generator reads `packages/lint/src`, so nothing was regenerated. - Changeset: `patch` for `@objectstack/lint`. `files[]` ships `dist`, and the rewritten comments reach it: 12 of the 19 shas appear in the built `dist` (for example `8ed9c54b4` and `def0d3e63` in `index.d.ts`, `b849e6911` in `index.js` and `index.d.ts`); the positive control, the unchanged sentence "the near-miss shape: a `try_catch` that declares no `catch`" of an exported docblock, is in `index.d.ts`. Hence patch, not `skip-changeset`. - Merge probe: a no-driver `merge-tree` of the head onto `origin/main` `0f6dcac5e9`, from a bare shared clone with no `merge.*` config, exits 0. The two commits `main` gained after the merge touch none of the 23 files. - No ablation or reverse verification: the change is comment-only, so there is no behaviour to invert. ## Hypotheses (measured first) 1. **Holds.** 84 dead sites, 21 numbers, 23 files at the tip `7a1faf1a5d`, equal per file to the claim. 2. **Holds.** Only comment and docblock lines moved. The one dead number inside a string (`validate-react-page-props.ts:1198`, a finding `message`) stays byte-identical; no test or script reads a rewritten line by literal. 3. **Holds, and conditions the card.** PR objectstack-ai#20593 was still open at 07:34Z, so `authoring-rules.ts` stays at its base blob. At that read, the 9 open PRs' full file lists and the newest `Claim:` on all 11 `pm:dispatched` cards name none of the other 23 paths. 4. **Holds.** `validate-searchable-fields.ts:312` `pre-objectstack-ai#8404` is listed dead before and is gone after. 5. **Holds, with nothing to regenerate.** No lint docblock projects into a generated page; no release page is touched. ## Deviations - Two changed lines beyond the census's sites. `validate-page-field-bindings.ts:208` carried `objectstack-ai#5775/objectstack-ai#6629`, a slash-joined dead number the citation grammar does not read; it now reads "the same objectstack-ai#5775 residue class (commit cd584d5)", stage 5's precedent for the slash-joined `objectstack-ai#9972`. `runtime-gate.ts:780` is the other half of the rewritten `:779` sentence and held no number. - `origin/main` was merged once (`0c7b847f18`, merging `c96beb2707`): the first derivation read STALE TREE because `scripts/sdui-manifest.record.json` changed on `main`. The merge was clean, no driver-routed path and no lockfile change, and it touches none of the 23 files; the build, tests and gates above ran after it. - Commit trailers follow AGENTS.md's model-free pair (`Claude-Session` plus `Co-authored-by: Claude`); the pre-push trailer check passed on every push. ## Acceptance notes **What stays for this card** (why it says `Part of`): `authoring-rules.ts`, 5 sites, excluded while PR objectstack-ai#20593 holds it. Anchors, verified the same way, for whoever takes it after that PR lands: `:198` objectstack-ai#10064 to `def0d3e63`; `:1117` objectstack-ai#16659 to `ecdfc9411` (it added `flow-schedule-organization-missing` to the registry); `:1687` "(PR objectstack-ai#8546)" to `ba5e957ef`, that PR's own squash commit; `:1713` and `:1733` objectstack-ai#19370 to `a227afa41`. **Form D, not touched:** `validate-react-page-props.ts:1198` is the `react-prop-deprecated` finding `message`, which ends "...is removed after the deprecation window (objectstack-ai#11284)." An author sees it, so it takes ruling D (no number), which is a string change and outside this comment-only scope. `scripts/doc-authoring-prose-id.baseline.json` pins it (`objectstack-ai#11284: 1` for this file). It needs a form-D carrier. **Outside the census's surface**, which blanks strings and defers test files (noted, not swept here): - `packages/lint/src/*.test.ts` titles and comments still cite several of these dead numbers (objectstack-ai#6290, objectstack-ai#8404, objectstack-ai#8663, objectstack-ai#10001, objectstack-ai#10064, objectstack-ai#10262, objectstack-ai#13681, objectstack-ai#19370 and others). - Hand-written docs pages cite them too: `content/docs/automation/hook-bodies.mdx` (objectstack-ai#8663, objectstack-ai#13657), `content/docs/automation/flows.mdx` (objectstack-ai#11060) and `content/docs/deployment/validating-metadata.mdx` (objectstack-ai#19370). - `packages/formula/src/cel-engine.ts` cites objectstack-ai#6290 four times, including the docblock `validate-expressions.ts:770` quotes. It is in the census, in another lane's package. **Wording, each true of its commit.** - `validate-expressions.ts:571` keeps objectstack-ai#6584 beside `e9b526597`: objectstack-ai#6584 is that commit's own PR, so "arrived in commit e9b5265, and needed that same change (objectstack-ai#6584) to be noticed" states the one act both old numbers named. - `runtime-gate.ts:362` names the objectstack-ai#9798 shape in words, as the fail-open that commit c7655d4 ended, next to objectstack-ai#9261 and ADR-0110 D3. - `lint-flow-patterns.ts:343` reads "The measured case commit 8ed9c54 records, exactly: one row with a null owner killed the sweep"; that commit wrote the sentence, and `c02f70e13` later fixed the same shape in the showcase flow. --- _Generated by [Claude Code](https://claude.ai/code/session_014EJ1ED8X4MMrT18BhVx4tx)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #20234
Clause-②: no
Stage 6 of the staged sweep: the
packages/spec/srcremainder outsidemigrations/and the held files. Its claim is5884233505, 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 onmainthat 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: basec876a7426d, board enumerated (185 pages, frontier #20590). After: head9d63cb6548, frontier #20604.Measurement
packages/spec/src/)api/rest-server.zod.tsb3a63d32c; #14369 ×2 toa3d5724c8system/i18n-resolver.ts901355c3b; #13218 ×4 toc45d8e6b4; #8460 ×2 to ADR-0029 D9.2a; #10926 tod173125fb; #13109 to8b236c826system/operation-message.tsaa5994e17(docblock lines only)system/translation.zod.tsd173125fbsystem/core-services.zod.tsd127ff002system/dev-login.zod.ts24d622b94(#17556 stays, 200)system/environment-artifact.zod.tse58ea8b38(#14865 and #13457 stay, 200)shared/identifiers.zod.tsc41b42e8d; #12144 ×2 to3a04b0125; #12194 and #12176 (:140-141) to311433f6b; #12176 (:189) to7986d973fshared/metadata-collection.zod.ts35ad101bcindex.ts(package root)ece4dad31(#11709 stays, 200); #10485 to35ad101bcautomation/control-flow.zod.tsc5a7448d5(#14954 stays, 200)automation/execution.zod.ts18d816a50automation/index.tsecdfc9411automation/schedule-organization.zod.tsecdfc9411ai/index.tsece4dad31identity/identity.zod.ts2c86fe3eaadded on :231security/explain.zod.ts42b05af89security/public-form.ts2ab1257c9data/api-derivation.ts6968885efalready on :164; file untoucheddata/driver/turso.zod.tse2798fab7conversions/walk.tsb799ac553meta-spelling/metadata-url-spelling.ts35ad101bcPer-file counts at base equal the claim's (census
5884031174atf11b5f20a2) in all 22 files. A second instrument agrees site for site: every#Nin the 22 files, classified by the TypeScript parser, and each of 201 distinct numbers probed by RESTissues/Nwithout 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 inapi/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 #16862, #16847 and #17698 answered 200 at every checkpoint (4 at base, 3 at head); dead controls #16714, #16715 and #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.RestServerConfigrows the liveness ledger now records asdead(#14369's verdicts) —routes.*,crud.patterns,crud.objectParamStyle,metadata.cacheTtl,metadata.endpoints.schema,batch.defaultAtomic,batch.operations.upsertMany#14691 tob3a63d32c: the retirement of the ten inertRestServerConfigkeys under ADR-0049 enforce-or-remove. Its ownrest-server.zod.tsdiff wrote all nine#14691lines: the tombstones, the droppedCrudEndpointPatternSchemaand theroutesblock.RestServerConfigkeys are normalized byRestServerand read by nothing —routes.*entirely,crud.patterns/objectParamStyle,metadata.cacheTtl/endpoints.schema,batch.defaultAtomic/operations.upsertMany(ADR-0049 enforce-or-remove candidates) #14369 toa3d5724c8: seeded the fourRestServerConfigliveness ledgers "from the census filed with [finding] Ten declaredRestServerConfigkeys are normalized byRestServerand read by nothing —routes.*entirely,crud.patterns/objectParamStyle,metadata.cacheTtl/endpoints.schema,batch.defaultAtomic/operations.upsertMany(ADR-0049 enforce-or-remove candidates) #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, androutesis parsed, defaulted and normalized, then never read.translatePagestops at region-level components, so copy on components nested in another component'sproperties.childrenis authorable but never resolved — 4 KPI labels stay English on hotCRM's zh-CN landing page #12961 to901355c3b: "Ruled 2026-08-29 (option A)".translatePagedescends into declaredproperties.children; on an id collision a region-level component wins outright, and among nested matches document order decides. Its diff wrote the#12961lines being replaced.translatePage's addressed-component walk is not exported, so the CLI extractor hand-mirrors its depth cap, cycle guard and collision arbitration — the copy hazard the key list closed, left open for the walk #13218 toc45d8e6b4: exportswalkAddressedPageComponentsand consumes it from both sides; its changeset reads "([finding]translatePage's addressed-component walk is not exported, so the CLI extractor hand-mirrors its depth cap, cycle guard and collision arbitration — the copy hazard the key list closed, left open for the walk #13218, ruled 2026-08-30)".i18n-extract's per-component pass is still region-level only, so the nested-children keystranslatePagenow resolves are never scaffolded into the skeleton bundle #13109 to8b236c826: 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.[#8460]becomes[ADR-0029 D9.2a].submitLabelcopy key lost its only declared carrier whenelement:formretired (#9249) — decide retire vs re-anchor #10926 tod173125fb: "Option A per the maintainer ruling on i18n: component-translationsubmitLabelcopy key lost its only declared carrier whenelement:formretired (#9249) — decide retire vs re-anchor #10926 (2026-08-22): drop the key", thesubmitLabelretirement all three lines describe.aa5994e17: addsrecord_write_deniedandapproval_recall_not_submitterahead of their emitters, with no placeholders. Its diff wrote the four docblock lines. The catalog's rendered strings are untouched, per hypothesis 5.ServiceStatus在 ./api 与 ./system 各有一个不同声明,#4593 的别名补齐卡在这个名字上 #6604 tod127ff002: "Per the maintainer's 2026-08-08 Option-B ruling the kernel side takes the domain-specific name", matchingKernelServiceMapSchema.24d622b94: lands Let an app contribute its own first-run credentials to the boot banner (devHint/devLogins[]) — the platform cannot know which of an app's audiences shows something, and only the app can #17556 as "Suggestion 1 of The boot banner's "🔑 Dev admin" is the only credential a first-run operator is given, and in any audience-gated app it is the account that sees nothing #17081". The line keeps Let an app contribute its own first-run credentials to the boot banner (devHint/devLogins[]) — the platform cannot know which of an app's audiences shows something, and only the app can #17556 and names the parent card in words.manifest.permissionsis live on its LEGACYstring[]arm only — the structuredPluginPermissionsSchema(services / hooks / network / fs) has zero readers, and new code is told to prefer it #11333 toe58ea8b38: declaresgrantedPermissions, described by the commit itself as "the artifact-contract half ofmanifest.permissionsis live on its LEGACYstring[]arm only — the structuredPluginPermissionsSchema(services / hooks / network / fs) has zero readers, and new code is told to prefer it #11333 option A / the Phase 1 of #11333: wire granted_permissions into PluginPermissionEnforcer (F4) as the load-time gate #13457 batch ruling". The line keeps spec: declaregrantedPermissionsonEnvironmentArtifactSchema(record of manifest id → PluginPermissions) — the artifact-contract half of #14034 / #11333 Phase 1, which must land BEFORE cloud can write it (plain z.object strips undeclared keys at the artifact door) #14865 and Phase 1 of #11333: wire granted_permissions into PluginPermissionEnforcer (F4) as the load-time gate #13457 and states the ruled option in words.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.3a04b0125: wrote the storage-owned length-ceiling note and its storage-column pin; its changeset heads "(The shared identifier schemas declare no maximum length, so every cap on an identifier is a storage accident rather than a contract #12144)"./in metadata item names — retire compound-name addressing (maintainer-ruled direction): census, migration surface, staged plan #12176 to311433f6b(:140-141): stage 1, the item-name grammar declared and refused at the publish door; its message reads "Stage 1 of Ban/in metadata item names — retire compound-name addressing (maintainer-ruled direction): census, migration surface, staged plan #12176". Ban/in metadata item names — retire compound-name addressing (maintainer-ruled direction): census, migration surface, staged plan #12176 to7986d973f(:189): "Retire compound-name metadata addressing", stage 3 of the maintainer-ruled retirement.defineStack({ themes })is parsed, ingested and never applied — the last hop from a storedthemeitem to the theme engine does not exist #10485 to35ad101bc: retires thethemescarrier,ThemeSchemaand thePLURAL_TO_SINGULARfold ("Ruled B"). Its own diff wrote all four lines.@objectstack/spec's root entry does not re-export three types its own public API's inferred types mention — every consumer inferring throughdefineStackhits TS2883 #11350 toece4dad31: "Invariant recorded (maintainer ruling 2026-08-23)". It also points the premise-delta note at@objectstack/specroot entry: two MORE structurally-mentioned types are unnameable through the CHUNKED build (BaseValidationRuleShape,FilterCondition) — a minimaldefineStackconsumer still hits TS2883 #11709, which is whatindex.ts:148now says. This is stage 1's wording forkernel/index.ts:53.create_recordcollapses the engine'sDUPLICATE_RECORDenvelope to a string, so a flow'stry_catch/faultedge still cannot tell "already there" from "the store is down" #14419 toc5a7448d5:create_recordsurfaces the engine'sDUPLICATE_RECORDcode and the engine binds it on$error, the founding case the line names. Its message names automation:create_recordcollapses the engine'sDUPLICATE_RECORDenvelope to a string, so a flow'stry_catch/faultedge still cannot tell "already there" from "the store is down" #14419 as the card it lands.loopnode aborts the entire flow run when one iteration's node fails — a single bad row kills a whole scheduled sweep, and there is no per-iteration containment to opt into #13681 to18d816a50: declares the run-levelFlowRunSummary.failed, the spec half of the contained-failure contract.notifydelivers nothing on a multi-organization install: the run carries no organization, so the tenant-scoped inbox/delivery writes are refused (#8844) while the run reads healthy #16659 toecdfc9411: declares the start-nodeconfig.organizationkey and "the one refusal sentence every enforcement point says". Its sub-commits pin "the three A schedule-triggered flow'snotifydelivers nothing on a multi-organization install: the run carries no organization, so the tenant-scoped inbox/delivery writes are refused (#8844) while the run reads healthy #16659 consequences", so it is also the commit that closed the defect the second line describes.42b05af89: "ONE closed contributor-state enumeration", maintainer-ruled 2026-08-18.stripReadonlyForInsert完全不读preserveAudit——readonly的历史导入豁免在 INSERT 侧是 declared ≠ enforced #6640 to2ab1257c9:preserveAuditis UPDATE-only (stage 1's anchor for the same rule).--database-driver/OS_DATABASE_DRIVER)在 CLI 与 standalone stack 之间仍有三处分叉(#6265 后续) #6345 toe2798fab7: its ownturso.zod.tsdiff wrote both lines ("The maintainer's cli/runtime: explicit driver(--database-driver/OS_DATABASE_DRIVER)在 CLI 与 standalone stack 之间仍有三处分叉(#6265 后续) #6345 ruling closes it…", "(cli/runtime: explicit driver(--database-driver/OS_DATABASE_DRIVER)在 CLI 与 standalone stack 之间仍有三处分叉(#6265 后续) #6345 fork 2)"). The wording is stage 3's inconfig-registry.zod.ts.b799ac553: addsmapViewPayloadstowalk.ts, the centralized walk the heading describes. Its message names Every view-family conversion walks only the container spellings — a stored ViewItem record (viewKind/config) or flattened overlay escapes applyConversionsToStoredItem('view', ...) entirely #13031 as the card it lands.ApiKeyreference table documents better-auth's apiKey-plugin schema — a plugin this platform does not load and a shapesys_api_keydoes not have #8715, kept:2c86fe3ea("Maintainer ruling 2026-08-15 (disposition B: delete)"; its diff wrote :230) now sits on :231.Mechanical proof
tokcmp.mjs: TypeScript 6.0.3 leaf tokens, JSDoc kinds excluded, controls mutate the head text in memory only). Basec876a7426dagainst the head, 21 files, 37,539 base tokens:ai/index.ts): 0 (exit 0).system/i18n-resolver.ts): DIFFER at token 14452 (exit 1).StringLiteralinsystem/operation-message.ts, found by the parser): DIFFER at token 5 (exit 1).PR #Nis on an added line. Net-removed: 60 sites, 24 numbers.rev-parse --disambiguateanswers 1 object for each.merge-base --is-ancestorexits 0 againstorigin/maine666636fd9and against the base.e9584681a4exits 0.data/api-derivation.test.ts:236splits on[#6259];packages/runtime/src/api-exposure.test.ts:152splits on#6259;identity/api-key-retirement.test.ts:118assertsare NOT declared here (#8715.Tests and gates (at head
9d63cb6548)pnpm exec turbo run build --concurrency=2 --filter=./packages/* --filter=./packages/*/*underos-verify-lock: Tasks 71 successful, 71 total, VERDICT command-exit 0.pnpm --filter @objectstack/spec check:generated: exit 1,check:docsstale (1 of 15).check:generated --fixthen regenerated exactly that artifact: 2 pages, 3 lines, each its docblock line verbatim.content/docs/references/automation/schedule-organization.mdxcontent/docs/references/data/driver-turso.mdxvitest run --maxWorkers=2over 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).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.tsis left to CI: it imports rather than reads, and rebuilds schemas in a temp tree.pnpm --filter @objectstack/spec typecheck: exit 0;check:test-typecheckOK (53 files / 251 errors / 138 pinned signatures held).eslint --no-inline-config --format jsonover the 21 touched.tsfiles gives 21 files, 0 errors, 0 warnings.isPathIgnoredis false for all 21, read through eslint's API.eslint.config.mjs:327-328says type-aware linting is never enabled, so a comment edit cannot move an untouched file's verdict.pnpm lintis CI's.node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands: 108 families derived and run, every one exit 0.--ranreads "108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN". Among them:pnpm check:issue-citationsplus the live diff-scopednode scripts/check-issue-citations.mjsjudged 7 citations across 21 files, 7 resolve: the live numbers kept beside the anchors (ADR-0049 candidate:element:formdeclares a full form contract with no renderer registration found in objectstack/objectui #9249, spec:TryCatchErrorValueSchemasilently strips thecodekey the engine now binds — the declared "ONE shape" and the runtime shape have diverged (#14419 follow-up) #14954, Let an app contribute its own first-run credentials to the boot banner (devHint/devLogins[]) — the platform cannot know which of an app's audiences shows something, and only the app can #17556, spec: declaregrantedPermissionsonEnvironmentArtifactSchema(record of manifest id → PluginPermissions) — the artifact-contract half of #14034 / #11333 Phase 1, which must land BEFORE cloud can write it (plain z.object strips undeclared keys at the artifact door) #14865, Phase 1 of #11333: wire granted_permissions into PluginPermissionEnforcer (F4) as the load-time gate #13457, and@objectstack/specroot entry: two MORE structurally-mentioned types are unnameable through the CHUNKED build (BaseValidationRuleShape,FilterCondition) — a minimaldefineStackconsumer still hits TS2883 #11709 twice).pnpm check:doc-authoring: 16,765 customer-facing strings across 1,175 spec sources clean; the sibling baseline holds.patchfor@objectstack/spec. 13 of the 21 touched sources aresrc/**/*.zod.ts, whichfiles[]ships verbatim, and the rewritten docblocks reachdist. For example,ruled collision arbitration (commit 901355c3b)is in 1.d.ts, and the unchanged neighbouring sentence in the same exported docblock (the positive control) is in 1.d.ts.merge-treeof the head ontoorigin/main7a1faf1a5d, from a bare shared clone, exits 0. The 3 commitsmaingained since the base touch none of this diff's files. No merge was made, as stages 1–4 did.Hypotheses (measured first)
f11b5f20a2.pre-#Nspelling is dead here.Claim:on all 15pm:dispatchedcards. None names any of this PR's 24 paths. The five exclusions stay excluded.content/docs/references/carries any of the 26 numbers.#Nin the 22 files is inside a string; the two literal-read sites stay as tokens. Insystem/i18n-resolver.tsandsystem/operation-message.tsonly docblock and line-comment lines moved.Deviations
data/api-derivation.tsis untouched, because its one dead site is read by literal and its commit already stands on the next line (stage 3's disposition).rest-server.zod.ts:756,identifiers.zod.ts:19,index.ts:133,environment-artifact.zod.ts:137,schedule-organization.zod.ts:82, andidentity.zod.ts:231(the commit placed beside the kept :230).Claude-SessionplusCo-authored-by: Claude); the pre-push trailer check passed on every push.Acceptance notes
What stays for later stages. At the tip
7a1faf1a5dwith this PR applied,packages/spec/srcholds 260 dead sites (34 numbers). This is the gate's census on this head, with the four spec sourcesmainchanged since the base re-extracted and re-probed at the tip. By area:migrations/233: os migrate meta prints tracker numbers to the author: ADR-0087 migration entries' reason / replacement / acceptanceCriteria text carries ~2,060 of them, 178 dead, which AGENTS.md's runtime-string rule forbids #20233 edits the same entry files; the author-shown fields are its form D.conversions/registry.ts12: held by PRs feat(spec)!: $empty joins FILTER_OPERATORS, and is_empty / is_not_empty lower to it (#20446) #20570 and feat(spec)!: retire the inner name on cube measures and dimensions — the record key is the member's name (#20300) #20458.stack.zod.ts9: free now; PR fix(spec,cli): os validate / os build read the ADR-0087 conversions defineStack applied — --json conversions and --strict see the producer's record #20579 landed as7a1faf1a5dat 06:02Z, after the claim, so it stayed excluded here.data/analytics.zod.ts3: PR feat(spec)!: retire the inner name on cube measures and dimensions — the record key is the member's name (#20300) #20458.integration/connector.zod.ts1: automation: connector triggers start flows, and a connector action'sdescription/outputSchemareach the flow designer (7 keys) #20287, PR feat(spec)!: retire the connector triggers array — the ConnectorTrigger shape nothing registered, polled or received (#20287) #20587.data/api-derivation.ts:163(DATA_ACTION_TO_API_OPERATION的batch: 'bulk'行在 #5856 之后没有生产者了 —— spec 注释仍称其为「runtime callData action」 #6259) andidentity/identity.zod.ts:230([finding] TheApiKeyreference table documents better-auth's apiKey-plugin schema — a plugin this platform does not load and a shapesys_api_keydoes not have #8715), 1 each: kept becauseapi-derivation.test.ts:236,packages/runtime/src/api-exposure.test.ts:152andapi-key-retirement.test.ts:118read 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
whystrings, thePROVENANCE_WAIVERSreason, the twoAGGREGATION_CASESnotes, and theliveness/**notes.Outside
packages/spec/src(#20556's lane):packages/spec/scripts/check-entry-nameability.tscites #11350 in its header (:14) and PRINTS "recorded on #11350" in its failure text (:727);packages/spec/scripts/root-entry-type-nameability.pin.test.tscites it too. Commitece4dad31is 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-retiredandtranslation-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:10names The boot banner's "🔑 Dev admin" is the only credential a first-run operator is given, and in any audience-gated app it is the account that sees nothing #17081 in words ("suggestion 1 of its parent card").environment-artifact.zod.ts:136-137statesmanifest.permissionsis live on its LEGACYstring[]arm only — the structuredPluginPermissionsSchema(services / hooks / network / fs) has zero readers, and new code is told to prefer it #11333's option A as "the option the Phase 1 of #11333: wire granted_permissions into PluginPermissionEnforcer (F4) as the load-time gate #13457 batch ruling chose".identifiers.zod.ts:18drops "itsos-dev-reportcomment 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:137andpackages/runtime/src/security/artifact-granted-permissions.ts:6cite "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