ADR-251: The Ledger Is the Contract, What It Was Derived From Is Beside It, and It Speaks Nikaia Where Nikaia Can Say It

Status: Accepted Date: September 29, 2026 Narrows: ADR-100 D3 (the hashes a ledger is believed by move to the record beside it; the rule is unchanged) Related: Part III 13.5, ADR-288 D31 (sync(f)), ADR-307 D4 and D5 (the parser keeps ///, and the contract carries no prose - D2’s rule, stated there from the comment’s side), ADR-020 (a package’s ledger is committed), ADR-290 D5 (a description records its crate’s sources), ADR-294 (the ledger’s reader, written in Nikaia)

1. Context

The owner asked, before the ledger’s reader moved to Nikaia, whether the format is right. Read against the ledgers in the repository and Part III 13.5, it was two files in one, and it spoke a second language beside Nikaia’s.

Two files in one. nikaia.contracts is what a caller’s compiler reads about a body it cannot see, and it also carried what the entries were derived from: toolchain, inference, and [sources] with the SHA-256 of every unit. Those decide whether a consumer may believe the contract (ADR-100 D3); they never say what the contract is. Kept in one file, a compiler upgrade or a comment edited in a source changed the published contract’s diff while no contract moved.

Prose in the contract. A pub item’s /// was carried in the ledger as doc. The compiler never read it, and --locked, which compares the file byte for byte, failed on a changed sentence.

A second language. The contract wrote returns = "borrows(self)" where Nikaia writes ref(x) (Part I 2.1); (ref Scan, c: char) plus mutates = true where the source wrote ref mut self; $T where Nikaia writes fn hand[T](x: T); sync = "from(f)" for a promise the language had no word for.

2. Decisions

D1 — What a contract was derived from is a file of its own, beside it.

nikaia.contracts holds the contract; nikaia.derived beside it holds toolchain, inference and [sources], with the same stem for a description: contracts/<crate>.contracts and contracts/<crate>.derived. Both are generated, both are committed, and --locked compares both - the record is what a consumer believes the contract by, so it travels with it. ADR-100 D3’s rule is unchanged: a contract is believed while its sources hash as the record beside it says, and a contract with no record beside it is not believed.

D2 — The contract carries no prose.

doc is withdrawn, and a ledger with a doc key is refused as a key nothing reads. The parser reads /// (ADR-307 D4), and std.contracts, which is written by hand, keeps its sentences as # comments above the entries they are about. Whether a dependency’s documentation needs a way to travel is answered later, over larger dependency graphs, by something that addresses the contract’s entries rather than living in them.

D3 — sync keeps its three written states in the contract, for now.

sync = true (the source promised), "inferred" (the body implies it) and the lambda form stay contract, although a caller reads the first two alike: ADR-288 D28 already makes the difference matter across a package boundary, and dropping it now would be a decision taken back later.

D4 — The contract writes Nikaia’s syntax wherever Nikaia has one.

Changed by ADR-views-3 D10: a result’s sources are written -> T from a | b, not -> ref(a | b) T.

mut self is short for mut self: T, T the type the entry is a method of. The long form is the one that can name the receiver’s type arguments, mut self: collections::HashMap[K, V] (ADR-288 D17); std.contracts writes both, and they read the same.

D5 — The contract says more than the language, in words of its own, and spells them in Nikaia’s grammar.

keeps, touches, locks, crosses, threads, provenance, compares, iterates, ends_by_length, sharing, and the types Seq, Par, Seen and ? stay: each says something a caller has to know that no Nikaia program writes. What a key’s value spells - a type, a parameter, a list - is spelled as Nikaia spells it.

3. Consequences

4. Considered and rejected

The record in nikaia.lock, which already holds per-unit hashes. The lock is written for the project a build runs in, is not committed in the examples, and has no counterpart for a dependency or a description; the record has to be beside each contract it describes.

The record in the build directory, uncommitted. A consumer believes a dependency’s contract by it; uncommitted, every fresh checkout would derive every dependency again, which is ADR-100 D3’s cost without its reason.

Prose kept for pub entries only. The compiler reads none of it; the question is where documentation travels, and it is answered by D2’s later record rather than by keeping it where it breaks --locked.

5. What reads and writes the spelling, and what keeps says of a view

This site is open source. Improve this page.