Found while implementing the 2026-09-02 ruling on #13906 (decision 2, option B). Recorded as residue of that change rather than fixed there, because closing it is new policy on the manual floor and the ruling named exactly two conditions.
What the ruling asked for, and what it produced
Decision 2 B: fail closed in one measured window — isAuthGateActive() answered true and the gate's session re-read then failed. That is implemented in RestServer.computeExecCtx (PR #15020) by raising AuthzStoreUnavailableError('auth_gate', err), which the existing fail-closed nets carry to the door as a 503.
The refusal is raised at context-resolution time. But the gate's own consumer, enforceAuth, does not block unconditionally — it exempts allow-listed paths:
const pathExempt =
typeof req?.path === 'string' && req.path.length > 0 && isAuthGateAllowlisted(req.path);
if (gate && req?.method !== 'OPTIONS' && !pathExempt) {
and the comment above it names the purpose: "a gated session (carrying authGate) is blocked from protected resources, while the core allow-list keeps auth + remediation reachable."
⇒ In the ruled window those remediation routes are also answered 503, because the refusal happens before any path is consulted. A user under an active gate whose session backend is briefly faulting therefore cannot reach the very routes provided so they can clear their gate.
⚠️ NOT MEASURED — this is a code reading
Stated plainly so triage is not misled: I did not drive this. It follows from where the throw sits relative to the pathExempt computation, and the #13906 work measured the throw itself, not its interaction with the allowlist. The first thing to do here is drive it: an active gate, a failing re-read, and a request to an allow-listed path, with a positive control on the same fixture.
Why it was not fixed in PR #15020
- The ruling's window is two conditions; a path-based carve-out inside
computeExecCtx is a third, and it is runtime authorization behaviour — the 2026-08-28 negative-boundary ruling puts that on the manual floor.
- It is not obviously wrong as it stands.
AuthzStoreUnavailableError is an outage (503), not a permission denial: the caller retries and remediation returns when the backend does. Whether an outage answer should be narrowed to protected paths is a product judgement about availability, the same axis the original decision 2 was ruled on.
The options, if this is taken up
- Leave it. A 503 is transient and honest; the gate could not be read, so nothing about this caller was determined — including whether they are gated at all.
- Narrow the refusal to non-exempt paths — consult
isAuthGateAllowlisted(req.path) before raising, so a gated user keeps the remediation door during the fault. Costs: computeExecCtx starts branching on the request path for an authorization decision, and the memoized per-request context becomes path-sensitive.
- Move the refusal into
enforceAuth — carry "the gate could not be read" as a distinct context value and let the existing consumer, which already knows about pathExempt, decide. Keeps one place that reasons about gate exemptions, but widens ExecutionContext.
⛔ No recommendation asserted on an unmeasured reading; option 3 is the one that puts the decision where the allowlist already lives, and that is an observation about structure, not a verdict.
Related, and distinct
Found while implementing the 2026-09-02 ruling on #13906 (decision 2, option B). Recorded as residue of that change rather than fixed there, because closing it is new policy on the manual floor and the ruling named exactly two conditions.
What the ruling asked for, and what it produced
Decision 2 B: fail closed in one measured window —
isAuthGateActive()answeredtrueand the gate's session re-read then failed. That is implemented inRestServer.computeExecCtx(PR #15020) by raisingAuthzStoreUnavailableError('auth_gate', err), which the existing fail-closed nets carry to the door as a 503.The refusal is raised at context-resolution time. But the gate's own consumer,
enforceAuth, does not block unconditionally — it exempts allow-listed paths:and the comment above it names the purpose: "a gated session (carrying
authGate) is blocked from protected resources, while the core allow-list keeps auth + remediation reachable."⇒ In the ruled window those remediation routes are also answered 503, because the refusal happens before any path is consulted. A user under an active gate whose session backend is briefly faulting therefore cannot reach the very routes provided so they can clear their gate.
Stated plainly so triage is not misled: I did not drive this. It follows from where the throw sits relative to the
pathExemptcomputation, and the #13906 work measured the throw itself, not its interaction with the allowlist. The first thing to do here is drive it: an active gate, a failing re-read, and a request to an allow-listed path, with a positive control on the same fixture.Why it was not fixed in PR #15020
computeExecCtxis a third, and it is runtime authorization behaviour — the 2026-08-28 negative-boundary ruling puts that on the manual floor.AuthzStoreUnavailableErroris an outage (503), not a permission denial: the caller retries and remediation returns when the backend does. Whether an outage answer should be narrowed to protected paths is a product judgement about availability, the same axis the original decision 2 was ruled on.The options, if this is taken up
isAuthGateAllowlisted(req.path)before raising, so a gated user keeps the remediation door during the fault. Costs:computeExecCtxstarts branching on the request path for an authorization decision, and the memoized per-request context becomes path-sensitive.enforceAuth— carry "the gate could not be read" as a distinct context value and let the existing consumer, which already knows aboutpathExempt, decide. Keeps one place that reasons about gate exemptions, but widensExecutionContext.⛔ No recommendation asserted on an unmeasured reading; option 3 is the one that puts the decision where the allowlist already lives, and that is an observation about structure, not a verdict.
Related, and distinct
computeExecCtxseams read "failed" and "not wired" as one value, and both feed authorization inputs — tenancy posture and the ADR-0069 auth gate #13906 / PR fix(rest): keep "failed" and "not wired" apart at the two computeExecCtx authorization-input seams #15020 — the fail-closed window this residue comes from.isAuthGateAllowlisted, but the other direction: itsno path ⇒ exemptdefault. Different condition, different fix; noted so the two are not merged by keyword.