FF-EPIC-13-S1 AC3/AC4 · FF-EPIC-10 fail-closed

States are contract — the shell must handle every one

Branding resolves from GET /api/v1/portal/context, which can be pending, fail, or fail closed. Each state below is a named surface (data-state) the implementation and Playwright specs are measured against. A frame that showed only the happy path would produce a shell that only handled the happy path.

data-state="loading"boot

Neutral skeleton until branding resolves — no flash of default FuzeFront branding before the tenant's paints (FF-EPIC-10-S2 AC3).

data-state="error"context failed

We couldn't load this workspace

The portal context request failed or timed out. We won't guess your branding.

data-state="suspended"HTTP 403

This portal is unavailable

This workspace has been suspended. Contact your administrator if you think this is a mistake.

resolvePortalContext → suspended → 403 (FF-EPIC-10-S1 AC4)

data-state="unknown-host"root fallback
FuzeFront
portal · root

An unrecognized host doesn't leak another tenant's identity — it falls back to the root FuzeFront portal for shell/UI routes (API routes needing an explicit portal get 404). Fail-closed, never fail-open (FF-EPIC-10-S1 AC3).

data-state="cross-portal-reject"wrong portal
This account isn't part of CorpABC. The email you entered belongs to a different portal. Sign in on your own portal's address, or ask a CorpABC admin to invite you.

token portal_id ≠ resolved-host portal → reject, re-auth required (FF-EPIC-10-S3 AC3) · scoping is token-derived, never a URL param.

design-review note · five contract states, each addressable by data-state for the RED Playwright specs. Loading proves the no-flash requirement; suspended + unknown-host + cross-portal prove fail-closed. Error offers retry, never a silent unbranded fallback.
Review · white-label-portal approve or reject each flow ↓
portal-shellapproved · izzywdev
portal-loginapproved · izzywdev