The lifecycle
Every tool write moves through four ordered stages. The write-ahead log is our durable state; the adapter models the external world.
- Persist. The intended write is written to the write-ahead log with a canonical fingerprint before the caller is acknowledged. If the process dies here, the intent survives.
- Apply. The adapter performs the external effect exactly once, keyed by the fingerprint so a replay cannot double-apply.
- Commit. The outcome is recorded against the log entry, which closes the write.
- Reconcile. On restart, any write that persisted but did not commit is recovered. Before retrying, the engine probes the adapter to discover whether the effect already happened, so it never blindly re-applies.
Three distinct verdicts
Reconciliation does not pretend to certainty it does not have. A recovered write resolves to one of three verdicts, and the CLI exit code matches:
pass(exit0) — the effect is confirmed applied exactly once.fail(exit2) — the effect is confirmed not applied, or a tamper/integrity check failed.unknown(exit3) — the adapter cannot be probed, so the effect is genuinely uncertain. This is reported honestly rather than guessed.
At-most-once and tamper-evidence
Because the fingerprint is written before the effect and checked before any retry,
a write is applied at most once across crashes. The persisted receipt store is
tamper-evident: each record must still hash to its recorded fingerprint, and the
whole store can be re-attested offline from the log alone — no
adapter, no re-apply — with node src/cli.mjs verify.
Why “crash” is deterministic
The crash is a deterministic injected signal (afterPersist /
afterApply), not a real fault. That makes the crash/restart acceptance
gate reproducible in CI with no real datastore and no real fault injection, and it
is proven against more than one adapter so the contract generalizes.