Found while implementing #11130 (the list door's registry half). Not fixed there — that card's dispatch ruling scoped the edit to the GET /api/v1/packages handler by name. Filed unassigned for triage.
The defect
packages/rest/src/package-routes.ts, the GET /api/v1/packages/:id handler, tries the durable read first and falls back to the registry:
// Fall back to registry (in-memory loaded packages)if(options.protocol&&typeofoptions.protocol.getMetaItems==='function'){try{constresult=awaitoptions.protocol.getMetaItems({type: 'package'});constmatch=result?.items?.find((item: any)=>(item.manifest?.id||item.id)===packageId);if(match){sendOk(res,{package: { ...match,source: 'registry'}});return;}}catch{// Protocol unavailable}}sendError(res,404,'RESOURCE_NOT_FOUND',`Package "${packageId}" was not found.`);When getMetaItems throws, the catch absorbs it and control falls straight through to the line below. The door then answers 404 RESOURCE_NOT_FOUND — Package "<id>" was not found.
Same standing family ruling as its siblings — #10965 · #10677 / PR #10788 · #10789 / PR #10964 · #11063 · #11130: a read that could not happen must not be reported as a read that found nothing.
Why this is the worse half, not a smaller one
The list door's version (#11130) answered a 200 with a total that under-counted. This one answers a terminal negative fact: 404 / RESOURCE_NOT_FOUND is not "the listing may be short", it is "this package does not exist". A caller acts on it — an installer decides the package is not installed and offers to install it; a console hides the entry; a script branches to the create path. The producer's own words for this condition are the opposite: "whether this item exists is unknown".
It is also the exact defect #5532 fixed one layer down, resurfacing one layer up. ObjectStackProtocolImplementation.getMetaItems was taught not to report an unreadable sys_metadata as "that item does not exist"; this consumer-side catch then re-applies precisely that relabelling to the protocol's answer.
The producer already declares the refusal
Measured on the live implementation while working #11130. The protocol service this registrar is handed is ObjectStackProtocolImplementation (packages/metadata-protocol/src/plugin.ts registers it; rest-api-plugin.ts resolves it and direct-mount-composition.ts passes it through). Its getMetaItems routes every non-benign sys_metadata overlay read failure through rethrowUnlessMetadataStoreUnprovisioned → metadataStoreUnavailableError: SERVICE_UNAVAILABLE / 503 with an ADR-0112 status+code on the error (#5532).
Driven with this door's own request:
outage (ECONNREFUSED) type=package => THREW status=503 code=SERVICE_UNAVAILABLE
msg="The metadata store could not be read, so whether
this item exists is unknown. …"
healthy store type=package => RESOLVED {"type":"package","items":[]}
no such table (1st boot) type=package => RESOLVED {"type":"package","items":[]}
So there is a declared envelope here waiting to be let through, and removing the catch is mechanical — the same edit #11063 made for the durable half and #11130 made for the list door's registry half. sendThrownError already carries the producer's status and code.
Worth stating: the in-tree comment currently overclaims
The [#11063] note beside the durable read asserts:
GET /packages/:id next door has never had an inner catch, so it has answered that same 503 since #10965.
True of that door's durable read. Its registry read is the block quoted above. Whichever way this is triaged, that sentence wants a qualifier.
Not measured here
- Whether a caller depends on the 404. The behaviour change is narrower than the list door's (it only moves the answer on a failed read, never on a genuine miss), but the 404 → 503 flip on this route was not surveyed against consumers.
- The
DELETE /api/v1/packages/:id handler was not read for this shape.
Refs
#11130 · #11063 · #10965 · #5532 · #10677 / PR #10788 · #10789 / PR #10964 · ADR-0112
Found while implementing #11130 (the list door's registry half). Not fixed there — that card's dispatch ruling scoped the edit to the
GET /api/v1/packageshandler by name. Filed unassigned for triage.The defect
packages/rest/src/package-routes.ts, theGET /api/v1/packages/:idhandler, tries the durable read first and falls back to the registry:When
getMetaItemsthrows, the catch absorbs it and control falls straight through to the line below. The door then answers404 RESOURCE_NOT_FOUND—Package "<id>" was not found.Same standing family ruling as its siblings — #10965 · #10677 / PR #10788 · #10789 / PR #10964 · #11063 · #11130: a read that could not happen must not be reported as a read that found nothing.
Why this is the worse half, not a smaller one
The list door's version (#11130) answered a
200with atotalthat under-counted. This one answers a terminal negative fact:404/RESOURCE_NOT_FOUNDis not "the listing may be short", it is "this package does not exist". A caller acts on it — an installer decides the package is not installed and offers to install it; a console hides the entry; a script branches to the create path. The producer's own words for this condition are the opposite: "whether this item exists is unknown".It is also the exact defect #5532 fixed one layer down, resurfacing one layer up.
ObjectStackProtocolImplementation.getMetaItemswas taught not to report an unreadablesys_metadataas "that item does not exist"; this consumer-side catch then re-applies precisely that relabelling to the protocol's answer.The producer already declares the refusal
Measured on the live implementation while working #11130. The
protocolservice this registrar is handed isObjectStackProtocolImplementation(packages/metadata-protocol/src/plugin.tsregisters it;rest-api-plugin.tsresolves it anddirect-mount-composition.tspasses it through). ItsgetMetaItemsroutes every non-benignsys_metadataoverlay read failure throughrethrowUnlessMetadataStoreUnprovisioned→metadataStoreUnavailableError:SERVICE_UNAVAILABLE/ 503 with an ADR-0112 status+code on the error (#5532).Driven with this door's own request:
So there is a declared envelope here waiting to be let through, and removing the catch is mechanical — the same edit #11063 made for the durable half and #11130 made for the list door's registry half.
sendThrownErroralready carries the producer's status and code.Worth stating: the in-tree comment currently overclaims
The
[#11063]note beside the durable read asserts:True of that door's durable read. Its registry read is the block quoted above. Whichever way this is triaged, that sentence wants a qualifier.
Not measured here
DELETE /api/v1/packages/:idhandler was not read for this shape.Refs
#11130 · #11063 · #10965 · #5532 · #10677 / PR #10788 · #10789 / PR #10964 · ADR-0112