Skip to content

service-datasource: the AuthzStoreUnavailableError the admin guard re-raises reaches the wire as 500 INTERNAL_ERROR, not the 503 SERVICE_UNAVAILABLE the brand declares #15999

Description

@zhuangjianguo

Found while implementing #15350 (out of its scope, filed rather than fixed inline).

What was measured

requireDatasourceAdmin in packages/services/service-datasource/src/admin-routes.ts deliberately re-raises the outage brand rather than laundering it into a denial:

} catch (err) {
  if (isAuthzStoreUnavailableError(err)) throw err;
  userId = undefined;
  systemPermissions = [];
}

That is the #13279 discipline and it is correct: an unreadable authorization store licenses no verdict. But every route in this family opens with if (await requireDatasourceAdmin(req, res)) return; and that call is not inside a try, so the throw escapes the route handler entirely. The Hono adapter renders any escaped throw as a bare 500 (adapter.ts, "route handler threw — request answered 500 with no cause in the body").

Measured on origin/main plus the #15350 branch, driving GET /api/v1/datasources with a tenancy service registered through a factory that throws:

status: 500
body:   { "success": false, "error": { "code": "INTERNAL_ERROR", "message": "No response from handler" } }

AuthzStoreUnavailableError declares status: 503 / code: SERVICE_UNAVAILABLE, and that class exists precisely so an outage is distinguishable from a capability denial on the wire. Here the distinction survives only in the sense that 500 is not 403; the declared code never reaches the caller, and the message says nothing an operator can act on.

Why this is pre-existing, and not #15350's

The escape has existed since #13279 for the ql permission-store outage, which is the fault that catch was written for. #15350 adds a second source of the identical brand at the identical seam (a tenancy service that was registered and failed to build), so it makes the path easier to reach but does not create it. #15350's pin asserts the outage CLASS deliberately — status in the set 500 or 503, never 200 and never 403 — so repairing the status here does not redden a security pin.

Scope question this card should settle

git grep over non-test packages/**/*.ts finds six sites re-raising this brand, five of them direct-mount registrars:

  • packages/services/service-datasource/src/admin-routes.ts:501 (measured, this card)
  • packages/services/service-settings/src/settings-service-plugin.ts:292
  • packages/services/service-storage/src/storage-service-plugin.ts:921
  • packages/cloud-connection/src/marketplace-install-local-plugin.ts:1764
  • packages/plugins/plugin-sharing/src/sharing-plugin.ts:857
  • packages/core/src/security/authz-store-unavailable.ts:208 (the shared helper itself)

Only the first was measured. Whether the other four render the declared 503 depends on each transport's own error path, and none of them was driven here — please do not read this list as five confirmed defects.

Two directions, not obviously one-line

  1. Per-family: each route's own catch reads err.status / err.code and relays, the way badRequest in this same file already relays a service-thrown 503/SERVICE_UNAVAILABLE envelope (IMetadataService.list() presents a known-partial answer as a complete one — the #5840 shape on the plural read #6504). Repeats the relay at every call site.
  2. Shared: the adapter, or a registrar wrapper, renders an escaped ADR-0112 envelope by its declared status and code. One place, but it changes what an escaped throw means for every direct-mount route in the repo, which is a design decision rather than a repair.

Filed unassigned; no labels, for triage to grade.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions