Aditya Gaur
Work

BFSI prototype

The prototype that became the spec

A policy-management prototype for banks where every trust-critical decision was moved out of the model and into architecture, which is why there's no confidence score in the codebase.

Role
Solo — product, prototype, PRD
Timeframe
2026 — June to July
Status
prototype
Stack
Vanilla JS·Zero dependencies·6 LLM providers·Static hosting

Same question, different person, different answer

A staff member asks the assistant for the personal-loan policy and gets it. They ask what a colleague earns and get not permitted. An admin asks the same two questions and gets two answers. That's the entire product in one interaction, and it's the moat to validate against the horizontal players, because a general-purpose company assistant that can't do this can't be sold to a bank.

The product is a policy-management platform for banks; the prototype's job was to prove the permission-faithful version of it was worth building. It had one month, one constraint that shaped everything (it had to run, live, at a URL a prospect could click), and a decision budget spent almost entirely on a single question: what is the model allowed to be wrong about?

8,067
lines, zero dependencies, no build step
15
views registered on a three-line module system
5
personas, each seeing a different product
11
models across 6 providers, none preselected

No framework, and no framework rationale either

There is no comment in the codebase explaining why it's vanilla JS, and I'm not going to invent one retroactively. The argument is in the structure. The module system is three lines: a views object and a register function. Every view is a pure function from state to an HTML string. There is no package manifest, no lockfile, no build; deployment is pointing a static host at the folder.

What that bought was the thing the month actually needed: the PRD links to the prototype as its own spec. Open this to see the target UI is a sentence a document can only say when the prototype deploys anywhere instantly and breaks for no environmental reason. The roadmap treated "prototype link frozen" as a hard milestone, which inverts the usual order: the prototype came first, and the document was written to describe it.

In place of a test framework there's a 258-line headless harness that stubs a minimal DOM, loads every view, and renders each one for every persona — the cross-product that matters for a product whose thesis is "the answer changes by persona."

Where the model is allowed to be wrong

The prototype connects to eleven models across six providers, bring-your-own-key, none preselected. And nowhere in the codebase is there a confidence score, an output validator, or a retry-on-malformed. That absence is the design.

The model can't leak what it never received

Permission filtering happens before the prompt, not after the answer. The context builder assembles only the policies the signed-in persona may see, then tells the model how many others exist and that they are out of scope. A hallucinating model still cannot reveal an out-of-scope policy, because that text was never in its window.

  • The prompt's one rule — answer only from the context; if something is absent, say the user is not permitted to see it — never guess
  • The product claim — log in as staff and the model literally can't answer about a policy they're not on

Figure 1. Three hard walls, one soft surface. The gate is architectural, not statistical.

There's also a third state between demo and live that turned out to be a sales feature: picking a model persists and shows in the header without a key. A prospect can see their preferred provider in the chrome during a demo; adding a key upgrades the same choice to live answers. Separating chosen from keyed cost one boolean and removed a whole category of demo awkwardness.

Citations correct by construction

The prototype's answers cite pages, clause 3.2, page 2, with a chip that opens the document at that page, clause highlighted. The honest engineering behind that is an inversion worth stating plainly: there are no real PDFs in the prototype. Getting from a real document to a reliable page number needs a parsing pipeline the month didn't have. So the prototype goes the other way: it synthesizes a paginated document from the policy's own structured data, which means every page number is correct by construction, because the pagination and the citation come from the same source.

The header comment in that file says exactly what it is (no real PDFs in the prototype; we synthesize a paginated document from a policy's own content), and that sentence is why the trick is honest. Real ingestion is scoped for the build phase. The prototype's job was to prove the citation experience, and it proved it without pretending to a parsing capability that didn't exist.

The simulator: evidence you attach to a change request

The most sellable idea in the prototype is small: before a bank changes a credit-policy threshold, show them who flips. The simulator re-runs a policy's eligibility rules over a fixed 220-applicant test cohort with ground-truth default labels, under both the live and the proposed parameters, and reports the delta on the book.

Approval rate
16.8% → 16.8%flat
Projected NPA
10.8% → 10.8%flat
Reclassified
00 lose approval · 0 gain it

Approved, by CIBIL band

600–6790
680–7193
720–75911
760–86023

No one flips at these settings, so the prototype shows no Propose button — a change request with no impact attached is noise in an approver's queue.

Figure 2. The personal-loan policy's real baseline thresholds. Tighten the CIBIL floor and watch approval fall, projected NPA fall, and the flip count climb.

Same eligibility engine and metrics as the prototype's sim.js. The 220-applicant cohort is regenerated synthetically with the same distribution shape and deterministic labels — none of the original rows. Projections model the test cohort, not a live book.

The design detail I'd defend hardest is the propose button's existence condition: it only renders when the change actually reclassifies someone. A rule tweak that moves nobody produces no change request, because a request with no impact attached is noise in an approver's queue. When it does render, the simulated deltas get written into the request's rationale automatically — the simulation is the evidence, filed where the approval will happen.

Scope was cut to fit the month, on purpose: five shared knobs across the four credit policies, not fully custom per-policy variables. The other five policies get an explainer instead of a broken form. The roadmap's phrase for this was build it cleanly, separate from the half-baked old test bed, a small sentence that is most of what I believe about prototypes.

What the prototype changed, and what this page can't show

Three reversals are traceable from document to code, which is the prototype-as-spec workflow paying for itself. A two-edition packaging model was defined, then retired to a single version with connectors parked behind no-op stubs. A standalone people directory was built, then explicitly disowned in the docs and merged into an access-centric view. And compensation answers — the flagship demo of permission-faithful retrieval — were removed entirely, one line: visible to no one. The docs still use salary as the example; the code overruled them. When a document and a running program disagree, the program is the one telling the truth.

One gap runs the other way, and it's the sharpest lesson here. The audit log was a phase-one, top-priority feature (audit is also the compliance sell) and it was never built in the app. Then a later standalone demo made an append-only audit ledger its most prominent surface. The demo prototyped the missing feature the product never got, which says something uncomfortable about demos: they get built for what the audience in the room needs to see, not for what the roadmap says comes first.

And what this page can't show: the prototype itself. Its seed data is not synthetic — it carries a real org chart, real email addresses, and internal material through to the deployment docs. Embedding it would publish all of that. The simulator above is the porting compromise: the same engine, a regenerated cohort, and a page that states the substitution instead of hiding it.

What I'd do differently

Seed data deserves the same discipline as code. Using the real directory made every demo instantly legible internally, and it welded the prototype shut for any external use — this page included. A generator producing a fictional org with stable ids would have cost an afternoon and made the artifact publishable forever.

The abstain path deserved a gate, not a prompt. The deny behaviour (say the user is not permitted, never guess) rides on a prompt instruction. The context filtering makes real leaks structurally impossible, but the polite refusal itself is unverified model behaviour. One post-check, that an answer must be a refusal whenever the context said "none in scope", would have converted the last soft wall into a hard one.

Build the audit log when the roadmap says to. It was ranked correctly, deferred anyway, and then needed urgently by the demo that came later. Features that exist to create trust are the worst possible candidates for deferral, because by the time you need them the absence is the story.