# Xo Language Specification (Core)

Status: draft 0.10 (2026-10-10; 0.9 released as toolchain v0.9.0, decision 0089)
Edition: 2026
Companion: `spec/std.md` (prelude methods and standard library surface)

Xo is a small, statically typed, garbage collected language for backends and
web services. It keeps what makes Go productive (few keywords, one obvious way,
fast builds, static binaries, a strong standard library, a compatibility
promise) and is designed so that an AI agent can write, check, build, and debug
programs for any OS with fast, structured, unambiguous feedback.

## Changes in 0.10

- **Assignment order** (5.2, decision 0140): an assignment computes its
  value before it reads the binding it stores into, so changes the value
  makes to that binding are kept (the native backends dropped them).
- **`for b in bytes`** (3.5, decision 0138): `Bytes` is iterable and
  yields each byte as a `UInt8`.
- **`while let`** (3.5, decision 0137): `while let Some(v) = expr { }`
  loops while a refutable pattern matches, and `while let v = opt` is
  the Option shorthand, as for `if let`.
- **Real-time functions** (6.5, decision 0133): `uses realtime` on a
  function declaration forbids allocation (`XO1401`), loops without a
  bound (`XO1402`), calls of functions that are not real-time, function
  values, and interface methods (`XO1403`), recursion (`XO1404`), and
  blocking (`XO1405`). Native builds defer the frees of a real-time
  section to its end and fault with `XO-F007` when it allocated.
- **Loop bounds** (3.5): `for x in xs bound N { }` and
  `while cond bound N { }`, a contract on any loop: a constant range
  longer than N is `XO1402`; otherwise starting iteration N + 1 faults
  with `XO-F006`. `bound` is a contextual word.

## Changes in 0.9

Meaning changes: std/http over TLS speaks HTTP/2 when both sides offer
it, on every backend (below), and
one variable passed to two `var` parameters of a call is an error
(`XO0503`, 5.2, decision 0111; the Go backend shared it, the native
backends reported XO1201). A value that holds a handle (`Mutex`,
`Chan`, `Task`, `Conn`, ...) or a function value is not comparable: `==`,
`Map` keys, `Set` elements, and `derive Eq`/`Hash` on it are `XO0404`
on every backend (2.9, decision 0112; the Go backend compared the
handles, the native backends reported XO1201). Native support grew
(`--no-gc`, 10.1):

- Interface types as values (2.4) build natively (decision 0100).
- Newtypes (2.8, decision 0097: construction, `e.0`, `Email(p)`, Debug
  text, equality, Map keys) and list patterns (3.5) build natively.
- Float32, `Secret[T]`, `Buf[T]`, order of `derive Ord` types, and the
  std value methods listed in decision 0101 build natively.
- `std/path`, `task.semaphore`, `task.once`, and `task.map_concurrent`
  build natively (std.md 7, 9).
- Every Fs method and attenuation, `Stdio.write`, and standard input
  reads (7.2, 7.5: canceled by a cancellation or a stop request) build
  natively (decision 0102).
- Error frames (11.1) are recorded natively and printed in main's error
  report (decision 0104).
- `std/time` builds natively (std.md 13, decision 0094), with the same
  per use zone data and output as the Go backend; so do
  `Duration.from_secs`, `scale`, `ceil_seconds`, and
  `Time.parse_rfc3339` (2.6).
- std/http serves and calls HTTPS on every backend (std.md 4.6, decisions
  0105 and 0106): `http.tls_cert`, `Server.with_tls`, `Client.with_roots`,
  and `TlsErr` are new; the native backends speak TLS with Mbed TLS, so an
  `https` URL is no longer `XO1201` there. Over TLS, servers and clients
  speak HTTP/2 when ALPN agrees on `h2` (decision 0121; the Go backend's
  server spoke HTTP/1.1 only before, its client already used HTTP/2);
  plain `http` stays HTTP/1.1.
- `use c "<header>" [link "<lib>"]... [as name] [uses ...]` calls C from
  native builds (4.5, decisions 0093 and 0116): bindings generated from
  the header (`xo bind --c`), scalars, Str, Bytes, flat structs, and
  opaque handles at the boundary, the effect `ffi` (6.1) unless the line
  narrows it, `XO1104` and `XO1105` for headers and names that do not
  bind, and `XO1201` on the Go backend.

## Changes in 0.8

From the spec audit of 2026-10-09 (`bench/results/spec-audit-2026-10-09.md`):
no meaning changes, the spec now says what is not implemented yet.
Method syntax stays receivers (decision 0090), `Map` keeps insertion
order (0091), error frames are lazy and always on (0092), and foreign
calls will be recorded at the boundary (0093); `std/time` is accepted as
specified in std.md 13 (decision 0094).

- `xo build --ci` exists: it fails before building on an unformatted file
  or a `dbg` left in code (10.1, 10.5, 11.4).
- 10.6 lists every diagnostic and fault code with its severity, kept equal
  to the toolchain's registry by a test. `XO1001` is reserved.
- `--json` is accepted by the commands that report results, not by every
  command; `xo test` takes `--cases=N`, not `--fuzz` (10.1).
- **[later]** markers for what is specified but not built: `xo why`,
  `xo debug`, the task line of error
  traces, spans and OpenTelemetry, SBOMs, `std/sys`, `OpenApi` and `Proto`
  derives.
- Modules (4.2, 4.3; decision 0086 steps 1 and 2): `require <path>
  <version>` of Xo modules with minimal version selection, git fetch into
  a read only module cache, `xo.lock` hashes verified by every command,
  `xo get`, `xo mod tidy`, `xo vendor`, `--offline`, and `xo.work`
  workspaces (`--workspace=off`). New codes `XO1301` to `XO1308` (10.6).
  An import from a module that is not required is `XO1306`.
- From the Codex r2 rerun (`bench/results/codex-xo-r2-2026-10-09.md`): a
  closure whose body never completes has result type `Never` when nothing
  else determines it (3.3); `x.display()` is callable on every `Display`
  type, derived and std errors included (std.md 1); several `match` arms
  on one line keep parsing, with a fix that splits them (`XO0175`).
- Effect ceilings and checked semver (4.2; decision 0086 steps 3 and 4):
  `require <path> <version> uses none` (or `uses net, fs.read`) in xo.mod
  and `use <dir> uses ...` in xo.work bound what a dependency's functions
  may declare; a call above the ceiling is `XO1309` at the call site.
  `xo get` prints the API and effect changes of every version change
  (`-u` upgrades every requirement). `xo publish [--check]` compares the
  exports and effects with the previous tag and rejects a breaking minor
  or patch release (`XO1310`, `XO1311`); it never tags or pushes.
  `xo init` writes xo.mod and gitignores xo.work. Module paths ending in
  `/vN` (N >= 2) are major version N of the same repository; the suffix
  is skipped for the local name (4.1).
- `std/time` (std.md 13, decision 0094) is implemented on the Go backend:
  zones with per use embedded data (`--tzdata=system` reads the host
  database), civil dates and times with explicit daylight saving gaps and
  overlaps, and layouts with named fields. A constant layout is checked at
  compile time: `XO0420` for an unknown field (10.6), `XO0177` for date
  patterns of other languages. Native backends report it as `XO1201`
  (planned).
- Import direction rules (4.2, 10.3; decision 0095): `deny <pkg> ->
  <pkg>, ...` lines in xo.mod, with `/...` patterns; a forbidden `use` is
  the new error `XO1312` at that line, a rule naming no package is
  `XO1301`. `xo query imports` and `xo query deps` list the `use` lines.
- `std/time` errors display as sentences (std.md 13.5): `ZoneErr` and
  `CivilErr` no longer derive `Display`.
- Newtypes (2.8; decision 0097): `e.0` reads (and on a `var` assigns) the
  underlying value, and the pattern `Email(p)` matches it, irrefutably
  when `p` is (`let Email(s) = e`). Go backend; native builds report
  `XO1201` as for every newtype (native support added in 0.9).
- `xo check --fix` (10.1, 10.2; decision 0096) applies every unambiguous
  machine fix, reformats the changed files, and reports what remains with
  the list of applied fixes; plain `xo check` stays read only. The MCP
  `check` tool takes `fix`.
- From the second pass of the spec audit: `x.debug()` is callable on
  every value, as std.md 1 says every type implements `Debug` (it was
  `XO0601` on concrete types); reproducible builds are stated for the same
  source directory (12.1), which a test now checks.
- Modules in repository subdirectories (4.2; decision 0086): the module
  `github.com/acme/tools/cli` in `cli/` with tags `cli/vX.Y.Z`; fetch,
  version selection, xo.lock, vendoring, `xo get`, and `xo publish`
  handle the prefix. Nested modules are left out of the module around
  them. `XO1311` now also reports a subdirectory module whose path does
  not end with its directory.
- Go calls are recorded (11.3; decision 0093 stage 1), replacing "Go calls
  are not recorded": calls with boundary values only are answered from
  the trace by `xo replay`; calls with a handle or a callback run again
  and are reported (``re executed foreign call `<name>` ``); pure
  functions are `replay rerun` (std's pure packages, and xo.mod lines
  `replay rerun go <path> [names]`, 4.4). Old traces still replay.
- Release builds (8, decision 0099): `xo build --release` compiles out
  `requires(debug)` and `ensures(debug)` on every backend and fails while
  a workspace overrides a required module (`XO1308`, formerly reported
  only by `--ci`). `--ci` does not imply `--release`; `xo run`, `xo test`,
  and plain `xo build` are debug builds.
- Error frames are lazy (11.1, decision 0092, Go backend): a frame keeps
  copies of the parameter values and formats them, at most 64 bytes'
  worth, only when the trace is printed; output is unchanged. About 10
  times cheaper per propagated error with short parameters, 29 times
  with 90 byte ones, and independent of parameter size. Frames are on in
  release builds; `--release --error-args=false` drops the parameter
  values (`(...)`).
- Module proxies, checksum log, definition hashes (4.2, 4.3, 10.3;
  decisions 0124 to 0126, completing 0086): `XO_PROXY` and `XO_NOPROXY`
  with a read only proxy protocol and `xo mod proxy serve`; `XO_SUMDB`
  and `XO_NOSUMDB` check every first fetch against a signed Merkle log
  (new `XO1313`, `XO1314`; `XO1302` now also covers proxies); `xo query
  hash` and the definitions list of `xo publish`.

## Changes in 0.7

Decisions 0041 (Go packages) and 0042 (two phase shutdown).

- Request scoped arenas are on by default for HTTP handlers (5.5; decision
  0066): an implementation detail with no observable effect, off with
  `--no-arenas`.

- Go packages with `use go` (4.4).
- Two phase shutdown (7.5).
- `Mutex.with_read`, shared read only access (5.4, std.md 2.7; decision
  0063). A closure parameter declared `var` where the expected function
  type takes it by value is error `XO0401` (it changed a copy only).
- `BytesBuilder`, the move only builder of `Bytes` (5.3, std.md 2.7;
  decision 0078).
- Warning `XO0305` is not reported for a `_` arm that hides at least 8
  variants and more than half of the enum (3.5; decision 0080).
- A rune literal where an integer type is expected is its code point
  (1.6); `Int(r)`, `Rune(n)`, and `Rune.try(n)` convert between `Rune` and
  the integer types (2.1; decision 0079).
- `xo build --hybrid` compiles hot leaf loops to native assembly inside Go
  backend builds (10.1; decision 0084).
- The native backends check `ensures` and struct invariants and unwind
  faults (deferred code, scopes, Mutexes, `task.isolate`, a 500 from an
  HTTP handler) as the Go backend does (3.5, 8, 9.1; decision 0085).
- Habit diagnostics: error `XO0177` (syntax from another language), and
  habit notes and fixes on existing codes, for Go, Rust, TypeScript,
  Python, Swift, and Kotlin forms (10.2; decision 0087). No program that
  was accepted is rejected.
- `clock.sleep_or_cancel(d)` returns cancellation as `ClockErr.Canceled`
  instead of unwinding; reading standard input is a cancellation point,
  and a stop request ends it too (7.2, 7.5; decision 0088).

## Changes in 0.6

Driven by the independent replant of planted safety bugs
(`bench/results/planted-xo-2026-10-07.md`). Decision 0038.

- Reading a `Map` by index, or changing an element in place through it, is
  error `XO0410`; use `get`, `get_or_fault`, or `update`. `m[k] = v` stays (3.6).
- Warning `XO0305`: a `_` arm hides variants of an enum declared in the same
  module (3.5).
- Warning `XO0804`: a value read in one `Mutex.with` is used, or acted on, in a
  later `with` on the same mutex (5.4).
- Regression tests for shrunk contract fuzz failures go in the module's
  `regressions_test.xo`, not `testdata/regressions/` (9.2).
- Go packages with `use go` (4.4), with the `go` effect.
- `for x in ch` over channels (3.5, 7.3); `task.Once` and
  `task.map_concurrent_until` in std.
- Two phase shutdown: the first signal requests a stop (`task.stop_requested`,
  `task.stopping`), cancellation follows after a grace period (7.5).
- Std: `Map.get_or_fault`, hex and base64 on `Bytes`, `Time.from_unix_millis`,
  `Atomic.max`, `Range.to_list`, `Patch.apply_to`/`apply_opt`, and `std/path`.

## Changes in 0.5

Driven by the stage 2 benchmark (`bench/results/stage2-xo-check-2026-10-07.md`).
Decision 0037.

- XO0520: a mutated copy of a collection element must be stored again; reading
  it afterwards no longer counts (5.2).
- `is` may be combined with `&&` and `||` (3.5, 15).
- Each arm of a tail `if` or `match` is in return position (2.6).
- `testing` is usable in helper signatures in `_test.xo` files (9.2).

## Changes in 0.4

Driven by the P1 parser and checker, the planted bug benchmark, and the harder
tasks (`bench/results/planted-and-harder-2026-10-07.md`,
`bench/results/check-corpus-v0.3.md`). Decisions 0028 to 0035.

- Task groups that can be stored in structs, `os.tasks` for program lifetime
  work, `try_send`/`try_recv`, `task.map_concurrent` (7.1, std).
- Discarding a `Result` with `let _ =` is an error; use
  `.ignore_err("reason")` (2.6).
- Effect arguments of generic types may be omitted (6.2).
- `x is Pattern` tests a pattern (3.5). All compound assignments, tuple
  fields `t.0`, local `const`, qualified types in `from`, doc comments on
  fields and variants.
- XO0520 also covers `.or_err(...)?` copies and values mutated after being
  stored in a collection (5.2).
- `Mutex.with` keep and fail pattern documented (5.4).
- Grammar readings from the parser and typing readings from the checker are
  written into the relevant sections (1.3, 1.6, 2.1, 2.6, 2.10, 3.3, 7.1, 15).
- Streaming HTTP bodies, `Fs.write_only`, fuller `Set`, `testing.run_main`
  (std).

## Changes in 0.3

Driven by the second paper test (`bench/results/paper-2026-10-07-v0.2.md`).
Decisions 0021 to 0027.

- String interpolation is `${expr}`; `{` and `}` are literal in plain strings.
  Raw strings `r#"..."#` may contain quotes (1.6).
- `defer` blocks are shielded from cancellation (3.5, 7.2).
- XO0520 applies only to values copied out of a collection (5.2).
- `?` works on `Option` in functions returning an optional (2.6).
- A line starting with `.` continues the previous line (1.3).
- Implicit conversions are listed in one place: there are three (2.10).
- Block and loop typing, generic literal inference, qualified variant
  patterns, module local names, pure means no capability effects, `uses (...)`
  allowed on declarations, process signals (various sections).

## Changes in 0.2

Driven by the paper test in `bench/results/paper-2026-10-07.md`. Decisions
0011 to 0020 in `knowledgebase/decisions/`.

- Explicit failure with `return Err(e)` and tail `Err(e)` (2.6). New `Never`
  type (2.1).
- `Ok Err Some None` replace `ok err some none`. `result` and `old` are
  contextual inside `ensures` only (1.4, 2.5, 2.6).
- Full pattern syntax including `_`, literals, ranges, or patterns, nested
  patterns, positional and named variant fields (3.5).
- Unused binding rule relaxed for parameters and `_` names (3.1).
- Effect variables and effect subsumption (6.2). Multiple effects in a
  function type are parenthesized.
- Scope rules: background tasks, exit behavior, error propagation; `scope ...
  timeout` removed in favor of `task.with_timeout` (7).
- Cancellation is observable as a `Canceled` error from fallible blocking calls
  (7.2).
- `Mutex.with` signature and write back rule (5.4); lost update check `XO0520`
  and `Map.update` (5.2).
- Test blocks get an implicit `os: TestOs`; `std/test` renamed `std/testing` (9.2).
- Logging is ambient and untracked (6.1).
- Slicing with ranges, closure literal grammar, trailing commas, const struct
  literals, `{}` is always an empty map (1.6, 3.x, 15).

## 0. Design goals

1. **No guessing.** Every name resolves to exactly one definition, found by
   reading local code. No overloading, implicit conversions, inheritance,
   macros, `init()`, or hidden control flow.
2. **The compiler is an API.** All tool output is structured JSON with stable
   codes and machine-applicable fixes.
3. **Errors cannot be ignored, nil does not exist, matches are exhaustive.**
4. **Authority is explicit.** Side effects are declared in signatures and
   performed only through capability values passed down from `main`.
5. **Concurrency is structured.** No task outlives its scope. No shared
   mutable state without an explicit synchronized type.
6. **Every target is checked on every build.** No build tags.
7. **Every failure is reproducible.** Effects are recordable and replayable.
8. **Compatibility.** Code that builds under an edition keeps building under
   that edition forever. New editions ship with automatic rewriters.

