Filed unassigned while implementing #11719. Out of scope there: that card's claim fixes the file surface at packages/types/src/response-envelope.ts and its sibling tests, and this is a packages/rest call site.
Blocked-by: #11719 (PR #12403) — until sendError's extra admits declaredCode, threading it here is a compile error.
Measured on origin/main @ cdbd920
packages/rest/src/package-routes.ts answers the nested envelope through one helper, which already resolves the error through the shared ADR-0112 rule:
functionsendThrownError(res: any,error: unknown): void{constthrown=resolveThrownHttpError(error);constmessage=thrown.status>=500&&looksLikeInternalErrorLeak(thrown.message)
? INTERNAL_ERROR_MESSAGE
: thrown.message;sendError(res,thrown.status,thrown.code,message,thrown.details ? {details: thrown.details} : undefined,);}resolveThrownHttpError returns declaredCode whenever the producer spelled a code the ledger does not know (packages/types/src/thrown-http-error.ts — the resolution table names it explicitly), and demotedDeclaredCode is the read that answers undefined when the spelling is already the vocabulary member in code. This helper consults neither: it forwards thrown.details and nothing else, so the demoted spelling it is holding in the same local is dropped one line later.
thrown.userMessage is dropped at the same call for the same reason — see #12404 for that channel, which needs a separate writer change first.
Why this call site specifically
The #11719 census over the seven modules named on the shared sendOk/sendError pair found this is the only one of them that holds a resolved ThrownHttpError at its sendError call. storage-routes, settings-routes, admin-routes, external-datasource-routes, i18n-service-plugin and share-link-routes construct code and message as literals at the call site and never consult the shared resolver at all — zero hits for resolveThrownHttpError / classifiedRefusalAnswer / demotedDeclaredCode across those six, on an instrument returning 4 hits on this file in the same sweep. So this is the cheapest place in the tree to make a nested-envelope route actually emit the channel, and it is not fenced: the record-share family's equivalent call sites live in packages/rest/src/rest-server.ts, held by #11926.
The repair looks like the flat door's own idiom — thrownCodeFields in packages/rest/src/error-response.ts builds { code, ...(demoted !== undefined ? { declaredCode: demoted } : {}) } from the same resolver output — so this is adopting an existing rule at a door that skipped it, not inventing one.
Not established here
- Reachability. Whether a tenant-authored code can actually reach this door was not measured.
/api/v1/packages routes into metadata-protocol, whose throws are platform-side; a producer spelling that is unregistered but platform-authored would still be demoted and still dropped, but that is a different severity from the metadata-app case ADR-0112's amendment names. Someone should drive it rather than reason about it. - Severity not judged.
- Whether the two doors serving
/api/v1/packages (this registrar and the rest-server twin) should be made to agree in the same change, given the first-match-wins mount order this module's own header documents.
Region: packages/rest/src/package-routes.ts, sendThrownError.
Generated by Claude Code
Filed unassigned while implementing #11719. Out of scope there: that card's claim fixes the file surface at
packages/types/src/response-envelope.tsand its sibling tests, and this is apackages/restcall site.Blocked-by: #11719 (PR #12403) — until
sendError'sextraadmitsdeclaredCode, threading it here is a compile error.Measured on
origin/main@cdbd920packages/rest/src/package-routes.tsanswers the nested envelope through one helper, which already resolves the error through the shared ADR-0112 rule:resolveThrownHttpErrorreturnsdeclaredCodewhenever the producer spelled a code the ledger does not know (packages/types/src/thrown-http-error.ts— the resolution table names it explicitly), anddemotedDeclaredCodeis the read that answersundefinedwhen the spelling is already the vocabulary member incode. This helper consults neither: it forwardsthrown.detailsand nothing else, so the demoted spelling it is holding in the same local is dropped one line later.thrown.userMessageis dropped at the same call for the same reason — see #12404 for that channel, which needs a separate writer change first.Why this call site specifically
The #11719 census over the seven modules named on the shared
sendOk/sendErrorpair found this is the only one of them that holds a resolvedThrownHttpErrorat itssendErrorcall.storage-routes,settings-routes,admin-routes,external-datasource-routes,i18n-service-pluginandshare-link-routesconstructcodeandmessageas literals at the call site and never consult the shared resolver at all — zero hits forresolveThrownHttpError/classifiedRefusalAnswer/demotedDeclaredCodeacross those six, on an instrument returning 4 hits on this file in the same sweep. So this is the cheapest place in the tree to make a nested-envelope route actually emit the channel, and it is not fenced: the record-share family's equivalent call sites live inpackages/rest/src/rest-server.ts, held by #11926.The repair looks like the flat door's own idiom —
thrownCodeFieldsinpackages/rest/src/error-response.tsbuilds{ code, ...(demoted !== undefined ? { declaredCode: demoted } : {}) }from the same resolver output — so this is adopting an existing rule at a door that skipped it, not inventing one.Not established here
/api/v1/packagesroutes intometadata-protocol, whose throws are platform-side; a producer spelling that is unregistered but platform-authored would still be demoted and still dropped, but that is a different severity from the metadata-app case ADR-0112's amendment names. Someone should drive it rather than reason about it./api/v1/packages(this registrar and therest-servertwin) should be made to agree in the same change, given the first-match-wins mount order this module's own header documents.Region:
packages/rest/src/package-routes.ts,sendThrownError.Generated by Claude Code