Skip to content

Commit 5a42404

Browse files
committed
chore(changeset): grade the dispatcher auth gate's outage answer as a launch-window breaking narrowing
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_015sSBzUJfLxTo3ZZ68LLfCG
1 parent ebf5848 commit 5a42404

2 files changed

Lines changed: 22 additions & 3 deletions

File tree

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/runtime': minor
3+
---
4+
5+
fix(runtime)!: the runtime dispatcher's ADR-0069 gate answers `503` when an active gate cannot read the session, as REST does
6+
7+
Clause-②: no (narrowing) — an active gate's outage now refuses what it served; core's existing exports only, no published schema or export moves.
8+
9+
<!-- adr-0087: not-required (no-migration-prescription) A runtime authentication narrowing at one seam, not a metadata change. No spec key, export, option, config field, response field or stored shape is removed, renamed or re-shaped, and every request whose session read answers receives the same answer as before, so there is no tombstone and nothing for `objectstack migrate meta` to rewrite. What narrows is one window: while an ADR-0069 gate feature is active and the dispatcher's gate cannot read the session, the request is now refused with `503 SERVICE_UNAVAILABLE` instead of being served. The other categories are closed on facts: `@objectstack/runtime` publishes (not unpublished); no ADR-0087 id covers this seam and this diff adds none (not registered / already-registered); and no published interface or type changes, the method being private (not runtime-interface-only / type-surface-only / runtime-export-retired). -->
10+
11+
**BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention for breaking changes.
12+
13+
ADR-0069 holds a signed-in user out of every protected resource while their password has expired or while enforced MFA is not yet enrolled. While a gate feature is active, the runtime dispatcher (`HttpDispatcher`) reads the session again at its own gate to ask that question. When that read failed, because the auth service could not answer, the dispatcher took the failure for "no session" and served the request, so a session the policy holds passed the gate on every dispatcher-served route for as long as the outage lasted. REST, and the dispatcher plugin's declared-endpoint fallback, answer the same window `503`.
14+
15+
- **Refused now:** with a gate feature active, a request whose gate read fails (the session read rejects, or the auth service cannot hand back its session API) is answered `503` with the code `SERVICE_UNAVAILABLE`, in the dispatcher's existing error envelope. It is the authorization-store outage (`AuthzStoreUnavailableError`, `@objectstack/core`, read object `auth_gate`) that REST raises for the same window, and the route's own handler does not run.
16+
- **Unchanged:** with no gate feature active the gate makes no read at all, and a deployment whose gate probe itself fails is treated as having no active gate, as on REST. Whenever the read answers, the answers are as before: a held session gets the gate's `403` with its own `code`, and a session the policy does not hold, an API-key caller and an anonymous caller are served as before.
17+
18+
What changes for you. Only deployments with password expiry or enforced MFA turned on are affected, and only while their auth service cannot answer a session read: requests on routes the runtime dispatcher serves now answer a retriable `503` instead of being served. Retry once the auth service answers. Nothing needs configuring.

‎packages/runtime/src/http-dispatcher.auth-gate-outage.pin.test.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,10 @@ describe('[#23245] HttpDispatcher.enforceAuthGate — an active gate that cannot
137137
});
138138

139139
it('CASE 1 — OUTAGE: an active gate whose session read fails rejects with the branded 503, and the member never runs', async () => {
140-
// SUPERSEDED, quoted — what origin/main answered on this fixture: the
141-
// read's rejection became "no session", the gate admitted, and
142-
// expect(member.handleRedeliver).toHaveBeenCalledTimes(1); // served
140+
// What origin/main answered on this fixture: the read's rejection
141+
// became "no session", the gate admitted, and `dispatch()` RESOLVED —
142+
// the request went on to the route exactly as the gate-off control
143+
// (CASE 2, same backend) still does.
143144
const { dispatcher, member } = harness({ gateActive: true, backend: 'outage' });
144145
const err: any = await rejectionOf(dispatcher.dispatch('POST', ROUTE, {}, {}, ctx(request())));
145146
expect(err, '`dispatch()` RESOLVED — the active gate failed open on the outage').toBeDefined();

0 commit comments

Comments
 (0)