Found while censusing the resolveAuthzContext callers under #15256 (ruling item 5). This is not that card's hole — the runtime door does supply a tenancyPosture, and it is one of only two callers that do. It is the ADJACENT defect, at the same seam, in the same permissive direction.
The site
packages/runtime/src/security/resolve-execution-context.ts:172
let tenancyPosture;
try {
tenancyPosture = effectiveTenancyPosture(await opts.getService('tenancy'));
} catch {
tenancyPosture = undefined;
}
A bare catch. Every rejection resolves to "no posture", and no posture means both posture-conditional API-key refusals (organization_required, organization_membership_ended) are skipped.
Why that is the shape #13906 already ruled on
#13906 decision 1 option A separated exactly these two facts at the REST seam, because collapsing them made a FAILURE read as "this check does not apply":
- never registered — branded (
isServiceNotRegisteredError), quiet undefined. The supported no-tenancy composition; refusing there would break every single-org embedder.
- registered and FAILED to build — unbranded, and must be re-raised so the door answers
AuthzStoreUnavailableError (503). A posture that could not be READ is not a posture that is ABSENT.
rest-server.ts carries that discrimination on both of its wirings (the second one added by #15256). This site carries neither half: the comment above it says only "Absent service ⇒ undefined", which describes the first case and silently covers the second.
Consequence, stated carefully
Under a wall-enforcing posture, on the runtime dispatcher door, a tenancy service that was registered and whose factory threw would admit an organization-less key and an ex-member's organization-stamped key — the #15256 outcome, reached through a service outage rather than through a missing provider. I have not measured this end-to-end; the reading here is of the source and of the two guards it feeds. Sizing it should start with the same kind of measurement #15163 ran for REST.
⛔ Note the repair is not "wrap it the way REST does" without checking opts.getService's rejection contract at this call site — REST splits on the kernel's async accessor being present for reasons that may not transfer.
Refs: #15256 (the census this came from) · #13906 (decision 1 option A) · #15163 · ADR-0105 D2/D3.
Found while censusing the
resolveAuthzContextcallers under #15256 (ruling item 5). This is not that card's hole — the runtime door does supply atenancyPosture, and it is one of only two callers that do. It is the ADJACENT defect, at the same seam, in the same permissive direction.The site
packages/runtime/src/security/resolve-execution-context.ts:172A bare catch. Every rejection resolves to "no posture", and no posture means both posture-conditional API-key refusals (
organization_required,organization_membership_ended) are skipped.Why that is the shape #13906 already ruled on
#13906 decision 1 option A separated exactly these two facts at the REST seam, because collapsing them made a FAILURE read as "this check does not apply":
isServiceNotRegisteredError), quietundefined. The supported no-tenancy composition; refusing there would break every single-org embedder.AuthzStoreUnavailableError(503). A posture that could not be READ is not a posture that is ABSENT.rest-server.tscarries that discrimination on both of its wirings (the second one added by #15256). This site carries neither half: the comment above it says only "Absent service ⇒ undefined", which describes the first case and silently covers the second.Consequence, stated carefully
Under a wall-enforcing posture, on the runtime dispatcher door, a
tenancyservice that was registered and whose factory threw would admit an organization-less key and an ex-member's organization-stamped key — the #15256 outcome, reached through a service outage rather than through a missing provider. I have not measured this end-to-end; the reading here is of the source and of the two guards it feeds. Sizing it should start with the same kind of measurement #15163 ran for REST.⛔ Note the repair is not "wrap it the way REST does" without checking
opts.getService's rejection contract at this call site — REST splits on the kernel's async accessor being present for reasons that may not transfer.Refs: #15256 (the census this came from) · #13906 (decision 1 option A) · #15163 · ADR-0105 D2/D3.