# AILang Roadmap

Priority-ordered list of upcoming work — milestones, features, todos,
and ideas. The orchestrator maintains this file. The user can request
additions; the orchestrator chooses what to remove or reprioritise as
work progresses.

## Conventions

- One checkbox per entry. `- [ ]` is open; `- [~]` is in progress
  (work has started — a plan exists, a branch is open, or commits are
  landing); `- [x]` is done. A finished entry may stay briefly for
  context, then is removed (with a one-line mirror in
  `docs/journals/`).
- Each entry is tagged by **kind** and lives under a **priority**
  bucket:
  - **\[milestone\]** — big chunk that will get a `docs/specs/<milestone>.md`.
  - **\[feature\]** — smaller addition inside a milestone, no full
    spec.
  - **\[todo\]** — concrete task that can run without a brainstorm
    (cleanup, doc fix, mechanical refactor, test backfill).
  - **\[idea\]** — not yet decision-ready, no commitment.
- Optional `depends on:` line names another entry that has to land
  first.
- Optional `context:` line points to the journal entry (per-iter
  file under `docs/journals/` or, for pre-2026-05-11 entries, the
  archived `docs/journal-archive.md`) where the rationale lives.
  The roadmap is intentionally terse; rationale stays in the
  journals.
- Priority buckets:
  - **P0** — in flight. Spec or plan already exists.
  - **P1** — next up. Decision made; not yet started.
  - **P2** — medium-term. Decided in principle, scheduled later.
  - **P3** — ideas. No commitment; may be cut.

## P0 — In flight

- [x] **\[milestone\]** Remove `mut` / `var` / `assign` — CLOSED
  2026-05-18. The local-mutable-state construct removed entirely
  and atomically (`Term::Mut`/`Term::Assign`/`MutVar`, the 3 Form-A
  keywords, the 4 `mut` `CheckError` variants, `mut_scope_stack`,
  every exhaustive `Term` arm across 17 src files) in lockstep with
  DESIGN.md + fixtures + drift trio + carve-out + roadmap; `loop`/
  `recur` + `let`/`if` are the surviving forms. Justified by the
  feature-acceptance criterion applied inverted (clause 2 redundant,
  clause 3 IS the iterated-mutable-state bug class). Approach A: no
  deprecation window, JSON tags fail closed; shared codegen alloca
  machinery kept (loop reuses it, renamed `binder_allocas`) + the
  `Term::Lam` escape-guard loop half byte-equivalent. spec/plan/
  iter (`d1ad50e`/`f355899`/`07f0802`), milestone-close `audit`
  clean (architect `[high]` form_a.md fixed in `.tidy` `4837871`;
  bench causally exonerated — HEAD vs pre-milestone bench binaries
  `cmp`-byte-identical, no ratify), `fieldtest` clean (4 fresh
  programs, all worked first try without mutable locals — the
  removal thesis empirically confirmed). Decoupled from
  Stateful-islands. Stays here briefly for context; remove once
  stale (full record in `docs/journals/INDEX.md`).
  - context: `docs/specs/2026-05-18-remove-mut-var-assign.md`;
    `docs/specs/2026-05-18-fieldtest-remove-mut-var-assign.md`;
    `docs/journals/2026-05-18-iter-remove-mut-var-assign.1.md`;
    `docs/journals/2026-05-18-audit-remove-mut-var-assign.md`

- [x] **\[milestone\]** Prose `loop` binders — projection redesign —
  CLOSED 2026-05-18. Form-B prose now renders loop binders as a
  parenthesised init-list on the keyword (`loop(acc = 0, i = 1)
  { … }`, positionally isomorphic to `recur(...)`) instead of bare
  `name = init;` statements inside the body block (which misread as
  C/Rust re-init-every-iteration). Single-iteration, projection-only:
  one `ailang-prose` `write_term` `Term::Loop` arm + 2 committed
  byte-equality `.prose.txt` snapshots/tests; loop/recur AST, Form A,
  JSON-AST, typecheck, codegen, and the Form-A↔JSON round-trip
  invariant all byte-unchanged. brainstorm→plan→implement
  (`c657e74`/`6533134`/`c9355d7`); milestone-close `audit` clean
  (architect zero drift; bench carry-on — sole firing is the tracked
  P2 `*.bump_s` environmental staleness, latency-tail proven sampling
  noise by re-run); no fieldtest (projection-only, no authoring-
  surface change). Stays here briefly for context; remove once stale.
  - context: `docs/specs/2026-05-18-prose-loop-binders.md`;
    `docs/journals/2026-05-18-audit-prose-loop-binders.md`; surfaced
    from the loop/recur prose review (2026-05-18 chat).

- [x] **\[milestone\]** Standalone `loop` / `recur` — CLOSED 2026-05-18.
  Strictly-additive strict-iteration surface, `recur` tail-position-only,
  no totality claim. Shipped: iter 1 `a179ec3` (additive AST nodes),
  iter 2 `1566ce0` (typecheck), iter 3 `edd2558` (codegen + run-to-value
  E2E), tidy `39380d3`/`2ee9794` (lambda-captures-loop-binder rejected
  at check). Milestone-close `audit` clean (architect drift resolved,
  bench pristine 25/0 carry-on); `fieldtest` 2026-05-18 clean on all
  four milestone axes (0 bugs; diagnostics point-exact+self-fixing,
  recur-tail through match/let, loop-as-value-subexpr byte-stable,
  no-termination exact). Two orthogonal non-blocking findings routed
  to P2 todos (niladic `(app f)` — re-confirms mut-local F3; module
  `(doc)` diagnostic hint). Stays here briefly for context; remove
  once stale (full record in `docs/journals/INDEX.md`).
  - context: `docs/specs/2026-05-17-loop-recur.md`;
    `docs/specs/2026-05-18-fieldtest-loop-recur.md`; principles entry
    `docs/specs/2026-05-17-llm-surface-discipline.md` §6.2.

- [x] **\[milestone\]** DESIGN.md / docs honesty lint — CLOSED
  2026-05-18. `docs/DESIGN.md` + `docs/PROSE_ROUNDTRIP.md` now mirror
  ONLY the current state, present-tense: 14 DESIGN.md corrections
  (Wunschdenken stated-as-fact + non-citation post-mortem/doc-
  archaeology stripped) + the PROSE tool-use/MCP paragraph; genuine
  forward intent (LLM tool-use/MCP/LSP) relocated to P3. The
  two-pronged tense+modality discriminator is codified present-tense
  as a DESIGN.md `### What this document is …` meta-subsection
  (Approach A) the architect cites. Anti-regrowth two ways: a
  wrap-robust enumerated absent/present pin
  `crates/ailang-core/tests/docs_honesty_pin.rs` (a `norm()`
  whitespace-collapse helper structurally discharges the recurring
  grep/contains line-wrap failure family — strictly better than the
  effect-doc-honesty "keep-on-one-line" precedent) + wrap-robust
  advisory Sweep 5 in `architect_sweeps.sh` (contiguous-fragment
  regex) with full sweep-count lockstep + a new `ailang-architect.md`
  "DESIGN.md honesty drift" bullet. Procedural enumeration vindicated:
  the Task-4 Step-14 catch-all caught a genuine soft-wrapped extra FIX
  (Form-B prose-projection bullet) a frozen list + the spec's
  illustrative regex would both have missed. Single iter
  (`aff25cd`/`de66eb7`/`7580d43`); milestone-close `audit` CLEAN
  (architect clean, one `[medium]` advisory Sweep-5 self-match wart →
  P3 below; bench causally exonerated — HEAD vs `5bb7211`/`de66eb7`
  bench binaries `cmp`-byte-identical, NO ratify); no fieldtest (zero
  authoring-surface change). Stays here briefly for context; remove
  once stale (full record in `docs/journals/INDEX.md`).
  - context: `docs/specs/2026-05-18-docs-honesty-lint.md`
    (grounding-check PASS 10/10);
    `docs/journals/2026-05-18-iter-docs-honesty-lint.1.md`;
    `docs/journals/2026-05-18-audit-docs-honesty-lint.md`

