Status: Superseded by ADR-297 — its D1-D2 are D12-D13 there
Date: September 27, 2026
Target Version: Nikaia 0.0.222
Answers: #186, the cleanup point the ledger should
narrate — ADR-094 D5, the only part of that record left
Related: ADR-101 D1 (NK2402, the same narration for
throws), Part III 13.5 (the ledger diff)
A callee that keeps what it is given runs its cleanup when it is done with
it; one that only reads it is handed a view, and the cleanup runs at the end
of the caller’s block. ADR-094 D5 accepted that the call site shows neither,
because the declaration does and the ledger records it — and it owed, in return,
a narration: a change to a parameter’s keeps, on a type whose teardown has an
effect, is a ledger diff that names the callers whose cleanup moved.
Nothing narrated it. Building the test for it found a second thing: a parameter
put into a list literal — let held: Vec[Handle] = [h] — was not counted as
kept, so the callee was declared to take &Handle and rustc refused the list.
NK2403, a warning at each call that hands the callee a name.The build compares the committed nikaia.contracts with the one it inferred, as
it does for throws (ADR-101 D1). A parameter the callee keeps now and did not,
or kept and only reads now, is a moved cleanup point where its type’s
teardown does something: an impl Drop or an impl Cleanup in any ledger the
build reads, on the type or — through its fields — on a type it holds. Each call
in the package that hands that parameter a name is told, once:
warning[NK2403]: `look` keeps `h` now, so `h`'s cleanup runs when `look` is done
with it rather than at the end of this block
A warning and not a refusal, for NK2402’s reason: the program is correct
either way, and committing the ledger diff is the acknowledgement — after it the
note is not given again. A temporary is not narrated: its cleanup was at the
end of its statement, and where inside a call that is, nobody can observe. A
parameter the old signature does not have is new rather than moved.
The clause a struct literal already had, one shape over: [h] and (h, 1)
build a value that holds h, so a parameter standing there is kept.
A call into another package whose keeps moved is not narrated: that
package’s own build rewrites its ledger before this one reads it (ADR-100 D5),
so the consumer has no committed contract of the callee to compare with — the
same limit NK2402 has.
All of it, in 0.0.222: check::Newly (what moved, throws and keeps),
project::keeps_that_moved, the checker’s a_cleanup_that_moved and
tears_down, the keeps clause, crates/nikaia/tests/cleanup_moves.rs and
project.rs’s a_moved_cleanup_point_is_narrated_once.