Are you an LLM? Read llms.txt for a summary of the docs, or llms-full.txt for the full context.
Skip to content

Formal specification (Lean)

CVN's load-bearing semantics — the dispatcher, the epoch ladder, and the protocol rules under cvn/enc — are specified in Lean in impl-spec (spec-lean/Enc/Cvn/), as executable definitions with theorems checked by Lean's kernel, and the TypeScript is held to them by a conformance corpus the model itself emits.

What is proved

Enc.Cvn.Dispatch — the dispatcher:

theoremsays
performNow_never_dropsa Cmd is performed or refused — there is no third outcome
refusal_names_kinda refusal names the kind it could not route
run_recordswhile recording, running an action appends exactly its emitted Cmds and performs nothing
dispatch_performs_all_in_orderoutside a dispatch, every emitted Cmd — nested dispatches included — is performed exactly once, in emission order, after the action, and the recording closes
nested_dispatch_drains_nothinga nested dispatch appends to the outer list and drains nothing
perform_outside_is_immediateoutside any dispatch, perform performs now
dispatch_performs_exactly_emittednothing invented, nothing lost

Enc.Cvn.Ladder — the epoch ladder (shared/kernel/dm-epoch.ts):

theoremsays
replay_is_idempotentidentical bytes at a held epoch: silent, unchanged
conflict_is_refuseddifferent bytes at a held epoch: refused
refused_leaves_laddera refusal never changes the ladder
opens_at_zeroan empty ladder accepts exactly epoch 0
accepted_is_above_maxacceptance is strictly monotone
accepted_is_heldwhat was accepted is held afterwards, with the bytes given

Enc.Cvn.Inclusion, Enc.Cvn.DmWire, Enc.Cvn.DataView — the protocol rules under cvn/enc; the theorems are listed there beside each module. The ones that are properties rather than examples:

theoremsays
inclusion_ok_iff_allan event is in the log exactly when all three checks hold
messageTags_heada DM message's first tag is to, always
DataView.step_le_maxSubswhatever the frames, a socket never holds more than 32 subs
DataView.query_ends_with_eosean accepted query's last frame is EOSE
DataView.broadcast_delete_alla delete reaches every sub on its table, matched or not
DataView.upsert_presenta strictly newest upsert survives any positive cap
DataView.diffKeys_disjointa key is never both started and stopped
InviteNotice.toSealed_validevery notice the app builds satisfies the spec's rule: a handoff carries its epoch number
InviteNotice.select_never_zerothe mint secret is never handed off
Identity.push_before_committhe DataView push goes before the enclave commit
Identity.unpushed_not_stampeda row that committed but never reached the DataView is not published, and the next attempt sends it again
Submit.outcome_refusal_keeps_senta refused sent keeps its optimistic row: the WS echo owns that verdict
Submit.enqueue_lengthan id is never queued twice
Query.length_le_1000a node never returns more than 1000 events to one query
Query.sorted_when_reversewith reverse, the result is newest first
GroupWire.kick_excludes_targeta removed member never receives the epoch that follows its removal
GroupWire.admit_wider_than_specthe app rotates on BLOCKED→MEMBER, which the spec's epoch rule does not name
RbacWire.transfers_droppedthe transfers section never reaches the node: ownership transfer is unreachable on this wire
RbacWire.no_outsider_keynobody is enrolled as an outsider
Push.backoff_uncapped_divergesat the ninth retry the node waits 512 s where the spec caps at 300 s
Push.tags_default_emptyan event with no tags is stored with [], never null
Signaling.echo_never_to_senderan echo never returns to the socket that sent it
Signaling.read_monotonica read cursor never moves backwards

How the code is held to it

The DSL that impl-spec compiles to TypeScript is first-order (bytes, maps, scalars, crypto); a dispatcher with callbacks is not expressible in it, so the TypeScript stays hand-written and is refined by replay, the same way the wire protocol is held to enc-oracle:

  1. lake exe cvn-oracle (in impl-spec/spec-lean) runs the Lean definitions over a fixed corpus and writes spec/cvn-conformance.jsonl into this repo — one JSON line per case with the model's expected outcome.
  2. test/spec-conformance.test.mjs replays every dispatch case through createDispatcher; impl-super-mvp's ladder-conformance.test.mjs replays every ladder case through dmEpochSet; test/enc-*.test.mjs replay the inclusion, dm-wire and dataview cases through cvn/enc, and impl-super-mvp's dataview-conformance.test.mjs replays the dataview cases through the real server module and kernel.
  3. spec/codegen-manifest.json pins the corpus by SHA-256; test/manifest.test.mjs refuses a mismatch — a model change that was not re-generated, or a corpus edited by hand.

What this proves: the implementation agrees with the model on every case the model enumerates. What it does not: that the corpus covers every path — a new behaviour gets a new case in Enc/Cvn/Oracle.lean, and a new theorem when it is a property rather than an example.

What is formalized, and what is not yet

A rule counts as formalized only when all three hold: Lean states it and its theorems check, the oracle emits its cases, and the real code replays them — the module the app ships, not a copy. impl-super-mvp's port plan (CVM-PORT-6.md) names, per verb family, the protocol residue the family surfaced. Its state on 2026-09-18:

