You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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
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:
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.
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/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.
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.
Found while implementing #15350 (out of its scope, filed rather than fixed inline).
What was measured
requireDatasourceAdmininpackages/services/service-datasource/src/admin-routes.tsdeliberately re-raises the outage brand rather than laundering it into a denial: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/mainplus the #15350 branch, drivingGET /api/v1/datasourceswith atenancyservice registered through a factory that throws:AuthzStoreUnavailableErrordeclaresstatus: 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
qlpermission-store outage, which is the fault thatcatchwas written for. #15350 adds a second source of the identical brand at the identical seam (atenancyservice 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 grepover non-testpackages/**/*.tsfinds 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:292packages/services/service-storage/src/storage-service-plugin.ts:921packages/cloud-connection/src/marketplace-install-local-plugin.ts:1764packages/plugins/plugin-sharing/src/sharing-plugin.ts:857packages/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
catchreadserr.status/err.codeand relays, the waybadRequestin this same file already relays a service-thrown503/SERVICE_UNAVAILABLEenvelope (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.statusandcode. 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.