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)
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.
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.
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.
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.
Changed by ADR-views-3 D10: a result’s sources are written -> T from a | b, not -> ref(a | b) T.
self, mut self and
keep self for a receiver, where it wrote ref Scan and a separate
mutates = true;from a | b after the result,
-> String from self, where returns = "borrows(self)" stood beside the
signature;[T](x: T) -> T and [H: handler::Handler](h: H), where $T stood;sync(f) (ADR-288 D31), where "from(f)"
stood.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.
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.
nikaia.derived beside nikaia.contracts, and nikaia
describe writes contracts/<crate>.derived beside the description; the
build cache stores the record as a third artifact./// change the record or nothing.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.
keeps says of a viewspell, which writes a signature the compiler
keeps as Nikaia writes it, are Nikaia, in
crates/nikaia-std/src/tools/ledger.nika. The reader also takes the spelling
before D4, so an older ledger reads unchanged.returns and mutates are written only where the signature has no place for
them: a few std entries whose result is a sequence read out of a text, and
no entry at all for mutates.ref self or ref mut self is not kept: it is a
reference by the author’s word, and a use that would need it whole is
NK1131’s. An argument written ref keeps as before (ADR-186 D1), but a
match over one does not keep it.c.to_ascii_lowercase() leaves the caller its char. A body that keeps a
copied value it is given (State::Quoted(c)) does keep it: the column says
what a body does, and what keeping a copy costs is the emitter’s question.