MeridiansMeridians

Access gate [Flow]

From the Meridians Wiki · Public · Maintained · joint

AccessProvider (lib/core/reducer/access-context) wraps every route and decides what renders before the page. The decision ladder:

flowchart TB
    Enter([any route]) --> Fresh{fresh master?}
    Fresh -->|yes| Claim["claim the instance<br/>via Supabase (no local PIN)"]
    Fresh -->|no| Public{public route?}
    Public -->|yes| Page["render the page"]
    Public -->|no| Signed{signed in?}
    Signed -->|no| Portal["UserPortal<br/>member sign-in · guest entry"]
    Signed -->|yes| Role{role allows route?}
    Role -->|no| Deny["NoAccess panel"]
    Role -->|yes| Page

    Claim -.->|"AFTER sign-in,<br/>non-blocking"| Ent{gateway entitled?}
    Ent -->|denied| Locked["locked portal"]
    Ent -->|"entitled OR unreachable"| Page
  • Role is decided by origin (getSyncRole): localhost / Electron / canonical web = master (owns its encrypted IndexedDB); a tunnel origin = client (joined a host). isLocalRequest is the server-side authority check.
  • Auth is local-first. Sign-in resolves entirely on the instance (beginLogin → login → signInMember); the Director's credential lives on Supabase. The entitlement check is a separate, non-blocking layer run after the session exists — an unreachable gateway keeps access. Never put a gateway call in the sign-in path.
  • Portal + onboarding render inside the shared GatewayShell, so signing in flows straight into the home console.
  • <ScenarioHost/> mounts at the app root, outside the gate, so the master keeps hosting every live game regardless of route. Inert on clients.

Public routes: /, /join, /white-paper, /faq, /scenario. Full journey: PRODUCT_BEHAVIOURS.md.