Skip to content

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

@os-project-manager

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.ts as #9098's surface.

The measurement

The 2026-08-16 ruling on #9106 says error.code is a closed vocabulary at every door, and #9106 delivers that for the three exits that read resolveThrownHttpError (HttpDispatcher.errorFromThrown, dispatcher-plugin's errorResponseBase, endpoint-executor's endpointErrorAnswer) 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:

  • THROWN errors: NOT narrowed, deliberately and symmetrically with this door. sendThrownError passes a caught error's code through verbatim; narrowing that is an ADR-0112 public-contract decision, not an internal typing one.

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 code at the body's top level ({ error: 'message', code: 'X' }), not in error.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:

  1. The invariant is now stated absolutely. After the [Decision] The dispatcher's error.code has 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 a sendThrownError body, sees a contradiction and has no way to tell which one is authoritative — the exact AI-error-resistance failure [Decision] The dispatcher's error.code has 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.
  2. check:dispatcher-error-vocabulary sweeps 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's error.code has 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 declaredCode sibling, (b) converge the envelope position first and let the vocabulary follow, or (c) rule the top-level code a 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 /meta 501 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 code from 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 an error.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.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions