Skip to content

docs(runtime,rest): record the dispatcher /meta plural tolerance as a ruled state, and pin it - #10613

Merged
os-elon merged 1 commit into
mainfrom
claude/issue-10179-record-dispatcher-plural-tolerance
Aug 21, 2026
Merged

docs(runtime,rest): record the dispatcher /meta plural tolerance as a ruled state, and pin it#10613
os-elon merged 1 commit into
mainfrom
claude/issue-10179-record-dispatcher-plural-tolerance

Conversation

@os-elon

Copy link
Copy Markdown
Collaborator

Fixes#10179

Part of #9180

Records the dispatcher /meta plural 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.ts is 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 is packages/runtime/src/domains/meta.ts:235:

if(parts.length===4&&(parts[0]==='objects'||parts[0]==='object')&&parts[2]==='state'&&(!method||method==='GET')){
front door/meta/objects/:name/state/:fieldevidence
REST (packages/restRestServer)transport 404 — no registration left to matchno live registration exists (grep finds 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:139
dispatch() (the @objectstack/honocreateHonoApp catch-all — the documented embed shape)200, answeredcreateMetaDomain registers prefix /meta and hands the remainder to handleMetadataRequest, whose FSM branch matches a hard-coded literal pair — newly pinned here

What 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's note) — 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's note) — 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_SINGULAR fold 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.ts says 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.ts closes 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:

/meta/objects/task/state/status => 200 {"object":"task","field":"status","from":"todo","next":["backlog","in_progress"]}
/meta/object/task/state/status => 200 {"object":"task","field":"status","from":"todo","next":["backlog","in_progress"]}
/meta/objectss/task/state/status => 404 RESOURCE_NOT_FOUND
/meta/objectz/task/state/status => 404 RESOURCE_NOT_FOUND
/meta/OBJECTS/task/state/status => 404 RESOURCE_NOT_FOUND

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.ts read-only, so the behavioural arm was not mutated): removing 2026-08-17 from the ledger note turned the note assertion red in the predicted direction, exactly one test, with the runner's own line —

× the ledger row keeps NAMING the asymmetry — the note is checked, not trusted
→ expected '#9180 step 2 moved the SDK to the sin…' to contain '2026-08-17'
Test Files 1 failed (1) · Tests 1 failed | 7 passed (8)

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 inside packages/runtime/src (../route-ledger.js, ../http-dispatcher.js), which vitest compiles from source — not through a dependency's exportsdist/. 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, at 5fd34b2d00, working tree clean. It named 8 matched + 5 convention-triggered families; the dispatch brief named none of them, so all 13 were run, plus check:nul-bytes. Every exit code captured before any pipe; each family's own verdict line quoted:

familyverdict line
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 classified
check: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 new
check:test-source-aliascheck-test-source-alias OK — 72 packages with tests scanned
check:type-source-resolutioncheck-type-source-resolution OK — 76 packages with a tsconfig.json scanned
scripts/check-cross-package-test-inputs.mjsOK: 12 package(s) read outside themselves, all declared
scripts/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 new
check:type-check-coveragecheck-type-check-coverage: OK — 64/77 workspace packages type-checked
check: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 loudly
check: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.yml does — Tasks: 70 successful, 70 total.

Tests and typechecks:

  • pnpm --filter @objectstack/runtime testTest Files 179 passed (179) · Tests 2675 passed (2675)
  • packages/rest ledger conformance + registration-order pins — Test Files 2 passed (2) · Tests 12 passed (12)
  • pnpm --filter @objectstack/runtime --filter @objectstack/rest typecheck — both tsc --noEmit echoed and Done (no zero-match silent pass)

All heavy work went through scripts/pm/os-verify-lock.sh; nothing was run outside it.

No changeset — skip-changeset

Nothing published changes. The diff is two ledger note documentation strings plus one new test file. Neither ledger is on an export surface: route-ledger.ts states it is "runtime-internal (not exported from the package index)", and neither packages/runtime/src/index.ts nor packages/rest/src/index.ts re-exports its ledger. No behaviour, no API, no wire shape moves.

Generated by Claude Code


Generated by Claude Code

… 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
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/rest, @objectstack/runtime, touching 3 documentable anchor(s).

24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json bde0ab95de0c5b3f2f18a6960dfda1ff06f7cd82.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 name(s) were too generic to anchor anything (single lowercase words)

Coarse fallback — 28 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json bde0ab95de0c5b3f2f18a6960dfda1ff06f7cd82packageMentionDocs.

Which tree this was computed on

This run read content/docs from 1a41f56ca157346466d4a622b41082ddfb5bb983 — the merge of head 5fd34b2d00c773d09561a64d8d86260e63560d0f into base bde0ab95de0c5b3f2f18a6960dfda1ff06f7cd82, which is what actions/checkout gives a pull_request run. Not the PR head.

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

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 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

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

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs bde0ab95de0c5b3f2f18a6960dfda1ff06f7cd82 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-elonos-elon added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 21, 2026 — with Claude
@os-elon
os-elon marked this pull request as ready for review August 21, 2026 09:05
@os-elon
os-elon added this pull request to the merge queueAug 21, 2026
Merged via the queue into main with commit 53a48c9Aug 21, 2026
35 checks passed
@os-elon
os-elon deleted the claude/issue-10179-record-dispatcher-plural-tolerance branch August 21, 2026 09:19
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[finding] The legacy dispatcher /meta branch still matches the plural objects literal after step ② retires the REST registration

2 participants

@os-elon@claude