Production hardening
The demo is production-shaped, not production-ready. This page is the gap analysis.
What to do first
Section titled “What to do first”Five things, in priority order:
- Persistent storage (replace the in-memory
Map) - Authentication on the audit pages
- Real lender adapters (replace
lib/decision-engine.ts) - Real disclosure publishing (replace hardcoded
lib/disclosures.ts) - Replay regression in CI
1. Persistent storage
Section titled “1. Persistent storage”The demo uses an in-memory Map<sessionId, ServerSession> plus URL-borne seeds. This works for demos. For production, replace with a real store.
The interface to swap is in lib/server-store.ts:
ensureSession(sessionId): ServerSessiongetSession(sessionId): ServerSession | undefinedappendCustomerMessage(sessionId, msg): voidappendInstallerMessage(sessionId, msg): voidapplyEvents(sessionId, events): CaseStatesetWaterfall(sessionId, w): CaseStateThese six functions are the boundary. Everything else reads through them.
A reasonable swap:
- Vercel KV / Upstash Redis for case state and message history. Sub-millisecond latency, Vercel-native.
- Postgres for the audit log (long retention, query-friendly).
The seed pattern continues to work as a recovery mechanism even with a primary store. It becomes a defensive fallback, not the source of truth.
See Cold-start recovery for the seed pattern’s privacy implications. With a real store, three things change about the seed:
- It carries a signature (HMAC-SHA256 over the JSON, with a server-side key).
hydrateFromSeedrejects any seed whose signature doesn’t verify, so a tampered seed cannot inject case state. - It encrypts personal data fields (name, DOB, address, financial facts). The seed in browser history and referrers no longer leaks PII.
- It includes a monotonic version (or a server-side timestamp + nonce) so a stale seed cannot be replayed against a newer in-memory state. Memory wins on ties when the seed is older.
These three together turn the seed from “demo-grade recovery” into “production-grade authenticated state envelope”.
2. Authentication
Section titled “2. Authentication”The audit page is unauthenticated in the demo. Anyone with a session ID can view full case state including PII. This is a deliberate demo trade-off; in production it must change.
What to add:
- Customer surface: magic-link auth on the customer URL. The customer receives the URL by SMS; opening it sends a one-time code to their email or to their phone for a re-auth check. The seed pattern stays in the URL but is encrypted.
- Audit dashboard: behind broker SSO with role-based access. Compliance reviewers see anonymised data by default; raw PII requires elevated permission.
- Installer surface: behind retailer SSO. The installer is a known user; treat the installer ID as part of the case provenance.
- Broker handoff page: trust-gradient depends on this being a controlled surface; harden authentication accordingly.
Vercel supports several auth integrations: NextAuth, Clerk, Auth0, Workos. Pick what fits the existing identity stack.
3. Real lender adapters
Section titled “3. Real lender adapters”lib/decision-engine.ts is the only file that knows about specific lenders. Replace its runWaterfall with a call to a panel of real LenderAdapter instances.
interface LenderAdapter { name: string; position: Offer["position"]; productCode: Offer["productCode"]; decide(application: ApplicationPayload): Promise<WaterfallStepOutcome>;}
async function runWaterfall({ caseState, fromIndex }) { const steps = []; for (let i = fromIndex ?? 0; i < panel.length; i++) { const adapter = panel[i]; const outcome = await adapter.decide(buildApplicationPayload(caseState)); steps.push({ lender: adapter.name, position: adapter.position, outcome }); if (outcome.kind !== "declined") break; } return { steps, ... };}Each lender adapter wraps that lender’s existing decision API. The waterfall calls them sequentially.
See Lender panel integration and Decision API adapter.
4. Real disclosure publishing
Section titled “4. Real disclosure publishing”lib/disclosures.ts hardcodes disclosure body text. In production:
- Each disclosure has a stable id, a body, and a version
- The audit log records the version that was presented
- A new version requires legal/compliance signoff before going live
- Old versions stay accessible for cases that were run against them
A simple swap: a CMS (Sanity, Contentful, Strapi, or just JSON files in a versioned S3 bucket) keyed by disclosure id, returning the current published version plus a way to fetch historical versions.
5. Replay regression in CI
Section titled “5. Replay regression in CI”The replay engine is on-demand in the demo. In production it should run automatically:
- A corpus of representative historical cases stored in CI
- Every prompt change, model version change, or disclosure change re-runs replay against the corpus
- Pass-rate drops below threshold (say 95% per disclosure) fails the CI check
- Drift detection alerts a human
This is what turns the replay engine from “compliance theatre we can show a regulator” into “compliance evidence the broker actually operates against”.
See Audit & replay integration.
Other hardening tasks
Section titled “Other hardening tasks”In rough priority order:
| Task | Why |
|---|---|
| Real DPIA + lawful-basis review | UK GDPR, see Privacy: DPIA |
| Sub-processor agreement with Anthropic | Article 28 GDPR, see Privacy: sub-processors |
| Vulnerability process integration | Consumer Duty, see Vulnerability process |
| KYC + AML before pre-contract | Money Laundering Regulations 2017 |
| E-signature for the credit agreement | Customer accepting an offer ends the demo; production needs signing |
| Lender funding webhooks | Settlement notification |
| Settlement reconciliation | Match credit agreements to actual disbursements |
| Customer comms (SMS, email) | The demo records preferences but sends nothing |
| Rate limiting on the chat route | Prevent abuse, especially for the (expensive) replay endpoint |
| Audit log redaction tooling | Subject access request handling |
| Backups and disaster recovery | Standard production hygiene |
| Logging and monitoring | OpenTelemetry, structured logs, dashboards |
| SOC 2 / ISO 27001 alignment | If the broker requires it |
What stays the same
Section titled “What stays the same”The structural architecture. The state machine, reconciliation, event protocol, audit log, replay engine. These are designed to scale to production without rewrite.
The bet is: the structural parts are correct and reusable. The hardening list is wiring and policy work, not architecture work.