Research / Formal and PL core

P01 · full · draft

Typed Revertible Effects: Inverse-or-Emit as a Checked Guarantee

shipped evidence full Formal and PL core

State of the work. The checker, the rejection corpus and the guarantee table are on main with tests; the Lean models cover the rule lattice but not the Python checker, and no evaluation on third-party code exists yet.

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

Planned to be combined with P23 (From Discipline to Type System: What revl Adds to the Cordis Plugin Framework) into a single paper.

Pitch

A compiler can make "every effect is undoable or visibly irreversible" a checked property by classifying effects as acquire, pure, witnessed or emission, and can then derive component lifecycle and LIFO undo from the dependency graph so the author never writes teardown code.

Abstract

Systems that load and unload components at run time depend on undo code that is written once, exercised rarely and wrong quietly. Resource-safety disciplines such as RAII, bracket and defer help, but nothing forces a mutation to name its inverse, and nothing separates an effect that can be undone from one that crossed the system boundary. We present revl, a language where reversibility is part of the type structure. Every effect is classified as acquire, pure, witnessed or emission. The checker's central rule (G4): a mutation either carries a well-shaped inverse or is an explicit, approval-gated emission. The rule is refined by capability scopes, so a service declaration emission[db] bounds where any provider may cross, and the bound is computed as a least fixed point over the call graph. Because code outside effect forms is pure, the teardown accumulator holds every mutation at the head granularity the proof ledger certifies, and lifecycle (activation, deactivation, LIFO undo) is derived from the dependency DAG with no author-written lifecycle code. Typed holes let a draft type-check around missing parts while noting them as unmet obligations that admission and every backend refuse. We describe the rule set, the decode table that separates guarantees inherited from the Cordis paradigm, revl-original ones and ordinary type theory, and the implementation and test corpus (186 rejection reproducer files, 47 of them with g4 in the name). We state what is not claimed: inverse semantics are checked for presence and shape, not correctness, and the foreign-code boundary is trusted but enumerable. Lean models of the rules and a differential oracle against the shipped checker are reported in a companion paper.

Problem and claim

Cordis (the paradigm revl descends from) has revertible effects as a discipline: its theorems rest on hypotheses a library can only ask programmers to respect (README.md:36-41). In practice that means undo logic is a write-only code path, and the distinction between "reversible" and "crossed the boundary" lives in programmer memory. Existing resource-safety mechanisms (RAII, bracket, defer, saga libraries) pair an acquisition with a release at best by convention; none is a type-level obligation with a checked escape hatch.

The claim is narrow and checkable: in revl, (1) every mutation form is classified, (2) a mutation without a declared inverse is refused unless it is marked as an emission, (3) the set of host boundaries a provider may cross is bounded by its service declaration and that bound is checked transitively, and (4) lifecycle and undo order are derived, not written. The paper does not claim that an undo expression truly reverts its effect; the project docs say this is undecidable in general and opt-in (verified) (docs/vision.md:95-99).

The positioning sentence the project itself uses is "Cordis has revertible effects as a discipline; revl makes them a type system", and the same page adds "Everything beyond that is roadmap, not claim" (docs/vision.md:113-114). The paper adopts that restraint. The RAII-to-Rust analogy is rhetorical framing for the introduction, not a result.

Contributions

  1. An effect classification (acquire, pure, witnessed, emission) with rule G4, inverse-or-emit, and its capability-scoped refinement: a provider's reachable capability set must be a subset of the scope in its service declaration (docs/capabilities.md:126-170; src/revl/lower.py:15055-15083).
  2. A transitive analysis for that rule: a least fixed point over the call graph, lifted from a boolean to a set of capabilities, with recursion terminating (src/revl/emission_analysis.py:157-200).
  3. Implicit lifecycle: activation and deactivation derived from the dependency DAG, with G2 (one provider per key, per realm) and G3 (acyclic) checked at link time (DESIGN.md:154-160, DESIGN.md:223-235).
  4. Checked undo: because code outside effect forms is pure (G6), the accumulator is complete at head granularity; host acquisitions must undo as their release on the bound handle, and no_residue fails when a bracket's undo did not reverse its write (README.md:127-135; docs/rejections.md:173-232; CHANGELOG.md:472-477).
  5. A decode table that assigns refusal codes an enforcement point and, where sourced, an origin (Cordis-inherited, revl-original, standard type theory) (docs/guarantees.md).
  6. Typed holes as a first-class unmet obligation: checked around, noted, refused at admission and by every backend (docs/holes.md; src/revl/holes.py:72-93; tests/test_holes.py:261-327). Sound typing with Opt[T] absence and a documented gradual frontier is a supporting piece (docs/roadmap-archive/02-done-dependency-order-as-built.md:29-31).

Evidence on main

