Skip to content

Architecture overview

Lending Agent Oversight is two persona surfaces over one supervision record. The principal-side surface is for the buyer (a principal-admin or compliance officer running an AR/IAR network). The AR-side surface is for the supervised individual or firm. Both surfaces read and write the same underlying entities (AppointedRep, BreachReport, FileReview, MIReturn, AnnualReview, ConductEvent, AuditEvent), scoped to one tenant.

The v1 product is a marketing demo. State lives in a Zustand store (lib/state.ts) and a deterministic fixture set (lib/fixtures.ts). The production system replaces the store with a Postgres database, row-level security on tenant_id, scheduled workers (anomaly recompute, SUP 15 deadline alerter, FCA Register reconciliation), and a tamper-evident audit chain. The shapes do not change at the boundary, only the persistence layer.

flowchart LR
subgraph Browser
P[Principal surfaces<br/>/demo/principal/*]
A[AR surfaces<br/>/demo/ar/*]
Z[(Zustand store<br/>lib/state.ts<br/>persisted to localStorage)]
end
subgraph DemoOnly[v1 demo]
F[(Fixtures<br/>lib/fixtures.ts<br/>deterministic, seeded)]
R[(Rubrics<br/>lib/rubrics.ts)]
S[(Skins<br/>lib/skins.ts)]
end
subgraph Production[Production, planned]
API[Next.js route handlers<br/>/api/*]
DB[(Postgres<br/>row-level security on tenant_id)]
AUDIT[(Audit chain<br/>SHA-256 prev-hash)]
JOBS[Workers<br/>risk recompute, deadline alerter,<br/>FCA Register reconciliation]
S3[(Object store<br/>Attachments)]
EMAIL[Postmark]
end
P --> Z
A --> Z
Z --> F
P --> R
P --> S
A --> S
P -.production.-> API
A -.production.-> API
API --> DB
API --> AUDIT
JOBS --> DB
API --> S3
API --> EMAIL

Each principal firm is one tenant. In the demo, the active tenant is encoded as a skin id (heritage, crown, pinpoint) in the URL and the Zustand store. In production, every persisted row carries tenantId and Postgres row-level security enforces tenant_id = current_setting('app.tenant_id')::uuid on every read and write. Session middleware sets the GUC at the start of each request. There is no cross-tenant query path that bypasses RLS, including in the API handlers (defence in depth).

Two role views live behind the same skin:

  • Principal compliance surface (/demo/principal/*). Network dashboard, AR register, breach triage queue, file-review workspace, annual-review packet, audit log. Roles: principal-admin, principal-compliance-officer, fca-auditor (read-only, feature-flagged).
  • AR-user surface (/demo/ar/*). Required actions, own risk score, MI-return submission, breach filing, principal communications. Role: ar-user, scoped to a single arId.

Persona switching in the demo is a chrome affordance (“Principal | AR” segmented control in the top strip). In production a person who legitimately holds both roles maintains two accounts and signs in to each.

useDemoStore (Zustand, persisted to localStorage under key lao-demo-state) tracks:

  • skin (the active tenant)
  • persona (the active role view)
  • focusedArId (so persona switches land on the right AR)
  • mode (scripted walkthrough or free explore)
  • walkthroughStep
  • liveBreaches and liveMIReturns (in-session writes layered onto fixtures so a breach filed AR-side surfaces on the principal triage queue immediately)

Only skin is persisted across reloads. Persona, mode, walkthrough step, and live writes reset every session, so a returning visitor lands in scripted mode on a clean fixture set. See Cold-start and recovery for the full reset semantics.

Every interactive surface in the demo has a production counterpart that swaps the in-memory store for a Postgres-backed API call. The shape on the wire is the same Zod-validated payload. For the full seam-by-seam mapping, see Mock-vs-real boundary.

ConcernFile
Type definitionslib/types.ts
Demo storelib/state.ts
Fixtureslib/fixtures.ts
Skins (tenants)lib/skins.ts
File-review rubricslib/rubrics.ts
Risk scoringlib/risk-scoring.ts
Walkthrough scriptlib/walkthrough.ts
Principal componentscomponents/principal/*
Shell, persona switchercomponents/shell/*
App routesapp/demo/principal/*, app/demo/ar/*