Why versioned ratesheets matter
Most pricing engines mutate ratesheets in place. We don't, and the audit story is the reason. Here's what versioning buys you operationally.
Imagine a regulator asks you to reproduce a quote from 14 months ago. The loan was fine; the price was fine; the audit log says everything was fine. But the ratesheet that was active that day has been overwritten 200 times since. Without versioning, the engine cannot reconstruct the quote — it will use today's ratesheet, today's rules, today's margin layer. The number won't match.
Versioned ratesheets fix this by treating each ratesheet revision as immutable. RateStack's ratesheet-service uses three states:
- DRAFT — the ratesheet has been ingested and parsed but is not yet visible to the pricing engine. QC dashboards diff it against the currently-active version.
- ACTIVE — the current version. Every priced quote captures the ratesheet version id alongside its trace.
- SUPERSEDED — a previous version. Still queryable, still replayable, just not the current production view.
Activations are explicit. The engine never silently rolls forward; an operator approves the DRAFT, the version flips to ACTIVE, and the previous version becomes SUPERSEDED with a journal entry. This single design choice makes historical replay tractable: pass an as-of timestamp, the engine finds the ratesheet that was ACTIVE then, and reprices deterministically.
The operational payoffs
Audit reproducibility. Every quote ships with the ratesheetVersion in its trace. A regulator's reproduce-this-quote ask becomes a single API call with an as-of timestamp.
Disputes become structured. When a borrower disputes a rate-lock price, you replay against the version that was active at lock and show the trace. The conversation moves from "he-said-she-said" to a row-by-row comparison.
Rollback is trivial. Bad ratesheet went ACTIVE? Roll back to the prior version with one API call. The current ACTIVE flips back, the bad version becomes SUPERSEDED. Pricing reflects the rollback within the NATS-driven cache invalidation window — typically under a second.
Vendor renegotiation. When you renegotiate with an investor, the diff against their previous version is a structured artifact you can take to the meeting. No more "wait, when did this change?"
The cost
Storage. Every version persists indefinitely. With a typical correspondent investor pack of 30-50 investors and daily ratesheet updates, you accumulate ~12,000 ratesheet versions per year. The actual storage footprint is small — ratesheet rows compress well — but it is non-zero, and you should plan for it.
The other cost is operational discipline. Activations require a human decision; the QC dashboard exists for a reason. Auto-activation is on the roadmap behind a feature flag, but we'd gently push back if you ask for it on day one.
The wrong way
Most pricing engines we've seen treat ratesheet ingestion as a destructive write. The current ratesheet is a single MySQL row (or worse, a single Excel cell range), and ingestion overwrites it. There is no journal, no history, no replay. When something goes wrong — and at scale, something does — the team rolls back the deployment to recover. That is not the same thing as rolling back the ratesheet.
The right way
Treat the ratesheet as data with history. Version it. Hash it. Audit every transition. Build the engine to consume version-pinned reads. Let operators approve activations. Let replay use prior versions. None of this is exotic — it is the same posture you would apply to any other financial system of record.
If you remember one thing from this guide: your ratesheet is not a configuration file. It is a data object with a lifecycle.