## P1 — Next

- [x] **\[milestone\]** Embedding ABI — M1: linkable artifact +
  scalar C entrypoint + `main`-free lifecycle — CLOSED 2026-05-18.
  First of the five-milestone arc (M1–M5) making a compiled AILang
  kernel callable in-process from a concurrent Rust host. Shipped:
  additive `FnDef.export: Option<String>` (hash-stable, ~85-site
  compile-driven thread); Form-A `(export "<sym>")` modifier
  (round-trip-gated); check-side scalar-only+effect-free export gate
  (`export-non-scalar-signature` / `export-has-effects` — the
  feature-acceptance clause-3 discriminator *in code*); codegen
  `Target::StaticLib` (suppresses `@main`+`MissingEntryMain`, emits
  one external `@<sym>` forwarder per export, decoupled from
  `ail_<module>_<fn>` mangling per Decision 2); CLI `ail build
  --emit=staticlib` (`lib<entry>.a` + separate `libailang_rt.a`).
  Coherent stop PROVEN: C host links both archives, calls the
  scalar kernel, typed return holds. spec `51160e9` (grounding-check
  PASS) → plan `b0bd7ef` (Repaired) → iter `818177d` (partial 1–3 +
  Boss plan Repair: a module≠stem fixture defect, resolved Option 2)
  + `e406d07` (DONE 4–7). Milestone-close `audit` `425c4eb` CLEAN
  (architect clean — Invariant 1 holds semantically, lockstep
  intact; sole `[low]` rustfmt-divergence evidence-adjudicated
  carry-on, 1137 pre-existing tree-wide divergences prove rustfmt
  is not a project convention; bench `check.py` exit 1 decisively
  causally exonerated — 21/21 bench binaries `cmp`-byte-identical
  vs pre-milestone, the two tracked-P2 noise families, NO ratify).
  `fieldtest` `6a4e866` substantiated the thesis (0 bugs; Int+Float
  positive E2E first-try clean = clause-1; both clause-3 rejections
  precise+self-correcting; 3 working) — 3 non-blocking findings
  routed to the two follow-up items directly below. Stays here
  briefly for context; remove once stale (full record in
  `docs/journals/INDEX.md`).
  - context: `docs/specs/2026-05-18-embedding-abi-m1.md`;
    `docs/specs/2026-05-18-fieldtest-embedding-abi-m1.md`;
    `docs/journals/2026-05-18-iter-embedding-abi-m1.1.md`;
    `docs/journals/2026-05-18-audit-embedding-abi-m1.md`

