Research / Effects, recovery and replay

P16 · empirical · draft

Testing Recovery Claims: Exhaustive Fault Sweeps and Inverse Round-Trips Across Runtimes

partial empirical Effects, recovery and replay

State of the work. Fault sweep, cross-tier sweep and round-trip tests are shipped with tests; no corpus-wide results, mutation analysis or baseline comparison yet.

This is a draft, not peer reviewed. Repository references link to revl at commit 45bec431f.

Planned to be combined with P17 (Seeded Deterministic Interleaving Tests for Lifecycle Operations, With Auto-Mocks Derived From Declarations) into a single paper.

Pitch

A language that promises every effect has an inverse can test that promise instead of trusting it: inject one failure at every step of every component on several runtimes, and round-trip each declared undo against a state fingerprint, with the report stating its blind spots on every run.

Abstract

Languages and frameworks that let authors declare an undo for each effect usually stop at checking that an undo exists. We describe two generated tests built into the revl toolchain that check that the undo works. The fault sweep derives, from the compiler's IR, one injected failure per top-level step of every component and asserts that the activation lands failed, leaves no residue, runs its inverses newest-first, and leaves sibling components unaffected; an unreachable step is listed rather than skipped, so an empty check cannot read as clean. The cross-tier sweep reruns the same faults on each mapped runtime whose toolchain is present (executing tests exist for py, go, java and rust only) and requires residue-freedom, agreement across tiers, and that exactly the owed compensations ran; a tier that cannot run is a loud skip. The verified-effect round trip snapshots an observable-state fingerprint, runs the effect and its inverse over 16 randomized configurations, and compares; a value-aware extension applies a logged inverse twice to catch delta-shaped inverses falsely declared idempotent. Each round-trip report names what it cannot see (aliased references, external emissions, clock and random values), and each sweep lists the steps it could not reach. The first fault test written against an async component body exposed a defect in a Cordis runtime (the fiber stayed ACTIVE after a failure that had correctly run its inverses), reproduced with a hand-built IR that is emitted and plugged directly, so it still goes through revl's emitter. The upstream fix is cited by the docs, not checked here. We report the design, the guarantees each assertion supports, and the failures it found. We do not yet report a bug-finding comparison against baselines or a mutation analysis.

Problem and claim

