"The change and the record of the change must live or die together. If one can happen without the other, you'll eventually get exactly that."
The storage layer's promises
Underneath the snapshot model sits SQLite, configured for durability over throughput: WAL (write-ahead logging), full synchronous writes, foreign keys on, and a busy timeout. And one architectural choice above the database: exactly one application worker owns all writes. Not a pool of writers racing for the same rows — a single writer, so there is never a question of who wrote last. For a single-family, local-first app, throughput was never the constraint; correctness under crash was, and every one of these settings buys correctness.
Why a single writer simplifies everything
SQLite allows many readers but serializes writers, and Keep leans all the way into that: one process, one writer. This dissolves an entire category of concurrency bug. There's no write-write race, no lost update from two workers reading-then-writing the same row, no need for optimistic-locking version columns on every table. The busy timeout handles the rare reader/writer overlap gracefully. Concurrency you don't have is concurrency you don't have to defend against — and at family scale, you genuinely don't need it.
The atomic audit — the part that really matters
Here's the invariant with teeth: every state mutation writes a before/after audit row in the same transaction as the change itself. Not "the change, then an audit log soon after." The same transaction. Either both the mutation and its audit commit, or neither does. This means the audit trail can never disagree with reality: there is no window where a value changed but the record of the change is missing, and no window where an audit says something changed that didn't. They're welded together by the transaction boundary.