Research / Security and information flow

P38 · full · draft

Right-to-Erasure as a Checkable Program Property: Typed Retention and Signed Erasure Receipts

partial full Security and information flow

State of the work. The compile-time refusal and both signed receipts are shipped with tests; the deadline is per policy and not per value, derivatives are declared and not inferred, and no evaluation exists.

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

Pitch

Erasure evidence becomes two things a reviewer can run: a compile-time refusal when expired data would reach a store, and a signed receipt that lists exactly the copies the compiler knows about and says in its own body what it cannot know.

Abstract

A right-to-erasure request asks an operator to show that a tenant's data is gone. In practice the evidence is a runbook and an assertion. We describe two artifacts in revl, a typed language for capability-scoped agent programs, that move parts of this obligation into the toolchain. First, Retained[T, P] is a qualifier in the same family as Untrusted[T] and Secret[T]: a named policy declares a deadline, a residence, a legal hold, a set of authorised deleters and the derivative classes it covers, and a value under an expired policy is refused at every crossing the compiler sees as an extern declared with a durable-store capability (db, fs, store, kv, blob, archive, index, cache, queue, wal), including across a service seam and through helper functions; a store reached only from inside a host body is not seen. The comparison instant is one per compile, not a per-value age, and we say so. Second, revl erase-report --realm composes realm isolation, the exact withdrawal query, the runtime no-residue proof and the enumeration of boundary crossings into one report, and an optional HMAC-signed receipt over it whose rows can only be crossings the report enumerated. A sibling receipt, revl retention-receipt, signs a policy together with the replicas and derivatives an operator names. Both receipts state, inside the signed body, that they prove the measurement is unaltered and not that anything was erased. We report the design corrections that review forced, and list the evaluation we have not run.

Problem and claim

Erasure tooling usually works on stores: delete by key, tombstone, excise. It cannot say whether the program that wrote the data had any path to a store the tooling does not know about, and the confirmation is a ticket. Language-level privacy work (Jeeves, Hails) restricts who may read a value, not how long it may be kept.

The claim has two parts and both are limited by the repository's own notes. (1) Within the declared boundary, a program that would write a value past its policy's deadline to a store the compiler can see does not compile, unless a legal hold is declared. (2) Within the same boundary, the report names every crossing the realm's components make, with the disposition the report assigned each, and a signature lets a third party check that the list was not altered. Neither claim covers copies made outside revl's boundary or what a remote host did after a crossing. The design note says this in the same words (docs/design/472-retention-erasure-receipts.md, "What a receipt proves and what it does not").

The paper's second theme is design history. The retention half of the original request rested on four nouns (sink, time fact, residence and hold vocabulary, derivative relation), and the design note observes that each of them "names something the tree does not have". It measured the premise, found that none of the four existed, then landed a narrower form of three of them and left the fourth declared rather than inferred. That sequence, and the correction after review that the refusal was argument-blind across a service seam, are results worth reporting.

Contributions

  1. A retention policy declaration and a Retained[T, P] qualifier, checked at admission, with the refusal derived from the capability a crossing declares and not from an author annotation (src/revl/retention.py:129, src/revl/taint.py:91, 612, 1975).
  2. An interprocedural persistence-reach: a provider's body walk notes which parameter reaches a store, so the caller's refusal follows the value across a service seam with a named path (src/revl/taint.py:1347-1409, 1647; docs/design/472-retention-erasure-receipts.md, "Corrections after the third review").
  3. A legal hold as a declaration that overrides the deadline and turns every replica row of the policy receipt (revl retention-receipt) into withheld:legal-hold; the realm erase report has no hold concept (src/revl/retention.py:441-453, 536).
  4. A compliance report that composes realm isolation (G2), the exact withdrawal survivors set, the runtime no-residue proof and the G8 crossing list, scoped to one realm (docs/erase-report.md:8-77, src/revl/erase_report.py:606-685).
  5. Two signed receipts with domain-separated HMAC, a body that carries its own scope of what it does not prove, and a verifier that refuses rows contradicting the word they carry even under a valid MAC (src/revl/erasure_receipt.py:77-153, 484-655; src/revl/retention.py:424-860).
  6. Retention is listed as an admission-kernel path, so a loop that edits a retention policy is a kernel-boundary change, not ordinary tuning (src/revl/kernel_boundary.py:58-72, 160). The module does not itself stop such an edit.

Evidence on main

