# The ww Language Specification Version 0.1 (2026-06-21). **Status: normative-intent, in progress.** ww is a systems language: *Hare semantics + CSP concurrency, no garbage collector*, with a Rob-Pike / Plan-9 sensibility and Go-style explicit `package`/`import`. This document is the language contract. Where the two compiler stages disagree with this text, the text is wrong and is a bug to be filed (CLAUDE.md rule 10 requires the stages to agree with each other; this spec requires them to agree with *it*). Two implementations track this spec and must stay byte-identical (rule 10): the C bootstrap frontend `cmd/wcc` (the reference) and the ww reimplementation `lib/ww/syntax`. The token table in §2 is kept numerically identical between them on purpose. This is not a tutorial. It is terse by design, modelled on the Go language specification (golang.org/ref/spec): EBNF where a grammar is worth pinning, prose for semantics, and a normative appendix (§12) that consolidates every sanctioned divergence from Hare. The appendix is the highest-value part of this document — it is the single normative home for rules currently scattered across CLAUDE.md, `.ai/`, and project memory. Sections marked **(reserved)** describe surface that is tokenised but not yet given meaning; they are written when the feature lands. --- ## 1. Notation The grammar uses Extended Backus-Naur Form, following the Go spec's conventions: ``` Production = production_name "=" [ Expression ] "." . Expression = Term { "|" Term } . Term = Factor { Factor } . Factor = production_name | token | Group | Option | Repetition . Group = "(" Expression ")" . Option = "[" Expression "]" . Repetition = "{" Expression "}" . ``` `|` is alternation, `{}` is zero-or-more, `[]` is zero-or-one, `()` is grouping. Terminal tokens are in `"quotes"` or are the named token classes from §2 (`ident`, `int_lit`, …). --- ## 2. Lexical elements ### 2.1 Source representation Source is UTF-8. The lexer operates on bytes; non-ASCII bytes are legal only inside string and rune literals and comments. ### 2.2 Comments Line comments only, introduced by `//` and running to end of line. There are no block comments (Hare lineage; `/* */` is not recognised). Two comment forms are *driver directives*, not ordinary comments — they carry module-bundle structure and lex to dedicated tokens: - `//ww:module ` — set the mangling module path for the decls that follow (token `MODPATH`). - `//ww:module-reset` — reset the current module to the empty string before a package-less bundled file (token `MODRESET`). These exist for the build driver's source bundling and are not written by hand in ordinary source. ### 2.3 Tokens Tokens are identifiers, keywords, operators/punctuation, and literals. Whitespace (space, tab, CR, LF) separates tokens and is otherwise ignored. A semicolon `;` terminates declarations and statements and is written explicitly (no automatic insertion). ### 2.4 Keywords The following 30 identifiers are reserved and may not be used as ordinary identifiers: ``` as break case chan const continue def defer else enum export false fn for if is import let match nil package proc return static struct switch true type void yield ``` `chan` and `proc` are reserved for the concurrency surface (§11, reserved). `package` is the package-clause keyword; `import` introduces an import. ### 2.5 Operators and punctuation ``` Assignment: = += -= *= /= %= &= |= ^= <<= >>= Arithmetic: + - * / % Bitwise: & | ^ ~ << >> Comparison: == != < <= > >= Logical: && || ! Grouping: ( ) { } [ ] Punctuation: , ; : . .. ... @ ? Arrows: <- -> => ``` `<-` (channel send/receive) and `->` are tokenised but reserved (§11). `=>` separates a `match` arm pattern from its body. `?` and `!` are the error operators (§9). `:` introduces a type ascription / cast (§8) and a field/binding type. `..` is the range operator (`for` range form, §7); `...` is the variadic / spread marker (§6.3). `@` introduces an attribute (§5.7). ### 2.6 Identifiers ``` ident = letter { letter | digit } . letter = "A"…"Z" | "a"…"z" | "_" . digit = "0"…"9" . ``` A lone `_` is the *discard* identifier: legal as a binding name to mean "ignore this", never as a reference. ### 2.7 Literals ``` int_lit = decimal | hex | octal | binary [ suffix ] . float_lit = digits "." digits [ exponent ] [ suffix ] . rune_lit = "'" ( byte | escape ) "'" . str_lit = '"' { byte | escape } '"' . bool_lit = "true" | "false" . nil_lit = "nil" . ``` - `int_lit` is held as a `u64`. `nil` is the null pointer value (§3.6). - A numeric literal may carry a *type suffix* naming its type directly: `42u8`, `0u64`, `1i64`, `3.0f32`. Suffixes are the fixed-width type names (`i8 i16 i32 i64 u8 u16 u32 u64 f32 f64`). Without a suffix a literal is an *untyped constant* (§3.5) whose type is inferred from context. - Escapes in rune and string literals: `\\ \" \' \n \t \r \0` and `\xNN` (two hex digits). Rune literals additionally accept the Unicode escapes `\uHHHH` and `\UHHHHHHHH`, yielding a codepoint. --- ## 3. Types A type describes the layout and operations of a value. Every type has a `size` and an alignment, both queryable with `size(T)` (§8). No size is ever written as a bare literal in size-computing code — it is routed through the type table (CLAUDE.md rule 13). ### 3.1 Boolean and void `bool` (1 byte; `true`/`false`). `void` (size 0) is the empty type, used as a fn result and as the second arm of a nullable pointer (§3.6). ### 3.2 Numeric types Fixed-width integers: `i8 i16 i32 i64` (signed), `u8 u16 u32 u64` (unsigned). Floats: `f32 f64`. Machine-word types (8 bytes on amd64): `int`, `uint`, `uintptr`, `size`. `int` and `uint` are a full machine word — Go-style, **not** Hare's 32-bit `int`. The limit constants (`INT_MIN`, `INT_MAX`, `UINT_MAX`) are **derived** from `size(int)`, never hardcoded to a width (§12). `rune` is a 32-bit Unicode codepoint (4 bytes). ### 3.3 String and slice `str` and `[]T` (slice of `T`) are both **24-byte three-word headers**: `{ ptr, len, cap }`. Their `.ptr`, `.len`, and `.cap` pseudo-fields are readable (§8.4). A `str` is UTF-8 bytes; reinterpreting `[]u8` as `str` is `strings.frombytes` (a pure cast — there is no validation; §12). Array: `[N]T` is `N` elements inline (size `N * size(T)`). `[_]T` infers `N` from the initialiser's element count. ### 3.4 Aggregate and named types ``` type S = struct { f0: T0, f1: T1, … }; // struct type Name = T; // named type / alias ``` - `struct` — named fields laid out in declaration order with natural alignment padding. Duplicate field names are rejected. - `tuple` — `(T0, T1, …)`. Each element occupies an **8-byte slot** (`size((u32, u32)) == 16`), diverging from Hare's packed tuples (§12). - `enum` — `enum i32 { A = 0, B, … }` (a named integer type with named constants) or bare `enum { … }`. - A named type / alias introduces a new spelling for an underlying type. TODO(spec): structs carry an internal `packed` property (no padding); surface its syntax (an `@packed` attribute? a `struct` modifier?) once confirmed against `cmd/wcc`. Likewise pin the `static let` qualifier spelling and whether `static` applies outside function bodies. ### 3.5 Untyped constants A literal without a suffix has an *untyped* type (`untyped int`, `untyped float`, `untyped str`, `untyped rune`, `untyped bool`, `untyped nil`). An untyped constant takes a concrete type from its use context (assignment target, operand of a typed operation, fn argument). This is the inference that lets `let x: u8 = 5;` work without a suffix. ### 3.6 Pointers and nullability `*T` is a pointer to `T`. **A plain `*T` is non-null**; dereferencing it is unconditional and free. A *nullable* pointer is written as the tagged union `(*T | void)`; `nil` inhabits it. This is a one-flag fold, zero extra cost for the non-null case, and it is the only optionality mechanism — there is no separate `nullable` qualifier keyword (§12). ww has no runtime null-check net, so a non-null `*T` carries the no-null obligation the way C does. ### 3.7 Tagged unions and the error tag ``` TaggedType = "(" Type { "|" Type } ")" . ``` A tagged union `(A | B | …)` holds one of its variants plus a runtime tag. Variants are discriminated with `match` (§7) or tested with `is` and narrowed with `as` (§8). A variant may carry the error mark `!T`, which participates in the error model (§9): `(i64 | invalid | overflow)` is a typical fallible result. ### 3.8 Function and channel types `fn(P0, P1, …) R` is a function type. `chan T` is a channel of `T` (**reserved**, §11). `never` is the bottom type of an expression that does not return (e.g. a call to `abort`). --- ## 4. Packages and imports ``` SourceFile = PackageClause { ImportDecl } { TopDecl } . PackageClause = "package" ident ";" . ImportDecl = "import" ( ImportPath | ImportName ImportPath ) ";" . ImportName = ident . ImportPath = ident { "." ident } . ``` - Every source file begins with a package clause. Within one semantic source action, eligible `.ww` files share one declared package name. Ordinary production and same-package test sources use `p`; external test sources may use the related `p_test`, and the actions remain separate even though one canonical directory owns their test product. The declared name need not equal the directory name or the final component of its canonical import identity. - Directory source eligibility uses Go 1.26.5 filename suffix semantics for WW's fixed `linux/amd64` target. In the basename stem before the first dot, a final `_test` token is ignored for platform matching. A final known OS or architecture token must match `linux` or `amd64`; a final known OS followed by a known architecture takes precedence and both must match. The known-name sets are Go 1.26.5's `syslist.KnownOS` and `syslist.KnownArch`. Unknown or misplaced suffixes are ordinary, and a platform word without a nonempty underscore prefix is ordinary (`linux.ww` and `plan9_test.ww` are selected; `x_windows.ww` and `x_plan9_test.ww` are not). Leading-dot/underscore entries are ignored. Eligible names are byte-sorted before source validation. Production excludes selected `*_test.ww`; test variants classify only those selected test files. An excluded file contributes no declarations, imports, package edge, action, export, artifact, initialization, test, or persistent invalidation. - `import acme.codec;` loads the canonical package `acme.codec`. If that package declares `package wire;`, the importing file sees its exported names as `wire.Name`; `codec.Name` is not an additional binding. An explicit alias replaces only that visible qualifier: `import stable acme.codec;` exposes `stable.Name`, not `wire.Name` or `codec.Name`. Both kinds of binding are scoped to that source file. A sibling file must declare its own import. Neither form exposes an imported declaration as a bare `Name`; ordinary unqualified lookup remains limited to lexical, builtin, and same-package declarations. - `import _ acme.codec;` is a blank side-effect import. The lone `_` creates no qualifier, exposes no bare declaration, and is never diagnosed as unused. It is nevertheless a real import occurrence: resolution and all missing, self-import, cycle, `internal`, vendor, and imported-command checks use the dotted path, and the dependency participates in executable and test package initialization. Repeated blank occurrences and blank plus default/explicit named occurrences of one path are valid; every named occurrence remains independently subject to duplicate-binding and unused checks. Each semantic action's package dependency graph is the sorted, deduplicated union of real imports in that action's eligible source set. Occurrences retain their owning file and position, but equal canonical targets create one graph edge. Test-only occurrences never enter ordinary production. Self-import is rejected, except that toolchain-owned external-test self wiring is rebound to the effective augmented package action after ordinary per-site validation. - An executable package is one declared `package main` and containing a `fn main`; path and directory spelling do not classify commands. An ordinary import of a package declared `main` is rejected, except for the toolchain's colocated external-test wiring. - Only names marked `export` (§5) are visible across module boundaries. Import paths remain unquoted and dotted. Grouped imports, quoted import paths, and dot imports are not implemented. `_` is reserved here for the blank form; it is not an ordinary alias. --- ## 5. Declarations ``` TopDecl = [ "export" ] ( FnDecl | TypeDecl | LetDecl | ConstDecl | DefDecl ) . ``` ### 5.1 Functions ``` FnDecl = "fn" ident "(" [ Params ] ")" Result "=" Block . | "fn" ident "(" [ Params ] ")" Result ";" . // bodiless: extern/FFI Params = Param { "," Param } . Param = ident ":" Type | ident ":" Type "..." | "..." . Result = Type | "void" . ``` The `=` between signature and body is required. A bodiless fn (`;` terminator) declares an external symbol (C FFI); the bare `...` C-style variadic is legal only on a bodiless declaration (§6.3). At package scope, exactly `fn init() void = Block;` declares a special package initializer. It must have a body, no parameters, no result, no `export`, and no attribute. Multiple init declarations are permitted and retain owner-file and source order. `init` is not inserted into ordinary package scope: it cannot be called as `init()`, selected as `pkg.init`, exported, or used by another kind of declaration. Package-variable initialization completes before these functions run. ### 5.2 `let` ``` LetDecl = "let" ident ":" Type [ "=" Expr ] ";" . ``` A mutable binding. Without an initialiser, package-level storage is zero-backed; an uninitialized function-local binding retains the existing uninitialized-local rule. A `static` qualifier (`static let …`) gives function-local storage static lifetime. For a package-level `let` with an initializer, values representable by the static-data emitter are installed statically. Every other otherwise valid initializer is evaluated exactly once at runtime. Checked references through package functions contribute variable-dependency edges; dependencies precede dependents, and source declaration order breaks ready ties. Initialization cycles are errors. Runtime package lets execute after imported package tasks and before the package's init functions. `const` and `def` are not broadened by this runtime path. ### 5.3 `const` `const` introduces an immutable storage binding of the same declaration shape as `let`; assignment to it is rejected. At package scope its initializer stays within the existing static-data forms. A `const` is not entered into the runtime package-variable schedule described above; use mutable `let` when an otherwise valid initializer requires runtime evaluation. ### 5.4 `def` ``` DefDecl = "def" ident ":" Type "=" ConstExpr ";" . ``` A compile-time constant definition, typically at module level (`def AF_INET: i32 = 2;`). Hare's `def`. ### 5.5 `type` ``` TypeDecl = "type" ident "=" Type ";" . ``` ### 5.6 Visibility `export` on a top-level declaration makes the name visible to importing modules. Unmarked names are module-private. ### 5.7 Attributes `@name` prefixes a declaration with an attribute. The defined attribute is `@test`: it marks a function as a test (§10). --- ## 6. Functions and calls ### 6.1 Blocks and results A function body is a `Block` (§7). The body's value is its result; an explicit `return Expr;` returns early. ### 6.2 Calls ``` Call = Expr "(" [ Args ] ")" . Args = Arg { "," Arg } [ "..." ] . ``` Arguments are passed by value (a `str`/`[]T`/`struct` copy is a shallow header/field copy). Aggregates larger than a register use the aggregate/`sret` ABI. ### 6.3 Variadic Two distinct variadic forms: - **Hare-style** `args: T...` — the callee receives a `[]T`. A call either gathers loose arguments (`fmt.println(1, "x")`) or forwards an existing slice with the spread `xs...`. - **C-style** bare `...` — only on a bodiless (extern) declaration, for calling C variadic functions across the FFI. `f32` arguments in the variadic tail are promoted to `f64` per the C ABI. --- ## 7. Statements ``` Stmt = Block | LetDecl | ConstDecl | Assign | If | For | Switch | Match | Return | Break | Continue | Defer | Yield | ExprStmt . Block = "{" { Stmt } "}" . ``` ### 7.1 Assignment ``` Assign = Expr AssignOp Expr ";" . AssignOp = "=" | "+=" | "-=" | "*=" | "/=" | "%=" | "&=" | "|=" | "^=" | "<<=" | ">>=" . ``` The compound forms apply to scalar, indexed, and pointer-field targets. ### 7.2 `if` ``` If = "if" "(" Expr ")" Block [ "else" ( If | Block ) ] . ``` ### 7.3 `for` ``` For = "for" "(" Expr ")" Block // condition | "for" "(" LetDecl Expr ";" Expr ")" Block // 3-clause | "for" "(" "let" ident ".." Expr ")" Block . // range ``` The 3-clause form is `for (let i: size = 0; i < n; i += 1) { … }`. The range form iterates the elements of a slice or array (`for (let x .. xs) { … }`). `break` and `continue` apply to the nearest enclosing loop; `continue` runs the post-clause / advances the range. TODO(spec): does the range form bind the *element* or an index, and is the bound name a copy or a view? Confirm against the checker's `N_FORRANGE` lowering; the continue-post behaviour was a fixed miscompile (project memory #138), so the desugaring is worth stating precisely. ### 7.4 `switch` ``` Switch = "switch" "(" Expr ")" "{" { SwCase } "}" . SwCase = "case" ExprList ":" { Stmt } | "case" ":" { Stmt } . ``` Value-matching on a scalar. A `case` may list comma-separated values; `case:` is the default arm. Arms do not fall through. ### 7.5 `match` ``` Match = "match" "(" Expr ")" "{" { MatchArm } "}" . MatchArm = "case" [ "let" ident ":" ] Type "=>" ArmBody | "case" "=>" ArmBody . ArmBody = Stmt | Block . ``` Discriminates a tagged union (§3.7). `case let v: T =>` binds the narrowed value to `v`; `case T =>` matches without binding; `case =>` is the default. A `match` used as an expression produces a value via `yield` (§7.7) from each arm. ### 7.6 `return`, `break`, `continue`, `defer` `return [Expr];` `break;` `continue;`. `defer Stmt;` schedules a statement to run when the enclosing block exits. ### 7.7 `yield` `yield Expr;` produces the value of an enclosing block- or match- expression. --- ## 8. Expressions ### 8.1 Operands Identifiers, qualified names (`mod.name`), literals (§2.7), struct/array/ tuple literals (§8.5), and parenthesised expressions. ### 8.2 Operators and precedence From loosest to tightest binding: ``` || && == != < <= > >= | ^ & << >> + - * / % unary: - ! ~ &(addr-of) *(deref) postfix: call() index[] slice[:] field. ? ! ``` `&e` takes the address of an addressable operand; `*p` dereferences. TODO(spec): the precedence ladder is transcribed from the parser's `bprec` (`parse.ww`); verify each tier against `cmd/wcc/parse.c` and state associativity per tier. ww has no generics — no type parameters anywhere in the grammar; call this out explicitly once confirmed. ### 8.3 Casts, `as`, `is` ``` Cast = Expr ":" Type . // type ascription / conversion As = Expr "as" Type . // tagged-union narrowing Is = Expr "is" Type . // tagged-union variant test (bool) ``` `expr: T` converts/ascribes (`len(xs): size`, `t.line: i64`). An integer cast does not silently truncate beyond the target width. `e as T` narrows a tagged union to variant `T`; `e is T` tests membership. ### 8.4 Index, slice, field, pseudo-fields `a[i]` indexes an array/slice/str (index operand must be an integer). `a[lo:hi]` produces a sub-slice. `e.f` selects a struct field or a qualified name. `.ptr`, `.len`, `.cap` read the header words of a `str` or slice. ### 8.5 Composite literals ``` StructLit = TypeName "{" [ FieldInit { "," FieldInit } ] "}" . FieldInit = ident ":" Expr . ArrayLit = "[" [ Expr { "," Expr } ] "]" . TupleLit = "(" Expr "," Expr { "," Expr } ")" . ``` `S{}` is the zero value of `S`. ### 8.6 Builtins `size(T)` (type size), `len(x)` (str/slice/array length), `alloc(v)!` (heap-allocate a value, yield `*T`; §9), `append(s, x)` (grow a slice), `delete(…)`, `abort(msg)` (terminate; type `never`). `alloc` takes a *value*: `alloc(T{…})!`, `alloc(T{})!`, or `alloc(expr)!`. Bare `alloc(T)!` is invalid (it parses `T` as a value reference) (§12). There is no `free`: ww has no GC and no manual reclamation in compiler-side code — allocation lives until process exit (§12). `lib/*` modules that genuinely own a heap buffer free it explicitly via `os.free`. --- ## 9. Errors ww has no exceptions. A fallible operation returns a tagged union whose error variants carry the `!T` mark (§3.7). Two postfix operators consume them: - `expr?` — if `expr` is an error variant, return it from the current function (propagate); otherwise yield the success value. - `expr!` — if `expr` is an error variant, abort; otherwise yield the success value. Error variants are ordinary sentinel types in the union (`strconv.invalid`, `strconv.overflow`, …), discriminated with `match`. This mirrors Hare's error idiom, spelled with ww's `!` tag. --- ## 10. Tests A function marked `@test` is a test, run by the test harness, not part of the program. Two placements, following the Go `foo` / `foo_test` model: - **White-box**, in-package: a colocated file `package ;` with `@test` functions, for testing unexported internals. - **Black-box**, external: `package _test;` with `import ;`, exercising only the public surface. ```ww @test fn adds() void = { if (1 + 1 != 2) { abort("math broke"); }; }; ``` Production, production-plus-white-box-test, external black-box-test, test support, recompiled-for-test dependencies, and generated test main are distinct package actions. One canonical selected directory owns one test product and one generated main, which imports every applicable same-package and external target and produces one binary/result. The external action's import of the package under test binds to the augmented white-box action when it exists; affected transitive importers are copied and rewired so ordinary and augmented package state do not coexist in the linked closure. Before a test function runs, the one generated product initializes its exact effective graph dependency-first and once per canonical action. Imports, runtime lets, and init declarations found only in `*_test.ww` never enter an ordinary production build. Test-only same-package, external-package, and valid mixed directories are accepted from their selected test files. A directory with no selected test file validates ordinary production but creates no test support, generated main, link, binary, result, or process. --- ## 11. Concurrency (reserved) The headline `+CSP` surface — `chan T`, `proc`/`spawn`, channel send/ receive `<-`, and the `alt` select construct — is tokenised (`chan`, `proc`, `<-`, `->`) but **not yet given semantics**. This section is written when the feature lands; until then, use of these tokens is rejected by the checker. **Meta-ruling (normative).** CSP is *the* sanctioned departure from the Hare-fidelity rules (CLAUDE.md rules 5 and 9) — Hare has no channels, and the `+CSP` mandate is the project's reason to add them. The concurrency surface therefore cites the Newsqueak → Limbo → Go lineage, **not** Hare, and that citation is correct by construction, not a rule-9 violation. The design of record is `.ai/csp-design-input.md` (library-first: `spawn`, unbuffered bidirectional `chan T` via `mkchan(T)`, `<-` send/recv, `recv → (T | closed)` error idiom, `alt` as v2). See §12 entry 9. TODO(spec): fill in syntax + semantics (channel construction, send/recv typing, `spawn` arg-copy ABI, `alt` arm grammar, the `closed` sentinel) once the MVP lands and the USER meta-ruling is explicitly on record. --- ## 12. Divergences from Hare (normative) ww follows Hare's semantics and `lib/` API shapes (CLAUDE.md rules 5, 9) except for the deliberate, USER-blessed departures below. Each is normative; the cited reference is the rationale of record. 1. **`int`/`uint` are a machine word (8 bytes), not Hare's 32-bit `int`.** All limit constants derive from `size(int)`; `size`/`uintptr` are 8 bytes. *Ref: project memory "int is a machine word"; USER 2026-05-26.* 2. **Tuples use 8-byte slots, not Hare's packed layout.** `size((u32, u32)) == 16`. Routed through the type table only. *Ref: USER 2026-06-04 tuple slot ruling; parity task #60.* 3. **Nullability is the union `(*T | void)`, not a `nullable` qualifier.** Plain `*T` is non-null; the union fold is one flag, zero-cost for the non-null path. *Ref: USER 2026-05-26 nullable-kept ruling.* 4. **No `_unsafe` suffix convention.** ww is unmanaged (no GC, no safe baseline), so Hare's `_unsafe` axis does not apply. `bytes`→`str` is the pure reinterpret `strings.frombytes` (Hare's `fromutf8_unsafe`); UTF-8 validation is opt-in at the IO source via `utf8.validate`, not wrapped per construction. *Ref: CLAUDE.md rule 9; ref/hare/strings/ utf8.ha.* 5. **The compiler frontend is one package `lib/ww/syntax`** (tok + lex + ast + sym + typ + parse), a Go-over-Hare consolidation of Hare's `{ast, lex, parse}` split. Its single consumer is the `wcc` backend. The internal data shapes still mirror `ref/hare/hare`. *Ref: CLAUDE.md rule 6; USER #74.* 6. **Tests use the Go `foo` / `foo_test` model** (§10): external black-box `package _test;` plus in-package white-box `@test` files. ww hard-rejects self-import. *Ref: CLAUDE.md rule 9; task #16.* 7. **No arena / no `free` in compiler-side code.** Per-node `alloc(value)!`; allocation is reclaimed at process exit. `alloc` takes a *value* (`alloc(T{…})!`), never a bare type. *Ref: project memory "drop amalloc", "Phase 0 closed", "alloc syntax form".* 8. **The checked AST carries a per-node `type_` stamp.** Hare's parse AST is untyped and harec keeps a separate checked AST in C; ww stamps `node.type_` in place as a checker invariant. *Ref: project memory "Hare AST has no per-expr result".* 9. **CSP** (§11) is the sanctioned departure from rule-5/9 Hare fidelity — Hare has no channels. The surface cites Newsqueak/Limbo/Go, not Hare. *Ref: `.ai/csp-design-input.md`; pending explicit USER meta-ruling.* 10. **The two compiler stages emit byte-identical asm** for the same input (CLAUDE.md rule 10); when inference power differs, the richer stage is aligned *down* to the leaner one. This is a project invariant, listed here because it is the contract this spec's "the text is the bug if stages disagree with it" rule rests on. --- ## Appendix A. Anti-rot This spec is hand-maintained. To keep it from drifting against the parser, every fenced ` ```ww ` example is intended to be extracted and compiled through `w6c`, asserting it parses (or rejects, for negative examples), wired as a `make spec-check` dependency of `make test`. The EBNF productions are not auto-verified; the compiling examples are the drift alarm. *(Check not yet implemented; filed as a follow-up.)*