Software for compliance-heavy industries / Building it right
Building an audit-ready codebase
Audit readiness is not a phase before an examination. It is a set of properties a codebase either has or lacks, most of which are cheap at the start and expensive later. This page covers the four that matter and the order to build them in.
Published August 22, 2026. Editorial.
Key takeaways
- Route access to regulated data through a single access point, because controls that depend on developers remembering will fail over time.
- Capture decisions as immutable records at the time they are made, since the data you did not capture cannot be recovered.
- Trace every compliance check to the provision it satisfies, so coverage is visible rather than inferred.
- Retain results and artefacts for the life of the obligation, not for the default retention of your CI system.
A system that is easy to audit and a system that is hard to audit rarely differ in how carefully they were built. They differ in four structural decisions, usually made early, usually without anybody framing them as compliance decisions at all.
One: route regulated data through a single access point
The single most useful decision is that reading or writing regulated data cannot happen without passing through code you control.
Every audit implementation that relies on developers adding a log call in the right place fails over time. Not through carelessness, but because there will be several hundred new code paths over three years and some of them will miss it. The version that keeps working is one where the mechanism that reaches the data is the mechanism that produces the trail, enforces the permission, and applies the retention rule.
Concretely: a data access layer that owns regulated entities, through which all reads and writes go. Direct database access from arbitrary application code is prevented rather than discouraged, ideally at the credential level so it is enforced rather than agreed. New code paths inherit the controls automatically, because they cannot reach the data any other way.
This is straightforward at the start and expensive once four hundred call sites exist, which is why it belongs first. If you are retrofitting, the intermediate step is to enumerate the paths that bypass the layer and shrink the list deliberately rather than trying to fix everything at once.
A concrete version of the failure this prevents: a support tool is added to help agents look up an account faster, and the fastest way to build it is a direct read against the production database, because going through the access layer would have meant writing an adapter first. That query works, ships, and is never revisited. Two years later a data-access audit walks every code path that touches a customer record and finds this one produces no log entry, because it never passed through the layer that writes the trail. Nobody added it maliciously and nobody skipped a review. The tool was built by someone who needed to solve a problem quickly, and the shortcut was invisible because it worked exactly as intended from the support agent's point of view. That is what "fails over time" means in practice: not a single bad decision, but an accumulation of reasonable ones made without the constraint in place.
Two: capture decisions as immutable records
Anything the system decides that affects a person, and anything it does to regulated data, should produce a record written at the time, containing enough to reconstruct what happened.
The critical property is that this cannot be added retrospectively. Analysis can be added later. Interfaces can be built later. The data itself, if you did not capture it, is simply gone, and no amount of engineering effort recovers it. That asymmetry is why decision capture deserves to be an early decision rather than a later one.
What to capture depends on sector, and the pattern is consistent: the inputs as values rather than as references to records that will change, the version of whatever logic produced the outcome, the outcome, the actor, an unambiguous timestamp, and the interface used. For model-driven decisions add the factor attribution computed at decision time, for the reasons on explainability and adverse action.
Make the store append-only, and make it unwritable by the application's ordinary credentials. A record the audited party can modify is not evidence.
The edge case worth planning for is the correction. Decisions get made on wrong data, logic has bugs, and a genuinely incorrect record will need to be fixed at some point. The instinct is to update the row, which destroys the immutability that made the record trustworthy in the first place. The pattern that preserves both properties is to never edit a record, and instead write a new one that supersedes it, with an explicit reference back to the record it corrects and a stated reason. The history then shows both what was originally decided and what correction was made and why, which is a stronger position in an examination than a record that looks like it was always right, because a record that has clearly never needed correcting over years of operation invites the question of whether corrections were happening outside the system that was supposed to capture them.
Three: trace checks to provisions
Each compliance check carries a reference to the specific provision it exists to satisfy, placed next to the assertion in the code.
The payoff is that coverage becomes visible. Put the obligation list beside the suite and the obligations with nothing attached stand out, which is the question everybody wants answered and almost nobody can answer. Without the trace you are inferring intent from test names, and nobody does that thoroughly enough to trust.
The second payoff is maintenance. When a provision is amended, the affected checks can be found with one search rather than a long search through a suite nobody has read in a year.
The third, and the one that matters during an examination, is that it lets you give someone a clear list. "Here is the provision, here is the assertion that enforces it, here is every commit it ran against" is a fundamentally different conversation from walking someone through an architecture diagram.
Four: retain long enough
Three things need retention aligned to the obligation rather than to a tool's default: the decision records, the check results, and the artefacts the records reference.
The check results are the ones teams lose. A CI system commonly discards build records after a few months, and that setting is one line of configuration nobody has a reason to look at until an examiner asks about a release from eighteen months ago. The evidential value of a check suite is in its history, as covered on using evals as compliance evidence, and a short retention window throws away exactly the part that cannot be reconstructed.
The artefacts are the ones teams do not think of. A decision record naming model version 47 is incomplete if model version 47 was deleted, because you can no longer say what it would have done. Retaining old artefacts feels like clutter right up to the moment it is the only thing that would have helped.
This is also where the sector guides on this site diverge in specifics while agreeing on the structure. A financial firm's recordkeeping obligation names a defined multi-year retention period for the record itself. A healthcare obligation may separate the retention of the record from the retention of the audit trail describing who accessed it, with different periods attached to each. Reading the obligation carefully enough to notice that it names two different things with two different clocks, rather than assuming one retention setting covers both, is exactly the kind of gap the specification exercise on compliance is a specification problem is built to catch.
What this costs
Being honest about it: the single access point costs design time early and saves substantially later. Decision capture costs storage and some computation on every decision, and the computation is the part people object to, particularly factor attribution. Tracing costs almost nothing. Retention costs storage.
The largest cost is the single access point if you are retrofitting, and it is the one worth paying, because every other control depends on there being a place to put it.
The thing to weigh it against is not zero. It is the cost of the alternative, which is an examination or an incident where the answer to a direct question is that the system cannot tell you. Our piece on the real cost of shipping unverified code covers the general form of that trade-off.
Where to start on an existing system
You will not retrofit all four at once, and trying produces a project that stops. The order that works:
Start with decision capture, because it is the one where every day of delay permanently loses data. Even a partial implementation covering the highest-consequence decisions starts accumulating a record immediately.
Then trace whatever checks you already have, which is cheap and immediately shows you how much of your obligation list is unenforced.
Then fix retention, which is usually a configuration change and occasionally a storage cost conversation.
Then work on the single access point incrementally, starting by enumerating the bypass paths rather than by building the layer, because knowing how many there are determines whether this is a quarter of work or a year.
If you want help sequencing that for an existing system, get in touch.
Best for
- New builds in regulated sectors, where all four properties are cheap to establish
- Existing systems facing an examination, certification, or diligence process
Avoid if
- The obligations have not been scoped, since what to capture and check depends entirely on that
Check before you decide
- Ask whether application code can reach regulated data without passing through the data layer
- Ask whether a decision from a year ago can be explained from records rather than reconstructed
- Check whether compliance checks name the provisions they satisfy
- Check the retention on CI build records and on model or logic artefacts, not just on the database
Common questions
What makes a codebase easy to audit?
Four structural properties: a single access point through which all access to regulated data passes, decisions captured as immutable records at the time they are made, compliance checks traced to the provisions they satisfy, and retention aligned to the obligation rather than to tool defaults. They rarely reflect how carefully a system was built, and mostly reflect early decisions nobody framed as compliance decisions.
Why is a single data access point so important?
Because any control depending on developers remembering to add a call will fail over time across several hundred new code paths over a few years. When the mechanism that reaches the data is the mechanism that writes the trail, enforces permissions, and applies retention, new code paths inherit the controls because they cannot reach the data any other way.
Why can't decision capture be added later?
Because analysis and interfaces can be built later but the underlying data cannot be recovered if it was never captured. That asymmetry is why decision capture is the first thing to fix on an existing system: every day of delay permanently loses records that no engineering effort will bring back.
What do teams most often lose to retention defaults?
CI build records, which commonly expire after a few months and take the run history of the check suite with them. That history is the part of the evidence that cannot be reconstructed, and the setting is one line of configuration nobody looks at until an examiner asks about a release from eighteen months ago.
In what order should an existing system be improved?
Decision capture first because delay permanently loses data, then tracing existing checks because it is cheap and immediately reveals how much of the obligation list is unenforced, then retention which is usually configuration, then the single access point incrementally, starting by enumerating bypass paths to find out whether it is a quarter of work or a year.
Is a single data access point the same thing as a permissions system?
No. A permissions system decides who is allowed to read or write a record. A single access point is the architectural decision that every read or write must physically pass through code you control, so the trail, the permission check, and the retention rule are applied by the same mechanism rather than by separate systems that can stop agreeing with each other over time.
How much more does it cost to add a single access point after the fact?
Substantially more, because retrofitting means enumerating every existing call site that bypasses it, which can number in the hundreds on a system several years old, versus establishing it before those call sites exist. The single access point is cheapest at the start of a build and is the single most expensive of the four properties to add later.
What is the risk of building an audit-ready codebase without independent verification?
The four structural properties, a single access point, decision capture, traced checks, and retention, describe where evidence is produced, not whether the checks enforcing them are trustworthy. A check written by the same process that wrote the code it verifies can pass while proving nothing, so audit readiness still depends on verification coming from a separate context using criteria written from the obligation first.
Related reading
Adding eval tests was the best decision we made
We generate every line of code we release. The single change that made that safe was writing the check before the code, and it turned out to change how we review, how we spec, and how fast we can work.
The real cost of shipping unverified code
The cost of unverified code does not arrive as a bug report. It arrives as a codebase nobody will touch, a review queue that never empties, and a team that has stopped trusting its own pipeline.
What technical debt really is, and when to pay it back
Technical debt is not messy code. It is a deliberate choice to give up some quality for speed. Here is how to tell smart debt from reckless debt, and when to pay each one back.