Skip to content

Context-Aware Authorization & Navigation Model

This document defines the 4-axis context model that governs modern navigation and access control across the platform.

1. The Core Principle

Access and navigation are no longer static "Role" based. They are Relationship-Backed, Forensically Verified, and Context-Oriented. - The Graph: The Directory is the system of record. - The Lifecycle: Capabilities are only provisioned after a Forensic Verification event (Standard 78). - The Snapshot: A user's capabilities are calculated at login as an AuthzSnapshot. - The Context: The user selects a specific "Hat" (Persona) and "Scope" (Target) to act within.

2. The 4-Axis Context Model (Implementation)

The platform's global state is governed by four primary axes as defined in ContextState:

Axis Field Definition
Hat activeHat The "acting role" for this session (Persona).
Scope activeScope The visibility boundary (My Units vs Building).
Asset activeAssetId The specific physical target (Unit 104, Plot B).
Principal actingForPrincipalId The entity being represented (Acting On-Behalf-Of).

2.1 Acting-For (Delegation)

The actingForPrincipalId allows for Proxy Interaction. - If null, the user acts as themselves ("Me"). - If set, the user acts as the target Principal using the capabilities granted by the Delegation Bundle (e.g., proxy_voting).

Standard: All representations must follow the Standard of Delegation (Time-bound, Scope-bound, and Traceable).

2.2 Persona Facets & Bundles

  • Facets (Sub-Roles):
    • Governance: President, Treasurer, Secretary, VigilanceMember.
    • Staff Dept: Maintenance, BuildingEngineer, FrontDesk, Security, Cleaning, Accounting, Concession.
    • Legal: Counsel, Notary.
    • Occupancy: TenantLong, TenantShort (STR).
    • Agent: PropertyManager, SalesAgent.
  • Governance Constraints:
    • Owner-Only Voting: Voting rights for Board roles (President/Treasurer/Secretary) are strictly predicated on the OWNER Hat.
    • Non-Voting Officers: A STAFF member serving as Secretary does not transition to a voting Board context.
  • Bundles: Pre-defined sets of capabilities defined in the Functional Capability Registry (Standard 78).
  • Registry-Driven Infusion: The system no longer hardcodes role-to-capability maps. It pulls the bundle from registry.ts and infuses it into the AuthzSnapshot during graph traversal.

3. AuthzSnapshot & Dirty Detection

To ensure security without repeated expensive graph traversals, the platform uses a Snapshot mechanism.

  • Storage: The snapshot is hydrated on the client at login.
  • Validation: Every server action verifies the user's AuthzSnapshot against the required context of the action.
  • Dirty Detection (authzVersion):
    • The DirectoryProfile tracks an authzVersion (timestamp/counter).
    • If a relationship changes (e.g., a Deed is transferred or a Board term ends), the authzVersion is bumped in the DB.
    • The platform detects the mismatch between the "Session Snapshot Version" and the "Latest Directory Version" and triggers a re-fetch of permissions (the "Dirty Snapshot" pattern).

4. Relationship-Based Role Derivation

Permissions are derived through graph traversal: - Email -> DirectoryProfile. - Profile -> Deeds -> Units (Produces OWNER role + MY_UNITS scope). - Profile -> BoardAssignment (Produces BOARD role + BUILDING scope). - Profile -> VendorAssignment (Produces VENDOR role + ASSIGNED_CONTRACT scope).


5. Implementation Status (Jan 11, 2026)

Status: Waves 1-3 - COMPLETED Status: Wave 4 (Server Enforcement) - COMPLETED Status: Wave 5 (Forensic Enrollment) - COMMENCED

The context-aware engine is now active across the platform.

The following foundational interfaces have been codified in apps/platform/src/lib/context/types.ts:

5.1 ContextState (Active State)

Defines the current "View" of the user.

export interface ContextState {
  activeHat: UserRole; // Why I am here (Persona)
  activeScope: 'my_units' | 'building'; // Action radius
  activeAssetId?: string; // Specific target (if applicable)
  actingForPrincipalId?: string; // Representation target
  isInitialized: boolean;
}

