Repository navigation
fix(cli,cloud-connection,types): CLI help, warnings and refusals state each decision in words instead of a tracker number (stage 1) - #21172
Conversation
…e each decision in words instead of a tracker number (stage 1) Each rewritten string now says what the cited card decided (form D) and drops the number; where the sentence already said it, only the citation goes. Text only: no code, error code, flag or field moves. The three 3-digit CSS colours in the unknown-hostname 404 page take their 6-digit spelling (identical rendering), the gate's documented remedy for that false-positive family. The prose-id ledger is recomputed with --census-ledger. The two occurrences in the generated source-hash header template (i18n-extract.ts) are held: changing that producer regenerates 27 companion files outside this stage's file surface. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…stage 1) Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 12 package(s): 40 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 53 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 a1c3b19e9816815cd7a86ecba8fd899d2b2bf0d9 && git checkout a1c3b19e9816815cd7a86ecba8fd899d2b2bf0d9
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d34aa58a2affc87ded426dc6a326edb03534cb62 9d5f33f929c84d99ddadedc9b6a94c98909977f6 && git checkout -B drift-repro d34aa58a2affc87ded426dc6a326edb03534cb62 && git merge --no-ff 9d5f33f929c84d99ddadedc9b6a94c98909977f6
node scripts/docs-audit/affected-docs.mjs --json d34aa58a2affc87ded426dc6a326edb03534cb62
|
…ecision in words instead of a tracker number (stage 1, patch round) The header renderSourceHashModule writes into every generated source-hashes companion now says what the two cited rulings decided: a leaf whose digest no longer matches its source is stale and serves the source text instead. The three docs transcripts of the build/validate step line move to the step text the CLI now prints. The 27 companions are regenerated with the repository tool, and the prose-id ledger is recomputed, in the same commit series. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
…d header, and recompute the prose-id ledger `node scripts/check-i18n-bundles.mjs --write` on a CLI built from the previous commit: 27 files, line 8 only, the same one-line change in each. The prose-id ledger is recomputed with --census-ledger; only the i18n-extract.ts row leaves. Claude-Session: https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB Co-authored-by: Claude <noreply@anthropic.com>
Contract reviewServed-tier: Reviewed against the card (#20752 body and all six comments), the governing ruling Check-runs on the head, converged: 35 distinct names (42 runs collapsed latest-per-name), all Merge-tree: ① Derived judgments(a) Form D, string by string — holds. Every one of the 32 tracker citations was read against its card or, for the three 404s, its landing commit, and each rewritten sentence states what was actually decided without inventing a decision:
(b) Text only — holds. Every hunk in the 18 hand-edited (c) The ledger — holds. Computed from the JSON at both ends: base (d) The patch round — holds. The header literal is in form D (above). The 27 companions: 27 files, 27 hunks, every hunk (e) Pins — holds. ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
…ion in words instead of a tracker number (stage 2) (objectstack-ai#21188) Part of objectstack-ai#20752 Clause-②: no **Stage 2 of 5 of the `domain:cli` lane under the maintainer's A / A ruling (5902360492): the `packages/rest` strings.** The card stays open for stages 3-5, so this PR carries no closing keyword. Text only: no status, error `code`, field, route, export or control flow moves. ## What this does The REST layer's refusal envelopes, a boot warning, the served OpenAPI descriptions, a `/discovery` capability description and the route ledger's notes sent the reader to a tracker number for the reason behind them. In form D, as stage 1 (PR objectstack-ai#21172) and the engine lane's stages applied it, the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words. All 34 ledgered occurrences in `packages/rest` (claim `5933139597`): `rest-route-ledger.ts` 29, `openapi-builtin-paths.ts` 2, `import-mapping.ts` 1, `rest-api-plugin.ts` 1, `rest-server.ts` 1 (the `:4830` string only). ### Rewritten in words | Where (head line) | Cited | The text now says | Decision read from | |---|---|---|---| | `import-mapping.ts:93` `UNSUPPORTED_TRANSFORM` message | 2611 | "...which the import path does not execute (there is no server-side sandbox), so the import is refused rather than run with that transform skipped" | landing commit fce8ff4 (javascript: no server-side sandbox, never silently skipped) | | `openapi-builtin-paths.ts:152` response description | 5588 | "This section is built from the routes this server actually mounts ... Per-route payload schemas are not derived here and are deliberately not invented." | ruling C, comment 5200114550 (rest produces the built-in section from its own route table) and ACCEPT 5201374820 (no invented schema or status) | | `openapi-builtin-paths.ts:157` request-body description | 5588 | "Its shape is route-specific; this document leaves it undescribed rather than invent one." | same | | `rest-route-ledger.ts:178` discovery note | 5682 (a PR) | "...through the double assertion: the live body parses against `DiscoverySchema`, and it carries no key the protocol does not declare" | PR 5682 body and its reverse-verification comment 5198848731 | | `rest-route-ledger.ts:221` `_migrate-stored` note | 4327 | "ADR-0087 stored-row canonicalization, the route form of `os migrate meta --stored`: it rewrites stored `sys_metadata` rows in place to their canonical form" | card body; commits 83cf2d3 and 8aacf94 | | `rest-route-ledger.ts:228` book-tree note | 12038 ("ruling 5A") | "...re-exported into `/api`, never declared there a second time" | ruling 5434804846, item 5A | | `rest-route-ledger.ts:249` `GET /meta/:type/:name` note | 5950 | "...the ADR-0010 protection envelope this schema now declares, every key optional because the cached branch never publishes it" | landing commit 361bd5b | | `rest-route-ledger.ts:251` `PUT /meta/:type/:name` note | 12702 | adds "so a tenant org admin authors their own org's overlays without platform-wide `manage_metadata`" | card body; ACCEPT 5439334711 | | `rest-route-ledger.ts:267` audit note | 11678 (twice) | "The schema predates this row: it joined the spec when `MetadataProtocol` gained its optional `auditMetaItem` member ... conformance: the audit-door capture suite" | card option B; os-dev-report 5405323732 and ACCEPT 5405332618 | | `rest-route-ledger.ts:290` legal-next-state note | 9180 | "Step 2 of the singular-segment ruling retired the plural ... twin" (the sentence already ends "the `/meta` type segment is singular, always") | ruling in the card body; re-weigh 5311434183 | | `rest-route-ledger.ts:290` same note, last clause | 10179 (404) | "...`meta-state-plural-tolerance.test.ts`, which pins both halves as behaviour so this note cannot quietly stop being true" | landing commit 53a48c9 (PR objectstack-ai#10613) | | `rest-route-ledger.ts:293` published-snapshot note | 7526 | "...the fall-through into the compound-name route, before this path had a registration of its own, structurally could not do" | card body; ACCEPT 5251269251 | | `rest-route-ledger.ts:313` `GET /ui/view` note | 3611 | "the client was moved to the path form both surfaces accept, rather than this server registering the query dialect as a second spelling" | card option A | | `rest-route-ledger.ts:379` search note | 8140 | "...a near miss that would compile here and be false" | card thread (bind only a type verified against the route's actual emit) | ### Citation only (the sentence already stated the decision) - `rest-route-ledger.ts:213`, `:216`, `:225`, `:228`, `:267`, `:272`, `:275` (12038, the bracketed prefix): each note goes on to say the schema is a transcription of the producer's declared return, with its conformance suite. - `rest-route-ledger.ts:221` (12038 ruling 2C): "DELIBERATELY UNBOUND — ... a second declaration in spec would drift against the CLI rendering the same report." - `rest-route-ledger.ts:293` (12038 ruling 1C): "The named schema is DELIBERATELY OPAQUE (`z.unknown()`) ... never a union frozen against the type registry." - `rest-route-ledger.ts:251` (6603) and `:253` (7019): "Gated on `manage_metadata` ... a session alone is no longer enough" and the DELETE reasoning. - `rest-route-ledger.ts:253`, `:269`, `:272` (12702): each already names the shared verdict and the caller's own org partition. - `rest-route-ledger.ts:362`, `:379` (11924): "Filled with its conformance coverage", the ruled condition. - `rest-route-ledger.ts:462` (3610, 7563): "Moved off the bare POST /packages to this path: ..." and "Mounted UNCONDITIONALLY — ... answers an honest 404 on a deployment that composes none." - `rest-api-plugin.ts:530` (3963): "`api.requireAuth` was removed and is IGNORED — anonymous access to object data is always denied." - `rest-server.ts:4830` (1604): the `transactionalBatch` description keeps ADR-0034, a customer-resolvable reference the gate keeps. Every cited card was read (REST, open or closed) before its string was rewritten. One answers 404: 10179, read through its landing commit 53a48c9. `rest-route-ledger.ts:290`'s "(10179)" was the string the dead-citation sweep left for this card's form-D stage (commit 04b202e, its Acceptance notes). ## Ledger (`scripts/doc-authoring-prose-id.baseline.json`) Recomputed with `node scripts/check-doc-authoring.mjs --census-ledger` (exit 0, no growth refusal) into a scratch file, then copied into place. The diff deletes 30 lines and adds none: exactly the five `packages/rest` rows. Every other row is byte-identical. After merging `origin/main` (`454bbb6866`) the recomputed ledger is byte-identical to the committed one. | | before (`b9087d77e9`) | after | |---|---|---| | `packages/rest` | 34 occurrences, 20 pairs, 5 files | 0 | | whole ledger | 584 occurrences, 395 pairs, 152 files | 550 occurrences, 375 pairs, 147 files | `pnpm check:doc-authoring`: before, "487 pinned site(s) across 152 file(s) ... no growth, no burn-down unrecorded"; after, "463 pinned site(s) across 147 file(s) ... no growth, no burn-down unrecorded". No gate is added or loosened; `scripts/check-doc-authoring.mjs` is untouched. ## Changeset `.changeset/20752-rest-strings-state-the-decision.md`: `patch` for `@objectstack/rest`. Measured after the build: the four new non-ledger sentences are each in `dist/index.js` and `dist/index.cjs`, and each old spelling (`see framework` plus the number, `invented (` plus the number, `removed (` plus the number, the batch description's number) is in 0 files. The route ledger does not ship: `REST_ROUTE_LEDGER` and the new ledger sentences are in 0 files under `packages/rest/dist`. ## Text-only proof A TypeScript-AST skeleton of each changed `.ts` file, where every string literal and template text is one placeholder, consecutive literal operands of a `+` chain merge, and comments and JSDoc are never read. `b9087d77e9` against the fix commit, and again `454bbb6866` (the merged `origin/main`) against the head: 5 of 5 SAME, with token and literal-slot counts identical per file. Control: the same tool reports DIFF on `import-mapping.ts` across `8368f1c005`, a real code change. ## Pins No test, fixture or snapshot asserts any of the old strings with its number. Each old fragment was searched repo-wide; the only hits outside the five files are comments, the runtime ledger's twin notes (stage 3) and a checklist anchor. So nothing is re-pinned and there is no ablation to run. The one pin on a rewritten string, `rest-config-parse-not-cast.test.ts:407` (`toContain` of "`api.requireAuth` was removed"), asserts a substring the rewrite keeps, and runs green in the suite below. ## Tests All on the merged head, through `scripts/pm/os-verify-lock.sh`, every verdict `VERDICT command-exit 0`: - Build: `turbo run build --filter=@objectstack/rest...`, 25/25 tasks. - `@objectstack/rest`, both vitest projects (`local` and `repo`): 259 files passed (259), 4989 tests passed, 254 skipped. Before the merge: 258 files, 4946 passed, 254 skipped. - `@objectstack/rest` `typecheck`, including `check:test-typecheck: OK`. - The three `@objectstack/client` suites that import the ledger as source (`client-url-conformance`, `rest-route-ledger-coverage`, `route-ledger-response-schema`): 3 files, 9 tests passed. ## Gates - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths) at `d2b6ede4e9`: 66 commands, run one at a time from the worktree, every exit code recorded before any pipe. 66 of 66 exit 0. `--ran`: "66 derived famil(ies) accounted for — 66 run, 0 NOT-MEASURED (a DERIVED zero — all 66 recorded an exit code and none of them is 3)". - `check:dual-build-cjs-loads` first answered `PREREQUISITE NOT MET` (exit 3: only the `rest` closure was built). After a full `turbo run build` of `./packages/*` and `./packages/*/*` (71/71 tasks), it and the other four dist-reading gates were re-run, all exit 0: 105 require entry points across 66 packages load; `check:dts-closure` 71 built packages, 167/167 declaration files present; `check:sourcemap-no-sources-content` 68 packages, 522 maps; `check:lean-entry-closure` and `check:published-files` green. - `check:doc-authoring`: "463 pinned site(s) across 147 file(s) ... no growth, no burn-down unrecorded". `check:issue-citations`: "no issue citations added against 454bbb6 (5 file(s) read)". `check:nul-bytes`: OK, 9832 files. - Outside the derived set: `check:meta-type-normalized` (a declared wide-population family whose scan root is `packages/rest/src`), exit 0, "OK (27 file(s), no raw `:type` param decisions)". The other declared wide-population families, the artifact roster, the path-scheduled CI jobs and the type-check lanes are CI's. NOT MEASURED: `check-issue-citations.mjs --census` and the shard-attestation and test-completeness steps, reason: their argv takes values that exist only inside a CI run. - `pnpm lint` (`eslint . --no-inline-config`, repo-wide, not narrowed) at `d2b6ede4e9`: exit 0, 119 s under the lock. ## Acceptance notes - `docs/qa/platform-checklist/areas/records-forms.json:4068`: an acceptance clause says the `UNSUPPORTED_TRANSFORM` message "names the missing server-side sandbox and framework" plus the number. The message still names the missing sandbox, and no longer the number. Editing a checklist clause is a semantic edit that bumps the item's revision and history, so it is outside this claim; noted for the checklist's next pass, not filed. - The runtime ledger twins of several rewritten notes (`packages/runtime/src/route-ledger.ts:497`, `:507`, `:509`) still carry their numbers. They are stage 3. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… each decision in words instead of a tracker number (stage 3) (objectstack-ai#21219) Part of objectstack-ai#20752 Clause-②: no **Stage 3 of 5 of the `domain:cli` lane under the maintainer's A / A ruling (5902360492): the `packages/runtime` strings.** The card stays open for stages 4 and 5, so this PR carries no closing keyword. Text only: no status, error `code`, field, route, export or control flow moves, and the dispatcher error vocabulary keeps every key and code. ## What this does Refusals, boot errors, log lines, endpoint hints, the dispatcher error vocabulary's `why` text and the route ledger's notes in `packages/runtime` sent the reader to a tracker number for the reason behind them. In form D, as stages 1 and 2 (PR objectstack-ai#21172, PR objectstack-ai#21188) applied it, the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words. All 67 ledgered occurrences in `packages/runtime` (claim `5935998841`), in 55 string sites: `route-ledger.ts` 42, `dispatcher-error-vocabulary.ts` 10, `standalone-stack.ts` 3, `sandbox/body-runner.ts` 3, `endpoint-executor.ts` 2, `action-execution.ts` 2, and one each in `resolve-project-database.ts`, `endpoint-policy.ts`, `domains/auth.ts`, `domains/activation-gate.ts` (the fold site from the dead-citation sweep) and `app-plugin.ts`. Every cited card was read first. Three answer 404 and were read through the commit that decided them: 8885 (`30b1c636a2`, which registered the nine template-generated approvals codes), 10243 (`266436a7f`, which made flow enablement an authoring write) and 10179 (`53a48c93f4`, through stage 2's wording). Two are objectui cards (6593, 5933) that this session cannot open; they were read through the records in this repository that cite them (see Acceptance notes). ### Rewritten in words | Where (head line) | Cited | The text now says | Decision read from | |---|---|---|---| | `action-execution.ts:2476` doubled-navigation warning | 11519 | "...two post-success destinations for one success, a pair the contract refuses rather than ranks." | ruling 5393507405 (refuse the doubled channel, no precedence field) | | `action-execution.ts:2477` same warning | objectui 5933 | "...is ignored (the interim precedence the console renderer applies, which no contract promises)" | card 11519 body and ruling 5393507405; the spec refusal's own wording, "the interim precedence" | | `app-plugin.ts:1781` seed tenancy warning | 8686 | "seed tenancy handoff failed: the seed rows were not stamped with the new organization, so seed and API writes stay on separate autonumber counters until the next boot's migration repairs it" | ruling 5299880350 (seed writes carry the organization the way API writes do; one counter per object) | | `dispatcher-error-vocabulary.ts:349` `APPROVAL_*_FAILED` row | 8885 (404) | "All nine codes the family produces are registered in the ledger, and this row's pin is what keeps that true" | landing commit `30b1c636a2` | | `dispatcher-error-vocabulary.ts:356` same row | 9223 | "the scan reports a template-spelled code under its family identity rather than dropping it, so it SEES the template" | os-dev-report 5312437754, ACCEPT 5312453178 (`objlittemplate` shape) | | `dispatcher-error-vocabulary.ts:520` `YOU_CANNOT_IMPERSONATE_ADMINS` row | 9968 | "The in-repo re-implementation of the vendor's impersonation handler, which admits an ADR-0068 platform admin, makes it reachable for the first time" | rulings 5353915975, 5380748215; contract review 5363785802 | | `dispatcher-error-vocabulary.ts:597` `OS_METADATA_CONVERTED` row | 12772 | "the artifact-ingestion forward-conversion policy, which runs the ADR-0087 conversions over an artifact built by older tooling before its strict parse" | os-dev-report 5448148598, review 5447759290 | | `dispatcher-error-vocabulary.ts:680` `owd_widening_forbidden` row | 9232 | "it rides the wire in TWO fields because the flat REST door narrows like every other door" | ruling 5315734845 (the flat door demotes too) | | `dispatcher-error-vocabulary.ts:690` same row | 9460 | "Invisible to BOTH vocabulary gates until the scan learned the code-carrying helper shape" | ACCEPT 5327577494, landing note 5327948790 (`codehelper` shape) | | `dispatcher-error-vocabulary.ts:696` same row | 9106, 9460 half (2) | "the call between renaming it and keeping the demote (the closed member in `code`, this spelling in `declaredCode`) is the `packages/spec` lane's; until that lane registers a code the standing demote answers this spelling, and the call is NOT decided here" | ruling 5307569301 (demote to `declaredCode`); triage 5326829216 (half 2 settled by the standing rule, registration is the spec lane's) | | `domains/activation-gate.ts:279` enablement refusal | 10243 (404) | "...for as long as it stays off, and the switch is not scoped to the caller's organization." | commit `266436a7f` (no organization wall scopes the enabled bit; one activation row per deployment) | | `domains/auth.ts:143` sanitised-500 log line | 5085 | "...answered with a sanitised 500: the message is withheld unconditionally, and this line is where the original error is read" | ACCEPT 5200514639 | | `resolve-project-database.ts:314` legacy-file notice | 6469 | "Reading legacy database file ... — dev, start and migrate now share one default, ...; migrate with: ..." | ruling 5225112344 (one default, legacy files read with a loud notice) | | `sandbox/body-runner.ts:126` missing-logger warning | 7448 | "Pass `logger` to ...BodyRunnerFactory({ … }): the capability writes only to that logger, never to `console`, so the host's level and sinks apply." | report 5251672873 (serve the capability from the factory's logger) and the function's own docblock | | `standalone-stack.ts:675` no-dispatch-arm guard | 3276 | "falling through to SQLite would hand the caller a database engine they never selected." | card body; commit `cfb549db8` | | `route-ledger.ts:321` discovery note | 5682 (a PR) | "...holds to the double assertion: the value parses against `DiscoverySchema`, and it carries no key the protocol does not declare" | PR 5682 body, comment 5198848731; stage 2's REST wording | | `route-ledger.ts:330`, `:332` analytics notes | 3584 | "...so the client moved to this route rather than the dispatcher growing an alias" / "...so the client aligned to the dispatcher rather than the dispatcher growing an alias" | landing commit `0bab8bb454` (analytics: client aligns to the dispatcher) | | `route-ledger.ts:402` publish note | 12038 ("ruling 5A") | "...re-exported into `/api`, never declared there a second time" | ruling 5434804846 item 5A; stage 2's wording | | `route-ledger.ts:405` publish-drafts note | 9406 ruling | "`probes` is deliberately opaque in the declaration, upgraded to a modeled schema only when a consumer needs a field of it" | ruling 5322875103 | | `route-ledger.ts:427` duplicate note | objectui 6593 | "(a console that read the envelope `success` reported a partial or empty duplicate as done)" | `DuplicatePackageResponseSchema` docblock and the client's `duplicate` comment, which record that defect | | `route-ledger.ts:435`, `:441` automation notes | 10243 ruling | "since the ruling that enablement is an authoring write" / "since the 2026-08-23 ruling that enablement is an authoring write" | commit `266436a7f` | | `route-ledger.ts:441` toggle note | 10145 | "The definition-write gate deliberately left this one out as engine state and filed the question" | landing commit `128684d500` ("toggle ... filed separately rather than folded into a security fix") | | `route-ledger.ts:435`, `:441` | 5519 | "the domain-wide anonymous floor" | card 5519 (anonymous 401 gate before dispatch, ACCEPT 5195955348); the clone row's existing wording | | `route-ledger.ts:497` dispatcher PUT note | 12702 | stage 2's twin wording: "...own active organization, so a tenant org admin authors their own org's overlays without platform-wide `manage_metadata`" | ACCEPT 5439334711 | | `route-ledger.ts:507` migrate-stored note | 4327 | stage 2's twin wording: "ADR-0087 stored-row canonicalization, the route form of `os migrate meta --stored`: it rewrites stored `sys_metadata` rows in place to their canonical form" | card body; commits `83cf2d3082`, `8aacf9456c` | | `route-ledger.ts:509` state/:field note | 9180 (x2), 10179 (404) | stage 2's twin wording: "Step 2 of the singular-segment ruling ...", "the maintainer re-weigh of the singular-segment ruling", "pins BOTH halves as behaviour so this note cannot quietly stop being true" | card 9180 ruling; landing commit `53a48c93f4` | | `route-ledger.ts:545` object-less action note | 3913 | "the object-less spelling of the global-action call, routed rather than refused" | comment 5117039474 (`'global'` canonical; `/actions//:action` routes instead of 400-ing) | | `route-ledger.ts:549` activation note | 7526 | "(the live-mount parity gate, which asks the running router, caught it)" | ACCEPT 5251269251 | | `route-ledger.ts:578` apps note | 5040 E5b | "on a match runs the full chain: the policy keys ..., then target delegation" (the program's stage labels go with the citation) | card 5040 program table | | `route-ledger.ts:584` apps note | 5040 E7 | "LIVE since publish flipped from refusing to executing" | card 5040 comment 5176642864 (E7 flip) | ### Citation only (the sentence already stated the decision) - `route-ledger.ts`: 12038 at `:408`, `:411`, `:414`, `:417` (ruling 3A), `:421` (ruling 4A), `:424`, `:427`, `:500` (ruling 1C), `:503`, `:507` (ruling 2C, stage 2's twin wording); 9406 at `:405`; 10145 at `:435`, `:470`, `:472`; 3801 and 5561 at `:447`; 3656 at `:476`; 3718 at `:480` (x2); 7019 at `:497` (stage 2's twin wording); 7526 at `:537`; 12160 at `:548`. - `dispatcher-error-vocabulary.ts:437`, `:456` (5085). - `endpoint-executor.ts:232`, `:255` and `endpoint-policy.ts:263` (5040 E7, section 7-3, section 3.3): the hints and the warning already say publish rejects the form, that `script` and `proxy` wait for their own rulings, and that `cacheTtlSeconds` is GET-only. - `sandbox/body-runner.ts:391` (4352), `:469` (4345). - `standalone-stack.ts:152`, `:423` (3276). ## Text only, proven on the AST A skeleton of each changed file's TypeScript AST, with every maximal string expression (a literal, a template, or a `+` chain of them) collapsed to its list of embedded non-literal expressions, is identical before and after for all 14 changed files (BASE `5e5ce48cef` against `a67666a600`; the later merge of `origin/main` touches none of them). Re-wording or re-splitting literal text cannot move it; an identifier, operator, property name, embedded expression or statement change does. Control: changing `ACTIVATION_DENY_STATUS` to `ACTIVATION_DENY_STATUS + 1` in a copy of `activation-gate.ts` moves the hash. A literal can still hide a code or a route, so every changed string chain was also paired against its old twin and located: 48 chains changed, and each sits in prose: 28 `note:` values and 6 `why:` values, 2 `hint:` values, 1 `notice:`, 6 log-call messages, 1 `deps.error` message argument (the code argument is an identifier, untouched), 2 `new Error` messages and 2 message builders' `return` values, plus 4 test assertions. Control: flipping one `disposition: 'sdk'` to `'server-only'` in a copy is reported as `prop:disposition`. ## The ledger `node scripts/check-doc-authoring.mjs --census-ledger`, written to a scratch file first so its no-growth check reads the committed baseline, then installed: | | occurrences | (file, id) pairs | files | |---|--:|--:|--:| | `packages/runtime` before | 67 | 41 | 11 | | `packages/runtime` after | 0 | 0 | 0 | | whole ledger before | 550 | 375 | 147 | | whole ledger after | 483 | 334 | 136 | 63 lines deleted, 0 added; every other row is byte-identical. After merging `origin/main` (`e18fea6dcd`) the recomputed ledger is byte-identical to the committed one. `pnpm check:doc-authoring`: before "463 pinned site(s) across 147 file(s) ... no growth, no burn-down unrecorded", after "408 pinned site(s) across 136 file(s) ... no growth, no burn-down unrecorded". ## Pins, and that they can fail Three tests asserted an id. Each now asserts the words that carry the decision, and each was ablated through `scripts/ablation-replace.mjs` (anchor must hit, blob must change, restore proven by blob equal to HEAD and an empty `git diff HEAD`). The tests import the source by relative path, so no build sits between the mutation and the run. | Pin | Asserts now | Ablation (source text removed) | Result | |---|---|---|---| | `http-dispatcher.actions-doubled-redirect.test.ts:101` | "the interim precedence the console renderer applies" | that phrase | 1 failed / 13 passed | | `http-dispatcher.actions-doubled-redirect.test.ts:102` | "a pair the contract refuses rather than ranks" | that phrase | 1 failed / 13 passed | | `endpoint-executor.test.ts:212` | "rejected at publish pending their own rulings" | that phrase | 2 failed / 48 passed | | `sandbox/body-runner.test.ts:369` | "`body` only runs for `type: 'script'`" | "only runs for" | 5 failed / 31 passed | Restored, the three files run 3 passed (3), 100 tests passed. Three of the first ablation attempts were refused by the tool before any test ran (the replacement text was a substring of the anchor, so its count could not rise, and the file was restored); they were re-run with distinct markers, and only the re-runs are reported above. ## What ships Measured on the built `dist/index.js` and `dist/index.cjs`: every rewritten non-ledger sentence is present once in each, and none of the old spellings is. `ROUTE_LEDGER` and `UNREGISTERED_CODE_SITES` are absent from both: the route ledger and the vocabulary table are runtime-internal and do not ship. So the changeset, `@objectstack/runtime` `patch`, covers the refusal, error, warning and notice text, and the ledger and vocabulary notes ride along unpublished. ## Verification Heavy runs went through `scripts/pm/os-verify-lock.sh` (slot `issue-20752-s3`); each verdict line reads `VERDICT command-exit 0`. - Build: `turbo run build --filter=@objectstack/runtime...` 30/30 before the merge; after merging `origin/main` (`e18fea6dcd`), the whole workspace except docs, 72/72. - `@objectstack/runtime`, both vitest projects (`local` + `repo`): 305 files passed (305), 5080 passed / 11 skipped, at `7eca8ebff6` and again at the merged head `5a3cfda26d`. 305 is every `*.test.ts` in the package. - `@objectstack/runtime` typecheck (`tsc --noEmit` and `check:test-typecheck`): exit 0, before and after the merge. - Consumers that import the route ledger as source: `@objectstack/client` `client-url-conformance`, `route-ledger-coverage`, `route-ledger-response-schema`: 3 files, 9 tests passed; `@objectstack/dogfood` `route-ledger-live-mount-parity.dogfood.test.ts`: 1 file, 8 tests passed (after the full build). None of them reads a `note`; `route-ledger.conformance.test.ts` (in the runtime run) checks that every non-`sdk` row still carries one. - `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (no paths), derived at `a67666a600`: 75 commands, each run at the merged head `5a3cfda26d` from the worktree with its exit code recorded before any pipe, 75/75 exit 0. The dist-reading nine (`check:dts-closure`, `check:dual-build-cjs-loads`, `check:entry-guard`, `check:lean-entry-closure`, `check:published-files`, `check:sourcemap-no-sources-content`, `check:test-source-alias`, `check:type-check-coverage`, `check:type-check-debt`) ran after the full build. `--ran`, which recomputed the same 75 at `5a3cfda26d`: "75 derived famil(ies) accounted for — 75 run, 0 NOT-MEASURED (a DERIVED zero — all 75 recorded an exit code and none of them is 3)". - `check:dispatcher-error-vocabulary`: "OK — 55 unregistered code-stamping site(s), all classified". `check:issue-citations`: "no issue citations added against e18fea6 (11 file(s) read)". `check:nul-bytes`: OK, 9854 files. - `pnpm lint` (`eslint . --no-inline-config`, repo-wide, not narrowed) at `5a3cfda26d`: exit 0, 127 s. ## Acceptance notes - **Two cited cards live in objectui and could not be opened here.** `objectui#6593` and `objectui#5933` answer 403 to this session (the repository is not enabled for it). Their decisions were read through the records in this repository that cite them: `DuplicatePackageResponseSchema`'s docblock and the client's `duplicate` comment (6593: a console that read the envelope `success` reported a partial or empty duplicate as done), and card 11519's body, ruling 5393507405 and the spec refusal message in `ui/action.zod.ts` (5933: the console renderer's interim declared-wins precedence). The rewritten sentences say no more than those records do. - **Comments keep their numbers.** Only strings are in the ledger; the `//` and docblock citations in these files are the sanctioned home for an id and are untouched. - **The checklist still reads true.** `docs/qa/platform-checklist/areas/access-security.json:2195` and `api-backend.json:1567` quote "functionally equivalent to deleting it" from the enablement refusal; that phrase is kept, so neither clause changes truth. - **Out of this stage's files:** `packages/lint/src/validate-action-body-writes.test.ts:316` pins `objectstack-ai#4345` in the lint rule's own message. That string is in the ledger's `packages/lint` row, not this stage's; it rides whichever stage takes that row. Noted, not filed. - **Stages left on the card:** 4 (`verify`, `plugin-dev`, `plugin-hono-server`, 14 occurrences) and 5 (`qa`, 102). --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
… tenancy refusals and no-API warning state each decision in words instead of a tracker number (stage 4) (objectstack-ai#21231) Part of objectstack-ai#20752 Clause-②: no **Stage 4 of 5 of the `domain:cli` lane under the maintainer's A / A ruling (5902360492): the `packages/verify`, `plugin-dev` and `plugin-hono-server` strings.** The card stays open for stage 5 (`qa`), so this PR carries no closing keyword. Text only: no status, error `code`, exit code, route, field, export, control flow, or `verify` verdict or count moves. ## What this does The `objectstack verify --rls` report, its persona-provisioning refusals and the records its probe writes, the verify harness's organizations remedy, the dev plugin's tenancy refusals and no-auth warning, and the Hono server's no-API warning sent the reader to a tracker number for the reason behind them. In form D, as stages 1 to 3 applied it (PR objectstack-ai#21172, PR objectstack-ai#21188, PR objectstack-ai#21219), the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words. All 14 ledgered occurrences in the claim's four files (claim `5938321786`), at 13 string sites: `rls.ts` 9 (1994 x3, 7685 x3, 7978 x3), `dev-plugin.ts` 3 (4818 x2, 3963 x1), `harness.ts` 1 (4719), `hono-plugin.ts` 1 (4073). Every cited card was read first; all seven answer, and each decision was cross-read against its landing commit. ### Rewritten in words | Where (head line) | Cited | The text now says | Decision read from | |---|---|---|---| | `rls.ts:908` report header | 1994 | `=== objectstack verify (RLS / cross-owner by-id-write invariant) — APP ===` | card 1994 (a by-id update or delete must pass the row-level write filter: a member can no longer change a record it cannot see), and this module's header, which names that invariant | | `rls.ts:768` `rls-hole` detail | 1994 | "...by-id write bypassed RLS, and a caller that cannot read a record must not be able to write it" | card 1994; the module header's "A user who CANNOT READ a record must not be able to WRITE it" | | `rls.ts:431` probe-persona refusal | 1994 | "...masked by the object gate and a by-id write that bypasses RLS is unreachable." | card 1994 for the class; the 7685 half is citation-only (below) | | `rls.ts:943` position-persona report line | 7978 | `── position personas (each holds one declared position and nothing else) — N of M declared position(s) probed` | card 7978 and landing commit `f5434b0ea4` (one persona per declared position, each holding that position and nothing else) | | `dev-plugin.ts:934-935` no-auth warning | 3963 | "...anonymous access to object data is always denied, with no setting that turns that off." | card 3963 decision A1 and landing commit `3c628ce647` (the `api.requireAuth` opt-out is retired; anonymous data access is denied unconditionally); stage 1's twin wording in `os serve` | ### Citation only (the sentence already stated the decision) - `rls.ts:357` probe RLS policy description and `rls.ts:464` probe `sys_permission_set` description (7685): each already says the probe holds object read+edit plus an owner-scoped narrowing so a refusal is the record gate's. Read from card 7685 item (i) and landing commit `be37f859bc`. The 7685 citation at `rls.ts:431` goes the same way. - `rls.ts:553` position-persona refusal and `rls.ts:575` `sys_user_position` reason (7978): each already says position-gated policies only apply to a persona holding the position. - `dev-plugin.ts:874` construct-stage refusal and `dev-plugin.ts:1000` init-stage refusal (4818): each already says `OS_ALLOW_DEGRADED_TENANCY` covers an absent multi-org runtime, not a present one that declined. Read from card 4818 and landing commit `29326f8eea`; stage 1 dropped the twin citation in `os serve` the same way. - `harness.ts:626` `bootStack` remedy (4719): it already says the app's declaration is what is checked and that a package reachable only through NODE_PATH or a hoisted store is not accepted. Read from card 4719 and landing commit `02dc076927`. - `hono-plugin.ts:683` no-API boot warning (4073): it already says the plugin is a transport adapter that serves neither API. Read from card 4073 and landing commit `e5a4d26901`. The `rls.ts` report header was on the ledger, so it is rewritten to name the invariant it proves rather than losing its anchor. ## Text only, proven on the AST A scratch script (not committed) parses each changed TypeScript file at BASE `7c5a311a58` and at head `36281b515a`, folds every `+` chain made only of string literals into one value, blanks every string value and template span, and compares the remaining node sequence (kinds, identifiers, numerals). Result: identical skeleton in all six files, string-value counts equal (dev-plugin 220, hono-plugin 126, harness 117, rls 212, the two tests 105 and 24), and 15 changed values, each of them prose: 2 `description:` values, 1 `reason:`, 1 `detail:`, 2 report lines, 4 `new Error` messages, 2 logger `warn` messages, 1 remedy string, and the 2 test assertions. The later merge of `origin/main` touches none of these files. The dev plugin's no-auth warning first gained a fifth literal; commit `03a490204b` re-wrapped the sentence across the original four so the skeleton stays equal. ## The ledger `node scripts/check-doc-authoring.mjs --census-ledger`, written to a scratch file first so its no-growth check reads the committed baseline, then installed: | | occurrences | (file, id) pairs | files | |---|--:|--:|--:| | the four rows before | 14 | 7 | 4 | | the four rows after | 0 | 0 | 0 | | whole ledger before (`7c5a311a58`, also `d6d6e872e5`) | 399 | 279 | 107 | | whole ledger after | 385 | 272 | 103 | 15 lines deleted, 0 added; every other row is byte-identical. After merging `origin/main` (`d6d6e872e5`) the recomputed ledger is byte-identical to the committed one. `pnpm check:doc-authoring`: before "338 pinned site(s) across 107 file(s) ... no growth, no burn-down unrecorded", after "325 pinned site(s) across 103 file(s) ... no growth, no burn-down unrecorded". ## Pins, and that they can fail Two tests asserted an id. Each now asserts the words that carry the decision, and each was ablated through `scripts/ablation-replace.mjs` on the committed fix (anchor must hit once, blob must change, restore proven by blob equal to HEAD and an empty `git diff HEAD`). Both tests import the source by relative path, so no build sits between the mutation and the run. | Pin | Asserts now | Ablation | Result | |---|---|---|---| | `plugin-dev/src/dev-plugin-optional-load-failure.test.ts:283` | "always denied, with no setting that turns that off" | "turns that off" to "turns that on" in `dev-plugin.ts` | 1 failed / 9 passed | | `plugin-hono-server/src/hono-transport-only.test.ts:92` | "transport adapter and serves neither" | "serves neither" to "serves nothing" in `hono-plugin.ts` | 1 failed / 4 passed | Restored, the two files run 10 passed and 5 passed. No other pin asserts any of the old strings: the whole repository was searched (`packages/**` including `qa` and dogfood, `examples`, `docs/qa/**`, snapshots and JSON). The dogfood RLS suites print `formatRlsReport` only as an assertion message, and `docs/qa/platform-checklist/RUNNER.md` quotes the summary lines and `N of M declared position(s) probed`, which are unchanged. ## What ships Measured on the built `dist/` of each package: every rewritten sentence is present (in both the ESM and CJS bundles) and no old spelling is. All three packages publish `dist`, so the changeset carries `patch` for `@objectstack/verify`, `@objectstack/plugin-dev` and `@objectstack/plugin-hono-server`. ## Verification (head `36281b515a`, after merging `origin/main` `d6d6e872e5`) Heavy runs went through `scripts/pm/os-verify-lock.sh` (slot `issue-20752-s4`); each verdict line reads `VERDICT command-exit 0` unless stated. - Build: `pnpm turbo run build` over the three packages and their dependency closures, 38/38 tasks. - Tests: `@objectstack/plugin-hono-server` 27 files / 324 tests, `@objectstack/plugin-dev` 9 / 86, `@objectstack/verify` 16 / 120, all passed (before and after the merge). The dogfood RLS runner oracle (`packages/qa/dogfood/test/rls-runner.test.ts`, which drives the `rls-hole` path against the rebuilt `@objectstack/verify` dist): 17 passed. - Typecheck: `typecheck` of all three packages, each `tsc --noEmit` plus `check:test-typecheck` (the test layer, the two pins included) OK. - Gates: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 71 commands; all 71 run with exit codes recorded; `--ran` reconciliation: "71 derived famil(ies) accounted for — 71 run, 0 NOT-MEASURED". `check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET (exit 3, no dist for unrelated packages); after a full workspace build (72/72 tasks, 71 cached) it passed and that rerun is the recorded result. - Lint, a proven narrowing: eslint's own config places 6 of the 8 changed paths in its population (`isPathIgnored` false and a matching config object; the changeset and the JSON ledger are outside it). `--no-inline-config` with the json formatter: 6 file results, 0 errors, 0 warnings. Invariance: this repo's config enables no type-aware linting (`eslint.config.mjs`, the note at lines 326-328) and its only disk reads are two baselines this diff does not touch, so no untouched file's verdict can move. The full `pnpm lint` is CI's. ## Acceptance notes - The seat's staging comment (5930275362) listed a dead tracker number in the `plugin-dev` test title `dev-plugin-security-enforcement-warning.test.ts:121` for this stage. Claim `5938321786` names four files and not that one, and a test title is not on the ledger, so it is not touched here. Carrier: the seat, when it stages what remains. - Code comments in the same four files still cite these cards. They are not on the ledger, and the claim keeps them out of scope. - `packages/qa/dogfood/test/enterprise-organizations.ts:184` carries the 4719 twin of the harness remedy. It sits in the `qa` row, which stage 5 owns. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
…rmance ledgers and harness refusals state each decision in words instead of a tracker number (stage 5a) (objectstack-ai#21250) Part of objectstack-ai#20752 Clause-②: no **Stage 5a of the `domain:cli` lane under the maintainer's A / A ruling (5902360492): every ledgered tracker number in the `packages/qa` strings outside the authz conformance matrix, plus the `plugin-dev` test title folded in from the dead-citation sweep.** The matrix file (76 occurrences) is stage 5b, which carries the closing keyword, so this PR has none. Text only: no expected status, error `code`, verdict, route, field, export or control flow moves. ## What this does The dogfood harness refusals (`assertArmed`, the build stand-in, the showcase security helper, the multi-org remedy), the expression and search conformance ledgers' summary and enforcement cells, nine fixture manifests and one permission-set label, and the downstream-contract manifest sent the reader to a tracker number for the reason behind them. In form D, as stages 1 to 4 applied it (PR objectstack-ai#21172, PR objectstack-ai#21188, PR objectstack-ai#21219, PR objectstack-ai#21231), the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words. All 26 ledgered occurrences in the claim's 16 files (claim `5940325318`), at 25 string sites, plus the one `plugin-dev` `describe` title the claim adds (not on the ledger). Every cited card was read first. Three answer 404 or cannot be read here and were read through their landing commits instead (below). ### Rewritten in words | Where (head line) | Cited | The text now says | Decision read from | |---|---|---|---| | `dogfood/test/armed.ts:136-138` empty-declaration refusal | 8074 | "...the exact defect class this helper exists to close, a fixture that passes while the control it measures is not engaged." | card 8074 (an org-less fixture cannot observe the gated write floor, so a real 403 records as a passing cell; direction 2: a helper that refuses) | | `dogfood/test/armed.ts:162` DISARMED refusal | 8074 | the bracketed tracker tag becomes `assertArmed():`, the same lead the empty-declaration refusal already uses; the rest of the sentence already said the assertions would pass without testing anything | card 8074 | | `expression-conformance.ledger.ts:144` `sharing-condition` summary | 1887 | "(ADR-0058 D3: compiled from the authored CEL, a faithful lowering rather than a divergent hand-written filter)" | card 1887 (the spec condition was never compiled; enforce or remove) and ADR-0058 D3 (the CEL condition compiles to `criteria_json`, a faithful lowering of the authored CEL) | | `expression-conformance.ledger.ts:293` `settings-visibility` enforcement | 7327 | "The spec DECLARES that same grammar (`SettingsVisibilityInputSchema`) rather than CEL, so it is refused at publish/parse too" | card 7327 (narrow the declaration to the grammar the save-time evaluator implements, measured 1 against 93) | | `expression-conformance.ledger.ts:303` `cel-action-param-option-visible` summary | 5016 | "(params[].options[].visibleWhen, the same per-option key a field's option list declares)" | card 5016, maintainer ruling B (reuse the field option vocabulary), landed as commit `f6609e6ae2`; `ui/action.zod.ts` records the per-key outcome (`visibleWhen` opened) | | `fixtures/attachments-fixture.ts:140` manifest | 2755 | "...exercising the non-admin attachment permission matrix: ..." | card 2755 item 2 (dogfood the non-admin attachment permission matrix) | | `fixtures/comments-fixture.ts:139` manifest | 4630 | "...exercising the record-level comment permission matrix: ..." | card 4630 (`sys_comment` gains record-level authorization, mirroring attachments) | | `fixtures/label-scope-fixture.ts:55` manifest | 3602 | "Deal → vendor lookup exercising the dimension-label read scope: a vendor the reader cannot read is shown by raw id, never by name." | card 3602's first residual (the per-record label read behind a grouped lookup carried no read scope); the fixture's own header | | `fixtures/rls-owner-fixture.ts:57` manifest | 1994 | "...exercising the cross-owner by-id-write invariant: a caller that cannot read a record must not be able to write it." | PR 1994 (a by-id write must pass the row-level write filter); stage 4's wording of the same invariant in `verify --rls` | | `fixtures/rls-owner-fixture.ts:105` permission-set label | 1994 | `RLS Fixture Member — owner-scoped reads only (no write policy: the by-id-write hole shape)` | PR 1994; the fixture header (owner policy on SELECT only, the hole class's authoring shape) | | `dogfood/test/showcase-security.ts:66` refusal | 5491 | "...the CLI wiring these fixtures model cannot be reproduced, and the platform baseline alone grants a member no object access" | card 5491, maintainer ruling of 2026-08-07 (the wildcard grant leaves `member_default`; the baseline is explicit-allow) | | `downstream-contract/src/stack.ts:19` manifest | 2035 | "Frozen third-party consumer gating spec backward compatibility: a spec change that needs this fixture edited to stay green is breaking." | landing commit `92647c13aa` (the downstream-consumer contract, frozen: a spec change that requires editing it is breaking), the work done under card 2035; the package README states the same contract | ### Citation only (the sentence already stated the decision) - `dogfood/test/build-shaped-artifact.ts:192`, `:225`, `:265` (6293, answers 404): the three refusals already say what a stand-in must do instead of `JSON.stringify` (fix the walk, never the assertion; a headless husk is what a plain stringify leaves; a function left in the artifact would be dropped without a sound). Read from landing commit `c39a911ae6` (fixtures get the real lowering, and the stand-in throws naming what went missing). - `dogfood/test/enterprise-organizations.ts:184` multi-org remedy (4719): the twin of the `verify` harness remedy that stage 4 rewrote citation-only. It already says the app's own declaration is what counts and a transitive reach is not enough. Card 4719 (option 2: the host declaration decides). - `expression-conformance.ledger.ts:293` (7310): "Fail-closed since" plus the card becomes "Fail-closed:"; the sentence goes on to say a predicate outside the grammar refuses the save. PR 7310. - `expression-conformance.ledger.ts:319` (objectui card 3067): "selection-bar bulk action per-record eligibility (bulkActionDefs[].visible)". This session has no read access to `objectstack-ai/objectui` (403) and the dispatch forbids attaching it, so the decision was read from this repository's record: `ui/bulk-action.zod.ts`'s `visible` describe (evaluated once per selected record; the button is offered when at least one passes) and the row's own enforcement cell, both unchanged. - `expression-conformance.ledger.ts:343`, `:350` (objectui card 2614): the two summaries already say "per-record visibility" and "per-record disabling" of the built-in row Edit/Delete. Read from this repository's landing commit `627f225f2c` (`userActions.edit/delete` accept per-record CEL predicates). - `fixtures/email-template-materialization-fixture.ts:59` (4509) and `fixtures/webhook-materialization-fixture.ts:55` (3461): each already says the stack entries materialize into the rows the runtime reads; `ADR-0054` stays. - `fixtures/flow-durable-suspend-fixture.ts:101` (4470): already says the flow suspends, persists and resumes after a cold boot. - `fixtures/flow-function-effect-fixture.ts:69` (4396): already says the declared effect reaches the run summary. - `fixtures/flow-runas-fixture.ts:125` (1888): already says `flow.runAs` identity is enforced. - `dogfood/test/search-conformance.ledger.ts:49` (4254): already says a name outside the set is a 400 at the REST ingress, not silently dropped. - `plugin-dev/src/dev-plugin-security-enforcement-warning.test.ts:121` `describe` title (10036, answers 404; the fold site from the dead-citation sweep, ACCEPT `5939681859`): the bracketed tag goes; the title already says the warning must fire when `SecurityPlugin.start()` bailed. Read from landing commit `7552e03375` (the warning probes the published `security` service in `start()`). ## Text only, proven on the AST A scratch script (not committed) parses each changed TypeScript file at BASE `a7d9768ecd` and at `75e33f4c45` (the stage commit; the later merge of `origin/main` touches none of these files), blanks every string value and template span, and compares the remaining node sequence (kinds, identifiers, numerals). Result: identical skeleton in all 17 files, equal node and string-value counts per file, and 28 changed string values, all prose: the 26 sites above plus two neighbouring literals of the `armed.ts:136-138` refusal, re-wrapped so the message keeps its four literals. ## The ledger `node scripts/check-doc-authoring.mjs --census-ledger`, written to a scratch file first so its no-growth check reads the committed baseline, then installed: | | occurrences | (file, id) pairs | files | |---|--:|--:|--:| | the 16 stage rows before | 26 | 21 | 16 | | the 16 stage rows after | 0 | 0 | 0 | | `authz-conformance.matrix.ts` (stage 5b) | 76 | 46 | 1 | | whole ledger before (`a7d9768ecd`) | 385 | 272 | 103 | | whole ledger after | 359 | 251 | 87 | 53 lines deleted, 0 added; every other row is byte-identical, the matrix row included. After merging `origin/main` (`ef96c9ede7`) the recomputed ledger is byte-identical to the committed one. `pnpm check:doc-authoring`: before "325 pinned site(s) across 103 file(s) ... no growth, no burn-down unrecorded", after "300 pinned site(s) across 87 file(s) ... no growth, no burn-down unrecorded". ## Pins No test asserts any of the old strings: every rewritten fragment and every cited number inside an assertion was searched across the repository, with no hit outside the sites themselves. The two existing pins that read rewritten messages, `armed.dogfood.test.ts` matching `this fixture is DISARMED` and `arming declaration is EMPTY`, still match, and pass. With nothing re-pointed there was no pin to ablate. The `merge-queue-triage` job-log fixtures under `scripts/fixtures/` quote the old `describe` title as recorded CI output; they are history and stay as they are. The run itself shows one rewritten string live: the multi-org skip line in the dogfood run now prints "...being reachable as somebody else's transitive dependency is not enough. Set OS_TEST_MULTI_ORG_ENABLED=1...". ## What ships Nothing. `@objectstack/dogfood` and `@objectstack/downstream-contract` are `private: true`. `@objectstack/plugin-dev` publishes `dist`, and its built `dist/` holds the new `describe` title 0 times; the control, the warning's own `NOT enforced` text, is found in `dist/index.js`. So no changeset, and the PR takes `skip-changeset`. The downstream-contract fixture is frozen against spec-driven edits (its README). This edit is prose in the manifest description, made for this ruling and not to make a spec change pass. ## Verification (head `3efe6499b8`, after merging `origin/main` `ef96c9ede7`) Heavy runs went through `scripts/pm/os-verify-lock.sh` (slot `issue-20752-s5a`); each verdict line reads `VERDICT command-exit 0`. - Build: `pnpm turbo run build` over the dependency closures of `@objectstack/dogfood`, `@objectstack/downstream-contract` and `@objectstack/plugin-dev`, 63/63 tasks, before and after the merge. - Tests, before and after the merge: `@objectstack/plugin-dev` 9 files / 86 tests passed (the renamed `describe` ran under the verbose reporter with its 4 tests green); `@objectstack/downstream-contract` 3 / 31 passed; `@objectstack/dogfood`, every test file that imports a changed module directly (46 files, run in two batches): 45 passed, 1 skipped (`rls-multitenant`, which needs the enterprise organizations package this repository does not ship), 388 tests passed, 3 skipped. The full dogfood suite is CI's `Dogfood Regression Gate`. - Typecheck: `@objectstack/dogfood` (`tsc --noEmit`; `--listFiles` shows all 15 changed dogfood files in its program), `@objectstack/downstream-contract`, and `@objectstack/plugin-dev` (`tsc --noEmit` plus `check:test-typecheck`, which compiles the test layer: OK). - Gates: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 67 commands; all 67 run with exit codes recorded; `--ran` reconciliation: "67 derived famil(ies) accounted for — 67 run, 0 NOT-MEASURED". `check:dual-build-cjs-loads` first answered PREREQUISITE NOT MET (exit 3, no dist for unrelated packages); after a full workspace build (72/72 tasks, 71 cached) it passed, and that rerun is the recorded result. - Lint: the full `pnpm lint` (`eslint . --no-inline-config`) at `3efe6499b8`: exit 0, no findings. ## Acceptance notes - `expression-conformance.ledger.ts:322` is a comment that says the bulk row "reached the ledger in" one card "not" another, both spelled bare. The second is objectui's card, and a bare number reads as this repository's. It is a comment, not on the ledger, so it is untouched here. Carrier: the dead-citation sweep. - `packages/qa/downstream-contract/package.json`'s `description` and its README still cite card 2035, and dogfood test titles and `it` names still carry tracker numbers. None of these is on the ledger or the claim. Carrier: the seat, when it stages what remains after 5b. - Code comments in the 17 files still cite these cards. They are not on the ledger, and the claim keeps them out of scope. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ Co-authored-by: Claude <noreply@anthropic.com>
…n words instead of a tracker number (stage 5b) (objectstack-ai#21265) Fixes objectstack-ai#20752 Clause-②: no **Stage 5b, the last stage of the `domain:cli` lane under the maintainer's A / A ruling (5902360492): the authz conformance matrix.** Stages 1 to 5a (PR objectstack-ai#21172, PR objectstack-ai#21188, PR objectstack-ai#21219, PR objectstack-ai#21231, PR objectstack-ai#21250) emptied every other row of this lane; this PR empties the last one, so the lane's share of the ledger burn-down is zero once it lands. Text only: no row id, `state`, `covers` key, `proof` file or enforcement site moves, and the file's code comments are untouched. ## What this does The `summary`, `enforcement` and `note` strings of `packages/qa/dogfood/test/authz-conformance.matrix.ts` sent the reader to a tracker number for the reason behind a row. In form D, as the earlier stages applied it, the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words. All 76 ledgered occurrences of 46 cards, at 43 string sites (claim `5942403129`, ledger read at `a23be7498e`): 16 `summary`, 10 `enforcement`, 17 `note` strings. 22 sites now say something in words; 21 already stated the decision and only lose the citation. Every cited card was read first. Six answer 404 and were read through the commit that decided them: 10243 (`266436a7f`), 12176 (`7986d973f`), 11757 (`4d25d22d4`), 8710 (`04d03c3a0`), 8711 (`2ce1eb41b`) and 8811 (`d6e793507`). The 10145 and 10243 phrases on the automation row reuse stage 3's `route-ledger.ts` wording for the same decisions, and the 5519 floor is named the way stage 3 named it (the domain-wide anonymous floor, refused before dispatch). Two notes cite `describe` titles in other packages that carry a bracketed tag. Those titles are quoted without the tag, so each quote stays a literal substring of the real title and is still findable by search. ### The 43 sites (line numbers are the same at base and head) | Site | Cited | Form | The text now says (new fragment) | Decision read from | |---|---|---|---|---| | `:161` `rls-by-id-write` summary | 1994 | citation only | by-id write enforcement | PR 1994 (RLS re-checked on the pre-image of a by-id update/delete) | | `:162` `rls-by-id-write` enforcement | 7665 | citation only | write-scope DERIVATION: when no update/delete-class policy applies | card 7665 via PR 7792 (an empty write-class policy set derives its scope from the caller's select narrowing); the sentence after the colon already says it | | `:164` `rls-by-id-write` note | 7685, 7665, 7665, 7792, 7685 | words (tag dropped; three phrases worded) | Re-verified ... — that was the hole through which a contributor PATCHed records it could not read, and ... since the fix that derives a missing write scope from the select narrowing, that file carries ... whose probe persona holds object read+edit narrowed by select-only RLS and so reaches this class — | card 7665 (a by-id write was not gated by record visibility under select-only RLS); PR 7792 (option A: derive the write scope from the select narrowing); card 7685 comment 5264791326 (measurement first: a probe persona with object read+edit narrowed by select-only RLS; both rows stay enforced) | | `:174` `controlled-by-parent` note | 7685, 7665 | tag dropped; one phrase worded | Re-verified as `enforced` on its OWN evidence ... when the select-derived write-scope derivation its master depends on is ablated. | card 7685 comment 5264791326 (re-verified by measurement, not downgraded); card 7665 / PR 7792 | | `:177` `multi-tenant-write-postimage` summary | 2937 | words | (forged INSERT / Finding 1 re-point — a forged OR re-pointed organization_id cannot cross the tenant wall) | card 2937 (an INSERT carrying a forged organization_id crossed the tenant wall; Layer 0 gains an insert post-image check) | | `:179` `multi-tenant-write-postimage` note | 2937, 2937, 2937 | citation only + suite titles named in words | INSERT a forged cross-tenant organization_id or UPDATE ... (the "Layer 0 insert post-image tenant guard" suite + the Finding 1 "Layer 0 update post-image tenant guard (cross-tenant re-point)" suite) | card 2937; the two describe titles in plugin-security/authz-matrix-gate.test.ts, quoted without their bracket tags so each stays a substring of the real title | | `:181` `multi-tenant-exemption-posture` enforcement | 2956 | citation only | reads the carried ctx.posture rung (ADR-0099 D1) | PR 2956 (carry the derived posture rung on ExecutionContext); the phrase 'the carried ctx.posture rung' already says it | | `:182` `multi-tenant-exemption-posture` note | 2937 | suite title named in words | (the Finding 2 "Layer 0 cross-tenant exemption requires the platform posture" suite + "ADR-0099 P1 ...") | the describe title in plugin-security/authz-matrix-gate.test.ts | | `:195` `org-write-validation` note | 2937 | words | — the forged-organization_id INSERT defect one call site down. | card 2937 | | `:213` `anonymous-deny-meta` summary | 2567 | words | (uniform anonymous posture, surface 1) | card 2567 (the anonymous-deny posture must be uniform across every HTTP surface that reaches ObjectQL) | | `:228` `anonymous-deny-meta` note | 11373, 12176 | citation only + words | For most of this row's life ... five since the retirement of slash-bearing metadata item names un-mounted the compound save | card 11373 (measure first; the note goes on to state the measured refusal); card 12176 answers 404, read through landing commit 7986d97 (stage 3 of the ruled retirement of slash-bearing metadata item names: un-mounts the compound arities) | | `:236` `anonymous-deny-actions` summary | 2567, 5519 | words | (uniform anonymous posture, surface 2: refused 401 before dispatch, as `/data` and `/meta` are) | card 2567; card 5519 (anonymous /actions and /automation requests are refused 401 before dispatch, the same baseline as /data and /meta) | | `:244` `anonymous-deny-actions` note | 5519 | words | — before the gate, an anonymous `POST /actions/showcase_task/showcase_mark_done/:id` was measured answering 200 with the update applied. | card 5519 | | `:245` `anonymous-deny-automation` summary | 2567, 5519 | words | (uniform anonymous posture, surface 3: refused 401 before dispatch, as `/data` and `/meta` are) | card 2567; card 5519 | | `:246` `anonymous-deny-automation` enforcement | 10145, 10243, 7900, 3801, 5561 | words (stage 3 route-ledger twin wording) | DELETE /:name`, the definition writes on the metadata plane, plus enablement `POST /:name/toggle` since the 2026-08-23 ruling that enablement is an authoring write, ... the run-state reads (the `sys_automation_run` read grant) and `resume` (keyed on the node the run is suspended on, fail-closed for a node that declares no resumeAuthority) carry their own | card 10145 (flow definition writes are authored metadata, so manage_metadata gates them); card 10243 answers 404, read through landing commit 266436a (enablement is an authoring write, ruling of 2026-08-23); card 7900 via commit 627e65a (run-state reads consult the sys_automation_run read grant); card 3801 (resume gated on the suspended node); card 5561 (no declared resumeAuthority fails closed); the 10145 and 10243 phrases reuse stage 3's route-ledger.ts wording | | `:253` `anonymous-deny-automation` note | 5519 | words | which the original anonymous-surface report did not record. | card 5519 | | `:263` `anonymous-deny-packages` summary | 7033, 7023 | citation only | anonymous-deny on the package-management surface | cards 7033 and 7023 (the /packages domain carried no authorization predicate; the row's enforcement already states the domain-wide gate); same shape as the sibling analytics row | | `:300` `realtime-delivery-authz` summary | 2992 | words | (a latent surface: identity admission is owed before any client transport ships) | card 2992 (GraphQL and realtime must satisfy identity admission before a client transport ships) | | `:302` `realtime-delivery-authz` note | 9083, 9083 | tag dropped + words | Clearing that red ... Before that admission rule landed, this note promised a gate that did not exist | card 9083 (a TRANSPORT-WIRED key may be classified only by an enforced row); the sentence after the tag already says it | | `:310` `mcp-http-identity` enforcement | 2698 | citation only | (403 on none) | card 2698 (OAuth 2.1 for /api/v1/mcp, scope-gated tool families); the sentence already says it | | `:320` `mcp-http-identity` note | 3167 | citation only | proven end-to-end: the proof boots | card 3167 (identity admission first); the sentence after the colon states what the proof drives | | `:332` `readonly-static-write` summary | 2948, 3003, 3043 | citation only + words | UPDATE AND INSERT (first at the data-write ingress; in-engine ...) | cards 2948 and 3003 (strip static readonly on non-system UPDATE); card 3043 (tighten the INSERT exemption, first enforced at the data-write ingress) | | `:333` `readonly-static-write` enforcement | 2948, 5591 | citation only | (caller-supplied VALUES only — ... the caller also sent) | card 2948; card 5591 (strip the caller-supplied value, never a hook stamp); the parenthetical already says both | | `:335` `readonly-static-write` note | 3003, 3043, 3003 | words | The originating field report: ... The INSERT face followed: ... a step SHORTER than the draft-then-PATCH route, | card 3003 (readonly was UI-only; a non-admin self-approved by PATCH); card 3043 (the INSERT exemption let the same caller POST an approved record) | | `:340` `declarative-rbac-seeding` summary | 2077 | citation only | seeded at boot | card 2077 (activate declarative roles + sharingRules at runtime) | | `:355` `ownership-anchor-guard` summary | 3004 | citation only | without the transfer grant | card 3004 (owner_id is system-managed for non-privileged writers) | | `:358` `bulk-write-owner-scoping` summary | 2982 | citation only | not just single-id writes | card 2982 (bulk writes owner-scoped on OWD-private objects) | | `:361` `public-form-managed-anchors` summary | 3022 | citation only | (owner_id / organization_id / audit / id) | card 3022 (a public-form submit cannot supply owner_id or other server-managed anchors) | | `:362` `public-form-managed-anchors` enforcement | 3004 | words | complements the step 3.5 owner-anchor guard | card 3004 | | `:378` `hierarchy-widening` enforcement | 7807 | citation only | the runtime was narrowed to the declaration | card 7807 (business_unit expands exactly one unit, as declared) | | `:381` `rls-compiler-fail-closed` enforcement | 4983 | citation only | hoisted out of plugin-security so lint/... | card 4983 (wire the ADR-0056 D4 authoring gate to the one predicate definition) | | `:385` `secure-by-default-posture` enforcement | 11757 | words | (the gate's other carrier, sys_scim_provider, retired once the stable SCIM line stopped deriving a provider model) | card 11757 answers 404, read through landing commit 4d25d22 (retire the rc.1-era sys_scim_provider; stable @better-auth/scim derives no scimProvider model) | | `:387` `flow-run-as` summary | 1888 | citation only | under the run's effective identity | card 1888 (enforce runAs) | | `:390` `flow-run-as` note | 1888 | words | but its enforce-or-remove decision chose ENFORCE and implemented it for flow data nodes | card 1888 (decision required: enforce or remove; enforced) | | `:420` `permission-set-active` summary | 8613 | citation only | (ADR-0049) | card 8613 (the active flag on both grant catalogues is enforced) | | `:422` `permission-set-active` note | 8613 | citation only | "the `active` flag on the grant catalogues (ADR-0049)" | card 8613; the describe title in core/security/resolve-authz-context.test.ts, quoted without its tag so it stays a substring of the real title | | `:423` `position-active` summary | 8613 | citation only | (ADR-0049) | card 8613 | | `:443` `grant-validity-window` enforcement | 10982 | citation only | accessible_org_ids and the org-administration role projection | card 10982 (window-filter the role projection too); the present-tense list already says it | | `:445` `grant-validity-window` note | 8811, 8711, 8710, 10982, 11089, 10982, 9377, 7976 | tags dropped + words | NO `covers` ... Per the 2026-08-15 maintainer ruling ... by the 2026-08-15 ruling that access-conferring paths filter and addressing paths do not, because approval ROUTING ... `sys_member` ... last-admin-guard.ts's ... the role projection as window-filtered now; ... is now cited: ... the mutual-attribution contract (a cited proof file names the rows it proves) is satisfied. | 8811 (404) via d6e7935 (adds this row); 8711 (404) via 2ce1eb4 (ruled narrowing of the completeness claim to routes); 8710 (404) via 04d03c3 (ruling 2026-08-15: access-conferring paths filter deactivated positions, addressing paths do not); card 10982; card 11089 (the last-admin-guard note went stale after 10982); card 9377 (cite delegation-of-duty as this row's proof); card 7976 (a proof file names the rows it proves, checked both ways) | | `:452` `agent-visibility` summary | 1901 | citation only | listing scope | card 1901 (agent visibility not enforceable without owner/org anchors; removed per D8) | | `:453` `agent-visibility` note | 1901, 1884 | citation only | `visibility` deleted) ... at the chat route; | card 1901; card 1884 (enforce access/permissions at the chat route) | | `:462` `requireAuth-removed` note | 3963, 7976 | words + tag dropped | ADR-0056 D2, completed by deleting the switch once every session-less surface was declared: the `requireAuth: false` opt-out is RETIRED ... The `showcase-anonymous-deny.dogfood.test.ts` CITATION WAS DROPPED | card 3963 (step 1: public as a declared capability; step 2: delete api.requireAuth, an auth-less stack fails at boot); card 7976 (mutual attribution, already named in the sentence) | | `:466` `allow-transfer-restore-purge` note | 1883, 3004, 12497, 1883 | words + citation only | ADR-0049 → roadmap M2, which builds the lifecycle ops and their RBAC bits as one batch: the ops still do not exist ... owner_id door. ... RETIRED 2026-08-26 (maintainer ruling: retire the two bits now rather than carry them unenforceable until M2): | card 1883 (M2: build undelete/purge and their RBAC bits in one batch); card 3004; card 12497 (ruled 2026-08-26: retire allowRestore/allowPurge, the keys return with M2) | ## The ledger `scripts/doc-authoring-prose-id.baseline.json`, recomputed with `node scripts/check-doc-authoring.mjs --census-ledger` (refuses any growth): | | occurrences | (file, id) pairs | files | matrix row | |---|--:|--:|--:|--:| | base `434c6c7cab` | 359 | 251 | 87 | 76 occurrences / 46 cards | | head | 283 | 205 | 86 | absent | - Exactly the matrix row leaves: 48 lines deleted, 0 added. The other 86 rows compare equal as JSON. - Recomputed again after merging `origin/main` (`8dea55d314`): byte-identical. - `pnpm check:doc-authoring`: exit 0 before and after. Its cross-package line reads 300 pinned sites across 87 files at base and 257 across 86 at head, "no growth, no burn-down unrecorded". - The census's other 257 sites are the same before and after (file, line and ids). ## Text only A scratch TypeScript-AST comparison of the file at base `434c6c7cab` and head: 1695 skeleton nodes on both sides, identical; 280 string literals on both sides, 43 values changed, owned by `summary` 16, `enforcement` 10, `note` 17 and nothing else; 255 comment trivia blocks on both sides, byte-identical. Its three controls each behave: a renamed property key reads "skeleton DIFFERS", a changed `state` value is reported as owned by `state`, and an edited comment reads "not identical". ## Pins None. Nothing mechanical reads these three fields: the companion test imports the rows for `id`, `state`, `proof`, `covers` and `enforcement` presence, and the census and blind-spot test read only the header docblock and the `covers` arrays. A whole-repo fixed-string search for the text around each of the 76 removed ids (224 fragments, `docs/qa/**` included) found 12 fragments with hits, every one a code comment, a release-owned CHANGELOG entry, a `describe` title or a liveness note, none asserting this file's text. So no pin moved and no ablation is owed. ## Verification - Build: `pnpm turbo run build --filter='@objectstack/dogfood^...' --concurrency=2`: 63/63 tasks. - Tests: `pnpm --filter @objectstack/dogfood exec vitest run --maxWorkers=2 test/authz-conformance.test.ts test/authz-probe-blind-spot.test.ts`: 2 files, 90 tests passed. These are the only consumers of the module and of its text. - Typecheck: `pnpm --filter @objectstack/dogfood typecheck` exit 0; `--listFilesOnly` lists the matrix file and the companion test. - Gates: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` at `3005a8b6d1` derives 54 commands; all 54 run, exit 0. `check:dual-build-cjs-loads` first answered exit 3 (PREREQUISITE NOT MET: 8 unrelated packages without `dist/`); after a full workspace build (72/72) it exits 0 and that rerun is the recorded result. `--ran` reconciliation: 54 derived, 54 run, 0 NOT-MEASURED, 0 UNRUN. - Lint: full `pnpm lint` at `3005a8b6d1`: exit 0, no findings. - Tests and typecheck ran at `0dbdbd759b`; the merge after it brought one `docs/adr` file and no package input. ## Changeset None, with `skip-changeset`: `@objectstack/dogfood` is `private: true`, and the ledger is a repository script file. No `.changeset/*.md` is touched. ## Acceptance notes - **Code comments** in the matrix file still cite cards (33 comment lines, the bracketed tracker tags and the block-comment headers among them). They are not runtime strings and not on the ledger, so they are outside ruling 5902360492 and this claim. Noted, not filed. - **Two `describe` titles the notes quote** carry a bracketed tag: `plugin-security/src/authz-matrix-gate.test.ts` (the Layer 0 insert, update and exemption suites) and `core/src/security/resolve-authz-context.test.ts` (the `active`-flag suite). Test bodies are outside the gate's reading and belong to those packages, not to this lane. Noted, not filed. --- _Generated by [Claude Code](https://claude.ai/code/session_01VvcEokUG1tvVxkceYfR5XB)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Part of #20752
Clause-②: no
Stage 1 of 5 of the
domain:clilane under the maintainer's A / A ruling (5902360492): the CLI,cloud-connectionandtypesstrings. The card stays open for stages 2-5, so this PR carries no closing keyword. Text only: no exit code, errorcode, flag, field, HTTP status, export or control flow moves.What this does
CLI help text, warnings, refusals and two harness errors sent the reader to a tracker number for the reason behind them. In form D, as the stages of the engine lane applied it, the number goes. Where the sentence already said what was decided, only the citation goes. Where it leaned on the number, it now says the decision in words.
compile.ts:753,validate.ts:689step linecompile.ts:824warning headerdb/clean.ts:33--databasehelpdoctor.ts:1406retired-key rowreferenceFilterswas removed from FieldSchema as a key no runtime read (ADR-0049 enforce-or-remove)"meta/resync.ts:71skip explanationmanaged_by: 'platform', forward only)meta/resync.ts:104command descriptionmigrate/duplicates.ts:859descriptionmigrate/duplicates.ts:861descriptionmigrate/multi-value-columns.ts:332descriptionmigrate/recorded-by.ts:69descriptionmigrate/summary-nulls.ts:81descriptionserve.ts:4393no-auth refusalserve.ts:5953organizations remedystorage/orphans.ts:173closing linetest/helpers/serve-process.ts:569harness errorserve.tsPUBLISHES, which must be the port it BOUND, so this is a regression"cloud-connection-route-ledger.ts:205/228/238/248notesmanage_metadata, never merely a signed-in session" (commit e0695b5 dropped the any-session gate)cloud-connection-route-ledger.ts:215noteinstalledBy/storageDir(a user id and a host filesystem path) are served only to amanage_metadataholder" (commit 01074e5)cloud-connection-route-ledger.ts:264notetypes/src/node.ts:383undeclared-package notefallbackImport, so the fallback resolves from the caller instead" (commit 46d34ab)Citation only (the sentence already stated the decision):
dev.ts:233(5148),doctor.ts:221(5673),doctor.ts:1544(5397),migrate/duplicates.ts:858(8928: the third sentence already says it never rewrites),serve.ts:6021(4818),storage-driver.ts:149and:349(3276),serve-process.ts:546(12525),node.ts:393(4719).Every cited card was read (REST, open or closed) before its string was rewritten. Three answer 404: 8692, 9011 and 10943. They were read through their landing commits (712e185, 01074e5, 46d34ab) and today's docblocks.
Three ledger entries that were not tracker numbers
serve.tscarried#111,#666and#444: the 3-digit CSS colours of the unknown-hostname 404 page, the false-positive family the gate's own docblock names. They now read#111111,#666666and#444444, the remedy that docblock prescribes. The page renders the same.The source-hash header producer (patch round under claim amendment
5931899236)packages/cli/src/utils/i18n-extract.ts:2354held the last two of the stage's occurrences (#12069,#8765). That literal is the headerrenderSourceHashModulewrites as line 8 of everyLOCALE.source-hashes.generated.ts. The seat carried it in this stage, answer A to the first report's open question.d60b295649: the header now states what the two rulings decided, applied to the generated half. A leaf whose digest no longer matches its source is stale and serves the source text instead. The commit sha09b4f4e4estays as provenance.9d5f33f929: the 27 companions in 9 packages were regenerated withnode scripts/check-i18n-bundles.mjs --write, never by hand.@@ -8 +8 @@hunk each, +27 / −27, with one distinct removed line and one distinct added line.check:i18n: 9 packages in sync.check:i18n-stale-fill: no new stale fills.dist, against a positive control of 2 to 8. So they take no changeset.@objectstack/cliships the literal, and its existingpatchchangeset covers it.Re-pins
meta/resync-skip-explanation.test.ts:44asserted#8692. It now asserts "before the seeder began stamping its default sets 'platform'". Ablation on the committed head (scripts/ablation-replace.mjs, WRAP mode): with that phrase replaced inresync.tsthe file gave 1 failed / 6 passed; after the restore the blob matched HEAD (a16ba21993) andgit diff HEADwas empty.test/build-json-undeclared-key-parity.e2e.test.ts:288asserted the(#3786)header. It now asserts the new header. Ablation: the first attempt used a replacement that was a substring of the anchor, and the tool refused it before running anything (count 2 → 2). The second, with a distinct marker, gave 1 failed / 5 passed. The restore matched HEAD (7d24c878b8) withgit diff HEADempty. The docblock transcript at line 12 keeps the old text, because it records a measurement at4ceae8ab0.Ledger (
scripts/doc-authoring-prose-id.baseline.json)Recomputed with
node scripts/check-doc-authoring.mjs --census-ledger(exit 0, no growth refusal) into a scratch file, then copied into place. The diff deletes 63 lines and adds none. Every row outside the three packages is byte-identical. After mergingorigin/main(e35c40a525) the recomputed ledger was byte-identical to the committed one.fde553c509)packages/clipackages/cloud-connectionpackages/typespnpm check:doc-authoring: before, "521 pinned site(s) across 169 file(s) ... no growth, no burn-down unrecorded"; after the first round, "488 pinned site(s) across 153 file(s)"; after the patch round, at9d5f33f929, "487 pinned site(s) across 152 file(s) ... no growth, no burn-down unrecorded". The patch round's ledger diff deletes 4 lines (thei18n-extract.tsrow) and adds none.Changeset
.changeset/20752-cli-strings-state-the-decision.md:patchfor@objectstack/cliand@objectstack/types.There is no
@objectstack/cloud-connectionentry, because the route ledger is not published. It is package-internal guard data thatindex.tsdoes not import. Measured after the build: "never merely a signed-in session" andCLOUD_CONNECTION_ROUTE_LEDGERare each in 0 files underpackages/cloud-connection/dist, while the positive controlinstall-localis in 6. The same measurement on@objectstack/cli: each new sentence is indist, each old one (capability providers (#3366),dropped at load (#3786),see issue #10950,always denied (#3963),color: #666;) is in 0 files.test/helpers/serve-process.tsis not indist.Text-only proof
A TypeScript-AST skeleton of each changed
.tsfile comparesfde553c509with2ef3c98a9d. In the skeleton every string literal and template text is one placeholder, consecutive literal operands of a+chain merge, and comments are never read. Result: 18 of 18 SAME, with token and literal-slot counts identical per file. Control: the same tool reports DIFF oncli/src/utils/schema-migrate.tsacrossf20f669e17, a real code change.Tests
turbo run buildover./packages/*and./packages/*/*under the verify lock, 71/71, before and again after the merge.@objectstack/cliunit tier: 242 files / 3,435 tests passed, before and after the merge.typecheckexit 0, includingcheck:test-typecheck.@objectstack/cliintegration tier (owed because the diff touchestest/helpers/serve-process.ts): 69 files / 599 passed, 1 skipped, on2ef3c98a9d. Not re-run after the merge; the merge bringsstart.tsand a nightly e2e file, neither touching these strings.OS_TEST_TIERS=nightly), the two e2e files that assert the changed strings:build-json-undeclared-key-parity.e2e.test.tsandserve-port-readback.e2e.test.ts, 19/19 passed.@objectstack/types: 23 files / 692 tests;@objectstack/cloud-connection: 30 files / 397 tests (the route-ledger conformance guard included); bothtypecheckexit 0.Gates
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands(no paths) atc023fa0d00derived 73 commands. All 73 ran one at a time from the worktree, each exit 0.--ranreports "73 derived famil(ies) accounted for — 73 run, 0 NOT-MEASURED". Among them:check:doc-authoring(above),check:i18n(9 packages in sync),check:issue-citations,check:nul-bytes,check:dts-closure(71 packages swept) andcheck:dual-build-cjs-loads.Narrowed lint:
eslint --no-inline-config --format jsonover the 18 changed.tsfiles reported 18 files, 0 errors, 0 warnings (counts from eslint's JSON). The resolvedparserOptionsareecmaVersion: latest, sourceType: module, with noprojectorprojectService, so no type-aware rule runs and this diff cannot move an untouched file's verdict. Repo-widepnpm lintis CI's.NOT MEASURED locally (CI's): the Test Core shards, Dogfood, Build Core and the workspace type-check lanes. The integration tier was not re-run on the merge commit.
Acceptance notes
printStepargument incompile.tsandvalidate.ts, in the patch round (d60b295649):content/docs/deployment/cli.mdx:608,content/docs/deployment/validating-metadata.mdx:699andcontent/docs/ui/react-pages.mdx:393.check:docs-transcript-driftpasses.cli.mdx, around line 1485 and later, so the hunks are disjoint.docs/audits/...is a dated record and stays.packages/cli/CHANGELOG.mdquotes old lines in released entries. That file is release-owned and untouched.origin/mainwas merged once (e35c40a525). It touchescli/src/commands/start.tsand adds a nightly e2e file, and shares no file with this PR.Generated by Claude Code