The Flight Recorder Sealed Its Own Coffin
Yesterday I published a post about my flight recorder crashing four times on its first real flight. I thought the lessons were banked.
Today I asked my agent to confirm the recorder was integrated correctly. It came back holding two bugs I shipped — and one paradox I still can’t stop thinking about: the recorder invalidated its own tamper-seal by recording the act of sealing.
Two Bugs The Author Shipped
The agent wrote the test suite in one session: 17 tests, 41 assertions. The suite paid for itself before lunch.
| Bug | What it meant | Root cause |
|---|---|---|
verify! ignored output blobs |
Corrupted payloads passed as “verified” — the recorder’s core promise, broken | The check loop only walked :in-hash, never
:out-hash |
| Topological sort never resolved dependencies | Any dependency chain degraded to UNRESOLVABLE-DEPS |
One-liner compared dep IDs against node maps instead of node IDs |
Neither bug crashed anything. Both failed silently — which is the worst kind of bug in a tool whose entire job is producing evidence.
The Paradox: A Seal That Breaks Itself
Then I wired auto-sealing: every run gets an Ed25519 signature over its chain head when the command finishes. The first live run failed verification.
signed-head: 46e7d0024a5d15be...
log-head: 86011ead1654c429...
The seal was valid — for a log that no longer existed. The culprit
was my own success message. After signing, the code logged
run-sealed to the flight recorder. That log line
appended a node, which moved the chain head, which left
the signature pointing at a past that was no longer the present.
The recorder recorded the act of sealing — and the recording invalidated the seal.
The fix is a rule I now enforce everywhere: nothing appends after the seal. The notice goes in before the signature. The signature is the last write to the log, always.
Why The Bugs Were Legible
People assume Clojure’s magic is homoiconicity. It wasn’t the hero today.
| Mechanism | What it did |
|---|---|
| EDN logs | The evidence was a readable file — stored head vs signed head, mismatch self-evident |
| Immutable append-only design | The paradox was provable in seconds; nothing overwrote anything, so both heads existed to compare |
| Pure functions + tests | The topo-sort bug confessed on sight:
["first" "UNRESOLVABLE-DEPS"] — deterministic output, no
excavation |
Data-orientation made the bugs legible. Purity made them reproducible. Writing tests made them findable. Any language can eventually find these bugs — few architectures hand you the evidence this cleanly.
Three Rules I’m Keeping
- Verify checks every hash it names — in-blobs and out-blobs. A verifier that samples is a liar with extra steps.
- A recorder’s own annotations are mutations — the seal is the last write, and the code that guards the evidence must treat itself as a suspect.
- Tests on day one — this suite caught two shipped bugs and one design paradox in its first hour. The second hour paid for the year.
The recorder works because it cannot lie. Now it cannot even lie about telling the truth.