### Staging

Each section is tagged with the phase in which it is implemented.

- **[P1]** Prototype: `xo` frontend written in Go, emits Go source, uses the Go
  toolchain for codegen, GC, scheduler, and cross compilation.
- **[P2]** Own runtime: deterministic scheduler, full record/replay, task tree.
- **[P3]** Own backend and content addressed code store.

The language semantics do not change between phases. Only implementation
quality and tooling depth change.

**[later]** marks a feature this draft specifies but the toolchain does not
implement yet, in a section otherwise implemented. The spec audit
(`bench/results/spec-audit-2026-10-09.md`) lists every such gap.

### Experimental sections

None. The five rules that were experimental through draft 0.6 (value
semantics, required `uses` on `pub` functions, no module declaration, block
scoped `defer`, enforced naming with `pub`) were accepted on 2026-10-08 from
the benchmark evidence (decisions 0003, 0004, 0006, 0009, 0010).

---

## 1. Lexical structure [P1]

### 1.1 Source files

- Encoding: UTF-8. Extension: `.xo`.
- A **module** is a directory. All `.xo` files in a directory belong to the
  same module and share one namespace. There is no module declaration in the
  file: the directory path is the module identity.
- `use` declarations are per file, as in Go.
- Files ending in `_test.xo` contain only tests and test helpers.

### 1.2 Comments

```
// line comment
/// doc comment, attaches to the next declaration (Markdown)
```

There are no block comments. Fenced `xo` code blocks inside doc comments are
rendered by `xo doc`; compiling and running them as examples under `xo test`
is planned ([P2]).

### 1.3 Statement termination

Newlines terminate statements, using Go's rule: a newline ends a statement if
the last token on the line is an identifier, literal, `)`, `]`, `}`, `?`, or one
of `return break continue`. Semicolons are not written by programmers and are
removed by `xo fmt`.

Exception: a line whose first token is `.` continues the previous line, so
method chains can be split:

```
http.json(429, {"error": "rate limited"})
  .with_header("Retry-After", "${secs}")
```

A line whose first token is a declaration clause (`uses`, `requires`,
`ensures`, `invariant`, `derive`), the `->` of a result type, `else` after a
`requires`, or the `{` that opens a function body, also continues the
declaration above it.

A line cannot start with a binary operator: after `a > 0` at the end of a
line, `|| b > 0` on the next would begin a new statement. End the first
line with the operator instead (`a > 0 ||`, then `b > 0`). A line
starting with `||`, `&&`, `??`, `|`, `^`, `+`, `/`, `%`, `<<`, `>>`, a
comparison, or a spaced `*` or `&` is error `XO0178`, whose fix moves the
operator up (decision 0136). `-`, `!`, `~`, and `..` start expressions,
so a line starting with them is a new statement.

Consequences: `} else {` and `} else if` must be on the same line as the
closing brace. A list that spans lines needs a trailing comma after the last
element. Trailing commas are allowed in every comma separated list.

### 1.4 Keywords (28)

```
as        break     const     continue  defer     else      ensures
enum      fn        for       if        in        interface invariant
let       match     pub       requires  return    scope     select
struct    test      type      use       uses      var       while
```

Predeclared identifiers (cannot be redefined at top level):

- Values and functions: `true false self target fault dbg`
- Variants: `Ok Err` (of `Result`), `Some None` (of `Option`), usable without a
  type prefix everywhere.
- Types: those in section 2.1 and the prelude interfaces in `spec/std.md`.
- In test blocks only: `os`, `expect`, `testing`.