5.2 AuthzSnapshot (Performance Cache)

Computed on login/switch to avoid N+1 DB calls during navigation.

export interface AuthzSnapshot {
  authzVersion: number; // For dirty invalidation
  availableHats: UserRole[];
  availablePrincipals: PrincipalOption[];
  availableAssets: Record<string, AssetOption[]>;
  capabilities: Record<string, boolean>; // Pre-computed boolean map
}

5.3 The Graph Resolver (Server)

Located in lib/auth/graph-resolver.ts. Responsible for mapping a basic User Identity + Roles into a multi-hat AuthzSnapshot. - Relationship Traversal: Queries Deeds/Leases to find owned/managed Units. - Registry Mapping: Injects the Capability Bundle from the CAPABILITY_REGISTRY corresponding to the resolved personas.

5.4 The Context Provider (Client)

Located in lib/context/ContextStateProvider.tsx. - Refreshes the AuthzSnapshot on mount via Server Action. - Maintains activeHat, activeScope, and activeAssetId. - Provides the usePlatformContext hook for workbenches. - Wired: Now wraps the Entire Authenticated Application in app/(app)/layout.tsx.

5.5 The Context Switcher (UI)

Located in components/navigation/ContextSwitcher.tsx. A Top-Header HUD that allows users with multiple capacities (e.g. Board Member + Owner) to swap their acting perspective instantly, which automatically filters the Registry-Driven Sidebar.


6. Server-Side Guard & Context Verification

As of Jan 06, 2026, the transition from "Hiding" (UI-only) to "Enforcement" (Server-side) is underway.

6.1 The verifyContext Guard (lib/auth/guard.ts)

This primitive serves as the final checkpoint for every write operation. It requires a clientContext (the claim) and a requirements object.

  • Non-Breaking Stabilization (Wave 1.5): To support progressive migration, the clientContext parameter is optional.
  • Fallback Verification: If context is missing, the guard performs a Direct Snapshot Verification using a provided fallback.assertAssetId from the action payload.
  • Identity Re-Verification: Re-resolves the AuthzSnapshot on the server to ensure the activeHat (or fallback identity) is valid.
  • Asset Boundary Enforcement: Validates that activeAssetId (or the fallback assertAssetId) is actually owned by or delegated to the user.
  • Hard-Deny (Wave 1.6): The guard explicitly throws an error if assertAssetAccess is required but no asset ID is provided (either via context or fallback payload). No "defaults" or "wildcards" are permitted.
  • Capability Checks: Verifies specific binary permissions (e.g., can_vote).

6.2 Mandatory Governance Rule

For governed actions (Voting, Proxies), a target assetId MUST be identifiable either via the context OR the payload regardless of migration status. If no asset can be verified as owned/authorized, the action must hard-deny.

6.2 Implementation (Phase 3 Wave 1)

  • Governance: submitVote and castVoteAction now require a verified context matching the target unit. castVoteAction strictly rejects ambiguous calls that lack a specific asset context.
  • Finance: Secured postJournalEntryAction against unauthorized administrative access.

6.3 Mock Snapshot Authority

Since the platform is in a transitional state from user.role (string) to full relationship traversal, the Graph Resolver (lib/auth/graph-resolver.ts) enforces conservative mock authority: - Finite Unit Set: Only returns unit-304 and unit-102 as available assets. - Strict Mapping: Assets are only granted if the base user role supports them (e.g., Guest has 0 assets). - No Wildcards: The resolver contains no logic for "All Assets" or "Default to First Available" in the authoritative path, preventing accidental privilege escalation during development.

6.4 Privileged Guard Relaxation (Wave 1.7 - Jan 06, 2026)