- [x] **\[todo\]** form_a.md scalar-parameter mode carve-out — docs-
  honesty tidy (M1 fieldtest friction + spec_gap#1, shared root) —
  CLOSED 2026-05-18. `crates/ailang-core/specs/form_a.md` stated an
  *unconditional* "every `(fn …)` param MUST carry an `(own/borrow)`
  mode" rule in **four** places that contradicts shipped checker
  behaviour (scalars take **and require** bare `(con Int)`; a mode on
  a scalar trips body-pointing `use-after-consume` /
  `consume-while-borrowed`). All four sites rewritten symmetric to
  the pre-existing return-type carve-out; the already-correct
  few-shot annotation left verbatim by design; DESIGN.md §"Embedding
  ABI (M1)" gained the bare-scalar export-param rule + a
  corpus-grounded `step` Form-A snippet; RED-first anti-regrowth pin
  via `ailang_core::FORM_A_SPEC` + `norm()` (4 ABSENT + 4 PRESENT
  form_a + 1 PRESENT DESIGN). Zero language/checker/codegen change;
  Boss-verified docs_honesty_pin 5/0, spec_drift 8/8 (not a lockstep
  partner), ailang-core 112/0, workspace 622→623 (+1 = the pin), zero
  `crates/**/src/**` diff. Pure docs+pin tidy — no audit/fieldtest
  gate (the pin IS the regression coverage). plan
  `docs/plans/form-a-scalar-param-mode-carveout.md` (`4c266a6`) → iter
  `7d7f04e`. Stays here briefly for context; remove once stale (full
  record in `docs/journals/INDEX.md`).
  - context: `docs/specs/2026-05-18-fieldtest-embedding-abi-m1.md`
    findings [friction] + [spec_gap]#1;
    `docs/journals/2026-05-18-iter-form-a-scalar-param-mode-carveout.md`.

- [x] **\[feature\]** `ail emit-ir --emit=staticlib` — restore the
  Decision-5 IR-readability affordance for kernels (M1 fieldtest
  spec_gap#2) — CLOSED 2026-05-18. `ail emit-ir` had no
  `--emit=staticlib` (only `ail build` did) and on a `main`-free
  kernel hit the executable-path `MissingEntryMain` rejection — an
  author could not read the generated `@<sym>` forwarder for an
  exported kernel. Resolution **added** (DESIGN.md *widened*, never
  narrowed): a one-line symmetric codegen convenience
  `lower_workspace_staticlib(ws)` routing through the M1-audited
  unchanged `Target::StaticLib` path, an `emit` clap field on
  `Cmd::EmitIr` symmetric with `Cmd::Build`'s, a zero-export guard
  byte-identical to `build_staticlib`'s; DESIGN.md §"Embedding ABI
  (M1)" affordance sentence + a CLI-synopsis correction also
  discharging a pre-existing M1 `ail build --emit=staticlib`
  omission. No new doc pin (E2E pins behaviour; architect's
  milestone-close DESIGN.md read catches stale prose). Boss-verified:
  3 new E2E 3/0 (incl. functional CLI spot-check — `@backtest_step`
  external forwarder readable, no `@main`), `embed_staticlib_cli`
  2/0 (build-side untouched), `docs_honesty_pin` 5/0, `ail` 186/0,
  workspace 623→626 (+3), zero out-of-scope diff, no fixture minted.
  Scoped feature — no audit/fieldtest gate (E2E is the coverage;
  reuses the M1-audited codegen path, no invariant/surface change).
  plan `docs/plans/emit-ir-staticlib.md` (`03493c9`) → iter
  `bcfe554`. Stays here briefly for context; remove once stale (full
  record in `docs/journals/INDEX.md`).
  - context: `docs/specs/2026-05-18-fieldtest-embedding-abi-m1.md`
    finding [spec_gap]#2;
    `docs/journals/2026-05-18-iter-emit-ir-staticlib.md`.

- [x] **\[milestone\]** Embedding ABI — M2: per-thread runtime
  context + concurrency safety — CLOSED 2026-05-18. A compiled
  scalar AILang kernel is now callable concurrently from a host
  thread swarm without an RC-runtime data race. `ailang_ctx_t*`
  is a mandatory leading parameter of the (M3-frozen-shape) C
  `@<sym>(ctx, scalars…)` ABI; the M1 forwarder publishes it via
  a `__thread` slot around the **byte-unchanged** internal
  `@ail_<mod>_<fn>` call (internal convention + `_adapter`/`_clos`
  untouched — M1 decision held; TLS sound because the kernel call
  is synchronous, ctx never held across a suspension). `runtime/rc.c`
  de-globalised: the two RC-counter sites became `if (ctx)
  ctx->{alloc,free}_count++ else g_rc_*++`, the `g_rc_*` statics +
  `atexit` retained verbatim as the null-ctx single-threaded
  executable fallback (executable-path leak readback byte-preserved).
  ctx near-empty by discipline (`{alloc,free}_count` only — no
  arena/lock/atomic/IO; the build-ahead trap named out-of-scope).
  Staticlib is RC-only enforced (`--emit=staticlib` rejects
  `--alloc=gc|bump`; Boehm never linked into a swarm artefact;
  standing P2 Boehm-retirement untouched). No authoring-surface /
  schema / `ailang-check` change — the `.ail` is byte-identical to
  M1; the deliverable is a swarm-safe generated ABI + de-globalised
  runtime, **sanitiser-verified** (per-thread-ctx scalar swarm
  tsan-clean == oracle; direct rc-accounting per-ctx clean;
  shared-ctx negative control a genuine `ThreadSanitizer` race at
  the de-globalised counters, exit 66 — teeth non-vacuous). Process
  note: the first implement dispatch correctly BLOCKED on a genuine
  spec defect (a negative control on the non-allocating scalar
  swarm is structurally impossible — the spec's own honesty point);
  Boss adjudicated a spec-consistency repair (teeth relocated to the
  rc-accounting harness) rather than a brainstorm bounce. spec
  `1c58055` (grounding-check PASS 9/9) → plan `b3388c8`
  (Boss-corrected) → iter `c9a84b3` (PARTIAL 4/9 + Boss repair) +
  `fbeeade` (DONE 5–9); milestone-close `audit` `ad88dec` (architect
  one `[medium]`+`[low]` doc-honesty drift; bench `check.py` exit 1
  decisively causally exonerated — bencher H0-refuted, all 3 firings
  the two tracked-P2 noise families, rc.c hunk branchless+free, NO
  ratify) → tidy `a80d495` (the `## Embedding ABI` lockstep rename,
  drift resolved). No fieldtest (zero authoring-surface change).
  Stays here briefly for context; remove once stale (full record in
  `docs/journals/INDEX.md`).
  - context: `docs/specs/2026-05-18-embedding-abi-m2.md`;
    `docs/journals/2026-05-18-iter-embedding-abi-m2.1.md`;
    `docs/journals/2026-05-18-audit-embedding-abi-m2.md`;
    `docs/journals/2026-05-18-iter-embedding-abi-m2.tidy.md`.

- [x] **\[milestone\]** Embedding ABI — M3: frozen value layout +
  single ADT/record crossing + RC ownership contract — CLOSED
  2026-05-18. A single-constructor record of `Int`/`Float` fields
  now crosses the embedding C ABI in **and** out; ownership follows
  the declared `own`/`borrow` mode (mechanically inherited — the M2
  forwarder body is byte-unchanged, all mode-driven RC stays in the
  byte-unchanged internal `@ail_<mod>_<fn>`); the record memory
  layout (header@p-8 / i64 tag@0 / fields i64-strided@8+i·8 /
  size=8+n·8) is **frozen** as a one-way commitment — a new
  DESIGN.md §"Embedding ABI" `### Frozen value layout` SSOT +
  `// FROZEN ABI` lockstep pointers at the three encoders + an
  enforceable `@ailang_rc_alloc` heap-box byte-pin (RED-on-offset-
  perturbation proven); host construction via `ailang_rc_alloc`,
  host-free via `ailang_rc_dec` (leak-free *because* M3 fields are
  scalar — no boxed children; non-scalar recursive-free named M4-
  additive). No authoring-surface / schema / Form-A / `CheckError`
  change (M2-style — the `.ail` is the minimal evolution of the
  M1/M2 scalar kernel into a record `State`). The `(State,Float)->
  State` fold round-trip is proven **globally leak-free** for both
  `own` and `borrow`. Process: spec `1fbb9c4` (grounding-check PASS
  8/8) → plan `15ee3c5` → iter `d5c565d` (PARTIAL 5/7 + a Boss
  spec-consistency repair: the orchestrator correctly BLOCKED on a
  genuine spec defect — the single-ctx-readback proof model is
  unsatisfiable for `borrow` by M2's TLS-ctx design; Boss
  M2.1-precedent adjudication → a stronger global-leak-freedom proof
  model) + `4ea8bc5` (DONE 6-7: the freeze). Milestone-close `audit`
  `b8a60b1` (architect: Invariant 1 clean, M1/M2 byte-invariant
  held, frozen-SSOT consistent — DRIFT only `[medium]`+`[low]`
  doc-honesty → tidy; bench `check.py` exit 1 **decisively causally
  exonerated** — byte-identical generated IR HEAD vs pre-M3
  `9a609ae` for every firing bench, the two tracked-P2 noise
  families, NO ratify) → tidy `63d7d60` (pin-safe; drift resolved).
  No fieldtest (zero authoring-surface change). Stays here briefly
  for context; remove once stale (full record in
  `docs/journals/INDEX.md`).
  - depends on: Embedding ABI — M2.
  - context: `docs/specs/2026-05-18-embedding-abi-m3.md`;
    `docs/journals/2026-05-18-iter-embedding-abi-m3.1.md`;
    `docs/journals/2026-05-18-audit-embedding-abi-m3.md`;
    `docs/journals/2026-05-18-iter-embedding-abi-m3.tidy.md`.

- [~] **\[milestone\]** Embedding ABI — M5: `ail-embed` adapter +
  `data-server` wiring + thread-swarm backtest. **In-flight**: spec
  written + user-approved + grounding-check PASS 2026-05-19. The
  host-side adapter — a lean reusable embedding module (`extern "C"`
  to the M3-frozen ABI + frozen-layout box helpers, zero finance
  knowledge) **plus** the real E2E on top; in-repo but
  `[workspace]`-excluded; sole `data-server`↔AILang meeting point
  (Invariant 1) — wires the **real external** `data-server`'s
  `while let next_chunk` loop to the shipped M3 kernel by
  **unrolling each chunk host-side** into per-tick
  `(State, Tick) -> State` calls (the adapter, not the kernel, owns
  chunk iteration; M4's cons-list crossing was retired). **Zero
  language/compiler/runtime change.** A real backtest runs over real
  Pepperstone tick data on a thread swarm: symbol-fan (headline) +
  time-shard (first actual proof of the chunk-boundary invisibility
  the M4 retirement rests on). Terminal embedding milestone:
  existence proof of the whole goal + friction harvest feeding the
  host-per-tick-FFI vs. batch-crossing perf decision (the P2
  flat-array item). Coherent stop: the goal itself.
  - depends on: Embedding ABI — M3 (shipped); Tick-coverage on M3
    (shipped `170464f`).
  - context: `docs/specs/2026-05-19-embedding-abi-m5.md`;
    M4 retirement
    `docs/journals/2026-05-18-brainstorm-embedding-abi-m4-retired.md`.

- [x] **\[milestone\]** Heap-`Str` ABI — runtime infrastructure for
  malloc-backed, refcounted `Str` values alongside the existing
  static `@.str_*` globals. Today the `Str` path is static-only
  (DESIGN.md §"Float semantics" notes this explicitly), so any
  primitive that needs to produce a `Str` at runtime — `int_to_str`,
  `float_to_str` (currently type-installed but `CodegenError::Internal`
  on call), eventual `Show.show`, future `++` on strings — cannot
  ship. Scope: pick the representation (likely a `{rc_header, len,
  bytes…}` slab consistent with the rest of the RC runtime), teach
  codegen to accept both static and heap `Str` at the same ABI slot,
  wire RC `inc`/`drop`/`clone`/`compare`/`eq` on the heap form, and
  ship `int_to_str` + `float_to_str` as the first two callers so the
  ABI gets exercised end-to-end. Unblocks Show + print rewire below.
  - context: DESIGN.md §"Float semantics" (`float_to_str` is type-
    installed, codegen-deferred); spec 2026-05-11-23-eq-ord-prelude
    §"Show. Defers behind a heap-Str-ABI milestone"; spec
    2026-05-10-fieldtest-floats §F4 ("dynamic Str allocation in the
    runtime"); spec 2026-05-09-22-typeclasses §22b.4b ("Show#Int
    needs an `int_to_str` primitive returning heap-allocated Str").

- [x] **\[milestone\]** Post-22 Prelude — Show + print rewire — shipped
  2026-05-13 as iters 24.1 (heap-Str runtime + codegen for `bool_to_str`
  + `str_clone`, f38bad8), 24.2 (prelude `class Show` + four primitive
  instances Int/Bool/Str/Float + 22b TShow/tshow migration), and 24.3
  (polymorphic `fn print : forall a. Show a => (a borrow) -> () !IO`
  + positive 4-prim E2E + user-ADT E2E + Show-aware NoInstance
  diagnostic + DESIGN.md amendments to §"Prelude (built-in) classes"
  and §"Float semantics"). The `MethodNameCollision` workaround that
  blocked the original spec retired in mq.3 (2026-05-13); spec
  `docs/specs/2026-05-13-24-show-print.md` re-derived 24.2 + 24.3
  against the post-mq architecture.
  - context: spec `docs/specs/2026-05-13-24-show-print.md`; per-iter
    journals 2026-05-12-iter-24.1, 2026-05-13-iter-24.2, 2026-05-13-iter-24.3.

## P2 — Medium-term

- [ ] **\[milestone\]** DESIGN.md → `design/` — role-split into a
  typed contract ledger + per-model whitepapers, decision-records
  rehomed to `docs/journals/`. `docs/DESIGN.md` is one 2905-line
  file doing three different jobs for three different consumers
  with three different lifetimes; the line count is the symptom of
  role-conflation, not of mess. Replace it with a `design/`
  directory split along the consumer/lifetime axis, with a typed
  `design/INDEX.md` as the sole addressable spine and primary
  entry point.

  **Motivation.** The just-closed `docs-honesty-lint` milestone is
  the proof this is not a cleanup problem: it ran a full docs pass
  and DESIGN.md stayed 2905 lines, because honesty-lint operates
  *within* a job (tense / Wunschdenken / citation) and its own
  audit-trail invariant *mandates retaining* the bulky
  decision-record prose (DESIGN.md:242 "records the *why* … for
  the audit trail"; the architect Sweep-5 discriminator explicitly
  keeps "present-tense design rationale"). You cannot clean your
  way out of conflation — the conflation itself is the structural
  defect.

  The three conflated jobs and their distinct consumers:
  - **Contract layer** — prescriptive, test-linked invariants
    (e.g. Decision 6's seven hard constraints, the Roundtrip
    Invariant, the borrow/own binding rules). Consumer:
    `architect` (mandatory full read at *every* milestone close),
    `grounding-check` (every spec), the bench drift lints. Wants:
    structured, addressable, test-linked, small, hot. This is the
    only job that belongs on the per-milestone hot path.
  - **Model exposition** — whitepaper-shaped model narratives
    (RC + Uniqueness, typeclasses, effects). Consumer: onboarding
    (fresh-context LLM, `fieldtester`). Irreducible — the code
    cannot speak the gestalt; one whitepaper per model.
  - **Decision-record / relitigating-guard** — why X was chosen,
    why Y/Z were rejected (region inference, tracing GC, …).
    Consumer: a future `brainstorm`, so it does not re-propose a
    settled-and-rejected idea. This already has a home —
    `docs/journals/` — and sits in DESIGN.md only because the
    audit-trail invariant predates per-iter addressable journals.

  Underlying principle: the code is authoritative for *what it
  does* — delete every description of behaviour, the code speaks;
  the spec's irreducible job is *what is promised* plus a pointer
  to the green test that ratifies it (code answers *what*; spec
  answers *what is promised*; test answers *does the promise still
  hold*).

  **Decided shape.** `design/` split on the consumer/lifetime
  axis — **not** by Decision-number (that is cosmetic; the
  architect would still read everything). `design/contracts/` (the
  hot, test-linked invariant set), `design/models/` (one
  whitepaper per model). `design/INDEX.md` is the typed ledger
  spine: per entry a one-liner + metadata
  `{kind, ratifying-test, consumer/lifetime}` + a content link
  whose target is polymorphic — a `design/contracts/…` file, a
  single-owner `//!` source header (mangling, env, roundtrip,
  float, tail-calls, data-model), or a `design/models/…`
  whitepaper. INDEX.md is the sole addressable entry point.
  Decision-records move out to `docs/journals/`; the honesty-lint
  audit-trail invariant is repointed there in the same milestone.

  **Rejected — do not relitigate.** Quarto / `.qmd` was evaluated
  and rejected: every distinctive Quarto feature (rendered
  HTML/site, figures, citations, cross-ref resolution, executable
  chunks) is render-time value for a *human reader who does not
  exist here* — the `design/` consumer is an LLM reading raw
  files. Executable chunks would reintroduce a second "is the
  invariant green?" truth that can diverge from `cargo test` (the
  duplicate-source-of-truth failure mode); Quarto `@ref`
  cross-refs degrade the raw-file reader (unresolved tokens vs.
  resolving relative-path links); plus a non-Rust toolchain
  dependency for zero in-loop value. Plain Markdown + a typed
  INDEX.md is strictly better for this consumer. A plain shard by
  Decision-number is likewise rejected (changes nothing for the
  architect hot path).

  **Sequencing / blast radius.** Deliberately scheduled *after*
  the in-flight Embedding ABI arc — a substantive sequencing
  reason, not effort. `DESIGN.md` is referenced by name by at
  least `bench/check.py` (history-anchor / `design_schema_drift`),
  `bench/architect_sweeps.sh` (Sweep 5; exit 2 = "could not find
  DESIGN.md"), the reading lists of `ailang-architect` /
  `ailang-grounding-check` / `ailang-fieldtester`, the CLAUDE.md
  "Roles of docs/…" section, and the honesty-lint invariant's own
  scope — load-bearing invariants and agent contracts, not just
  paths. Every Embedding-ABI milestone close runs `audit`
  (architect + bench) against DESIGN.md; restructuring it mid-arc
  would break the architect's reading list and the drift scripts
  *under* an in-flight milestone. Hence no overlap with active
  implementation that closes audits against the current
  DESIGN.md. Brainstorm hard-gate before any plan/code — it
  rewrites enforced invariants and agent contracts.
  - context: 2026-05-18 chat (DESIGN.md-management dialogue —
    six-turn convergence: role-conflation diagnosis, three-job
    split axis, "let the code speak" principle, qmd rejection,
    blast-radius sequencing). No journal entry yet; pre-brainstorm.

- [ ] **\[milestone\]** Flat array/slice primitive — performance
  follow-up to the Embedding ABI arc, *not* a capability gap.
  AILang's only sequence is the recursive `List` ADT (one RC cell
  per element); marshalling a 1024-record chunk across the
  embedding boundary builds a 1024-cell cons-chain per chunk on a
  multi-GB hot path (Embedding ABI M4 ships the capability this
  way). A contiguous array/slice primitive would remove that, but
  it is a *language* change (interacts with uniqueness inference,
  RC, and the structurally-decreasing-recursion totality story)
  and is explicitly out of the ABI arc's scope. Sequenced after
  M5 so the decision is driven by M5's fieldtest measurements,
  not speculation.
  - depends on: Embedding ABI — M5 (for the measured
    justification).
  - context: 2026-05-18 chat (user deferred this explicitly to
    keep the ABI arc clean).

- [ ] **\[milestone\]** Iteration-totality story — structural +
  Int-bounded total recursion with *enforced* non-negativity.
  AILang's iteration story stays as-is (structural / tail recursion;
  `tail-app` intact). The genuine ambition — make `f(n-1)`-family
  recursion (incl. branching tree builders) total *by construction*
  with the non-negative-entry precondition **enforced**, not merely
  documented — is deferred here because doing it without a purity-
  pillar concession requires refinement/`Nat` type machinery the
  language has not built (Decision 4 keeps refinements opaque, no
  SMT). Not abandoned; correctly sequenced after the type machinery.
  - depends on: a future `Nat`/refinement-types milestone (no spec
    yet).
  - context: `docs/specs/2026-05-16-iteration-discipline-revert.md`
    (why the 2026-05 attempt was reverted) and
    `docs/journals/2026-05-15-iter-it.3.md` (the branching-builder
    counter-example that surfaced the gap).

- [x] **\[milestone\]** Retire `io/print_int` / `io/print_bool` /
  `io/print_float` effect-ops + migrate example corpus to `print`.
  Shipped 2026-05-14 as iter rpe.1.
  - context: per-iter journal `docs/journals/2026-05-14-iter-rpe.1.md`.

- [x] **\[todo\]** Author `examples/prelude.ail` alongside
  `examples/prelude.ail.json`. (Satisfied 2026-05-13 by iter
  form-a.0 — `examples/prelude.ail` rendered via `ail render`,
  116 lines / 6386 bytes, round-trip-CI green.)

- [x] **\[milestone\]** Form-A as the default authoring surface for
  examples and docs. (Closed 2026-05-13 by iter form-a.1.) Render every `examples/*.ail.json` to its
  `.ail` sibling via `ail render`, **delete the now-redundant
  `.ail.json`**, and regenerate the JSON-AST per `ail parse` at
  build / test time. Same for inline JSON-AST blocks in `docs/`
  markdown (~7 in DESIGN.md plus ~29 other md files) — convert to
  Form-A snippets where the snippet's purpose is to show "what the
  language looks like", not "what the schema is".

  After this milestone the working tree contains exactly one
  representation per program: the `.ail` source. The JSON-AST is a
  build artefact, not a checked-in twin. Tests and benches that
  currently glob `*.ail.json` either parse-then-consume or are
  rewired to point at `.ail` directly. The round-trip invariant
  flips role: today it gates that the two forms agree; afterwards
  it gates that `parse` is deterministic.

  Carve-outs that MUST stay JSON-AST (no Form-A counterpart, and
  no `.ail` sibling shall be created — these are the only seven
  files that remain `.ail.json`-only post-milestone):
  - Fixtures that test canonical-form rejection — Form A would
    reject them at parse, defeating the test:
    `test_ct1_bare_xmod_rejected.ail.json`,
    `test_ct1_qualified_class_rejected.ail.json`,
    `test_ct1_bad_qualifier.ail.json`, `broken_unbound.ail.json`,
    `test_22b2_invalid_superclass_param.ail.json`,
    `test_22b2_kind_mismatch.ail.json`,
    `test_22b2_unbound_constraint_var.ail.json`.
  - DESIGN.md schema documentation blocks where the JSON-AST shape
    *is* the point (Decision 11, ParamMode, canonical-form
    invariants).
  - Any other case where the structured form is the artefact under
    discussion, not a vehicle for a program.

  Touches CLAUDE.md ("source of truth is structured data") — the
  language doctrine doesn't change (JSON-AST is still the canonical
  hashable form), but the *authoring* doctrine does: authors write
  `.ail`, the build derives JSON-AST. Sentence in CLAUDE.md needs
  rewording in the same milestone.

  Mechanical for the bulk; per-fixture judgement call only for the
  carve-out list. Run as one milestone so the corpus flip-over
  happens at a single, audit-gated point.
  - context: post-Form-A-as-canonical-authoring corollary of the
    `examples/prelude.ail` entry above. Generalises the same idea
    to the rest of the corpus and follows through on the cross-
    model authoring data (textual form cheaper, more first-try
    hits) by treating Form A as the privileged surface in the
    working tree, not just in flavour text.

- [ ] **\[feature\]** Operator routing through `Eq` / `Ord` — `==`,
  `<` etc. resolved via the typeclass instead of the built-in
  primitive comparators. No commitment; gated on bench re-baselining
  to make sure the indirection doesn't tank latency.
  - context: JOURNAL 2026-05-09
  - depends on: Post-22 Prelude — Eq/Ord (shipped 23.5)
- [x] **\[todo\]** `types` / `ctor_index` overlay shape question —
  decide whether the env's two parallel ctor maps should collapse
  into one overlay, or stay split. Surfaced during the
  env-construction unify audit.
  - context: JOURNAL 2026-05-10 ("Audit close: env-construction unify"); closed by iter ctt.1 — DESIGN.md §"Env construction" anchors the split decision.
- [x] **\[todo\]** CLI human-mode diagnostic surface for `WorkspaceLoadError`.
  Shipped 2026-05-14 as iter cli-diag-human — a new `load_workspace_human`
  helper in `crates/ail/src/main.rs` routes 9 non-JSON
  `ailang_surface::load_workspace(&path)?` call sites through
  `workspace_error_to_diagnostic`, so the bracketed `[code]` prefix is
  preserved across `ail check`, `build`, `run`, `emit-ir`, `prose`,
  `describe`, `deps`, `diff`, `manifest`.
  - context: per-iter journal `docs/journals/2026-05-14-iter-cli-diag-human.md`.
- [x] **\[todo\]** Retire dead `KindMismatch` arm — `validate_classdefs`'s
  `walk_kind_mismatch` path is structurally unreachable through
  well-formed schema post-ct.1 (the canonical-form validator catches
  the malformed `Type::Con { name: param }` shape earlier). The
  enum variant + `walk_kind_mismatch` helper stay as dead-but-defensive
  code; a future tidy can delete both.
  - context: JOURNAL 2026-05-11 ("Iteration ct.1"); closed by iter ctt.3 — variant + helper + dispatch + Display arm all deleted; canonical-form-rejection test stays green asserting `BareCrossModuleTypeRef`.
- [x] **\[todo\]** Re-key `Registry.type_def_module` to handle
  bare-name-collision-across-modules — `BTreeMap<String, String>` keyed
  by bare type name silently overwrites when two modules each define
  `type Foo`; `normalize_type_for_registry` would then collapse `M.Foo`
  and `N.Foo` to whichever insert won. Acceptable for current corpus
  (distinct bare type names across modules), but the proper fix is to
  key by `(owning_module, bare_name) → defining_module`.
  - context: JOURNAL 2026-05-11 ("Iteration ct.1") — flagged by ct.1.5a quality reviewer.
- [ ] **\[feature\]** 22c — typeclass corpus expansion. User-defined
  classes beyond the prelude four; multi-parameter classes; superclass
  chains; richer instance bodies. Deferred from milestone 22.
  - context: JOURNAL 2026-05-09
- [x] **\[milestone\]** Module-qualified class names + type-driven
  method dispatch — retired the `MethodNameCollision` workaround;
  shipped 2026-05-13 as iters mq.1 (canonical-form extension for
  class-ref fields + workspace-internal qualification), mq.2 (type-
  driven dispatch mechanism installed: `method_to_candidate_classes`
  inverse index, multi-candidate `ResidualConstraint`,
  `resolve_method_dispatch` 5-step rule, `AmbiguousMethodResolution` +
  `UnknownClass` diagnostics), and mq.3 (retirement + multi-class E2E
  + `class-method-shadowed-by-fn` warning + DESIGN.md sync). Two
  libraries can now each declare `class Eq` with their own `eq`;
  cross-class method ambiguity is resolved at the call site via
  type-driven dispatch with `<module>.<Class>.<method>` as the
  disambiguation form.
  - context: `docs/specs/2026-05-10-canonical-type-names.md` "Out of
    scope: Class names" — the workaround was named there so it
    stayed visible until this milestone retired it.
- [ ] **\[todo\]** Boehm full retirement — remove the transitional
  Boehm GC path now that RC + uniqueness is the canonical memory
  story.
  - context: JOURNAL pre-22, "Boehm transitional"
- [ ] **\[feature\]** Closure-pair slab / pool — codegen tweak to
  pool the env+code closure pairs instead of one-shot heap allocs.
  Bench-gated.
- [ ] **\[todo\]** `FnDef::synthetic` flag — formalise the
  monomorphiser-emitted FnDefs so downstream passes can tell
  user-authored from synthesised at a glance. Currently inferred from
  symbol naming.
- [ ] **\[todo\]** 21'h iteration — final 21' carry-over (latency
  methodology pass). Numbering kept for continuity with the 21' arc.

- [x] **\[todo\]** DESIGN.md effect-prose is fiction — standalone
  documentation-honesty tidy. Shipped 2026-05-16 as iter
  effect-doc-honesty (a29700c): the three false effect-system claims
  (row-polymorphic `![IO | r]`; `IO`+`Diverge` both wired up;
  `Term::Do` "effect-handler table at link time") reconciled to the
  real flat-closed-set / IO-only / `IndexMap`+codegen-`match`
  mechanism, satellite lockstep done, guarded by a 4-test
  doc-presence pin. No language/checker/codegen change.
  - context: per-iter journal
    `docs/journals/2026-05-16-iter-effect-doc-honesty.md`.
- [ ] **\[todo\]** `io/print_float` always-emit-`.0` — surface
  printer always emits `.` or `e/E` so re-lex routes to Float;
  the runtime printer (`printf("%g\n", v)`) doesn't, so `2.0`
  prints as `2` (Int-shaped). Asymmetric. Either switch the
  runtime path to a `.0`-fallback printer (matching surface) or
  document the `%g` contract in DESIGN.md §"Float semantics" so
  the LLM-author knows `io/print_float`'s output is for-humans
  not round-trip.
  - context: `docs/specs/2026-05-10-fieldtest-floats.md` finding F1.
- [ ] **\[todo\]** Rustdoc warning sweep — `cargo doc --no-deps`
  reports 16 pre-existing warnings (15 in `ailang-check`, 1 in
  `ailang-core`: private-item links from public doc, unresolved
  intra-crate links). All predate the design-md-consolidation
  milestone; treat as a one-off sweep.
  - context: JOURNAL 2026-05-10 ("Audit close: design-md-consolidation").
- [ ] **\[todo\]** `ailang-plan-recon` site-undercount countermeasure
  (P2, **escalated** — now a structural recon-contract defect, no
  longer a single-milestone curiosity) — the recon agent hand-lists
  the change blast radius and has under-counted it **four times across
  two milestones, through a different site type each time**:
  loop-recur.1 walker arms, loop-recur.2 cross-module `synth` callers,
  loop-recur.tidy implicit, and now remove-mut-var-assign.1 (in-source
  `mod tests` fns + a drift-pin fn + 5 orphaned `.ail.json` carve-outs
  + a non-enumerated `codegen_import_map_fallback_pin.rs` E0599) **plus
  a spec-named doc the recon was told did not exist**
  (`crates/ailang-core/specs/form_a.md` — the spec listed it for
  deletion; a Boss plan-time grep checked only `docs/form_a.md`,
  concluded "no such file", and propagated that into the recon brief;
  caught only at milestone-close audit). Two distinct gaps: (a) the
  compile-driven sweep catches exhaustive-`match`/caller misses but
  **not** non-compile-checked sites (docs, fixtures, drift pins) —
  those need a separate "every spec-named path is verified to exist
  and is handled" cross-check; (b) a recon brief must never inherit an
  unverified "X does not exist" — existence claims feeding a recon are
  themselves load-bearing and must be tree-wide, not dir-scoped.
  Tighten `skills/planner/agents/ailang-plan-recon.md`: for any
  signature-change / enum-variant / removal scope the recon REPORTS
  the compile-driven enumeration as the authoritative code-site set
  (hand-list advisory) AND emits a spec-named-path existence table
  (every path the spec names, `ls`-verified, with its handling task).
  No language change; an agent-definition discipline fix.
  - context: loop/recur close audit (architect `[low]`, 2026-05-18) +
    remove-mut-var-assign close audit (architect `[high]` form_a.md +
    `[medium]` process-drift, 2026-05-18); pairs with planner Step-5
    items 7+8 (those scrub the *plan*; this scrubs the *recon* and the
    *recon brief*).
- [ ] **\[todo\]** `design_schema_drift.rs` fidelity widening —
  current test checks anchor *presence* anywhere in DESIGN.md;
  the audit found that `[high]` schema gaps in §"Data model"
  are invisible because anchors live in Decision 11 instead.
  Constrain the test to scan only §"Data model" + ParamMode
  block, or extract JSON-schema blocks into a machine-readable
  file the test consumes.
  - context: JOURNAL 2026-05-10 ("Audit close").
- [ ] **\[todo\]** Split `BadCrossModuleTypeRef` into two diagnostics —
  the current single shape collapses unknown-owner and
  known-owner / unknown-type-in-owner into one message. The two
  cases suggest different fixes (add `(import <owner>)` vs. fix the
  type name). In the known-owner branch, list the owner's available
  type defs as candidates the way `bare-cross-module-type-ref`
  lists candidates from imports.
  - context: fieldtest 2026-05-11 — `examples/ct_3*.ail` exhibits both branches with identical-shape diagnostics.
- [ ] **\[todo\]** Zero-arg `(app f)` rejected at parse — the Form-A
  parser refuses an application with an empty argument list, so a
  nullary call has no surface form. Surfaced by the mut-local
  fieldtest (F3) AND **independently re-confirmed by the loop/recur
  fieldtest** (2026-05-18, finding spec_gap): the maximally-natural
  infinite-event-loop shape is a niladic `run_forever : fn() -> Int`
  called `(app run_forever)`, which dies at parse before reaching
  loop/recur semantics. Two independent fieldtests hitting the same
  gap raises the priority signal. Decide: accept `(app f)` as the
  nullary-call surface, or ratify in DESIGN.md that nullary functions
  are expressed differently (and say how). DESIGN.md's `Term::App`
  `args:[Term...]` states no minimum, so the surface "expected at
  least one argument" rule is unbacked by spec — a genuine design
  fork, deliberately NOT auto-ratified under autonomous orchestration.
  Not a blocker; no current corpus program needs it, but an LLM
  author reaches for it.
  - context: `docs/specs/2026-05-15-fieldtest-mut-local.md` finding F3;
    `docs/specs/2026-05-18-fieldtest-loop-recur.md` spec_gap.
- [ ] **\[todo\]** Module-level `(doc …)` diagnostic omits where `doc`
  belongs — a `(module NAME (doc "…") (fn …))` is correctly rejected
  (`unknown def head \`doc\``) but the message lists valid def heads
  without saying doc strings attach *inside* `fn`/`data` defs; an
  author can read it and conclude doc strings are unsupported. One-line
  tidy: append a hint like "(doc strings attach inside `fn`/`data`
  defs, not at module level)". Low cost; verbose-diagnostic-philosophy
  gap, not a bug.
  - context: `docs/specs/2026-05-18-fieldtest-loop-recur.md` friction.
- [ ] **\[todo\]** Workspace search beyond entry-module's directory —
  `load_workspace` only finds sibling `.ail.json` files in the same
  directory as the entry module, so any consumer of prelude/std in a
  subdirectory has no way to resolve cross-module imports. Add either
  a `--workspace-root` flag on `ail check` / `ail build` / `ail run`,
  or upward-search from the entry module's directory. Alternatively
  ratify the flat-workspace assumption in DESIGN.md if intentional.
  - context: fieldtest 2026-05-11 — fieldtest fixtures could not be
    placed under `examples/fieldtest/` because of this; predates the
    canonical-type-names milestone but surfaces every time.
- [ ] **\[todo\]** `*.bump_s` throughput baseline is stale vs current
  hardware — `bench/check.py`'s `throughput.*.bump_s` family (the
  fastest, most jitter-prone metrics) reads ~+5–13% over
  `bench/baseline.json` on the current machine. Localised at the
  iteration-discipline-revert audit (2026-05-16): an interleaved
  3×60-run measurement of `bench_list_sum` bump_s built from the
  byte-oracle commit `1ff7e81` shows the *same* ~+11% elevation as
  HEAD, and the two bump binaries are `cmp`-identical — so this is
  environmental drift relative to the 2026-05-09 baseline-capture
  machine state, **not** a codegen regression. Re-capture the full
  `*.bump_s` set on current hardware from a known-clean commit and
  recalibrate the `bump_s` baseline+tolerance pair (or widen the
  tolerance) so the noise floor stops tripping `check.py` exit 1.
  Pure bench-harness recalibration; no language change.
