Research / Lifecycle, hot swap and Cordis

P24 · full · draft

Hot Swap That Refuses Unsafe State Migration: Typed Hand-Off and the No-Laundering Rule

partial full Lifecycle, hot swap and Cordis

State of the work. The declared hand-off gate, warm start and undo reporting are shipped with tests on the Python tier; the default instance-migration path has a named, test-pinned, unfixed correlation hazard, and no measurements exist.

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

Pitch

Erlang's code_change hands state from old code to new and trusts the new module to cope; revl checks the hand-off type at admission, before teardown, so a mismatch is refused with the running composition untouched, and it refuses to carry state across a cold-start path.

Abstract

Hot code replacement is only useful if long-lived state survives it, and only safe if the new code can actually hold that state. Erlang/OTP leaves the decision to a user-written code_change callback; dynamic software updating systems such as Ginseng, Kitsune and Proteus verify state transformers against types or update points, usually for C programs. We describe the state transfer protocol of revl, a language whose components acquire resources through effects with declared inverses and are hot-swapped in a running composition. A swap drains in-flight calls, captures state, tears the old provider down in derived LIFO order, loads the successor and restores. Two channels carry state. Live instances of a spawned template migrate through a snapshot and restore protocol (__revl_state__ and __revl_restore__) gated by a same-length, same-type check on the resource vector. Composition-level providers declare a handoff type; the admission gate checks the successor's accepted type against the predecessor's exported type with the same compatibility relation used for interface drift, refuses a mismatch with a trace that names both shapes before teardown, and threads the value into the successor on success. The instance channel checks the whole cohort before writing and rolls the swap back on rejection; a defence-in-depth check on the hand-off path also rolls back. State never crosses a respawn or a rollback (the no-laundering rule), and an agent-authored successor receives operator state only through a declared and type-compatible hand-off. We document one unfixed hazard, same-typed resources reordered by a successor, pin it with a test, and list the evidence on main: 13 test functions in the hand-off grammar and gate file (8 gate, 5 grammar and parse), 4 execution tests, 7 undo-dossier tests, and 4 instance-migration tests. These are counted from the files, not run for this draft.

Problem and claim

A stateful provider (a cache, a session store, a rate limiter) loses its world when the old version is torn down. The swap then degrades to restart with extra steps and no diagnostic. The two usual cures both have a hole. Trusting the successor to accept any state, as OTP does, lets a type mismatch surface later as wrong data. Positional or structural migration, as in many live-reload tools, can silently put state in the wrong slot.

The claim: a swap can be made all-or-nothing with respect to state, for the shapes the gate can see. Either the successor is checked to accept the predecessor's exported shape (checked at admission, re-checked on the live resource vector as defence in depth), or the swap is refused and the running composition is exactly as it was. And the negative half: no path that starts the successor cold (respawn, rollback, undo) carries state, because carrying it would launder one generation's writes into the other generation's code.

What the paper does not claim: that the declared hand-off type is a full behavioural contract (it states shape, not meaning), or that instance migration is sound when a component holds two resources of one type (that case is a known, pinned wrong result).

Contributions

  1. A snapshot and restore protocol for effect-acquired resources with a same-length, same-type gate and cohort-wide check-then-apply, with rollback on rejection (StateIncompatible).
  2. A declared hand-off (a prelude declaration, one per component, additive IR field) whose type reads as "exports" for the predecessor and "accepts" for the successor, checked with the covariant case of the existing compatibility relation; correlation by provided key, so renamed successors still match.
  3. The no-laundering rule: respawn, cold start, rollback and undo carry no state, and the undo report names the state it drops. Under the declared policy an agent-authored successor receives state only through a declared, type-compatible hand-off.
  4. A named and test-pinned hazard (same-typed resource reorder under positional migration) with the narrowed fix stated.
  5. A statement of what instantiation as acquisition does not give: request-scoped instances leak without sub-component teardown scopes.

Evidence on main