Static guarantees such as "every effect has an inverse" (revl's G4) say the inverse is present and well-shaped, not that it is correct. docs/verified-effect.md:13-30 states the concession directly: the runtime will not catch an undo that is wrong, partial or a no-op. Hand-written scenario scripts, one per backend, prove the property only for components their authors wrote (docs/fault-tests.md:15-20).

The claim: for a fixed failure model (a failure striking at a step boundary of a top-level activation body) the property "no mid-life failure point leaves residue, on any runtime that is present, and the runtimes agree" is checkable exhaustively and mechanically from the IR, and an inverse's correctness can be tested by a generated round trip whose report is explicit about its blind spots. The claim is scoped: the sweep is sequential and the round trip is in-process only (docs/fault-tests.md:405-414; docs/verified-effect.md:163-180).

The honesty discipline is part of the contribution: skips are loud, unreachable steps are counted, emissions are reported as "note: irreversible" and never as reverted, and a passing round trip says "not a proof".

Contributions

  1. revl test --sweep: one injected failure per top-level IR step, same assertion set as hand-written fault tests, an exhaustive verdict over top-level steps, unreachable steps reported (docs/fault-tests.md:325-415; src/revl/fault.py:660 sweep_units, :802 run_sweep).
  2. The injection placement argument: inject after step N completes, not before it, because the earlier placement made fail at step 1 vacuous (a false green) (docs/fault-tests.md:53-96).
  3. The cross-tier sweep: same fault set on py, ts, rust, java, wasm, go via an authenticated residue proof, plus a compensation-owed-versus-ran check; agreement is a portability criterion and a skip is never a pass (docs/fault-tests.md:416-550; src/revl/fault.py:913,1363).
  4. Verified-effect round trip: snapshot, effect, inverse, fingerprint over randomized rounds, reported as a test not a proof, with blind spots named in every report (docs/verified-effect.md; src/revl/fault.py:1586,1900,2208).
  5. Value-aware double-undo round for idempotency claims, with the value-blind fold kept as a negative control (src/revl/fault.py:1858; tests/test_idempotent_inverse_309.py:144-180).
  6. A finding: a Cordis (py) runtime left the fiber ACTIVE when an async body failed, found by the first fault test over an await component (docs/fault-tests.md:297-322).

Evidence on main

Tree: revl main at commit 45bec431f.

claimpath:line or teststatusnote
Fault test syntax and assertion setdocs/fault-tests.md:1-165; tests/test_fault_tests.py (58 tests)shippedpy tier only; other tiers refuse the construct (docs/fault-tests.md:240-274)
Sweep over every top-level step, exhaustive verdictsrc/revl/fault.py:660,802; docs/fault-tests.md:325-366shipped
Unreachable steps listed, counteddocs/fault-tests.md:379-395shippednested-in-if steps arise only from hand-written or imported IR (doc)
Provider dependents held out of the bring-updocs/fault-tests.md:367-378; tests/test_fault_sweep_cross_tier.py:90shipped
Emissions reported as irreversible notesdocs/fault-tests.md:167-198shippednot individually traced into tests
Cross-tier sweep, agreement, loud skipsrc/revl/fault.py:1363-1500; tests/test_fault_sweep_cross_tier.py (31 tests)shipped
Cross-tier executed on py, go, java, rusttests/test_fault_sweep_cross_tier.py:370,444,471,484,499,514 (def lines)shipped, toolchain-gatedeach corpus here is one composition (_two_phase()), 4 fault points (test lines 441-456)
ts and wasm in the sweepsrc/revl/fault.py:913,945 (runner mapping)designed/partialNo executed test for ts or wasm; the docs sample output lists wasm as skipped
Docs sample shows rust and java skippeddocs/fault-tests.md:495-505stalethe tests above show rust and java executing
Authenticated residue proof (runner token)docs/fault-tests.md:449-460shippedissue 1621
Compensations owed versus randocs/fault-tests.md:462-485shippedservice-call and host-builtin compensations "not observed"
Verified-effect round trip, 16 roundssrc/revl/fault.py:1586 (_ROUNDTRIP_ROUNDS = 16), :1900, :2208, :2276; tests/test_verified_effect.py (23 tests)shippedpy tier only (src/revl/test.py note "py reference tier only")
Partial inverse leaving a key is caughttests/test_verified_effect.py:278shippedthe static check and the fault sweep both miss it (test comment)
No-op inverse now refused at compile timetests/test_verified_effect.py:262shippedissue 1859 moved this case from runtime to the checker
Honesty bound printedtests/test_verified_effect.py:291shipped
Double-undo value-aware round, negative controlsrc/revl/fault.py:1858; tests/test_idempotent_inverse_309.py:144-180shipped
Shape-proven tierdocs/crash-recovery.md:303-316removednever built; parse error now
Cordis-py async gap found and fixeddocs/fault-tests.md:297-322; tests/test_fault_tests.py:692 test_an_await_body_lands_failed_like_a_sync_bodyshipped (fixed)upstream commit named in the doc; the upstream repository was not checked
Fault units are py-only in the default revl testsrc/revl/test.py:106-114,164,286shipped

Numbers that exist

Paper outline

  1. Introduction: from "an inverse exists" to "the inverse works".
  2. Background: G4, A8 (mid-body failure reverts and contains), R4 (no residue).
  3. Fault tests and the injection placement lesson (the vacuous fail at step 1).
  4. The sweep and its refusal to read nothing as clean.
  5. The cross-tier sweep: authenticated proof, compensations owed versus ran, agreement, loud skips.
  6. The round trip: fingerprint from the trace ledger, rounds, blind spots; value-aware double-undo.
  7. Findings: the async fiber state defect, the false green in the first injection placement, the no-op inverse moved to compile time.
  8. Evaluation: corpus results, mutation analysis, baseline (not yet run).
  9. Limits and related work.

Threats to validity and limits

Fact-check

Claims in this draft were checked against revl main at commit 45bec431f: 40 checked, 9 corrected.

Cite this draft

@misc{revlp16,
  author       = {Thomas Moussajee},
  title        = {Testing Recovery Claims: Exhaustive Fault Sweeps and Inverse Round-Trips Across Runtimes},
  year         = {2026},
  howpublished = {\url{https://inso1337.github.io/revl/research/P16.html}},
  note         = {Research draft, not peer reviewed. Written against revl commit 45bec431f. Licensed CC BY 4.0.}
}

Text licensed CC BY 4.0, author Thomas Moussajee.

← All research drafts