Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

The guide

README.md is a thin pointer. This is where the depth lives: what the language is, why it’s built this way, what exists, and an index of every design document. History — what shipped when, and what each slice found, including corrections — is ROADMAP.md. Neither belongs in the index below, which says what each document settles, not how it got there.

Design lands here before the code that implements it, which is the cheap place for it to be wrong. When it turns out wrong anyway, the document that made the claim is corrected in place rather than quietly edited — ROADMAP.md says which.


Where this came from

cancho began as the lower-level sibling of Lex, a high-level, interpreted language, and took its worldview from it: ownership and authority as one system, defined behaviour, a canonical content-addressable syntax tree. That is history. cancho stands on its own now: it has its own compiler, its own standard library, its own packages and its own tools (cancho authority, cancho-vcs, the products listed in the repositories of this organisation), and it depends on nothing from Lex or from lex-os, the agent runtime Lex had. If a runtime that confines a program to what its authority report says is needed, it is built in cancho, from the report the compiler already writes.

The documents that compared the two (vcs.md, under-a-grant.md, related-work.md) are kept as the record of what was measured and decided; they are not a plan to integrate with Lex.


Why

Three things this philosophy buys that no systems language currently combines:

  1. Capabilities all the way down. Ownership and effects are the same idea — both are resource tracking. Allocation is an effect, a heap value is a linear resource, FFI is a capability you must be granted. Owning a capability discharges its effects and borrowing declares them, so main’s row is [] however much it does.
  2. Determinism as a language property. No UB, defined evaluation order, deterministic layout — which is what makes replay, attestation and content-addressing mean anything, and exactly what C throws away. Written out operation by operation in defined-behaviour.md.
  3. A checker that is fast and total, because the guarantee is only worth what it costs to verify.

What exists

Settled by
CapabilitiesWorld, Io, Fs(prefix), Ffi(lib), Heap, Args, File, Net(bound) — linear values, split once, released by namelinearity-and-effects.md
Effect rowsA canonically ordered set, exact in both directions, every label tracing to a builtinlinearity-and-effects.md
NarrowingPrefix extension, one way, and it consumes what it attenuatesfilesystem.md, reach.md
Authority reportcancho authority, computed from reachability; --output json for a supervisor, and it fails closed on foreign codeauthority.md
BorrowingLexical regions, no borrow checker; &! is a lock on the bindingaliasing.md
MemoryArenas, a general heap with recursive types, boxed slices, growable buffersheap.md, boxed-slices.md
Typesint byte bool float (and f32 from edition 6), structs, enums with exhaustive match, tuples, generics with [T: val] boundsfloating-point.md, tuples.md
Defined behaviourChecked arithmetic that traps, left-to-right evaluation, every C hole named and closeddefined-behaviour.md
ThreadsCompiler-provided spawn/join, never crossing the C ABI; one pointer-width payload today; atomics designed, not builtthreads.md, atomics.md
Program identitycancho ids — per-declaration content hashes, checked against golden fixturescanonical-ast.md, hash-stability.md
A content-addressed op logcancho vcs publish/log — every declaration as a typed, gated operationvcs.md, vcs-publish.md
Compile timePure calls on constant arguments folded; static items whose bodies run during compilationcompile-time-data.md
I/OThe console in three directions, file handles as linear resources, bulk reads and writes, a Net capability for socketsfile-handles.md, bulk-io.md, net.md
RefusalsEvery rule carries a stable tag; check --output json reports every independent one as dataagent-errors.md
Standard libraryWritten in cancho, including shortest round-trip float printing and a UTF-8 decoderstandard-library.md
Two backendsCranelift (dev) and LLVM (release, default) — an opt-in second backend became the default once nothing it refused had an asker leftllvm-backend.md

What is not there yet, and why, is ROADMAP.md.


The language in one page

A value is res or val. A res value is consumed exactly once on every path. Mode is structural — a res member makes the whole aggregate res — so Held[File] is res where Held[int] is val.

res struct Ticket { serial: int }

fn redeem(t: Ticket) -> [] int {
    let Ticket { serial } = t;      // the whole is spent, the parts produced
    return serial;                  // `int` is `val`, so nothing is owed now
}

There is no drop and no destructor: a resource is destroyed by naming the function that knows how, which is what keeps an effect row honest once there are effect rows.

Looking without spending is a borrow. borrow freezes for a read, borrow mut locks for a write — nothing else may touch the value at all, not even a read.

fn serial_of[&r](t: &r Ticket) -> [] int { return t.serial; }

borrow held as &r in {
    putchar(i, 48 + serial_of(r));    // `held` is frozen; `r` reads it
}                                     // owned again here

There is no borrow checker. A region is a block, so a reference’s validity is lexical rather than inferred: no non-lexical lifetimes, no variance, no dataflow. A binding is Owned, Frozen or Locked, set at block entry and restored at block exit; r_inner <= r_outer holds exactly when the outer block encloses the inner one, which is a walk up a stack; and escape is an occurs-check over one type.

An effect is a borrowed capability.

fn triple(n: int) -> [] int { return n * 3; }                // pure, and says so
fn show[&i](io: &!i Io, n: int) -> [io_write] int { ... }    // borrows the console

fn main(world: World) -> [] int {
    let Split { io, ffi, fs, heap, args } = split(world);    // the one source of authority
    release(args); release(heap); release(fs); release(ffi); // unused authority is still a resource
    borrow mut io as &!i in { show(i, 7); }
    release(io);                                             // destroyed exactly once
    return 0;
}

[io_write] on a signature means the function was handed an &!i Io it did not create — so reading the row and reading the parameter list are the same act, and a function that was given nothing cannot print however much it wants to. There is no ambient constructor: Io { } is refused, main’s World is the only authority in the program, and it is linear, so forgetting to release it does not compile.

main’s own row is [] even though it prints, because it owns the capability rather than borrowing one — and ownership is already visible in the parameter list.

A row is a canonically ordered set, so it hashes, which is what per-unit identity is made of. It is exact in both directions: performing an effect you did not declare and declaring one you never perform are both errors, because an inexact row means [] stops meaning pure. And there is no list of legal labels — every label traces back to a builtin that performs one, so a label with nothing underneath it is refused the moment it is written.