To resolve Hazard 446 (Stale Context Scope Mismatch), the verifyContext guard has been enhanced with localized relaxation for administrative personas.

  • Objective: Prevent administrative interactions (e.g., Work Order escalation) from being blocked by stale resident-mode context (e.g., activeScope: 'my_units') if the user's current hat inherently provides the required access.
  • Logic: If the requirements.scope is set to building, and the user is acting under a privileged hat (ADMIN, STAFF, BOARD_MEMBER), the guard auto-corrects the scope claim instead of throwing a Scope Mismatch error.
  • Diagnostics: Auto-corrections are logged as warnings in the server console during development/testing to identify components that are failing to update their client-side context state correctly.

7. Hybrid Identity Model: "The Two Keys"

The platform operates a hybrid model to manage infrastructure ownership (Firebase) vs. human persona representation (GCP).

7.1 Key 1: The "Robot" Key (Firebase)

  • Purpose: System-level infrastructure and data management.
  • Used For: Real-time DB CRUD, Storage security, and low-level auth.

7.2 Key 2: The "Delegate" Key (OAuth/GCP)

  • Purpose: Brand/Human Persona actions. Representation of the Community to external entities.
  • Implementation: Domain-Wide Delegation allows the service account to impersonate platform-admin@singulardream.org for tasks like mailing from service@singulardream.org or managing Google Groups.

8. Branching Authorization Pattern (Standard 74.2)

To balance privacy with administrative efficiency, server actions implement a branching pattern:

  1. Branch A: Administrative (Building Scope) If the user is a known Admin/Staff, they bypass specific asset ownership checks for building-wide data.
  2. Branch B: Identity-Backed (Unit Scope) If the user is a Resident/Owner, the guard strictly verifies their relationship to the specific activeAssetId or the payload's assertAssetId.

9. AuthZ Migration & "Hostage Rescue" Strategy

The platform is transitioning from a legacy Firebase-claim based model to the 4-axis Relationship model. During this period, "Hostage" accounts (those using deprecated role strings) are handled via the Transition Bridge:

9.1 The Bootstrap Hat

Legacy roles are treated as high-level "Hats" without underlying relationship data. - role: 'admin' -> Auto-maps to activeHat: STAFF + activeScope: building. - role: 'resident' -> Auto-maps to activeHat: OWNER + activeScope: my_units.

9.2 The Mapping Guard

The verifyContext guard (Section 6.1) implements a "Soft-Fail" for legacy roles: 1. Attempt Snapshot: Try to resolve a full AuthzSnapshot from the Directory. 2. Legacy Fallback: If no Directory profile exists, build a temporary snapshot based solely on the legacy Firebase role. 3. Encouraged Migration: UI prompts users with "Partial Context" to complete their Directory onboarding.

9.3 Hard Deprecation Goal

By Wave 5, all server actions will require a DirectoryProfile reference. Custom claims will be used only for Initial AuthN, while the AuthZ Snapshot becomes the sole authority for capabilities.


10. The Delegation Standard (Protocol 74.3)

To support Proxy Voting and Property Management, the platform implements a first-class "Acting-As" protocol.

10.1 The Masquerade Contract

A user ($Actor) may act on behalf of another ($Principal) if and only if: 1. Grant Exists: A valid DelegationGrant record exists in the Directory. 2. Time-Bound: The current server timestamp is within [grant.validFrom, grant.validUntil]. 3. Scope-Bound: The $Actor inherits only the intersection of the Principal's rights and the Grant's scope.

10.2 Delegation Scopes

Scope ID Description Typical Use Case
DELEGATE_VOTE Cast votes in Governance. Proxy for Annual Meeting.
DELEGATE_MANAGE Full Unit Operations (Work/Payments). Property Manager.
DELEGATE_READ Read-Only visibility. Auditor / Lawyer.

10.3 Traceability

Every action performed under delegation must be logged with the dual-identity signature: { actorId: "user-123", principalId: "user-456", context: "DELEGATE_VOTE" }


Created Jan 06, 2026. Phase 3.1: Consolidated Hybrid Identity and Branching Auth patterns. Updated Jan 08, 2026 with 6-Layer Persona support and Hostage Rescue strategy.