Release report — 4fb1554
Frozen evidence for one tested commit. This file is not updated as the project moves;
it records what was verified at 4fb15542dfbf9af0bf34c42fe52783637b3daed4 and stays that
way. Current implementation truth is ../capability-matrix.md.
Absorbed 2026-09-21: the release and limitation sections of the Era-1 ledger
(goal-status.md), which is retired. Git retains the ledger.
What was built under the Era-1 execution contract (retired 2026-09-21; see Git history), what verifies it, and what it does not establish. Written to be checkable: every claim here names the command or file that supports it, and the limitations are in the same document as the results rather than in a different one.
Outcome
Nazm is a small, specified language with two implementations that agree — a tree-walking
interpreter and a native compiler through LLVM IR — and a compiler for it written in
Nazm that reproduces itself byte for byte — over a stated bootstrap subset, which
covers concurrency and modules: the Nazm-written compiler resolves use
and builds a multi-file program on its own. The language has structured concurrency, a
canonical formatter, machine-applicable fixes under a versioned JSON schema, and measured
performance baselines with one profile-driven optimisation pass behind them.
It is an experimental release. It has been verified on one target, it has no package ecosystem, no visibility control, no recursion in native code, and no cross-language performance comparison of any kind.
The checklist, and the evidence for each line
| Definition of done (§13) | State | Evidence |
|---|---|---|
Scalar control flow, including return and transfers inside values | done | crates/nazm-cli/tests/build.rs, every case at -O0 and -O2 against the interpreter |
| A bootstrap subset with text, structured data, collections, memory behaviour, file I/O, errors and modules | done | docs/bootstrap.md §1; the subset is what compiler/*.nz is written in, and use is resolved by the Nazm-written compiler itself — compiler/module.nz |
| Compiler frontend and lowering/emission in Nazm, external dependencies documented | done for the bootstrap subset | compiler/lex.nz, parse.nz, analyse.nz, emit.nz; dependencies in docs/bootstrap.md. Concurrency and module resolution are both implemented — compiler/module.nz, and the task and channel runtime in compiler/emit.nz |
| Native C1 → C2 → C3 bootstrap, conformance and reproducibility | done | compiler/bootstrap.sh; C2 and C3 byte-identical in IR and executable, 5 conformance cases. Every stage compiles compiler/emit.nz itself and resolves its use lines with compiler/module.nz, so what is bootstrapped is the compiler as written rather than a concatenation of it |
| Ordinary independent programs compile with the Nazm-written compiler | done within the subset | crates/nazm-cli/tests/selfhost.rs. Concurrent and multi-file programs both compile with it, imports and all |
| Initial structured concurrency: transfer/capture rules, cancellation, cleanup | done | docs/spec.md; 17 interpreter tests and 17 native tests, all deadline-bounded. Both implementations join a scope before reporting a failure, verified at -O0 and -O2 against the interpreter for a failing body, a failing task, a failure below the body, nested scopes, and body-over-task precedence |
| Structured developer interfaces, grammar/guide, formatting, repair workflow | done | docs/diagnostics.md, schema/, docs/grammar.ebnf, docs/guide.md, scripts/repair.sh |
| Performance baselines, a profiling/optimisation pass, no unsupported speed claims | done | docs/performance.md, bench/baseline-linux-aarch64.json (current); bench/baseline.json (Darwin) is stale — see docs/performance.md |
| A clean rebuild of the integrated final state on a supported target | done | a fresh copy, --offline, full suite — see Reproducing this |
| A final local review/release package | done | scripts/release-candidate.sh, run contained at 4fb1554: every gate passed and the candidate was verified against its own manifest — 70 files, every digest matching — then extracted and re-digested independently on the host. The script validates what it assembled rather than trusting it, and compares file counts both ways so a file copied in but not listed fails |
What is verified, and what that means
Six different kinds of evidence are involved, and they are not interchangeable.
| Claim | Established by | Not established |
|---|---|---|
| The two implementations agree | every native test runs the same program under nazm run and compares | that either matches the specification where both are wrong the same way |
| The compiler is self-hosting | bootstrap.sh: C2 and C3 byte-identical | that the compiler is correct. A compiler that miscompiles its own source in a way that reproduces the miscompilation is also a fixpoint — stated in docs/bootstrap.md |
| Concurrency works | 34 tests, each with a deadline so a hang fails by name; invariants and totals, never a schedule | freedom from deadlock, which the specification explicitly does not promise |
| The formatter is safe | it re-lexes to the same token kinds, is idempotent, and keeps every comment | that its layout is the one you would choose |
| Fixes are applicable | preconditions on every fix; --apply refuses to write a file that would not parse | that a needs_review fix is right — that is what the label means |
| The optimisation helped | a profile before, a measurement against a recorded baseline after, same machine minutes apart | anything about another machine, or about any other language |
Supported platform and dependencies
| Verified targets | aarch64-apple-darwin (Darwin arm64) and aarch64-unknown-linux-gnu (Linux aarch64, inside the contained runner). Other targets remain unverified rather than unsupported |
| Toolchain | rustc 1.98.1, pinned in rust-toolchain.toml. Workspace MSRV floor 1.85; xtask declares 1.89 for itself |
| External at build time | clang, to assemble the emitted LLVM IR and link |
| External at run time | libc — malloc, realloc, write, exit, stdio, and pthreads for concurrency |
| Rust dependencies | clap, serde, serde_json, tempfile (dev). The benchmark harness has its own set; see Cargo.toml |
The bootstrap needs the Rust implementation to exist: C1 is built by it. Nazm does not
build itself from nothing, and docs/bootstrap.md says so at length.
Known limitations
These are properties of the release, not a backlog.
- No visibility control. Every top-level function in a file is visible to anything
that
uses it. Adding private-by-default later will break existing multi-file programs; it already forcedcompiler/lex.nzto be split fromcompiler/lexer.nz, because a library carrying amaincollides with its importer’s. - No recursion in native code. Refused by name (
N0101), pending a stack contract — by both backends now. Until 2026-09-21 only the Rust one refused it: the Nazm-written compiler silently emitted native recursion that clang assembled and that ran, for programsnazm buildrejects.compiler/emit.nznow makes the same refusal over every declared function, covering direct and mutual recursion, cycles that exist only across files, and cycles through aspawn.nazm runstill runs recursive programs: this is a backend limit, not a language rule. The compiler written in Nazm is iterative throughout, which is why it compiles natively. - Ambient authority.
read_file,write_fileand the rest are available to any function with no capability to pass. Labelled as such indocs/spec.md; a capability parameter is the growth path andmain(io: IO)was considered and not chosen. - A compiled program has no iteration budget.
nazm runstops a runaway loop and reportsN0402; the executable does not stop. - Concurrency is one OS thread per task. No M:N scheduling, no work stealing, no fairness, no cancellation beyond closing a channel, and no deadlock freedom. At roughly 4.5 µs per channel hand-off it is suited to coarse-grained work and not to fine-grained message passing.
- Module resolution keys files by a textually normalised path, not a canonical one.
./x.nz,x.nzanda/../x.nzare one file; two names that reach one file through a symbolic link are two, and it would be read twice. The driver keys by the canonical path instead, because it can — the emitted runtime has norealpath. lexer.nz,parser.nzandcheck.nzremain single-file stage probes. They exist to be compared token-for-token and node-for-node against the reference stages, so they read exactly the file they are given. Resolution belongs to the compiler driver,emit.nz, and that is where it is.- Almost no cross-language performance comparison.
docs/performance.mdmeasures this implementation against itself, plus one program — a sieve — againstclangandpython3on one host. Nothing has been measured against Rust, Zig or Go, and one program is not a language comparison. (This entry read “No cross-language performance comparison” until 2026-09-21; the references were added at87e1a47and the disclaimer outlived them.) - The benchmark harness (
nazm-bench) cannot produce a benchmark number. Its agent drivers, provider adapter and task corpus are unwritten, andrunis disabled. It is unrelated to the language work above; seeREADME.md.
Results
cargo test --workspace, run through cargo xtask contained on Linux:
549 passed, 0 failed, 6 ignored. Across 35 suites.
Frozen. This is the count at 4fb1554, and it is not updated as the suite grows — that
is what “frozen evidence for one tested commit” means. capability-matrix.md carries the
current figure. What they cover, grouped by the question each group
answers:
| count | what it establishes | |
|---|---|---|
Language semantics (nazm-core/tests/language.rs) | 114 | each rule in docs/spec.md that a refactor could quietly reverse |
Native end-to-end (nazm-cli/tests/build.rs) | 127 | every case compiled at -O0 and -O2, and compared against the interpreter |
Self-hosting differentials (nazm-cli/tests/selfhost.rs) | 20 | the Nazm lexer, parser and checker against the Rust ones over every .nz in the tree, the full bootstrap chain, its own module resolution, concurrency compiled by it end to end, and its refusal of recursion matching the reference’s |
Concurrency, interpreted (nazm-core/tests/concurrency.rs) | 17 | joins, capture, cancellation, cleanup, contention — every one with a deadline |
Concurrency, native (nazm-cli/tests/concurrent.rs) | 17 | the same programs compiled, at -O0 and -O2, against a written expectation and the interpreter. Six of them establish that a failure joins its scope before it is reported, by giving a task an observable effect and ordering it with channel handshakes rather than by how fast anything runs |
Machine interface (nazm-cli/tests/schemas.rs) | 5 | real command output validated against schema/*.json |
Formatter (nazm-syntax/tests/format.rs) | 11 | token preservation, idempotence, comment preservation, over every source in the tree |
Grammar (nazm-syntax/tests/grammar.rs) | 4 | every lexer token written down, every rule reachable, every example parsed by the real parser |
Repair workflow (nazm-cli/tests/workflow.rs) | 10 | nazm fix, nazm fmt, nazm test and scripts/repair.sh end to end |
Coverage of the guarantees themselves (codes.rs, builtins.rs, docs.rs, counts.rs, resource_regression.rs) | 10 | every diagnostic code asserted or named unreachable; every built-in exercised where an answer is asserted; every example run by a test; the README’s command table, the published schemas and the verified target checked against the build; and this table’s own rows required to sum to the total above; and the stage drivers required to assemble their output in one allocation, measured under a ceiling |
| The rest | 214 | unit tests across the six crates, the CLI’s own behaviour, and the benchmark harness — which is not part of this goal |
The bootstrap: C2 and C3 byte-identical in IR and executable, with 5 conformance
cases agreeing across C2, C3 and the interpreter. compiler/bootstrap.sh writes the
record, including the source digest and line count.
Benchmarks: docs/performance.md, with the noise floor of the method stated alongside
the numbers.
Mutation coverage
cargo xtask mutate injects each catalogued defect into a disposable copy and
requires the suite to catch it. Run at final integration against 524cd24, which is the
integrated state minus nazm test — an addition that touches none of the code the
catalogue mutates.
48 caught, 0 survived, 0 unusable, against a catalogue that held 50 entries at the time and holds 51 now. Every catalogued defect that could be injected was detected by the suite.
Two were not injected, and both were already known: the run confirmed, in the runner’s own words, what checking the catalogue against the tree had found an hour earlier.
| the runner said | why | |
|---|---|---|
the-conditional-selects-the-wrong-branch | pattern occurs 2 times; it must be unique | its anchor was the br i1 line alone, which appears in both statement_if and conditional |
the-loop-reuses-its-first-condition | pattern not found | its anchor predated break and continue, which made the back edge conditional |
Neither is a survivor, and both are worse than one: a mutation that does not apply is a
problem only if somebody reads the log, and reads as coverage if they do not. Both are
repaired, and the ambiguous one is split into two entries, because the statement and
value forms of if are different code paths. cargo xtask mutate --only NAME exists so
the repairs could be verified without re-running fifty.
A caught mutation establishes that the suite detects that defect, and nothing broader. Fifty-one entries is fifty-one facts, not a coverage percentage.
Skipped and conditional coverage
Reported rather than folded into a total, because a skipped test that looks like a passing one is the most misleading thing a suite can contain.
Two diagnostic codes, N0404 and N0406 | asserted by no test, and named as unprovokable in crates/nazm-cli/tests/codes.rs with the reason: one needs the machine to run out of threads, the other out of memory. That test requires every other code to be asserted somewhere, so the list is the only way out and it costs a sentence that has to be true |
6 ignored tests in cargo test --workspace | xtask lifecycle tests. They spawn nested cargo builds and kill real process groups, so they run separately: cargo test -p xtask --test lifecycle -- --ignored --test-threads=1 |
10 docker_boundary tests | exercise nothing without NAZM_REQUIRE_DOCKER=1. They belong to the benchmark harness, not the language |
| The mutation catalogue | cargo xtask mutate is not in the default gate set: it injects defects into a copy and costs a full build per entry |
The exact source state
| Repository | local only; no remote is configured, which is not a blocker to anything here |
| Integrated commit | 4fb15542dfbf9af0bf34c42fe52783637b3daed4 — what the packaged candidate was built and tested at. scripts/release-candidate.sh records it in PROVENANCE.txt and refuses to assemble a candidate whose revision it cannot establish |
| Superseded candidate | c3fb0789bc8b0731bed083f90ab998816ecd8a09, retained unchanged. Its bin/nazmc-selfhosted accepts recursion, the divergence closed at f0f9fc4 — it is the artefact tested at that revision, not a fallback |
| Remaining diff | none at 4fb1554. Corrected 2026-09-21: this row previously named c3fb078 and described everything committed after it as documentation. That was wrong, and it is the same mischaracterisation f0f9fc4 was written to correct: of the four commits between, f0f9fc4 changed compiler/emit.nz, ea6fc06 changed crates/nazm-cli/tests/selfhost.rs, and 4fb1554 changed scripts/release-candidate.sh. Only 3de05c9, after the tested commit, is documentation alone |
| Source digest | PROVENANCE.txt carries one over the source files (.rs, .nz, .md, .json, .sh, .ebnf under seven roots — 140 files). It is not a whole-tree digest: Cargo.toml, Cargo.lock, rust-toolchain.toml, the Dockerfile, the mutation catalogue and every .expected fixture sit outside it |
Reproducing this
From a clean checkout on Darwin arm64, with clang available:
cargo build --workspace
cargo xtask contained tests # the whole suite
cargo lint # clippy, the same alias CI runs
cargo xtask check # architectural gates and the guide against the grammar
cargo xtask contained bootstrap # C1 -> C2 -> C3, byte-identical
cargo xtask contained bench # measurements; --check compares to this host's baseline
cargo xtask contained run \'sh scripts/release-candidate.sh\' # every gate, then the local artefact
# Not in the default set, and each says why in its own output:
cargo xtask contained mutate # one build and test run per catalogued defect
cargo test -p xtask --test lifecycle -- --ignored --test-threads=1
NAZM_REQUIRE_DOCKER=1 cargo test --test docker_boundary -- --test-threads=1
The compiler also runs a corpus of its own:
cargo run -p nazm-cli -- test compiler/conformance # 5 cases, interpreted and compiled
Independent example programs are in examples/: max.nz, sum.nz, primes.nz,
search.nz and pipeline.nz (structured concurrency). Each runs identically under
nazm run and as a nazm build executable, and crates/nazm-cli/tests/build.rs
asserts exactly that.
A repair workflow anyone can run:
printf 'fn main()->Int{\nlet x=1;\nx=2;\nx}\n' > broken.nz
NAZM=./target/debug/nazm sh scripts/repair.sh broken.nz
nazm run broken.nz # 2
The package, as retained
Migrated from the Era-1 ledger, 2026-09-21. This is the authoritative record of the candidate’s identity and of exactly what it establishes.
Milestone G is complete and the goal’s Section 13 checklist is fully ticked. The release is experimental: one verified target, no package ecosystem, no visibility control, no native recursion, no cross-language performance claim.
| Tested commit | 4fb15542dfbf9af0bf34c42fe52783637b3daed4, clean tree |
| Path | ~/nazm-release-4fb1554/ — retained outside the build tree. A copy also sits at target/release-candidate/, which is gitignored and is overwritten by the next release run |
| Archive | nazm-release-candidate.tar.gz, 942,431 bytes, SHA-256 c75b02811904469c577a7cddc9e9a21feedec90cb2896d3adb4cfba8d1ddb820 |
MANIFEST.txt | 7,611 bytes, SHA-256 17f534f5af01d2bf9924db4d870d59b0bc0c8a6d856cc3f14ae5f5d2597a9ff3 |
PROVENANCE.txt | 3,175 bytes, SHA-256 c92715ad9230394b44915ae601c8cfeef58b3bcd7f9948a95b89c8f118c395bc |
| Target | Linux aarch64, inside nazm-contained:1.98.1 — rustc 1.98.1, Debian clang 19.1.7 |
| Contents | 70 files: bin/nazm (the Rust implementation, 1,250,104 B, which builds C1) and bin/nazmc-selfhosted (the Nazm-written compiler as C2, 668,504 B, byte-identical to C3) — both ELF 64-bit LSB pie, ARM aarch64, neither portable off it — plus the bootstrap sources, examples/, schema/, docs/, both benchmark baselines, and the bootstrap record |
| Bootstrap | ir: identical, C2 and C3 digests both a8a16304…, runnable-bytes-differing: 0, 5 conformance cases, 5 sources / 4,647 lines |
| Suite | 549 passed, 0 failed, 6 ignored, 35 suites, 0 survivors |
| Verified twice | the runner checked the candidate against its own manifest before archiving — 70 files, every digest matching — and the exported archive was then extracted on the host and re-verified with shasum -c: 70 matching, 0 failed, and 70 files present against 70 listed, counted both ways |
The previous candidate, kept
~/nazm-release-c3fb078/ is retained unchanged and re-verified after this run:
932,147 B, SHA-256 361347f63a36aaa2c9838c5685f2464f5ec985998e1f3a7af15e17ce45e357ed
(MANIFEST.txt a121c2c4…, PROVENANCE.txt 5d52da22…). It is the candidate tested at
c3fb078, and its bin/nazmc-selfhosted accepts recursion — the defect closed in
f0f9fc4. Kept as the artefact that was tested at that revision, not as a fallback.
Test exclusions, stated rather than buried.
- 6 ignored, all in
xtask/tests/lifecycle.rs: they drive real mutation runs, nestedcargobuilds, and deliberately competing processes. Run withcargo test -p xtask --test lifecycle -- --ignored --test-threads=1. - 10 self-skipping,
crates/nazm-bench/tests/docker_boundary.rs: withoutNAZM_REQUIRE_DOCKER=1they printskipping:and exercise nothing, while still counting as passes in the 549. - The mutation catalogue is not in the gate set at all.
What the package establishes, and what it does not.
PROVENANCE.txtnow carriessource-digestwith asource-digest-scopeline beside it, so the field says what it covers:.rs,.nz,.md,.json,.sh,.ebnfundercompiler crates xtask docs schema examples scripts bench/programs— 140 files. It is not a whole-tree digest. 69 tracked files fall outside it, includingCargo.tomland every crate manifest,Cargo.lock,rust-toolchain.toml,.cargo/config.toml,docker/contained.Dockerfile,xtask/mutations/mutations.toml, and every.expectedfixture. It corroborates the commit for the sources; it cannot detect a changed dependency pin or a changed expectation. (Thec3fb078package still carries the oldsource-tree-digestlabel.)- Archive entry metadata is normalised; whole-archive reproducibility is not
established. Different claims, and the package now says so itself. The tar is written
--sort=name --owner=0 --group=0 --numeric-owner --mtime=@0and the gzip stream carries no timestamp — verified on the archive: every entry readsJan 1 1970, uid/gid 0, gzip MTIME zero. The archive is not bit-reproducible and as the pipeline stands cannot be:PROVENANCE.txtand the packageddocs/bootstrap-record.txt(a file inside the archive, not in the repository) both embed the run’sdate-utc:, so two runs differ by construction. Neither packaged executable has been tested for bit-reproducibility. Byte-identity is established between C2 and C3, within one run and after the build-id is stripped — a third claim again.
The release gate earned its keep this run. The first attempt at this candidate
failed: scripts/release-candidate.sh: 207: printf: Illegal option --. The
reproducibility note added in c6a4215 wrapped so that one line began with --owner=0,
and dash reads a leading -- in a format string as an option. bash does not, which is why
sh -n and a host run said nothing — the container’s /bin/sh is dash. Fixed in
4fb1554 by passing the paragraph as %s\n arguments; the rest of scripts/ and
compiler/ were swept for the same shape and have none. The candidate is assembled and
then validated, so a PROVENANCE.txt that failed to write took the release down instead
of shipping short.
Commits after the tested commit: none. 4fb1554 is the tip. Earlier, c6a4215 had
described itself as “documentation only” and was not — it also edited
scripts/release-candidate.sh, which is what assembles and attributes a candidate, and
nothing tested that edit until the release gate rejected it two commits later.
The archive
scripts/release-candidate.sh assembles target/release-candidate/: both compilers
(bin/nazm, the Rust implementation that builds C1, and bin/nazmc-selfhosted, the
Nazm-written C2 — both Linux aarch64 ELF and neither portable off it), the sources the
bootstrap needs, the documentation, the JSON schemas, the examples, every recorded
baseline, the bootstrap record, a MANIFEST.txt with a SHA-256 of every file, and a
PROVENANCE.txt saying which revision it is and what was checked against it. It builds
nothing that has not passed every gate, and it does not tag, push or publish — that is a
decision for a person.
The candidate built at 4fb1554 is described above under The package, as retained. The
superseded candidate built at c3fb078, retained beside it and unchanged:
| Path | target/release-candidate/ — under target/, so gitignored and rebuildable, not a durable store |
nazm-release-candidate.tar.gz | 932,147 bytes, SHA-256 361347f63a36aaa2c9838c5685f2464f5ec985998e1f3a7af15e17ce45e357ed |
MANIFEST.txt | 7,611 bytes, SHA-256 a121c2c46ef5423229427f64844308a5d6973266d32d1b4c52d8c31f5b259171 |
PROVENANCE.txt | 2,447 bytes, SHA-256 5d52da22c3b0dea8c670283e68a106fcb9a8e04ed0cd0cdf1446e15e82b0d773 |
Two different claims, and only one of them holds.
Entry metadata is normalised. The tar is written with --sort=name --owner=0 --group=0 --numeric-owner --mtime=@0, and the gzip stream carries no timestamp of its own — checked
on the archive itself: every entry reads Jan 1 1970, uid and gid 0, and the gzip header’s
MTIME field is zero. Entry order and metadata are therefore stable across runs.
Whole-archive reproducibility is not established — and as the pipeline stands it cannot
be, because two packaged files embed the run’s wall clock: PROVENANCE.txt and
docs/bootstrap-record.txt both carry date-utc:. Two runs differ by construction. Beyond
that, neither packaged executable has been tested for bit-reproducibility; the bootstrap
establishes only that C2 and C3 match each other within one run, and only after the
linker’s build-id is stripped, which the packaged binary keeps.
What is established is that the contents match MANIFEST.txt — checked inside the
container and again after extraction on the host.