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.
High-level diagram
Section titled “High-level diagram”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 --> EMAILTenant boundary
Section titled “Tenant boundary”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).
Persona surfaces
Section titled “Persona surfaces”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 singlearId.
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.
Demo store
Section titled “Demo store”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)walkthroughStepliveBreachesandliveMIReturns(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.
Mock-vs-real seam
Section titled “Mock-vs-real seam”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.
Source files
Section titled “Source files”| Concern | File |
|---|---|
| Type definitions | lib/types.ts |
| Demo store | lib/state.ts |
| Fixtures | lib/fixtures.ts |
| Skins (tenants) | lib/skins.ts |
| File-review rubrics | lib/rubrics.ts |
| Risk scoring | lib/risk-scoring.ts |
| Walkthrough script | lib/walkthrough.ts |
| Principal components | components/principal/* |
| Shell, persona switcher | components/shell/* |
| App routes | app/demo/principal/*, app/demo/ar/* |