ADR-250: Stage 1 Is Reached a Module at a Time, Leaves First, and the Share Is Measured

Status: Superseded by ADR-294 — its D1-D5 are D1-D5 there Date: September 28, 2026 Target Version: Nikaia 0.0.252 Answers: ADR-001 D4’s Stage 1 — self-hosting: Nikaia compiling Nikaia, which said where the road ends and not how it is walked Related: ADR-196 (a Nikaia piece joins a Rust program as an ordinary Cargo module), ADR-002 D4 (the committed pre-lowering), ADR-195 D1 (this language has to be written in)

1. Context

The owner’s instruction is to take self-hosting as far as it goes. Five pieces have moved by ADR-196’s route - did you mean, a dsl body’s parameters, describe’s reader, the comptime map’s table and now the HTML template scan - and each move found defects the test suite had not: four at 0.0.250, three at 0.0.248, four in this package. That is the argument for the road in one line: a compiler written in its own language is tested by compiling itself, and every module moved is a program nobody else would have written.

Where it stands, measured (scripts/self_hosting.py): 43,842 lines of Rust in crates/nikaia/src that say something, and 876 of Nikaia - 2.0 %.

The module graph decides the order. Read off the crate:: paths each file uses:

Depends on Modules
nothing of the compiler fixed, emit::template, manifest
the syntax tree only fold, interpreter, contracts::trust, contracts::ty, contracts::send
the tree and the checker’s types dsl, traits, types, contracts::{throws, locks, touch, keeps, sync, tether, keep, sharing, order}, views, text_tiers, diagnostics
nearly everything check (21,408 lines), emit (12,938), parser (3,897), project (3,196)

The syntax tree is the root. ast/mod.rs is 1,191 lines, and 60,000 lines of Rust pattern-match on it. Moving it is not a module’s move: it is the decision of what a Span is (a usize range today, where Nikaia has no usize), whether Spanned[T] is a generic struct Nikaia writes, and how the Rust that remains reads a tree the other language declares.

2. Decisions

D1 - A module moves whole, behind its own interface, and the Rust module stays as the adapter.

The .nika is written beside std’s, lowered by nikaia lower-std and committed; the compiler’s module keeps its name and its callers, and shrinks to the conversions a Nikaia module cannot write for this crate - an error into anyhow, an i64 into a usize. The whole test suite is the proof: the tests that held the Rust module hold the Nikaia one, unchanged or changed only where a type’s spelling moved.

D2 - The order is the graph, leaves first, and the syntax tree waits for its own record.

A module moves once everything it reads is Nikaia or crosses the boundary as plain data (text, numbers, lists). The leaves go first; manifest is next. Its TOML reader stays the toml crate, behind the adapter (§3, corrected at 0.0.253); what moves is every decision the manifest makes. The syntax tree moves by a record of its own that answers the three questions in §1, when the leaves are gone and the modules that read only the tree are the next ring. That record is ADR-252 (0.0.261): the tree moves whole, and a Span is two u32.

D3 - A defect a move finds is fixed in the compiler, in the same package.

Never worked around in the .nika - the move exists to find it. A workaround that is the language’s own answer (.clone() where ADR-216 D4 says a view stays a view) is not a workaround; a line bent to avoid a lowering the emitter gets wrong is, and is not written.

D4 - The share is measured and published.

scripts/self_hosting.py counts the lines that say something in each language

D5 - Stage 1 is the pipeline in Nikaia, and Stage 2 is a fixpoint a test holds.

Stage 1 is reached when main’s pipeline - read, check, lower - runs Nikaia from end to end, with Rust left only as adapters to crates (winnow, clap, the file system). Stage 2 is reached when the lowered compiler lowers its own sources to byte-identical Rust, and a test compares the two.

3. Consequences

4. Considered and rejected

A second compiler written from scratch in Nikaia, the way many languages bootstrapped. It would be tested by nothing until it was finished, and finished means 44,000 lines; the module-at-a-time road is tested at every step by the suite that already exists.

The largest module first (check), for the share. It reads everything, so it cannot move until everything it reads can; starting there is starting at the end.

5. What is built

Built at 0.0.252.

Built at 0.0.258: the ledger’s reader, its shape half.

Built at 0.0.260: what nikaia.toml may say.

This site is open source. Improve this page.