lungo’s Lean library is the Lake package lungo, released with lungo at lungo’s version (its
sources are lean/ in lungo-rs). A
project imports it to state what lungo carries into the packages it generates
(formal assurance): the attributes write records the worker
reads, and the helper modules hold the definitions claims are usually stated with.
Requiring the library
From a checkout or a vendored copy, by path:
# lakefile.toml
[[require]]
name = "lungo"
path = "../lungo-rs/lean"
From the lungo-rs repository, at the tag of the lungo you generate with:
[[require]]
name = "lungo"
git = "https://github.com/machinefabric/lungo-rs"
rev = "v1.88.0"
subDir = "lean"
Run lake update once and commit lake-manifest.json: lungo’s builds never fetch dependencies.
The library pins the same Lean toolchain as lungo (leanprover/lean4:v4.34.1).
Then import Lungo. It imports two parts:
Lungo.Core:Lungo.Registryand the helper modules below. They import onlyInitand declare no initializers, so a program carries exactly the code of them it calls.Lungo.Attr: the attributes. They need Lean’s elaborator and run only while the project is compiled; lungo never links them, or the parts of Lean they import, into a program.
The records the attributes write are versioned by Lungo.Registry.schemaVersion (1). lungo
refuses a library whose version it does not read (LNG0709).
Kinds
A specification kind, a claim relation, a role and a facility identifier are namespaced
strings: two or more .-separated segments of lowercase letters, digits and _ (and - after
the first segment), each starting with a letter — lungo.decides, acme.cost-bound,
timing.clock.
The lungo namespace is lungo’s own and holds only these:
| lungo’s own | |
|---|---|
| Specification kinds | lungo.relation, lungo.contract, lungo.model, lungo.state, lungo.protocol, lungo.property |
| Claim relations | lungo.decides, lungo.satisfies, lungo.refines, lungo.preserves, lungo.equals, lungo.roundtrip, lungo.law, lungo.monitors |
| Roles | lungo.implementation, lungo.oracle, lungo.monitor, lungo.model |
| Facility identifiers | none: a facility’s identifier is in a namespace of your own |
Any other namespace belongs to whoever defines it: lungo checks its spelling, carries it into
every package and gives it no meaning. A package can define its own relations and kinds this way
(examples/semantic-model defines acme.cost-bound) with no change to lungo.
Attributes
Every attribute is global: local and scoped uses are refused, and so is giving one to a
declaration of another module (a record is read where its declaration is). A declaration gets
each attribute once. Each writes one record, the internal constant decl._lungo_<kind>; the
worker checks every record again when it reads the compiled project
(LNG0701–LNG0707).
@[lungo_spec "kind"]
Marks a specification of the given kind. Any declaration can be one. A specification that is a
logical object — a contract, a relation, a protocol — is best noncomputable, so it is not
exported.
@[lungo_spec "lungo.contract"]
noncomputable def SortsAscending : Contract (List Nat) (List Nat) := sortedPermutation fun a b => decide (a ≤ b)
@[lungo_claim "relation" subject f … spec S …]
On the theorem that proves the claim: its subjects f … stand in relation to its
specifications S …. subject is followed by one or more declarations; spec and the
specifications are optional.
@[lungo_claim "lungo.satisfies" subject sort spec SortsAscending]
theorem sort_satisfies : Satisfies sort SortsAscending := …
Checks: the declaration is a theorem; each subject exists, is not a theorem and is mentioned by
the statement; each specification has @[lungo_spec] and is mentioned by the statement; none is
named twice; and for a lungo.* relation, the statement has the shape the relation
requires. Only the statement is read, never the proof. The assumptions a claim is conditional on
are not declared: lungo finds them among the types of the statement’s leading ∀ binders.
@[lungo_facility "ns.name"]
Names a facility the host provides, by its identifier. It is given to any declaration that is not
a theorem — usually an empty structure — or to an instance of Lungo.Async.Interface Op, which
makes an async facility whose operations are the constructors of Op.
@[lungo_facility "timing.clock"]
structure Clock
inductive StoreOp where
| get (key : String)
| put (key value : String)
@[lungo_facility "store.kv"]
instance storeOps : Lungo.Async.Interface StoreOp where
Ret
| .get _ => Option String
| .put _ _ => Unit
For an async facility, Op must be an inductive type without parameters or indices, no field of
a constructor may be a proof or a type, and what each constructor answers (Ret) must reduce to a
type that does not depend on the constructor’s fields (LNG0707).
@[lungo_operation C]
On an @[extern] declaration: it is an operation of the facility C, which the host implements.
Write @[extern "symbol"] first; the attribute applies after the declaration is compiled.
@[extern "timing_now", lungo_operation Clock]
opaque now (u : Unit) : IO Nat
Checks: C has @[lungo_facility] and is not an async facility; the declaration has @[extern].
lungo further requires an operation to have a C entry and not to be implemented by the runtime or
by an @[export] (LNG0706). An operation whose Lean type is not IO or
EIO is a function to Lean, which may evaluate a call once and keep the value: the host’s
implementation must answer the same for the same arguments.
@[lungo_assumption C]
On a proposition (or a family of them, or a class in Prop): it is assumed, never proved, of the
host’s implementation of the facility C. A claim taking it as a hypothesis is conditional on it.
@[lungo_assumption Clock]
def Ticks : Prop := 0 < ticksPerSecond ()
@[lungo_role "role"]
On a definition a program runs (not a theorem): what it is for. lungo.oracle marks a reference
implementation other code is tested against; lungo.monitor, lungo.model and
lungo.implementation are lungo’s other roles.
Relations
A claim under one of lungo’s relations must state it in its shape, so the relation means the same
in every package. The shape is read from the statement after its leading ∀s; f mentions every
subject means each subject occurs in f.
| Relation | The statement, after its ∀s |
|---|---|
lungo.decides |
f … = true ↔ P …, the left side mentioning every subject |
lungo.equals |
lhs = rhs, lhs mentioning every subject |
lungo.roundtrip |
lhs = rhs, lhs mentioning every subject and rhs none, such as decode (encode x) = some x |
lungo.satisfies |
Lungo.Spec.Satisfies f c or Lungo.Spec.SatisfiesExcept f c, f mentioning every subject |
lungo.refines |
Lungo.Spec.Refines impl spec, impl mentioning every subject |
lungo.preserves |
Lungo.Spec.StateSpec.Preserves s exec or Lungo.Spec.StateSpec.Implements s exec, exec mentioning every subject |
lungo.monitors |
Lungo.Monitor.Sound m p, m mentioning every subject |
lungo.law |
anything mentioning every subject |
Helper modules
Lungo.Spec.Contract
Contract α βwithpre : α → Propandpost : α → β → Prop;Satisfies f c: every input meetingpregives an output meetingpost.ExceptContract α ε βaddsrefuses : α → ε → Prop;SatisfiesExcept f cfor a function returningExcept ε β: it returns an output meetingpost, or refuses asrefusesallows.Contract.Strengthens strong weakandsatisfies_weaken;Contract.seqandsatisfies_compfor two functions in sequence.
Lungo.Spec.Model
BehavioralModel βwithadmits : β → Prop;Refines impl spec: every behaviourimpladmits,specadmits.BehavioralModel.ofFunction f,ofRelation r,function_refines_relation,interandrefines_inter;refines_reflandrefines_trans.
Lungo.Spec.State
StateSpec σ ι οwithinit,invariantandstep : σ → ι → ο → σ → Prop.- For an executable step
exec : σ → ι → ο × σ:StateSpec.Preserves s exec(initial states satisfy the invariant and every step keeps it) andStateSpec.Implements s exec(from states satisfying the invariant, every step is onestepallows). StateSpec.run exec,preserves_runandreachable_invariant: a preserved invariant holds over every run from an initial state.
Lungo.Trace
Protocol ε: the traces itallowed, closed under prefixes;Protocol.any,always p,ofSteps start next,both,Withinandwithin_trans.Reducer ε σwithstartandstep : σ → ε → Option σ;Reducer.run,Reducer.protocolandaccepts_iff.ReducerRefines r p(every traceraccepts,pallows) andrefines_of_invariant, which proves it from an invariant linking states to the traces that reach them.Hyperproperty εandNoninterference r low observe, for properties over several traces.
Lungo.Monitor
Monitor ε σwithstartandstep : σ → ε → Except String σ: a host feeds events one at a time; an error is a violation and its reason.Monitor.run,Accepts, andSound m p:maccepts exactly the tracespallows.- Monitors with their soundness:
ofPredicate(ofPredicate_sound),ofReducer(ofReducer_sound), andboth(both_sound).
Lungo.Async
Interface OpwithRet : Op → Type, what each operation answers.Program Op α:done valueorcall op resume; aMonad, withProgram.perform op. An export returning aProgramis an async function in every language, taking a handler that performs the operations.Program.run handler: interprets a program in any monad, so claims can run it against a model of the host.Exchange,Admits p exchanges a,trace answer p, and the theoremsadmits_trace,run_id,admits_uniqueandadmits_bind.
The library’s own tests
lake build LungoTest checks, with #guard_msgs, every refusal of every attribute.