Found while implementing #15366 (the same defect class, one seam over — measured on origin/main at 6acb37eb9; not addressed there, and out of that card's scope by site).
The two sites
Both derive the effective tenancy posture through deps.resolveService(context, 'tenancy', …) and a bare catch:
packages/runtime/src/domains/keys.ts — handleKeysRequest's mint path: under a walled posture an org-less key is refused at mint time (walled && !activeOrganizationId). Its comment says an ABSENT service means "we cannot tell, and the honest answer at MINT time is to mint" — a deliberate decision for absent, which does not distinguish absent from failed.
packages/runtime/src/domains/activation-gate.ts — refuseUngrantedActivationWrite: under a wall-enforcing posture an organization administrator may not flip an install-wide activation row (ADR-0126 §5). With no posture the refusal is skipped (if (!posture || !postureEnforcesWall(posture)) return undefined;).
HttpDispatcher.resolveService is a capability probe: every step of its fallback chain absorbs every rejection and falls through to undefined, so a tenancy factory that threw and a name nothing registered both arrive at these gates as the same absent posture. The seam-level catch beneath it inspects nothing either.
Consequence, stated carefully (source reading, not an end-to-end run)
Under a wall-enforcing posture whose tenancy service is registered and fails to build:
POST /keys mints an organization-less key it would otherwise refuse;
- an organization administrator's activation write is served instead of refused.
Both are the permissive direction #13906 decision 1 option A ruled against at the authorization-input seam: 「A posture that could not be READ is not a posture that is ABSENT.」 The fix for #15366 gives HttpDispatcher a classified lookup (resolveServiceOrLoud: branded "never registered" → quiet undefined; every other rejection re-raised) and uses it for the identity step only. These two sites were left as they are on purpose:
- they are domain gates, not the authz-input seam — the loud answer there is a domain-level decision (the keys comment documents absent-posture-mint as deliberate, and re-reading it under the failed/absent split is a decision, not a mechanical edit);
DomainHandlerDeps.resolveService is the probe by contract, and rerouting 'tenancy' for every domain would change behaviour on every gate that reads it in one stroke.
Sizing note
In this tree tenancy is registered as an instance by plugin-auth (ctx.registerService('tenancy', …)), so the "registered and failed to build" class is reachable only through a factory registration by an embedder, or through the loader's other unbranded rejections (a scoped registration resolved without a scope id, a circular dependency). Same reachability as #15366, and the same reason it is not p0 on this reading.
Refs: #15366 (the identity-step fix) · #13906 (decision 1 option A) · #13905 (the registry brand) · ADR-0126 §5.
Found while implementing #15366 (the same defect class, one seam over — measured on
origin/mainat6acb37eb9; not addressed there, and out of that card's scope by site).The two sites
Both derive the effective tenancy posture through
deps.resolveService(context, 'tenancy', …)and a barecatch:packages/runtime/src/domains/keys.ts—handleKeysRequest's mint path: under a walled posture an org-less key is refused at mint time (walled && !activeOrganizationId). Its comment says an ABSENT service means "we cannot tell, and the honest answer at MINT time is to mint" — a deliberate decision for absent, which does not distinguish absent from failed.packages/runtime/src/domains/activation-gate.ts—refuseUngrantedActivationWrite: under a wall-enforcing posture an organization administrator may not flip an install-wide activation row (ADR-0126 §5). With no posture the refusal is skipped (if (!posture || !postureEnforcesWall(posture)) return undefined;).HttpDispatcher.resolveServiceis a capability probe: every step of its fallback chain absorbs every rejection and falls through toundefined, so atenancyfactory that threw and a name nothing registered both arrive at these gates as the same absent posture. The seam-levelcatchbeneath it inspects nothing either.Consequence, stated carefully (source reading, not an end-to-end run)
Under a wall-enforcing posture whose
tenancyservice is registered and fails to build:POST /keysmints an organization-less key it would otherwise refuse;Both are the permissive direction #13906 decision 1 option A ruled against at the authorization-input seam: 「A posture that could not be READ is not a posture that is ABSENT.」 The fix for #15366 gives
HttpDispatchera classified lookup (resolveServiceOrLoud: branded "never registered" → quietundefined; every other rejection re-raised) and uses it for the identity step only. These two sites were left as they are on purpose:DomainHandlerDeps.resolveServiceis the probe by contract, and rerouting'tenancy'for every domain would change behaviour on every gate that reads it in one stroke.Sizing note
In this tree
tenancyis registered as an instance byplugin-auth(ctx.registerService('tenancy', …)), so the "registered and failed to build" class is reachable only through a factory registration by an embedder, or through the loader's other unbranded rejections (a scoped registration resolved without a scope id, a circular dependency). Same reachability as #15366, and the same reason it is not p0 on this reading.Refs: #15366 (the identity-step fix) · #13906 (decision 1 option A) · #13905 (the registry brand) · ADR-0126 §5.