Skip to main content
All posts
December 6, 2024Β·2 min readΒ·Diogo Hudson

The append-only ledger: the best boring idea in inventory

A log of every movement, never modified. Unsexy, unflashy, and the single biggest structural win a distribution system can have.

The append-only ledger: the best boring idea in inventory

Every inventory system worth trusting shares one property: its source of truth is a log of what happened, not a running total. Banks do it. Accounting software does it. Most ERPs do it β€” and then hide it behind an interface that pretends otherwise.

The running-total model is seductive because it's fast. A single row per product, a single integer to update, a single read to answer 'how many do we have?' It's also fragile, because that single integer is a derived value with no provenance. When it's wrong β€” and it will eventually be wrong β€” there's no way to know why, when, or by whom. You can only fix it by overwriting it, which destroys the evidence of the error.

Why append-only If every change writes a row and no row is ever edited, the audit trail is trivial. Questions like 'what was stock of SKU AB-001 on March 14th?' are a single SQL sum. Questions like 'who moved this?' are a simple join.

The operational difference is real. In a mutable system, answering a historical question requires trust β€” trust that nobody edited the history, trust that the backup is accurate, trust that the person who made the change remembered to log it somewhere else. In an append-only system, answering a historical question requires a query. The answer is deterministic, reproducible, and admissible in a dispute with a customer or auditor.

No delete button Our StockMovement admin explicitly disables add/change/delete permissions β€” even for superusers. This <a href='/security' class='text-[var(--color-accent)] underline'>security measure</a> means corrections go through a dedicated POST /api/stock/{id}/adjust/ endpoint that writes an ADJUSTMENT row with a required note. The ledger stays honest.

This is the discipline that makes append-only work. If there's a back door β€” a database admin who can UPDATE stock_movement SET quantity = 5 WHERE id = 1234 β€” then the ledger is just a log with an asterisk. By disabling all mutating operations at the Django admin level and providing a single, auditable correction path, we guarantee that every number in the ledger has a paper trail. Even the corrections have a paper trail β€” the adjustment note is required, timestamped, and attributed to the admin who made it.

Cache, not truth StockItem.on_hand and StockItem.reserved are a cache, updated in the same transaction as the ledger write. If they ever drift, the ledger rebuilds them.

The cache exists for performance β€” you don't want to SUM six years of stock movements every time you render the inventory page. But the cache is disposable by design. A management command can zero every StockItem and rebuild it from the ledger in a single pass. This design pattern β€” cache as optimization, ledger as truth β€” is borrowed from <a href='/docs/product/finance' class='text-[var(--color-accent)] underline'>accounting systems</a> and applied to inventory. It's not novel, but it's correct, and correctness in inventory is worth more than novelty.

How Quotery's platform is put together.

All posts
Short pieces on quoting, inventory, AI, and how small distributors ship a lot of stuff without the fuss.