Repository navigation
Commit 7c5a311
fix(runtime): runtime refusals, warnings and route-ledger notes state each decision in words instead of a tracker number (stage 3) (#21219)
Part of #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 #21172, PR #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 `#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>1 parent e47355b commit 7c5a311
16 files changed
Lines changed: 99 additions & 128 deletions
File tree
- .changeset
- packages/runtime/src
- domains
- sandbox
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2472 | 2472 | | |
2473 | 2473 | | |
2474 | 2474 | | |
2475 | | - | |
2476 | | - | |
2477 | | - | |
| 2475 | + | |
| 2476 | + | |
| 2477 | + | |
| 2478 | + | |
2478 | 2479 | | |
2479 | 2480 | | |
2480 | 2481 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1777 | 1777 | | |
1778 | 1778 | | |
1779 | 1779 | | |
1780 | | - | |
| 1780 | + | |
| 1781 | + | |
| 1782 | + | |
| 1783 | + | |
| 1784 | + | |
1781 | 1785 | | |
1782 | 1786 | | |
1783 | 1787 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
346 | 346 | | |
347 | 347 | | |
348 | 348 | | |
349 | | - | |
350 | | - | |
| 349 | + | |
| 350 | + | |
351 | 351 | | |
352 | 352 | | |
353 | 353 | | |
354 | 354 | | |
355 | | - | |
356 | | - | |
| 355 | + | |
| 356 | + | |
| 357 | + | |
357 | 358 | | |
358 | 359 | | |
359 | 360 | | |
| |||
433 | 434 | | |
434 | 435 | | |
435 | 436 | | |
436 | | - | |
437 | | - | |
| 437 | + | |
| 438 | + | |
438 | 439 | | |
439 | 440 | | |
440 | 441 | | |
| |||
452 | 453 | | |
453 | 454 | | |
454 | 455 | | |
455 | | - | |
| 456 | + | |
456 | 457 | | |
457 | 458 | | |
458 | 459 | | |
| |||
516 | 517 | | |
517 | 518 | | |
518 | 519 | | |
519 | | - | |
| 520 | + | |
| 521 | + | |
520 | 522 | | |
521 | 523 | | |
522 | 524 | | |
| |||
592 | 594 | | |
593 | 595 | | |
594 | 596 | | |
595 | | - | |
| 597 | + | |
| 598 | + | |
596 | 599 | | |
597 | 600 | | |
598 | 601 | | |
| |||
674 | 677 | | |
675 | 678 | | |
676 | 679 | | |
677 | | - | |
| 680 | + | |
678 | 681 | | |
679 | 682 | | |
680 | 683 | | |
| |||
684 | 687 | | |
685 | 688 | | |
686 | 689 | | |
687 | | - | |
| 690 | + | |
| 691 | + | |
688 | 692 | | |
689 | 693 | | |
690 | 694 | | |
691 | 695 | | |
692 | | - | |
693 | | - | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
694 | 700 | | |
695 | 701 | | |
696 | 702 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
275 | 275 | | |
276 | 276 | | |
277 | 277 | | |
278 | | - | |
279 | | - | |
| 278 | + | |
| 279 | + | |
280 | 280 | | |
281 | 281 | | |
282 | 282 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
140 | 140 | | |
141 | 141 | | |
142 | 142 | | |
143 | | - | |
| 143 | + | |
| 144 | + | |
144 | 145 | | |
145 | 146 | | |
146 | 147 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
209 | 209 | | |
210 | 210 | | |
211 | 211 | | |
212 | | - | |
| 212 | + | |
213 | 213 | | |
214 | 214 | | |
215 | 215 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
229 | 229 | | |
230 | 230 | | |
231 | 231 | | |
232 | | - | |
| 232 | + | |
233 | 233 | | |
234 | 234 | | |
235 | 235 | | |
| |||
252 | 252 | | |
253 | 253 | | |
254 | 254 | | |
255 | | - | |
| 255 | + | |
256 | 256 | | |
257 | 257 | | |
258 | 258 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
260 | 260 | | |
261 | 261 | | |
262 | 262 | | |
263 | | - | |
| 263 | + | |
264 | 264 | | |
265 | 265 | | |
266 | 266 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
98 | 98 | | |
99 | 99 | | |
100 | 100 | | |
101 | | - | |
102 | | - | |
| 101 | + | |
| 102 | + | |
103 | 103 | | |
104 | 104 | | |
105 | 105 | | |
| |||
0 commit comments