A foreign call is the same idea pointed at C.

extern fn labs[&f](ffi: &f Ffi("libc"), n: int) -> [ffi("libc")] int;

let libc = narrow(ffi, "libc");        // `Ffi("")` -> `Ffi("libc")`, one way only
borrow libc as &f in { n = labs(f, 0 - 7); }

Narrowing is prefix extension and goes one way. Ffi("") names no library, so it authorises nothing until narrowed; an Ffi("libcrypto") can never become an Ffi("libc"). It also consumes what it attenuates, so there is no way back to the wider capability. The capability is checked and then erased: what libc receives is the integer and nothing else.

Memory has three shapes, and the same region machinery checks all of them.

region a {                                   // an arena: released in one call
    let xs = alloc_slice[a](5, 0);           // xs : &!a [int]
    xs[0] = 3;                               // bounds-checked; out of range traps
}

let node = box(h, Node { value: 1 });        // the heap: `Box[T]` is `res`
let tail = unbox(h, node);                   // the only thing that ends one

An arena is a region — same block, same parent chain, same occurs-check. Nothing whose type mentions a leaves the block, and release is one free whatever was allocated. Box[T] is res, so the exactly-once rule written for capabilities and file handles turns out to be a memory safety rule for free: no leaks, no double frees, no use-after-free, none of them checked by anything new.

A string is a run of bytes and claims no encoding. str is not a type; a string is &r [byte], an ordinary slice and therefore an ordinary reference, so regions, the escape check and the unique-to-shared coercion all came for free.

Arithmetic is checked. +, -, * and unary - produce the right answer or trap — they never wrap. Wrapping is expressible but has to be asked for by name. Division traps on a zero divisor and on int::MIN / -1 rather than being undefined. Evaluation order is left to right everywhere, including a struct literal’s fields, and that is enforced rather than merely intended.

Real programs run on this, byte-for-byte checked against the originals: GNU coreutils base64 and sort (porting.md), a REST endpoint answered over a real socket (net.md), and examples/seek/, not a port — a search tool shaped for an agent calling it rather than a person typing at a shell (agent-tools.md).


cancho builds on other people’s ideas, and the closest of them got there first.

  • Austral — the nearest relative: linear types, capabilities as linear values with a root capability handed to the entry point, lexical borrowing, no borrow checker. Most of this core is Austral’s first. cancho adds the same authority stated as an exact effect row, which is what cancho authority reads.
  • Koka — effects as a row of labels. Kept the row; no handlers, no row polymorphism.
  • Effekt — effects as capabilities, from the effect-handler side: the closest statement of “an effect is a borrowed capability”.
  • Cyclone — lexical regions; here without Rust’s inference.
  • Rust — ownership as move; the borrow checker declined.
  • Vale — generational references, which Gen is.
  • Zig — defer.
  • Pony, Hylo — other answers to aliasing and ownership. Pony’s val means something different from this one.
  • The object-capability model (E, Robust Composition) and Capsicum — no ambient authority.
  • Lex — where the worldview came from (history; cancho no longer depends on it).

The competitor is WASI, not Rust. For running code you did not write, WebAssembly with WASI enforces authority at run time, by trying. cancho knows it before execution, from the program’s text, with no runtime cost. What each project contributed, traced to the document that used it: related-work.md.


Design commitments

AreaCommitmentWhy
MemoryLinear/affine types + regions/arenas — not an NLL borrow checkerLocal, cheap, total to check
EffectsCapability-typed effects in the type system, unified with linearityOne resource system, not two
BehaviourNo UB, defined evaluation order, deterministic layoutReproducibility is load-bearing
ASTCanonical, content-addressable, stable per-unit identityDesigned in, never retrofitted
TypesFast, total, decidable inferenceThe guarantee must be cheap
MetaprogrammingHygienic deterministic comptime — no textual or proc macrosMacros break stable content-addressing
GenericsMonomorphisedZero-cost, matches Rust’s codegen
FFIExplicit, capability-gatedC’s effects must not be invisible
BackendLLVM by default, Cranelift for fast dev buildsReuse; own backend only if zero-C becomes a goal

Taken from Lex’s design, long ago: examples-as-tests, [budget], effect declarations as the function’s contract.

Explicit non-goals

  • Not a Rust clone. No trait-system maximalism, no GATs, no specialisation, no borrow checker. Chasing Rust’s power means inheriting Rust’s implementation cost and abandoning totality — the failure mode this design exists to avoid.
  • Not self-hosting-first. The compiler started in Rust, as a starting point, not an end-state; it is now being written in cancho itself, stage by stage, with the Rust compiler as the check at each one (self-hosting.md).

Performance

Linearity and effects are erased at compile time; generics monomorphise. The gap to C is a backend question, not a price of ownership: Rust sits within 4% of C on the same kernels (against-c-and-rust.md), and once the overflow trap and a vectorising backend (--backend llvm, now the default) are both in place, this language measures statistically indistinguishable from C on the one kernel checked directly against it.

The derivation — what the trap costs, what each backend can and cannot express, and every kernel measured — lives where it was measured rather than restated here: overflow-cost.md, check-cost.md, backend-limits.md, llvm-backend.md, benchmarks-game.md.


Layout

crates/cancho-syntax    lexer, canonical-shaped AST, parser
crates/cancho-types     the type vocabulary: representation and unification
crates/cancho-ir        resolution, type checking, monomorphisation; the IR
crates/cancho-codegen   Cranelift lowering, native object emission
crates/cancho-codegen-llvm  the LLVM backend, --backend llvm (default)
crates/cancho-id        canonical encoding and content hashes
crates/cancho-vcs       content-addressed operation log (vcs.md)
crates/cancho           the CLI
std/                     the standard library, as Lex source
examples/                programs meant to be read
packages/                published cancho-vcs packages (package-system.md)
tests/accept             fixtures that must compile and run
tests/reject             fixtures that must be refused, each stating why
benches/                 checked/wrapping pairs; what the overflow trap costs
scripts/bench.py         runs them and prints the table
docs/                    this directory

