Replace an implementation
Move an implementation's alias to a new implementation, backed by an accepted proof that the new one replaces the old.
The replace method moves an alias from one implementation to another only when you name an accepted theorem of the exact replacement claim. For Core v2 the claim is Replaces from lean/core/Replaces.lean: the two implementations’ start states are related, and Simulates holds between them under that relation. By replaces_runs, every client that is safe on the contract then gets the same result from both. Contracts, implementations and proofs explains the idea. The steps below follow crates/lema-cli/tests/replace_v2.rs and its counter example, lean/core/ReplaceV2.lean.
Before you start
- A running server on a store where Core v2 is accepted. Publish
lean/core/CoreV2.leanwith"prefix": "LemaDraft","seed_salt": "lean/core"and"seeds"set to theseedsobject oflean/core/seeds.json, and keep thenamesmap from the response. - You know how
Received.*bindings work (Reuse a proof by identity). - In the commands,
$SOCKis the server’s socket. In the request files, values in angle brackets are placeholders.sourceis the Lean file’s text as one JSON string:jq -Rs . lean/core/Replaces.leanprints it encoded.
Steps
-
Publish
lean/core/Replaces.lean, which definesReplacesand provesreplaces_runs. Bind the Core v2 names it uses. Each binding’s value is that name’s entry in Core v2’snamesmap, copied as it is (some entries carry arole):replaces.json {"source": "<text of lean/core/Replaces.lean>", "prefix": "LemaDraft", "seed_salt": "lean/core/Replaces", "bindings": { "Received.Contract": <names["LemaDraft.Contract"]>, "Received.Impl": <names["LemaDraft.Impl"]>, "Received.Simulates": <names["LemaDraft.Simulates"]>, "Received.Safe": <names["LemaDraft.Safe"]>, "Received.Prog": <names["LemaDraft.Prog"]>, "Received.run": <names["LemaDraft.run"]>, "Received.replacement": <names["LemaDraft.replacement"]>}}Shell lema --socket $SOCK call publish --params-file replaces.jsonKeep the response’s
names: the next step bindsLemaDraft.Replaces. -
Publish
lean/core/ReplaceV2.leanthe same way, with"seed_salt": "lean/core/ReplaceV2"and these bindings:Binding Entry Received.ContractLemaDraft.ContractReceived.mkContractLemaDraft.Contract.mkReceived.ImplLemaDraft.ImplReceived.mkImplLemaDraft.Impl.mkReceived.SimulatesLemaDraft.SimulatesReceived.ReplacesLemaDraft.Replaces, from step 1The file defines the contract
counter(each tick replies with the count so far) and two implementations:plainholds the count, andoffsetholds the count plus five. It also defines the relationShiftedbetween their states, the claimPlainOffset := Received.Replaces 0 0 5 counter plain offset Shiftedand its proofplain_offset. -
Point the alias at the current implementation, if it does not name it already:
Shell lema --socket $SOCK call alias --params '{"name": "counter.impl", "target": {"id": "<plain>"}, "expect": null}' -
Replace it. Every field except
nameis the ID of an accepted declaration, taken from thenamesmaps (LemaDraft.ReplaceV2.plainand so on):replace.json {"name": "counter.impl", "expected": "<plain>", "target": "<offset>", "contract": "<counter>", "claim": "<PlainOffset>", "proof": "<plain_offset>", "representation": "<Shifted>", "relation": "<LemaDraft.Replaces>"}Shell lema --socket $SOCK call replace --params-file replace.jsonYou may also give
expected_generation(the generationfindreports) and anidempotency_key, which work as they do foralias. Any other field is refused. -
Read the result. The response is the witness:
name,old,new,contract,claim,proof,representation,namespace,relation,prefixandscope. Hereprefixholds the claim’s eight leading arguments: the implicitC R S M Nand the start states0 0 5. The move and its witness are onereplaceline in the store’s history, andfindnow showscounter.implatoffset, generation 2.
Refusals
The store checks the claim before anything moves:
| Message contains | Cause |
|---|---|
different contract or shape | The claim is about another contract, contract names another one, or relation is missing. |
does not bind the requested implementations | The claim is used the other way round, with expected and target swapped. |
different relation | relation is not the claim’s head, for example Simulates. |
is not accepted | A field names an object this store has not accepted. |
the proof does not establish this exact replacement claim | proof proves something else. |
does not point at the expected implementation | The alias no longer names expected, for example when a finished replacement is sent again. |
a replacement must move the alias to a different implementation | expected and target are the same. |
Reading the witness
The witness records that an accepted proof of exactly this claim exists. Nothing is re-addressed: a client sees offset only when it looks the alias up again and binds the new ID. If you rely on a witness, check its relation, leading arguments and representation. The start states are yours to choose, so they must match the deployment. Moving back needs a claim of its own, with the representation (Shifted) flipped.
Written against lema-lang at commit 5a94951, 17 Oct 2026.