- [ ] **\[todo\]** `check.py` RC-latency `*.max_us` is a structural
  false-positive at `-n 5` — **distinct** from the `*.bump_s`
  staleness above (different metric family, different root cause).
  `latency.{explicit,implicit}_at_rc.max_us` is the single worst of
  ~5000 samples over only 5 runs, dominated by OS scheduling / THP /
  IRQ jitter on a shared host, not allocator behaviour. It has fired
  a false `REGRESSION` on **three consecutive no-runtime-change
  milestones** (iteration-discipline-revert, prose-loop-binders,
  remove-mut-var-assign) and vanished on identical-code re-run every
  time (remove-mut-var-assign close: HEAD vs `48e7774` bench binaries
  `cmp`-byte-identical, `explicit_at_rc.max_us` collapsed
  +108%→-2.30% ok by rerun3) — a ~3-for-3 false-positive rate on null
  changes, while the median/p99/p99.9 of the same benchmarks held
  across all re-runs. Pick one mitigation: drop `*.max_us` from the
  gating set (keep median/p99/p99.9, which are the trustworthy
  signal), or raise `-n` substantially for the tail metrics, or pin
  the latency arm to an isolated cpuset. Pure bench-harness change;
  no language change.
  - context: remove-mut-var-assign close audit, bencher localisation
    (2026-05-18) — byte-identical-binary causal exoneration + the
    3-milestone false-positive history.
  - context: `docs/journals/2026-05-16-audit-iteration-discipline-revert.md`
    (the bencher localisation evidence).
