Uh oh!
There was an error while loading. Please reload this page.
docs(runtime,rest): record the dispatcher /meta plural tolerance as a ruled state, and pin it - #10613
Conversation
… ruled state, and pin it The legacy dispatcher `/meta` if-chain answers the FSM state read under both `objects` and `object`, while REST refuses the plural after #9180 step 2. The asymmetry was inferable only from a code comment plus a ledger note that read as "not got to yet" — so an author arriving at either surface could not tell it was deliberate. Both ledger rows now record it with its provenance (the maintainer's 2026-08-17 re-weigh, item 3: no new refusals beyond step 1, external break deferred with no scheduled window), state which front door refuses and which answers, and disclaim the `META_URL_TO_SINGULAR` fold explicitly — a separate mechanism under a separate decision. `meta-state-plural-tolerance.test.ts` makes the note checkable rather than trusted: nothing in this repo checked a ledger note against reality, so a note that quietly stopped being true would pass every gate and then be believed instead of the source. Deleting the plural arm now fails a test that names the ruling to reopen first. Measured, not assumed: `objects` and `object` answer 200; `objectss`, `objectz` and `OBJECTS` all 404 RESOURCE_NOT_FOUND — a literal pair, never the fold. No refusal changes anywhere; `packages/runtime/src/domains/meta.ts` is untouched. Part of #10179 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019bmVFqoQPq63zhKrxdYG1r
📓 Docs Drift CheckThis PR changes 2 package(s): 24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 28 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 1a41f56ca157346466d4a622b41082ddfb5bb983 && git checkout 1a41f56ca157346466d4a622b41082ddfb5bb983
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin bde0ab95de0c5b3f2f18a6960dfda1ff06f7cd82 5fd34b2d00c773d09561a64d8d86260e63560d0f && git checkout -B drift-repro bde0ab95de0c5b3f2f18a6960dfda1ff06f7cd82 && git merge --no-ff 5fd34b2d00c773d09561a64d8d86260e63560d0f
node scripts/docs-audit/affected-docs.mjs --json bde0ab95de0c5b3f2f18a6960dfda1ff06f7cd82
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#10179
Part of #9180
Records the dispatcher
/metaplural tolerance as a ruled state rather than something inferable only from a code comment, and pins it so the record cannot quietly stop being true.⛔ No refusal changes anywhere.
packages/runtime/src/domains/meta.tsis untouched — option (a) of #10179 (deleting the plural arm) is a new refusal on a second surface and needs the maintainer, not this card.The asymmetry, re-derived at head
Every clause below was verified in source on this branch's merge base, not taken from the card body. The card said
meta.ts:236; at head the arm ispackages/runtime/src/domains/meta.ts:235:/meta/objects/:name/state/:fieldpackages/restRestServer)grepfinds only pins asserting its absence);meta-published-and-state-routes.dogfood.test.ts:128,route-ledger-live-mount-parity.dogfood.test.ts:419,meta-route-registration-order.test.ts:139dispatch()(the@objectstack/honocreateHonoAppcatch-all — the documented embed shape)createMetaDomainregisters prefix/metaand hands the remainder tohandleMetadataRequest, whose FSM branch matches a hard-coded literal pair — newly pinned hereWhat is recorded, and where
An author arriving at either surface now learns the rule and learns it is deliberate, without reading
meta.ts:packages/runtime/src/route-ledger.ts(the dispatcher ledger row'snote) — extended from "that tolerance is out of step 2 scope and is not narrowed here", which reads as not got to yet, into the deliberate state with its provenance.packages/rest/src/rest-route-ledger.ts(the REST row'snote) — the other half. This row previously ended at "the boundary accept set is unchanged", so an author reading only it would conclude the plural is refused everywhere. It now names what the retirement did not make universal. (Declared addition to the claimed file surface: same defect class, note-only, same ledger/conformance gate family, no other claim on the file — none of the 8 open PRs touches either ledger, checked per-PR against their diffs.)Both notes carry the provenance: the maintainer's 2026-08-17 re-weigh of the #9180 ruling, item 3 — the tolerance is kept for external callers, no new refusals beyond what step ① shipped, the external break deferred with no scheduled window; reopening it is the maintainer's call.
⛔ Both notes state explicitly that this is not the
META_URL_TO_SINGULARfold whose retirement was deferred. That is a map consulted for/meta/:type; this is a literal||no request reaches through the fold. Separate mechanisms under separate decisions — conflating them is the specific error the record exists to prevent.⛔ Neither note says "the plural is supported". It is tolerated on one front door and refused on another, and the pin asserts both halves so neither flattening survives.
Why a test and not just prose
route-ledger.conformance.test.tssays it in as many words: "The ledger's per-route rows are documentation; the machine contract here is domain-level". Nothing in this repo checked a ledger note against reality — a plausible-sounding false note passes every gate and is then trusted instead of the code.packages/runtime/src/domains/meta-state-plural-tolerance.test.tscloses that for this note. Delete the plural arm and it fails, naming the ruling that has to be reopened first; reword the note past its load-bearing clauses and it fails too.Measured, not assumed — the tolerance is exactly two literals:
Not even its own uppercase. A fold would have normalised at least one of these — which is the sharpest available evidence for the "not the fold" clause, so it is a permanent control in the file rather than a line of prose.
Reverse verification
Ablated the half this card owns (the fence keeps
meta.tsread-only, so the behavioural arm was not mutated): removing2026-08-17from the ledger note turned the note assertion red in the predicted direction, exactly one test, with the runner's own line —Restore leg re-run and green (
Test Files 2 passed (2) · Tests 14 passed (14)). No rebuild was needed between legs and none is claimed: the subject resolves through relative imports insidepackages/runtime/src(../route-ledger.js,../http-dispatcher.js), which vitest compiles from source — not through a dependency'sexports→dist/. The one dependency that is resolved that way,@objectstack/objectql(dynamically imported by the FSM branch), was built before any leg ran and was not the ablation subject.Verification
Gate set re-derived with
node scripts/pm/dispatch-gates.mjs(no path arguments) after the final commit, at5fd34b2d00, working tree clean. It named 8 matched + 5 convention-triggered families; the dispatch brief named none of them, so all 13 were run, pluscheck:nul-bytes. Every exit code captured before any pipe; each family's own verdict line quoted:check:nul-bytes✓ check-nul-bytes --self-test: 75 assertions over a temp git repo (real scan() path)check:cross-package-test-inputsOK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check:dispatcher-error-vocabularycheck-dispatcher-error-vocabulary: OK — 21 unregistered code-stamping site(s), all classifiedcheck:route-envelope✓ Dispatcher domains — 16 audited: 11 helper-only, 5 with declared hand-built responses (0 ratcheted)check:slot-lookup✓ slot-lookup ratchet holds: 107 unswept site(s) in 25 file(s), none newcheck:test-source-aliascheck-test-source-alias OK — 72 packages with tests scannedcheck:type-source-resolutioncheck-type-source-resolution OK — 76 packages with a tsconfig.json scannedscripts/check-cross-package-test-inputs.mjsOK: 12 package(s) read outside themselves, all declaredscripts/docs-audit/check-affected-docs.mjs✓ affected-docs self-test: 281 cases pass.check:query-options-erasure✓ query-options-erasure ratchet holds: 67 unswept non-test site(s) in 17 file(s), none newcheck:type-check-coveragecheck-type-check-coverage: OK — 64/77 workspace packages type-checkedcheck:engine-double-contractcheck-engine-double-contract: OK — 350 pinned, 133 in the DEBT ledger, 2 exempt.check:where-matcher✓ where-matcher conformance holds: 271 matcher(s) discovered, 271 answer the combinator battery correctly or refuse it loudlycheck:type-check-debtcheck-type-check-coverage --re-measure: OK — 33 ledger entr(ies) re-measured in 224.5s, 1924 raw tsc error(s) total, none above its recorded number.The ratchet needs the workspace closure built, so the full build ran first exactly as
lint.ymldoes —Tasks: 70 successful, 70 total.Tests and typechecks:
pnpm --filter @objectstack/runtime test—Test Files 179 passed (179) · Tests 2675 passed (2675)packages/restledger conformance + registration-order pins —Test Files 2 passed (2) · Tests 12 passed (12)pnpm --filter @objectstack/runtime --filter @objectstack/rest typecheck— bothtsc --noEmitechoed andDone(no zero-match silent pass)All heavy work went through
scripts/pm/os-verify-lock.sh; nothing was run outside it.No changeset —
skip-changesetNothing published changes. The diff is two ledger
notedocumentation strings plus one new test file. Neither ledger is on an export surface:route-ledger.tsstates it is "runtime-internal (not exported from the package index)", and neitherpackages/runtime/src/index.tsnorpackages/rest/src/index.tsre-exports its ledger. No behaviour, no API, no wire shape moves.Generated by Claude Code
Generated by Claude Code