Korvun v0.14.1 — Beta: the consolidation an external review earned
A patch release with a story worth telling: an independent external
review of v0.14.0 surfaced three reproducible defects and one
over-broad promise — findings our own unit tests never saw, because
they attacked the system from OUTSIDE its components: another process,
another feature, another open path. Every fix below was born from a
test that first reproduced the reviewer's exact scenario in red. The
lesson is now project law (the cross-check law): absolute claims
require adversarial tests from outside, behavior pins cover the whole
door, and cross-scenarios are first-class citizens of every stage spec.
The read-only door (critical)
The operator's consults — receipt verify, ledger check, intent list/show — opened the store through the same door as the server:
migrations, crash recovery and the retention prune ran on EVERY open.
Two real consequences: a verify running beside a live server could
close the server's in-flight actions as crash-recovered behind its
back, and a consult could silently migrate a profile's schema. Now
every consult goes through a true read-only door: no migration, no
recovery, no prune, no file creation — and the connection itself
refuses every write at the SQLite level. A schema older or newer than
the binary's is refused by name, never migrated. The reviewer's exact
scenario is a permanent regression test at both the store and CLI
levels, plus a real-concurrency cross-check: consults hammering a
store while the server records and closes actions in parallel.
Honest custody under retention
Receipts are the evidence that SURVIVES the retention prune — that is
their design. Yet verifying a receipt whose action row had been
legitimately pruned failed with custody_mismatch: a lie. The
verifier cannot distinguish prune from deletion, which is exactly why
the digest-sealed receipt, not the live row, carries the custody. An
absent row now degrades to the named, non-fatal note
action_row_absent; ledger check counts and names pruned rows out
loud; and a PRESENT row that contradicts its receipt keeps failing as
loudly as ever.
The coverage gate stops swallowing failures
make cover piped its test run through a pattern match that lost the
real exit code and ended in a fallback that reported success no matter
what. Reproduced with a planted failing test (exit 0, happy
percentage), then fixed: real exit codes, no fallback to hide behind,
and a committed demonstrator script that re-proves the guarantee on
demand.
The scoped promise
v0.14.0 said "an unreceipted effect cannot exist" — broader than the
design guarantees. The honest scope, now stated in the README,
SECURITY.md and a dated erratum on the v0.14.0 notes: atomicity holds
INSIDE the store (an attempt that cannot be recorded fails closed
before any effect; outcome and receipt are one transaction), while an
EXTERNAL effect that completed just before a failed terminal close is
a real, documented window until stage 6's reconciliation ships. That
failure also stopped being a warning: it now logs at ERROR with rule
record_failed — a loss of evidence for a real-world effect is
something an operator investigates, not scrolls past.
Compatibility
No schema changes, no config changes, no API changes. The same
binary, the same files — with a consult path that can finally be
trusted next to a running server, and documentation that promises
exactly what the code delivers.