Repository navigation
finding: packages/rest's flat sendThrownError still puts a thrown error's code on the wire un-narrowed — ADR-0112's closure does not reach that door #9232
Description
Activity
Triage: decision card —
needs-user-decision+domain:cli, type Task. Rationale: the fix lands either inpackages/rest's flat responder (error-response.ts) or in ADR-0112's text, and the choice among (a) narrow the flat door too, (b) converge the envelope position first, (c) rule the top-levelcodea field ADR-0112 does not govern and say so in the ADR, is a public-contract decision — manual floor, not delegable to triage. Dedup:sendThrownErrormatches only this card; #9106 (ruled), #9098 / PR #9222, and #8087 are the named siblings, not duplicates.Four-prong analysis:
- Platform long-term coherence — after the [Decision] The dispatcher's
error.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106 amendment ADR-0112 states "closed at every door" absolutely, while this thrown path demonstrably is not. (a) or (c) shrinks the contradiction; (b) removes the dialect that hosts it. Leaving it unwritten re-creates the exact ambiguity [Decision] The dispatcher'serror.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106 closed, one door over. - Measured business pull — zero today: the card's consumer sweep found no
error.code-class read of the flat top-levelcode. Contract-coherence card, not an outage. - AI-agent error-resistance — an absolute ADR sentence contradicted by an observable door is the worst state for an agent reading both; whichever option wins, writing the boundary into ADR-0112 is the resistant move.
- Startup scope discipline — (c) is one ADR paragraph plus repointing the stale finding:
rest-server.ts里三个相邻/metahandler 的错误信封是三种不同形状,其中两种不符合 ADR-0112 #7035 citation (closed 2026-08-10) at a live envelope-convergence card; (a) spends code on a legacy dialect already slated for envelope convergence (cloud#944 measures five dialects on/api/v1).
Recommendation: (c) now — amend ADR-0112 to name the flat responder's top-level
codeas outside D4's governed field and point at envelope convergence as the real closure; fold the stale-citation fix into the same PR. Option (a) becomes moot if the envelope converges.本评论来自分诊座位 Routine — session
session_01EquW1DnXShvkwiJZUfDtwi, scheduled fire ~04:36Z 2026-08-17.
Generated by Claude Code
- Platform long-term coherence — after the [Decision] The dispatcher's
Maintainer ruling (2026-08-17, recorded by PM session
session_01NYgmGheCzM6NrHZN436Cxf). The maintainer accepted the decision-inbox report's recommendations in full, verbatim: 「其他接受你的所有建议」. For this card:Ruled: demote alignment — the flat
sendThrownErrordoor narrows too.- A thrown error's
codethat is not anErrorCodeenum member is demoted to adeclaredCodesibling in the flat body, exactly as the dispatcher/actions door now does under the [Decision] The dispatcher'serror.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106 ruling —codeis a closed vocabulary at every door, with no carve-out for body position. Option (c) (declaring the top-levelcodea different, ungoverned field) is rejected: it would cut an exception into an invariant amended to be absolute yesterday, recreating the reader contradiction this card measures. - Envelope-position convergence (option b) is not a precondition — the vocabulary fix does not wait on the larger envelope program.
- Same batch: fix the stale finding:
rest-server.ts里三个相邻/metahandler 的错误信封是三种不同形状,其中两种不符合 ADR-0112 #7035 citations in The REST door's ownsendErroroverload does not narrow, so an unregisterederror.codereaches the wire there too #9098's prose and the ADR-0112 amendment — the envelope-position line points at this card (or a dedicated envelope card if triage splits one out). - The parity-test sentence in
package-door-error-parity.test.ts("NOT narrowed, deliberately and symmetrically") is updated in the same change — the symmetry it cites no longer exists. - Tier: this narrows caller-visible vocabulary back to the declared ADR-0112 contract — same class as [Decision] The dispatcher's
error.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106's slice; dispatch per that precedent.
Label flipped
needs-user-decision→pm:queuein the same stroke.
Generated by Claude Code
- A thrown error's
- added a commit that references this issue
on Aug 17, 2026 Claim: PM dispatch seat, round 13
Session:session_012WKSnqAaoqtW3QX7SSf1Vk
Branch:claude/issue-9232-flat-door-declared-code
Worktree:objectstack-issue-9232
Domain:domain:cli
File surface:packages/rest/src/error-response.ts+ its tests,packages/runtime/src/package-door-error-parity.test.ts(the parity sentence), the stale#7035citations inpackages/rest/src/**prose,.changeset/**— plusdocs/adr/0112-*.mdonly if the reading below shows it genuinely needs an edit (stop on breach; explain in the report)
Container & model: M,mode:subagent,model: claude-fable-5—--tierreports no path-derived mandate, but the ruling says verbatim "same class as #9106's slice; dispatch per that precedent", and #9106 was dispatchedclaude-fable-5as an ADR-class card. A ruling that names a precedent tier outranks this seat's own sizing.Serial constraints cleared:
packages/restis free — #9326's PR #9425 merged 01:20:41Z (origin/main@499f55e17), which is the exact release condition recorded for this card. #9364 is in flight but onplugin-hono-server/adapters/hono/cli/ the envelope gate script — nopackages/restoverlap. #9377 is inpackages/qa.
⚠️ A premise check that changes how this PR LANDS, not just how it is writtenThe ruling's third bullet says to fix "the stale #7035 citations in #9098's prose and the ADR-0112 amendment". Read literally that puts
docs/adr/**in the file surface — and an ADR-path PR is maintainer-merge-only; ⛔ this seat cannot enqueue it.Measured on
origin/main@499f55e17before dispatching:docs/adr/0112-error-code-vocabulary-and-ledger.mdcontains ZERO7035citations. Control: the same file returns 13 hits forADR, so the search works and the zero is real.- Every stale
#7035citation is inpackages/rest/src/**prose —error-response.ts,query-multiplicity.ts,rest-server.tsand six test files.
So on the evidence there may be nothing to fix in the ADR at all, and this would be an ordinary PR this seat can land. But the ruling names the ADR explicitly, so ⛔ I am not deciding that from a grep. The dev's first task is to establish it by reading, and to say so unmistakably in the report, because the answer decides the landing path:
- ADR needs no edit ⇒ normal flow, this seat ACCEPTs and enqueues.
- ADR needs an edit ⇒ put it in its own clearly-labelled commit and say so loudly; ⛔ this seat will NOT enqueue, and the PR goes to the maintainer to merge.
Zone 1 — the ruling (2026-08-17, maintainer, verbatim 「其他接受你的所有建议」)
- Demote alignment. A thrown error's
codethat is not anErrorCodeenum member is demoted to adeclaredCodesibling in the flat body — exactly as the dispatcher/actions door does under [Decision] The dispatcher'serror.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106.codeis a closed vocabulary at every door, with no carve-out for body position. The precedent to mirror is live atpackages/runtime/src/dispatcher-error-vocabulary.ts. - ⛔ Option (c) is rejected — declaring the top-level
codean ungoverned field would cut an exception into an invariant amended to be absolute the day before. - Envelope-position convergence (option b) is NOT a precondition. Do not wait on it and do not start it.
- The parity sentence must be updated in the same change.
packages/runtime/src/package-door-error-parity.test.tscurrently states the flat door is "NOT narrowed, deliberately and symmetrically with this door" — [Decision] The dispatcher'serror.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106 removed the symmetry it rests on, so that sentence is now false. Leaving it is the same defect class as the change itself.
Zone 2 — my assumptions
- I assume
declaredCodeis the exact sibling spelling used by the dispatcher door and that mirroring it needs no new vocabulary. Verify againstdispatcher-error-vocabulary.tsrather than inventing a variant. - I assume the consumer sweep on this card still holds — it found no
error.code-class read of the flat top-levelcode, i.e. zero measured business pull. If your own sweep finds a consumer, that changes this from a coherence card into a breaking one and it comes back to me. - I assume the six test files carrying
#7035need only the citation repointed, not behavioural edits. If a citation fix implies a behavioural one, report rather than fold it in.
Gates
Derive with
node scripts/pm/dispatch-gates.mjs <your actual paths>once the diff is real — expect at leastcheck:route-envelope,check:dispatcher-error-vocabulary,check:cross-package-test-inputsand the changeset family.⚠️ check:dispatcher-error-vocabularyis the gate most likely to have an opinion about this exact change; run it early rather than at the end. Report by CI JOB name, not by the local command.⛔ Never weaken a gate to get green. ⛔ No model identifier in any pushed artifact. Required checks are six:
TypeScript Type Check,ESLint,Test Core,Dogfood Regression Gate,Build Core,Temporal Conformance (live PG + MySQL).
Generated by Claude Code
Tier downgraded
claude-fable-5→opus, and re-dispatched. Recording it rather than doing it quietly.PM
domain:cliseat (#6024), sessionsession_012WKSnqAaoqtW3QX7SSf1Vk. The first dispatch died on an API error: the account's Fable 5 quota is exhausted. The agent terminated before creating a worktree or pushing anything — zero artifacts, so this is a clean re-dispatch, not a recovery.Why the downgrade is defensible on the merits, and not merely on availability:
- The
fabletier came from this card's ruling saying "same class as [Decision] The dispatcher'serror.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106's slice; dispatch per that precedent" — guidance naming a precedent, not a mandate. - The two ⛔ mandatory fable clauses do not fire here. It touches no
.claude/skills/pm-dispatch/**. And on the one-question test this seat applies consistently — does the set of accepted requests change? — the answer is no: this narrows caller-visible response vocabulary (a non-enumcodestarts arriving asdeclaredCode); the same requests are accepted and still fail. That is automation: the run-resume route still answers HTTP 200 wrapping an inner {success:false} — the route #3962's status-code unification left behind #8684's shape, not automation resume: the request body's OUTER envelope is lenient — an unknown top-level key is silently ignored and the submission is treated as empty #8796's. opusis the stated default for "M/L、裁决实施、任何带设计判断的卡", and this is a ruling implementation: every decision is already made, and the dev's judgement is spent on one premise check and on mirroring an existing precedent.
Stated plainly so the record is honest: the quota is what prompted me to re-examine the tier. Having re-examined it,
opusis where I would have put this card had the ruling not named a precedent — but I would not have moved it down on my own initiative, and I am not pretending otherwise.⚠️ Escalated to the maintainer — model quota is budget territory, which is theirs, not this seat's. The operational consequence worth knowing: while Fable 5 is exhausted, a card that is fable-mandatory (anything touching.claude/skills/pm-dispatch/**, or one that genuinely changes contract accept/reject behaviour) cannot be dispatched at all by this seat — there is no legitimate downgrade for those, because that clause is a mandate rather than guidance. None is currently queued indomain:cli, so nothing is blocked today.Everything else in the claim above stands unchanged — file surface, serial constraints, and the
⚠️ first task establishing whetherdocs/adr/0112-*.mdgenuinely needs an edit (which decides whether this seat may enqueue the PR at all).
Generated by Claude Code
- The
- added 4 commits that reference this issue
on Aug 18, 2026 { "issue": 9232, "status": "done", "branch": "claude/issue-9232-flat-door-declared-code", "pr": "https://github.com/objectstack-ai/objectstack/pull/9459", "premise_still_valid": true, "summary": "⚠️ FIRST TASK ANSWER, LOUDLY: the ADR **does** need an edit, so this PR is MAINTAINER-MERGE-ONLY and you cannot enqueue it. The ADR edit is isolated in its own commit (ad0beb32) so it can be dropped if you prefer. But your measurement was right and the ruling's literal reading was wrong: docs/adr/0112 contains ZERO `7035` citations (control: 13 `ADR` hits in the same file). What actually needed editing is the #9106 amendment's **Scope** paragraph, which explicitly carves this door OUT of the closure and tells the reader '#9232 is where the remaining question about the flat top-level code is recorded'. Landing the fix without touching it would MOVE the declared-≠-actual contradiction rather than remove it — the ADR would say the flat door is exempt while the code narrows. That is the ruling's own bullet-4 logic pointed the other way. Implementation: all FOUR verbatim passthrough arms (resolveErrorResponse 4xx/5xx, mapDataError 4xx/5xx) now resolve the code through the shared resolveThrownHttpError / demotedDeclaredCode pair — registered codes unchanged, unregistered ones demoted to a top-level `declaredCode` sibling with `code` carrying the status-derived member, and a throw with no code still carrying none. The parity sentence in packages/runtime is rewritten, and the stale #7035 citations (plus one stale #8111) in packages/rest prose are repointed at the check:route-envelope ratchet — deliberately NOT at a card, because #7035, #8111 and #3843 have all completed and #9232 itself closes on merge, so no live card exists to cite.", "tests": "All figures at HEAD eb70f7f4a (final commit); the gate union was re-run on that head AFTER the last commit. Workspace closure BUILT first (`pnpm exec turbo run build --filter=./packages/* --filter=./packages/*/*` — 70/70 tasks OK), which the type-check-debt ratchet and the dogfood run both require. SUITES: `pnpm --filter @objectstack/rest exec vitest run` → 'Test Files 125 passed (125) / Tests 2058 passed (2058)'. `pnpm --filter @objectstack/runtime exec vitest run` → '167 passed (167) / 2499 passed (2499)'. `pnpm --filter @objectstack/http-conformance exec vitest run` → '4 passed / 72 passed'. `pnpm --filter @objectstack/dogfood exec vitest run` → '110 passed | 1 skipped / 792 passed | 3 skipped' (run against the REBUILT dist, per the turbo build above). TYPES: `pnpm --filter @objectstack/rest typecheck` → exit 0. DOWNSTREAM (dependents direction, `--filter '...^@objectstack/rest'`): 16 packages typecheck green — cli, client, client-react, runtime, hono, cloud-connection, plugin-auth, plugin-dev, driver-turso, service-sms, verify, dogfood, http-conformance, and the three example apps. LINT: `pnpm exec eslint --no-inline-config` over the changed .ts files → exit 0. GATE UNION at eb70f7f4a, all exit 0: check:dispatcher-error-vocabulary ('OK — 12 unregistered code-stamping site(s), all classified; 0 awaiting a ledger entry'), check:route-envelope, check:cross-package-test-inputs, check:adr-anchors ('OK — 50 anchored file(s) ... 26120 citation(s) across 3293 file(s) resolve'), check:nul-bytes, check:changeset-gate-self-tests, check:objectui-changeset, check:authz-resolver, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage; plus check:type-check-debt → 'OK — 33 ledger entr(ies) re-measured in 211.0s ... none above its recorded number'. Also green: check-adr-links, check-empty-changeset, check-changeset-no-major, check-adr-0087-registration, docs-audit/check-affected-docs. ⚠️ check:type-check-debt went RED first (@objectstack/rest TEST_DEBT 155 → 156, TS2835 from my new file's extensionless import); fixed AT SOURCE in eb70f7f4a with an explicit `.js` extension — the ratchet was NOT raised. REVERSE VERIFICATION (direction predicted before running, source-level ablation through vitest — no dist involved, so no rebuild applies to this leg): restoring the resolveErrorResponse 5xx verbatim passthrough gave '3 failed | 21 passed', reddening exactly the predicted cases — §1's 5xx demote ('expected PACKAGE_IS_HAUNTED to be SERVICE_UNAVAILABLE'), §4's numeric errno ('expected 1062 to be undefined'), §5's status-derivation ('expected PACKAGE_IS_HAUNTED to be NOT_IMPLEMENTED') — while every §2 registered-code case stayed GREEN, the predicted asymmetry. That ablation also caught a vacuous case of MY OWN (§1's 'presence means demotion' stayed green because its assertions are all true of `undefined`); strengthened in 896cac00 with `toBeTypeOf('string')`. Restored via `git checkout branch -- path` from the committed state, never git stash.", "open_questions": [ { "question": "The ADR commit (ad0beb32) is what makes this PR maintainer-merge-only. Should it ship with the code, or be split so the code half can be enqueued now?", "options": [ "A — ship as is: one PR, maintainer merges. The ADR and the door change land together, so there is no window in which ADR-0112 tells a reader the flat door is carved out while the code narrows.", "B — drop ad0beb32 from this PR (it is a clean `git revert` or cherry-pick away), enqueue the code half, and land the ADR as a separate docs-only PR to the maintainer.", "C — drop the ADR edit entirely and leave the Scope paragraph as it stands." ], "recommendation": "A, because the whole card is 'an ADR sentence contradicted by an observable door'. Option B re-creates that exact state for the length of the gap, pointing the other way, and the ruling's bullet 4 already established that this class of prose gap ships in the SAME change as the code ('Leaving it is the same declared-≠-actual defect class as the code change itself'). Option C is not viable: the paragraph does not merely go stale, it actively tells readers to treat the flat dialect as exempt and to 'read that card before quoting every door at the flat dialect' — the card being #9232, which will be closed. B is a legitimate second choice if throughput matters more than the gap; C is not." } ], "out_of_scope_findings": [ "filed as #9460: `check:dispatcher-error-vocabulary` cannot see a LOWERCASE thrown code — its shapes match SCREAMING_SNAKE only, so `plugin-security`'s live `owd_widening_forbidden` (a real 403 producer reaching a real wire) was never swept and was never reported as unclassified either. Same blind-spot class #9223 fixed for objlitconst/objlittemplate. Its wire answer is already improved by this PR (demoted, spelling preserved), so it is a gate-coverage card, not an outage.", "filed as #9461: the envelope-position line has NO live owner — both `ratchet:` fields in `scripts/check-route-envelope.mjs` (for rest-server.ts and error-response.ts) name #7035, completed 2026-08-10. #8111 and #3843 are completed too, and no open issue owns that line. Outside this PR's declared file surface, which is why the in-prose citations were repointed at the ratchet mechanism instead of at a card." ] }
Generated by Claude Code
ACCEPT — and ⛔ NOT enqueued. PR #9459 is maintainer-merge-only.
PM
domain:cliseat (#6024), sessionsession_012WKSnqAaoqtW3QX7SSf1Vk. Six required checks running clean oneb70f7f4a; the only failing check isADR maintainer approval, which is the gate working as designed.⚠️ A mechanism note that matters more than it looksADR maintainer approvalis NOT one of the six required checks. So auto-merge, if armed, would carry this PR into the queue the moment the six go green — landing an ADR change without the approval that check exists to demand. What stops that here is the standing rule (⛔ this seat never enqueues an ADR-path PR), not the mechanism. Anyone reading "red check, but not required" as "proceed" would ship it. Recording this on the card because the next seat will meet the same shape.My inference was wrong, and the dev's reading is why
I measured that
docs/adr/0112-*.mdcontains zero7035citations and inferred the ADR probably needed no edit — I put that in the dispatch brief and in my own patrol text. The measurement was right; the inference was wrong because I searched for the wrong marker.What actually needed changing is the #9106 amendment's Scope paragraph, which carves this exact door out of the closure and tells the reader that "#9232 is where the remaining question about the flat top-level
codeis recorded". Once this card is ruled, that paragraph is false — and landing the code without it would move the declared-≠-actual contradiction rather than remove it: the ADR would say the flat door is exempt while the code narrows. That is this card's own defect, inverted. It is also the ruling's bullet-4 logic pointed the other way, which is the right way to have found it.⭐ A grep can only find the marker you thought of. This one needed a read.
Ruling on the open question: A — ship as is, one PR, the maintainer merges.
B (drop the ADR commit, enqueue the code half, land the ADR separately) is a legitimate throughput trade and I am declining it: it re-creates precisely this card's defect for the length of the gap, with the ADR telling readers the flat dialect is exempt while the code has already narrowed. The ruling's bullet 4 already settled that this class of prose gap ships in the same change — "Leaving it is the same declared-≠-actual defect class as the code change itself." C is not viable: the paragraph does not merely go stale, it actively instructs readers to treat the flat dialect as exempt and to consult a card that closes on merge.
The isolation instruction was honoured exactly —
ad0beb326touches one file, +12/−2, across four commits that each do one thing. That made this decision cheap to take either way, which is the point of asking for it.Two things done right that CI would never have shown
check:type-check-debtwent red first (@objectstack/restTEST_DEBT 155 → 156, aTS2835from an extensionless import in the new pin). Fixed at source with an explicit.jsextension — the ratchet was not raised. That is the one temptation this gate exists to create, and it was declined.- The reverse verification caught a vacuous case of the dev's own making: §1's "presence means demotion" stayed green because all its assertions are also true of
undefined. Strengthened withtoBeTypeOf('string'). That is the second self-caught vacuous pin in this lane tonight, both found by asking what a case is for rather than whether it is green.
Two findings filed, and the first is a gate that reports clean while blind
- [finding]
check:dispatcher-error-vocabularycannot see a lowercase thrown code —plugin-security's liveowd_widening_forbiddenwas never swept #9460 —check:dispatcher-error-vocabularycannot see a lowercase thrown code: its shapes match SCREAMING_SNAKE only, soplugin-security's liveowd_widening_forbidden(a real 403 reaching a real wire) was never swept and was never reported as unclassified either. A gate that answers "12 sites, all classified" while structurally unable to see a whole spelling class is worse than a gate that fails, and it is the same blind-spot class check-dispatcher-error-vocabulary is blind to a non-literalcode:in an object literal — a constant or template produces no finding, silently #9223 fixed once already. - [finding] The envelope-position line has no live owner —
check-route-envelope.mjs's tworatchet:fields both name #7035, closed since 2026-08-10 #9461 — the envelope-position line has no live owner: bothratchet:fields naming it point at finding:rest-server.ts里三个相邻/metahandler 的错误信封是三种不同形状,其中两种不符合 ADR-0112 #7035, completed 2026-08-10, and [finding]registerSharingEndpoints/respondSharingErrorstill answers the bare-string error dialect (ADR-0112) — the sharing half split out of #8073 #8111 and Envelope drift is not just service-storage: four more route modules emit bare bodies, two of them the pre-#3675{ error: '<string>' }#3843 are complete too. That is why the in-prose citations were repointed at the ratchet mechanism rather than at a card — there was no live card to cite, and inventing one would have been worse.
Over to the maintainer for the merge. Nothing further from this seat on this PR.
Generated by Claude Code
Follow-up to the report above —
origin/mainmerged for the renamed required check. Head is now047457ca3(waseb70f7f4a).Why
Ruleset
12119582now requiresLint & Repo Gatesinstead ofESLint, and the workflow PR that emits that context (#9421) landed after my first push. The old head could not emit it. Merged (never rebased) — no conflicts.Required-check status on
047457ca3Required context Status TypeScript Type Check🟡 in_progress (~18 min; whole-monorepo typecheck) Lint & Repo Gates✅ success Test Core✅ success — rollup, plus shards (1/3) (2/3) (3/3) all green Dogfood Regression Gate✅ success — rollup, plus shards (1/3) (2/3) (3/3) all green Build Core✅ success Temporal Conformance (live PG + MySQL)✅ success Five of six green; the sixth is still running. Local proxies for it were green on this exact tree:
pnpm --filter @objectstack/rest typecheckclean, andcheck:type-check-debt --re-measureOK across all 33 ledger entries.⚠️ ADR maintainer approvalis FAILING, by design, and is NOT one of the six. Its own output: "The PR's current review standing is not APPROVED. No state-setting review has been submitted at all… Drafting and pushing this PR was fine and stays fine — only the MERGE is gated." It also records that the gate does not check who approved — anyone with review rights can clear it.⛔ Auto-merge deliberately not armed, and no PR state flipped. That gate reads the auto-merge state, so arming it would carry the ADR change into the queue past the very approval it demands.
Re-verified on the merged head, not carried over
mainmoved 18 commits / 64 files, so nothing was assumed.pnpm install --frozen-lockfile, full closure rebuild (70/70 tasks, 68 not cached), then:- My diff is unchanged — still 12 files, +596/-55, four narrowed arms intact.
@objectstack/rest125 files / 2058 tests ·@objectstack/runtime167 files / 2503 tests (4 more than before — main added them) ·http-conformance72 ·dogfood792 passed / 3 skipped on the rebuiltdist/— all green.- Gate union (12 families) green at
047457ca3, pluscheck:type-check-debt --re-measure(33 entries, none above its recorded number) andcheck:route-envelope,check:dispatcher-error-vocabularyspecifically. - Vehicle assumptions re-checked against the rebuilt union. Main registered
FLOW_DISABLEDandFLOW_NO_START_NODE;STORED_TYPE_NOT_CANONICALwas a comment-only edit.PACKAGE_IS_HAUNTED,owd_widening_forbidden,UPSTREAM_UNAVAILABLE,HOOK_REFUSED,X_FAULTare all still unregistered — so no §1 case went vacuous — andRECORD_LOCKED,CONNECTOR_UPSTREAM_UNAVAILABLE,ERR_HOOK_TARGET_REBIND,FIELD_VISIBILITY_UNRESOLVEDstill registered. - Downstream: 14 of the 16 dependents of
@objectstack/restdeclare atypecheckscript; all 14 ran and passed. Counted rather than read off the exit code, since a filter matching no script exits 0.
Nothing was adjusted to accommodate main.
One thing worth knowing for finding #9461
mainrewrotescripts/check-route-envelope.mjsby 420 lines in this window — and left both#7035ratchet:fields (lines 245 and 309) untouched. The rewrite added a new plugin-route audit with live#9364owners and did not repoint the two stale ones, which is evidence the miss is systematic rather than incidental.The PR body has been updated to cite
047457ca3throughout, after re-running the union on that head.
Generated by Claude Code
- added 3 commits that reference this issue
on Aug 23, 2026 - added a commit that references this issue
on Aug 26, 2026 - added a commit that references this issue
on Sep 1, 2026 - added a commit that references this issue
on Sep 28, 2026
Found while implementing #9106 (the demote at the dispatcher/actions door). Filed unassigned, not fixed there: #9106's ruled scope named the actions door and the shared resolver, and the claim for it explicitly fenced off
packages/rest/src/error-response.tsas #9098's surface.The measurement
The 2026-08-16 ruling on #9106 says
error.codeis a closed vocabulary at every door, and #9106 delivers that for the three exits that readresolveThrownHttpError(HttpDispatcher.errorFromThrown,dispatcher-plugin'serrorResponseBase,endpoint-executor'sendpointErrorAnswer) plus the direct-mount package registrar.One thrown-error path is left outside it, and #9098's own landed prose says so in
packages/runtime/src/package-door-error-parity.test.ts, verbatim:That sentence was true when it landed. #9106 changed the half it rests on — the dispatcher door narrows now — so the stated symmetry is gone and
packages/rest's flat responder is the one thrown path that still emits an unregistered spelling.Why it is narrower than it looks (and why it is still worth a card)
The flat dialect puts
codeat the body's top level ({ error: 'message', code: 'X' }), not inerror.code. So it is arguably not the field ADR-0112 D4 closes, which is why #9106 did not reach it by construction rather than by oversight. Two reasons it is still a real card:error.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106 amendment, ADR-0112 reads "closed at every door". An agent reading it, then reading asendThrownErrorbody, sees a contradiction and has no way to tell which one is authoritative — the exact AI-error-resistance failure [Decision] The dispatcher'serror.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106 was filed to remove, moved one door over.check:dispatcher-error-vocabularysweeps only the PLATFORM producers of that path. It cannot reach a code that is not written in this repo — the same construction limit that produced [Decision] The dispatcher'serror.codehas a limb authored by TENANTS at runtime — registration cannot close it, and ADR-0112 does not say what should happen there #9106's tenant-authored limb.Deliberately not decided here
Whether the answer is (a) narrow the flat door too and add a
declaredCodesibling, (b) converge the envelope position first and let the vocabulary follow, or (c) rule the top-levelcodea different field that ADR-0112 does not govern, and say so in the ADR. That is a public-contract decision, per #9098's own note.Stale citation to fix while here
#9098's prose and the #9106 ADR amendment both point at #7035 as the open envelope-position finding. #7035 is closed (completed 2026-08-10, PR #7293) and was only about three
/meta501 handlers. The envelope-convergence line needs a live card to point at — this one, or a dedicated envelope card if triage prefers to split them.Not reproduced as a client-visible break
No consumer branch on an unregistered top-level
codefrom that door was found: the #9106 precondition sweep classified every out-of-vocabulary SCREAMING_SNAKE literal on the consumer surfaces (client,client-react,examples,qa, the objectui checkout) and none was anerror.code-class read. So this is a contract-coherence card, not an outage.Related: #9106 (the ruling this measures against) · #9098 / PR #9222 (which split the responder and wrote the sentence above) · #8087 (the gate) · ADR-0112.