# zerolang Mental Model Wiki

> zerolang is an agent-first programming language and compiler toolchain designed so AI agents can learn it on the fly, receive structured (JSON) diagnostics, and repair programs with deterministic fix plans. This wiki maps the mental model a reader needs to predict the system's behavior, boundaries, and safe-change rules.

## Context Links

- [Agent index](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/llms.txt)
- [Human interactive wiki](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0)
- [GitHub repository](https://github.com/vercel-labs/zerolang)

## Repository Metadata

- Repository: vercel-labs/zerolang

- Generated: 2026-05-21T23:19:14.680Z
- Updated: 2026-05-21T23:20:25.773Z
- Runtime: Claude Code
- Format: Mental Model
- Pages: 8

## Page Index

- 01. [The Mental Model — zerolang in One Map](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/01-the-mental-model-zerolang-in-one-map.md) - The simplest useful model of zerolang: what it is, its five design invariants, the main flows from source to running binary, and what facts change your predictions about its behavior. Read this first to anchor every other page.
- 02. [Language Invariants — Types, Effects, and Fallibility](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/02-language-invariants-types-effects-and-fallibility.md) - The core Zero language rules an agent must internalize to write correct .0 source: explicit capabilities via World, opt-in mutability with let mut, mandatory raises + check for fallibility, the Maybe<T> pattern, and why integer casts require as. These invariants are the checkpoints the compiler enforces before any code runs.
- 03. [Compiler Pipeline — From .0 Source to Native Binary](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/03-compiler-pipeline-from-.0-source-to-native-binary.md) - How the native C compiler (native/zero-c) processes a .0 file: lexer → parser → checker → IR → MIR verifier → direct emitter (ELF64 / Mach-O / COFF). Key invariant: generatedCBytes must be zero for a direct-frontend build. The legacy C bridge path is removed and must not be reintroduced.
- 04. [CLI Command Surface — check, run, build, graph, size, fix](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/04-cli-command-surface-check-run-build-graph-size-fix.md) - The full set of zero CLI commands, their JSON output contracts, and when to use each. Commands are the only stable agent-facing interface: always pass --json for machine consumption. Key distinction: zero fix --plan is read-only (reports repairs, never edits files). Covers provenance guardrails and the sandbox execution requirement for conformance and native tests.
- 05. [Diagnostics & Fix Plans — Structured Error Triage](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/05-diagnostics-fix-plans-structured-error-triage.md) - How zerolang surfaces compiler errors as machine-readable JSON with stable codes (NAM003, TYP009, TAR002, …), source spans, fixSafety levels, and repair ids. The agent-repair loop: check --json → explain <code> → fix --plan --json → apply the narrowest safe patch. Covers all fixSafety categories and when requires-human-review must block automation.
- 06. [Packages & Modules — zero.json, Imports, and Dependency Resolution](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/06-packages-modules-zero.json-imports-and-dependency-resolution.md) - How multi-file Zero projects are structured: the zero.json manifest schema (package, targets, dependencies), how use-imports resolve from src/ (including mod.0 fallback), local path dependencies requiring zero.json, and the deterministic lock written to .zero/package-locks/. Key failure modes: IMP001 (unknown import), PKG001 (missing manifest), IMP002/PKG002 (cycles).
- 07. [Standard Library & Targets — Capabilities, Modules, and Cross-Build Boundaries](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/07-standard-library-targets-capabilities-modules-and-cross-build-boundaries.md) - The two-tier stdlib model: target-neutral modules (std.mem, std.codec, std.parse, std.json, std.io) available everywhere versus hosted-capability modules (std.fs, std.net, std.http, std.args, World.out) that are target-gated and rejected at compile time on non-host targets. How to inspect target facts before cross-building and choose the right build profile.
- 08. [Safe-Change Map — Agent Repair Loop, Testing, and Invariants to Preserve](https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/08-safe-change-map-agent-repair-loop-testing-and-invariants-to-preserve.md) - The closing synthesis: the complete agent edit loop (read → change → check --json → explain → fix --plan → patch → test), the conformance fixture contract (pass/ vs fail/), xfail test markers, the no-legacy-C invariant to guard in every build output, safe-change rules for the pre-1 unstable surface, and the sandbox requirement that keeps native test artifacts off developer machines by default.

## Source File Index

- `AGENTS.md`
- `bin/zero`
- `conformance/check/pass`
- `conformance/diagnostics`
- `conformance/native`
- `conformance/packages`
- `conformance/provenance-surface.json`
- `conformance/run.mjs`
- `examples/add.0`
- `examples/branch.0`
- `examples/fallibility.0`
- `examples/generic-pair.0`
- `examples/memory-primitives.0`
- `examples/point.0`
- `examples/std-http-request.0`
- `examples/std-path-io.0`
- `examples/std-platform.0`
- `native/zero-c/src/call_resolve.c`
- `native/zero-c/src/checker.c`
- `native/zero-c/src/emit_elf64.c`
- `native/zero-c/src/ir.c`
- `native/zero-c/src/lexer.c`
- `native/zero-c/src/main.c`
- `native/zero-c/src/mir_verify.c`
- `native/zero-c/src/parser.c`
- `native/zero-c/src/specialize.c`
- `native/zero-c/src/target.c`
- `native/zero-c/targets`
- `package.json`
- `README.md`
- `scripts/agent-repair-demo.mts`
- `scripts/native-test-sandbox.mts`
- `scripts/provenance-guardrails.mts`
- `scripts/snapshot-command-contracts.mts`
- `skill-data/zero-agent.md`
- `skill-data/zero-builds.md`
- `skill-data/zero-diagnostics.md`
- `skill-data/zero-language.md`
- `skill-data/zero-packages.md`
- `skill-data/zero-stdlib.md`
- `skill-data/zero-testing.md`

---

## 01. The Mental Model — zerolang in One Map

> The simplest useful model of zerolang: what it is, its five design invariants, the main flows from source to running binary, and what facts change your predictions about its behavior. Read this first to anchor every other page.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/01-the-mental-model-zerolang-in-one-map.md
- Generated: 2026-05-21T23:15:26.107Z

### Source Files

- `README.md`
- `AGENTS.md`
- `package.json`
- `bin/zero`
- `skill-data/zero-agent.md`
- `examples/add.0`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [README.md](README.md)
- [AGENTS.md](AGENTS.md)
- [package.json](package.json)
- [bin/zero](bin/zero)
- [skill-data/zero-agent.md](skill-data/zero-agent.md)
- [skill-data/zero-language.md](skill-data/zero-language.md)
- [skill-data/zero-diagnostics.md](skill-data/zero-diagnostics.md)
- [skill-data/zero-builds.md](skill-data/zero-builds.md)
- [examples/add.0](examples/add.0)
- [examples/hello.0](examples/hello.0)
- [examples/fallibility.0](examples/fallibility.0)
- [examples/agent-repair-demo/broken.0](examples/agent-repair-demo/broken.0)
- [examples/agent-repair-demo/fixed.0](examples/agent-repair-demo/fixed.0)
- [examples/systems-package/src/main.0](examples/systems-package/src/main.0)
- [native/zero-c/src/main.c](native/zero-c/src/main.c)
- [native/zero-c/src/checker.c](native/zero-c/src/checker.c)
- [native/zero-c/src/target.c](native/zero-c/src/target.c)
- [native/zero-c/src/ir.c](native/zero-c/src/ir.c)
- [scripts/agent-repair-demo.mts](scripts/agent-repair-demo.mts)
</details>

# The Mental Model — zerolang in One Map

zerolang is a pre-1 experiment in building a programming language whose primary user is an AI agent, not a human. Every design choice—the small regular syntax, the structured JSON tooling surface, the embedded skill docs, the explicit error propagation—exists to make a language that an agent can learn on the fly, inspect deterministically, and repair from structured feedback without needing contextual guessing.

This page gives you the fastest accurate picture of what zerolang is, how its pieces connect, and which facts actually change how programs behave. Read it before anything else; every other page in this wiki adds detail to the model laid out here.

---

## What zerolang Is, In One Sentence

zerolang is a compiled, statically-typed language with a single native-C compiler (`zero-c`), a capability-gated standard library, and a CLI designed to emit machine-readable JSON at every inspection boundary so that agents can check, repair, build, and explain programs without inference from prose.

Sources: [README.md:3-6](), [AGENTS.md:3-8]()

---

## Five Design Invariants

These are the load-bearing commitments. If you change any of them you change what the language is.

| # | Invariant | What It Buys |
|---|-----------|--------------|
| 1 | **Agent-first learnability** — small, regular surface; one obvious way per pattern | An agent can cold-start from examples and `zero skills get zero-language` without pre-training |
| 2 | **Deterministic tooling** — every diagnostic, graph fact, size report, and fix plan is emittable as structured JSON | Agents consume JSON, not parsed terminal output; round-trip is exact |
| 3 | **Standard-library depth** — common capabilities live in coherent `std.*` APIs, not dependency stacks | Most programs never need a package search; `use std.codec`, `use std.time`, etc. cover the common surface |
| 4 | **Explicit effects** — `World`, `std.fs`, `std.args`, `std.env` are passed explicitly; effects are capabilities, not ambient globals | Target-gating is mechanical; a non-host target cannot silently drop a capability call |
| 5 | **Explicit fallibility** — `raises`, `check`, `raise`, `Maybe<T>` replace hidden failures | Errors propagate visibly; an agent reading source code can trace every fallible path without running it |

Sources: [README.md:11-17](), [skill-data/zero-agent.md:45-49](), [skill-data/zero-language.md:8-18](), [skill-data/zero-language.md:111-127]()

---

## Repository Layout

```text
zerolang/
├── native/zero-c/          # The compiler — C source, single binary
│   └── src/
│       ├── main.c          # CLI dispatch, command routing, JSON emission
│       ├── lexer.c / row_syntax.c  # Two-tier lexer (classic + row layout)
│       ├── parser.c        # AST construction
│       ├── checker.c       # Type checker, provenance/borrow analysis
│       ├── type_core.c / unify.c   # Type inference and unification
│       ├── ir.c            # IR/MIR construction + specialization
│       ├── mir_verify.c    # MIR-level invariant verification
│       ├── specialize.c    # Generic monomorphization
│       ├── emit_elf64.c    # ELF/x64 direct emitter
│       ├── emit_elf_aarch64.c
│       ├── emit_macho64.c  # Mach-O/x64
│       ├── emit_coff.c     # COFF/Windows
│       └── target.c        # Target manifest and capability gating
├── examples/               # Runnable .0 programs and packages
├── conformance/            # Language and CLI snapshot fixtures
├── skill-data/             # Markdown skills embedded in the binary
├── scripts/                # Validation, release, agent demo tools (TypeScript)
├── docs/                   # Public documentation site
├── extensions/vscode/      # VS Code extension
└── bin/zero                # Thin shell wrapper → .zero/bin/zero (native)
```

Sources: [AGENTS.md:64-70](), [native/zero-c/src/main.c:1-60]()

---

## The Main Flow: Source to Running Binary

```mermaid
sequenceDiagram
    participant User/Agent
    participant bin/zero
    participant zero-c (native)
    participant Checker
    participant IR/MIR
    participant DirectEmitter
    participant OS

    User/Agent->>bin/zero: zero run examples/add.0
    bin/zero->>zero-c (native): exec .zero/bin/zero run examples/add.0
    zero-c (native)->>Checker: parse → type-check → provenance
    Checker-->>zero-c (native): diagnostics (JSON or stderr)
    zero-c (native)->>IR/MIR: build IR, specialize generics, verify MIR
    IR/MIR->>DirectEmitter: select target emitter (ELF/Mach-O/COFF/…)
    DirectEmitter-->>zero-c (native): native object / binary bytes
    zero-c (native)->>OS: exec produced binary
    OS-->>User/Agent: stdout output
```

`bin/zero` is a thin shell wrapper (`set -euo pipefail; exec $root/.zero/bin/zero "$@"`). It adds no logic — it either finds the native binary or exits 127 with a build instruction. The native compiler is the only real actor.

Sources: [bin/zero:1-13](), [native/zero-c/src/main.c:29-58](), [native/zero-c/src/ir.c:1-10]()

---

## The Agent Repair Loop

The second major flow is not compilation; it is the **inspect-explain-fix cycle** that the tooling is specifically designed for. This is what "agent-first" means in practice.

```mermaid
sequenceDiagram
    participant Agent
    participant zero-c

    Agent->>zero-c: zero check --json broken.0
    zero-c-->>Agent: {ok:false, diagnostics:[{code:"TYP009", repair:{id:"make-binding-mutable"}}]}
    Agent->>zero-c: zero explain --json TYP009
    zero-c-->>Agent: {code:"TYP009", repair:{id:"make-binding-mutable"}, ...}
    Agent->>zero-c: zero fix --plan --json broken.0
    zero-c-->>Agent: {mode:"plan", appliesEdits:false, fixes:[{id:"make-binding-mutable"}]}
    Agent->>Agent: apply edit (let → let mut)
    Agent->>zero-c: zero check --json fixed.0
    zero-c-->>Agent: {ok:true, diagnostics:[]}
```

Key facts:
- `zero fix` is **plan-only** in this compiler; it reports candidate repairs but does not edit files. Edits are the agent's responsibility.
- Every diagnostic carries a stable `code` (e.g., `TYP009`, `NAM003`), a `repair` hint, and a `fixSafety` label (`format-only`, `behavior-preserving`, `api-changing`, `requires-human-review`).
- `zero explain <code>` is a first-class command, not docs-site lookup.

Sources: [scripts/agent-repair-demo.mts:23-56](), [skill-data/zero-diagnostics.md:19-46](), [examples/agent-repair-demo/broken.0](), [examples/agent-repair-demo/fixed.0]()

---

## The Language Surface

### Minimal program

```zero
// examples/hello.0
pub fun main(world: World) -> Void raises {
    check world.out.write("hello from zero\n")
}
```

- `pub fun` exports a function. Only `main` is the entry point.
- `World` is the capability handle for all I/O. It is a parameter, not a global.
- `raises` marks a fallible function. An open `raises` accepts any error; `raises { InvalidInput }` restricts it.
- `check` propagates failure upward. Omitting it on a fallible call is a type error.

### Key syntax forms

| Form | Meaning |
|------|---------|
| `let x = ...` | immutable binding |
| `let mut x = ...` | mutable binding |
| `shape Point { x: i32, y: i32 }` | product type |
| `enum Mode { fast, small }` | C-like enum |
| `choice Result { ok: i32, err: String }` | tagged union |
| `fun f<T>(v: T) -> T` | generic function |
| `ref<T>` / `mutref<T>` | read / write borrow |
| `Span<T>` / `MutSpan<T>` | contiguous view |
| `Maybe<T>` | optional value |
| `raise Err` | explicit error raise |
| `check expr` | propagate fallible result |
| `test "name" { expect(true) }` | inline test block |

Sources: [skill-data/zero-language.md:12-160](), [examples/add.0](), [examples/fallibility.0]()

### Effects are explicit capabilities

```zero
// examples/systems-package/src/main.0
pub fun main(world: World) -> Void raises {
    check world.out.write("systems package\n")
}
```

`World` is passed explicitly. There is no implicit I/O. Standard library modules — `std.fs`, `std.args`, `std.env`, `std.time`, `std.codec`, `std.parse` — are imported with `use` and called directly.

Sources: [examples/systems-package/src/main.0:7-17](), [skill-data/zero-agent.md:45-46]()

---

## Target and Capability Model

zerolang targets are manifest-driven. Each target is a named record with `os`, `arch`, `abi`, `objectFormat`, `linker`, `libc`, and a `capabilities` array.

```text
darwin-arm64   → Mach-O, cc linker, [memory, stdio, args, env, fs, time, rand, net, proc]
linux-musl-x64 → ELF64, zig cc linker, musl, [memory, stdio, args, env, fs, time, rand]
linux-musl-arm64 → ELF64, zig cc, musl, [memory, stdio, time, rand]   ← no args/env/fs
win32-x64.exe  → COFF
```

The capability array is checked at **compile time** against the APIs your program uses. If you call `std.fs` on a target that lacks `fs`, the checker emits `TAR002` before any code is generated. This is the mechanical enforcement behind Invariant 4 (explicit effects).

Use `zero targets` to list the full manifest. Use `zero check --target <name>` to validate before cross-building.

Sources: [native/zero-c/src/target.c:9-87](), [skill-data/zero-builds.md:48-56](), [skill-data/zero-diagnostics.md:52-54]()

---

## Embedded Skills

The native binary bundles documentation as embedded skills, accessible at runtime:

```sh
zero skills list              # names + descriptions
zero skills get zero-language # full skill content
zero skills get zero-diagnostics
zero skills get zero-builds
zero skills get zero-stdlib
zero skills get zero-testing
zero skills get zero-packages
zero skills get zero-agent
```

Skills are compiled from `skill-data/*.md` into the binary (`embedded_skills.inc`). An agent that knows nothing about Zero can bootstrap its understanding by running `zero skills get zero-language` before writing a single line of source. This is a first-class design goal, not a convenience feature.

Sources: [native/zero-c/src/main.c:202-341](), [skill-data/zero-agent.md:14-20]()

---

## What Changes Your Predictions

These are the facts that bifurcate behavior. Get them wrong and your mental model will produce false predictions.

| Fact | Effect |
|------|--------|
| **Target selection** | Determines available capabilities and emitter (ELF64/Mach-O/COFF). Using `std.fs` on `linux-musl-arm64` is a compile error, not a runtime error. |
| **`raises` annotation** | Open `raises` propagates any error upward. Closed `raises { Err }` restricts the set and is checked by the type system. Calling a fallible function without `check` is a type error. |
| **`let` vs `let mut`** | Mutability is a static property. Writing to an immutable binding produces `TYP009`. The repair is always `make-binding-mutable`. |
| **`bin/zero` vs installed `zero`** | `bin/zero` shells out to `.zero/bin/zero` (the locally built binary). If `.zero/bin/zero` is absent, it exits 127. The installed `zero` on `PATH` is a different binary for end users. In the repository, always use `bin/zero`. |
| **`zero fix` is plan-only** | The compiler proposes edits but does not apply them. Applying patches is the caller's responsibility. |
| **`--json` flag** | Changes output from human-readable stderr/stdout to a structured JSON object on stdout. Agent tooling must use `--json`; the prose format is unstable. |
| **Direct emitters vs legacy C backend** | The generated-C backend has been removed. `BLD003` is the diagnostic code for any reference to it. All compilation paths go through the native direct emitters. |

Sources: [skill-data/zero-diagnostics.md:37-65](), [skill-data/zero-builds.md:31-32](), [bin/zero:6-12](), [AGENTS.md:52-59]()

---

## Validation Entry Points

| Command | What it covers |
|---------|----------------|
| `pnpm run conformance` | Language and CLI snapshot fixtures (`conformance/run.mjs`) |
| `pnpm run native:test` | Native compiler unit tests (`scripts/test-native.sh`) |
| `pnpm run docs:test` | Documentation site tests |
| `pnpm run command-contracts` | Snapshot contracts on CLI JSON output shapes |
| `pnpm run agent:demo` | End-to-end agent repair loop (`scripts/agent-repair-demo.mts`) |
| `bin/zero check --json <file>` | Focused type-check with structured output |
| `bin/zero fix --plan --json <file>` | Generate repair plan without applying edits |

Conformance and command-contract tests run inside a sandbox by default (via `@vercel/sandbox`). Pass `ZERO_NATIVE_TEST_ALLOW_LOCAL=1` to run against the locally built compiler.

Sources: [README.md:59-71](), [AGENTS.md:44-61](), [package.json:13-51]()

---

## Summary

zerolang is a single-binary native compiler whose architecture is organized around one idea: every boundary an agent touches should return structured facts, not prose. The compiler (`native/zero-c`) owns the full pipeline from parsing through direct binary emission. The language enforces explicit effects (`World`), explicit fallibility (`raises`/`check`), and explicit mutability (`let mut`) at the type level so that an agent reading source can predict behavior without running it. Target capability gating catches mismatches at compile time, not at runtime. Skills embedded in the binary let an agent learn the language from the language itself. The repair loop—`check → explain → fix → check`—is a designed circuit, not an afterthought.

Sources: [README.md:11-17](), [AGENTS.md:16-24](), [skill-data/zero-agent.md:1-63]()

---

## 02. Language Invariants — Types, Effects, and Fallibility

> The core Zero language rules an agent must internalize to write correct .0 source: explicit capabilities via World, opt-in mutability with let mut, mandatory raises + check for fallibility, the Maybe<T> pattern, and why integer casts require as. These invariants are the checkpoints the compiler enforces before any code runs.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/02-language-invariants-types-effects-and-fallibility.md
- Generated: 2026-05-21T23:15:04.075Z

### Source Files

- `skill-data/zero-language.md`
- `examples/fallibility.0`
- `examples/branch.0`
- `examples/memory-primitives.0`
- `examples/point.0`
- `examples/generic-pair.0`
- `conformance/check/pass`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [skill-data/zero-language.md](skill-data/zero-language.md)
- [examples/fallibility.0](examples/fallibility.0)
- [examples/branch.0](examples/branch.0)
- [examples/memory-primitives.0](examples/memory-primitives.0)
- [examples/point.0](examples/point.0)
- [examples/generic-pair.0](examples/generic-pair.0)
- [examples/result-choice.0](examples/result-choice.0)
- [examples/direct-raises-basic.0](examples/direct-raises-basic.0)
- [examples/direct-rescue-basic.0](examples/direct-rescue-basic.0)
- [examples/direct-unhandled-error-exit.0](examples/direct-unhandled-error-exit.0)
- [examples/static-interface.0](examples/static-interface.0)
- [conformance/check/pass/checker-type-forms.0](conformance/check/pass/checker-type-forms.0)
- [conformance/check/pass/memory-types.0](conformance/check/pass/memory-types.0)
- [conformance/check/pass/payload-match.0](conformance/check/pass/payload-match.0)
- [conformance/check/pass/const-arithmetic.0](conformance/check/pass/const-arithmetic.0)
</details>

# Language Invariants — Types, Effects, and Fallibility

Zero is a systems language built around a small set of compiler-enforced invariants: all I/O and side-effects are accessed only through an explicit `World` capability, every binding is immutable by default, and every fallible call site must be marked with `check` or handled with `rescue`. Violating any of these rules is a compile-time error, not a runtime surprise.

This page documents each invariant precisely — what the compiler demands, what the syntax looks like, and what breaks if the rule is absent. It is the primary reference for writing correct `.0` source files.

---

## The `World` Capability: Explicit Effects

Zero has no global I/O. All effects — writing to stdout, reading files, accessing the network — flow through a `World` value that the runtime injects into `main`. Functions that need effects must receive `World` (or a derived capability) as an explicit parameter.

```zero
pub fun main(world: World) -> Void raises {
    check world.out.write("hello from zero\n")
}
```

`world.out.write(...)` is the only path to stdout. A function that does not accept `World` cannot produce observable output, making the effect boundary readable from the signature alone.

**What breaks without it:** A helper function that omits `World` cannot call `world.out.write` — the compiler will reject any attempt to use `world` outside its scope. Pure computation functions intentionally omit `World`, which signals their side-effect-free nature.

Sources: [skill-data/zero-language.md:12-18](), [examples/branch.0:1-8]()

---

## Mutability: `let` vs `let mut`

All bindings are immutable unless explicitly declared mutable with `let mut`. This is not a lint suggestion — the compiler tracks mutation and rejects assignments to immutable bindings.

```zero
pub fun main(world: World) -> Void raises {
    let message = "hello from a binding\n"     // immutable
    check world.out.write(message)
}
```

When a binding does need to change:

```zero
let mut point = Point { x: 1, y: 2 }
bump_x(&mut point)   // passes a mutable borrow
```

The same principle applies to function parameters: `ref<T>` is a read-only borrow (caller passes `&value`), and `mutref<T>` is a mutable borrow (caller passes `&mut value`).

```zero
fun read_x(point: ref<Point>) -> i32 {
    return point.x
}

fun bump_x(point: mutref<Point>) -> Void {
    point.x = point.x + 1
}
```

**What breaks without it:** Passing a `ref<T>` where a `mutref<T>` is required is a type error. The conformance suite explicitly tests this boundary — `conformance/native/fail/mem-copy-immutable-dst.0` demonstrates the "immutable storage passed to a mutable API" error.

Sources: [skill-data/zero-language.md:51-52](), [conformance/check/pass/checker-type-forms.0:20-26](), [examples/direct-raises-basic.0:1-15]()

---

## Fallibility: `raises`, `raise`, `check`, and `rescue`

Zero makes failure explicit at every function boundary.

### Declaring a fallible function

A function that can fail must carry a `raises` marker in its signature. The error set can be open (bare `raises`) or closed (a named set in braces):

```zero
// Open: any error may propagate
pub fun main(world: World) -> Void raises { ... }

// Closed: only InvalidInput can be raised
fun validate(ok: Bool) -> i32 raises { InvalidInput } {
    if ok == false {
        raise InvalidInput
    }
    return 42
}
```

The closed form `raises { Name }` constrains what the function is allowed to raise; the compiler verifies the set is accurate.

### Calling a fallible function: `check`

Every call to a function marked `raises` must be wrapped in `check`. `check` propagates failure upward — the enclosing function must also be marked `raises` to accept that propagation:

```zero
fun run() -> Void raises { InvalidInput } {
    check validate(true)   // propagates InvalidInput if raised
}
```

Omitting `check` is a compile-time error. There is no implicit propagation.

### Handling failure at a boundary: `rescue`

At a non-fallible boundary (e.g., a C-exported entry point), use `rescue` to convert a raised error into a return value:

```zero
export c fun main() -> u8 {
    return parse(false) rescue err { 9_u8 }
}
```

`rescue` turns the error into a local binding (`err` here) and evaluates the block as the function's return value.

### The error propagation chain in practice

```
validate(ok: Bool) -> i32  raises { InvalidInput }
         ↓ check
run()               -> u8   raises { InvalidInput }
         ↓ check
main(world: World)  -> Void raises          (open)
```

Each layer either propagates via `check` or terminates with `rescue`. No intermediate layer can silently discard an error.

Sources: [examples/fallibility.0:1-15](), [examples/direct-raises-basic.0:1-15](), [examples/direct-rescue-basic.0:1-10](), [skill-data/zero-language.md:113-127]()

---

## The `Maybe<T>` Pattern: Representing Absence

Zero has no null. Optional values are represented as `Maybe<T>`, inspected through `.has` (presence) and `.value` (payload). The pattern appears naturally in memory and buffer types:

```zero
shape BufferView {
    bytes: Span<u8>,
    allocator: Maybe<ref<Alloc>>,   // optional read-only allocator
}

shape EditableBuffer {
    bytes: Span<u8>,
    owner: Maybe<mutref<Alloc>>,    // optional mutable allocator
}
```

`Maybe<T>` composes with both `ref<T>` and `mutref<T>`, so optionality and mutability are orthogonal concerns that combine without special syntax.

**What breaks without it:** There is no nullable pointer syntax. Code that needs an "absent" state must use `Maybe<T>` explicitly; the compiler does not permit null to flow through any other type.

Sources: [examples/memory-primitives.0:1-10](), [conformance/check/pass/memory-types.0:1-7](), [skill-data/zero-language.md:133-141]()

---

## Integer Types and Casts: `as` Is Mandatory

Zero has a full numeric tower with explicit widths:

| Category | Types |
|----------|-------|
| Signed integers | `i8`, `i16`, `i32`, `i64`, `isize` |
| Unsigned integers | `u8`, `u16`, `u32`, `u64`, `usize` |
| Floats | `f32`, `f64` |
| Other | `Bool`, `char`, `String`, `Void` |

Integer literals are checked against context. When the type cannot be inferred, a suffix disambiguates:

```zero
let pair: Pair<i32, u8> = makePair(40, 2_u8)   // _u8 suffix
```

Widening or narrowing between integer types is **never implicit**. The `as` keyword is the only mechanism for an intentional cast:

```zero
let byte: u8 = first_byte(readonly)
let widened: u32 = byte as u32    // explicit widening
```

**What breaks without it:** Writing `let widened: u32 = byte` (without `as`) is a type error even though `u8` always fits in `u32`. Zero treats every numeric conversion as a decision the programmer must make deliberately.

Sources: [conformance/check/pass/checker-type-forms.0:53-54](), [skill-data/zero-language.md:63-65](), [examples/generic-pair.0:11]()

---

## Algebraic Types: `shape`, `enum`, and `choice`

Zero provides three composite-type forms with distinct roles:

| Keyword | Role | Matching style |
|---------|------|----------------|
| `shape` | Product type (named fields) | Field access, method receivers |
| `enum` | Enumeration with optional backing type | `match` without payload |
| `choice` | Sum type (tagged union with payloads) | `match` with payload binding |

```zero
shape Point { x: i32, y: i32 }

enum Mode : u8 { off, on }

choice Result { ok: i32, err: String }
```

### Constructing values

```zero
let point = Point { x: 40, y: 2 }     // shape: named fields
let mode: Mode = Mode.on               // enum: qualified name
let result: Result = Result.ok(42)     // choice: constructor with payload
```

### Exhaustive matching

`match` on a `choice` must cover all arms. The `.arm => binding { }` form binds the payload:

```zero
match result {
    .ok => value {
        expect(value == 42)
    }
    .err => message {
        check world.out.write("choice err\n")
    }
}
```

The wildcard `_` arm acts as a catch-all when exhaustive enumeration is unnecessary.

Sources: [examples/result-choice.0:1-23](), [conformance/check/pass/checker-type-forms.0:6-14](), [skill-data/zero-language.md:69-109]()

---

## Memory View Types: `Span<T>`, `MutSpan<T>`, and Fixed Arrays

Zero distinguishes read and write access at the type level for contiguous memory:

| Type | Meaning |
|------|---------|
| `[N]T` | Fixed-size stack array of N elements |
| `Span<T>` | Read-only contiguous view (borrowed) |
| `MutSpan<T>` | Writable contiguous view (borrowed) |
| `owned<T>` | Explicit resource ownership |

```zero
let mut bytes: [4]u8 = [1, 2, 3, 4]
let readonly: Span<u8> = bytes       // coerces to read-only view
let writable: MutSpan<u8> = bytes    // coerces to mutable view
let window = readonly[1..3]          // slice produces a Span<u8>
```

Passing `readonly` where `MutSpan<u8>` is required is a type error (confirmed by the error-tour's "immutable storage passed to a mutable API" example).

Sources: [conformance/check/pass/checker-type-forms.0:49-56](), [examples/memory-primitives.0:15-22](), [skill-data/zero-language.md:130-135]()

---

## Generics: Type and Static Parameters

Generic shapes and functions use angle-bracket type parameters. Zero additionally supports `static` const parameters for compile-time values such as array lengths:

```zero
shape Pair<T, U> {
    left: T,
    right: U,
}

fun makePair<T, U>(left: T, right: U) -> Pair<T, U> {
    return Pair { left: left, right: right }
}

shape FixedVec<T, static N: usize> {
    len: usize,
    items: [N]T,
}
```

Static parameters participate in type checking: `FixedVec<u8, 8>` and `FixedVec<u8, 16>` are distinct types. Interface constraints use the `T: Interface<T>` form:

```zero
fun readValue<T: Readable<T>>(value: ref<T>) -> i32 {
    return T.read(value)
}
```

Sources: [examples/generic-pair.0:1-17](), [skill-data/zero-language.md:145-164](), [examples/static-interface.0:1-24]()

---

## Invariant Summary

```text
┌─────────────────────────────────────────────────────────────────┐
│  Invariant             Rule                   Compiler enforces  │
├─────────────────────────────────────────────────────────────────┤
│  Effects               All I/O via World       Yes — type error  │
│  Mutability            let by default          Yes — type error  │
│  Fallibility (declare) raises on signature     Yes — type error  │
│  Fallibility (call)    check at every call     Yes — type error  │
│  Absence               Maybe<T>, no null       Yes — type error  │
│  Integer casts         as keyword required      Yes — type error  │
│  Match exhaustiveness  All arms required       Yes — type error  │
└─────────────────────────────────────────────────────────────────┘
```

Every row in this table is a property that Zero verifies before generating any output. An agent or programmer writing `.0` source should treat these as hard gates: code that violates any of them will not pass `zero check` and cannot be built or run. The design is intentional — Zero prefers a clear compile error over a runtime surprise, and the invariants are sized to fit in a single mental model.

Sources: [skill-data/zero-language.md:1-167](), [examples/fallibility.0:1-15](), [conformance/check/pass/checker-type-forms.0:44-71]()

---

## 03. Compiler Pipeline — From .0 Source to Native Binary

> How the native C compiler (native/zero-c) processes a .0 file: lexer → parser → checker → IR → MIR verifier → direct emitter (ELF64 / Mach-O / COFF). Key invariant: generatedCBytes must be zero for a direct-frontend build. The legacy C bridge path is removed and must not be reintroduced.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/03-compiler-pipeline-from-.0-source-to-native-binary.md
- Generated: 2026-05-21T23:16:05.129Z

### Source Files

- `native/zero-c/src/main.c`
- `native/zero-c/src/lexer.c`
- `native/zero-c/src/parser.c`
- `native/zero-c/src/checker.c`
- `native/zero-c/src/ir.c`
- `native/zero-c/src/mir_verify.c`
- `native/zero-c/src/specialize.c`
- `native/zero-c/src/emit_elf64.c`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [native/zero-c/src/main.c](native/zero-c/src/main.c)
- [native/zero-c/src/lexer.c](native/zero-c/src/lexer.c)
- [native/zero-c/src/parser.c](native/zero-c/src/parser.c)
- [native/zero-c/src/checker.c](native/zero-c/src/checker.c)
- [native/zero-c/src/ir.c](native/zero-c/src/ir.c)
- [native/zero-c/src/mir_verify.c](native/zero-c/src/mir_verify.c)
- [native/zero-c/src/specialize.c](native/zero-c/src/specialize.c)
- [native/zero-c/src/emit_elf64.c](native/zero-c/src/emit_elf64.c)
- [native/zero-c/src/emit_macho64.c](native/zero-c/src/emit_macho64.c)
- [native/zero-c/src/emit_elf_aarch64.c](native/zero-c/src/emit_elf_aarch64.c)
- [native/zero-c/src/emit_coff.c](native/zero-c/src/emit_coff.c)
</details>

# Compiler Pipeline — From .0 Source to Native Binary

The Zero language compiler (`native/zero-c`) takes a `.0` source file and produces native machine code directly — no intermediate C, no external compiler toolchain required. The pipeline runs entirely within a single process: tokenize, parse, type-check, lower to IR, verify IR contracts, then emit a binary object or executable for the host platform.

This page documents each stage, the data structures that flow between them, the invariant that `generatedCBytes` must always be zero in a direct-frontend build, and why the legacy C bridge path is permanently removed and must not be reintroduced.

---

## Pipeline Overview

```text
.0 source text
       │
       ▼
  ┌──────────┐
  │  Lexer   │  z_tokenize()  → TokenVec
  └──────────┘
       │
       ▼
  ┌──────────┐
  │  Parser  │  z_parse()     → Program (AST)
  └──────────┘
       │
       ▼
  ┌──────────────┐
  │   Checker    │  z_check_program()  (type + borrow check)
  └──────────────┘
       │
       ▼
  ┌──────────────────────────┐
  │   IR Lowering            │  z_lower_program_with_source()
  │   + Specialization       │  → IrProgram (MIR)
  └──────────────────────────┘
       │
       ▼
  ┌──────────────────┐
  │  MIR Verifier    │  z_mir_verify_direct_contracts()
  └──────────────────┘
       │
       ▼
  ┌──────────────────────────────────────────────────────────┐
  │   Direct Emitter (selected by target)                    │
  │   zero-elf64   → ELF64 (.o / ELF exe)                   │
  │   zero-macho64 → Mach-O (.o / Mach-O exe)               │
  │   zero-elf-aarch64 → ELF AArch64 (.o / exe)             │
  │   zero-coff-x64    → COFF x64 (.obj / PE exe)           │
  └──────────────────────────────────────────────────────────┘
       │
       ▼
  native binary written to disk
```

Sources: [native/zero-c/src/main.c:4365-4385](), [native/zero-c/src/main.c:10175-10211]()

---

## Stage 1 — Lexer

**Entry point:** `z_tokenize(const char *source, ZDiag *diag)` in `native/zero-c/src/lexer.c`.

The lexer performs a single forward pass over the UTF-8 source text. It produces a `TokenVec` — a growable array of `Token` values. Each token carries:

| Field | Purpose |
|---|---|
| `kind` | `TOK_KEYWORD`, `TOK_IDENT`, or punctuation |
| `text` | heap-allocated string copy of the lexeme |
| `line`, `column` | source position for diagnostics |
| `offset`, `length` | byte range into the original source |

The keyword table is a simple null-terminated string array checked with `strcmp`. Two-character symbols (`->`, `=>`, `..`, `==`, `!=`, `<=`, `>=`, `&&`, `||`, `+%`, `+|`) are detected with a lookahead comparison before the single-character fallback.

The lexer emits a single structured `ZDiag` on error (e.g., code 3024 for a malformed character literal) and returns immediately; the caller checks `diag->code != 0` before proceeding.

Sources: [native/zero-c/src/lexer.c:20-39](), [native/zero-c/src/lexer.c:58-97]()

---

## Stage 2 — Parser

**Entry point:** `z_parse(TokenVec *tokens, ZDiag *diag)` in `native/zero-c/src/parser.c`.

The parser consumes the `TokenVec` produced by the lexer and constructs an AST stored in a `Program` struct. It maintains a `Parser` context with the token stream and current index:

```c
typedef struct {
  TokenVec *tokens;
  size_t index;
  ZDiag *diag;
} Parser;
```

The `Program` contains separate growable vectors for top-level declarations: `FunctionVec`, `EnumVec`, `ShapeVec`, `InterfaceVec`, `ConstVec`, `CImportVec`, and `UseImportVec`. Expressions and statements are heap-allocated individually (`Expr *`, `Stmt *`) and owned by their parent nodes.

The parser emits a diagnostic and aborts on the first error; there is no error recovery.

Sources: [native/zero-c/src/parser.c:1-80]()

---

## Stage 3 — Type Checker and Borrow Checker

**Entry point:** `z_check_program(Program *program, ZDiag *diag)` in `native/zero-c/src/checker.c`.

The checker performs semantic analysis in a single pass over the AST. It operates through a `Scope` structure that tracks:

- Variable names, types, mutability, and move state (`moved` flag)
- Provenance information (`ValueProvenance`, `ProvenanceEntry`) for borrow checking
- Parameter and type-parameter classification

The borrow checker computes `FunctionProvenanceSummary` for each function, tracking which return values and storage effects propagate ownership. This is a flow-sensitive analysis that detects aliasing and invalid moves without a separate lifetime pass.

The checker calls `z_set_check_target(target)` before running, making the target architecture visible for ABI-sensitive type rules (e.g., pointer-width types).

Diagnostic codes in the 3000-range cover type errors (`TYP001`–`TYP027`), ownership errors (`OWN001`–`OWN002`), borrow errors (`BOR001`–`BOR002`), interface errors (`IFC001`–`IFC005`), and match exhaustiveness (`MAT001`–`MAT005`).

Sources: [native/zero-c/src/checker.c:1-80](), [native/zero-c/src/main.c:4378-4384]()

---

## Stage 4 — IR Lowering and Generic Specialization

**Entry point:** `z_lower_program_with_source(const Program *program, const SourceInput *input)` in `native/zero-c/src/ir.c`.

### IR (MIR) Data Model

The IR is a flat, register-based mid-level representation stored in `IrProgram`:

- `IrFunction[]` — one entry per concrete function (generics are monomorphized)
- `IrLocal[]` per function — typed storage slots (scalars, arrays, records, `ByteView`, `Alloc`, `Vec`)
- `IrInstr[]` per function — flat instruction list (stores, calls, branches, returns)
- `IrValue` — typed SSA-like values referencing literals, locals, or sub-expressions
- `readonly_data[]` — embedded read-only byte strings (string literals, etc.)

`IrTypeKind` enumerates the value types the direct backend understands:

```
IR_TYPE_VOID, IR_TYPE_BOOL, IR_TYPE_U8, IR_TYPE_U16, IR_TYPE_USIZE,
IR_TYPE_I32, IR_TYPE_U32, IR_TYPE_I64, IR_TYPE_U64,
IR_TYPE_BYTE_VIEW, IR_TYPE_ALLOC, IR_TYPE_VEC,
IR_TYPE_MAYBE_BYTE_VIEW, IR_TYPE_MAYBE_SCALAR,
IR_TYPE_RECORD, IR_TYPE_UNSUPPORTED
```

Sources: [native/zero-c/src/ir.c:145-160]()

### Lowering Sequence

`ir_lower_direct_backend_subset()` implements the main lowering logic:

1. **Guard:** Reject programs with `choices`, `interfaces`, `aliases`, or `consts` — these are not yet supported by the direct backend MVP.
2. **Collect functions:** Clone all non-test, non-generic functions into `direct_functions`. Generic (type-parameterized) functions are excluded from the initial set.
3. **Generic specialization:** Call `ir_collect_generic_specializations_from_stmt_vec()` for each function body. When a call site uses a generic function with concrete type arguments, `z_specialization_plan_add()` records a monomorphization entry. The specialization name is constructed by appending `__TypeArg` suffixes (via `z_specialized_function_name()`). The plan is capped at `IR_SPECIALIZATION_PLAN_LIMIT = 1024` entries.
4. **Sort functions** by stable ID (for deterministic output).
5. **Lower function bodies:** `ir_lower_function_body()` recursively lowers statements and expressions into `IrInstr` sequences for each `IrFunction`.
6. **MIR contract verification:** `z_mir_verify_direct_contracts(ir)` (see Stage 5).
7. **Export check:** If no function is marked `is_exported`, lower fails with "direct backend requires at least one exported C ABI entry function".

Sources: [native/zero-c/src/ir.c:3452-3527](), [native/zero-c/src/specialize.c:58-80]()

### `ir_mark_unsupported`

When lowering encounters a construct the direct backend cannot handle, it calls `ir_mark_unsupported(ir, message, line, column, actual)`. This sets `ir->mir_valid = false` and records structured diagnostic information. Subsequent IR operations check `ir->mir_valid` and short-circuit, so the program consistently fails at the first unsupported construct rather than producing partial output.

Sources: [native/zero-c/src/ir.c:554]()

---

## Stage 5 — MIR Verifier

**Entry point:** `z_mir_verify_direct_contracts(IrProgram *ir)` in `native/zero-c/src/mir_verify.c`.

After IR lowering, the MIR verifier performs a structural correctness pass over the populated `IrProgram`. It checks:

- **Local index bounds:** Every instruction that references a local slot must use a valid index within the function's `local_len`.
- **Initializer kind compatibility:** Each local's `IrTypeKind` constrains which `IrValueKind` may initialize it (e.g., `IR_TYPE_ALLOC` requires `IR_VALUE_FIXED_BUF_ALLOC`; `IR_TYPE_VEC` requires `IR_VALUE_VEC_INIT`).
- **ABI compatibility:** `mir_type_is_direct_abi()` accepts `Bool` and all integer types (`U8`–`U64`); `mir_type_is_direct_fallible_value()` accepts `Void`, `Bool`, `U8`, `U16`, `Usize`, `I32`, `U32`. These define the subset of types that may cross the exported C ABI boundary.
- **Helper requirements:** The verifier tracks `MirHelperRequirements` (allocator helpers, buffer helpers, runtime helpers, host/HTTP runtime imports) and verifies their counts against what the IR actually references.

When a violation is found, `mir_verify_mark_unsupported()` sets `ir->mir_valid = false` with a structured message explaining the violated contract. The `backend_blocker` field is also populated so diagnostics can report the precise stage (`"lower"`) and the unsupported feature to the user.

Sources: [native/zero-c/src/mir_verify.c:92-101](), [native/zero-c/src/mir_verify.c:30-48](), [native/zero-c/src/mir_verify.c:146-158]()

---

## Stage 6 — Direct Emitter

After successful IR lowering and MIR verification, `main.c` selects an emitter based on the resolved target and requested emit kind (`EMIT_EXE` or `EMIT_OBJ`):

| Emitter string | Format | Source |
|---|---|---|
| `zero-elf64` | ELF64 x86-64 object | `emit_elf64.c` |
| `zero-elf64-exe` | ELF64 x86-64 executable | `emit_elf64.c` |
| `zero-elf-aarch64` | ELF AArch64 object | `emit_elf_aarch64.c` |
| `zero-elf-aarch64-exe` | ELF AArch64 executable | `emit_elf_aarch64.c` |
| `zero-macho64` | Mach-O x86-64 / arm64 object | `emit_macho64.c` |
| `zero-macho64-exe` | Mach-O executable | `emit_macho64.c` |
| `zero-coff-x64` | COFF x64 object | `emit_coff.c` |
| `zero-coff-x64-exe` | PE/COFF x64 executable | `emit_coff.c` |

The emitter receives the validated `IrProgram *ir` and writes into a `ZBuf *artifact` (a growable byte buffer). It writes binary data directly using helpers like `elf_append_u8`, `elf_append_u16`, `elf_append_u32`, `elf_append_u64` — little-endian, no external library.

The ELF64 emitter's diagnostic helper illustrates the expected scope:

```c
// native/zero-c/src/emit_elf64.c:52-55
snprintf(diag->expected, sizeof(diag->expected), "direct ELF64 object MVP subset");
snprintf(diag->help, sizeof(diag->help),
  "choose a supported direct target or restrict this program to exported primitive integer arithmetic functions");
```

Unsupported constructs that passed the MIR verifier but cannot be encoded in the binary format emit diagnostic code 4004 (`CGEN004`).

Sources: [native/zero-c/src/emit_elf64.c:8-68](), [native/zero-c/src/main.c:10198-10244]()

---

## The `generatedCBytes = 0` Invariant

Every JSON output path in `main.c` emits `"generatedCBytes": 0` and `"cBridgeFallback": false`. This is not a default or a placeholder — it is a hard invariant asserting that the direct-frontend build path produces **zero C code**. Examples from the codebase:

```c
// main.c:2304
zbuf_append(buf, ",\n  \"generatedCBytes\": 0,\n  \"cBridgeFallback\": false,\n ...");

// main.c:5652 (print_build_json)
printf(",\n  \"generatedCBytes\": %lld,\n ...", generated_c_bytes, ...);
// called with generated_c_bytes = 0 at every call site
```

The value is plumbed through as a `long long generated_c_bytes` parameter but always passed as `0` in direct builds. No code path in the current compiler assigns a non-zero value.

Sources: [native/zero-c/src/main.c:2304](), [native/zero-c/src/main.c:5597-5652](), [native/zero-c/src/main.c:10243]()

---

## Removal of the C Bridge Path

`--emit c` and `--legacy-backend` flags are fully rejected at startup, before any compilation begins:

```c
// native/zero-c/src/main.c:9799-9810
if (command.legacy_backend || command.emit == EMIT_C) {
  diag.code = 2003;
  snprintf(diag.message, sizeof(diag.message), "C backend output is not supported");
  snprintf(diag.expected, sizeof(diag.expected), "zero build --emit exe|obj <input>");
  snprintf(diag.actual, sizeof(diag.actual),
    command.legacy_backend ? "--legacy-backend" : "--emit c");
  snprintf(diag.help, sizeof(diag.help),
    "use direct emitters; C backend output is not a compatibility or debug path");
  ...
  return 1;
}
```

The `EmitKind` enum still has an `EMIT_C` variant and `Command` still has a `legacy_backend` bool (used only to detect and reject the flag). The infrastructure exists solely to produce a clear error. There is no code path that generates C source text from `.0` input. The help text is explicit: C backend output is **not a compatibility or debug path** — the intent is to make reintroduction unattractive at the code level.

Sources: [native/zero-c/src/main.c:29-33](), [native/zero-c/src/main.c:9799-9811](), [native/zero-c/src/main.c:9070-9073]()

---

## Error Propagation and Diagnostics

Errors are reported through a single `ZDiag` struct threaded through every stage. It carries `code`, `line`, `column`, `length`, `message`, `expected`, `actual`, and `help` fields. The `backend_blocker` sub-struct (`ZBackendBlocker`) adds `target`, `object_format`, `backend`, `stage`, and `unsupported_feature` for structured machine-readable output.

Diagnostic codes are organized by prefix:

| Range | Prefix | Domain |
|---|---|---|
| 1001–1003 | ERR | Internal errors |
| 2001–2003 | APP/BLD | Build-level errors |
| 3000–3110 | NAM/TYP/OWN/BOR/IFC/MAT/VAR | Semantic errors |
| 4004 | CGEN004 | Direct backend / emit errors |
| 6001–6002 | TAR | Target errors |
| 7001–7003 | IMP | Import errors |

The `zero explain <CODE>` command maps these codes to documentation. Sources: [native/zero-c/src/main.c:79-162](), [native/zero-c/src/main.c:165-170]()

---

## Build Dispatch Summary

The top-level build command follows this sequence in `main.c`:

1. Reject `--legacy-backend` / `--emit c` immediately (line 9799).
2. Resolve target and validate it is known (`z_find_target`).
3. Load source into `SourceInput`, recording per-phase timing (`resolve_ms`, `parse_ms`, `check_ms`).
4. Tokenize → Parse → Check (lines 4365–4384).
5. Lower to `IrProgram` via `z_lower_program_with_source` (line 10176).
6. Dispatch to the correct emitter: `EMIT_OBJ` → `z_emit_*_object_from_ir`; `EMIT_EXE` → `z_emit_*_exe_from_ir` (lines 10190–10406).
7. Write the `ZBuf` artifact to disk and emit JSON or human-readable output with `generatedCBytes: 0`.

The compiler is a direct, single-pass, single-binary tool: one `.0` file in, one `.o` / executable out, zero generated C, zero external compiler dependencies.

Sources: [native/zero-c/src/main.c:9783-9811](), [native/zero-c/src/main.c:10175-10250]()

---

## 04. CLI Command Surface — check, run, build, graph, size, fix

> The full set of zero CLI commands, their JSON output contracts, and when to use each. Commands are the only stable agent-facing interface: always pass --json for machine consumption. Key distinction: zero fix --plan is read-only (reports repairs, never edits files). Covers provenance guardrails and the sandbox execution requirement for conformance and native tests.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/04-cli-command-surface-check-run-build-graph-size-fix.md
- Generated: 2026-05-21T23:16:06.184Z

### Source Files

- `skill-data/zero-builds.md`
- `skill-data/zero-agent.md`
- `native/zero-c/src/main.c`
- `scripts/snapshot-command-contracts.mts`
- `scripts/native-test-sandbox.mts`
- `conformance/run.mjs`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [skill-data/zero-agent.md](skill-data/zero-agent.md)
- [skill-data/zero-builds.md](skill-data/zero-builds.md)
- [skill-data/zero-diagnostics.md](skill-data/zero-diagnostics.md)
- [docs/articles/cli-reference.md](docs/articles/cli-reference.md)
- [scripts/snapshot-command-contracts.mts](scripts/snapshot-command-contracts.mts)
- [scripts/native-test-sandbox.mts](scripts/native-test-sandbox.mts)
- [conformance/run.mjs](conformance/run.mjs)
- [native/zero-c/src/main.c](native/zero-c/src/main.c)
</details>

# CLI Command Surface — check, run, build, graph, size, fix

The `zero` CLI is the only stable, machine-facing interface to the Zero compiler. Every command that produces structured output supports a `--json` flag that switches from human-readable terminal text to a versioned JSON envelope; agents and automation must always pass `--json` to get stable, parseable results. Text output is for humans and has no stability contract.

This page documents the core commands you will use in daily development and CI — `check`, `run`, `build`, `graph`, `size`, and `fix` — explains their JSON output shapes, describes when to reach for each one, and covers two important operational guardrails: the read-only constraint on `zero fix --plan`, and the sandbox execution requirement for conformance and native tests.

---

## Input Forms

All major commands accept the same three input forms:

| Input | Resolves to |
|---|---|
| `file.0` | A single Zero source file |
| `project/` | A package directory containing `zero.json` |
| `zero.json` | A package manifest directly |

Sources: [docs/articles/cli-reference.md:3-11]()

---

## Command Quickref

| Command | Use it for |
|---|---|
| `zero check <input>` | Parse, typecheck, and report diagnostics |
| `zero run <input>` | Build and run a host executable |
| `zero build <input>` | Emit an executable or object file |
| `zero graph <input>` | Inspect modules, symbols, capabilities, and helper use |
| `zero size <input>` | Explain artifact size, retained helpers, and profile budgets |
| `zero fix --plan --json <input>` | Ask for a typed repair plan (read-only) |
| `zero ship <input>` | Produce a release preview with checksums and metadata |
| `zero doctor` | Check host and target readiness |

Sources: [docs/articles/cli-reference.md:14-28]()

---

## `zero check`

`zero check` is the innermost loop command. It parses source, typechecks it, and reports all diagnostics. It does not emit any build artifact.

```sh
zero check examples/hello.0
zero check --json examples/hello.0
zero check --json --target linux-musl-x64 --emit exe examples/memory-package
```

### When to use it

Run `zero check` after every source edit. It is faster than `build` because it stops before code generation. In an agent edit loop, run `zero check --json <file-or-package>` immediately after patching source to see whether the change introduced or resolved diagnostics.

### JSON output shape

`zero check --json` returns an envelope with these key fields:

| Field | Description |
|---|---|
| `ok` | `true` if no diagnostics |
| `diagnostics` | Array of diagnostic objects (see below) |
| `compileTime` | Bounded `meta` evaluation, sandbox denials, cache key inputs, static values |
| `incrementalInvalidation` | `cacheHits`, `cacheMisses`, `changedInputs` |
| `interfaceFingerprints` | Per-module interface hashes (algorithm `fnv1a64-zero-interface-v1`) |
| `targetReadiness` | Present when `--target` is passed: `ok`, `buildable`, target-specific diagnostics |

Each diagnostic object has:

| Field | Description |
|---|---|
| `code` | Stable code such as `NAM003`, `TAR002`, `BLD003` |
| `message` | Short human summary |
| `path`, `line`, `column`, `length` | Source span |
| `expected`, `actual` | Structured mismatch facts when available |
| `help` | Concise next action |
| `fixSafety` | Safety label for an agent repair |
| `repair` | Optional repair id and summary |
| `related` | Extra spans or facts |

Sources: [skill-data/zero-diagnostics.md:21-33](), [docs/articles/cli-reference.md:58-76](), [scripts/snapshot-command-contracts.mts:361-364]()

### Separating language validity from target buildability

Passing `--target <target> --emit <kind>` keeps these two axes independent:

- Top-level `ok` and `diagnostics` describe parse/typecheck results.
- `targetReadiness.ok`, `buildable`, and nested diagnostics describe predictable backend blockers — without writing any artifact to disk.

This lets an agent check whether code is valid language and whether it would compile for a specific target, in a single invocation, without side effects.

Sources: [docs/articles/cli-reference.md:73-77]()

---

## `zero run`

`zero run` builds a host executable using the direct backend, runs it, passes through stdout/stderr, and exits with the program's own exit status. It is the fastest path from source to execution during local development.

```sh
zero run examples/hello.0
zero run examples/cli-file.0 -- input.txt
```

Arguments after `--` are forwarded to the Zero program. An optional `--out <file>` flag controls where the compiled binary lands; if omitted, the binary is placed in a temporary path.

The generated-C backend is not a fallback path. When the binary exists after `run`, its presence reflects direct compilation. The `.c` extension is never generated alongside the artifact — the command contracts verify `existsSync(runArtifact.c) === false`.

Sources: [skill-data/zero-builds.md:19-27](), [docs/articles/cli-reference.md:42-48](), [scripts/snapshot-command-contracts.mts:649-656]()

---

## `zero build`

`zero build` compiles a source file or package to a native artifact. Pass `--emit exe` for a linked executable or `--emit obj` for a relocatable object file.

```sh
zero build --emit exe examples/hello.0 --out .zero/out/hello
zero build --emit obj examples/hello.0 --out .zero/out/hello.o
zero build --json --emit exe --target linux-musl-x64 examples/hello.0 --out .zero/out/hello-linux
```

Always pass `--target` when building for a non-host platform. Inspecting target names before cross-building:

```sh
zero targets
zero check --target linux-musl-x64 examples/memory-package
```

### Profiles

Profiles control optimization level and size/speed tradeoffs. Common profile names:

| Profile | Semantics |
|---|---|
| `debug` | Full debug metadata, no optimizations |
| `dev` | Fast build, limited optimizations |
| `release-fast` | Optimized for speed |
| `release-small` | Optimized for binary size |
| `tiny` | Maximum size reduction (tiny hello < 10 KB) |
| `audit` | Additional safety checks |

```sh
zero build --profile release-small examples/hello.0
```

### JSON output shape

`zero build --json` returns:

| Field | Description |
|---|---|
| `schemaVersion` | Always `1` |
| `emit` | `exe` or `obj` |
| `target` | Target triple string |
| `hostTarget` | The host platform triple |
| `compiler` | Emitter path such as `zero-elf64` |
| `artifactPath` | Path to the output artifact |
| `artifactBytes` | Byte size of the artifact |
| `generatedCBytes` | Must be `0`; non-zero indicates an unsupported backend |
| `loweredIrBytes` | Lowered IR byte size |
| `profileSemantics` | `canonical`, `profileKey`, `profileBudget` |
| `objectBackend` | `objectEmission.path`, `linking.externalToolchain`, direct facts |
| `releaseTargetContract` | Artifact kind, object format, linker flavor, libc mode, sysroot requirements, emitter readiness, capability facts, repeat-build hash policy |

The `releaseTargetContract.fallbackPolicy` is always `"explicit-direct-never-c-bridge"`, meaning the compiler never silently falls back to the removed C bridge.

Sources: [scripts/snapshot-command-contracts.mts:624-646](), [docs/articles/cli-reference.md:64-84](), [skill-data/zero-builds.md:30-45]()

### Provenance guardrail: `generatedCBytes`

The contract script asserts `generatedCBytes === 0` on every build and ship report. This is the machine-readable provenance guardrail: any non-zero value means the removed generated-C backend was used, which violates the direct-emitter contract. Agents reading build JSON must check this field before treating an artifact as conformant.

Sources: [scripts/snapshot-command-contracts.mts:47-48](), [conformance/run.mjs:38-39](), [skill-data/zero-builds.md:31-32]()

---

## `zero graph`

`zero graph` inspects a program without building it. It reports the complete module graph — all source files, imports, import edges, public symbols, capability use, and static helper facts — without writing any artifact.

```sh
zero graph examples/systems-package
zero graph --json --target linux-musl-x64 examples/memory-package
```

### JSON output shape

| Field | Description |
|---|---|
| `schemaVersion` | Always `1` |
| `sourceFile` | Entry source file resolved |
| `sourceFiles` | All transitive source files |
| `imports` | Import module names |
| `importEdges` | `from`, `to`, `path` for each import edge |
| `symbols` | All symbols with `name`, `kind`, `public` |
| `functions` | Per-function metadata |
| `compileTime` | Same as `check` — static evaluation, sandbox facts |

`zero graph` includes the `compileTime` object, giving visibility into bounded `meta` evaluation results and sandbox denials without triggering code generation.

Test-internal symbols (names starting `__zero_test_`) are excluded from graph output, so the graph reflects only the public and private program surface.

Sources: [scripts/snapshot-command-contracts.mts:365-393](), [docs/articles/cli-reference.md:58-69](), [scripts/snapshot-command-contracts.mts:534-537]()

---

## `zero size`

`zero size` explains why an artifact is the size it is. It does not build a final artifact but does require the compiler to lower IR in order to compute size facts.

```sh
zero size --json examples/hello.0
zero size --json --profile tiny examples/hello.0
zero size --json --profile debug --target linux-musl-x64 examples/memory-primitives.0
```

### JSON output shape

| Field | Description |
|---|---|
| `profileSemantics` | `canonical`, `profileKey`, `profileBudget` |
| `profileCatalog` | All available profiles |
| `profileBudget` | Budget policies for the selected profile (e.g. `debugMetadataAllowed`, `helperBudgetPolicy`) |
| `sizeBreakdown` | `functions`, `sections`, `stdlibHelpers`, `imports`, `runtimeShims`, `debugMetadata` |
| `retentionReasons` | Why each retained function is included (`retainedBy` such as `"entry point"`) |
| `optimizationHints` | Actionable size hints with `id` |

Example: for `--profile debug`, `sizeBreakdown.sections` includes a `"debug-metadata"` entry, `profileBudget.debugMetadataAllowed` is `true`, and `optimizationHints` includes `"profile-debug-metadata"` to prompt switching to a smaller profile.

Sources: [scripts/snapshot-command-contracts.mts:674-685](), [docs/articles/cli-reference.md:65](), [skill-data/zero-builds.md:62-67]()

---

## `zero fix --plan` — Read-Only Repair Planning

`zero fix --plan --json` is the only form of `zero fix` supported by the current compiler. It reports candidate typed repairs but **never edits files**. This is a hard design constraint, not a missing feature.

```sh
zero fix --plan --json examples/broken.0
zero fix --plan --json --target linux-musl-x64 examples/target-fail.0
```

### Why plan-only matters for agents

Because `zero fix --plan` is read-only, an agent can call it safely on any source file at any time without risk of modifying the working tree. The output is a repair proposal; the agent decides whether and how to apply it. Never assume the fix plan edits files — verify the change independently after applying the suggestion.

Sources: [skill-data/zero-diagnostics.md:19](), [docs/articles/cli-reference.md:26](), [docs/articles/cli-reference.md:151]()

### Fix safety levels

Each proposed repair carries a `safety` label. The full set, from safest to least safe:

| Safety | Meaning |
|---|---|
| `format-only` | Formatting or trivia only; no semantic change |
| `behavior-preserving` | Intended not to change runtime behavior |
| `api-changing` | Signatures, exports, or call sites may change |
| `target-changing` | Target support or capability use may change |
| `requires-human-review` | The compiler cannot prove the edit is safe |

The `--plan` JSON includes a `safetyLevels` summary and per-fix `safety` fields. Apply only the edit you can justify from source and the fix plan. Treat `requires-human-review` as a planning hint, not an automatic patch.

Sources: [skill-data/zero-diagnostics.md:38-46]()

---

## `zero check` vs `zero fix --plan` — Decision Flow

```text
Source change
     │
     ▼
zero check --json          ← fast, no artifacts, always first
     │
     ├─ ok: true           → proceed to build/test
     │
     └─ ok: false
           │
           ├─ Inspect diagnostic fields (code, span, expected, actual, help)
           │
           ├─ zero explain <code>      ← structured explanation
           │
           └─ zero fix --plan --json  ← read-only: inspect safetyLevels,
                                         apply manually, then re-check
```

Sources: [skill-data/zero-agent.md:26-38](), [skill-data/zero-diagnostics.md:62-68]()

---

## Conformance and Native Tests: Sandbox Execution Requirement

Conformance tests and command contract snapshots emit native artifacts (compiled binaries). Running them locally requires either:

1. **Vercel Sandbox** — the default path via `pnpm run conformance` or `pnpm run command-contracts`, which uses `scripts/native-test-sandbox.mts` to provision an isolated remote environment.
2. **Explicit local opt-in** — setting `ZERO_NATIVE_TEST_ALLOW_LOCAL=1` before running `conformance/run.mjs` or `scripts/snapshot-command-contracts.mts` directly.

Without one of these, the scripts exit immediately with a clear error:

```
conformance emits native test artifacts; run `pnpm run conformance`
for Vercel Sandbox execution or set ZERO_NATIVE_TEST_ALLOW_LOCAL=1
to opt into local artifacts.
```

Sources: [conformance/run.mjs:7-10](), [scripts/snapshot-command-contracts.mts:8-11]()

### Why sandboxing is required

The conformance runner builds and executes native ELF and Mach-O binaries. Running untrusted compiled output locally is a security boundary; the sandbox provides an isolated Linux environment (default runtime `node24`, 16 vCPUs, 10-minute timeout) that does not copy native test binaries back to the developer's machine.

The sandbox flow:

```
Developer machine
  │
  ├─ Create source archive (excludes .git, .zero, node_modules, dist)
  │
  ├─ Upload to Vercel Sandbox (VERCEL_OIDC_TOKEN required)
  │
  │   Sandbox (linux/x64)
  │   ├─ Extract source
  │   ├─ pnpm install --frozen-lockfile
  │   ├─ make -C native/zero-c    (build compiler)
  │   └─ Run conformance/command-contracts
  │
  └─ Exit code and stdout/stderr streamed back; binaries stay in sandbox
```

Snapshots can be reused between runs via `ZERO_NATIVE_TEST_SANDBOX_SNAPSHOT_ID` to avoid re-provisioning. Set `ZERO_NATIVE_TEST_SANDBOX_REFRESH=1` to force a fresh environment.

Sources: [scripts/native-test-sandbox.mts:144-170](), [scripts/native-test-sandbox.mts:194-255](), [scripts/native-test-sandbox.mts:257-292]()

### Conformance platform detection

The conformance runner detects whether it can execute built binaries and skips execution (but not building) when the host platform doesn't match the target:

```js
const runnableDirectTarget =
  process.platform === "darwin" && process.arch === "arm64" ? "darwin-arm64" :
  process.platform === "linux" && process.arch === "x64" ? "linux-musl-x64" :
  null;
```

On an unsupported host, `assertDirectRuntimeRequired` still builds the artifact and verifies the JSON contract; it only skips the actual execution step. This means conformance is partial on non-Linux/non-ARM64-mac hosts — which is why the sandbox (always `linux/x64`) is the canonical execution environment.

Sources: [conformance/run.mjs:15-19](), [conformance/run.mjs:65-77]()

---

## JSON as the Agent-Stable Interface

The command contracts script (`scripts/snapshot-command-contracts.mts`) is the authoritative reference for what each command's JSON envelope must contain. It asserts every field that agents are permitted to rely on, and it runs in the sandbox on every CI pass. Key invariants enforced by the contracts:

- `schemaVersion` is always `1` on check, build, ship, size, and test JSON.
- `generatedCBytes` is `0` on every build, ship, and size report.
- `cBridgeFallback` is `false`.
- `releaseTargetContract.fallbackPolicy` is `"explicit-direct-never-c-bridge"`.
- Repeat builds produce bit-identical artifacts (`sha256File` comparison).
- `--json` flag on error commands still writes JSON to stdout (not stderr), with a non-zero exit code.
- ANSI control bytes are absent from error output (`hasAnsiControlBytes` check).

The final invariant matters for piping: even when a command fails, its `--json` output is machine-parseable JSON on stdout. Do not scrape stderr when `--json` is available.

Sources: [scripts/snapshot-command-contracts.mts:195-197](), [scripts/snapshot-command-contracts.mts:47-50](), [scripts/snapshot-command-contracts.mts:566-570]()

---

## Summary

The `zero` CLI's six core commands form a stable, layered surface: `check` gives the fastest feedback loop; `graph` adds module and capability visibility without artifacts; `size` explains artifact composition by profile; `run` closes the local development loop; `build` and `ship` produce versioned release artifacts with provenance contracts. `zero fix --plan` is strictly read-only — it proposes repairs but never patches files, making it safe to call from any automated context. Conformance and native test suites must run inside Vercel Sandbox (or with an explicit local override) because they build and execute native binaries. Pass `--json` on every command that supports it; the `generatedCBytes === 0` invariant is the single most important signal that an artifact was produced by the conformant direct emitter.

Sources: [docs/articles/cli-reference.md:1-159](), [scripts/snapshot-command-contracts.mts:195-217]()

---

## 05. Diagnostics & Fix Plans — Structured Error Triage

> How zerolang surfaces compiler errors as machine-readable JSON with stable codes (NAM003, TYP009, TAR002, …), source spans, fixSafety levels, and repair ids. The agent-repair loop: check --json → explain <code> → fix --plan --json → apply the narrowest safe patch. Covers all fixSafety categories and when requires-human-review must block automation.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/05-diagnostics-fix-plans-structured-error-triage.md
- Generated: 2026-05-21T23:19:14.673Z

### Source Files

- `skill-data/zero-diagnostics.md`
- `scripts/agent-repair-demo.mts`
- `native/zero-c/src/checker.c`
- `conformance/diagnostics`
- `conformance/provenance-surface.json`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [skill-data/zero-diagnostics.md](skill-data/zero-diagnostics.md)
- [scripts/agent-repair-demo.mts](scripts/agent-repair-demo.mts)
- [native/zero-c/src/main.c](native/zero-c/src/main.c)
- [conformance/diagnostics/unknown-name.expected.json](conformance/diagnostics/unknown-name.expected.json)
- [conformance/diagnostics/unknown-name.0](conformance/diagnostics/unknown-name.0)
- [conformance/provenance-surface.json](conformance/provenance-surface.json)
- [scripts/snapshot-command-contracts.mts](scripts/snapshot-command-contracts.mts)
- [scripts/zls.mts](scripts/zls.mts)
- [examples/agent-repair-demo/broken.0](examples/agent-repair-demo/broken.0)
</details>

# Diagnostics & Fix Plans — Structured Error Triage

Zerolang surfaces every compiler error — parse failures, type mismatches, import cycles, target capability gaps — as machine-readable JSON with stable alphanumeric codes, precise source spans, and attached repair metadata. This design makes the diagnostic stream the primary protocol between the compiler and any automated consumer: an editor plugin, a CI gate, or an autonomous coding agent. Human-readable prose is a fallback, not the source of truth.

The repair pipeline extends this contract one step further. After a check failure the agent can call `zero fix --plan --json` to receive a structured fix plan that classifies every candidate repair by its safety impact, pairs it with a `fixSafety` label, and explicitly marks cases that require human judgment before automation is allowed to write a file. This page explains the full flow from raw error output to safe patch application.

---

## Diagnostic JSON Shape

When any `zero` subcommand is run with `--json`, failures are returned to stdout as a self-describing envelope. The schema is versioned at `schemaVersion: 1`. The following fields are present on every diagnostic object inside the `diagnostics` array:

| Field | Type | Description |
|---|---|---|
| `code` | string | Stable alphanumeric code such as `NAM003` or `TYP009` |
| `severity` | string | Always `"error"` for check failures |
| `message` | string | Short human-readable summary |
| `path` | string | Source file path |
| `line` | number | 1-based line number |
| `column` | number | 1-based column number |
| `length` | number | Token width in characters |
| `expected` | string | Structured expected value (type, target, etc.) |
| `actual` | string | Structured actual value found |
| `help` | string | Concise next-action hint |
| `fixSafety` | string | Safety label for automated repair |
| `repair` | object | `{ id, summary }` — repair identifier and prose summary |
| `related` | array | Additional spans or cross-file facts |

Optional fields also appear for specific diagnostic classes:

- `borrowTrace` — borrow-checker diagnostics include the active borrow root, its binding, and the conflict location.
- `backendBlocker` — backend-blocked diagnostics include the backend name and the compilation stage that failed.

The JSON is assembled in `native/zero-c/src/main.c` inside `print_check_json` and `append_fix_plan_diagnostic`, which write each field in a defined order so downstream parsers can rely on stable key presence.

Sources: [native/zero-c/src/main.c:3130-3172]()

---

## Stable Diagnostic Codes

Diagnostic codes are derived from a numeric internal code via a single switch in `diag_code()`. The mapping is stable: the same internal code always yields the same string code across compiler versions.

```c
// native/zero-c/src/main.c:80-162 (excerpt)
case 3003: return "NAM003";   // unknown identifier
case 3010: return "TYP009";   // mutable storage required
case 6002: return "TAR002";   // capability unavailable for target
case 7001: return "IMP001";   // unknown package-local import
case 9001: return "PKG001";   // local dep path lacks zero.json
```

### Code Namespaces

| Prefix | Domain |
|---|---|
| `PAR` | Parser / syntax |
| `NAM` | Name resolution |
| `TYP` | Type system |
| `ERR` | Error/fallibility handling |
| `BOR` | Borrow checker |
| `OWN` | Ownership |
| `IMP` | Import resolution |
| `PKG` | Package graph |
| `TAR` | Target / capability |
| `BLD` | Build / backend |
| `STD` | Standard library contract |
| `IFC` | Interface constraints |
| `SHM`, `RCV` | Shape methods, receiver methods |
| `PUB`, `FLD` | Public API, field completeness |

The `explain` command exposes full documentation for any code in either prose or JSON:

```sh
zero explain TYP009
zero explain --json TYP009
```

The JSON form returns `code`, `title`, `description`, `rationale`, `help`, `bad`, `good`, and the same `repair` object that appears inside a diagnostic. This lets an agent fetch authoritative guidance before applying a patch without parsing terminal output.

Sources: [native/zero-c/src/main.c:80-163](), [native/zero-c/src/main.c:2740-2820]()

---

## Common Codes Reference

The following codes appear most frequently in agent triage workflows and are all covered by the embedded skill data:

| Code | Meaning | Default Repair ID |
|---|---|---|
| `NAM003` | Unknown identifier | `declare-missing-symbol` |
| `TYP009` | Mutable storage required (immutable binding passed to mutable API) | `make-binding-mutable` |
| `TAR001` | Unknown target name | `choose-target-with-required-capability` |
| `TAR002` | Capability unavailable for selected target | `choose-target-with-required-capability` |
| `IMP001` | Unknown package-local import | `fix-import-path` |
| `IMP002` | Package-local import cycle | `break-import-cycle` |
| `PKG001` | Local dependency path lacks `zero.json` | `fix-package-dependency-path` |
| `PKG002` | Package dependency cycle | `break-package-dependency-cycle` |
| `PKG003` | One package name resolves to conflicting versions | `choose-one-package-version` |
| `PKG004` | Selected target not supported by a dependency | `choose-target-compatible-package` |
| `BLD003` | Removed backend flag (`--emit c`) | `use-direct-emitter` |
| `ERR001` | `raises` annotation missing | `add-raises-or-rescue` |
| `ERR003` | Unchecked fallible call | `check-or-rescue-fallible-call` |
| `BOR001` | Borrow conflict | `end-conflicting-borrow` |
| `OWN001` | Use after move | `avoid-use-after-move` |

Sources: [skill-data/zero-diagnostics.md:49-68](), [native/zero-c/src/main.c:2591-2640]()

---

## Fix Safety Levels

Every diagnostic carries a `fixSafety` label. The same label is repeated in the `fixes` array of a `zero fix --plan --json` response. The compiler assigns safety at code-registration time inside `diag_fix_safety()`:

```c
// native/zero-c/src/main.c:2541-2588
static const char *diag_fix_safety(int code) {
  switch (code) {
    case 7001: case 7002: case 6002: case 9001: ...
      return "requires-human-review";
    case 1001: case 1002: case 1003:
      return "api-changing";
    case 3010: case 3011: case 3012: ... case 3049:
      return "behavior-preserving";
    default:
      return "requires-human-review";
  }
}
```

The fix plan envelope always enumerates the complete ordered list in `safetyLevels`:

```json
"safetyLevels": [
  "format-only",
  "behavior-preserving",
  "api-changing",
  "target-changing",
  "requires-human-review"
]
```

### Safety Category Definitions

| Category | Meaning | Can Automate? |
|---|---|---|
| `format-only` | Whitespace or trivia only; no semantic effect | Yes, safely |
| `behavior-preserving` | Compiler-verifiable: runtime behavior is unchanged | Yes, after span inspection |
| `api-changing` | Signatures, exports, or call sites change | Only with caller impact analysis |
| `target-changing` | Target support or capability use changes | Only after target matrix review |
| `requires-human-review` | Compiler cannot prove the edit is safe | Block automation; require explicit approval |

The default for unknown codes is `requires-human-review`, so any unrecognized diagnostic conservatively blocks automation.

Sources: [native/zero-c/src/main.c:2541-2588](), [native/zero-c/src/main.c:3214]()

---

## Fix Plan JSON: `zero fix --plan --json`

The fix plan command produces a separate JSON envelope from `zero check`. Its top-level fields are:

| Field | Value |
|---|---|
| `schemaVersion` | `1` |
| `ok` | `false` if any diagnostic is present |
| `mode` | `"plan"` |
| `appliesEdits` | `false` — the plan command never writes files |
| `safetyLevels` | Ordered enumeration of all safety categories |
| `input` | Source file path |
| `selfHostRepairPolicy` | Compiler's own policy for unsupported features and C-bridge fallback |
| `diagnostics` | Same diagnostic objects as in `zero check --json` |
| `fixes` | Array of fix entries: `{ id, diagnosticCode, safety, summary, appliesEdits }` |

The `fixes` array is the canonical list of recommended repairs. Each entry pairs a repair `id` with the `diagnosticCode` that triggered it, so an agent can join across `diagnostics` and `fixes` by code.

The `selfHostRepairPolicy` field signals the compiler's own migration constraints:

```json
"selfHostRepairPolicy": {
  "unsupportedFeatureSafety": "requires-human-review",
  "compatibilityFallback": "removed",
  "directFallback": "never-c-bridge"
}
```

This policy means the compiler never automatically falls back to C bridging; any such path requires human review.

Sources: [native/zero-c/src/main.c:3208-3230]()

---

## The Agent-Repair Loop

The canonical four-step loop demonstrated in `scripts/agent-repair-demo.mts` is:

```
check --json → explain <code> --json → fix --plan --json → inspect span → patch
```

```mermaid
sequenceDiagram
    participant Agent
    participant zero as zero CLI
    participant Source as Source File

    Agent->>zero: check --json <file>
    zero-->>Agent: { ok: false, diagnostics: [{ code, fixSafety, repair, line, column }] }
    Agent->>zero: explain --json <code>
    zero-->>Agent: { code, title, rationale, repair: { id, summary }, bad, good }
    Agent->>zero: fix --plan --json <file>
    zero-->>Agent: { mode: "plan", appliesEdits: false, fixes: [{ id, safety, summary }] }
    Agent->>Source: Read span at (line, column, length)
    Agent->>Source: Apply narrowest safe patch
    Agent->>zero: check --json <file>
    zero-->>Agent: { ok: true, diagnostics: [] }
```

### Step-by-Step

**1. Run `zero check --json`**

Parse the `diagnostics` array. Read `code`, `fixSafety`, `repair.id`, `line`, `column`, and `length`. Do not scrape stderr text; the JSON stream is the authoritative output.

**2. Run `zero explain --json <code>`**

Get the authoritative rationale and a concrete `bad`/`good` example pair for the code. This step is especially important before any broad refactor because the explain output confirms the compiler's intended fix direction.

**3. Run `zero fix --plan --json`**

Retrieve the structured fix plan. Read `fixes[].safety` and check it against the automation policy:
- `behavior-preserving` → inspect the span; apply if the edit matches the repair summary
- `api-changing` → check call sites; human sign-off recommended
- `requires-human-review` → block automation; surface to human reviewer

**4. Apply the narrowest safe patch**

Read only the source span identified by `(path, line, column, length)`. Apply only the edit that the repair summary describes. Re-run `zero check --json` to verify `ok: true` and `diagnostics: []`.

The demo in `scripts/agent-repair-demo.mts` shows this loop concretely for `TYP009` / `make-binding-mutable`:

```typescript
// scripts/agent-repair-demo.mts:23-38
const check = zeroJson(["check", "--json", workFile], true);
assert.equal(check.diagnostics[0].code, "TYP009");
assert.equal(check.diagnostics[0].repair.id, "make-binding-mutable");

const explain = zeroJson(["explain", "--json", "TYP009"]);
assert.equal(explain.repair.id, "make-binding-mutable");

const plan = zeroJson(["fix", "--plan", "--json", workFile]);
assert.equal(plan.mode, "plan");
assert.equal(plan.appliesEdits, false);
assert.equal(plan.fixes[0].id, "make-binding-mutable");

// Narrowest patch: only the one binding declaration changes
const fixedSource = brokenSource.replace("    let dst: [4]u8", "    let mut dst: [4]u8");
```

Sources: [scripts/agent-repair-demo.mts:23-41]()

---

## When `requires-human-review` Must Block Automation

The `requires-human-review` label is the compiler's explicit stop signal. It is assigned by default for unknown codes and explicitly for:

- All **import** and **package graph** errors (`IMP001`, `IMP002`, `PKG001`–`PKG004`): the fix involves structural decisions about module boundaries and version resolution that require human intent.
- **Target capability** errors (`TAR002`): moving code behind a target-specific entry point is an architectural change.
- **C interop** errors (`CIMP003`): configuring a target C dependency is environment-specific.
- **Build flag removal** (`BLD002`): migration off a removed flag may require pipeline changes.

The ZLS language server adapter in `scripts/zls.mts` propagates this label directly into the LSP code action payload:

```typescript
// scripts/zls.mts:293-301
.filter((diagnostic) => diagnostic.data?.repair?.id)
.map((diagnostic) => ({
  title: diagnostic.data.repair.summary ?? `Repair ${diagnostic.code}`,
  kind: "quickfix",
  data: {
    id: diagnostic.data.repair.id,
    safety: diagnostic.data.fixSafety ?? "requires-human-review",
  },
}))
```

The default when `fixSafety` is absent is `requires-human-review`, so the system is conservatively safe: unknown or missing safety labels never silently authorize automation.

Sources: [scripts/zls.mts:293-301](), [native/zero-c/src/main.c:2541-2588]()

---

## `diagnostic_can_apply_edits`: The Only Automatic Write Gate

The compiler contains exactly one code path that will actually write a file: `diagnostic_can_apply_edits`. It is gated to a single condition:

```c
// native/zero-c/src/main.c:3293-3294
static bool diagnostic_can_apply_edits(const ZDiag *diag) {
  return diag && diag->code == 3010 &&
         strcmp(diag_fix_safety(diag->code), "behavior-preserving") == 0;
}
```

Only `TYP009` (`make-binding-mutable`, internal code 3010) with a confirmed `behavior-preserving` safety label can pass this gate. All other repairs remain in plan-only mode (`appliesEdits: false`). This means `zero fix --plan --json` never edits files; only `zero fix --apply` can, and only for this one code.

Sources: [native/zero-c/src/main.c:3293-3319]()

---

## Conformance Test Coverage

The conformance suite verifies that every known diagnostic code produces the expected `fixSafety` and `repair.id` fields. The snapshot contract test in `scripts/snapshot-command-contracts.mts` runs `zero check --json` against a curated fixture for each code and asserts:

```typescript
// scripts/snapshot-command-contracts.mts:1213-1222
assert.equal(typeof diagnostic.fixSafety, "string");
assert.equal(typeof diagnostic.repair.id, "string");
assert.equal(typeof diagnostic.expected, "string");
assert.equal(typeof diagnostic.actual, "string");
assert.equal(Array.isArray(diagnostic.related), true);
```

The fixture list covers `PAR100`, `NAM003`, `IMP001`, `TYP009`, `BOR001`, `ERR001`–`ERR003`, `ABI001`, `PUB001`, `IFC002`, `SHM001`, `RCV001`, `RCV002`, and more. This ensures the JSON contract is regression-tested for every code namespace.

The `conformance/diagnostics/` directory additionally stores per-code expected JSON blobs. For example, the `NAM003` fixture confirms `fixSafety: "local-edit"` in its expected output:

```json
// conformance/diagnostics/unknown-name.expected.json
{
  "schemaVersion": 1,
  "code": "NAM003",
  "title": "Unknown identifier",
  "fixSafety": "local-edit",
  "defaultOutput": "plain-text-no-ansi"
}
```

Sources: [scripts/snapshot-command-contracts.mts:1185-1228](), [conformance/diagnostics/unknown-name.expected.json:1-7]()

---

## Summary

Zerolang's diagnostic system is built around a single invariant: every compiler error is addressable by a stable code, locatable by a precise span, and annotatable with a machine-readable safety label. The agent-repair loop — `check --json` → `explain --json` → `fix --plan --json` → apply the narrowest patch → `check --json` — is the sanctioned automation path. The `fixSafety` field is the primary gate: `behavior-preserving` permits narrow automated edits; `requires-human-review` blocks them unconditionally, with a conservative default of blocking for any code the compiler has not explicitly classified. The only file-writing path in the entire compiler is `diagnostic_can_apply_edits`, which is restricted to a single code under a confirmed safe label.

Sources: [native/zero-c/src/main.c:3293-3294]()

---

## 06. Packages & Modules — zero.json, Imports, and Dependency Resolution

> How multi-file Zero projects are structured: the zero.json manifest schema (package, targets, dependencies), how use-imports resolve from src/ (including mod.0 fallback), local path dependencies requiring zero.json, and the deterministic lock written to .zero/package-locks/. Key failure modes: IMP001 (unknown import), PKG001 (missing manifest), IMP002/PKG002 (cycles).

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/06-packages-modules-zero.json-imports-and-dependency-resolution.md
- Generated: 2026-05-21T23:15:39.396Z

### Source Files

- `skill-data/zero-packages.md`
- `examples/std-platform.0`
- `conformance/packages`
- `native/zero-c/src/call_resolve.c`
- `scripts/provenance-guardrails.mts`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [skill-data/zero-packages.md](skill-data/zero-packages.md)
- [docs/articles/package-manifest.md](docs/articles/package-manifest.md)
- [docs/articles/diagnostics.md](docs/articles/diagnostics.md)
- [native/zero-c/src/fs.c](native/zero-c/src/fs.c)
- [native/zero-c/src/main.c](native/zero-c/src/main.c)
- [conformance/packages/dep-app/zero.json](conformance/packages/dep-app/zero.json)
- [conformance/packages/cycle-a/zero.json](conformance/packages/cycle-a/zero.json)
- [conformance/packages/conflict-app/zero.json](conformance/packages/conflict-app/zero.json)
- [conformance/packages/target-incompatible-app/zero.json](conformance/packages/target-incompatible-app/zero.json)
- [conformance/packages/test-app/src/main.0](conformance/packages/test-app/src/main.0)
- [examples/std-platform.0](examples/std-platform.0)
- [scripts/provenance-guardrails.mts](scripts/provenance-guardrails.mts)
</details>

# Packages & Modules — zero.json, Imports, and Dependency Resolution

Zero organizes multi-file programs into _packages_: directories rooted at a `zero.json` manifest. The manifest declares the package identity, executable targets, and dependency references. The compiler resolves all intra-package module imports from the `src/` tree, resolves local path dependencies recursively, and writes a deterministic dependency fingerprint into `.zero/package-locks/` before any compilation step. Understanding this flow is essential for authoring, debugging, and tooling Zero projects—the same model governs both standalone programs and library packages shared across a repository.

This page covers the full lifecycle: the `zero.json` schema, how `use`-imports resolve to `src/` files (including the `mod.0` directory convention), how local path dependencies are validated and linked, what the lock file contains and why, and the complete set of diagnostic codes that fire when any of these invariants are violated.

---

## The zero.json Manifest

Every package is identified by a `zero.json` file at its root. Passing either the package directory or the manifest path itself to any `zero` command is equivalent—the compiler detects both forms.

```json
{
  "package": { "name": "hello", "version": "0.1.0", "license": "MIT" },
  "targets": { "cli": { "kind": "exe", "main": "src/main.0" } },
  "dependencies": {
    "local-tools": { "path": "../local-tools", "version": "0.1.0" },
    "registry-tools": "1.2.3"
  },
  "profiles": {
    "dev":     { "inherits": "dev" },
    "release": { "inherits": "release" }
  }
}
```
Sources: [docs/articles/package-manifest.md:1-19]()

### Schema Fields

| Field | Required | Description |
|---|---|---|
| `package.name` | Yes | Package identity string used in dependency graphs and lock files |
| `package.version` | Yes | SemVer string; must match what dependents declare |
| `package.license` | No | Optional SPDX license identifier |
| `targets.<name>.kind` | Yes | Only `"exe"` is supported in the native bootstrap compiler |
| `targets.<name>.main` | Yes | Entry-point file path relative to the package root (e.g., `"src/main.0"`) |
| `dependencies.<alias>` | No | Object with `path` + `version` for local; bare version string for registry metadata |
| `profiles` | No | Named build profiles (`dev`, `release`) that inherit built-in defaults |

The `targets` field can be named anything (by convention `"cli"` for executables). The `kind` field under a target controls what the compiler emits. The `main` field is the file the resolver uses as the entry point from which all `use`-imports are discovered.

Sources: [native/zero-c/src/fs.c:1343-1358](), [docs/articles/package-manifest.md:1-19]()

---

## Module Imports: use-import Resolution

Within a package, modules are imported using the `use` keyword. The compiler scans every source file for `use` lines, translates the module name into a file path relative to `src/`, and recursively loads the referenced module.

### Resolution Algorithm

Given `use config.parser` in any source file, the resolver performs these two steps in order:

1. **Direct file**: translate dots to slashes and append `.0` → look for `src/config/parser.0`
2. **Directory module (mod.0 fallback)**: strip `.0`, treat as a directory name → look for `src/config/parser/mod.0`

If neither path exists, the resolver emits `IMP001`.

```
use helpers        →  src/helpers.0            (or src/helpers/mod.0)
use config.parser  →  src/config/parser.0      (or src/config/parser/mod.0)
```

Sources: [native/zero-c/src/fs.c:428-451](), [skill-data/zero-packages.md:43-45]()

The exact C implementation that performs this lookup:

```c
// native/zero-c/src/fs.c:428-451
static char *module_path_to_source(const char *src_root, const char *module_name) {
  // 1. Try src/<module/path>.0
  ...
  if (file_exists(file_path)) return file_path;
  // 2. Try src/<module/path>/mod.0
  char *mod_path = join_path(dir_path, "mod.0");
  if (file_exists(mod_path)) return mod_path;
  return NULL;
}
```

### Standard Library Imports

`use` lines whose module name begins with `std.` are treated specially: they are skipped by the local file resolver and handed to the standard library built into the compiler. They never generate `IMP001` even if no matching file exists in `src/`.

```c
// native/zero-c/src/fs.c:703-705
if (strncmp(module_name, "std.", 4) == 0) {
    free(module_name);
} else {
    // resolve local module ...
}
```

Sources: [native/zero-c/src/fs.c:700-715]()

### Import Syntax

The current compiler recognizes two forms, but only `use` is idiomatic:

```zero
use helpers          // idiomatic
import helpers       // legacy form, still parsed but not recommended
```

Sources: [native/zero-c/src/fs.c:685-701]()

### Module Name-to-Symbol Mapping

The reverse mapping (file path → module name visible to the program) replaces path separators with dots and strips the `.0` extension (and trailing `/mod` for directory modules). So `src/config/parser.0` presents its public symbols under `config.parser`.

Sources: [native/zero-c/src/fs.c:396-411]()

---

## Dependency Declaration and Resolution

### Local Path Dependencies

Local dependencies must be declared with both `path` and `version`. The `path` must point to a directory that contains a valid `zero.json`.

```json
{
  "dependencies": {
    "dep-lib": { "path": "../dep-lib", "version": "0.1.0" }
  }
}
```
Sources: [conformance/packages/dep-app/zero.json:1-8]()

The resolver performs a depth-first traversal of all dependencies. For each local path entry:

1. It resolves the path relative to the current manifest and checks whether `zero.json` exists there (`PKG001` if not).
2. It checks whether this manifest path is already on the resolution stack (`PKG002` if a cycle is detected).
3. It parses the target manifest and compares the resolved `package.version` against the requested version (`PKG003` if they differ).
4. It recurses into that dependency's own dependencies.

```c
// native/zero-c/src/fs.c:1249-1303
static bool resolve_manifest_dependencies(...) {
  for (each dep in manifest->dependencies) {
    if (!dep->path) { push registry-reference; continue; }
    if (!file_exists(dep_manifest_path)) → PKG001;
    if (dependency_stack_contains(dep_manifest_path)) → PKG002;
    if (version mismatch) → PKG003;
    if (name already seen with different version) → PKG003;
    recurse into dep...
  }
}
```

Sources: [native/zero-c/src/fs.c:1249-1305]()

### Registry Metadata Dependencies

A bare version string dependency (e.g., `"registry-tools": "1.2.3"`) is recorded as `registry-reference` metadata without fetching or resolving any code. The resolver does not perform remote fetches.

Sources: [native/zero-c/src/fs.c:1252-1254](), [skill-data/zero-packages.md:63-70]()

---

## The Lock File: .zero/package-locks/

After a successful dependency resolution, the compiler writes a deterministic lock file under `.zero/package-locks/<hash>.lock.json`. The filename is the 64-bit FNV-1a hash of the resolved dependency graph, formatted as a 16-character hex string.

### Lock File Format

```json
{
  "schemaVersion": 1,
  "format": "zero-lock-v1",
  "package": { "name": "dep-app", "version": "0.1.0" },
  "dependencyGraphHash": "a1b2c3d4e5f60001",
  "dependencies": [
    {
      "name": "dep-lib",
      "version": "0.1.0",
      "resolvedName": "dep-lib",
      "resolvedVersion": "0.1.0",
      "status": "path-resolved",
      "fingerprint": "0123456789abcdef"
    }
  ]
}
```

Sources: [native/zero-c/src/fs.c:1209-1247]()

The `status` field records how each dependency was resolved:

| Status | Meaning |
|---|---|
| `path-resolved` | Local directory confirmed to contain `zero.json` |
| `registry-reference` | Bare version string; no code fetched |

### Lock File as a Cache Key Input

The lock file hash feeds directly into the compiler cache key. The full set of cache-key inputs is:

| Input | Role |
|---|---|
| Compiler version (`ZERO_VERSION`) | Invalidate on toolchain upgrades |
| Target name | Separate caches per build target |
| Package version | From `package.version` in `zero.json` |
| `dependencyGraphHash` | FNV-1a hash of all resolved dep fingerprints |
| `manifestHash` | FNV-1a hash of the `zero.json` text |
| `lockfileHash` | FNV-1a hash of the written lock file |

```c
// native/zero-c/src/main.c:2144-2156
zbuf_append(buf, "{\"cacheKeyInputs\":{\"compilerVersion\":");
// ... target, packageVersion, dependencyGraphHash, manifestHash, lockfileHash
zbuf_append(buf, ",\"invalidationReasons\":[\"source changed\",\"manifest changed\",
  \"dependency graph changed\",\"target changed\",\"profile changed\",
  \"compiler version changed\"]");
```

Sources: [native/zero-c/src/main.c:2144-2157]()

---

## Resolution Flow Diagram

```text
zero check/build <package-dir>
        │
        ▼
  Read zero.json
  (manifest_path, main_path)
        │
        ├──► Resolve dependencies (DFS)
        │         │
        │         ├── local path dep → verify zero.json exists (PKG001)
        │         ├── cycle check on stack (PKG002)
        │         ├── version match check (PKG003)
        │         └── recurse into transitive deps
        │
        ├──► Write .zero/package-locks/<graph-hash>.lock.json
        │
        └──► Resolve use-imports (src/ tree)
                  │
                  ├── use std.* → built-in, skip file lookup
                  ├── use <name> → src/<name>.0
                  │                  └── fallback: src/<name>/mod.0
                  │                  └── missing → IMP001
                  └── cycle in import graph → IMP002
```

---

## Diagnostic Codes Reference

All package and import resolution failures use stable diagnostic codes. Every package-level failure uses `fixSafety: "requires-human-review"` because the correct repair may change package topology.

### Import Diagnostics

| Code | Trigger | Repair |
|---|---|---|
| `IMP001` | `use <name>` resolves to neither `src/<name>.0` nor `src/<name>/mod.0` | Create the module file, fix its path, or remove the `use` |
| `IMP002` | A chain of `use`-imports forms a cycle within the package | Break the cycle by extracting shared declarations into a third module |
| `IMP003` | Two imported modules both export the same public name | Rename one export or choose only one of the two modules |

Sources: [native/zero-c/src/fs.c:706-733](), [docs/articles/diagnostics.md:107-109]()

### Package Diagnostics

| Code | Trigger | Repair |
|---|---|---|
| `PKG001` | A local dependency `path` does not point to a directory with `zero.json` | Fix the path or create the dependency package |
| `PKG002` | Package A depends on B, which depends back on A (or any cycle) | Move shared code into a separate third package |
| `PKG003` | The same package name is resolved to two different versions in the graph | Choose one version for the whole graph |
| `PKG004` | The dependency's `targets` list in the manifest does not include the selected build target | Select a compatible target or restrict the dependency behind target-specific metadata |

Sources: [native/zero-c/src/fs.c:1257-1294](), [native/zero-c/src/main.c:8911-8923](), [docs/articles/diagnostics.md:258-266]()

### PKG004 in Detail

When a dependency declaration includes a `targets` array (see the conformance fixture below), the compiler validates that the current build target is listed before proceeding:

```json
// conformance/packages/target-incompatible-app/zero.json
{
  "dependencies": {
    "target-webbits": {
      "path": "../target-webbits",
      "version": "0.1.0",
      "targets": ["win32-x64.exe"]
    }
  }
}
```

If you build this for `darwin-arm64`, the compiler fires `PKG004` because `darwin-arm64` is not in `["win32-x64.exe"]`. An empty or absent `targets` array means the dependency is target-neutral.

Sources: [conformance/packages/target-incompatible-app/zero.json:1-10](), [native/zero-c/src/main.c:2091-2094]()

---

## Introspection Commands

```sh
# Inspect the resolved module graph, import edges, lockfile path, and cache key inputs
zero graph --json <package>

# Inspect public API symbols for each module
zero doc --json <package>

# Run type-checking with full JSON diagnostics
zero check --json <package>

# Get a human-readable explanation of any diagnostic code
zero explain PKG001
zero explain IMP002
```

The `graph` command reports `package.dependencies`, `package.lockfile`, `package.resolver`, and `packageCache.cacheKeyInputs`—the last item exposes the inputs that, when changed, trigger a rebuild.

Sources: [skill-data/zero-packages.md:74-84](), [docs/articles/package-manifest.md:31-50]()

---

## Common Repairs Quick Reference

| Symptom | Code | Fix |
|---|---|---|
| `use helpers` fails to resolve | `IMP001` | Create `src/helpers.0` or `src/helpers/mod.0` |
| Two modules import each other | `IMP002` | Extract shared declarations to a new `src/shared.0` |
| `../missing-lib` has no `zero.json` | `PKG001` | Create the package or correct the `path` |
| cycle-a depends on cycle-b and vice versa | `PKG002` | Extract the common interface into a third package |
| `shared-v1` (0.1.0) and `shared-v2` (0.2.0) both named `shared` | `PKG003` | Alias to different names or pin to one version |
| Building for `darwin-arm64` but dep only lists `win32-x64.exe` | `PKG004` | Remove the `targets` restriction or choose a compatible build target |

The deterministic lock file written to `.zero/package-locks/` means that if you see a cache miss after changing a dependency or its version, the old lock file is simply superseded—no manual cleanup is required. The compiler writes a new one keyed to the new graph hash on every successful resolve.

Sources: [native/zero-c/src/fs.c:1209-1247](), [docs/articles/package-manifest.md:38-47]()

---

## 07. Standard Library & Targets — Capabilities, Modules, and Cross-Build Boundaries

> The two-tier stdlib model: target-neutral modules (std.mem, std.codec, std.parse, std.json, std.io) available everywhere versus hosted-capability modules (std.fs, std.net, std.http, std.args, World.out) that are target-gated and rejected at compile time on non-host targets. How to inspect target facts before cross-building and choose the right build profile.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/07-standard-library-targets-capabilities-modules-and-cross-build-boundaries.md
- Generated: 2026-05-21T23:17:18.259Z

### Source Files

- `skill-data/zero-stdlib.md`
- `skill-data/zero-builds.md`
- `native/zero-c/src/target.c`
- `native/zero-c/targets`
- `examples/std-http-request.0`
- `examples/std-path-io.0`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [skill-data/zero-stdlib.md](skill-data/zero-stdlib.md)
- [skill-data/zero-builds.md](skill-data/zero-builds.md)
- [native/zero-c/src/target.c](native/zero-c/src/target.c)
- [native/zero-c/targets/targets.manifest](native/zero-c/targets/targets.manifest)
- [examples/std-http-request.0](examples/std-http-request.0)
- [examples/std-http-json.0](examples/std-http-json.0)
- [examples/std-path-io.0](examples/std-path-io.0)
- [examples/std-platform.0](examples/std-platform.0)
</details>

# Standard Library & Targets — Capabilities, Modules, and Cross-Build Boundaries

Zero's standard library is divided into two tiers: **target-neutral modules** that compile on every supported platform, and **hosted-capability modules** whose availability is gated by the target's declared capability set. This distinction is enforced at compile time — using a hosted module on a target that lacks the required capability is a hard build error, not a runtime failure. Understanding this boundary is essential for writing portable Zero programs and for making correct cross-build decisions.

This page covers all stdlib modules, the capability system that gates them, how target facts are stored and queried in the toolchain, how build profiles interact with targets, and the workflow for safely inspecting a target before committing to a cross-build.

---

## The Two-Tier Standard Library Model

### Tier 1 — Target-Neutral Modules

These modules have no dependency on host OS services. They operate purely over caller-owned memory and are available on every build target:

| Module | Responsibilities |
|---|---|
| `std.mem` | Spans, copy, fill, length, safe indexed `get`, fixed-buffer allocation, byte buffers, caller-owned vectors |
| `std.codec` | Byte reads, varint sizing, CRC helpers, byte checksums |
| `std.parse` | ASCII predicates, decimal integer parsers returning `Maybe<T>` |
| `std.json` | Explicit-buffer JSON parsing and string writing helpers |
| `std.io` | Buffered reader/writer surfaces over caller-owned storage |
| `std.time` | Duration construction and conversion helpers |
| `std.rand` | Explicit deterministic random sources |
| `std.crypto` | Small hash and byte-oriented crypto helpers |
| `std.path` | Path normalization, relative path resolution, basename/dirname/extension |

All of these follow the same invariant: no ambient heap, no global logger, no hidden system calls. When a function can fail or return nothing, it returns `Maybe<T>` rather than panicking.

Sources: [skill-data/zero-stdlib.md:19-28]()

### Tier 2 — Hosted-Capability Modules

These modules require an OS capability that not every build target provides. Using them on a target that lacks the relevant capability is rejected at compile time with a target diagnostic:

| Module | Required Capability | What It Provides |
|---|---|---|
| `std.args` | `args` | Process argument access |
| `std.env` | `env` | Process environment variables |
| `std.fs` | `fs` | Hosted filesystem handles (`Fs`, `owned<File>`) |
| `std.net` | `net` | Bootstrap network handles |
| `std.http` | `net` | HTTP request/response helpers built on `std.net` |
| `std.proc` | `proc` | Process spawning and exit-code inspection |
| `World.out`, `World.err` | `stdio` | Program output and error streams |

Sources: [skill-data/zero-stdlib.md:33-43]()

---

## The Capability System

### How Capabilities Are Declared

Every build target declares an explicit list of capabilities in `native/zero-c/targets/targets.manifest`. The manifest is TOML-like, with one `[[target]]` stanza per supported target:

```toml
[[target]]
name = "darwin-arm64"
capabilities = ["memory", "stdio", "args", "env", "fs", "time", "rand", "net", "proc"]

[[target]]
name = "linux-musl-arm64"
capabilities = ["memory", "stdio", "time", "rand"]
```

The full known capability vocabulary — as enumerated in the toolchain — is: `memory`, `stdio`, `args`, `env`, `fs`, `net`, `proc`, `time`, `rand`, `web`.

Sources: [native/zero-c/targets/targets.manifest:1-55](), [native/zero-c/src/target.c:299-310]()

### Per-Target Capability Matrix

| Target | memory | stdio | args | env | fs | net | proc | time | rand |
|---|---|---|---|---|---|---|---|---|---|
| `darwin-arm64` | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ | ✓ |
| `darwin-x64` | ✓ | ✓ | — | — | ✓ | — | — | ✓ | ✓ |
| `linux-musl-x64` | ✓ | ✓ | ✓ | ✓ | ✓ | — | — | ✓ | ✓ |
| `linux-musl-arm64` | ✓ | ✓ | — | — | — | — | — | ✓ | ✓ |
| `linux-x64` | ✓ | ✓ | — | — | — | — | — | ✓ | ✓ |
| `linux-arm64` | ✓ | ✓ | — | — | — | — | — | ✓ | ✓ |
| `win32-x64.exe` | ✓ | ✓ | — | — | — | — | — | ✓ | ✓ |
| `win32-arm64.exe` | ✓ | ✓ | — | — | — | — | — | ✓ | ✓ |

Sources: [native/zero-c/targets/targets.manifest:1-112]()

### How the Toolchain Enforces Capabilities

The runtime function `z_target_has_capability` is the gatekeeper. It checks the target's `capabilities` string from the manifest. There is one special case: for the host target specifically, the five capability strings `args`, `env`, `fs`, `net`, and `proc` are additionally considered always available if the host reports itself as the active target — even before the manifest string is consulted. For all other targets, the manifest is authoritative.

```c
static bool host_capability_available(const char *capability) {
  return capability &&
         (strcmp(capability, "args") == 0 ||
          strcmp(capability, "env") == 0 ||
          strcmp(capability, "fs") == 0 ||
          strcmp(capability, "net") == 0 ||
          strcmp(capability, "proc") == 0);
}

bool z_target_has_capability(const ZTargetInfo *target, const char *capability) {
  if (z_target_is_host(target) && host_capability_available(capability)) return true;
  return target->capabilities && strstr(target->capabilities, capability) != NULL;
}
```

Sources: [native/zero-c/src/target.c:247-261]()

---

## Inspecting Target Facts Before Cross-Building

Before using a hosted module in code that will be cross-compiled, inspect the target's capability set explicitly. The toolchain provides three commands for this:

```sh
# List all known targets and their capabilities
zero targets

# Check whether a specific input compiles cleanly for the target
zero check --target linux-musl-x64 examples/memory-package

# Emit a full JSON fact report (capabilities, toolchain, libc, backend status)
zero graph --json --target linux-musl-x64 examples/memory-package
```

The JSON output includes `capabilityFacts` — an array of objects with `name`, `available` (boolean), and `source` fields — alongside toolchain, libc, sysroot, and direct-backend fields. Use this to verify all required capabilities before cross-building.

Sources: [skill-data/zero-stdlib.md:44-50](), [skill-data/zero-builds.md:48-54]()

### Sysroot Requirements

Some targets use `libcMode = "sysroot"`, meaning the toolchain needs a platform SDK rooted at the path given by a target-specific environment variable. The variable name is derived from the target's Zig target string, uppercased and with non-alphanumeric characters replaced by `_`, prefixed with `ZERO_SYSROOT_`. For example, `x86_64-linux-gnu` → `ZERO_SYSROOT_X86_64_LINUX_GNU`. If the sysroot is missing, the build fails explicitly with a diagnostic naming the missing variable.

Sources: [native/zero-c/src/target.c:285-296]()

---

## Build Profiles and Their Interaction With Targets

Profiles control optimization level, debug metadata, and artifact size, and are orthogonal to the target selection:

| Profile | Typical Use |
|---|---|
| `debug` | Maximum diagnostics, no optimization |
| `dev` | Fast builds for local iteration |
| `release-fast` | Speed-optimized release |
| `release-small` | Size-optimized release |
| `tiny` | Minimum-footprint artifacts |
| `audit` | Security-oriented build pass |

Profile selection does **not** expand the capability set. A `release-fast` build for `linux-musl-arm64` still has no `args`, `env`, `fs`, `net`, or `proc` capability.

```sh
zero build --profile release-small examples/hello.0
zero size --json --profile tiny examples/hello.0
```

The `zero size --json` command explains retained functions, sections, literals, runtime shims, and optimization hints — useful for understanding what hosted shims were linked and what can be trimmed.

Sources: [skill-data/zero-builds.md:58-67]()

---

## Code Patterns for Each Tier

### Target-Neutral: Memory and IO

Path normalization, buffered IO, and span copies are all caller-stack operations with no syscalls:

```zero
// examples/std-path-io.0
let mut path_buf: [32]u8 = [0, ...]
let normalized = std.path.normalize(path_buf, "src//./main.0/")
let mut reader_buf: [8]u8 = [0, ...]
let reader = std.io.bufferedReader(reader_buf)
```

Sources: [examples/std-path-io.0:3-9]()

### Hosted: Filesystem Access

File handles are explicit and owned. There is no ambient filesystem:

```zero
let fs = std.fs.host()
let mut file: owned<File> = check std.fs.createOrRaise(fs, ".zero/out/log.txt")
check std.fs.writeAllOrRaise(&mut file, std.mem.span("hello\n"))
```

Sources: [skill-data/zero-stdlib.md:88-93]()

### Hosted: HTTP with `net` Capability

`std.net` and `std.http` both require the `net` capability. The HTTP runtime is further restricted to host targets with an audited provider (currently libcurl on macOS and Linux ELF64):

```zero
// examples/std-http-request.0
let net = std.net.host()
let client = std.http.client(net)
let mut response: [512]u8 = [0_u8; 512]
let result = std.http.fetch(client, std.mem.span(maybe_request.value), response, std.time.ms(5000))
if std.http.resultOk(result) {
    check world.out.write("http request ok\n")
}
```

Sources: [examples/std-http-request.0:8-14]()

### Hosted: Platform Utilities

`std.proc`, `std.rand`, `std.crypto`, and `std.time` cover process spawning, randomness, hashing, and timing. Note that `std.time` and `std.rand` are available on all targets (they appear in every target's capability list), while `std.proc` requires the `proc` capability and is only available on `darwin-arm64` as of the current manifest:

```zero
// examples/std-platform.0
let status = std.proc.spawn("zero-noop")
let hash = std.crypto.hash32(std.mem.span("message"))
let mut rng = std.rand.seed(7_u32)
```

Sources: [examples/std-platform.0:9-11]()

---

## Capability Boundary Diagram

```text
┌────────────────────────────────────────────────────────────────┐
│                    Zero Standard Library                        │
│                                                                 │
│  ┌──────────────────────────────┐   ┌───────────────────────┐  │
│  │   TARGET-NEUTRAL (always OK) │   │ HOSTED (capability-   │  │
│  │                              │   │ gated, compile error  │  │
│  │  std.mem    std.codec        │   │ if target lacks cap)  │  │
│  │  std.parse  std.json         │   │                       │  │
│  │  std.io     std.path         │   │  std.args  → "args"   │  │
│  │  std.time   std.rand         │   │  std.env   → "env"    │  │
│  │  std.crypto                  │   │  std.fs    → "fs"     │  │
│  └──────────────────────────────┘   │  std.net   → "net"    │  │
│                                     │  std.http  → "net"    │  │
│                                     │  std.proc  → "proc"   │  │
│                                     │  World.out → "stdio"  │  │
│                                     └───────────────────────┘  │
│                                                                 │
│  Capability set is declared per-target in targets.manifest      │
│  and enforced by z_target_has_capability() at compile time.     │
└────────────────────────────────────────────────────────────────┘
```

---

## Cross-Build Decision Workflow

```text
1. Identify required stdlib modules for your program
      │
      ▼
2. Determine which capabilities they require
   (see table above: "Hosted-Capability Modules")
      │
      ▼
3. Run: zero targets
   → Verify the cross-target lists those capabilities
      │
      ├─ Capability present → proceed
      │
      └─ Capability absent  → refactor to target-neutral modules,
                               or restrict to a capable target
      │
      ▼
4. Run: zero check --target <target> <input>
   → Compile-time confirmation (no surprises at link time)
      │
      ▼
5. Run: zero build --profile <profile> --target <target> <input>
   → Check for sysroot env-var errors (libcMode = "sysroot" targets)
      │
      ▼
6. Optionally: zero ship --json --target <target> <input>
   → Produces artifact with size, hash, and target contract facts
```

---

## Failure Modes

| Failure | Cause | Fix |
|---|---|---|
| Compile error naming a hosted module | Target lacks the required capability | Use `zero targets` to find a capable target, or remove the hosted import |
| `BLD003` error | Old backend flag (`--emit c` or similar removed flag) | Remove the flag; use `--emit exe` or `--emit obj` |
| Missing `ZERO_SYSROOT_*` | Cross-target with `libcMode = "sysroot"` but env var unset | Set the correct sysroot path for the target |
| `http runtime unsupported` on cross-target | `std.http` requires host + `net` + direct ELF/Mach-O emitter | Run HTTP on the host target only; `darwin-arm64` and `linux-musl-x64` qualify |
| `zero check` passes but `zero build` fails | Sysroot missing or toolchain not found | Run `zero doctor --json` to enumerate host readiness |

Sources: [skill-data/zero-builds.md:80-86](), [native/zero-c/src/target.c:409-432]()

---

## Summary

Zero's standard library enforces a hard boundary between modules that are always available and modules that are gated by target capabilities declared in `native/zero-c/targets/targets.manifest`. Target-neutral modules (`std.mem`, `std.codec`, `std.parse`, `std.json`, `std.io`, `std.path`, `std.time`, `std.rand`, `std.crypto`) are safe to use in any cross-build. Hosted modules (`std.args`, `std.env`, `std.fs`, `std.net`, `std.http`, `std.proc`, `World.out`) are rejected at compile time on any target whose manifest entry does not include the required capability string. The correct cross-build workflow is: inspect target facts with `zero targets` and `zero check --json`, choose a profile appropriate to the deployment goal, and treat a clean `zero check` as the authoritative signal before committing to a `zero build` or `zero ship`.

Sources: [native/zero-c/src/target.c:256-261]()

---

## 08. Safe-Change Map — Agent Repair Loop, Testing, and Invariants to Preserve

> The closing synthesis: the complete agent edit loop (read → change → check --json → explain → fix --plan → patch → test), the conformance fixture contract (pass/ vs fail/), xfail test markers, the no-legacy-C invariant to guard in every build output, safe-change rules for the pre-1 unstable surface, and the sandbox requirement that keeps native test artifacts off developer machines by default.

- Page Markdown: https://grok-wiki.com/public/wiki/vercel-labs-zerolang-9ab46b2a38e0/pages/08-safe-change-map-agent-repair-loop-testing-and-invariants-to-preserve.md
- Generated: 2026-05-21T23:15:41.250Z

### Source Files

- `skill-data/zero-testing.md`
- `skill-data/zero-agent.md`
- `scripts/agent-repair-demo.mts`
- `conformance/run.mjs`
- `conformance/native`
- `AGENTS.md`

<details>
<summary>Relevant source files</summary>
The following files were used as context for generating this wiki page:

- [skill-data/zero-testing.md](skill-data/zero-testing.md)
- [skill-data/zero-agent.md](skill-data/zero-agent.md)
- [scripts/agent-repair-demo.mts](scripts/agent-repair-demo.mts)
- [conformance/run.mjs](conformance/run.mjs)
- [conformance/native/pass/test-blocks.0](conformance/native/pass/test-blocks.0)
- [conformance/native/pass/test-expected-fail.0](conformance/native/pass/test-expected-fail.0)
- [conformance/native/fail/test-unexpected-pass.0](conformance/native/fail/test-unexpected-pass.0)
- [examples/agent-repair-demo/broken.0](examples/agent-repair-demo/broken.0)
- [AGENTS.md](AGENTS.md)
- [package.json](package.json)
- [scripts/native-test-sandbox.mts](scripts/native-test-sandbox.mts)
</details>

# Safe-Change Map — Agent Repair Loop, Testing, and Invariants to Preserve

This page is the operational synthesis for contributors and agents working inside the Zero repository. It documents the precise edit loop agents must follow, how conformance fixtures are structured and interpreted, which runtime signals constitute a hard invariant violation, the policy for evolving a pre-1 unstable surface, and the sandbox guardrail that prevents native test artifacts from escaping onto developer machines.

Understanding these rules together is critical: a change that individually passes a type check, satisfies one conformance fixture, and emits no C—yet leaves an `xfail` marker on a bug that is now fixed—is still a bad change. The rules are a system. Breaking any one of them leaves the repository in a state that misleads the next agent or contributor.

---

## The Agent Repair Loop

The repair loop is a deterministic, observable cycle built around machine-readable CLI output. It is not a prose-driven workflow; every step produces structured JSON that the next step consumes.

```text
┌─────────────┐
│  Read files │  .0 sources, zero.json, tests, examples
└──────┬──────┘
       │
       ▼
┌─────────────┐
│  Edit       │  Smallest change that satisfies the request
└──────┬──────┘
       │
       ▼
┌──────────────────────────┐
│  zero check --json <file> │  Structured diagnostics, repair hints
└──────┬───────────────────┘
       │ diagnostic present?
       ├─── yes ──►  zero explain <code>        (structured explanation)
       │              zero fix --plan --json     (repair plan, no writes)
       │              apply patch manually
       │              loop back to check
       │
       └─── no  ──►
┌───────────────────────────────┐
│  zero test --json <file-or-pkg>│  Verify behavior is correct
└───────────────────────────────┘
```

Sources: [skill-data/zero-agent.md:26-45](), [scripts/agent-repair-demo.mts:23-60]()

### Step-by-Step Details

| Step | Command | What to look for |
|------|---------|-----------------|
| Check | `zero check --json <input>` | `ok`, `diagnostics[].code`, `diagnostics[].repair.id` |
| Explain | `zero explain --json <code>` | `code`, `repair.id`, human description |
| Plan | `zero fix --plan --json <input>` | `mode: "plan"`, `appliesEdits: false`, `fixes[].id` |
| Patch | Edit source manually per plan | N/A — fix is applied by the agent, not the CLI |
| Test | `zero test --json <input>` | `ok`, `passedTests`, `failedTests`, `unexpectedPasses` |

**Critical constraint:** `zero fix --plan` returns a plan object but never writes files itself (`appliesEdits: false`). The agent is responsible for applying the indicated patch. This is intentional: Zero's design keeps the compiler as an oracle, not an autonomous mutator.

The live demo encodes this contract exactly:

```typescript
// scripts/agent-repair-demo.mts:32-37
const plan = zeroJson(["fix", "--plan", "--json", workFile]);
assert.equal(plan.mode, "plan");
assert.equal(plan.appliesEdits, false);
assert.equal(plan.fixes[0].id, "make-binding-mutable");

const fixedSource = brokenSource.replace("    let dst: [4]u8", "    let mut dst: [4]u8");
writeFileSync(workFile, fixedSource);
```

The broken source (`examples/agent-repair-demo/broken.0`) assigns through an immutable binding—triggering diagnostic `TYP009`. The repair id `make-binding-mutable` names the fix without performing it.

### Focused Commands for Narrow Loops

Use `--filter` during an edit loop to avoid running unrelated tests:

```sh
zero test --json --filter addition conformance/native/pass/test-blocks.0
```

The filter matches test names by substring. When the surface under change is wider, run the containing package or fixture after narrowing work is done. Sources: [skill-data/zero-testing.md:31-36]()

---

## Conformance Fixture Contract: `pass/` vs `fail/`

Conformance fixtures live under `conformance/native/` and are split into two directories with opposing semantics.

```text
conformance/native/
├── pass/   ← fixtures that MUST compile, check, or test-pass successfully
└── fail/   ← fixtures that MUST produce a diagnostic or test-failure
```

### `pass/` Fixtures

A fixture in `conformance/native/pass/` is a language-level contract that:
- `zero check --json` returns `ok: true`
- `zero test --json` returns `ok: true`
- No legacy C is emitted (see no-legacy-C invariant below)

Example — the canonical test-block fixture:

```zero
// conformance/native/pass/test-blocks.0
fun add(a: i32, b: i32) -> i32 {
    return a + b
}

test "addition works" {
    expect(add(2, 3) == 5)
}
```

The conformance runner asserts:

```javascript
// conformance/run.mjs:2404-2415
const zeroTestRun = await execFileAsync(zero, ["test", "conformance/native/pass/test-blocks.0"]);
assert.equal(zeroTestRun.stdout, "1 test(s) ok\n");

const zeroTestJsonBody = JSON.parse(zeroTestJsonRun.stdout);
assert.equal(zeroTestJsonBody.ok, true);
assert.equal(zeroTestJsonBody.fixtures.snapshotKey, "zero-test-direct-frontend-v1");
assert.equal(zeroTestJsonBody.results[0].status, "passed");
```

### `fail/` Fixtures

A fixture in `conformance/native/fail/` is a negative contract — the compiler or test runner **must** reject it. Placing a fixture in `fail/` is a commitment that it should never silently succeed.

The conformance harness verifies this by asserting a non-zero exit code or a specific diagnostic code. For example, the `assertCheckTimeoutOrDiagnostic` helper asserts that either the check times out or one of the expected diagnostic codes appears in output:

```javascript
// conformance/run.mjs:102-113
async function assertCheckTimeoutOrDiagnostic(fixture, expectedCodes) {
  const result = await execFileAsync(zero, ["check", "--json", fixture], { timeout: checkTimeoutMs }).catch((error) => error);
  if (result.killed || result.signal) {
    assert.equal(result.killed, true);
    return { timedOut: true };
  }
  assert.notEqual(result.code, 0);
  const body = JSON.parse(result.stdout);
  const code = body.diagnostics?.[0]?.code;
  assert(expectedCodes.includes(code), `expected one of ${expectedCodes.join(", ")}, got ${code}`);
}
```

---

## `xfail` Test Markers

Expected-failure markers let the test suite document known incomplete behavior without blocking CI. They are a contract, not a workaround—and they require active maintenance.

### Marker Forms

Any of the following prefixes in a test name activates xfail semantics:

| Marker | Example |
|--------|---------|
| `xfail:` | `test "xfail: pending parser edge case" { ... }` |
| `expected fail:` | `test "expected fail: borrow edge" { ... }` |
| `[xfail]` | `test "[xfail] unresolved variant" { ... }` |

Sources: [skill-data/zero-testing.md:54-68]()

### Semantics

An `xfail` test must **fail** its `expect(...)` assertion for the overall `zero test` command to succeed. If the underlying code is fixed and the test starts passing, the command fails with `unexpectedPasses`.

```javascript
// conformance/run.mjs:2426-2438
const zeroExpectedFailBody = JSON.parse(zeroExpectedFailJsonRun.stdout);
assert.equal(zeroExpectedFailBody.ok, true);
assert.equal(zeroExpectedFailBody.expectedFailures, 1);
assert.equal(zeroExpectedFailBody.results[0].status, "expected-fail");

const zeroUnexpectedPassBody = JSON.parse(zeroUnexpectedPassJsonRun.stdout);
assert.equal(zeroUnexpectedPassBody.ok, false);
assert.equal(zeroUnexpectedPassBody.unexpectedPasses, 1);
assert.equal(zeroUnexpectedPassBody.results[0].status, "unexpected-pass");
```

The fixtures that encode these two behaviors live at:
- `conformance/native/pass/test-expected-fail.0` — `expect(false)` under `xfail:`, command succeeds
- `conformance/native/fail/test-unexpected-pass.0` — `expect(true)` under `xfail:`, command fails

### Agent Rule

> Do not leave an `xfail` marker on a fixed bug.

When a fix causes an xfail test to pass, the marker must be removed in the same change. Leaving it causes the opposite failure mode—the test suite now rejects the fix as an `unexpectedPasses` violation. Sources: [skill-data/zero-testing.md:74-76]()

---

## The No-Legacy-C Invariant

Every build output—check, test, size, graph, doc, mem, build—must demonstrate that Zero's native frontend is in use, not a C code generation fallback. This is enforced uniformly as three JSON field assertions.

### The `assertNoC` Contract

The agent repair demo codifies this as a reusable assertion:

```typescript
// scripts/agent-repair-demo.mts:48-52
function assertNoC(body) {
  assert.equal(body.generatedCBytes ?? 0, 0);
  assert.equal(body.cBridgeFallback ?? body.selfHostRouting?.cBridge?.required ?? false, false);
  assert.notEqual(body.legacy, true);
}
```

| JSON Field | Assertion | Meaning |
|------------|-----------|---------|
| `generatedCBytes` | `=== 0` | No C source was emitted during compilation |
| `cBridgeFallback` | `=== false` | The C bridge codepath was not activated |
| `legacy` | `!== true` | Legacy backend was not selected |

### Applied to Every Output Mode

The demo applies `assertNoC` to every output surface after any significant change:

```typescript
// scripts/agent-repair-demo.mts:54-84
assertNoC(projectCheck);   // zero check --json
assertNoC(projectTest);    // zero test --json
assertNoC(projectDoc);     // zero doc --json
assertNoC(projectSize);    // zero size --json
assertNoC(projectMem);     // zero mem --json
assertNoC(projectRelease); // zero build --json --release
```

Additionally, the conformance runner's native fixture helpers each assert `generatedCBytes === 0` and `legacy === false` independently for build outputs:

```javascript
// conformance/run.mjs:54-55
assert.equal(body.generatedCBytes, 0);
assert.equal(body.legacy, false);
```

A failing `assertNoC` in any output indicates a regression: some code path activated the C bridge or legacy backend that should no longer be reachable. This is a hard blocker, not a warning.

---

## Safe-Change Rules for the Pre-1 Unstable Surface

Zero is explicitly pre-1.0 and agent-first. The contributor charter (`AGENTS.md`) establishes specific policies for what kinds of changes are safe and expected.

### Breaking Changes Are Acceptable

> Breaking changes are acceptable when they move the language, standard library, compiler, or tooling closer to [agent-first] goals.
>
> — [AGENTS.md:9-11]()

This means contributors must not defend backward compatibility as a first principle. Compatibility shims, migration layers, and legacy paths are explicitly out of scope unless they are required for the current agent-facing design.

### What Counts as Safe

A change is safe if it satisfies all of:

1. **Conformance fixtures pass** — both `pass/` (must succeed) and `fail/` (must fail) hold their contracts.
2. **No C emitted** — `assertNoC` passes across check, test, build, and related outputs.
3. **xfail markers are current** — no marker left on a now-passing test; no unmarked test that should be expected-fail.
4. **Examples remain runnable** — `examples/` stay copyable and executable from the repository root.
5. **Docs describe current behavior** — no release-planning language, internal diary details, or stale API descriptions.

### What Is Not Safe

| Pattern | Why It Fails |
|---------|--------------|
| Adding a compatibility shim for removed syntax | Contradicts "prefer the clearer agent-facing design" |
| Leaving an `xfail` on a fixed test | Causes `unexpectedPasses` failure in CI |
| Writing examples that require a legacy flag | Violates "keep examples runnable" |
| Emitting generated C in build output | Hard invariant (`generatedCBytes > 0`) |
| Publishing docs with validation-report language | Violates public docs policy |

Sources: [AGENTS.md:6-16](), [AGENTS.md:41-43]()

---

## The Sandbox Requirement

Native test artifacts (compiled binaries, object files, ELF executables) must not land on developer machines by default. The conformance runner enforces this with a hard startup guard.

### The Gate

```javascript
// conformance/run.mjs:7-10
if (process.env.ZERO_NATIVE_TEST_SANDBOX !== "1" && process.env.ZERO_NATIVE_TEST_ALLOW_LOCAL !== "1") {
  console.error("conformance emits native test artifacts; run `pnpm run conformance` for Vercel Sandbox execution or set ZERO_NATIVE_TEST_ALLOW_LOCAL=1 to opt into local artifacts.");
  process.exit(1);
}
```

The script exits immediately unless one of two environment variables is set. This is not a warning—it is an exit(1) with no recovery path inside the script.

### The Two Modes

| Mode | How to activate | What runs |
|------|----------------|-----------|
| **Sandbox (default)** | `pnpm run conformance` | Uploads a source archive to Vercel Sandbox; native binaries never touch the local machine |
| **Local (explicit opt-in)** | `ZERO_NATIVE_TEST_ALLOW_LOCAL=1 pnpm run conformance:local` | Runs on the local machine; developer accepts artifact presence |

The `package.json` scripts make sandbox the default for all three related test commands:

```json
// package.json (lines 16, 22-25, 38-39)
"conformance":          "pnpm run conformance:sandbox",
"conformance:sandbox":  "node ... scripts/native-test-sandbox.mts -- pnpm run conformance:local",
"command-contracts":    "pnpm run command-contracts:sandbox",
"native:test":          "pnpm run native:test:sandbox",
"native:test:local":    "ZERO_NATIVE_TEST_ALLOW_LOCAL=1 bash scripts/test-native.sh",
```

The sandbox script (`scripts/native-test-sandbox.mts`) documents explicitly that it "never copies native test binaries back to the local machine."

### Why This Matters

Zero's security posture in `AGENTS.md` calls out that compiler crashes, malformed output, and unsafe runtime behavior should never reach production state. The sandbox requirement extends that philosophy to the test artifacts themselves—executables that might crash or misbehave are contained in ephemeral compute rather than persisted locally.

Sources: [conformance/run.mjs:7-10](), [package.json scripts](), [scripts/native-test-sandbox.mts:1-35](), [AGENTS.md:23-30]()

---

## Summary

The agent edit loop (`read → check --json → explain → fix --plan → patch → test`) is a closed, observable cycle where every transition produces structured JSON. The conformance fixture contract (`pass/` must succeed, `fail/` must fail) is a binary invariant—fixtures do not have gradations. The `xfail` marker system handles known incomplete behavior but demands active cleanup: a marker left on a fixed test breaks CI as surely as a broken test would. Every compiler output must satisfy `assertNoC` (`generatedCBytes === 0`, `cBridgeFallback === false`, `legacy !== true`) across all output modes; this is the no-legacy-C invariant that guards Zero's native frontend progress. The pre-1 unstable surface policy accepts breaking changes that advance agent-first goals and explicitly rejects compatibility shims. Finally, the sandbox gate in `conformance/run.mjs:7-10` ensures native test binaries stay in ephemeral compute by default, protecting local machines from experimental artifacts until the developer consciously opts in with `ZERO_NATIVE_TEST_ALLOW_LOCAL=1`.

---