Contextual words (meaningful only in one position, usable as identifiers
elsewhere): `derive` (after a type body), `from` (in an enum variant),
`with` (postfix struct update), `after` (in a `select` arm), `seed` (after a
test name), `result` and `old` (inside `ensures`), `is` (binary operator
position), `bound` (after a loop header's expression, 3.5), `realtime` (in
a declaration's uses clause, 6.5).

To keep parsing unambiguous, an unparenthesized struct literal may not appear
in an `if`, `while`, `for`, `match`, `requires`, or `ensures` expression (the
same rule as Go). Write `if (p == Point{x: 0, y: 0}) { ... }`.

### 1.5 Identifiers and naming

- Identifiers: `[A-Za-z_][A-Za-z0-9_]*`.
- Naming is enforced by the compiler (error `XO0101`), not left to linters:
  - Types, enum variants, interfaces: `UpperCamel`.
  - Functions, methods, variables, fields, modules: `snake_case`.
  - Constants: `UPPER_SNAKE`.
  - Effect variables: `snake_case`.
- Exported names are marked with `pub`, not by capitalization.

### 1.6 Literals

```
42  1_000_000  0xff  0b1010  0o755        // Int
3.14  1e-9                                // Float
true  false                               // Bool
'a'  '\n'  '\u{1F600}'                    // Rune
"hello ${name}, you have ${count + 1} items" // Str with interpolation
"GET /users/{id}"   "{\"a\": 1}"           // braces are literal
"price: \$5"                              // \$ is a literal $ before {
r"raw: no escapes, no interpolation \d+"
r#"raw, may contain "quotes": {"a": 1}"#
"""
multi line string, common leading indentation is removed
"""
b"raw bytes\x00"                          // Bytes, escapes but no interpolation
5s  250ms  10us  2m  1h                   // Duration
[1, 2, 3]   []                            // List
{"a": 1, "b": 2}   {}                     // Map
#{1, 2, 3}   #{}                          // Set
```

- `${expr}` interpolates any expression whose type implements `Display`.
  `Secret[T]` values render as `<redacted>`. `$` not followed by `{` is literal.
- Raw strings `r"..."` have no escapes and no interpolation. `r#"..."#` may
  also contain `"`, which makes JSON fixtures easy to write.
- An integer literal takes the integer type expected in context (`Int32`,
  `UInt8`, ...), defaulting to `Int`. It never becomes a `Float`; write `2.0`.
- A rune literal is a `Rune`, except where an integer type is expected (an
  annotation, a parameter, the other operand of an operator, a pattern on
  an integer, a `const` of an integer type): there it is that type's value
  of its code point, so byte level code reads `b[i] == '"'` and
  `match c { 'a'..='z' => ... }` on a `UInt8` (decision 0079). The code
  point must fit the type; `Int8` and `UInt8` take only ASCII (`'é'` is two
  bytes in UTF-8, so it is never one byte): error `XO0401` otherwise.
- Duration literals are an integer followed by `ns`, `us`, `ms`, `s`, `m`,
  or `h`. `1.5s` is not a literal; write `1500ms`.
- Escapes in `Str` and `Bytes`: `\n \t \r \0 \\ \" \' \$ \xHH \u{...}`.
  In `Str`, `\xHH` is at most `\x7f`.
- `{}` in expression position is always an empty `Map`. An empty block used as
  a value is written `()`.

---

## 2. Types [P1]

### 2.1 Builtin types

| Type | Description |
|---|---|
| `Bool` | `true` or `false` |
| `Int` | 64 bit signed. Overflow faults. |
| `Int8 Int16 Int32 Int64` | Sized signed integers. Overflow faults. |
| `UInt8 UInt16 UInt32 UInt64` | Sized unsigned integers. Overflow faults. |
| `Float` | 64 bit IEEE 754. `Float32` also exists. |
| `Rune` | Unicode scalar value |
| `Str` | Immutable UTF-8 text |
| `Bytes` | Immutable byte sequence |
| `Duration`, `Time` | Monotonic duration and wall clock instant |
| `List[T]` | Immutable persistent vector |
| `Map[K, V]` | Immutable persistent hash map, insertion ordered |
| `Set[T]` | Immutable persistent hash set, insertion ordered |
| `Buf[T]` | Mutable, move only growable buffer (section 5.3) |
| `Chan[T]` | Channel handle (section 7) |
| `Mutex[T]`, `Atomic[T]` | Shared mutable state (section 5.4) |
| `Secret[T]` | Value that is redacted in every output path |
| `Option[T]`, written `T?` | Optional (section 2.5) |
| `Result[T, E]` | Success or failure value (section 2.6) |
| `(A, B)` | Tuple |
| `fn(A) -> B ! E uses fx` | Function type (section 6.2) |
| `()` | Unit |
| `Never` | The type with no values. Type of `return`, `break`, `continue`, `fault(...)`, `proc.exit(...)`, and infinite loops. Converts to every type. |

There are no implicit numeric conversions. Conversions are calls on the target
type: `Float(i)`, `Int(f)` (truncates toward zero, faults on NaN or out of
range), `Int32(x)` (faults out of range), `Int32.try(x) -> Int32?`. Parsing
text is `Int.parse(s) -> Int?`, `Float.parse(s) -> Float?`.

A `Rune` is not a number, but converts both ways with an integer type
(decision 0079): `Int(r)` (and every sized integer type, faulting out of
range: `UInt8('é')` faults) is its code point; `Rune(n)` from any integer
type is the character with code point `n` and faults when `n` is not a
Unicode scalar value (negative, a surrogate `0xd800` to `0xdfff`, or above
`0x10ffff`); `Rune.try(n) -> Rune?` is `None` there instead. A `Rune`
does not convert to `Float` (`XO0409`, with a note pointing at `Int(r)`).

Float comparison (decision 0053; the same for `Float32`):

- `==` and `!=` are IEEE 754: NaN is not equal to anything, itself
  included, and `-0.0 == 0.0`. Structural `==` on structs, tuples, enums,
  options, lists, and recursive (boxed) values compares Float fields this
  way, so a value holding a NaN is not equal to itself (`x == x` is
  false).
- `<`, `<=`, `>`, `>=` use the total order of `Ord` (std.md 1): every
  NaN sorts after `+Inf` and equals every other NaN in the order, and
  `-0.0` and `0.0` are equal in the order. So `NaN < NaN` is false,
  `NaN <= NaN` is true, `NaN > x` is true for every non NaN `x`,
  `-0.0 < 0.0` is false, and `!(a < b)` is always `a >= b`. Because the
  two families differ, `a <= b && a >= b` does not imply `a == b` (both
  NaN).
- Unary `-` flips the sign of every Float, zero included: `-0.0` is
  negative zero.

`Duration` may be negative (`d.abs()` gives the magnitude).

Operators on `Duration` and `Time`:

| Expression | Type |
|---|---|
| `d1 + d2`, `d1 - d2` | `Duration` |
| `d * n`, `n * d`, `d / n` (n: `Int`) | `Duration` |
| `d1 / d2` | `Int` (truncated) |
| `t + d`, `t - d` | `Time` |
| `t1 - t2` | `Duration` |
| comparisons | `Bool` (both are `Ord`) |

Methods (`d.seconds() -> Float`, `d.scale(f: Float) -> Duration`, and more) are
listed in `spec/std.md`.

### 2.2 Structs

```
pub struct User {
  pub id: Int
  pub name: Str
  pub email: Str?
  created: Time
}
```

- Construction names every field: `User{id: 1, name: "a", email: None, created: now}`.
  Type arguments of a generic struct are inferred from the fields or the
  expected type: `Job{id: 1, input: "x"}` is a `Job[Str]`.
  Fields with a declared default may be omitted:
  `struct Config { port: Int = 8080 }`, so `Config{}` is valid.
- Update syntax creates a modified copy: `u with {name: "b"}`.
- Structs are values. Assignment copies (cheaply: fields are shared because all
  builtin collections are immutable).
- Fields may be separated by newlines or commas.

### 2.3 Enums (sum types)

```
pub enum LoadErr {
  NotFound(id: Int)
  Db(msg: Str)
  Timeout
}
```

- Variants may carry named fields. Construction uses names:
  `LoadErr.NotFound(id: 42)`. A single field variant may also be constructed
  positionally: `LoadErr.NotFound(42)`.
- The enum prefix may be omitted when the expected type is known (in a
  `match` arm, an argument, a return, a typed binding, or nested inside one of
  those): `return Err(NotFound(id: 42))`.
- Enums from other modules are qualified with the module: `task.Timed.TimedOut`,
  `http.ClientErr.Canceled`. Type arguments of generic enums are inferred:
  `task.Timed.Failed(e)`.
- Enums are closed. `match` must be exhaustive (error `XO0301`). Variants with
  a field of type `Never` cannot be constructed and may be omitted from a
  `match`.

### 2.4 Interfaces

```
pub interface Store {
  fn get(self, id: Int) -> User ! LoadErr uses net
  fn put(self, u: User) -> () ! LoadErr uses net
}
```

- Satisfied structurally and implicitly, as in Go.
- Interfaces may be used as types (dynamic dispatch) or as generic constraints.
- `Never` satisfies every interface.
- A type satisfies an interface through its methods whether or not they are
  `pub`. Calling a non `pub` method by name from another module is still an
  error.
- Every derivable name (`Display`, `Ord`, `Json`, ...) is also an interface and
  may be used as a constraint: `fn reply[T: Json](v: T)`.

### 2.5 Optionals

- `T?` is `Option[T]`: either `Some(v)` or `None`. There is no nil, null, or
  zero value pointer.
- A plain `T` is accepted where `T?` is expected and is wrapped in `Some`
  automatically (one of the three implicit conversions, section 2.10).
- Unwrapping:
  ```
  if let Some(e) = user.email { send(e) } else { log.warn("no email") }
  let e = user.email ?? "unknown@example.com"
  let e = user.email.or_err(LoadErr.NotFound(id: user.id))?
  ```
  `if let e = opt` (without `Some`) is also accepted as shorthand for
  `if let Some(e) = opt`.
- In a function that returns `U?`, `opt?` yields the value or returns `None`:
  ```
  fn retry_after(resp: http.Response) -> Duration? {
    let secs = Int.parse(resp.header("Retry-After")?.trim())?
    1s * secs
  }
  ```
  Using `?` on an `Option` in a function that returns a `Result` is error
  `XO0204`, with a fix that inserts `.or_err(...)`.

### 2.6 Results, errors, and explicit failure

- A function that can fail declares its error type after `!`:
  `fn load(id: Int) -> User ! LoadErr`. Such a function returns
  `Result[User, LoadErr]` to its caller.
- `!` with no type means `! Error`, the builtin open error interface.
  `fn main(os: Os) !` is `fn main(os: Os) -> () ! Error`.
- `T ! Never` is the same type as `T`: a function that cannot fail.
- **Returning.** In a function declared `-> T ! E`, a returned value (a tail
  expression or `return x`) may be:
  - a `T`, which means success, or
  - `Err(e)` with `e: E`, which means failure, or
  - any `Result[T, E]` value, returned as is.

  ```
  fn parse_port(s: Str) -> Int ! ConfigErr {
    let n = Int.parse(s).or_err(ConfigErr.NotANumber(s))?
    if n < 1 || n > 65535 {
      return Err(ConfigErr.OutOfRange(n))
    }
    n
  }
  ```
  If `T` is itself a `Result`, success must be written `Ok(x)` explicitly
  (error `XO0203` otherwise).
- Each arm of a tail `if` or `match` is in return position, so arms may mix
  `T` values and `Err(e)`: `match x { 0 => 1  _ => Err("no") }`.
- **Propagating.** `expr?` on a `Result[T, E2]` yields the `T` or returns the
  error from the enclosing function. `E2` must convert to the enclosing error
  type `E`:
  - `E2` is `E`, or
  - `E` is `Error` and `E2` implements `Display`, or
  - `E` has a `from E2` variant.
- **from variants** wrap other error types:
  ```
  enum ApiErr {
    BadRequest(msg: Str)
    Load(from LoadErr)   // `?` on a `! LoadErr` call wraps into ApiErr.Load
  }
  ```
  A `from` variant has one positional field: construct with
  `ApiErr.Load(LoadErr.Timeout)`, match with `Load(inner)`.
- **Mapping.** `r.map_err(f)` converts the error inline:
  `json.decode[User](b).map_err(fn(e) { ApiErr.BadRequest(msg: "${e}") })?`.
- **Handling is mandatory.** A `Result` value that is never used (not
  propagated with `?`, matched, passed, or returned) is error `XO0201`. A
  `Result` bound to a name that is never used is also `XO0201`, whatever the
  name (including `_x`).
- **Discarding needs a reason.** `let _ = expr` on a `Result` is error
  `XO0206`. Write `expr.ignore_err("why this failure does not matter")`,
  which yields `T?` and records the reason, so every discarded error is
  searchable and reviewable:
  ```
  let _ = dst.remove(tmp).ignore_err("best effort temp file cleanup")
  let nick = nickname_field(fields).ignore_err("nickname is optional") ?? ""
  ```
  `r.ok()` remains for when failure simply means "no value".
- A closure whose tail is a `Result` is a failing closure; the `Result` is
  returned as is. A closure declared `-> T` with no `!` cannot fail.
- A non `Result` value may be discarded as an expression statement.

### 2.7 Generics

```
pub fn map[T, U](xs: List[T], f: fn(T) -> U) -> List[U] { ... }
pub fn largest[T: Ord](a: T, b: T) -> T { if a > b { a } else { b } }
```

- Square brackets, Go style. Constraints are interfaces. Effect variables are
  declared in the same list (section 6.2).
- Explicit type arguments at a call: `json.decode[User](body)`.
- Monomorphized at compile time. Type arguments are inferred at call sites when
  unambiguous; otherwise error `XO0402` lists the candidates.
- No higher kinded types, no specialization, no variadic generics.

### 2.8 Type aliases and newtypes

```
type UserId = Int                   // alias, interchangeable with Int
type Email struct(Str)              // newtype, distinct, construct with Email("x")
type Handler = fn(http.Request) -> http.Response ! Error uses net
```

A newtype has the equality, hashing, and JSON of its underlying value
(decision 0097):

- `e.0` is the underlying value, typed with the newtype's type arguments
  (`Wrap[Int]`'s `.0` is `Int`); on a `var` it is assignable like a tuple
  field. Any other index is `XO0601`.
- The pattern `Email(p)` (`mod.Email(p)` for another module's newtype)
  matches when `p` matches `e.0`; it is irrefutable when `p` is, so
  `let Email(s) = e` destructures. `Email(..)` matches any value. Without
  parentheses, with other than one positional pattern, or against another
  type it is `XO0302`. When the value is an enum with a variant of the
  same name, the variant is meant.
- Construction and reading are open wherever the type is visible; a type
  that must guard its value is a struct with a non `pub` field.

```
type Email struct(Str)

fn domain(e: Email) -> Str {
  let Email(s) = e
  s.split("@").last() ?? ""
}

fn is_blank(e: Email) -> Bool { e.0.trim() == "" }
```

### 2.9 Derived behavior

Every type automatically implements:

- `Debug`: structural printing used by the debugger, error frames, and `dbg()`.
- `Eq` and `Hash` when all fields do.

A type is *comparable* when it implements `Eq`: every field, enum payload,
tuple element, and element or type argument is comparable. Function values
and handles are not (decision 0112): `Chan`, `Mutex`, `Atomic`, `Scope`,
`Task`, `Listener`, `Conn`, `task.Group`, `task.Semaphore`, `task.Once`,
`http.Router`, `http.Server`, `http.Client`, `http.BodyWriter`,
`http.TlsCert`, `sql.Db`, `sql.Tx`, and the std/testing fakes
(`FakeNet`, `FakeClock`, `MemFs`, `FakeEnv`, `FakeStdio`, `FakeProc`).
A value that holds one, directly or in a field, payload, tuple, or
collection, cannot be compared with `==` or `!=`, cannot be a `Map` key
or `Set` element, and its type cannot `derive Eq`, `Hash`, or `Ord`
(`XO0404`, with a note naming the field that holds the handle). Compare
an identifying field instead (`a.id == b.id`). Interface values compare
through their dynamic value and stay comparable; the opaque Go values of
`use go` bindings keep Go's equality (4.4). Automatic `Debug` is
unaffected.

Opt in with `derive` on the declaration:

```
pub struct User { ... } derive Display, Ord, Json, OpenApi
```

- Derived `Display` prints the same text as `Debug`:
  `NotFound(id: 42)`, `User{id: 1, name: "a"}`. Implement `Display` by hand
  (`fn (e: ApiErr) display() -> Str`) for user facing text.
- `Json`, `OpenApi`, and `Proto` derives generate codecs and schemas from the
  same declaration so wire formats and docs cannot drift. JSON rules are in
  `spec/std.md`. **[after 1.0]** `OpenApi` and `Proto` are planned for
  after 1.0 (owner, 2026-10-09; additive, so not a 1.0 blocker): the
  derives are reserved, accepted only when every field derives them too
  (no builtin type implements them yet), and nothing is generated.
- `SqlRow` (structs only) generates a decoder from a std/sql row: each field
  is read from the column of the same name (`spec/std.md` 10.4).

### 2.10 Implicit conversions

There are exactly three, and each applies only where the target type is
already known:

1. A `T` where a `T?` is expected becomes `Some(T)`.
2. A value where an interface type is expected (a parameter, a field, a
   collection element, a return) becomes that interface, if the type
   implements it. This includes passing a capability fake where the
   capability interface is expected.
3. An error at a `?` site converts to the enclosing error type (2.6).

Conversions are not applied when joining branches (`if`/`match` arms must
already agree) or to the `else` value of `requires`. Comparing `opt == v`
wraps `v` in `Some`.

Everything else, including all numeric conversions, is explicit.

---

## 3. Declarations and statements [P1]

### 3.1 Bindings

```
let x = 10            // immutable binding
var total = 0         // mutable binding
total += x
const MAX_USERS = 1000
const DEFAULT = Config{port: 8080}
```

- `const` may appear at top level or in a block. Initializers must be compile
  time values: literals, other
  constants, struct and enum construction, and collection literals of those.
- Unused local bindings, local constants, and imports are errors (`XO0110`). Not covered:
  function and closure parameters, names starting with `_`, and `_` itself.
  `xo fix` removes unused bindings.
- Shadowing in the same block is an error. Shadowing in a nested block is
  allowed.

### 3.2 Functions

```
/// Returns the user with the given id.
pub fn fetch_user(db: Db, id: Int) -> User ! LoadErr
  uses net
  requires id > 0
{
  let row = db.query_one("select id, name, email from users where id = $1", id)?
  User{id: row.int("id")?, name: row.str("name")?, email: row.str_opt("email")?, created: row.time("created")?}
}
```

- The last expression of a block is its value. `return` exits early. An empty
  body `{}` has type `()`.
- A block whose last item is a statement (an assignment, `let`, `for`,
  `while cond`, `defer`) has type `()`.
- `while { ... }` with no `break` has type `Never`, so it may end a function
  body of any type (all exits are `return`).
- Parameters are immutable unless declared `var` (in out, see 3.3). Named
  arguments are allowed and recommended when a call has more than two
  parameters of the same type: `transfer(from: a, to: b, amt: 5)`. Positional
  arguments must come before named ones.
- Default parameter values are allowed: `fn listen(addr: Str, backlog: Int = 128)`.
- No variadic functions. Pass a `List` or `Map`.

### 3.3 Closures

```
let double = fn(x: Int) -> Int { x * 2 }
xs.map(fn(x) { x * 2 })                         // types inferred from context
srv.handle("GET /hi", fn(req) { http.text(200, "hi") })
let load = fn(id: Int) -> User ! LoadErr uses net { store.get(id)? }
```

- Parameter and return types may be omitted when the expected function type
  is known.
- Closures capture by value. Mutating a captured `var` inside a closure is
  error `XO0502`. To share mutable state with a closure, capture a `Mutex` or
  `Atomic` handle.
- Creating a closure performs no effects; calling it does.
- `return` inside a closure returns from the closure.
- Effects of a closure are inferred from its body unless declared.
- A closure whose body never completes normally (it ends in `while { ... }`
  without `break`, `fault(...)`, or another `Never` expression) and whose
  result type nothing else determines has result type `Never`, so
  `s.spawn_background(fn() { while { ... } })` needs no type argument
  (`Task[Never, E]`).

### 3.4 Methods

```
fn (u: User) display_name() -> Str { u.name }

pub fn (var a: Account) deposit(amt: Money) requires amt > 0 {
  a.balance += amt
}
```

- A `var` receiver mutates the caller's binding in place (in out semantics). It
  can only be called on a `var` binding (error `XO0501`), a `var` parameter, or
  a field of one (`st.users.put(k, v)` where `st` is `var`).
- Methods may be declared only in the module that declares the type.

### 3.5 Control flow and patterns

```
if cond { ... } else if other { ... } else { ... }   // also an expression

for x in xs { ... }
for (i, x) in xs.indexed() { ... }
for (k, v) in m { ... }
for job in jobs_chan { ... }  // receives until the channel is closed and drained
for b in data { ... }         // each byte of a Bytes, as a UInt8
for i in 0..10 { ... }        // half open range, 0..=10 is inclusive
for _ in 0..3 { ... }
while cond { ... }
while let Some(job) = queue.pop() { ... }  // until the pattern fails
while { ... }                 // infinite loop
for x in xs bound 64 { ... }  // a loop bound: a contract (below, 6.5)
while cond bound 1000 { ... }

match code {
  200..=299 => "ok"
  429 | 503 => "retry later"
  _ => "error"
}
```

- `if` without `else` has type `()`.
- `for` iterates over a List, Buf, Set, Map (as `(k, v)` tuples), Chan, Range,
  or Bytes. Over `Bytes` it yields each byte as a `UInt8`, in order
  (decision 0138); a byte compares with an ASCII rune literal
  (`b >= 'a'`) and converts with `Int(b)`. Index pairs come from
  `xs.indexed()` on a List; for Bytes, loop over `0..data.len()` and
  read `data[i]`. A `Str` is not iterable: choose `.runes()` or
  `.bytes()`.
- `while let pattern = value { ... }` computes `value` before each
  iteration and runs the body while it matches the pattern, with the
  pattern's names bound in the body; the loop ends at the first value
  that does not match (decision 0137). Any refutable pattern works
  (`while let Tok.Num(n) = next()`), and `while let v = opt` is
  shorthand for `while let Some(v) = opt`, as with `if let` (2.5).
  `bound N` follows the value: `while let Some(x) = q.pop() bound 64`.
- `x is Pattern` is a `Bool` expression that tests a pattern without binding
  names: `expect(r is Err(Timeout))`, `if resp is Ok(_) && retries < 3`.
  `is` is a contextual word in operator position (precedence of `==`), so it
  combines with `&&` and `||` like a comparison.
- Compound assignment works for `+= -= *= /= %=` on `var` bindings and their
  fields. There are no bitwise compound assignments.
- A value counts as stored again (for XO0520) by any later `push`, `put`,
  `insert`, `set`, `add`, `update`, or `m[k] = v` of it, including later in
  the same loop.
- Tuple fields are `t.0`, `t.1`, ...
- `match` is an expression. Arms are separated by newlines. An arm body may be
  an expression, a block, or a single statement such as an assignment (type
  `()`): `Timeout => last = Some(AttemptErr.Timeout)`.
- Labeled loops: `outer: for ... { break outer }`.
- **Loop bounds** (decision 0133). `bound N` after the iterated value of a
  `for` or the condition of a `while` states that the loop runs at most N
  iterations; N is a constant Int expression (literals, `const` names,
  `+ - * / %`), not negative, else `XO1402`. A `for` over a range with
  constant ends that is longer than N is `XO1402`. Otherwise the bound is
  checked at run time on every backend: starting iteration N + 1 faults
  with `XO-F006` (`loop ran past its bound of N iterations`). Like a
  `requires` clause it is a contract (9.1): `xo test` fuzzes a function
  with a bounded loop. Real-time functions (6.5) need a bound on every
  loop whose count is not constant. `while { }` takes no bound; write
  `while true bound N { }`.
- `defer stmt` or `defer { ... }` runs when the enclosing **block** exits (not
  the function, unlike Go), in reverse order, including on errors, faults, and
  cancellation. Values are read when the deferred code runs.
- Deferred code is **shielded from cancellation**: inside it, cancellation
  points do not report cancellation, so cleanup such as removing a temp file
  works after a timeout. A shielded `defer` that runs longer than 5 seconds
  of real time faults (`XO-F005`); the Go backend checks this when the
  deferred code returns, and not in test worlds, whose clock is virtual.
- The native backends (`--backend=llvm`, `--backend=arm64`) run deferred
  code on every way out alike, faults included (decisions 0070, 0085).
- Deferred code that faults ends there; the deferred code registered
  before it and that of the blocks around it still runs, and the new
  fault replaces the one being unwound, if any (the last fault is
  reported). The task is no longer shielded afterwards. Every backend
  behaves this way (decision 0085, amended 2026-10-10).

**Patterns** are used in `match`, `let`, `if let`, `while let`, and `for`:

| Pattern | Matches |
|---|---|
| `_` | anything, binds nothing |
| `x` | anything, binds `x` |
| `42`, `"GET"`, `'a'`, `true` | equal literal |
| `1..=9`, `'a'..='z'` | value in inclusive range |
| `p1 \| p2` | either (both must bind the same names) |
| `(a, b)` | tuple |
| `Some(p)`, `None`, `Ok(p)`, `Err(p)` | prelude variants |
| `Timeout`, `http.ClientErr.Canceled` | unit variant, bare or qualified |
| `NotFound(p)` | variant fields positionally, in declared order |
| `NotFound(id: p)` | variant fields by name; `..` ignores the rest: `Db(msg: m, ..)` |
| `Variant(..)` | variant with any fields |
| `User{id: p, ..}` | struct fields by name |
| `[]`, `[first, ..]`, `[.., last]`, `[a, .., z]` | list shape (`..` at most once, anywhere) |
| `(p)`, `()` | grouping, unit |

Patterns nest: `Some(Ok(v))`, `Err(LoadErr.NotFound(id))`.

An unguarded `_` (or catch all binding) arm that hides variants of an enum
declared in the same module is warning `XO0305`: list the variants, so that
adding one later is a compile error at every `match`. Enums from other modules
may use `_`, since their owners can add variants. A `_` arm that hides at
least 8 variants and more than half of the enum is a default ("these few are
special, the rest are not", as on a token kind enum) and is not reported; the
warning names at most 8 hidden variants, then how many more (decision 0080).
An `UpperCamel` name in a pattern always refers to a variant; bindings are
`snake_case` (1.5), so the two never collide.
`let` requires an irrefutable pattern (`let (a, b) = pair`).

### 3.6 Indexing and slicing

- `xs[i]` on `List`, `Bytes`: faults when out of range. `xs.get(i)` returns `T?`.
- `Map` cannot be read by index: `m[k]` in an expression, or an in place change
  through it (`m[k].f = v`, `m[k] += 1`, `m[k].push(x)`), is error `XO0410`,
  because a missing key is the Map form of a nil dereference. Use `m.get(k)`
  (`V?`), `m.get_or_fault(k, "why the key must exist")`, or
  `m.update(k, init, fn(var v) { ... })`. On a `var` map, the whole value
  assignment `m[k] = v` is allowed and means `m.put(k, v)`.
- `Str` cannot be indexed by position (use `runes()` or slicing).
- Ranges slice: `xs[1..]`, `xs[..n]`, `xs[a..b]` on `List`, `Bytes`, and `Str`
  (byte offsets; slicing a `Str` inside a UTF-8 sequence faults). Slices of
  immutable values are O(1) and share storage.

### 3.7 Holes

`???` is a typed hole, valid anywhere an expression is expected.

- `xo check` succeeds with warnings and reports, for each hole, the expected
  type, the effects in scope, and in scope values or functions that fit
  (diagnostic `XO0900`, see section 10).
- `xo build` rejects programs with holes. `xo run --holes` and `xo test --holes`
  allow them and fault if a hole is evaluated.

Holes let an agent write a type correct skeleton first and fill it in steps.

---

## 4. Modules and dependencies [P1, content addressing P3]

### 4.1 Imports

```
use std/http
use std/json as j
use github.com/acme/billing/invoice
```

- The last path segment is the local name unless `as` is given, with `-`
  replaced by `_` (`use acme/rate-limit` binds `rate_limit`). A `/vN` major
  version suffix of a module path is skipped (`use github.com/acme/tick/v2`
  binds `tick`). If the result is
  still not a valid identifier, or is a keyword, `as` is required (`XO0602`).
- A path is segments of ASCII letters, digits, `_`, `-`, and `.` separated
  by `/` (`PathSeg`, section 15). Only the last segment becomes the local
  name, so dashes elsewhere need nothing (`use github.com/xo-lang/xo/lexer`
  binds `lexer`). A leading or trailing `/`, an empty segment, or a
  segment that is exactly `.` or `..` is `XO0158`: import paths are
  absolute, never relative (`use ./lib`), and name each package one way,
  so `deny` rules (4.2) see every import. A `.` inside a segment is fine
  (`example.com`, `.github`, `v1.2`).
- Imports are explicit. There is no dot import or glob import.
- Cyclic module imports are an error.

### 4.2 xo.mod

```
module github.com/acme/api
edition 2026

require github.com/acme/billing v1.4.0
```

**[P1]** Dependencies (decision 0086):

- `require <module path> <version>` requires another Xo module. The module
  path is its git location (`github.com/acme/billing` is fetched from
  https://github.com/acme/billing.git; the module is the repository root
  and its xo.mod declares the same path); the version is a tag `vX.Y.Z`
  (`-prerelease` allowed). A module is required once. `require go
  <module> <version>` pins a Go module for `use go` instead (4.4).
- A module may live in a subdirectory of its repository, as in Go:
  `github.com/acme/tools/cli` is the directory `cli/` of the repository
  `github.com/acme/tools`, its versions are the tags `cli/vX.Y.Z`, and a
  version is that directory's tree at the tag. On github.com, gitlab.com,
  and bitbucket.org the repository is the first three path elements; on
  other hosts the longest prefix of the path that git answers for is the
  repository. A directory holding its own xo.mod is never part of the
  module around it (it is not fetched, hashed, or vendored with it).
- There is no `replace`, `exclude`, or `retract` (`XO1301`); local copies
  are reached only through a workspace (below).
- Minimal version selection: the build uses, for each module, the highest
  version that any reached xo.mod requires (the main module's, then those
  of every required version, transitively). No ranges, no solver; a
  version changes only when an xo.mod changes.
- Imports resolve inside the main module, the embedded `std`, and the
  selected module versions (the longest module path that prefixes the
  import path). A module must require every module it imports from:
  importing a package of a module xo.mod does not require, even one in the
  build because another module requires it, is `XO1306`.
- Fetching runs git and extracts the tag's tree (`git archive`) into a read
  only module cache; no code from a module runs at fetch or build setup
  (no scripts, no hooks). A version that cannot be fetched is `XO1302`.
- Module proxies (decision 0126): `XO_PROXY` lists where versions come
  from, in order: proxy URLs (`http://`, `https://`), `direct` (git), and
  `off` (no fetching); the default is `direct`. After a comma the next
  source is tried only when a proxy answers 404 or 410, after a pipe on
  any error. `XO_NOPROXY=<patterns>` (comma separated globs over leading
  path elements, `github.com/acme/*`) always goes direct. The protocol is
  read only HTTP: `<path>/@v/list`, `<path>/@v/<version>.mod`, `.zip`
  (the tree, files under `<path>@<version>/`), and `.lock` (the xo.lock
  lines), the path and version escaped as in the module cache (`!` before
  a lower cased capital). A proxy is not trusted: its tree must declare
  the module path and match xo.lock as a git fetch must. `xo mod proxy
  serve [--addr=host:port] [--fetch] [<cache dir>]` serves a module cache
  this way (`--fetch` fills it from git). A malformed `XO_PROXY` is
  `XO1314`.
- Offline (`--offline` on any command, or `XO_OFFLINE=1`) nothing is
  fetched: every version must be in the module cache or vendored, else
  `XO1303`.
- `xo vendor` copies every selected version into `vendor/<module path>/`
  with `vendor/modules.txt`. When `vendor/modules.txt` exists, builds use
  only vendor/ (no cache, no network); a requirement it lacks is `XO1307`.
- Commands: `xo get <path>[@version|@latest|@none]` sets a requirement
  (latest tag by default) and rewrites xo.mod and xo.lock; `xo mod tidy`
  requires every imported module, removes requirements nothing imports
  (unless they raise a version the graph selects), and rewrites xo.lock.

**[P1]** Workspaces: an `xo.work` file at or above the main module makes
the modules it lists build from their directories:

```
xo 2026
use ./api
use ./billing
```

- A listed module overrides every `require` of it; minimal version
  selection runs over the requirements of all listed modules.
- The workspace applies when it lists the main module; `--workspace=off`
  (or `XO_WORK=off`) ignores it, `XO_WORK=<file>` names another one.
- `xo check`, `test`, `query`, `edit`, and `mcp` take every listed module
  as one program (a rename reaches all of them, atomically).
- xo.lock is never written with workspace overrides (`xo get`, `xo mod
  tidy`, and `xo vendor` ignore the workspace); builds record the local
  directories in build information only. A release build (`xo build
  --release`, 8) and `xo build --ci` fail while a workspace overrides a
  required module (`XO1308`).
- `xo.work` is gitignored by `xo init` in a single module repository;
  a monorepo may commit it.

**[P1]** Effect ceilings (decision 0086 step 3): a `uses` clause after
the version bounds the effects of a dependency.

```
module github.com/acme/api

require github.com/acme/billing v1.4.0 uses none
require github.com/acme/geo v2.1.0 uses net.dial, fs.read
```

- Every call from the requiring module into the dependency (any of its
  packages), and every use of one of its functions as a value, may
  perform only effects within the ceiling (`fs` admits `fs.read`; `uses
  none` admits nothing), else `XO1309` at the call site naming the
  dependency, the ceiling, and the effect. The effects counted are those
  the dependency's function declares; effect variables (6.2) carry the
  caller's own effects and do not count.
- An upgrade whose new version adds an effect to a function this module
  calls therefore fails at that call. A require without `uses` has no
  ceiling. Unknown labels are `XO1301`.
- In a workspace, `use ./billing uses none` in xo.work sets the ceiling
  of that module for calls from the other workspace modules; their
  xo.mod ceilings apply as well.
- `xo get` prints, for every requirement whose version changes, the
  exports added, removed, or changed and the effects added or removed,
  and the exports above the ceiling; `xo get -u` upgrades every
  requirement to its latest version.

**[P1]** Releases (decision 0086 step 4): a version is a `vX.Y.Z` tag of
the repository whose root holds the module, or a `<dir>/vX.Y.Z` tag for a
module in the directory `<dir>`. `xo publish [--check]
[--version vX.Y.Z]` compares the exports (the declarations `xo query api`
lists, in every package of the module) and their effects with the
previous tag:

- A removed export, a changed signature, or an effect added to a
  function needs a major version; in a minor or patch release each is
  `XO1310` (before v1.0.0, a minor release may break and a patch may
  not). Added exports need a minor version.
- Without `--version` the release is the next minor version (the next
  patch before v1.0.0), so a breaking change fails unless a major version
  is asked for. The JSON result lists every change, the breaking ones,
  the bump they need, and the smallest version they allow.
- `XO1311` when the release cannot be made: the module is not in a git
  repository, a module in a subdirectory has a path that does not end
  with that directory, the tag exists or is not above the last release, or
  the major version does not match the module path. Without `--check`,
  uncommitted changes are `XO1311` and the module must load and check
  without a workspace.
- Publishing is tagging: `xo publish` prints `git tag vX.Y.Z && git push
  origin vX.Y.Z` (`git tag cli/vX.Y.Z ...` in `cli/`) and runs neither.
- Major versions 2 and above live at a module path ending in `/vN`
  (`github.com/acme/billing/v2`), in the same repository: the xo.mod of
  those tags declares that path, and a require of a path without the
  suffix takes only v0 and v1 tags (`XO1301` otherwise).

**[P1]** Import direction rules (decision 0095): `deny` lines state which
packages of the module may not import which.

```
module example.com/notes

deny example.com/notes/domain -> example.com/notes/store, example.com/notes/api
deny example.com/notes/store -> example.com/notes/api/...
```

- `deny <pattern> -> <pattern>, ...`: a package matching the left pattern
  may not `use` a package matching any pattern on the right. A pattern is
  a package import path (of this module, a required module, or std), or
  such a path ending in `/...`, which matches the path and every package
  under it (`example.com/notes/...` is the whole module, so "`domain`
  imports only std" is `deny example.com/notes/domain ->
  example.com/notes/...`).
- Every direct `use` line is checked, whether the import is used or not:
  a forbidden one is error `XO1312` at the `use` line, naming the rule and
  its xo.mod line; it has no machine fix (removing an import is a design
  change). Transitive edges are not checked: a chain is caught at its
  first forbidden step.
- Rules apply to the packages of the module whose xo.mod holds them
  (the main module, and each workspace module checked as one);
  a dependency's rules are checked when it is built as a main module.
- A malformed `deny` line, or a pattern that names no package, is
  `XO1301` on its line. `xo mod tidy` and `xo get` keep `deny` lines.
- `xo test` checks first, so a broken rule fails the tests too.
  `xo query imports <package>` and `xo query deps <package>` list the
  `use` lines (10.3).

**[P1]** `gc` lines tune the garbage collector of a program whose main
module this is (decision 0065); they are ignored in imported modules:

```
gc min_heap 64MiB      // floor of the heap goal
gc percent 200         // GOGC: goal growth over the live heap, or `off`
gc memory_limit 1GiB   // soft memory limit (GOMEMLIMIT)
```

Sizes take `B`, `KiB`, `MiB`, `GiB`, `TiB` (or `KB`, `MB`, `GB`, `TB`, powers
of 1000). Without `gc` lines a program's heap goal is the live heap plus 100
percent of it (as Go's GOGC=100), but never below a floor of 2 MiB per
processor (`GOMAXPROCS`), at least 16 MiB; there is no memory limit. The
environment always wins: `GOGC` set turns the floor off and sets the
percent, `GOMEMLIMIT` replaces `memory_limit`, `XO_GC_MIN_HEAP` replaces
`min_heap` (0 turns the floor off).

### 4.3 xo.lock

Written only by the toolchain. Maps every imported definition actually used to
its content hash.

- **[P1]** Hash is per module version (source tree hash), like Go's `go.sum`:
  `<path> <version> h1:<hash>` for the tree of every selected version and
  `<path> <version>/xo.mod h1:<hash>` for the xo.mod of every version
  version selection read. `xo get` and `xo mod tidy` write it; every
  command that loads the program recomputes the hashes from the module
  cache or vendor/ and compares them. A mismatch (a moved tag, an edited
  cache or vendor copy) is `XO1304` and nothing builds; a version with no
  entry is `XO1305` (run `xo mod tidy`). Malformed lines are `XO1301`.
- **[P1]** Checksum log (decision 0124): with `XO_SUMDB=<name>+<key
  id>+<public key> [<URL>]` (default `off`), the first fetch of every
  module version, from git or a proxy, is checked before it enters the
  module cache: the log's record of the version (its two xo.lock lines)
  must equal the fetched tree's hashes, the record must be in the log's
  signed Merkle tree (inclusion proof), and that tree must extend every
  tree of the log this module cache saw before (consistency proof). A
  failure is `XO1313` and nothing is cached; a log that cannot be
  consulted, or malformed settings, is `XO1314`. `XO_NOSUMDB=<patterns>`
  (as `XO_NOPROXY`, 4.2) names private modules that are never looked up.
- **[P1]** Definition hashes (decision 0125, step 5 of decision 0086):
  every top level definition (function, method, type, constant) has a
  content hash `d1:<base64 SHA-256>` over its syntax tree with
  positions, comments, and doc comments dropped, its own name left out
  (functions and constants; a type's name is part of it), local names
  erased (numbered by first binding: `let`, `var`, patterns, loops,
  closure parameters, type parameters, receivers, labels; the
  parameters of declared functions keep their names, which named
  arguments use), imports by path rather than by local name, and the
  definitions of the same module it references replaced by their hashes
  (mutually recursive ones hashed together). `hash` covers that closure,
  `own` the definition alone. `xo query hash <name>` prints them (10.3);
  `xo publish` lists every definition, exported or not, whose hash
  differs from the previous release (`changed`, or `changed_via` when
  only a definition it uses changed), next to the API comparison. The
  hashes are syntactic (no types): a method called on a value and other
  modules' definitions are referenced by name. xo.lock does not record
  them; decision 0125 proposes how it would.
- **[P3]** Hash is per definition: the normalized AST with local names erased
  and referenced definitions replaced by their hashes. Consequences:
  - Two versions of a dependency coexist without conflict. Types from different
    versions are the same type if and only if their hashes are equal.
  - An upgrade that does not change a definition is a no-op for code using it.
  - A name that does not resolve to a hash fails at once with the nearest real
    names suggested (`XO0601`). APIs cannot be hallucinated silently.

### 4.4 Go packages [P1]

Until Xo has its own ecosystem, Go packages can be used directly (decision
0041):

```
use go "regexp"
use go "github.com/google/uuid" as uuid

fn main(os: Os) ! {
  let re = regexp.must_compile("[a-z]+")
  os.stdio.println("${re.find_string("123abc")} ${uuid.new_string()}")
}
```

- `xo check`, `build`, `run`, and `test` generate bindings automatically into
  `.xo/gobind/` (or `$XO_GOBIND_DIR`); `xo bind <path>` does it explicitly.
  Third party module versions follow Go's module resolution.
- Names are converted to Xo naming: functions and methods to `snake_case`
  (`Split` is `split`, `EncodeToString` is `encode_to_string`), types stay
  `UpperCamel`.
- Types at the boundary: `(T, error)` becomes `T ! go.Error`; a pointer result
  that may be nil becomes `T?`; mutable Go objects become opaque handles;
  slices and maps are copied in and out; a `context.Context` parameter is
  supplied from the current task (deadline and cancellation); a panic becomes
  a fault.
- An Xo function passed to Go as a callback that Go calls during the Go call,
  on the calling task's goroutine, runs as the calling task. A call from
  another goroutine (`sync.WaitGroup.go_`, a timer) runs as a task of its own,
  a child of the calling task (canceled with it), with its own held mutexes
  (decision 0067); under the deterministic scheduler such a call waits for
  a turn like a spawned task and is recorded by `--record` (core 7.4, 11.3).
- Every call into Go performs the effect `go`, which `pub` functions must
  declare. Go code can reach files and the network without a capability, so
  `uses go` marks that authority escape in signatures.
- Exported names whose signatures cannot be mapped are skipped and listed at
  the top of the generated binding; using one is `XO1102`. A package that
  cannot be bound is `XO1101`.
- C headers are the native backends' counterpart (4.5).
- Calls into Go take part in record and replay (11.3, decision 0093): each
  binding's doc comment says how (`/// Replay: ...`). A pure function can
  be marked `replay rerun` in xo.mod, so a replay calls it again instead of
  answering it from the trace and the trace does not record it:

  ```
  replay rerun go github.com/acme/slug
  replay rerun go os getpid File.name
  ```

  The line names a Go import path and optionally the functions (`name`)
  and methods (`Type.method`) it marks; without names it marks every
  function of the package. The pure std packages (`strconv`, `strings`,
  `bytes`, `unicode`, `unicode/utf8`, `unicode/utf16`, `math`,
  `math/bits`, `regexp`, `regexp/syntax`, `path`, `html`, `net/url`,
  `encoding/hex`, `encoding/base64`, `encoding/base32`) are marked
  already, except functions that take or return a handle of another
  package (`regexp.Regexp.match_reader`).

### 4.5 C headers [P2, native builds]

Native builds (`--no-gc`, 10.1) call C through bindings generated from a
header (decisions 0093 and 0116):

```
use c "tiny.h" link "tiny.c"
use c "zlib.h" link "z" uses none

fn main(os: Os) {
  os.stdio.println("${tiny.tiny_add(2, 40)} ${zlib.crc32(0, b"hello")}")
}
```

- `use c "<header>"` names a header relative to the module directory, or
  one on the C compiler's include path (`zlib.h`). `link "<x>"`, repeated
  as needed, names what to link: a C source, object, or archive relative
  to the module directory (`tiny.c`, `libtiny.a`), or a library name
  without `/` or extension (`z` links `-lz`). `as` sets the local name;
  the default is the header's base name in snake_case.
- The checker binds the header with clang's AST dump
  (`xo bind --c <header>` does it explicitly) into `.xo/cbind/` (or `$XO_CBIND_DIR`): an
  Xo declaration file and a C adapter that the native build compiles and
  links with the program. Function names become snake_case
  (`zlibVersion` is `zlib_version`); struct tags become UpperCamel types.
- Types at the boundary (decision 0093), all copied, none pointing into Xo
  memory after the call returns: `int` is `Int32`, `unsigned` `UInt32`,
  `long` and `long long` `Int`, their unsigned forms `UInt64`, `short`,
  `char`, and their unsigned forms the sized integers, `float` `Float32`,
  `double` `Float`, `_Bool` `Bool`, an enum `Int32`; a `const char *`
  parameter is a `Str` (copied and NUL terminated) and a `const char *`
  result a `Str` (copied; NULL is ""); a const byte pointer followed by an
  integer length parameter is one `Str` (`const char *`) or `Bytes`
  parameter; a struct of such scalars passed or returned by value is an Xo
  struct with the same fields, copied; a pointer to a struct the header
  declares but does not define is an opaque handle type (a result is
  `T?`, None for NULL). Handles are not comparable (2.9) and Xo never frees
  them: call the library's free function. Anything else (output
  pointers, mutable buffers, `char *` results, pointers to defined
  structs, function pointers, arrays, variadic functions) is skipped and
  listed with the reason at the top of the binding; using a skipped name
  is `XO1105`. A header that cannot be bound is `XO1104`.
- Every call performs the effect `ffi` (6.1), which `pub` functions
  declare. A `use c` line may replace it with the effects a human vouches
  for, `uses none` or `uses fs.read, clock`, as an effect ceiling of
  decision 0086 does for a dependency.
- `use c` is impossible on the Go backend: `xo check`, `build`, `run`, and
  `test` without `--no-gc` report `XO1201` on the line. (`use go` is
  likewise `XO1201` on the native backends.)
- A C crash is not recoverable: it ends the process (8).

---

## 5. Values, mutation, and memory [P1]

### 5.1 Value semantics

All builtin types and user structs, enums, and tuples have value semantics.
There are no pointers and no reference types other than the explicit handle
types (`Chan`, `Mutex`, `Atomic`, capability values, `Buf` by move).

Because values never alias mutably, data races are impossible except through
`Mutex` and `Atomic`, which are synchronized.

### 5.2 Mutation

- Only `var` bindings, `var` parameters, and `var` receivers can be mutated.
- `xs.push(v)` on a `var xs: List[T]` rebinds `xs` to a new persistent list
  sharing structure with the old one. The same holds for every mutating
  collection method (`put`, `remove`, `update`, `pop`, `add`).
- Field assignment `u.name = "b"` on a `var u` rebinds `u` to a modified copy.
  Nested paths work: `st.users.put(k, v)`, `st.config.port = 9090`.
- An assignment evaluates the indexes of its target, then the value, then
  stores into the target (decision 0140): changes the value makes to the
  target's binding are kept, so `t.objs[o].kids = t.add_kids()` keeps the
  objects `add_kids` pushed onto `t.objs`.
- A call evaluates the place of a `var` receiver or `var` argument, then
  every argument in order, then reads those places (decision 0142):
  `t.get(t.add(v))` on a `var t` sees the item `add` pushed, and
  `xs.push(f())` keeps what `f` pushed onto `xs`.
- To change a value stored in a collection, use `update`, which reads, mutates,
  and writes back in one call:
  ```
  let allowed = m.update(key, new_bucket(limit, now), fn(var b) { b.take(now) })
  ```
- **Lost update check.** Error `XO0520` ("mutation of `b` is never written
  back") applies to a local `var` whose initializer copies an element out of a
  collection (`m.get(k)`, `m[k]`, `xs[i]`, `xs.get(i)`, `first`, `last`,
  `find`, optionally followed by `??`, `?`, or `.or_err(...)?`) when the
  binding is mutated and is not stored again after its last mutation. Storing
  means passing the whole value as a call argument (`m.put(k, b)`,
  `save(b)`) or assigning it as a whole (`m[k] = b`). Reading or returning it
  does not count, because the collection still holds the old value. This
  catches `var b = m.get(k) ?? new_bucket(); b.take(now)` without
  `m.put(k, b)`, and a PATCH handler that returns the edited copy but never
  stores it. For an intentional scratch copy, copy through a local first
  (`let base = m.get(k)?; var scratch = base`). Values derived with `with`
  are not tracked, since views such as redacted copies are common. It also applies to any local `var` that is mutated after
  being stored into a collection (`xs.push(v)`, `m.put(k, v)`) and then not
  stored again, since the stored copy does not see the change. Reads inside
  `defer` count. Parameters, including `var` closure parameters, are never
  checked.
- **No aliased `var` arguments** (decision 0111). One call may not pass
  the same variable, or two overlapping places, to two `var` parameters
  (a `var` receiver counts as one): `both(xs, xs)`, `one(p, p.x)`,
  `p.add(p.y)` with a `var` receiver, and `both(g[i], g[j])` are
  `XO0503`. Places overlap when they start at the same binding and one
  path of fields and indexes is a prefix of the other; two different
  field names, or two indexes that are different integer or string
  literals (`g[0]`, `g[1]`), keep them apart. Copy one into a temporary
  first. With aliasing ruled out, passing by reference (the Go backend)
  and copying in and out (the native backends) give one result.

### 5.3 Move only buffers

`Buf[T]`, `StrBuilder`, and `BytesBuilder` are mutable in place and **move
only** (affine):

```
var b = Buf[Int].with_capacity(1024)
b.push(1)
let xs = b.freeze()      // moves b, returns an immutable List[Int] in O(1)
b.push(2)                // error XO0510: use of moved value `b`
```

- Assigning, passing, sending on a channel, or capturing in a spawned task moves
  the buffer. The compiler tracks moves only for these types. There is no
  general borrow checker.

### 5.4 Shared mutable state

```
let cache = Mutex[Map[Str, User]].new({})
cache.with(fn(var m) { m.put(key, user) })
let n = cache.with(fn(m) { m.len() })               // read only access
let size = cache.with_read(fn(m) { m.len() })       // shared with other readers
let hits = Atomic[Int].new(0)
let now_hits = hits.add(1)                           // returns the new value
```

`Mutex[T].with` has the signature

```
fn (m: Mutex[T]) with[R, E, fx: Effects](f: fn(var T) -> R ! E uses fx) -> R ! E uses fx
```

- The closure's return value (or error) is the result of `with`.
- Mutations to the `var` parameter are written back when the closure returns
  successfully. If it returns an error or faults, the protected value is left
  unchanged.
- A closure parameter declared without `var` gets a read only view.
- To keep changes and still report failure, return the outcome as the
  closure's success value and apply `?` outside:
  ```
  let outcome = q.state.with(fn(var st) -> Result[Job, QueueErr] {
    st.expire_leases(now)                  // kept
    if !st.token_valid(id, token) { return Err(QueueErr.StaleToken) }
    Ok(st.ack(id))
  })
  outcome?
  ```
- Warning `XO0804` (split critical section): a value read inside one
  `m.with(...)`, or derived from one, is used inside a later `m.with(...)` on
  the same mutex, or a branch on it decides whether the later `with` runs.
  Another task may have changed the state in between (check then act). Do the
  check and the action in one `with`.
- `with` cannot be called re entrantly on the same mutex within one task
  (fault `XO-F004` with both acquisition sites).
- `m.with_read(f)` (std.md 2.7) is shared read only access: `f` gets the
  value read only, readers run together, and `with` excludes them. Writers
  are preferred, so a reader must not wait for a reader that has not
  entered yet. A value read in `with_read` and used in a later `with` gets
  `XO0804` like one read in `with`.

### 5.5 Memory management

Tracing GC, plus request scoped arenas (decision 0066; on by default, off
with `--no-arenas`). An arena is memory owned by one HTTP request served by
`http.server` (std.md 4.4) and reused by a later request once the handler has
returned and the response is written. The compiler and the runtime place in it
only memory that provably cannot outlive the request: the request's header,
parameter, and query tables and its body when the handler uses the request
only through `json`, `param`, `param_int`, `query`, `header`, and its `Str`
fields (and not in a nested closure); the response built by `http.json` or
`http.text` that is directly the handler's result; and the `List` made by
`m.values()`, `m.keys()`, `m.entries()`, or `s.to_list()` when it is only
consumed in its own expression. Everything else, including every value a
program stores, sends, captures, returns, or hands to Go, is ordinary garbage
collected memory, so arenas are invisible: a program prints the same with and
without them. A user written `arena scope` block remains **[P2]**.

---

## 6. Effects and capabilities [P1 checking, P2 replay]

### 6.1 Effect labels

The builtin effects are:

| Effect | Sub effects | Authority |
|---|---|---|
| `net` | `net.dial`, `net.listen` | Sockets, HTTP, DNS |
| `fs` | `fs.read`, `fs.write` | Files and directories |
| `clock` | | Wall clock, timers, sleep |
| `rand` | | Randomness (crypto and math) |
| `env` | | Environment variables, args |
| `proc` | | Spawn processes, signals, exit |
| `stdio` | | Standard input, output, error |
| `ffi` | | Calls into C through `use c` (4.5, native builds) |

A function with no `uses` clause is **pure**: it performs no capability
effects. It may still allocate, fault, be cancelled, log, and use
synchronized state (`Mutex`, `Atomic`, `Chan`).

Logging (`std/log`) is ambient: it needs no capability and is not an effect.
Log output goes to the sink configured in `main` (standard error by default).
It is recorded during record and replay like any other output.

Effect labels live in their own namespace: a parameter or module named `fs` or
`net` does not conflict with the effect `fs` or `net`.

### 6.2 The uses clause, effect variables, and subsumption

```
pub fn sync(src: Fs, out: Stdio) -> () ! SyncErr uses fs.read, stdio { ... }
```

- A function's effects are the union of the effects of everything it calls.
- `pub` functions and interface methods are not inferred: their `uses` clause
  is the declared set, and no clause means pure. Non pub functions and
  closures have their effects inferred.
- Performing an effect outside the declared set is error `XO0701`, which shows
  the call chain that performs it. Declaring more is allowed (for interface
  stability) and noted by `xo vet`.
- **Function types** carry effects. One effect: `fn(Int) -> Int uses clock`.
  Several effects are parenthesized: `fn(Int) -> Int uses (net, clock)`.
- In a declaration clause the parentheses are optional:
  `uses net, clock` and `uses (net, clock)` are the same.
- **Subsumption.** A function value is accepted where a function type is
  expected if its effects are a subset of the expected effects (`fs.read` is a
  subset of `fs`) and its error type converts to the expected one. A function
  that cannot fail is accepted where a failing one is expected. A pure function
  is accepted everywhere a function of the same shape is.
- **Effect variables** let a higher order function pass through its callback's
  effects. Declare them in the type parameter list with the `Effects` kind:
  ```
  pub fn run_pool[T, R, E, fx: Effects](
    jobs: List[T],
    workers: Int,
    f: fn(T) -> R ! E uses fx,
  ) -> Report[R, E] uses fx { ... }

  pub fn retry[T, E, fx: Effects](clock: Clock, f: fn() -> T ! E uses fx) -> T ! E
    uses (clock, fx) { ... }
  ```
  An effect variable is instantiated at each call from the argument's effects,
  and may be empty.
- **Structs may take effect parameters** (`struct Cache[K, V, fx: Effects]`),
  and effect sets may be written as type arguments (`Cache[Int, Item, net.dial]`).
- **Effect arguments may be omitted.** Writing `Cache[Int, Item]` leaves the
  effect arguments to inference. Either all of a type's effect arguments are
  written or all are omitted.
  - In a local binding they are inferred from the value.
  - In parameter and receiver types they become implicit effect variables of
    the function. These count as declared: the function may perform them
    without naming them in `uses`.
  - In result and error types they are inferred from the function body, so a
    constructor `fn new_cache(...) -> Cache[Int, Item]` returns the effects of
    the loader it was given.
  - In a struct field they become implicit effect parameters of the struct.
- A non pub function with a `uses` clause is checked against it. Test blocks
  may perform any effect on their `os`.

### 6.3 Capability values

Effects describe what a function may do. Capability values grant the authority
to do it. The only source of capabilities is `main`:

```
fn main(os: Os) ! {
  let srv = http.server(os.net.listen(":8080")?, router(os.net))
  srv.serve()?
}
```

`Os` has fields `net: Net`, `fs: Fs`, `clock: Clock`, `rand: Rand`, `env: Env`,
`proc: Proc`, `stdio: Stdio`, and `tasks: task.Group` (program lifetime task
group, core 7.1). Each capability type is a predeclared interface,
so test fakes and attenuated versions satisfy it. Their methods are in
`spec/std.md`.

`os.fs` is rooted at the filesystem root and accepts absolute paths
(`/home/me/a`, `C:/data`). Relative paths given to `os.fs` resolve against the
working directory. Capabilities can be attenuated. An attenuated capability
has the same type; operations outside its authority fail with a `Denied`
error. Inside a sub capability, paths are relative to its root:

```
let uploads = os.fs.sub("/var/app/uploads").read_only()
```

A library cannot open a file or a socket unless it is handed a capability. A
dependency's reach is visible in its signatures.

### 6.4 Tests and fakes

See section 9.2: every test block receives `os: TestOs` with fake capabilities.

### 6.5 Real-time functions [native enforcement]

```
const TAPS = 32

struct Filter { coeffs: List[Float], window: List[Float], pos: Int }

fn (var f: Filter) step(x: Float) -> Float uses realtime {
  f.window[f.pos] = x
  f.pos = (f.pos + 1) % TAPS
  var acc = 0.0
  for i in 0..TAPS {
    acc += f.coeffs[i] * f.window[(f.pos + i) % TAPS]
  }
  acc
}
```

`uses realtime` on a function or method declaration marks it
**real-time** (decision 0133). `realtime` is written in the uses clause but
is a discipline, not an effect: it is not part of the effect set and
plays no part in subsumption. A real-time function has a declared effect
set like any function with a uses clause; the only effect it may declare
is `ffi`. `realtime` in a closure, a function type, an interface method,
or a test is `XO0702`. In a real-time function, including its contract
clauses:

- **No allocation** (`XO1401`): no List, Map, or Set literal, string
  interpolation, `+` or `+=` on Str, List, or Bytes, slicing, closure,
  store into a Map, or prelude method outside the bounded set: scalar
  methods of Int, the sized integers, Float, Rune, Duration, and Time;
  `len`, `is_empty`, `get`, `first`, `last`, `contains`, `index_of`, and
  `set` (also `xs[i] = v`) on List; `len`, `is_empty`, `get`, `contains`,
  `get_or_fault` on Map and Set; `is_some`, `is_none`, `ok`, `err`,
  `or_fault` on Option and Result; the Atomic operations; `len` on Str,
  Bytes, Chan, Buf, and the builders; `contains`, `starts_with`,
  `ends_with`, `find` on Str. The arguments of `fault(...)` are exempt.
- **Bounded loops** (`XO1402`): every loop is a `for` over a range with
  constant ends or has `bound N` (3.5). `for (i, x) in xs.indexed()`
  does not build the pairs and is allowed with a bound.
- **Known callees** (`XO1403`): calls go only to real-time functions, the
  bounded prelude methods, and functions of a `use c` module, never to a
  function value or an interface method.
- **No recursion** (`XO1404`): the calls among real-time functions form
  no cycle, so the stack depth is bounded.
- **No blocking** (`XO1405`): no channel operation (or `for` over a
  channel), Mutex, `scope`, `select`, task, `dbg`, capability call, or
  declared effect other than `ffi`.

Faults stay allowed (they are bounded checks); a fault ends the section.

**Initialization** is all code that is not real-time: it allocates
freely and prepares what real-time code writes in place, typically
Lists of a fixed length written by index (`List.set` is in place on a
List nothing else references, decision 0046).

**Real-time sections** [native builds]. A call from code that is not
real-time to a real-time function is a real-time section. Inside it the
native runtime:

- defers frees: a reference count that reaches zero puts the value on a
  per-thread queue of 256 entries instead of freeing it (and what it
  holds); the queue is freed when the section ends. A full queue frees
  at once (counted in `XO_RTSTATS=1` output as `rt_overflow`).
- counts allocations; a section that allocated faults when it ends with
  `XO-F007` (``real-time function `f` allocated n times``), which catches
  what the rules cannot see: a shared List copied on write, the box of a
  recursive enum, a defaulted field.

The Go backend checks the rules and the loop bounds; it has no sections.

---

## 7. Concurrency [P1 basic, P2 deterministic]

### 7.1 Scopes and tasks

```
let page = scope s {
  let a = s.spawn(fn() { fetch_user(db, id) })
  let b = s.spawn(fn() { fetch_orders(db, id) })
  render(a.await?, b.await?)
}
```

- A scope is an expression; its value is the body's value.
- `s.spawn(f)` starts a **foreground** task and returns a `Task[T, E]` handle.
  The handle may be discarded.
- `s.spawn_background(f)` starts a **background** task (a daemon such as a
  cache evictor). Background tasks are cancelled automatically when the body
  finishes.
- `t.await` waits for a task and yields `T ! E`.
- **Exit rules.** When the body completes normally, the scope cancels its
  background tasks and waits for all tasks to finish. When the body exits
  early (`return`, `?`, `break`, error, or fault), the scope cancels all tasks
  and waits for them to unwind. No task ever outlives its scope.
- **Child failure.** If a task fails with an error that is not consumed by an
  `await`, or faults, the scope cancels everything else and the error
  propagates out of the scope as if by `?`: it must convert to the enclosing
  function's error type (error `XO0801` otherwise). A task that fails only
  because it was cancelled does not count as a failure.
- `s.cancel()` cancels all tasks of the scope (not the body).
- **Task groups.** `s.group()` returns a `task.Group`, a handle that can be
  stored in a struct or passed to functions so code can start tasks later
  (`g.spawn(f)`, `g.spawn_background(f)`). Tasks in a group belong to the
  scope that created it and never outlive it. A value of a type that contains
  a `Group` cannot be returned from, or stored outside, the creating scope
  (error `XO0802`). `main` receives `os.tasks`, a group that lives for the
  whole program, for services such as caches that refresh in the background.
  Tasks started on a group must not fail with an error the creating scope
  cannot convert (`XO0801`); a failing group task fails that scope. When the
  group's origin is not visible (it came from a field or parameter), a task
  started on it must not fail unless its handle is bound and awaited.
- The escape rule applies to values derived from that scope's `s.group()`.
  `os.tasks` lives for the whole program and may be stored anywhere.
- A task's error is consumed only if its handle is bound with `let` and
  awaited.
- Handles from an enclosing scope may be awaited inside `task.with_timeout`.
- Closures passed to `spawn` capture by value. Move only values are moved.
- The scope tree is the context: deadlines, cancellation, request ids, and
  trace spans flow down implicitly. There is no `ctx` parameter.
- Deadlines are set with `task.with_timeout` (see `spec/std.md`), which runs a
  closure in a child scope and returns `T ! Timed[E]`.

### 7.2 Cancellation

- Cancellation is **sticky**: once a task is cancelled, every cancellation
  point it reaches reports cancellation.
- Blocking operations are cancellation points: effect calls, channel
  operations, `await`, `clock.sleep`, `Mutex.with` and `Mutex.with_read`
  while waiting.
- A blocking operation that already returns a `Result` reports cancellation
  as its `Canceled` error variant (`http.ClientErr.Canceled`,
  `FsErr.Canceled`, ...). Code may map it to its own error, for example
  `FetchErr.Cancelled`, but cannot continue doing blocking work.
- A blocking operation that cannot fail (`clock.sleep`, `ch.send`, `ch.recv`,
  `t.await` in a cancelled task) unwinds the task immediately, running
  `defer` blocks.
- `clock.sleep_or_cancel(d) -> () ! ClockErr` is the sleep that reports
  cancellation as a value: when the task is cancelled before or during the
  sleep it returns `Err(ClockErr.Canceled)` instead of unwinding, so code
  that must return its own error (a retry loop's `FetchErr.Cancelled`)
  can. Cancellation stays sticky: the task's next cancellation point
  reports it again. Inside deferred code it sleeps in full, like `sleep`
  (decision 0088).
- Reading standard input (`stdio.read_line`, `read_all`, `lines`) is a
  cancellation point also while it waits for input: a cancelled reader
  gets `Err(IoErr.Canceled)` at once, and no input is lost (a line that
  arrives later goes to the next read).
- `task.is_canceled() -> Bool` reports the current task's state without
  blocking.
- `defer` blocks run shielded (3.5).

### 7.3 Channels

```
let ch = Chan[Job].new(cap: 64)
ch.send(job)                // blocks when full
let j = ch.recv()           // Job? : None when closed and drained
ch.close()

select {
  j = jobs.recv() => handle(j)
  results.send(r) => count += 1
  after 5s => log.warn("idle")
}
```

- `ch.try_send(x) -> Bool` and `ch.try_recv() -> T?` never block.
- `for x in ch { ... }` receives until `ch` is closed and drained; each receive
  is a cancellation point.
- `after` arms require the `clock` effect.
- Sending moves move only values.

### 7.4 Deterministic scheduling [P2]

Every `xo test` runs its tasks on a deterministic scheduler, seeded with
`--seed=N` (default 1): one task runs at a time, and at each scheduling point
the next task is chosen from the seed, so the same seed always produces the
same interleaving. Scheduling points are channel operations (including
`select`, `try_*`, and `close`), `await`, `Mutex.with`, `Mutex.with_read`,
`Semaphore.with`,
`Once.get`, `spawn`, `Atomic` operations, `sleep`, `task.is_canceled()`,
`task.stop_requested()`, and effect calls. Failing seeds are printed and can
be pinned with `test "x" seed 8812`.

`xo test --explore=N` reruns each concurrent test that passed under N further
seeds and reports the first failing seed. `xo run --seed=N` (or `XO_SEED=N` for
any built binary) uses the same scheduler with the real clock; interleavings
are then reproducible as long as timer and I/O timing does not decide them.

An Xo function that Go calls on a goroutine of its own (`use go`, for
example `sync.WaitGroup.go_`) runs as a scheduled task: Go's goroutine waits
until the scheduler admits the call, which happens only at a scheduling
point after Go's goroutines have settled, in a fixed order (by callback,
then arguments), so the seed decides how callbacks interleave. Once an Xo
function has crossed into Go, the return of every Go call is a scheduling
point. Go code woken by a Go timer or I/O of its own is not reproducible
(decision 0073).

The deterministic scheduler belongs to the Go backend. Native builds
(`--backend=llvm` and `--backend=arm64`, decision 0068) run tasks on the real
scheduler only, on a pool of threads: `xo run --seed` is refused with them and
`XO_SEED` has no effect, and a native program whose output depends on how its
tasks interleave may print differently from run to run. The rest of core 7
means the same on every backend.

### 7.5 Process signals and graceful shutdown

Shutdown has two phases, so programs can finish queued work without fighting
sticky cancellation:

1. **Stop requested.** The first SIGINT or SIGTERM cancels nothing. It sets
   the program's stop state: `task.stop_requested()` returns `true` and the
   channel `task.stopping()` is closed, so loops can `select` on it. Servers
   started with `serve` stop accepting new connections and finish in flight
   requests. Readers stop reading: a read of standard input that is
   blocked, or starts later, returns `Err(IoErr.Canceled)` (decision 0088).
   Workers drain what is queued.
2. **Cancel.** When `main`'s body has not returned after the grace period
   (default 30s, set with `os.proc.set_grace(d)`), or on a second signal,
   `main`'s root scope is cancelled as in 7.2: blocking calls unwind and
   `defer` blocks run. A third signal exits immediately with status 130.

A program that ignores phase 1 behaves as before, only 30 seconds later; a
program that wants the old behavior calls `os.proc.set_grace(0s)`.

```
fn reader(os: Os, queue: Chan[Str]) -> () ! IoErr {
  defer queue.close()              // workers drain the queue, then exit
  while {
    match os.stdio.read_line() {
      Ok(Some(line)) => queue.send(line)
      Ok(None) => return Ok(())      // end of input
      Err(Canceled) => return Ok(()) // stop requested (or cancelled)
      Err(e) => return Err(e)
    }
  }
  Ok(())
}
```

In tests, `os.proc.signal_after(d)` delivers the first signal to a program run
by `testing.run_main`; `os.proc.signal_after(d)` called again delivers the
next one.

Under `testing.run_main`, a third signal makes `run_main` return 130 after
`main` has unwound (the test process itself does not exit), and requests
handled in memory by a fake network observe the stop state but are not
cancelled by it.


---

## 8. Faults [P1]

A **fault** is a bug, not an error: overflow, out of bounds, failed contract,
failed `Int32(x)` conversion, evaluated hole, explicit `fault("msg")`.
`fault` has type `Never`.

- A fault terminates the current task. Its scope treats it like a failed child
  and cancels siblings. A fault reaching `main` exits the process with status
  70 and writes a crash bundle (section 11.2).
- `task.isolate(f) -> T ! Isolated[E]` runs `f` in a child scope and converts a
  fault into a value. `std/http` isolates every request and returns 500.
- Every fault message includes the evaluated values:
  `index 7 out of range for List of length 5 at users.xo:41:18`.
- The native backends unwind a fault the same way (decision 0085: deferred
  code, scopes, Mutexes, and references are released on the way); they
  print the fault without its frames, write no crash bundle, and
  `Fault.frames` of `task.isolate` is empty there.

There is no undefined behavior. Debug and release builds have identical
semantics; release builds may only disable contract checks that are explicitly
marked `requires(debug)` or `ensures(debug)`.

- `xo build --release` is a release build (decision 0099): the clauses
  marked `(debug)` are compiled out on every backend, `requires(debug) c
  else E` included (the function no longer returns that error). Every
  other contract, every invariant, and every fault check stays. A release
  build fails while an xo.work workspace overrides a required module
  (`XO1308`, 4.2) and is the only build that accepts
  `--error-args=false` (11.1). Error frames are recorded in both.
- Every other build is a debug build: `xo build` without `--release`,
  `xo run`, `xo test` (whose contract fuzzing checks the debug clauses
  too), and `xo replay`, which rebuilds in the mode recorded in the
  program's build information. `--ci` is a gate, not a mode: it does not
  imply `--release`; a pipeline that ships the binary passes both.

---

## 9. Contracts and tests [P1]

### 9.1 Contracts

```
fn transfer(var from: Account, var to: Account, amt: Money) -> () ! TxErr
  requires amt > 0
  requires from.balance >= amt else TxErr.Insufficient
  ensures from.balance + to.balance == old(from.balance + to.balance)
{ ... }

struct Account { id: Int, balance: Money } invariant balance >= 0
```

- `requires expr` faults on violation. `requires expr else E` returns `Err(E)`
  instead, which makes input validation declarative. A function may consist
  only of contracts and an empty body.
- `ensures` may reference `result` (the success value) and `old(expr)`.
- `invariant` is checked after construction (including from defaults) and
  after every `var` method.
- Contract failures print every sub expression's value:
  ```
  ensures failed: from.balance + to.balance == old(from.balance + to.balance)
    from.balance = 40   to.balance = 50   old(...) = 100
  ```
- `xo test` fuzzes every function with contracts using generated inputs that
  satisfy `requires`, and checks `ensures`. Capability parameters receive
  seeded fakes. Contract clauses must be pure.

### 9.2 Tests

```
test "fetch missing user" {
  let store = new_store()
  expect(store.get(7)) == Err(LoadErr.NotFound(id: 7))
}

test "reverse twice" for xs: List[Int] {
  expect(xs.reverse().reverse()) == xs
}

test "retries after 503" {
  let calls = Atomic[Int].new(0)
  os.net.handle("GET https://api.test/x", fn(_req) {
    if calls.add(1) < 3 { http.empty(503) } else { http.text(200, "ok") }
  })
  let body = get_with_retry(os.net, os.clock, os.rand, "https://api.test/x")?
  expect(body) == b"ok"
  expect(os.clock.sleeps()) == [100ms, 200ms]
}
```

- `test` blocks may live in any file. They are compiled only by `xo test`.
- A test body has type `() ! Error`, so `?` fails the test with the error.
- Inside a test block, and anywhere in a `_test.xo` file for `expect` and
  `testing`, three names are predeclared:
  - `os: TestOs` with fake capabilities (in memory filesystem, virtual clock,
    seeded rand, network that fails unless a handler is registered, captured
    stdio, fake env and proc). Fakes have extra control methods such as
    `os.clock.advance(d)` and `os.fs.fail_next(...)`; see `std/testing` in
    `spec/std.md`. Each fake satisfies the matching capability interface.
  - `expect(x)` followed by any comparison: `expect(a) == b`, `expect(n) <= 3`.
    `expect(cond)` alone asserts a `Bool`.
  - `testing`, the `std/testing` module.
- The virtual clock auto advances: when every task in the test is blocked,
  time jumps to the next timer, so sleeps and timeouts finish instantly.
- `for` on a test makes it a property test over generated values.
- Failures found by fuzzing or property tests are shrunk to a minimal input.
  A shrunk contract fuzz failure is written as a permanent test to
  `regressions_test.xo` in the module itself (so it can call private
  functions), named `regression: <function> (seed N)`, but only if the module
  still type checks with it; otherwise `xo test` says why and the seed
  reproduces the failure. Shrunk property test failures are reported with
  their seed and input.
- Real capabilities require `test "name" uses net { ... }` and
  `xo test --real`; `os` then holds real capabilities for the listed effects.

---

## 10. Toolchain and diagnostics [P1]

### 10.1 Commands

| Command | Purpose |
|---|---|
| `xo build [--targets=all\|os/arch,...] [--no-gc] [--tzdata=embedded\|system] [--ci] [--release [--error-args=false]]` | Build static binaries; `--no-gc` on a native backend without a garbage collector (below); `--tzdata=system` reads time zones from the host instead of embedding the ones the program uses (std.md 13); `--ci` fails before building on an unformatted file (10.5) or a `dbg` left in code (11.4); `--release` is a release build (8): `requires(debug)` and `ensures(debug)` compiled out; `--release --error-args=false` drops parameter values from error frames (11.1). Builds are debug builds otherwise, and `xo run` and `xo test` always are |
| `xo run` | Build and run for the host |
| `xo check [--no-gc] [--fix]` | Parse and type check all targets, no codegen; `--no-gc` adds the native build check (XO1201); `--fix` first applies the machine fixes (below) |
| `xo test [--seed=N] [--explore=N] [--cases=N] [--real]` | Tests, contract fuzzing, property tests (`--cases` per property test and fuzzed function, default 100); deterministic scheduling. Doc comment examples are [P2] (1.2) |
| `xo dump <pid>` | Task tree of a running program (11.4) |
| `xo mcp` | Model Context Protocol server over stdio: `check` (with an optional `fix`), `query`, `edit`, `explain`, `fmt`, `test`, `build` tools returning compact JSON |
| `xo lsp` | Language Server Protocol server over stdio for editors: diagnostics with quick fixes, hover, definition, references, rename, document symbols, semantic tokens, formatting |
| `xo serve` | Resident checker on a unix socket; `xo check` and `xo query` use it automatically when it answers |
| `xo fmt` | Canonical format, no options |
| `xo fix [--dry-run] [--from=draft-0.N] [--edition=YYYY]` | Apply all machine fixes until none apply; `--from` migrates code written for an older draft (interpolation, prelude variants, `let _ =` discards, scope timeouts, Map index reads) |
| `xo vet` | Advisory checks, never errors: checker warnings plus `XO0207` (`??` precedence trap), `XO0208` (each `ignore_err` with its reason), `XO0703` (declared effect never performed), `XO0901` (`dbg` left in code), `XO1103` (use of Go packages per module) |
| `xo query <kind> ...` | Semantic queries (10.3): `type`, `def`, `callers`, `effects`, `holes`, `api`, `imports`, `deps` |
| `xo edit <op> ...` | Semantic edits (10.4) |
| `xo replay`, `xo dump` | Debugging (section 11); `xo why` and `xo debug` are [P2] and not implemented yet (11.3, 11.5) |
| `xo doc` | Docs from doc comments |
| `xo get <path>[@version]`, `xo mod tidy`, `xo vendor`, `xo mod clean`, `xo mod proxy serve` | Dependencies (4.2, 4.3): require a module version, tidy xo.mod and xo.lock, copy dependencies into vendor/, empty the module cache, serve a module cache as a module proxy (decision 0126). Every command takes `--offline` and `--workspace=off` |
| `xo init <path>`, `xo publish [--check]` | Start a module (xo.mod, xo.work gitignored); check a release against the previous tag (4.2) |
| `xo explain <code>` | Long form explanation of a diagnostic |
| `xo bind [--json] <Go path>...`, `xo bind --c [--link x]... [--uses none\|e,...] <header>...` | Generate the bindings of Go packages (`use go`, 4.4) or C headers (`use c`, 4.5) into the project's binding cache |

The commands that report diagnostics or results accept `--json`: `parse`,
`check`, `build`, `test`, `fmt`, `fix`, `vet`, `query`, `edit` (always
JSON), `doc`, `explain`, and `bind`. `run`, `replay`, `dump`, `serve`, and
`version` print the program's or the tool's own output. When stdout is not a
terminal, JSON is the default for `query`.

**Check and fix.** `xo check` is read only. `xo check --fix` first
applies every unambiguous machine fix, the same set `xo fix` applies
(without `--from`), round after round until none applies, reformats the
files it changed, and then reports the diagnostics that remain, with the
exit status of `xo check` on them. Its JSON adds `applied`, the list of
fixes made (`{file, line, rule, title}`, `rule` being the diagnostic
code); plain `xo check --json` has no `applied` field. This is the agent
loop's check: an agent runs `xo check --fix` and then `xo test`, so a
diagnostic with an exact fix never costs a run (decision 0096). The MCP
`check` tool takes `fix: true` for the same.

**Builds without a garbage collector.** `xo build --no-gc` (also `xo run`,
`xo test`, and `xo check`) builds on a native backend whose memory is
reference counted instead of collected: arm64 on darwin/arm64 and
linux/arm64 hosts, LLVM otherwise; `--backend=llvm` or `--backend=arm64`
picks one, and `--backend=go` with `--no-gc` is a usage error. After type
checking and before any code generation, a check reports every construct,
std API, or effect the native backends do not support as error `XO1201` at
its site, naming the feature and saying whether support is *planned* (build
without `--no-gc` until then) or *impossible* (code from a `use go` package
needs Go's runtime). A program the check accepts builds; the check is
derived from the native code generator itself, so the two cannot disagree.
`xo check --no-gc` runs the same check without building (programs only: a
module without `main` has nothing to lower). The Go backend's deterministic
scheduler (`--seed` of `xo run`, `--explore`) and recording (`--record`) are
not available on native builds (decision 0083). `xo test --no-gc` builds a
native test binary and runs each test in a process of its own against the
fakes of 9.2 (virtual clock, seeded `Rand`, fake environment, streams, and
network, MemFs, FakeProc, `os.tasks`) with the same report as the Go
backend; a fault fails only its test.
Property tests run there with the Go backend's cases and shrunk inputs
(one process per case, decision 0120), `--real` gives real capabilities
for the declared effects (a test declaring `clock`, `net`, or `proc` runs
on the real clock there), contract fuzzing (9.1) is reported as skipped
(decision 0085), and error
traces are recorded natively but printed only in main's error report
(decision 0104), and fault frames are not recorded natively.

**Hybrid builds.** `xo build --hybrid` (Go backend) compiles the hot leaf
functions of the main module (a loop over `Int`, `Float`, `Bool`, and
`List`s of those or of structs of `Int` and `Float` fields, with no
allocation) to native assembly linked into the Go program; everything
else, and every program the native backends cannot lower, stays Go.
Behavior is the Go backend's, faults included, except that fault frames
omit the assembly functions and `Float` results may differ in the last bit
where the Go backend fuses a multiply and an add (decision 0084). Loops
in assembly yield to the scheduler and the garbage collector as Go code
does (decision 0131).

### 10.2 Diagnostic format

```json
{
  "code": "XO0201",
  "severity": "error",
  "message": "result of `fetch_user` is not handled",
  "span": {"file": "api/users.xo", "line": 18, "col": 3, "end_line": 18, "end_col": 24},
  "notes": [{"message": "`fetch_user` can fail with LoadErr", "span": {"file": "users/fetch.xo", "line": 4, "col": 1}}],
  "fixes": [
    {"title": "propagate with ?", "edits": [{"span": {"file": "api/users.xo", "line": 18, "col": 24, "end_line": 18, "end_col": 24}, "replace": "?"}]}
  ]
}
```

- Codes are stable forever within an edition.
- Codes are grouped: `01xx` lexical and naming, `02xx` errors and results,
  `03xx` match, `04xx` types and generics, `05xx` mutation and moves,
  `06xx` resolution, `07xx` effects, `08xx` concurrency, `09xx` holes and
  contracts, `10xx` platform, `11xx` Go interop, `12xx` native builds
  without a garbage collector (`XO1201`: not available with `--no-gc`,
  planned or impossible; 10.1). Every code is listed in 10.6.
- The compiler reports all errors it can, not just the first, and never
  cascades: a bad expression has type `<error>` and suppresses dependent
  diagnostics.
- **Habits from other languages** (decision 0087). Code written with the
  syntax or library names of Go, Rust, TypeScript, Python, Swift, or
  Kotlin gets one diagnostic per habit that names the habit and the Xo
  form, with a machine fix when the rewrite is unambiguous, and checking
  continues as if the Xo form had been written:
  - Syntax that the grammar would otherwise reject with a generic code
    (`XO0160`, `XO0168`, `XO0172`, `XO0175`) is error `XO0177` (syntax
    from another language): for example `func`, `x := v`, `let mut`,
    `T: Hash + Eq`, `Vec<T>`, `[]T`, `var x` or `&x` at a call site,
    `c ? a : b`, `switch`/`case`, `when`, `elif`, `and`/`or`/`not`,
    `for i := 0; i < n; i++`, Go `for k, v := range m` (reported by the
    checker, which picks the Xo form for the type of `m`), `x++`, `throw`,
    `try f()`, `|x| e`, `(x) => e`, `lambda`, `x as T`, `A::B`, `impl`,
    `trait`, `): T` and Go's bare result types, `(T, error)` results,
    backtick templates, f-strings, and `/* */` comments. `xo explain
    XO0177` lists them.
  - When a specific code already describes the error, it keeps that code
    and gains the habit in a note and the fix: `XO0601` for names from
    other languages (`nil`, `null`, `this`, `len(x)`, `append`, `print`,
    `String`, `Vec`, `int`, `.unwrap()`, `.length`, camelCase methods,
    Python's `t = 0` declaring by assignment), `XO0169` (commas between
    match arms), `XO0173` (`use` after declarations), `XO0701` (an effect
    argument left out of a parameter type, which stays an abstract effect
    variable), `XO0408` (TypeScript's `const x = f()`), `XO0110` (Kotlin's
    `"$name"`, literal text in Xo), `XO0205` (`x?.f` optional chaining in
    a function that does not return an optional), `XO0403` (a missing
    `reason`), `XO0404` (JSON for tuples), `XO0101`, `XO0502`, and the lexical
    codes `XO0150` (`#` comments, `#[derive]`), `XO0152` (`'text'`), and
    `XO0153` (Swift's `\(x)`).
  - A habit gets a fix only when one rewrite keeps the meaning; where the
    Xo form needs a decision (`x!!` needs a reason, `try`/`catch` needs a
    Result handling choice, `print` needs a capability in scope) the
    diagnostic names the form and has no fix.
- A suggestion from edit distance (`did you mean`) is a note; it is also a
  fix only when it is clear: a unique closest candidate at most half the
  name's length away, for names of three or more characters.
- A diagnostic's first fix is the one `xo fix` and `xo check --fix` apply
  (10.1); fixes whose edits overlap in one round wait for the next round.
  `xo check --fix --json` adds `"applied": [{"file", "line", "rule",
  "title"}]` next to `modules` and `diagnostics`.

### 10.3 Queries

```
xo query type api/users.xo:18:7
xo query def http.server
xo query callers users.fetch_user
xo query effects api.get_user          // with the call chain per effect
xo query holes
xo query api github.com/acme/billing   // exported surface of a module
xo query imports example.com/notes/domain   // its `use` lines
xo query deps example.com/notes/server      // the same, transitively
xo query hash users.fetch_user              // content hash of a definition (4.3)
```

`hash` (decision 0125) answers `{list}`, one entry per definition the
name resolves to (a name, `Type.method`, `pkg.name`, or `<package
path>.name` of the module holding `--dir`): `{id, package, name, kind,
pub, hash, own, refs, file, line}`. It needs no check run.

`imports` and `deps` (decision 0095) answer
`{package, transitive, imports, packages}`: each entry of `imports` is a
`use` line `{from, path, pos, std, go, test, missing, denied}`, where
`denied` is the `XO1312` message when a `deny` rule forbids the line, and
`packages` lists the distinct paths reached. `deps` follows every
imported package of the program (not std or Go packages).

### 10.4 Semantic edits [P1: rename, add-field, change-sig]

```
xo edit rename users.fetch_user users.load_user
xo edit add-field User created: Time = Time.UNIX_EPOCH
xo edit change-sig users.load_user "(db: Db, id: UserId) -> User ! LoadErr"
```

Edits update every reference atomically and are rejected if the result does
not type check.

- References are found through the checker's resolved symbols, never by
  text search. An edit of a program that does not check clean is rejected
  before anything is computed.
- `rename` renames a function, method, or constant and every call and use
  as a value. The new name may repeat the target's qualifier.
- `add-field` adds `name: Type` to the struct declaration and
  `name: default` to every struct literal of the type. The default is a
  migration value; it is not kept as a declared field default. Without
  `= default` the edit is rejected when struct literals exist.
- `change-sig` replaces the parameter list, result, and error type (not
  the clauses). Call arguments are matched to the new parameters by name;
  a parameter renamed in place keeps its position, and its uses in the body
  are renamed. New parameters with a default need no argument. A call that
  passes an argument for a removed parameter, or lacks one for a new
  parameter without a default, rejects the edit, listing those call sites.
  Arguments keep their order (and so their evaluation order); arguments
  that no longer match their position become named.
- The result is a JSON summary: `{op, target, ok, written, reason, files,
  edits, diagnostics, sites}`. `diagnostics` holds the errors that rejected
  the edit; `sites` the call sites or literals that blocked it. `--dry-run`
  checks the edit without writing.

### 10.5 Formatting

`xo fmt` produces one canonical layout from the AST. There are no options.
`xo build` fails on unformatted files in CI mode (`--ci`).

### 10.6 Diagnostic codes

Every code the toolchain reports, its default severity, and what reports
it: the lexer and parser (`xo parse` and every later command), the checker
(`xo check`), `xo vet` (advisory, never an error except `XO0901` under
`xo build --ci`), the no GC check (`--no-gc`, 10.1), and module
resolution (`modules`: every command that loads a program reports these
on the xo.mod, xo.lock, or xo.work line, and so do `xo get`, `xo mod
tidy`, and `xo vendor`; 4.2, 4.3), and `xo publish` (`publish`).
`xo explain <code>` prints the long form of each. The registry is
`internal/diag/codes.go`; a test keeps this table equal to it.

| Code | Severity | Reported by | Meaning |
|---|---|---|---|
| `XO0101` | error | checker | name breaks the enforced naming rules (core 1.5) |
| `XO0110` | error | checker | unused local binding or import (core 3.1) |
| `XO0150` | error | lexer | character that cannot start any token |
| `XO0151` | error | lexer | string, raw string, bytes, or triple quoted string not closed |
| `XO0152` | error | lexer | rune literal that is empty, unterminated, or holds more than one character |
| `XO0153` | error | lexer | unknown or malformed escape sequence |
| `XO0154` | error | lexer | bad digit, misplaced `_`, missing digits, or out of range integer |
| `XO0155` | error | lexer | unknown duration unit, fractional or non decimal duration |
| `XO0156` | error | lexer | `${` without a matching `}` in the same string |
| `XO0157` | error | lexer | `${}` with no expression |
| `XO0158` | error | lexer | `use` not followed by a valid import path |
| `XO0160` | error | parser | a token that does not fit the grammar here |
| `XO0161` | error | parser | list element followed by a newline instead of a comma (core 1.3) |
| `XO0162` | error | parser | `else` must be on the line of the closing brace (core 1.3) |
| `XO0163` | error | parser | unparenthesized struct literal in a condition (core 1.4) |
| `XO0164` | error | parser | `(`, `[`, or `{` not closed before end of file |
| `XO0165` | error | parser | syntax that is not a pattern (core 3.5) |
| `XO0166` | error | parser | `while cond` used as an expression; only `while { }` is one |
| `XO0167` | error | parser | top level declaration (fn, struct, const, ...) inside a block |
| `XO0168` | error | parser | statement (let, expression, ...) at top level |
| `XO0169` | error | parser | match or select arms separated by `,` instead of newlines |
| `XO0170` | error | parser | several effects in a function type must be parenthesized (core 15) |
| `XO0171` | error | parser | `..` used more than once or not last in pattern fields |
| `XO0172` | error | parser | function declaration without a body |
| `XO0173` | error | parser | `use` after the first declaration (core 15: File) |
| `XO0174` | error | parser | bare `self` parameter outside an interface method signature |
| `XO0175` | error | parser | two statements or arms on one line |
| `XO0176` | error | parser | `pub` before something that cannot be exported |
| `XO0177` | error | parser | syntax from another language (Go, Rust, TypeScript, Python, Swift, Kotlin); names the habit and the Xo form (decision 0087) |
| `XO0178` | error | parser | a line starts with a binary operator (`\|\|`, `&&`, `+`, `==`, ...); end the previous line with it (core 1.3, decision 0136) |
| `XO0201` | error | checker | Result value is never used (core 2.6) |
| `XO0202` | error | checker | error type does not convert to the enclosing error type (core 2.6) |
| `XO0203` | error | checker | success of a Result typed T must be written Ok(x) (core 2.6) |
| `XO0204` | error | checker | `?` on an Option in a function returning a Result (core 2.5) |
| `XO0205` | error | checker | `?` where the enclosing function cannot return the failure (core 2.5, 2.6) |
| `XO0206` | error | checker | `let _ =` discards a Result; use `.ignore_err("reason")` (core 2.6) |
| `XO0207` | warning | vet | method chain on the right of `??` starts at a literal, so it applies only to the default (core 15 precedence) |
| `XO0208` | note | vet | `.ignore_err("reason")` discards an error; listed with its reason for review (core 2.6) |
| `XO0301` | error | checker | match does not cover every case (core 2.3) |
| `XO0302` | error | checker | pattern does not fit the type of the matched value (core 3.5) |
| `XO0303` | error | checker | refutable pattern where an irrefutable one is required (core 3.5) |
| `XO0304` | error | checker | alternatives of an or pattern bind different names or types (core 3.5) |
| `XO0305` | warning | checker | `_` arm hides variants of an enum declared in this module (core 3.5) |
| `XO0401` | error | checker | value of the wrong type; only three implicit conversions exist (core 2.10) |
| `XO0402` | error | checker | type arguments cannot be inferred (core 2.7) |
| `XO0403` | error | checker | wrong arguments or fields: count, names, missing, or duplicates |
| `XO0404` | error | checker | type does not implement a required interface or constraint (core 2.4) |
| `XO0405` | error | checker | type cannot be inferred here; annotate it |
| `XO0406` | error | checker | operator, index, slice, or iteration not defined for the type |
| `XO0407` | error | checker | name used as the wrong kind of thing (a type as a value, a value that is not callable) |
| `XO0408` | error | checker | const initializer is not a compile time value (core 3.1) |
| `XO0409` | error | checker | invalid explicit conversion (core 2.1) |
| `XO0410` | error | checker | reading a Map by index; use `get` or `get_or_fault` (core 3.6) |
| `XO0420` | error | checker | constant std/time layout is invalid: unknown field, bad field form, or stray brace (std.md 13.3) |
| `XO0501` | error | checker | var method called on a non var binding (core 3.4) |
| `XO0502` | error | checker | assignment to an immutable binding or to a captured variable (core 5.2, 3.3) |
| `XO0503` | error | checker | one variable or overlapping places passed to two `var` parameters of one call (core 5.2, decision 0111) |
| `XO0510` | error | checker | use of a moved Buf, StrBuilder, or BytesBuilder (core 5.3) |
| `XO0520` | error | checker | mutation of a copied or stored collection element is never written back (core 5.2) |
| `XO0601` | error | checker | name does not resolve (core 4.3) |
| `XO0602` | error | checker | import path does not yield a valid local name (core 4.1) |
| `XO0603` | error | checker | name declared twice in one scope, or a predeclared name redefined (core 3.1, 1.4) |
| `XO0604` | error | checker | cyclic module imports (core 4.1) |
| `XO0605` | error | checker | name is not `pub` in its module, or the std type is an opaque handle (core 1.5) |
| `XO0606` | error | checker | method declared outside the module of its type (core 3.4) |
| `XO0607` | error | checker | `break` or `continue` outside a loop, or an unknown label (core 3.5) |
| `XO0701` | error | checker | effect performed outside the declared set (core 6.2) |
| `XO0702` | error | checker | unknown effect label (core 6.1) |
| `XO0703` | warning | vet | effect declared in `uses` is never performed (core 6.2) |
| `XO0801` | error | checker | child or group task error does not convert to the enclosing error type (core 7.1) |
| `XO0802` | error | checker | scope handle, or a value containing a task group, escapes its scope (core 7.1) |
| `XO0803` | error | checker | task started without a scope handle (core 7.1) |
| `XO0804` | warning | checker | value read in one `Mutex.with` is written back in a later one (core 5.4) |
| `XO0900` | warning | checker | typed hole report (core 3.7) |
| `XO0901` | warning | vet | `dbg(...)` left in code (core 11.4) |
| `XO1001` | error | reserved | `std/sys/<os>` used outside the matching target arm (core 12.2) |
| `XO1101` | error | checker | Go package cannot be bound (not found, does not build, main package, or binding error) |
| `XO1102` | error | checker | Go name exists but its signature does not map to Xo, so the binding skipped it |
| `XO1103` | note | vet | per module report of `use go` packages and the functions that perform `go` (decision 0041) |
| `XO1104` | error | checker | C header cannot be bound (not found, clang cannot read it, a `link` file is missing, or an unknown effect in its `uses`) |
| `XO1105` | error | checker | C function exists in the header but its signature does not map to Xo, so the binding skipped it |
| `XO1201` | error | no GC check, checker (`use c` without `--no-gc`) | construct, std API, or effect not available without a garbage collector (`--no-gc`); planned or impossible |
| `XO1301` | error | modules | malformed xo.mod require or deny line, xo.lock, or xo.work line, or a deny rule naming a package that does not exist (core 4.2, 4.3) |
| `XO1302` | error | modules | module version cannot be fetched from git or a module proxy (XO_PROXY): repository, tag, or version missing, or its xo.mod declares another path |
| `XO1303` | error | modules | module version is not in the module cache and the build is offline (XO_OFFLINE, --offline) |
| `XO1304` | error | modules | module tree does not match its xo.lock content hash (core 4.3) |
| `XO1305` | error | modules | xo.lock has no hash for a module version the build uses; run `xo mod tidy` |
| `XO1306` | error | checker | import of a package from a module that xo.mod does not require |
| `XO1307` | error | modules | vendor/ does not match xo.mod; run `xo vendor` |
| `XO1308` | error | build | release build (`xo build --release`, or `--ci`) while an xo.work workspace overrides required modules |
| `XO1309` | error | checker | call into a dependency performs an effect above its ceiling (`require ... uses ...` in xo.mod, `use ... uses ...` in xo.work) |
| `XO1310` | error | publish | `xo publish`: a minor or patch release removes or changes an export, or adds an effect to one |
| `XO1311` | error | publish | `xo publish`: the release cannot be made (not in a git repository, a subdirectory module path not ending in its directory, uncommitted changes, tag exists, version not above the last, wrong /vN path) |
| `XO1312` | error | checker | `use` of a package that a `deny` rule of xo.mod forbids (decision 0095) |
| `XO1313` | error | modules | a fetched module version disagrees with the checksum log (XO_SUMDB), or the log's signature or proofs do not verify (4.3, decision 0124) |
| `XO1314` | error | modules | the checksum log cannot be consulted, or XO_PROXY, XO_SUMDB, XO_NOPROXY, or XO_NOSUMDB is malformed (4.2, decisions 0124, 0126) |
| `XO1401` | error | checker | allocation in a real-time function (literal, interpolation, `+` on Str or List, closure, growth method) |
| `XO1402` | error | checker | loop without a provable bound in a real-time function, or a `bound N` that is not a constant or is exceeded by a constant range |
| `XO1403` | error | checker | call in a real-time function of a function that is not real-time, a function value, or an interface method |
| `XO1404` | error | checker | recursion among real-time functions |
| `XO1405` | error | checker | blocking operation (channel, Mutex, scope, select, task) or capability effect in a real-time function |

`XO1001` is reserved: no `std/sys/<os>` module exists yet (14), so it is
never reported.

Faults (8) that have a stable code print it in their message:

| Code | Fault |
|---|---|
| `XO-F001` | deadlock: every task is blocked on another task (11.4) |
| `XO-F004` | `Mutex.with` or `with_read` called re entrantly on a mutex the task holds (5.4) |
| `XO-F005` | a shielded `defer` ran longer than 5 seconds (3.5) |
| `XO-F006` | a loop started more iterations than its `bound N` (3.5) |
| `XO-F007` | a real-time section allocated (6.5, native builds) |

---

## 11. Debugging [P1 basics, P2 replay]

### 11.1 Error traces

Each `?` that propagates an error appends a frame:

```
LoadErr.Db(msg: "timeout")
  at users.fetch_user(id=42)          users/fetch.xo:18
  at api.get_profile(req=GET /u/42)   api/profile.xo:7
```

- Frames record function, location, and parameters that implement `Debug`,
  truncated to 64 bytes each. `Secret[T]` prints `<redacted>`. Capability
  parameters are left out. Frames are built only when an error
  propagates; the success path records nothing.
- Frames are recorded in every build, debug and release (decision 0092).
  A frame keeps a copy of each parameter value and produces its `Debug`
  text only when the trace is printed (the error report of `main`, a
  crash bundle, a test failure, a handler's error log), formatting no
  more of a value than the 64 bytes it shows. The text is what it would
  have been at the `?`: values are immutable or copied on write, and the
  few whose `Debug` text can change in place (`Buf`, `Atomic`) are
  formatted when captured.
- `xo build --release --error-args=false` records the function and
  location only and prints `(...)` for the parameters:
  `at users.fetch_user(...)   users/fetch.xo:18`. It is a usage error
  without `--release`.
- **[later]** A last line naming the task and its scope
  (`in task "handler#913" (scope api.serve)`) is planned.
- Native backends (`--no-gc`, LLVM, arm64) record the same frames
  (decision 0104) and print them in main's error report; test reports,
  handler logs, and crash bundles do not show them there yet (10.1).

### 11.2 Crash bundles

On a fault reaching `main`, an unhandled error from `main`, or an isolated
fault in a server, the runtime writes `<name>-<time>.xocrash` containing: the
error or fault with frames, the task tree, build id, source hashes, target, and
**[P2]** the effect trace for the failing request. Native builds (`--no-gc`)
write none yet (decision 0085).

### 11.3 Record and replay [P2]

All effect calls go through capability values, so the runtime can log each
call and its result, plus scheduler decisions.

- `xo run --record=trace.xotrace` (or `XO_RECORD=trace.xotrace` for any built
  binary) records a full run. Values of type `Secret[T]` are recorded redacted;
  `xo replay` then requires them from the environment and exits with status 3
  naming the missing one.
- Calls into Go packages (`use go`, 4.4) are recorded at the boundary
  (decision 0093). A call whose arguments and results are all boundary
  values (scalars, `Str`, `Bytes`, `Time`, `Duration`, lists and maps of
  those, `go.Error` with its text and Go type) and that takes no callback
  is an effect `go.call(<name>, <argument hash>)` with its results; `xo
  replay` checks the name and the hash (a different argument diverges)
  and returns the recorded results without calling Go. A Go panic is
  recorded and raised again as the same fault. A call whose result, an
  argument, or the receiver is a Go handle, or that takes an Xo callback,
  runs again in the replay: a handle's Go state exists only if the calls
  that make and change it run (`sync.WaitGroup.wait` must wait for real).
  The trace marks such a call (`"x": "handle"` or `"callback"`), the
  replay checks that it comes at the same point, and `xo replay` reports
  it once per name: ``re executed foreign call `os.open` ``. Functions
  marked `replay rerun` (4.4) run again and are not recorded. A trace
  written before decision 0093 (no `"foreign"` field in its header) runs
  every Go call again, as before.
- Calls into C (`use c`, 4.5) follow the same rule once native builds
  record (10.1: they do not yet): a call whose arguments and results are
  boundary values is answered from the trace as `ffi.call(<name>,
  <argument hash>)`, and one that passes or returns a C handle runs
  again. Each binding's doc comment already says which applies
  (`/// Replay: by value` or `/// Replay: a C handle crosses`).
- An Xo callback that Go calls on a goroutine of its own is recorded
  (where the scheduler admitted it, with its arguments) and replayed at
  the same point, and a replay diverges when Go calls it with other
  arguments (decision 0073).
- In production, a per request ring buffer (`XO_RING=N` requests) is kept and
  attached to crash bundles.
- `xo replay trace.xotrace` (or a `.xocrash`) re executes the program offline
  with identical effect results and interleavings.
- `xo debug --replay trace.xotrace` supports stepping backward.
- `xo why <expr> --at file:line` traces where a value came from (the
  assignments, calls, and effect results that produced it) using the replay.

### 11.4 Live inspection

- `xo dump <pid>` (or SIGQUIT) prints the task tree: each scope, its children,
  and what each task is blocked on, with locations. The program keeps running;
  the dump is also written to `$XO_DUMP_DIR` (or the temp dir) as
  `xo-dump-<pid>.txt`.
- **[P2]** Deadlocks are detected by the runtime and reported as a wait cycle
  with locations (fault `XO-F001`, exit status 70, crash bundle). A task
  counts as blocked when it waits on another task (channel, mutex, semaphore,
  `await`, scope end, `Once`); waits on timers, `after`, `task.stopping()`,
  signals, and I/O do not. In test worlds the report is immediate; real runs
  report after 200 ms with no progress. A task inside a Go call (`use go`)
  counts as blocked only while it waits for its own Go callbacks running on
  goroutines of Go's, those callbacks are blocked in turn, and every
  goroutine's stack shows that nothing else can move (the Go call parked in
  a `sync` wait, no goroutine running, sleeping, or in I/O); the report
  shows the edge as `blocked in Go call sync.(*WaitGroup).Wait at ... for
  callback 3`. A Go call waiting on a Go channel, select, or timer is never
  reported, and nothing is reported while any Go runtime timer is pending
  (a `time.AfterFunc` may release the wait; the check looks again after it
  fires); builds for targets other than darwin and linux never report it
  (decision 0073). Native builds (decision 0068), which
  have no timers, I/O, or signals, report at once when every task is blocked
  and none is queued, with the fault's first line only (no location or list
  of blocked tasks yet).
- `dbg(expr)` prints the expression source and its `Debug` value, returns the
  value, and is rejected by `xo build --ci`.

### 11.5 Debugger protocol

`xo debug --json` speaks a JSON line protocol (breakpoints, conditional
breakpoints, break on contract failure, eval, step, step back with replay).
`xo debug --dap` speaks the Debug Adapter Protocol for editors.

**[P1]** The generated Go code carries `//line file.xo:N` directives, so Delve,
Go stack traces, and pprof report Xo source locations.

### 11.6 Observability [later]

Not implemented yet: no spans exist, log records carry no span or request
id, and `XO_OTEL_ENDPOINT` is not read.

- Every `scope` and every effect call is a trace span. `std/log` is structured
  and attaches the current span and request id automatically.
- OpenTelemetry export is enabled by setting `XO_OTEL_ENDPOINT`.

---

## 12. Targets and platform code [P1]

### 12.1 Targets

`xo build --targets=all` builds every supported target from any host:
`linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64 wasi/wasm32`.

Binaries are static and builds are reproducible: the same source in the
same directory, lockfile, and toolchain version produce byte identical
output (the binary records the absolute source directory, which crash
bundles and `xo replay` use, so a copy elsewhere differs in those bytes).
Each build writes an SBOM (`<bin>.sbom.json`). **[later]** No SBOM is
written yet.

### 12.2 Platform specific code

There are no build tags and no filename based platform selection.
`target` is a predeclared compile time constant with fields
`os: TargetOs` (`Linux Darwin Windows Wasi`) and `arch: TargetArch`
(`Amd64 Arm64 Wasm32`):

```
pub fn config_dir(env: Env) -> Str ! Error uses env {
  match target.os {
    Linux => env.get("XDG_CONFIG_HOME") ?? "${env.home()?}/.config"
    Darwin => "${env.home()?}/Library/Application Support"
    Windows => env.get("APPDATA").or_err("APPDATA not set")?
    Wasi => "/config"
  }
}
```

- A `match` on `target.os` or `target.arch` is resolved at compile time; only
  the chosen arm is code generated.
- **Every arm is type checked for every target on every `xo check` and
  `xo build`**, so a broken Windows branch fails on a macOS machine. A missing
  OS is a non exhaustive match error.
- Platform specific APIs live in `std/sys/<os>` and can only be referenced from
  inside the matching arm (error `XO1001` otherwise). **[later]** No
  `std/sys/<os>` module exists yet, so `XO1001` is reserved (10.6).
- The native backends build `target` as a constant of the build's target,
  so only the chosen arm is code generated (decision 0107).

---

## 13. Editions and compatibility [P1]

- `xo.mod` declares an `edition`. The compiler supports every edition ever
  released. Modules on different editions link together.
- A new edition may change syntax and defaults, never the meaning of code in
  older editions.
- `xo fix --edition=YYYY` migrates a module mechanically and must leave it
  compiling. An edition is not released until its rewriter is complete.
- The standard library follows the same rule: `std` APIs are never removed
  within an edition; deprecated APIs ship rewrites in `xo fix`.

---

## 14. Standard library

The prelude (methods on builtin types) and the standard library surface are
specified in `spec/std.md`. Modules:

| Module | Contents |
|---|---|
| `std/http` | Router, server, client, requests, responses, middleware, error to status mapping |
| `std/json` | Derive based encode and decode, `Patch[T]` for partial updates |
| `std/log` | Structured, ambient logging bound to spans |
| `std/task` | `isolate`, `with_timeout`, `is_canceled`, `Semaphore` |
| `std/time` | Time zones, civil dates and times, layouts with named fields (arithmetic is in the prelude; std.md 13). Go backend only so far |
| `std/testing` | Fake capability types, generators, captured logs |
| `std/sql` | Database handles over drivers, in memory driver (draft) |
| `std/crypto` | SHA-256, HMAC, constant time compare, random tokens, password hashing |
| `std/path` | Pure helpers for `/` separated paths |
| `std/go` | `go.Error`, the error of a Go call (4.4) |
| `std/trace`, `std/config`, `std/text`, `std/sys/<os>` | Later drafts |

---

## 15. Grammar summary (informal EBNF)

Every comma separated list allows a trailing comma and may span lines. The
P1 parser (`internal/parser`) is the reference implementation of this grammar.

```
File        = { UseDecl } { TopDecl } .
UseDecl     = "use" ImportPath [ "as" ident ]
            | "use" "go" string [ "as" ident ]                      // 4.4
            | "use" "c" string { "link" string } [ "as" ident ]
              [ "uses" ( "none" | Effect { "," Effect } ) ] .       // 4.5
ImportPath  = PathSeg { "/" PathSeg } .               // std/http, github.com/acme/x
PathSeg     = PathChar { PathChar } .                 // xo-lang, rate-limit, v2, example.com
PathChar    = letter | digit | "_" | "-" | "." .      // ASCII only; no empty, `.`, or `..` segment (XO0158)
TopDecl     = { DocComment } [ "pub" ] ( FnDecl | StructDecl | EnumDecl | IfaceDecl
            | TypeDecl | ConstDecl ) | TestDecl .

FnDecl      = "fn" [ Receiver ] ident [ TypeParams ] Params [ "->" Type ] [ ErrSpec ]
              { FnClause } Block .
FnSig       = "fn" ident [ TypeParams ] Params [ "->" Type ] [ ErrSpec ] { FnClause } .
Receiver    = "(" [ "var" ] ident ":" Type ")" .
TypeParams  = "[" TypeParam { "," TypeParam } "]" .
TypeParam   = ident [ ":" ( Type | "Effects" ) ] .
Params      = "(" [ Param { "," Param } ] ")" .
Param       = [ "var" ] ( ident ":" Type [ "=" Expr ] | "self" ) .   // self: interface methods only
ErrSpec     = "!" [ Type ] .
FnClause    = "uses" ( EffectList | "(" EffectList ")" )
            | "requires" [ "(" "debug" ")" ] Expr [ "else" Expr ]
            | "ensures" [ "(" "debug" ")" ] Expr .
EffectList  = Effect { "," Effect } .
Effect      = ident [ "." ident ] .

Type        = BaseType { "?" } .
BaseType    = QualName [ "[" TypeArg { "," TypeArg } "]" ]
            | "(" ")" | "(" Type ")" | "(" Type "," [ Type { "," Type } ] ")"
            | FnType .
TypeArg     = Type | EffectList .                     // effect sets for fx parameters
FnType      = "fn" "(" [ FnTParam { "," FnTParam } ] ")" [ "->" Type ] [ ErrSpec ]
              [ "uses" ( Effect | "(" EffectList ")" ) ] .
FnTParam    = [ "var" ] Type .

StructDecl  = "struct" ident [ TypeParams ] "{" { Field } "}" [ Invariant ] [ Derive ] .
Field       = { DocComment } [ "pub" ] ident ":" Type [ "=" Expr ] .
Invariant   = "invariant" Expr .
Derive      = "derive" QualName { "," QualName } .
EnumDecl    = "enum" ident [ TypeParams ] "{" { Variant } "}" [ Derive ] .
Variant     = { DocComment } ident [ "(" VField { "," VField } ")" ] .
VField      = ident ":" Type | "from" Type .
IfaceDecl   = "interface" ident [ TypeParams ] "{" { FnSig } "}" .
TypeDecl    = "type" ident [ TypeParams ] ( "=" Type | "struct" "(" Type ")" ) .
ConstDecl   = "const" ident [ ":" Type ] "=" Expr .
TestDecl    = "test" string [ "for" Param { "," Param } ] [ "seed" int ]
              [ "uses" EffectList ] Block .

Stmt        = "let" Pattern [ ":" Type ] "=" Expr
            | "var" ident [ ":" Type ] "=" Expr
            | ConstDecl
            | Place AssignOp Expr
            | "defer" ( Stmt | Block ) | "return" [ Expr ]
            | "break" [ ident ] | "continue" [ ident ]
            | ForStmt | WhileStmt | Expr .
AssignOp    = "=" | "+=" | "-=" | "*=" | "/=" | "%=" .
Place       = ident { "." ident | "." int | "[" Expr "]" } .
ForStmt     = [ ident ":" ] "for" Pattern "in" Expr [ "bound" Expr ] Block .
WhileStmt   = [ ident ":" ] "while" [ ( Expr | "let" Pattern "=" Expr ) [ "bound" Expr ] ] Block .

Expr        = IfExpr | MatchExpr | ScopeExpr | SelectExpr | Block | FnLit | Binary
            | "while" Block | "???" .      // infinite loop as an expression, type Never
Binary      = Operand { BinOp Operand } .
Operand     = Unary [ "is" Pattern ] .            // `is` binds like `==`
FnLit       = "fn" "(" [ LParam { "," LParam } ] ")" [ "->" Type ] [ ErrSpec ]
              [ "uses" ( EffectList | "(" EffectList ")" ) ] Block .
LParam      = [ "var" ] ident [ ":" Type ] .
IfExpr      = "if" ( Expr | "let" Pattern "=" Expr ) Block [ "else" ( IfExpr | Block ) ] .
MatchExpr   = "match" Expr "{" { Pattern [ "if" Expr ] "=>" ( Expr | Stmt ) } "}" .
ScopeExpr   = "scope" ident Block .
SelectExpr  = "select" "{" { SelectArm } "}" .
SelectArm   = ( [ ident "=" ] Expr | "after" Expr ) "=>" ( Expr | Stmt ) .
Postfix     = Primary { "." ident | "." int | Call | Index | "?" | "with" StructLit } .
Index       = "[" ( Expr | [ Expr ] ( ".." | "..=" ) [ Expr ] ) "]" .
Call        = [ "[" TypeArg { "," TypeArg } "]" ] "(" [ Arg { "," Arg } ] ")" .
Arg         = [ ident ":" ] Expr .
Literal     = int | float | duration | string | rawstring | bytes | rune | "true" | "false" .

Pattern     = PatAlt { "|" PatAlt } .
PatAlt      = "_" | ident | [ "-" ] Literal [ ( ".." | "..=" ) [ "-" ] Literal ]
            | "(" ")" | "(" Pattern ")" | "(" Pattern "," [ Pattern { "," Pattern } ] ")"
            | [ QualName "." ] ident [ "(" ( ".." | PatFields ) ")" ]   // variant
            | QualName "{" PatFields "}"
            | "[" [ PatElem { "," PatElem } ] "]" .               // ".." at most once
PatElem     = Pattern | ".." .
PatFields   = PatField { "," PatField } [ "," ".." ] .
PatField    = [ ident ":" ] Pattern .
QualName    = [ ident "." ] TypeName .     // optional module prefix
TypeName    = ident .
```

Operator precedence, highest first: postfix (`. () [] ?`), unary (`- ! ~`),
`* / % << >> &`, `+ - | ^`, `.. ..=`, `== != < <= > >= is`, `&&`, `||`, `??`.

Parsing rules the grammar alone does not settle:

- **Type arguments vs. indexing.** `x[...]` holds type arguments when every
  element looks like a type (`UpperCamel`, `mod.UpperCamel`, `(`, or `fn`) and
  either `(` follows or `x` is an `UpperCamel` type name; otherwise it is an
  index. So `json.decode[User](b)` and `Mutex[Int].new(0)` take type
  arguments, while `xs[i]` and `m[Red]` index. An effect argument (a
  lowercase `name` or `name.sub`) also counts as a type element, except that
  a base spelled like a constant (`UPPER_SNAKE`) with only lowercase names
  in the brackets is an index: `NAMES[i]` (decision 0143).
- **`{` in expression position** is an empty map when it is `{}`, a map literal
  when a `:` appears at depth zero before the first newline, and a block
  otherwise. A match arm `=> {}` is therefore an empty map; write `()` for unit.
- **Function types in return position** take a trailing `uses` themselves:
  `-> fn(A) -> B uses clock {` gives the effect to the returned function. Write
  `-> (fn(A) -> B) uses clock {` to put it on the declaration.
- In a function type, a `uses` list with more than one effect must be
  parenthesized, because a following comma would otherwise be ambiguous.
- Only `while { }` (no condition) may appear in expression position.
- Triple quoted strings support escapes and interpolation; a blank first and
  last line are dropped; the common indentation is measured on the source.

---

## Appendix A: complete example

```
// api/main.xo
use std/http
use std/log

pub struct User {
  pub id: Int
  pub name: Str
  pub email: Str?
} derive Json

pub enum ApiErr {
  BadRequest(msg: Str)
  NotFound(id: Int)
}

// http maps handler errors to responses through this interface.
fn (e: ApiErr) status() -> Int {
  match e {
    BadRequest(_) => 400
    NotFound(_) => 404
  }
}

fn (e: ApiErr) display() -> Str {
  match e {
    BadRequest(msg) => msg
    NotFound(id) => "user ${id} not found"
  }
}

struct Store {
  users: Mutex[Map[Int, User]]
}

fn (s: Store) get(id: Int) -> User ! ApiErr
  requires id > 0 else ApiErr.BadRequest(msg: "id must be positive")
{
  s.users.with(fn(m) { m.get(id) }).or_err(ApiErr.NotFound(id: id))?
}

fn routes(store: Store) -> http.Router {
  var r = http.router()
  r.handle("GET /users/{id:Int}", fn(req) -> http.Response ! ApiErr {
    let u = store.get(req.param_int("id"))?
    http.json(200, u)
  })
  r
}

fn main(os: Os) ! {
  let store = Store{users: Mutex.new({1: User{id: 1, name: "Ada", email: None}})}
  let srv = http.server(os.net.listen(":8080")?, routes(store))
  log.info("listening", {"addr": ":8080"})
  srv.serve()?
}

test "missing user is 404" {
  let store = Store{users: Mutex.new({})}
  let resp = routes(store).call(http.new_request("GET", "/users/9"))
  expect(resp.status) == 404
}
```

## Appendix B: open questions

Tracked in `knowledgebase/decisions/` once decided.

1. ~~Method syntax: Go style receivers or `impl` blocks.~~ Decided in
   0090: receivers.
2. ~~`Map` iteration order.~~ Decided in 0091: insertion order.
3. ~~Error frame capture cost in release builds.~~ Decided in 0092:
   frames on in every build, captured lazily (implemented on the Go
   backend; the native backends record them too, decision 0104).
4. ~~How `http` handler errors map to status codes.~~ Decided in 0019.
5. ~~FFI design and record/replay.~~ Decided in 0093: `use go` and
   native `use c`, foreign calls recorded at the boundary (`use go`
   recording implemented; `use c` is implemented (4.5, decision 0116)
   without recording, since native builds do not record yet).
6. ~~Interpolation syntax.~~ Decided in 0021: `${x}`.

