Program core
The Core v2 definitions and theorems for programs that issue typed commands, with the companion modules built on them and their status.
The program core describes programs that return a value or issue a typed command and continue from its reply, and states protocol safety as a separate proposition. It is Lean source under lean/core/, published into a store and checked by the pinned Lean 4.34.1 kernel like any other library. Signatures on this page are copied from the files; proofs are omitted.
| File | What it is | Status |
|---|---|---|
lean/core/CoreV2.lean | Core v2: Contract has pre, next and post | accepted through the API, reverify agreeing (crates/lema-cli/tests/core_v2.rs) |
lean/core/Core.lean | Core v1: Contract has pre and next only | the authoring view of the accepted Core v1 objects |
lean/core/CoreBridge.lean | v1’s Safe, Implements and Simulates are v2’s with unpromised | accepted (core_v2.rs) |
Core v2 elaborates Prog and Impl with the seeds of the accepted Core v1 objects (lean/core/seeds.json), so they are the same objects by identity, and Core v1 programs and implementations are Core v2 ones. All names are in namespace LemaDraft. Files that use the core receive it by identity, for example Received.Contract bound to Contract’s type, Received.mkContract to its constructor (role ctor), and Received.progRec to Prog’s recursor (role generated:rec).
Types
inductive Prog (C : Type) (R : C → Type) (A : Type) : Type where
| ret : A → Prog C R A
| call : (c : C) → (R c → Prog C R A) → Prog C R A
structure Contract (C : Type) (R : C → Type) (S : Type) where
pre : S → C → Prop
next : (s : S) → (c : C) → R c → S
post : S → (c : C) → R c → Prop
structure Impl (C : Type) (R : C → Type) (M : Type) where
step : M → (c : C) → R c × M| Name | Meaning |
|---|---|
Prog C R A | a program over commands C with reply types R c, returning A: ret a stops with a; call c k issues c and continues with k r for its reply r |
Contract C R S | a protocol over abstract state S: when a command may run (pre), how a reply changes the state (next), and what the reply promises (post) |
Impl C R M | a total mathematical operation model with private state M, not a claim about blocking OS I/O |
Judgments
def Safe {C : Type} {R : C → Type} {S A : Type}
(K : Contract C R S) (Q : S → A → Prop) : Prog C R A → S → Prop :=
Prog.rec (fun a s => Q s a)
(fun c _ ih s => K.pre s c ∧ ∀ r, K.post s c r → ih r (K.next s c r))
def Implements {C : Type} {R : C → Type} {S M : Type}
(K : Contract C R S) (h : Impl C R M) (Inv : S → M → Prop) : Prop :=
∀ s m c, Inv s m → K.pre s c →
K.post s c (h.step m c).1 ∧ Inv (K.next s c (h.step m c).1) (h.step m c).2
def Simulates {C : Type} {R : C → Type} {S M N : Type}
(K : Contract C R S) (first : Impl C R M) (second : Impl C R N)
(Rel : S → M → N → Prop) : Prop :=
∀ s m n c, Rel s m n → K.pre s c →
∃ r m' n', first.step m c = (r, m') ∧ second.step n c = (r, n') ∧
K.post s c r ∧ Rel (K.next s c r) m' n'| Name | Meaning |
|---|---|
Safe K Q p s | from state s, p issues only commands K permits, for every reply that keeps K’s promise, and ends where Q holds of the final state and result |
Implements K h Inv | h keeps K’s promises and preserves the invariant Inv between abstract and private state |
Simulates K first second Rel | from Rel-related states, two implementations give the same promised reply and stay related |
Safe is relative to the promise: a promise no reply keeps makes it trivial. It says something about runs only through sound and replacement, whose premises make implementations keep the promise.
Composition and semantics
def bind {C : Type} {R : C → Type} {A B : Type}
(p : Prog C R A) (f : A → Prog C R B) : Prog C R B :=
match p with
| .ret a => f a
| .call c k => Prog.call c (fun r => bind (k r) f)
def interp {C D : Type} {R : C → Type} {E : D → Type} {A : Type}
(module : (d : D) → Prog C R (E d)) (p : Prog D E A) : Prog C R A :=
match p with
| .ret a => Prog.ret a
| .call d k => bind (module d) (fun r => interp module (k r))
def run {C : Type} {R : C → Type} {M A : Type}
(h : Impl C R M) (p : Prog C R A) : M → A × M :=
match p with
| .ret a => fun m => (a, m)
| .call c k => fun m => let answer := h.step m c
run h (k answer.1) answer.2
def trace {C : Type} {R : C → Type} {M A : Type}
(h : Impl C R M) (p : Prog C R A) : M → List (Sigma R) :=
match p with
| .ret _ => fun _ => []
| .call c k => fun m => let answer := h.step m c
Sigma.mk c answer.1 :: trace h (k answer.1) answer.2
def after {C : Type} {R : C → Type} {S : Type}
(K : Contract C R S) (s : S) : List (Sigma R) → S
| [] => s
| event :: rest => after K (K.next s event.1 event.2) rest
def Allowed {C : Type} {R : C → Type} {S : Type}
(K : Contract C R S) (s : S) : List (Sigma R) → Prop
| [] => True
| event :: rest => K.pre s event.1 ∧ K.post s event.1 event.2 ∧
Allowed K (K.next s event.1 event.2) rest
def unpromised {C : Type} {R : C → Type} {S : Type}
(pre : S → C → Prop) (next : (s : S) → (c : C) → R c → S) : Contract C R S :=
⟨pre, next, fun _ _ _ => True⟩| Name | Meaning |
|---|---|
bind p f | sequencing: f substituted for each returned value |
interp module p | linking: each command of p replaced by a program over another signature |
run h p m | the result and final private state of running p on h from m |
trace h p m | the command and reply pairs of that run |
after K s t | the abstract state after trace t from s |
Allowed K s t | each command of t permitted and each reply promised |
unpromised pre next | a contract that promises nothing about replies: Core v1’s contracts |
run and trace are mathematical semantics, not evidence about actual I/O.
Theorems
theorem sound {C : Type} {R : C → Type} {S M A : Type}
(K : Contract C R S) (h : Impl C R M) (Inv : S → M → Prop)
(law : Implements K h Inv) (Q : S → A → Prop) (p : Prog C R A) :
∀ s m, Safe K Q p s → Inv s m →
Allowed K s (trace h p m) ∧
Inv (after K s (trace h p m)) (run h p m).2 ∧
Q (after K s (trace h p m)) (run h p m).1
theorem replacement {C : Type} {R : C → Type} {S M N A : Type}
(K : Contract C R S) (first : Impl C R M) (second : Impl C R N)
(Rel : S → M → N → Prop) (law : Simulates K first second Rel)
(Q : S → A → Prop) (p : Prog C R A) :
∀ s m n, Safe K Q p s → Rel s m n →
(run first p m).1 = (run second p n).1 ∧
trace first p m = trace second p n ∧
Rel (after K s (trace first p m)) (run first p m).2 (run second p n).2
theorem safe_bind {C : Type} {R : C → Type} {S A B : Type}
(K : Contract C R S) (Q : S → B → Prop)
(p : Prog C R A) (f : A → Prog C R B) :
∀ s, Safe K Q (bind p f) s ↔
Safe K (fun t a => Safe K Q (f a) t) p s
theorem safe_interp {C D : Type} {R : C → Type} {E : D → Type}
{S T A : Type} (K : Contract C R S) (G : Contract D E T)
(Rel : T → S → Prop) (module : (d : D) → Prog C R (E d))
(law : ∀ t s d, Rel t s → G.pre t d →
Safe K (fun s' r => G.post t d r ∧ Rel (G.next t d r) s') (module d) s)
(Q : T → A → Prop) (p : Prog D E A) :
∀ t s, Safe G Q p t → Rel t s →
Safe K (fun s' a => ∃ t', Rel t' s' ∧ Q t' a) (interp module p) s
theorem safe_mono {C : Type} {R : C → Type} {S A : Type}
(K : Contract C R S) (P Q : S → A → Prop)
(implies : ∀ s a, P s a → Q s a) (p : Prog C R A) :
∀ s, Safe K P p s → Safe K Q p s
theorem run_bind {C : Type} {R : C → Type} {M A B : Type}
(h : Impl C R M) (p : Prog C R A) (f : A → Prog C R B) :
∀ m, run h (bind p f) m = run h (f (run h p m).1) (run h p m).2
theorem trace_bind {C : Type} {R : C → Type} {M A B : Type}
(h : Impl C R M) (p : Prog C R A) (f : A → Prog C R B) :
∀ m, trace h (bind p f) m =
trace h p m ++ trace h (f (run h p m).1) (run h p m).2
theorem safe_unpromised {C : Type} {R : C → Type} {S A : Type}
(pre : S → C → Prop) (next : (s : S) → (c : C) → R c → S)
(Q : S → A → Prop) (c : C) (k : R c → Prog C R A) (s : S) :
Safe (unpromised pre next) Q (Prog.call c k) s ↔
pre s c ∧ ∀ r, Safe (unpromised pre next) Q (k r) (next s c r)| Theorem | States |
|---|---|
sound | a safe program run on an implementation that keeps the contract’s promises produces an allowed trace, keeps the invariant, and meets its postcondition |
replacement | two implementations related by Simulates give every safe program the same result and trace, and related final states |
safe_bind | sequencing is safe exactly when the first program establishes the continuation’s safety |
safe_interp | linking: when each substituted command keeps its high-level transition and promise, a safe high-level client stays safe, through the relation |
safe_mono | a weaker postcondition keeps a program safe |
run_bind, trace_bind | run and trace of a sequence are those of its parts |
safe_unpromised | without promises, Safe asks about every reply, as in Core v1 |
safe_ret and safe_call unfold Safe one step (both by Iff.rfl). The file’s example, fresh handles, adds twoHandles_safe, counter_implements, twoHandles_runs (sound applied) and twoHandles_unsafe_unpromised (the same program is not safe without open’s promise).
Companion modules
Each file is in lean/core/. Most receive Core v2 by identity; Replies.lean, Durable.lean and Escrow.lean receive nothing, and CallGraph.lean receives Actor.lean’s mailbox. “Accepted” means accepted through the API by the test named; “checked locally” means elaborated and kernel-checked by the pinned Lean outside a store.
| Module | What it adds | Status |
|---|---|---|
Replaces.lean | Replaces s0 m0 n0 K first second Rel: Simulates from related start states; replaces_runs | accepted (replace_v2.rs); the relation the replace method reads |
ReplaceV2.lean, ReplaceGeneric.lean | two counters and the claim plain_offset; the same over a parameter (generic_plain_offset) | accepted (replace_v2.rs, replace_generic.rs) |
Refines.lean | Refines new old Rel, contract refinement with a total relation; refines_implements, refines_refl, refines_trans | accepted (evolve.rs); the relation the refine method reads |
Transport.lean | Transported Rel Q; refines_safe, refines_runs, false_post_safe, false_post_unimplementable, replaces_conforms | checked locally; store acceptance (protected.rs) written, not yet run |
Protected.lean | protected-reference examples: the keyed counter wide, a refinement roomy, and checked negatives (narrow_not_refines, silent_not_refines, never_unimplementable) | checked locally; store acceptance (protected.rs) written, not yet run |
Handles.lean | ownership in contract state: a handle map with per-handle protocols; handleSafe_open and the other step lemmas, handle_counter_implements, use_after_close, double_close | accepted (handles.rs) |
Sums.lean | sums of two signatures: sum_implements, safe_liftL, safe_liftR, safe_seq | accepted (sums.rs) |
Parallel.lean | programs over disjoint handles: schedule_independent, interleave_results, the frame rule safe_par, safe_interleave | accepted (parallel.rs) |
Actor.lean | one certified actor with a bounded mailbox: busy_iff_full, busy_unchanged, every_order_preserves, actor_implements | accepted (actor.rs) |
Replies.lean | a request/response actor with tickets: trace_linearizable, replies_invariant | accepted with the item store (kv.rs, a suite of the retired M3/M4 apps) |
Durable.lean | durable actors: snapshot recovery (recover_eq), idempotency keys (retry, applied_at_most_once) | accepted (durable.rs) |
Escrow.lean | escrow transfers between two shards through a coordinator: conservation, debits_at_most_once, credits_at_most_once, progress; compaction: step_refines, compacted_conservation | accepted (escrow.rs) |
Tickets.lean | tickets and spawned tasks over Handles: system_safe, system_allowed, overlap, fanOut_safe | accepted (tickets.rs) |
Channels.lean | session channels with dual endpoints: dual_dual, client_safe, server_safe, ask_any | accepted (channels.rs) |
Fueled.lean | fueled programs (FProg) over a costed vocabulary: slices_le_gap, total_work, commands_le, runF_some, runF_enough, loop_exits | checked locally; store acceptance (fueled_call_graph.rs) not confirmed |
CallGraph.lean | acyclic actor call graphs with bounded mailboxes: ranked_acyclic, no_wait_cycle, deadlock_free, progress | checked locally; store acceptance (fueled_call_graph.rs) not confirmed |
Connection.lean, ConnectionV2.lean | a connection client on Core v1 and v2; a receive-before-open program is well typed but unsafe | ConnectionV2.lean accepted (core_v2.rs) |
The records disagree on fueled_call_graph.rs: lean/core/README.md records a native pass on 17 Oct 2026, and docs/architecture.md lists it as not yet run. Test files are under crates/lema-cli/tests/.
Replacement, refinement and transport
def Replaces {C : Type} {R : C → Type} {S M N : Type} (s0 : S) (m0 : M) (n0 : N)
(K : Received.Contract C R S) (first : Received.Impl C R M) (second : Received.Impl C R N)
(Rel : S → M → N → Prop) : Prop :=
Rel s0 m0 n0 ∧ Received.Simulates K first second RelSimulates alone has no start states, so a relation that never holds proves it. Replaces adds related start states, and replaces_runs gives a safe client the same result from both implementations from those states.
def Refines {C : Type} {R : C → Type} {S T : Type}
(new : Received.Contract C R T) (old : Received.Contract C R S) (Rel : S → T → Prop) : Prop :=
(∀ s, ∃ t, Rel s t) ∧
∀ s t c, Rel s t → Received.pre old s c →
Received.pre new t c ∧ ∀ r, Received.post new t c r →
Received.post old s c r ∧ Rel (Received.next old s c r) (Received.next new t c r)Rel must be total, so an empty relation cannot make two contracts refine. refines_implements says an implementation of new implements old from related states.
def Transported {S T A : Type} (Rel : S → T → Prop) (Q : S → A → Prop) (t : T) (a : A) : Prop :=
∃ s, Rel s t ∧ Q s arefines_safe makes a client safe on old safe on new with the transported postcondition; refines_runs combines it with sound. The store pins Replaces, Refines, Transported, Implements and Safe by template ID for protected references.
Not covered
runandtraceare checked semantics, not evidence of actual I/O.- The types are universe 0.
safe_interpproves transported safety and the postcondition, not run or trace refinement of linking. - Whole-language quantitative typing (single-use ownership), linearity of OS handles, OS behaviour, compiler refinement and Lean’s consistency are not proved (
lean/core/rules.md, “Remaining gaps”). - Protocol safety models sequencing obligations; it does not stop another external alias from reaching the same real resource.
Written against lema-lang at commit 5a94951, 17 Oct 2026.