DECISION RECORDARCHITECTURE

An append-only log as the source of truth: the decision and what it cost

The reasoning behind an append-only log as the source of truth, including the part we expect to age badly.

FILED
READ
AUTHOR
REF

Written in the form we use internally, because the useful part of a decision record isn't the decision. It's the context that made it reasonable. In two years someone will want to reverse this, and they deserve to know what we knew at the time.

If you only store the current state, every question about how it got there is unanswerable. Keeping the sequence of things that happened and deriving state from it costs more up front and answers questions you'll otherwise pay a consultant to guess at.

WHAT AN UPDATE THROWS AWAY

An UPDATE statement destroys evidence. It replaces a fact with a different fact and keeps no record that the first one existed. In most tables that's fine. In the four or five tables that matter, it means the answer to "why does this say what it says" is permanently unavailable.

Teams work around this with audit tables bolted on beside the real ones. That's the same idea, implemented after the fact, with two problems: the audit table is written by a trigger nobody tests, and it's never quite complete enough to reconstruct anything.

DERIVED STATE IS A CACHE

The mental shift is treating the current-state table as a cache of the log rather than the truth. You can rebuild it, which means you can fix a bug in how state was computed and replay, instead of writing a migration that patches history.

That replay ability is the real return. We've fixed a two-year-old rounding bug by correcting one function and rebuilding, on a system where the alternative would have been a manual reconciliation of several thousand records.

We modelled it at roughly 6,000 open cases and the difference only showed up in the tail. At median load you couldn't tell them apart. At the ninety-ninth percentile, one of them stopped being able to explain itself.

THE COSTS, HONESTLY

It's more machinery. Two representations to keep in step, a replay path that needs testing, and a schema for events that's genuinely hard to change once they're in production, because the whole point is that you can't rewrite them.

So it's not a default. It earns its place where the history is itself valuable: money, regulated decisions, anything with a dispute process. Apply it to the whole system and you'll spend your budget on ceremony.

The deciding factor was regulatory, not technical. Caseworkers have to be able to reconstruct why a given case record was handled the way it was, months later, in front of someone unfriendly. That killed two of the three options on the spot.

WHERE IT GOES WRONG

  • An audit table written by an untested trigger, which turns out to be missing exactly the field you need.
  • Events with the schema of the current UI, so the log is unreadable after the next redesign.
  • A replay path that was never run in anger and doesn't work when it's finally needed.
  • Event sourcing applied to all forty tables, so every trivial change is a two-day exercise.

Keep the sequence for the records people argue about. Derive the rest.

CONSEQUENCES WE ACCEPTED

We took a slower first two months in exchange for a system you can still reason about in year three. On an eighteen-month horizon we'd have chosen differently, and we said so at the time.

RELATED
SAME GROUND, DIFFERENT ANGLE
ALL TRANSMISSIONS