Claimpath:line or testStatusNote
Swap protocol: gates, capture, teardown, load, restore, rollbacksrc/revl/mcp/session.py:1692 (Session.swap), src/revl/mcp/session.py:1750 (_plan_swap), src/revl/mcp/session.py:1901-1917shippedFailure before teardown puts the session back from a checkpoint (issue #1446 text at src/revl/mcp/session.py:1722-1727)
Migration policies generational / respawn / declaredsrc/revl/mcp/session.py:1696-1727shippedDefault is generational
__revl_state__ / __revl_restore__ snapshot protocolbackends/python/runtime.py:7585-7592 (Map), backends/python/runtime.py:1743-1778 (capture_state, check_state)shipped on cordis-pyOther tiers: not verified for this draft
Same-length same-type gate, StateIncompatible, rollbackbackends/python/runtime.py:1694-1698, backends/python/runtime.py:1760-1778; src/revl/mcp/session.py:1901-1917; tests/test_instance_migration.py::test_incompatible_successor_is_rejected_and_rolled_back, ::test_state_migrates_onto_a_compatible_successorshippeddocs/design-v2-instances.md:510-525 states the protocol
Same-typed reorder hazard named and pinneddocs/design-v2-instances.md:529-541; tests/test_instance_migration.py::test_reorder_hazard_same_typed_resources_migrate_to_the_wrong_slotshipped as a known defectThe test asserts the WRONG (crossed) result so a fix trips it. Fix (stable-key correlation) still open
Handoff declaration, additive IR field, ir_version stays 3docs/state-handoff.md:20-60 (ir_version 3 at line 47); tests/test_state_handoff.py (parse, lower, prelude position, at most one)shipped
Admission gate on the hand-off type, why.kind state-handoff-driftdocs/state-handoff.md:62-100; src/revl/admission.py (state-handoff-drift trace, about line 502); tests/test_state_handoff.py::test_incompatible_successor_is_refused_at_admission, ::test_refusal_names_both_shapes_and_carries_a_why_trace, ::test_narrowed_acceptor_is_refused, ::test_widened_acceptor_is_admittedshippedRefusal happens at admission, before teardown; no rollback is needed on this path
Warm start and admission refusal on cordis-pytests/test_state_handoff_exec.py::test_state_crosses_to_a_compatible_successor, ::test_incompatible_handoff_is_refused_at_admission_state_intact, ::test_successor_without_handoff_starts_cold, ::test_stateless_swap_reports_no_handoffshippedcordis-py only; no test in this file names a rollback on the hand-off channel
No laundering across respawnsrc/revl/mcp/session.py:1708-1712 (docstring), src/revl/mcp/session.py:1803-1809 (capture skipped under respawn)shippedNo test found that names the rule for this path; undo tests below cover the user-visible part
Undo reports dropped state, never restores itsrc/revl/mcp/session.py:2910-2920 (_undo_state_dropped); tests/test_mcp_undo_handoff_state_2346.py (7 tests, e.g. ::test_undo_after_a_handoff_swap_reports_the_state_it_drops)shipped
Declared policy: agent successor gets state only via declared compatible hand-off; STATE_UNDISCLOSED otherwisesrc/revl/mcp/session.py:1716-1727; src/revl/gate.py:1119-1120; tests/test_self_extending_334.py::test_propose_refuses_to_inherit_live_instance_state_across_trust_boundary, ::test_provider_handoff_state_migrates_across_propose, ::test_propose_refuses_type_incompatible_handoff_inheritanceshippedTest names checked; bodies not read in full. The STATE_UNDISCLOSED refusal is on the propose gate
Defence in depth accept-vs-export type checksrc/revl/mcp/session.py:2372-2384shipped
Structural interface compatibility at admissiondocs/service-compat.md:1-83; tests/test_service_compat.py (16 tests)shippedModel for the state gate; belongs mainly to P34
Instantiation as acquisition and request-scoped leakdocs/design-v2-instances.md:79-106 (the "unbounded leak" text, about line 100); spawn form src/revl/parser.py:647-661; SpawnHandle backends/python/runtime.py:1701partialThe doc's own words: "an unbounded leak" without sub-component teardown scopes
Ambient hot admission against running manifestsrc/revl/mcp/admit_bridge.py (module docstring)shippedCited as part of the admission path, not read deeply

Numbers that exist

Paper outline

  1. Introduction and the state loss problem on hot swap.
  2. Background: components, effects with inverses, derived LIFO teardown, swap and admission.
  3. Two channels: instance migration (snapshot protocol, same-type gate) and declared hand-off (exports and accepts, covariant check, key correlation).
  4. Atomicity: capture before teardown, check the cohort before applying, rollback; defence in depth.
  5. No laundering: respawn, rollback, undo; the declared policy for untrusted successors.
  6. Hazards: same-typed reorder (pinned), request-scoped instance leak.
  7. Evaluation (to be done), comparison with OTP code_change and DUS systems.
  8. Limits and conclusion.

Threats to validity and limits

Fact-check

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

Cite this draft

@misc{revlp24,
  author       = {Thomas Moussajee},
  title        = {Hot Swap That Refuses Unsafe State Migration: Typed Hand-Off and the No-Laundering Rule},
  year         = {2026},
  howpublished = {\url{https://inso1337.github.io/revl/research/P24.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