Skip to content

GET /api/v1/packages/:id swallows the same failed REGISTRY read — and answers a terminal 404 RESOURCE_NOT_FOUND for it #11376

Description

@os-zhuang

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 && typeof options.protocol.getMetaItems === 'function') {
  try {
    const result = await options.protocol.getMetaItems({ type: 'package' });
    const match = 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_FOUNDPackage "<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 rethrowUnlessMetadataStoreUnprovisionedmetadataStoreUnavailableError: 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

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