residuefamilystate
the epoch ladderdmEnc.Cvn.Ladder, replayed through dmEpochSet
verify-event (inclusion)explorerEnc.Cvn.Inclusion, replayed
the DM envelope, its tags, epoch 0, the r-tagged Delete and Updatedm, submit, message-opsEnc.Cvn.DmWire, replayed
the subscription diff, the DataView frames, the server's stepsubscriptionsEnc.Cvn.DataView, replayed three ways
the invite-notice envelope and handoffinviteEnc.Cvn.InviteNotice (refines Enclaves.Personal), replayed through the real kernel module
the profile-publish event, the registry keysprofile, lookupEnc.Cvn.Identity (refines Registry.RegIdentityContent), replayed through the real publisher, families and registry DO
the submit command and its receiptsubmitEnc.Cvn.Submit (refines NodeAPI), replayed through the real kernel modules
query-enclave-eventsexplorerEnc.Cvn.Query (beside Handlers.QueryFilter), replayed through the real adapter and node
the membership MovegroupEnc.Cvn.GroupWire (refines Enclaves.Group), replayed through the real roster, readmission and enrichWelcome
the mint manifest and initial statemintEnc.Cvn.RbacWire (the wire manifest Manifest.lean does not model), the shell and the protocol tier through one source
the DataView push envelopemomentsEnc.Cvn.Push (beside Enc.Core.Push, what ships), replayed through the real client, node pipeline and DataView DO
signaling framescallsEnc.Cvn.Signaling (new: the spec assigns the relay to the host), replayed through the real SignalHub

The column is closed. Where the spec already defined the shape, the Enc.Cvn module imports and refines it and its header lists what the app carries that the spec does not; where the spec had no shape — the wire manifest, the relay — the module states what ships. What the survey found along the way and this work reports rather than changes, filed upstream on 2026-09-18 with the exact lines: the generated core's event-hash domain (computeEventHash inlines 1 where Event.lean pins 0x11 — impl-spec #19), signCommit ignoring its alg argument (impl-spec #20), Enc.Core.Push disagreeing with the node's shipped pipeline (impl-spec #21), STHResponse's swapped t/ts (impl-node #12), and the wire manifest with no place for gates, aliases, scoped grants, transfers and retention (impl-node #13). Two are the app's own and are fixed in the families that own them, each with its replay case first: the registry's /reg_enclaves and /reg_nodes routes it does not serve (lookup), and the cursorless first WS connect against decideReplay (subscriptions).

The app's own decisions are modelled too

The column above is ENC's. It was written when the app's own decisions — the 87 invariants across sixteen families — were held to be sentences a test refutes, not theorems, on the reasoning that they name Super's nouns and Lean would prove Super rather than ENC.

That turned out to be the wrong line, and it was moved. A family's decisions are exactly the kind of thing a model is good at — which enclave a write names, which cipher seals it, what a receipt does to the row it was for — and the reason to model them is not proof for its own sake but the corpus: a decision with a model has cases, and cases replay through the shipping module. Every family that has a run has one:

familymodeltheoremscaseswhat it states
submitFamilies.Submit63103which enclave a write NAMES, which of five ciphers seals it, the epoch-zero ask, the three manifest grants a delete routes by, what a receipt does to its row
dmFamilies.Dm3956the plaintext refusal's three shapes, the peer route, the retry fallback, the mirror's tags, and the three ways a message skips the ratchet
lookupFamilies.Lookup3441who a key belongs to, the cache, the bounded re-ask
payFamilies.Pay2632the asset and its decimals, the refusal, the faucet plan
persistFamilies.Persist2334the key templates, the value serialisation, the active-enclave pair
message-opsFamilies.MessageOps2219one composer in two moods, the truncations, forwarding's provenance
momentsFamilies.Moments2127what a retraction IS, and the tombstone that comes first
mintFamilies.Mint2025the mint's chain, the setup, the admits one at a time
groupFamilies.Group1827membership as a FOLD over the log, which is what refuses the resurrection
inviteFamilies.Invite1720the picked members, the banner, the self refusal
agentFamilies.Agent1613the install, and why a missing provider answers OK
explorerFamilies.Explorer1517the tree head, the events, the inclusion proof
profileFamilies.Profile1112the publish, and what it carries

calls, open and subscriptions declare NO_RUN and decide nothing, so there is nothing to model — and clean/test-port-complete.mjs knows the difference: it derives Families/<Name>.lean from the family name and refuses a family with a run and no model, no corpus kind, or a replay that does not import the module it claims to replay.

Two of these models went in stating a type the code does not have — a Staged { replyArmed, editArmed } record in message-ops, and a isMembershipChange written as a list of two events the app never emits. Both were true theorems about a fiction; both were caught by the replay, which is the only thing that can catch them.

Regenerating

cd ../impl-spec/spec-lean
lake build Enc                                 # the proofs (Enc.Cvn.* are in the library)
lake exe cvn-oracle                            # the corpus → ../impl-cvn/spec/

then update expectedDigest in spec/codegen-manifest.json to the new SHA-256 and commit both repos. Enc.Cvn is part of EncProofs, the framework-free public verification surface, so a public clone re-checks these theorems with the codegen framework absent.