ClaimWhereStatusNote
Core move: every mutation beside its inverse, or an emit marker at the call siteREADME.md:30-34; docs/rejections.md:173-190 (g4_missing_undo)shippedRejection reproducer examples/rejections/g4_missing_undo.rvl; message asserted at tests/test_frontend.py:239
Four effect classes enumerate the boundary (G8)docs/design/456-ambient-and-prog-export.md:223 (the four-class row); docs/rejections.md:551-560 (G8 refusal). DESIGN.md:184-191 covers only acquisition vs emissionshipped for the G8 refusal; the four-class list is in the export format, not DESIGN.mdUnclassified extern is refused
acquire and witnessed externs must declare undosrc/revl/lower.py:4832, src/revl/lower.py:5078shippedThe Lean file cites older line numbers (lower.py:2573, :2744), which have drifted
Emission is forbidden an undo, since it is a one-way crossingsrc/revl/lower.py:4893-4896shippedThe hint redirects to compensate. Lines 3956-3966 are the G5 witnessed-through-emission message, a different rule
Capability-scoped G4: provider reach must be a subset of the declared scopesrc/revl/lower.py:15055-15083; docs/capabilities.md:126-170; examples/rejections/g4_capability_not_declared.rvl; tests/test_frontend.py:278shippedSubset, including empty, is accepted: "a provider purer than declared is sound"
Transitive least fixed point over the call graph, as setssrc/revl/emission_analysis.py:157-200; docs/capabilities.md ("Transitivity")shippedScoped externs contribute the scope, not their name; a bug where name-keying refused an in-bounds provider is documented in the docstring
Scoped-extern subset behaviourtests/test_1234_g4_scoped_extern_subset.py:75, :84; tests/test_emission_capscope.py:51-90shipped
Lifecycle derived from DAG, no author-written lifecycleDESIGN.md:154-160; docs/guide-humans.md:9-35shippedG2/G3 refusals: examples/rejections/g2_provision_conflict.rvl, examples/rejections/g3_dependency_cycle.rvl
Pure code outside effect forms makes the accumulator completeREADME.md:127-135; DESIGN.md:161 and the guarantee table (G6)shipped at head granularityThe proof status file says G6 is "full at head granularity"; docs/vision.md:109 says G6 is "partial". The paper must state which one it relies on. The README's "provably" is softened in the abstract
Host acquisition undo must be its release; no_residue red otherwisedocs/rejections.md:193-232; CHANGELOG.md:472-477; tests/test_extern_acquire_inverse_1859.py, tests/test_host_undo_release_1859.pyshippeddocs/rejections.md:228: "What this proves is the choice of inverse; that the host body reverts is still the declaration's assertion"
Typed holes noted, never run; admission and backends refusedocs/holes.md:1-33; src/revl/holes.py:72-93 (code T3); tests/test_holes.py:261, :304shipped
Decode table with originsdocs/guarantees.md (G1-G9, A, T families)shippedSeveral rows say "origin: unconfirmed", so the origin split is not complete
Opt[T] absence, gradual frontierdocs/roadmap-archive/02-done-dependency-order-as-built.md:29-31shipped (archive entry)Typing rules T1-T3 have no Lean theorem (formal/STATUS.md, T1/T2/T3 row: "none")
G4 over the classification lattice mechanisedformal/RevL/Theorems/G4_ClassifiedInverseOrEmit.lean:75, :88, :112, :169, :178full over the lattice; shape-level statement weak and markedModels the per-declaration rule and the reach fold; the G4 row of formal/STATUS.md lists open items (fuel bound, FnDecl.calls stand-in, first-class dispatch read as *)
G4 rule checked against the shipped checkerformal/STATUS.md G4 row ("182 G rows, 25 P rows")partialDifferential oracle in formal/harness/diff_corpus.py; this is evidence of agreement on a corpus, not a proof about Python

Numbers that exist

Paper outline

  1. Introduction: undo as the write-only path; the discipline-to-type-system pitch, stated with the docs' own restraint.
  2. Model: components, services, effect ... undo, emit, the four classes.
  3. The G4 rule and its capability refinement, with the transitive analysis (set-valued least fixed point).
  4. Derived lifecycle and checked undo: G2, G3, G6, G7 as consequences; what "the accumulator is complete" depends on.
  5. Typed holes and admission: drafts that check but cannot run.
  6. Decode table: which guarantees are inherited, original, or ordinary typing.
  7. Implementation and corpus: where each rule is enforced; the rejection corpus; mechanized models and the differential oracle in brief (details in the companion paper).
  8. Evaluation: a study on code not written for the rule, and a bug-detection comparison against existing languages, once run.
  9. Related work and limits.

Threats to validity and limits

Fact-check

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

Cite this draft

@misc{revlp01,
  author       = {Thomas Moussajee},
  title        = {Typed Revertible Effects: Inverse-or-Emit as a Checked Guarantee},
  year         = {2026},
  howpublished = {\url{https://inso1337.github.io/revl/research/P01.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