Claimpath:line or teststatusnote
Design note with the measured premise, the landed slice, three review rounds of correctionsdocs/design/472-retention-erasure-receipts.mdshipped (doc)The note's early sections say the retention half is design-only and later sections say it landed, so quote the landed section
retention <name> { until, residence, hold, deleters, derivatives } and Retained[T, P]src/revl/retention.py:1-80, 210-400; src/revl/taint.py:91shippedstripped to base type, IR byte-identical for programs without it (tests/test_retention_472.py:306)
Persistence sink set derived from capability scopesrc/revl/retention.py:129-143shippedfs is both a source and a sink class
Single evaluation instant per compile, REVL_RETENTION_AS_OF overridesrc/revl/retention.py:407-419; tests/test_retention_472.py:444, 451shippedweaker than a per-value age; module docstring says so
Refusal on declaration and on flow, G-RETAINsrc/revl/taint.py:612, 1975; docs/guarantees.md:97; examples/rejections/gretain_expired_at_persistence_sink.rvlshippedfixture deadline is 2020 so it refuses under any clock
Refusal across a service seam, through a helper, at a non-first argumenttests/test_retention_472.py:212-270shippedfour tests were red before the seam fix, per the note
Legal hold overridestests/test_retention_472.py:264, 278, 285, 546shipped
G-RETAIN not part of G9tests/test_retention_472.py:329, 351shipped
Erase report: runtime no-residue proof, crossings, survivorsdocs/erase-report.md:21-77; src/revl/erase_report.py:606-799; tests/test_erase_report.py (22), _network_254 (8), _relay_1707 (6)shippedexit 1 on unknown realm, residue, or breached realm
Signed realm receipt, opt-in via --receipt-keysrc/revl/erasure_receipt.py:77-153, 359-470; docs/erase-report.md:134-189shippedtests/test_erasure_receipt.py (45)
Row dispositions: revertible, compensated, unresolved, bare; in-process reclaimed, residue, unprovensrc/revl/erasure_receipt.py:97-112, 303-342shippedtri-state fixed after second review
Verifier refuses contradictory bodies even with valid MACsrc/revl/erasure_receipt.py:484-606shipped
Scope is a member of the signed bodysrc/revl/erasure_receipt.py:118-153; tests/test_retention_472.py:683shipped
Policy receipt and CLI verb revl retention-receiptsrc/revl/retention.py:424-1032; src/revl/cli/retention.py:159-253; src/revl/__main__.py:1481-1489; tests/test_retention_receipt_cli_472.py (21)shippedThe design note's "No CLI verb yet" line is stale on main; the verb exists
Residence checked against supplied rows onlysrc/revl/retention.py:1-80; tests/test_retention_472.py:524shipped, narrowno placement fact derived
Derivatives declared from a closed vocabulary (summary, index, embedding, backup, export, cache), reported covered / not-coveredsrc/revl/retention.py:351-385; tests/test_retention_472.py:409-425, 498, 515, 594shipped, declared not inferred
Requester must be a member of the policy's deleterstests/test_retention_472.py:533, 621shipped
Retention in the admission kernelsrc/revl/kernel_boundary.py:58-72, 160shipped
Domain separation from the erasure receipt (test 638); canonical bytes and key rule taken from attest (test 648)tests/test_retention_472.py:638, 648; docs/design/472-retention-erasure-receipts.md ("The signature is domain separated")shipped against the erasure receipt and attestthe design note says deploy._receipt_mac still differs and is reported, not fixed (design note :225; the function is at src/revl/deploy.py:508 on main, not the note's :473-489)

The test-function count for tests/test_retention_472.py is 57 by a grep of def test_. Some are parametrized, so the executed count differs. The tests were counted for this draft, not run.

Numbers that exist

Paper outline

  1. Introduction: erasure as a runbook; two artifacts; scope.
  2. Background: revl's realms, boundary surface (G8), qualifier family.
  3. Retention policy and the refusal: sinks from capability scope, evaluation instant, hold, seam reach.
  4. The erase report and receipts: rows, dispositions, verifier checks, domain separation.
  5. What a receipt proves: the signed scope text and the limits on external copies and derivatives.
  6. Design history: why the original request's retention half was not taken as written, what was supplied, what three reviews corrected.
  7. Evaluation: seeded violations, case study, verifier interoperability.
  8. Discussion: what the compiler can and cannot know; relation to lineage and deletion proofs.
  9. Related work and conclusion.

Threats to validity and limits

Fact-check

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

Cite this draft

@misc{revlp38,
  author       = {Thomas Moussajee},
  title        = {Right-to-Erasure as a Checkable Program Property: Typed Retention and Signed Erasure Receipts},
  year         = {2026},
  howpublished = {\url{https://inso1337.github.io/revl/research/P38.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