Everything a program can be refused for is refused in cancho-ir, so the backend has no error path for a program — only for the environment. Each fixture declares its own expectation in its header (//~ STDOUT, //~ EXIT, //~ ERROR), so adding a rule to the language means adding a fixture.


Every design document

One line of purpose, one short phrase of status. What each slice found — the measurements, the bugs, the corrections — is ROADMAP.md, not repeated here; a doc’s own header carries its own detail.

DocPurposeStatus
linearity-and-effects.mdCore type-system rules: linear/affine ownership, capability-typed effects, how they unifysettled and built (#2)
bootstrap.mdWhere self-hosting stands and how to use the compiler in cancho; below it, what M0 settled (host language, file extension, the M0 surface)current; history below
canonical-ast.mdAST shape, canonicalisation, per-unit identity (cancho ids)written and built
memory-model.mdRegions/arenas, escape, the escape hatchesnot written — settled piecemeal by heap.md/sharing.md; nothing left to write on its own
strings.mdA string is bytes, not an encoding; byte as storagesettled and built
boxed-slices.mdBox[[T]]: a pointer and a lengthsettled and built
many-files.mdA program in more than one file; identity by contentsettled and built
arguments.mdThe Args capabilitysettled and built
reading-references.mdReading through a reference; a reference gives referencessettled and built
heap.mdHeap and Box[T]: one value, one allocationsettled and built
filesystem.mdFs(prefix), the path checksettled and built
sharing.mdWhy Rc is not expressible, and Gensettled and built — corrects linearity-and-effects.md §9
tuples.md(A, B) as a structural anonymous structsettled and built
shadowing.mdShadowing: allowed exactly when the binding is deadsettled and built
standard-input.mdgetchar: a second label on Io, not a new capability; read_bytes (edition 9): the bulk read behind the same label, 73x getchar on macOSsettled and built
modules.mdmodule/import/pub; a module is a namespace, not a trust boundarysettled and built
standard-library.mdWhat belongs in std, and how a function earns its way inwritten and built
mode-polymorphism.md[T: val] boundssettled and built — found a leak and a double free
collections.mdWhich collections hold a resource, and why it’s about shapesettled and built
json.mdstd.json: a strict, zero-copy tape parser and a Writer that cannot write bad JSONbuilt; 13,000 numbers bit-exact against Rust, 1,500 mutated documents against serde_json, 355 MB/s to the tape; parse_with (§3.1): no region per call, 5.8x on macOS and 8.7x more at 16 threads, identical to parse on 58,733 documents and the 318 files of JSONTestSuite
map.mdstd.map: a hash map from byte strings to copyable values, iterating in insertion orderbuilt; tested against a model over 30,000 random operations; found the LLVM backend growing the stack inside loops
http.mdstd.http and std.route: a strict request parser (refuses what request smuggling lives on) and a router that answers with an id you match onbuilt; 1,089 requests identical to httparse field for field, none accepted that it refuses; 1.0-1.4 µs a request, flat to 5,000 routes
server.mdexamples/api: a JSON API server over std.http, std.route, std.json and native sockets – one thread, a Poller, keep-alive, pipelining, no Ffibuilt; 17 tests over real sockets; migrated off poll(2)/libc (§8): authority now exact, throughput 134,000 → 73,000 a second – the kernel’s epoll in this VM, not the program (a C server falls the same way) – still ~25× FastAPI
http-server.mdhttp.server: the server loop as a package – wait/next/respond, the application owns its loop; why a callback does not type-check; hold/answer for a request whose answer is not ready, and the server’s poller shared with the application’s own handles (§10); the byte-fed mode (§11): open_bytes/attach/input/output/detach for a server with no sockets, so it can sit behind the TLS engine, and examples/https_hello that does; streaming request bodies and Expect: 100-continue (§12): limits, then body_part/body_take/body_state/proceed on a held request’s ticket, in-place chunked decoding, rule-tagged refusals, a head deadline and a body stall timeoutbuilt; first package that imports std; examples/api migrated, 21 + 2 tests, ~7% faster than the loop inside the example; §10 tested against 7 mutations; §11: 15 tests without a socket, 79 of 90 mutants, HTTPS example against curl, OpenSSL and Python, cost measured; §12: 22 tests without a socket and 4 with, uploads to 1 GiB hashed, mutants (§12.13)
http-client.mdhttp.client: a non-blocking HTTP/1.1 client for a program with one poller (cancho-gateway’s upstream side, http-server.md §11.8 item 4): a table of connections fed bytes and returning events, no I/O and no authority in the package; streamed request bodies with back-pressure, status and headers as soon as complete, bodies in pieces by length / chunked / until close, Expect: 100-continue, 1xx skipped, early responses; pooling with the strict reuse rules and the one retry of cancho-hooks §53; timeouts on the caller’s clock; TLS to upstreams through packages/tls in the example’s driver; DNS the caller’sbuilt; 118 unit tests on both backends, 517 mutants, a million damaged responses, live against Python, nginx, examples/https_hello and a hostile server over plain TCP and TLS 1.3, measured cost (§10)
parallelism.mdthreads and vectorization: what exists, what was measured (LLVM vectorizes what cannot trap; a multi-field payload crosses by reference; DuckDB on one core), two ways of keeping the overflow guarantee that did not work, and the order to do it in, each step with a gate that can failstrategy, with its measurements (benches/parallel/)
atomics.mdatomics and a bounded channel: hard constraint, or nobody asked? – nobody asked (the compiler already emits an atomic, both backends have all five operations, &Atomic already crosses); one res Atomic of n words in heap memory because a borrow copies, sequentially consistent only, row conc, edition 8; and the finding that the checker does not stop two threads writing one value (threads.md §3 corrected). A gate fixed first: litmus tests with a power check, stress, nine mutants, a model check; stages A0-A3design, with measurements (benches/atomics/); not built
zeroed-slices.mdbox_slice(h, n, 0) (and byte_of(0), false, 0.0) is calloc with no fill loop; untouched pages are never resident. jsonq: 0.46 s to 0.15 s, 427 MB to 89 MB peakbuilt on both backends; freed-memory and residency checks; seven of eight mutants killed, the eighth equivalent
checked-output.mdflush_out(io) -> [io_write] Done, edition 5: flush standard output and learn whether what was written arrived – fflush and then ferror, because fflush answers 0 after an earlier failure emptied the buffer (measured); gap L1 of agent-toolbox.mdbuilt on both backends; /dev/full, a closed descriptor and an earlier failure each observed; seven mutants killed
memory-moves.mdcopy_within(buf, dst, src, n): a bounds-checked memmove inside one slice, edition 5; why a byte loop cannot do it (overlap, direction, 0.5 GB/s: 114.7 ms to compact a 64 MiB arena) and what the two backends emitbuilt, with a fixture, nine trapping shapes on both backends and three mutants checked
byte-search.mdindex_of_byte(text, b): the first b in a slice as one memchr, edition 5, and std.bytes’ find and count_byte on it; four to five times faster when the byte is rare, and the measured LLVM regression when a needle’s first byte is commonbuilt, with fixtures on both backends and mutants checked
word-scan.mdload_le64, byte_mask64, trailing_zeros, leading_zeros, popcount (edition 9): scanning memory a word or 64 bytes at a time with a checked unaligned load, a 64-byte compare mask and the bit counts, on both backends and wasm32-wasip1; measured against the byte loop and memchr on 1 GiB, on arm64 and x86-64built, with fixtures on both backends, a differential test against Rust over random buffers and every offset and length, and the trapping edges
bulk-copy.mdcopy_into(dst, src): all of one slice onto the front of another as one memmove, edition 5; why overlap is possible, and std.buffer’s copies rewritten on it (640 MiB of appends: 2.00 s to 0.026 s on Cranelift, 0.47 s to 0.026 s on LLVM)built, with a fixture on both backends, six trapping and edge shapes, and mutants checked
native-sockets.mdServers without Ffi("libc"): typed Listener/Conn/Poller handles, Clock, and the four-stage road to a toolchain with no Cslice 1 built (edition 5): tcp_listen/tcp_accept/conn_read/conn_write over typed handles on both backends, no Ffi; fixed-signature fd builtins rejected because an int descriptor is forgeable (§2); Poller, Clock, tcp_connect next; tcp_listen_on (edition 9): listen on one IPv4 literal, with the host in the authority row (§12)
udp.mdDatagram sockets without Ffi: a connected Udp for one upstream (net_out), a bound one for a server (net_in), replies to a sender only through a ticket the runtime issued; no sendto with a free destinationbuilt, both halves (udp_connect/udp_send/udp_recv/Poller; udp_bind/udp_recv_from/udp_send_to with peer tickets); both backends; round-trip measurement not done (#355, #356; asker cancho-dns#18)
signals.mdSignals/SignalWatch (edition 6): knowing you were asked to stop without Ffi("libc") – a capability narrowed to the signals it claims, so the authority report names them and stays bounded; a bitmask signals_pending, a handle the Poller waits on, and signals_close for “a second signal kills at once”built on both backends: signalfd on Linux, measured; kqueue EVFILT_SIGNAL on macOS, written and not run
conn-peer.mdThe other end of a connection: conn_peer(&Conn, &![byte]) (getpeername, 19 bytes, no effect row, not authority), std.addr (Peer, canonical text, parse, IPv4-mapped normalised, a /32 and /64 key) and std.conns.peer; asked at call time, not stored at accept; the socket peer, not an identity (PROXY protocol out of scope)built on both backends; examples/tls_echo logs it and bounds by it (#367); Linux and macOS arm64 run, x86-64 is CI’s; WASI refuses it
directory-handles.mdDir (edition 6): a path opened beneath a directory one component at a time, following no link – the fix for a symlink inside a narrowed prefix or a tool’s --root reading outside it (#227, gap L6). open_dir, dir_enter, dir_open_read, dir_close, and std.dirs for a whole path; measured against openat2slices 1 and 2 built on both backends (read beneath; create, append, rename, remove, sync beneath); cancho-tools on top is slice 3; slice 4 (edition 7) is dir_rename_new, the rename that never replaces a name, with the filesystems measured
processes.mdExec (edition 7): starting a program without Ffi("libc") – a capability narrowed to a path prefix of the programs it may start, a linear Child that must be reaped, Pipe ends the parent cannot leak, an exact environment and descriptor set, and the Poller waiting on both (#237, cancho-tools#10)slices 0 to 4 built: close-on-exec everywhere, the capability with children and channels, the Poller watching both, std.process.capture (#271, #274, #275, #276), and the MCP server that uses them (cancho-tools#17); slice 5, the two prerequisites of a run tool: a child’s working directory (exec_spawn_in, §4.10) and capture_both (§7.2); letting go of a child for a job-starting server – exec_spawn_detached, child_pid, child_release and the exec_detached label (edition 9, §4.11, #524)
directory-listing.mdListing a directory and a file’s status on a Dir handle rather than a path (#222, gaps L2 and L3): dir_list, dir_next, dir_list_close, dir_stat, and std.dirs.list sorted bytewise; the dirent/stat layout table for three targets; measured cost per entryslices 1 and 2 built on both backends (dir_list, dir_next, dir_list_close, std.dirs.list; dir_stat); the list tool is slice 3
foreign-authority.mdWhat a program that calls C can reach, symbol by symbol: Ffi("libc,libssl") is a set of libraries (one capability, lent narrower), the report lists every reachable foreign symbol as scope:symbol (unbounded_by, one a line, so a CI pin diffs an added symbol), and a foreign function must borrow exactly one Ffibuilt on both backends; closes a hole: an extern fn with no capability was accepted, ran a shell and reported bounded: true. Symbol-level rows and capabilities weighed and declined; the statx argument shape and \x found, pinned, not fixed
narrowing-into-several.mdWhy a program that reads two unrelated files (/dev/urandom and a CA bundle) holds Fs("") and reports fs_read(""): narrowing consumes, so one Fs makes one chain. Options weighed (a set of paths, a remainder, a fork from a borrow, lending a root narrower, directory handles); recommended: narrow(fs, "a", "b") answers a tuple of children, for Fs and Exec, with no report change. Found a latent row bug: Label::covers compared path labels by byte prefix; fixed in PR #359built (PR #364; the covers fix first, PR #359); all six questions decided as proposed 2026-10-07; found by cancho-pg#14; examples/tls_echo_fixed is the payoff
websocket-spike.mdA WebSocket/OCPP connection layer on cancho against the lex runtime: 10,000 connections, measured, plus the gaps found and the cancho-cache-for-Redis questionmeasured: 80 MB and 0% idle CPU at 10,000 connections (the runtime’s thread-per-connection stand-in saturates a core near 1,500); the service-level gaps are listed in §10
formatting.mdcancho fmt: the canonical layout with comments, blank lines and literal spellings keptbuilt; refuses, rather than risks, a file it cannot reproduce
testing.mdtrap() and std.test’s assert — what a program needed to state “this must be true” at alltrap(), std.test, and cancho test are built; no per-test timeout, no message on a failed assertion
satisfy.mdThe spec-to-implementation loop: a contract is a .cho file whose tests call the candidate; satisfaction is check plus test composed, with the verdict and the recomputed SigId answered (#406)built; the first contract (tests/satisfy/) is CI-checked
slicing.mds[a..b], half-open and trappingsettled and built
defer.mddefer E; as sugar, expanded during loweringsettled and built
authority.mdcancho authority; release at main is the declarationsettled and built
budget.mdWhether [budget] is a type-system featuresettled — no
reach.mdWhat a program can reach, via a real REST endpointsettled and built; corrected by opaque-pointers.md and foreign-linking.md
opaque-pointers.mdc_ptr: one opaque foreign-pointer shapesettled and built (edition 3)
foreign-linking.md-l/-L: linking beyond libcsettled and built — examples/tls_client/, a real handshake
tls-nonblocking.mdhttps for a service with one thread: a non-blocking OpenSSL client on the Poller (memory BIOs), certificate verification with every failure told apart, the authority it costs, a resolver that does not stop the loop and a pinned address, what cancho-hooks would change, and 12 gaps with reproducersspiked and measured (examples/tls_nb/): 64 handshakes on one thread, ~0.6 ms of CPU each (0.3 resumed), 26-48 KiB a connection; not built into any service; OpenSSL through FFI by decision, a pure cancho TLS is a research project
overflow-cost.mdWhat the overflow trap costs, measuredmeasured — corrects the README and defined-behaviour.md §2.1
bitwise.md& | ^ ~ << >>, hex literalssettled and built
porting.mdReal programs ported and checked byte-for-byte: base64, sortdone twice
against-c-and-rust.mdcancho against C and Rust on the same algorithmmeasured
purity.mdThe checked purity proof C can only promise and Rust can’t statemeasured and unspent
floating-point.mdfloat, IEEE-754 binary64settled and built
float-printing.mdShortest round-trip decimal printingsettled and built
f32.mdf32, IEEE-754 binary32, asked by cancho-gpu: the type, the f32 suffix, f32_of/float_of32/bits_of32/f32_of_bits, the bit-for-bit gate against binary64, sqrt32 and the int conversions, and std.fmt32 (Rust’s {:?} and {:.N} and a decimal read straight to the nearest f32, every 2³² pattern checked)F1, F2 and F3 built (edition 6); F4 cancho-gpu not
compile-time.mdConstant folding and pure-call evaluationsettled and built
compile-time-data.mdstatic items evaluated during compilationsettled and built
layout.mdWhat every leaf costs; packing and transposingmeasured, deferred with a trigger (cancho layout)
benchmarks-game.mdFive kernels from the Computer Language Benchmarks Gamemeasured
bulk-io.mdwrite_bytes: a whole slice in one callsettled and built
file-handles.mdAn open file as a linear resourcesettled and built
file-writes.mdThe write side of a file handle: append, positional read/write, fsync, truncate, rename, lock — what a durable log needs; fopen-based opens to avoid variadic openslice 1 built (edition 5): open_append/open_write/open_new/open_rw, file_write/file_pwrite/file_pread/file_sync/file_truncate/file_size on both backends; the cost of sync measured (0.4 µs written, ~190 µs durable, 4.2 µs batched by 100); slice 2 adds fs_rename, fs_remove and file_lock (a lock the kernel drops when the holder dies)
utf8.mdDecoding &r [byte] into code pointssettled and built
standard-error.mderr_write: a third label on Io, not an eighth capabilitysettled and built
../AGENTS.mdHow to write cancho in one pagewritten and enforced
float-math.mdsqrt as a builtin, then exp/log/pow, then floor/ceil/round/fabs/fmin/fmax/sin/cos, then exp/log/pow fixed to 1–4 ulp and expm1/log1p/log2/log10/the hyperbolics added, then tan/atan/atan2/asin/acos and a Payne–Hanek reduction for every finite argumentsettled and built; every function measured against libm in ulps
gpu.mdWhether cancho can run on a GPU, and whether lex-gpu should existmeasured; decided
line-reading.mdWhether std needs a line readermeasured — no; found and fixed a silent truncation bug
agent-errors.mdRefusals a machine can read: stable rule tags, check --output jsonsettled and built
structured-ingest.mdThe AST as JSON and back: cancho print --output json and cancho ingest, the ingest path an agent writesbuilt
tty.mdTty, the serial-port capability (#422 T1): both measured termios ABIs, the surface, the labels — design
crc.mdstd.crc: CRC-16/XMODEM, CRC-32/IEEE and two CRC-64s (XZ, and Redis’s Jones), and the two-halves designbuilt
net-bounds.mdSeparate ways in and out, set-valued bounds, exact host matching (#362)design; §2.4 is a report fix recommended first
shared-store.mdSeveral threads, one store: the answer is process-per-core, with the two missing primitives named (#354)design; answered, topic closed
agent-tools.mdA tool genuinely shaped for an agent’s own loopbuilt (examples/seek/)
agent-toolbox.mdWhether a set of unix-like tools in cancho, with a JSON contract, rule-tagged errors with repair hints and a compiler-derived authority, is worth building — and the protocol that would say sodesign; the probes it rests on are run and recorded, nothing is built. Found: Fs extent cannot be static (narrow takes a literal), no directory listing or regex, sha256 traps past 64 KiB, a failed stdout write is invisible, and lex-os reads a cancho net_out label as no network; corrects bulk-io.md §3.3 and agent-tools.md §1
aliasing.mdWhether &! should mean Rust’s &mutmeasured — no
check-cost.mdThe price of every check this language emitsmeasured — corrects overflow-cost.md and gpu.md
poison.mdA per-lane flag instead of a trap, on a vector ISAmeasured — depends on the check
backend-limits.mdWhat Cranelift can and cannot be asked foran audit, checked against source
llvm-backend.mdA second backend, --backend llvmcomplete, and the default
effect-polymorphism.mdWhether an effect row can be polymorphica documented no, counted by reading
hash-stability.mdHow often a content hash actually movesmeasured — the plateau vcs.md needed
stability-exemptions.mdThe plateau rule as a checked property: every historical .cho revision that fails today’s compiler, by rule, with a reason and a count; scripts/history.py --explain and the stability-plateau CI job fail on any increasebuilt; measured baseline 210 revisions, 140 failing (67%)
vcs.mdHow much of lex-vcs cancho can reuseplateau answered yes; foundation, gate, op log and attestation built
vcs-publish.mdThe first real caller of cancho-vcs: publish, no diffing neededbuilt (cancho vcs publish/log)
package-system.mdWhat a package would be, built from what already exists rather than invented freshcancho vcs resolve/vcs lock/vcs fetch are the first three real slices; net.sockets, net.connect, agent.wire, http.request, and http.response (packages/) are five real packages, examples/fetch/fetch.cho composes two of them at once with no new tooling, and http.request/net.connect/http.response each depend on net.sockets – a true closure of stores, resolved recursively by vcs publish --requires/vcs resolve/vcs fetch (§4.6), the one thing this row used to call missing. §7 (packages across repositories) is built for its first two steps: a lock can carry an origin (a git URL, a full commit hash and a path) and vcs fetch/resolve/lock --git fetch it into a cache of checkouts keyed by commit, with every pin re-checked as for a local store; vcs publish --dir publishes a library of several files in dependency order, and static declarations now publish. §8 (the project file) is built, cancho test in a project included: cancho.toml, cancho install, cancho add, cancho build with no files, and a compiler revision in --version that a project checks. §9 (prebuilt compilers) is built, unreleased: scripts/package-release.sh, scripts/install.sh (checksum and reported commit both checked) and a release workflow whose tag is the commit. Still missing: the first published release, and a human-readable version string
character-literals.md'a': a third spelling of an integersettled and built
emitted-checks.mdThe price list, read out of the binaryan audit; found a real compiler bug
flags.mdstd.flags: a cursor, not a getopt_long tablesettled and built
first-page.mdWhat a reader of README.md actually learnsmeasured, and acted on — this guide is the result
under-a-grant.mdWhether a lex-os grant can decide a cancho authority reportmeasured — filesystem is enforceable; network and exec are not
net.mdNet: sockets, taken out of libcsettled and built
related-work.mdWhat cancho took from Cyclone, Koka, Rust, Vale, Zig, Lex, Australwritten
differential.mdThe constant folder against the backenda test; found and fixed one disagreement
connect.mdexamples/fetch/: an HTTP client, and what connect neededa probe
listen.mdexamples/collect/: the inbound counterparta probe; cleared Net’s last bar
internal-errors.mdA backend failure as a located refusal, not an environment errorsettled and built
fuzzing.mdA mutation fuzzer over the whole corpusa test; found four printer bugs
function-values.mdWhether closures exist, and what mode a capturing one hassettled and built
threads.mdCompiler-provided spawn/join, never crossing the C ABIsingle-leaf slice and owned-capability case built; multi-field payload decided not yet
editions.mdAn edition marker, a path for renamesdesign, measured
defined-behaviour.mdEvery place C/Rust leave behaviour open, defined insteadwritten and enforced
crypto.mdstd.crypto’s first slice: SHA-256built
sha512.mdstd.crypto’s second slice: SHA-512built
ed25519.mdstd.ed25519: sign and verifybuilt
chacha20.mdstd.chacha20: ChaCha20, Poly1305 and the AEAD (RFC 8439), for the pure TLS clientbuilt; not independently reviewed
hkdf.mdSHA-384, std.hmac and std.hkdf for the TLS 1.3 key schedule; the 64 KiB trap every std hash hadbuilt; not independently reviewed
tls-pure.mdThe TLS 1.3 client with no C library: where the code lives, the sans-io API both backends share, cipher policy, threat model, trust, refusal tags, open questionsdesign; the protocol built in packages/tls (#205, tls-core.md), chain validation not yet (#206)
x25519.mdstd.x25519, and std.field25519: one constant-time field for X25519 and Ed25519built; checked with Valgrind (ctgrind); not independently reviewed
x509.mdpackages/x509: a strict DER reader and an X.509 v3 parser, the pure TLS client’s certificatesbuilt; not independently reviewed
rsa.mdstd.bigmod (Montgomery arithmetic to 4,096 bits) and std.rsa (PKCS#1 v1.5 and PSS verification)built; not independently reviewed
ecdsa.mdstd.ecdsa (P-256 and P-384 verification, DER and raw signatures) on std.bigmod’s new register APIbuilt; not independently reviewed
tls-core.mdpackages/tls: the TLS 1.3 client handshake and record layer – the PR split, a slot, the state machine, pinned certificates until #206, and why the byte-for-byte gate uses a recorded ChaCha20 handshake instead of RFC 8448’s AES tracebuilt: the protocol, the 64-slot engine, live servers, a lying server, 22 mutants killed
x509-verify.mdpackages/x509’s chain verification for #206: the API over a sent chain’s ranges, path building with backtracking and a budget, what leaf, intermediate and root are each checked for, name constraints and the types it cannot read, and the four gates (x509-limbo, an OpenSSL matrix, saved real chains against the system roots, mutants); §10, a chain verified with no name for a purpose (verify_chain, for verify-ca and client certificates), the name as its own step, and the hazardbuilt: 9,743 of limbo’s 9,802 cases pass (every BetterTLS one), 39 disagreements each explained against OpenSSL, 22 mutants killed; packages/tls verifies with it. §10: a 56-case no-name matrix against openssl verify -purpose sslclient/sslserver, one known disagreement listed; 33 mutants killed
tls-parity.md#207: parity with the OpenSSL backend (“AEAD parity”): what OpenSSL 3.0.13’s ClientHello offers, measured; constant-time AES-GCM, P-256/P-384 key exchange and HelloRetryRequest, TLS 1.2 with ECDHE and AEAD suites only; what stays different and why; client certificates and ALPN, and the revocation and post-quantum decisions (§6)built: AES-GCM (§3.1.1), P-256/P-384 (ecdh.md), TLS 1.3’s AES-GCM and HelloRetryRequest (§3.3.1), TLS 1.2 (§3.4.1); §6 (#386): client certificates and ALPN built, revocation and X25519MLKEM768 decided (proposed)
ecdh.mdstd.ecdh: P-256 and P-384 key exchange with a secret scalar – complete formulas, a masked table, std.bigmod’s reductions made constant time, and the timing test that found LLVM turning the masks into branchesbuilt; not independently reviewed
value-barrier.mdvalue_barrier, edition 6: a value clang -O2 cannot reason about, so a constant-time mask stays an andbuilt
crypto-builtins.mdHardware AES and carry-less multiply: aes_encrypt_block, ghash_update (since #382 ghash_powers, gcm_tag, gcm_tag_diff; aes_ctr32) and hw_aes_gcm, block-level over byte slices so no 128-bit type is needed; measured so far: Cranelift has no such instruction, and LLVM compiles the intrinsics only with the target feature onbuilt: DIT, the builtins, per-key caching and the std hardware path
wide-multiply.mdmul_wide, add_carry and sub_borrow: a 64x64->128 multiply and add/subtract with carry as builtins over int pairs, lowered without a branch or a libcall on both backends and on wasm32; prototyped on X25519 (5 limbs of 51 bits); then P-256’s kernels (five 56-bit limbs inside them), std.bigmod (64-bit limbs) and Poly1305 (44-bit limbs) moved onto them (#381, §12)built; measured on x86-64 and arm64; dudect, mutants and differentials in §12
gcm-wide.md#382: multi-block AES-GCM and aggregated GHASH: where a record’s time goes (the cipher is 17% of 16 KiB, a region’s malloc is 88% of 64 B on Darwin), aes_ctr32 eight blocks at a time, GHASH reflected with one reduction for eight blocks and the powers of H in the prepared key, a tag in one call with no scratchbuilt: 5.9 to 11.2 times at 16 KiB, 89% (x86-64) to 68% (M4) of OpenSSL’s speed; the record layer is what dominates now
sha256-builtin.md#445 and #446: hw_sha256, sha256_blocks and sha256_block_words (SHA-256’s compression on SHA-NI and ARMv8 SHA-2, a state of eight ints and N blocks of bytes, or one block of words where std.crypto keeps its partial block), std.crypto on them, and HMAC with a key’s pads hashed once (hkdf.md §9); Valgrind hides SHA-NI on x86-64, so the guard counts the software path therebuilt
tls-hooks.mdThe pure TLS backend in cancho-hooks (#210): the two interfaces are not the same (an adapter is needed), one source cannot carry both capability rows (no effect polymorphism), the pure client has no resumption, and “no Ffi at all” cannot hold because hooks also holds libc; the build, the tests on both backends, the measurementsdesign
tls-resumption.mdTLS 1.3 resumption for packages/tls: the full handshake’s 2.8 ms is two X25519 and two ECDSA verifications (measured); PSK with (EC)DHE removes the verifications and keeps forward secrecy, about 1.2 ms; eight rules bound the hazard that a resumed session skips verification. Built: resumes against OpenSSL, Go, rustls, wolfSSL, nginx and GnuTLS; a resumed connection measured at half a full one’s CPUbuilt
tls-server.mdA TLS 1.3 server for packages/tls, for cancho-mqtt and cancho-gateway: constant-time ECDSA P-256 signing first (ecdsa-sign.md, built), then the server handshake with SNI, ALPN and HelloRetryRequest (built: interop with OpenSSL, curl, Go, wolfSSL and mosquitto, a lying client of 110 cases, a differential against openssl s_server, fuzzing and mutants; a full handshake measured at 3.5 to 4 ms of CPU on X25519 or P-256 (6 ms on P-384), 17 to 20 times OpenSSL’s); then examples/tls_echo, the server the broker and the gateway copy (§11: handshakes bounded in progress and a second, delayed not refused; reload on SIGHUP; close_notify on stop; 3.0 ms a handshake on an M4 and 5.5 ms on x86-64; no foreign code); session tickets (§12, #394); client certificates (§13): modes off, optional and required, a trust store of its own that is never the system bundle and is replaced at run time, the CertificateRequest, the client’s chain verified for clientAuth and its CertificateVerify (P-256, P-384, RSA-PSS, Ed25519), the verified identity given to the program per slot, no slot memory; interop with OpenSSL, curl, Go, wolfSSL and mosquitto, a lying client of 78 connections, a differential against openssl s_server, a fuzz harness, 202 mutants; a client certificate costs 1.7 to 4.2 ms of the server’s CPU on an M4; TLS 1.2 as a later stepsteps 1 to 5 built; not independently reviewed
tls-memory.mdMemory per TLS connection (#383, of #378): measured at 50 to 10,000 connections on arm64 and x86-64 beside OpenSSL’s server (an established idle tls_echo connection is 119 KiB, OpenSSL’s 14 to 49); where the slot’s 263 KiB and the 119 KiB are (the work area std.ecdh needs is 75 KiB of integers every handshake writes, and nothing is released); six ways out, and the one chosen: a small core per slot, and the handshake’s scratch and the record buffers leased from a pool of work areasbuilt, measured: an established idle tls_echo connection 17 KiB (from 119), 4.5 KiB of it the engine’s; §11: one whose client presented a verified certificate 19 KiB (from 126: #398 kept the leaf in the work area), the leaf in a 2 KiB room a slot
ecdsa-sign.mdstd.ecdsa_sign: ECDSA P-256 signing in constant time on std.ecdh’s ladder, RFC 6979’s nonce hedged with the caller’s randomness, a signature verified before it is given out; and packages/x509’s x509_key, P-256 private keys from PKCS#8 and SEC 1 PEM. Step 1 of tls-server.mdbuilt; not independently reviewed
tls-performance.mdTLS performance (#378), first deliverable: where the time of a full server and client handshake goes on x86-64 and arm64, measured beside OpenSSL. Three operations (two X25519, the signature, the verifications) are 77 to 91% of a handshake and each costs inside it what it costs alone; the engine’s copies, zeroing, hashing, HKDF and event loop are about 5%; every region is a malloc and free of 64 KiB, which is 6 to 10% of a handshake and 45 to 53% of a request on a kept connection (glibc told not to trim made a request 1.9 to 2.5 times cheaper on x86-64). Targets and gates stated first; a ranked lever list with gains computed from the measured parts (the 5-times end of the target is reached without a new builtin, by 1% (arithmetic); wide multiply is the one thing the language cannot do); a regression guard counting instructions under valgrind, in CI; open questions. §10, as built after #392 to #398: the server’s full handshake 2.86 times OpenSSL’s cycles on x86-64 (2.12 times its CPU on arm64), the client’s 2.77 (2.26), a resumed one 2.34 (2.17), a kept request 1.11 to 1.15; the allocator now the largest cost of every handshake (telling glibc not to trim removed a third of a full one and half a resumed one); each lever against its projection, T1 to T4 met and T5 not, the next levers rankedmeasured; design; the levers built in #392 to #397, the guard’s baselines complete
record-in-place.mdL1 and R1 of #378 (#447, #448): a kept https_hello request spent 24% copying a connection’s core into a work area and back and clearing it, and that was the 296 words copied one at a time, not the bytes; a TLS record went through a region and byte loops around the cipher. Block copies; an established connection that rests in its area until another needs one (at most 32, bounded memory); seal_in_place/open_in_place for AES-GCM (both paths) and ChaCha20-Poly1305, the record sealed where it is sent from and opened straight into the received data, no regionbuilt: a kept request 1.20 to 0.94 times OpenSSL’s cycles (x86-64); a record through both engines 5.7 (64 B) to 1.5 (16 KiB) times cheaper; 2.1 MiB more per engine at most, idle connections unchanged
region-limit.md#514: a region is one chunk, 65,536 bytes, and an allocation past it was a bare SIGILL learned from a crash in production. Measured exactly (8,192 ints or 65,536 bytes, the whole chunk, on both backends); a constant size is refused at check with region-chunk-exceeded, the message saying the limit and box_slice; a size the compiler cannot see still traps but writes a named line to stderr, on both backends. A heap-backed chunk is argued and not built (a language decision)built on both backends; the boundary measured; mutants listed in §6
region-chunk-cache.mdA1 of #378 and #375, one mechanism: every region was a 64 KiB malloc and free, and glibc trimmed and regrew its heap for nearly each (112 brk calls a full TLS server handshake, 136 a resumed one, measured; 0.7 µs a pair on macOS and serialised across threads). Each thread keeps the chunks its regions give back, at most 16 (1 MiB), last in first out, which is the region stack (regions nest strictly); the bytes a region used are cleared when it ends; a thread’s chunks are freed when it ends; the same runtime on both backends, a global on WASI. Options measured or argued (mallopt is glibc-only and changes every allocation), hazards (secrets, threads, memory), gates stated first. As built: 9 brk calls in a whole server run; a full TLS server handshake 34.6% cheaper (1.86 times OpenSSL’s cycles on x86-64), a resumed one 55.8% (1.04 times OpenSSL’s resumed: T5 met for the server; the client’s 1.21, not met); 16 threads on macOS 0.04 s where 8.8built on both backends and WASI; counted under strace and an LD_PRELOAD shim; 14 of 14 mutants killed
p256-fast.md#380 (of #378): faster P-256 – where the time goes (field operations are 93-98% of every operation, a bigmod.mul is 115 ns of which 55% is checked arithmetic), then a ten-limb 28-bit field with straight-line generated kernels and the NIST prime’s form, a fixed-base table for k·G as a static, wNAF verification; §11, a table for a key that verifies many signatures (the server’s own, B2; a client’s P-256 trust anchors, B3): four runs of 65 wNAF digits sharing 64 doublings, 10,305 bytes a key, once per engine and key; a verification 2.1 times cheaper (2.26 times fewer instructions), a full server handshake 24% fewer instructions (1.46 times s_server), a client’s 18% (1.41 times tconn); §12, P-384 measured (8 to 17 times OpenSSL) and decided: its verification is worth it, on #381’s field; §13 its multiplications on mul_wide (#381), four 64-bit limbs measured and not chosenbuilt; gates in §8, §11.6 and §13.3; not independently reviewed
self-hosting.mdWhether cancho could host its own toolchainrun; decided not yet — no asker
agent-cli.mdcancho introspect/skill: the CLI surface as data, imported from lex-lang’s ACLI integrationbuilt; found a false-familiarity bug in the process (-o/-l/-L would have rendered as --o/--l/--L)
next-phase.mdReplacing episodic duplication/staleness hunts with a mechanical check, argued from MANIFESTO.md and three of this session’s own mistakes§3’s real instance (10 examples duplicating std.io) migrated, 7 of them (§3.1 has the three exceptions); §4’s own standing check, the actual proposal, not built yet

linearity-and-effects.md was the gating artifact: the decision set that determined whether this is a three-month prototype or a three-year project. It was settled before M2 started, its must-reject list was read as what it is — the M2 conformance suite, stated in advance — and M2 was then built against it slice by slice.