- [x] **\[todo\]** `check_in_workspace` per-module overlay narrowing —
  `crates/ailang-check/src/lib.rs:1234` still clears+rebuilds
  `env.ctor_index` per-module; ct.3.2 narrowed the analogous mono
  overlay to types-only. The typecheck-side overlay's `env.ctor_index`
  half serves the duplicate-detection diagnostic at workspace-build
  time, not the runtime ctor lookup (which is type-driven post-ct.2.2).
  Narrowing is mechanical but needs a careful read to confirm no
  other consumer survives.
  - context: JOURNAL 2026-05-11 ("Iteration ct.4") — milestone close
    follow-up; closed by iter ctt.1 — recon confirmed the rebuild is the load-bearing consumer of in-band DuplicateCtor (pinned by `crates/ailang-check/tests/duplicate_ctor_pin.rs`); the asymmetry with the mono side is intentional.
- [x] **\[feature\]** `str_concat : (borrow Str, borrow Str) -> Str` —
  heap-Str concatenation primitive. Shipped 2026-05-13 as iter
  str-concat (closes fieldtest-form-a friction #4). Symmetric to the
  iter 24.1 `str_clone` / `int_to_str` / `bool_to_str` plumbing:
  runtime C helper (`ailang_str_concat`), `ailang-check` builtin
  registration, `ailang-codegen` extern + `lower_app` arm, plus a
  fresh `examples/show_user_adt_with_label.ail` corpus fixture
  exercising the LLM-natural Show-body shape.
  - context: per-iter journal 2026-05-13-iter-str-concat.md.

- [~] **\[milestone\]** Stateful islands — bounded mutation for
  streaming workloads (`Stateful a b` + `!Mut` effect + `mut`
  syntactic block). Adds a sealed mutable-state layer on top of
  the pure core: a `Stateful a b` first-class type whose interior
  is mutable, whose exterior is a typed callable with `!Mut` in
  its effect set, and whose state non-aliasing is enforced by
  uniqueness inference at the block boundary. Targets the
  online / streaming workload class — rolling indicators, IIR
  filters, online aggregates, sensor fusion, online learning —
  whose mathematics is "new sample + old state → new state +
  output" per step, and which today's pure-functional state-
  threading makes ergonomically expensive at scale (multi-record
  explicit threading for the canonical sliding-window SMA, no
  zero-allocation pipe combinator, growing tuple-state types as
  pipelines lengthen).

  **Sub-milestone sequencing is UNDER RE-THINK with the user
  (2026-05-16) — no further sub-milestone is planned or brainstormed
  until that conversation happens.** Why: the 2026-05-15 decomposition
  named "(2) effect-handler infrastructure as a prerequisite for any
  non-IO/non-Diverge effect" as the next step. A read-only recon of
  the live effect subsystem (2026-05-16) showed that premise is false:
  the effect raise / declared-set / `UndeclaredEffect`-subset / call-
  propagation machinery is already fully generic over an arbitrary
  effect string; `!Mut` needs **no** effect-handler machinery. The
  only real code prerequisite the recon found is one hard-coded
  assumption (`linearity.rs` walks every `Term::Do` arg as `Borrow`;
  `EffectOpSig` has no per-arg mode field) — a single struct field,
  not a milestone. A brainstorm spec that tried to make sub-ms-2 a
  standalone deliverable bundled that speculative one-field capability
  with an unrelated DESIGN.md honesty fix; the user correctly rejected
  the bundle as incoherent and the build-ahead-of-consumer half as the
  iteration-discipline trap repeated. **Resolution:** (a) the DESIGN.md
  effect-honesty correction is split out and runs now as a standalone
  documentation tidy (see the P2 `[todo]` "DESIGN.md effect-prose is
  fiction" below); (b) the `arg_modes` finding is recorded as recon
  context for the future `!Mut` design, NOT built ahead — it is
  first-iteration material of whatever the `!Mut` milestone turns out
  to be, validated there against a real consuming op, never a
  synthetic test op; (c) the old "(2)…(5)" sequence (effect-handler
  infra → `!Mut`+`ref a` → `MutArray a` → `Stateful a b`+`pipe`) is
  no longer treated as settled — the whole ordering and granularity
  is what the pending user conversation re-decides.

  **Motivation.** AILang's signature-as-contract thesis is *better*
  served by an explicit `Stateful a b` + `!Mut` annotation than by
  the implicit-closure-mutation idiom of myc / Lua / JS factory
  patterns: the signature tells the truth about time-identity
  without the body needing to be read — exactly the LLM-author
  correctness affordance AILang exists to deliver, extended to a
  workload class it does not yet serve. Decision 10's constraint #3
  forbids *shared* mutable refs (DESIGN.md:1082); it does not
  forbid uniqueness-bounded mutation. Lean 4 (`ST`), Roc (`Task`),
  Haskell (`ST`/`IO`), and Koka (effects) all use a layered design
  of this shape to host exactly this workload. AILang's existing
  `own` / `borrow` mode machinery plus its effect-slot architecture
  are the foundation; this milestone supplies the layer that sits
  on top.

  **Scope (subject to brainstorm refinement).**
  - `Stateful a b` as a first-class type — a sealed callable with a
    hidden mutable env, externally a typed callable whose effect
    set includes `!Mut`.
  - `!Mut` effect, the first non-IO / non-Diverge effect; forces
    the effect-handler infrastructure forward.
  - `mut` block as the syntactic boundary in Form A — outside,
    AILang's pure self; inside, `var` / `assign` / mutable arrays
    legal. (Iteration is *not* part of this boundary: AILang has no
    `while`/`for` and this milestone does not introduce one —
    repetition stays recursion, exactly as the language has it today
    (structural / tail recursion); a `mut` block is a sealed
    expression, never a loop over mutable state.)
  - `var x = expr` + `assign x expr` AST nodes, legal only inside
    `mut`.
  - Mutable array primitive (`MutArray a`, O(1) index/update under
    uniqueness) — co-developed because the ring-buffer use case
    that motivates the whole effort has no carrier today.
  - `pipe : Stateful a b → Stateful b c → Stateful a c` as a
    built-in combinator with zero-allocation lowering — composition
    is fusion at codegen, not closure-pair layering at runtime.
  - Decision 12 (or amendment to Decision 10) that names
    uniqueness-bounded mutation as the legitimate exception to
    "no shared mutable refs", and the pure / mutable-island layering
    as the architectural shape.

  **Blockers (hard prerequisites + open design questions).**
  - *Effect handlers absent.* DESIGN.md:2663 — "No effect
    handlers — only the built-in IO and Diverge ops." `!Mut` is the
    first non-trivial effect and forces handler infrastructure to
    ship. May warrant lifting out as its own prerequisite milestone;
    brainstorm decides.
  - *Uniqueness inference through `var` captures.* Current
    inference operates over the immutable RC graph; mutable
    bindings captured by closures need a more powerful pass. Lean 4
    has a known algorithm; AILang does not implement it.
  - *No mutable-array primitive.* No `Array a`, no slab container
    of any kind in the language today. Separate spec needed inside
    this milestone (representation, `own`-only or `borrow`-able,
    bounds-checking discipline).
  - *`runST`-equivalent escape discharge.* Producing a `Stateful`
    that legitimately escapes its `mut` block while proving the
    inner state doesn't leak. Haskell's rank-2 trick
    (`runST :: (forall s. ST s a) -> a`) is unavailable —
    DESIGN.md:1930 confirms higher-rank polymorphism is not
    supported. Need an alternative — likely sealed-by-construction
    at the factory boundary, or an effect-mode tag that gates
    escape.
  - *Form A surface design.* Decision 1 forbids macros (source =
    data, not text), so `mut` / `var` / `assign` are genuine AST
    nodes with round-trip-invariant coverage. Surface needs LLM-
    utility validation (does the author reach for `var` / `mut`
    unprompted?) before implementation.
  - *Codegen for in-place struct-field writes.* `Term::ReuseAs`
    today lowers ADT in-place rewrite; no direct mutable struct-
    field-write path exists. `crates/ailang-codegen/src/escape.rs`
    plus the lowering passes need extension.
  - *Boundary escape analysis.* "Interior state doesn't leak" is an
    escape analysis specifically over `var` / `ref` values; the
    existing pass covers allocations, not mutable cells.
  - *Decision-10 status.* Whether to amend Decision 10 inline or
    add a Decision 12 that names the mutable-island layer alongside
    the pure layer. The latter is probably cleaner — Decision 10
    stays load-bearing for the pure layer, Decision 12 names the
    extension and its discipline.
  - *Streaming bench corpus.* Current corpus (list_sum, tree_walk,
    closure_chain, hof_pipeline) is all pure. Streaming-specific
    benches (SMA pipeline, IIR filter, online stats) plus an
    external baseline (myc, hand-C, Python/NumPy) need to exist to
    validate that the layered design hits the zero-alloc
    performance target that myc demonstrates.
  - *LLM-utility test.* DESIGN.md §"Feature-acceptance criterion"
    is the gate: the surface must produce code an LLM author
    naturally writes. Fieldtest after spec, before any code
    commits.

  - context: 2026-05-15 chat on the `~/sma_factory.myc` analysis —
    myc's stateful-closure-plus-pipe idiom delivers a streaming
    workload pattern (3-line SMA factory, 2-line pipeline, zero
    runtime allocation per tick) that AILang's pure-only model
    cannot match without this layered extension. Pending brainstorm
    will produce `docs/specs/<date>-stateful-islands.md` and decide
    the effect-handler-prerequisite question.

## P3 — Ideas

- [x] **\[todo\]** `compare_primitives_smoke.ail` counterpart.
  Satisfied 2026-05-13 by milestone form-a-default-authoring —
  `examples/compare_primitives_smoke.ail` is the canonical
  authoring form for that fixture. Form A is now the default
  authoring surface across the entire corpus.
  - context: closed incidentally by form-a iter form-a.1.
- [ ] **\[todo\]** Codegen `lookup_ctor_in_pattern` type-anchoring —
  `crates/ailang-codegen/src/lib.rs:1790` still walks every module's
  ctor_index by bare ctor name. Plumbing the scrutinee's
  qualified `Type::Con` through pattern lowering would type-anchor
  it symmetric to the ct.2.2 typecheck-side fix. Not load-bearing
  (uniqueness is enforced at typecheck), but cleaner.
  - context: JOURNAL 2026-05-11 ("Iteration ct.3" + ct.4 close).
- [ ] **\[todo\]** `compare_primitives_smoke` IR-shape assertion —
  observe `compare__Int` / `compare__Bool` / `compare__Str` symbols
  in the emitted IR. Blocked on `emit-ir` CLI not running mono;
  resolution paths include extending the CLI to run mono, using
  library APIs directly in e2e.rs, or adding `--dump-ir` to `ail
  build`. The E2E stdout assertion already covers correctness; the
  IR-shape test would catch refactor regressions that rename
  mono symbols.
  - context: JOURNAL 2026-05-11 ("Iteration ct.4") — dropped from
    ct.4.4 when the BLOCKED condition surfaced.

- [ ] **\[idea\]** Latency methodology rework — switch from per-run
  timing to a histogram-based approach so tail-latency regressions
  are visible without re-running. Queued from 21'g.
- [ ] **\[idea\]** Parser-test backfill for 22b.4a-era duplicate-clause
  sites (class × 3, class method × 2, instance × 3, instance method
  × 1). No regression pin today; only worth it if a 22b.4a-era
  diagnostic needs to change.
  - context: JOURNAL 2026-05-10 ("Iteration 22-tidy.7")
- [ ] **\[idea\]** `write_type` `Type::Forall` arm in
  `crates/ailang-prose/src/lib.rs` silently drops constraints in
  inline-type rendering. Dormant — surface forms today only carry
  forall at fn-signature top level. Fix only if a future feature
  carries forall + constraints inline.
  - context: JOURNAL 2026-05-10 ("Iteration 22-tidy.6")
- [ ] **\[idea\]** Richer integration paths between RC and
  uniqueness — deferred from the 21' arc; revisit once the
  uniqueness inference covers more program shapes.
- [ ] **\[idea\]** LLM tool-use schema / MCP server / LSP front-end
  over the prose-roundtrip cycle — additive layers on top of the
  static-prompt `ail merge-prose | client | ail parse | ail check`
  path (LLM calls back into `ail parse`/`ail check` mid-turn; an
  MCP-compatible client discovers AILang's resources/tools/prompts).
  Relocated here from DESIGN.md §"prose roundtrip" + PROSE_ROUNDTRIP.md
  by the docs-honesty-lint milestone (forward intent does not live in
  the canonical docs).
  - context: `docs/specs/2026-05-18-docs-honesty-lint.md`
- [ ] **\[todo\]** Sweep 5 self-match anchor-exclusion — `bench/architect_sweeps.sh`
  Sweep 5's regex matches the DESIGN.md discriminator subsection's own
  forbidden-phrasing catalogue (L62 `"planned / will back / on the
  path to / if-when X arrives"`) forever, so the script inherits a
  guaranteed self-referential EXIT 1 the architect must re-adjudicate
  by hand each run. Not a new failure mode (Sweeps 1-4 already retain
  legitimate date-anchors; non-zero is advisory-by-design per the
  script header) — minor signal-erosion only. Optional fix: a
  negative-lookbehind / line-range exclusion so the rule's own example
  list is not a hit. P3 — may be cut; the architect adjudicates the
  single known self-match trivially today.
  - context: `docs/journals/2026-05-18-audit-docs-honesty-lint.md`
    (architect `[medium]` advisory wart, carry-on)
