# Iteration-discipline revert — Implementation Plan

> **Parent spec:** `docs/specs/0032-iteration-discipline-revert.md`
>
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement`
> to run this plan. Steps use `- [ ]` checkboxes for tracking.

**Goal:** Surgically revert the Iteration-discipline milestone (it.1
`96db54d` + it.2 `a4be1e5`) forward against the `1ff7e81` tree as byte
oracle, keeping feature-acceptance clause 3 and the F3 todo, and add
one honest F1/F4 DESIGN.md note.

**Architecture:** One forward iteration. `main` is sacrosanct — NO
`git revert`/`git reset`. For every *restored* region the byte oracle
is `git show 1ff7e81:<path>`; the implementer makes the region
byte-identical to the oracle and verifies with a targeted `diff`.
Net-new it.1/it.2 surface is deleted outright. DESIGN.md is edited
hunk-by-hunk in place (NEVER wholesale-restored — `1ff7e81` has no
clause 3). Tasks are clustered on the recon's lockstep-invariant
pairs so each task leaves the workspace compiling.

**Tech Stack:** `ailang-core`, `ailang-check`, `ailang-surface`,
`ailang-prose`, `ailang-codegen`, `ail` CLI; `cargo test --workspace`;
`git show 1ff7e81:` as the restoration oracle.

**Oracle convention (used by every task):** to restore a region,
`git show 1ff7e81:<path> > /tmp/oracle_<name>` and diff the edited
file's region against it; "matches oracle" means
`diff <(sed -n 'A,Bp' <file>) <(sed -n 'C,Dp' /tmp/oracle_<name>)`
is empty for the corresponding regions, OR the symbol is absent in
both. The recon line-map (below, per task) is the *map* of what/where;
the oracle is the *authority* on every byte. Line numbers drift as
edits land — always re-locate symbols by name, not by frozen line.

---

## Task 1: AST node family + lockstep drift anchors + DESIGN.md §Data-model

Removing `Term::Loop`/`Term::Recur` from the enum forces the
exhaustive-match arms in three drift tests and the DESIGN.md
§Data-model anchors to move in the same change (recon cross-ref #1,
#2), else the crate is non-exhaustive / a drift test asserts a
missing anchor.

**Files:**
- Modify: `crates/ailang-core/src/ast.rs` (`Term::Loop` ~540-546, `Term::Recur` ~547-552, `struct LoopBinder` ~596-605, serde units `term_loop_round_trips_through_json` ~974-998, `term_recur_empty_args_round_trips` ~1000-1009)
- Modify: `crates/ailang-core/tests/design_schema_drift.rs` (loop tuple ~156-160, recur tuple ~161-164, match arms ~186-187)
- Modify: `crates/ailang-core/tests/spec_drift.rs` (loop tuple ~133-139, recur tuple ~140, arms ~165-166)
- Modify: `crates/ailang-core/tests/schema_coverage.rs` (Loop arm ~251-258, Recur arm ~259)
- Modify: `docs/DESIGN.md` (§Data-model loop/recur JSON ~2422-2436 AND prose ~2452-2465)

- [ ] **Step 1: Capture the AST oracle**

Run: `git show 1ff7e81:crates/ailang-core/src/ast.rs > /tmp/oracle_ast.rs`
Expected: file written; `grep -c 'Term::Loop\|Loop {' /tmp/oracle_ast.rs` prints `0`.

- [ ] **Step 2: Remove `Term::Loop`, `Term::Recur`, `struct LoopBinder` + serde units**

Delete the `Loop { binders, body }` and `Recur { args }` variants from the `Term` enum, the `struct LoopBinder { ... }` definition, and the two `#[cfg(test)]` serde units (`term_loop_round_trips_through_json`, `term_recur_empty_args_round_trips`). The enum's last variant returns to `Assign` exactly as in `/tmp/oracle_ast.rs`.

- [ ] **Step 3: Verify the `Term` enum + serde region is byte-identical to oracle**

Run: `diff <(grep -n 'enum Term' -A120 crates/ailang-core/src/ast.rs) <(grep -n 'enum Term' -A120 /tmp/oracle_ast.rs)`
Expected: empty (the enum body matches `1ff7e81`).
Run: `grep -nc 'Loop\|Recur\|LoopBinder' crates/ailang-core/src/ast.rs`
Expected: `0`.

- [ ] **Step 4: Remove loop/recur exemplars + match arms in the three drift tests**

In `design_schema_drift.rs`, `spec_drift.rs`, `schema_coverage.rs`: delete the loop/recur exemplar tuples and the `Term::Loop => "loop"` / `Term::Recur => "recur"` exhaustive-match arms. Oracle each test file region against `git show 1ff7e81:crates/ailang-core/tests/<file>`.

- [ ] **Step 5: Remove the DESIGN.md §Data-model loop/recur hunks**

Delete `docs/DESIGN.md` lines covering the it.1 loop/recur JSON block (~2422-2436) and the it.1/it.2 prose paragraphs (~2452-2465: "`loop`/`recur` are additive as of iter it.1 … through it.2."). Do NOT touch anything else in DESIGN.md (clause 3, Decision 3 — later tasks). Re-locate by the literal strings `"t": "loop"` / "additive as of iter it.1", not by frozen line.

- [ ] **Step 6: Compile + drift tests green for the AST surface**

Run: `cargo build -p ailang-core 2>&1 | tail -3`
Expected: builds (no non-exhaustive-match error).
Run: `cargo test -p ailang-core --test design_schema_drift --test spec_drift --test schema_coverage 2>&1 | tail -5`
Expected: PASS; no assertion referencing `"t": "loop"`/`"t": "recur"`.

---

## Task 2: `CheckError` variant + `code()` + the three dedicated `ctx()` arms

**Files:**
- Modify: `crates/ailang-check/src/lib.rs` (variants `RecurOutsideLoop` ~706, `RecurArityMismatch` ~711, `RecurTypeMismatch` ~716-721, `RecurNotInTailPosition` ~727, `NonStructuralRecursion` ~737 incl. doc lines; `code()` arms ~791-795; dedicated `ctx()` arms `RecurArityMismatch` ~870-872 / `RecurTypeMismatch` ~873-875 / `NonStructuralRecursion` ~876-878)
- Verify-only: `crates/ailang-check/src/diagnostic.rs` (no loop/recur entries — confirm, no edit)

- [ ] **Step 1: Capture the check oracle**

Run: `git show 1ff7e81:crates/ailang-check/src/lib.rs > /tmp/oracle_check.rs`
Expected: written; `grep -c 'RecurOutsideLoop\|NonStructuralRecursion' /tmp/oracle_check.rs` prints `0`.

- [ ] **Step 2: Delete the five `CheckError` variants (with their doc comments)**

Remove `RecurOutsideLoop`, `RecurArityMismatch`, `RecurTypeMismatch`, `RecurNotInTailPosition`, `NonStructuralRecursion` from the `CheckError` enum, including their `///` doc lines. Re-locate by variant name.

- [ ] **Step 3: Delete their five `code()` arms**

In `fn code(&self)`, delete the five contiguous arms returning the loop/recur/non-structural code strings (~791-795). The enum + `code()` must remain exhaustive over the *remaining* variants.

- [ ] **Step 4: Delete the three dedicated `ctx()` arms (non-uniform — only three)**

In `fn ctx(&self)`, delete ONLY the `RecurArityMismatch`, `RecurTypeMismatch`, `NonStructuralRecursion` arms (~870-878). Do NOT touch the `_ =>` catch-all (~879) — `RecurOutsideLoop`/`RecurNotInTailPosition` correctly fell into it and now simply no longer exist.

- [ ] **Step 5: Confirm `diagnostic.rs` is a genuine no-op**

Run: `grep -nE 'recur|loop|non-structural' crates/ailang-check/src/diagnostic.rs`
Expected: no output (the `//!` "Stable codes" list ends at mq.3; nothing to revert here — confirms spec/recon).

- [ ] **Step 6: Verify variants gone (build will still fail until Task 4 — expected)**

Run: `grep -nc 'RecurOutsideLoop\|RecurArityMismatch\|RecurTypeMismatch\|RecurNotInTailPosition\|NonStructuralRecursion' crates/ailang-check/src/lib.rs`
Expected: `0`.
Note: `cargo build -p ailang-check` is NOT expected to pass yet — the `synth`/walker arms that *raise* these variants are removed in Tasks 3-5. This task's gate is the grep, not a build.

---

## Task 3: Remove the it.2 guardedness pass + `Diverge`-injection (restore effect reconcile to `1ff7e81`)

**Files:**
- Modify: `crates/ailang-check/src/lib.rs` — `verify_structural_recursion` (~3175-3268), its raise of `NonStructuralRecursion` (~3264), helpers `mutual_structural_group` (~3373) / `adt_families` (~3336) / `referenced_con_names` (~3310) **iff absent at `1ff7e81`**, `term_contains_loop` (~2871-2917) + its three callers / Diverge-injection sites (FnDef body ~2074-2077, lam-arrow ~4314/4331-4334, LetRec ~4412/4431-4432), the `verify_structural_recursion` call site (~2063).

- [ ] **Step 1: Determine helper provenance against the oracle**

Run: `for s in verify_structural_recursion term_contains_loop verify_loop_body mutual_structural_group adt_families referenced_con_names; do echo -n "$s @1ff7e81: "; grep -c "fn $s" /tmp/oracle_check.rs; done`
Expected: `verify_structural_recursion`, `term_contains_loop`, `verify_loop_body`, `mutual_structural_group` print `0` (it.2-net-new → delete). `adt_families`/`referenced_con_names`: whatever the oracle prints — `0` ⇒ delete, `≥1` ⇒ KEEP (pre-existing; do not remove). Record the verdict for each in the iter journal.

- [ ] **Step 2: Delete `verify_structural_recursion` and its it.2-only helpers**

Delete the whole `fn verify_structural_recursion` and every helper Step 1 proved absent at `1ff7e81`. Keep any helper Step 1 proved pre-existing. Delete the call site of `verify_structural_recursion` (the line `verify_structural_recursion(...)?;` ~2063).

- [ ] **Step 3: Delete `term_contains_loop` and restore the three Diverge-injection sites to `1ff7e81`**

Delete `fn term_contains_loop`. At each of its three call sites (FnDef-body effect reconcile, lam-arrow, LetRec), restore the effect-reconciliation code to its `1ff7e81` form using `/tmp/oracle_check.rs` as the per-region oracle (the `1ff7e81` code has NO `Diverge` antecedent — no `term_contains_loop` call, no `Diverge` push). Diff each region against the oracle.

- [ ] **Step 4: Verify the guardedness/Diverge surface is gone and effect-reconcile matches oracle**

Run: `grep -nc 'verify_structural_recursion\|term_contains_loop' crates/ailang-check/src/lib.rs`
Expected: `0`.
Run: `diff <(grep -n 'fn reconcile\|effects.*Diverge\|declared.*effect' crates/ailang-check/src/lib.rs) <(grep -n 'fn reconcile\|effects.*Diverge\|declared.*effect' /tmp/oracle_check.rs)` (anchor the comparison on the reconcile fn the implementer identified in Step 3)
Expected: empty for the reconcile region (no residual `Diverge` injection).

---

## Task 4: Remove it.1 check surface — `synth` `loop_stack`, Loop/Recur arms, `verify_loop_body`, `verify_tail_positions` it.1 arms

`verify_tail_positions` is pre-it.1 (oracle `fn verify_tail_positions` at `1ff7e81:2590`) — the function SURVIVES; only its it.1 `Term::Loop`/`Term::Recur` arms are deleted. `synth` loses the it.1 `loop_stack` parameter, forcing every threaded call site + local decl back to the `1ff7e81` signature (recon cross-ref #4).

**Files:**
- Modify: `crates/ailang-check/src/lib.rs` — `synth` signature (`loop_stack` param ~3612) + ~30 threaded call sites + `let mut loop_stack` local decls (recon list: ~2045-2046, 3579-3580, 4001-4654 cluster, 5043/5082/5124/5239/5280/5320/5423/8163); synth `Term::Loop` arm ~4599-4628, synth `Term::Recur` arm ~4641-; `clone_term` rebuild arms ~267-289; post-mono walker arms ~3131-3145 / 3491 / 3549 / 4494-4495; `verify_loop_body` ~2819-2869 (whole it.1 helper); `verify_tail_positions` it.1 arms ~2798-2815.

- [ ] **Step 1: Confirm the `1ff7e81` `synth` signature**

Run: `grep -n 'fn synth' /tmp/oracle_check.rs | head -1`
Expected: prints the `1ff7e81` `synth` signature (no `loop_stack` parameter). Record it; it is the target signature.

- [ ] **Step 2: Delete the synth `Term::Loop` / `Term::Recur` arms and `verify_loop_body`**

Delete both synth match arms for `Term::Loop`/`Term::Recur` (incl. the `loop_stack.push/.pop` lines) and the whole `fn verify_loop_body`.

- [ ] **Step 3: Remove the it.1 arms from `verify_tail_positions` (function survives)**

Delete ONLY the `Term::Loop`/`Term::Recur` arms inside `verify_tail_positions`. Then diff the whole function against the oracle: `diff <(sed -n '/fn verify_tail_positions/,/^}/p' crates/ailang-check/src/lib.rs) <(sed -n '/fn verify_tail_positions/,/^}/p' /tmp/oracle_check.rs)` — Expected: empty (the function is byte-identical to its `1ff7e81` form).

- [ ] **Step 4: Drop the `loop_stack` parameter from `synth` and every call/decl**

Restore `fn synth` to the Step-1 signature. Remove the `loop_stack` argument from every `synth(...)` call and delete every `let mut loop_stack: Vec<Vec<Type>> = ...` local. Remove `clone_term` Loop/Recur arms and the post-mono Loop/Recur walker arms (incl. the `"loop"`/`"recur"` name-string arms ~4494-4495).

- [ ] **Step 5: `ailang-check` compiles**

Run: `cargo build -p ailang-check 2>&1 | tail -3`
Expected: builds clean.
Run: `grep -nc 'Term::Loop\|Term::Recur\|loop_stack\|verify_loop_body' crates/ailang-check/src/lib.rs`
Expected: `0`.

---

## Task 5: Remove Loop/Recur walker arms from the remaining `ailang-check` + `ailang-core` modules

**Files:**
- Modify: `crates/ailang-check/src/lift.rs` (~400-419, 749-750, 787-790)
- Modify: `crates/ailang-check/src/mono.rs` (~717-725, 1247-1257, 1362-1369, 1635-1645)
- Modify: `crates/ailang-check/src/linearity.rs` (~612-620, 809-810, 953-958)
- Modify: `crates/ailang-check/src/uniqueness.rs` (~355-361)
- Modify: `crates/ailang-check/src/reuse_shape.rs` (~268-274)
- Modify: `crates/ailang-check/src/pre_desugar_validation.rs` (~136-142)
- Modify: `crates/ailang-check/src/builtins.rs` (~346-358, 591-603)
- Modify: `crates/ailang-core/src/desugar.rs` (~366-373, 605-628, 1275-1287, 1485-1519, 1663-1674, 1755-1759, 1807-1810, 1841-1844, 2966-2969)
- Modify: `crates/ailang-core/src/workspace.rs` (~1262-1272, 1411-1417)

- [ ] **Step 1: Capture oracles for the walker files**

Run: `for f in lift mono linearity uniqueness reuse_shape pre_desugar_validation builtins; do git show 1ff7e81:crates/ailang-check/src/$f.rs > /tmp/oracle_$f.rs; done; for f in desugar workspace; do git show 1ff7e81:crates/ailang-core/src/$f.rs > /tmp/oracle_core_$f.rs; done`
Expected: written; `grep -lc 'Term::Loop' /tmp/oracle_*.rs /tmp/oracle_core_*.rs` prints nothing (none contain Loop arms at `1ff7e81`).

- [ ] **Step 2: Delete every Loop/Recur arm + `loop_stack` decl in the seven check modules**

For each `ailang-check` file above: delete the `Term::Loop`/`Term::Recur` match arms and any `let mut loop_stack` decl. Diff each file against its `/tmp/oracle_<f>.rs` and make it match.

- [ ] **Step 3: Delete every Loop/Recur arm in `desugar.rs` and `workspace.rs`**

Same, for `crates/ailang-core/src/desugar.rs` and `workspace.rs`, against `/tmp/oracle_core_<f>.rs`.

- [ ] **Step 4: Workspace-wide grep is clean for check+core**

Run: `grep -rnc 'Term::Loop\|Term::Recur' crates/ailang-check/src crates/ailang-core/src | grep -v ':0'`
Expected: no output.
Run: `cargo build -p ailang-check -p ailang-core 2>&1 | tail -3`
Expected: builds clean.

---

## Task 6: Surface + Prose + Form-A grammar

**Files:**
- Modify: `crates/ailang-surface/src/parse.rs` (`parse_loop` ~1656-1706, `parse_recur` ~1712-1721, dispatch arms ~1217-1218, keyword-list entry ~1227, EBNF `//!` block lines ~43/71/73-74, surface units `parses_loop_with_one_binder_and_recur_body` ~2665-2680, `parses_recur_zero_args` ~2682-2688)
- Modify: `crates/ailang-surface/src/print.rs` (`Term::Loop` arm ~597-633, `Term::Recur` arm ~634-644)
- Modify: `crates/ailang-prose/src/lib.rs` (`write_term` Loop/Recur ~938-968, `count_free_var` ~1170-1186, `subst_var_with_term` ~1339-1371, lockstep unit ~2188-2220)
- Modify: `crates/ailang-core/specs/form_a.md` (grammar ~287-288, semantics ~327-342)

- [ ] **Step 1: Capture oracles**

Run: `git show 1ff7e81:crates/ailang-surface/src/parse.rs > /tmp/oracle_parse.rs; git show 1ff7e81:crates/ailang-surface/src/print.rs > /tmp/oracle_print.rs; git show 1ff7e81:crates/ailang-prose/src/lib.rs > /tmp/oracle_prose.rs; git show 1ff7e81:crates/ailang-core/specs/form_a.md > /tmp/oracle_forma.md`
Expected: written.

- [ ] **Step 2: parse.rs — remove parse_loop/parse_recur, dispatch, keyword list, EBNF, units**

Delete `fn parse_loop`, `fn parse_recur`, their dispatch arms, the `loop`/`recur` entries in the error keyword list (oracle line ~1222-1223: `…, `mut`, `assign`, `lit-unit``), the EBNF `//!` lines for `loop-term`/`recur-term`, and the two surface units. Diff parse.rs against `/tmp/oracle_parse.rs`.

- [ ] **Step 3: print.rs + prose lib.rs — remove the Loop/Recur arms + prose lockstep unit**

Delete the `Term::Loop`/`Term::Recur` arms in `print.rs::write_term` and in `prose/src/lib.rs` (`write_term`, `count_free_var`, `subst_var_with_term`) plus the prose lockstep unit test. Diff both against their oracles.

- [ ] **Step 4: form_a.md — remove loop/recur grammar + semantics**

Delete the loop/recur grammar entries (~287-288) and the semantics block (~327-342). Diff against `/tmp/oracle_forma.md`.

- [ ] **Step 5: Surface + prose build + grep clean**

Run: `cargo build -p ailang-surface -p ailang-prose 2>&1 | tail -3`
Expected: builds clean.
Run: `grep -rnc 'Term::Loop\|Term::Recur\|parse_loop\|parse_recur' crates/ailang-surface/src crates/ailang-prose/src | grep -v ':0'`
Expected: no output.

---

## Task 7: Codegen — remove the loop lowering, keep the tail-driven seam

The it.1 parallel `block_terminated` setter is the ONLY setter removed (`lib.rs:1996`, inside the Recur arm; the in-code comment ~1976-1978 is the authoritative boundary). The four tail-driven setters (`~2430` emit_call, `~2511` emit_indirect_call, `~2652`/`~2724` lower_effect_op) and the four control-flow terminators (`~1401/1625/2816/2862`) are pre-existing and STAY (recon cross-ref #7).

**Files:**
- Modify: `crates/ailang-codegen/src/lib.rs` (`loop_frames` field ~746, `struct LoopFrame` ~755-760, init ~880, clear ~1086, `Term::Loop` arm ~1873-1972, `Term::Recur` arm ~1979-1997 incl. the it.1 setter at ~1996, synth-shim arms ~3258-3259)
- Modify: `crates/ailang-codegen/src/escape.rs` (~204-214, 409-417, 549-562)
- Modify: `crates/ailang-codegen/src/lambda.rs` (`loop_frames` save ~152, restore ~267, arms ~518, ~531)
- Modify: `crates/ail/src/main.rs` (Loop/Recur arms ~1549-1562, ~2811-2838)

- [ ] **Step 1: Capture oracles**

Run: `git show 1ff7e81:crates/ailang-codegen/src/lib.rs > /tmp/oracle_cg.rs; git show 1ff7e81:crates/ailang-codegen/src/escape.rs > /tmp/oracle_escape.rs; git show 1ff7e81:crates/ailang-codegen/src/lambda.rs > /tmp/oracle_cglam.rs; git show 1ff7e81:crates/ail/src/main.rs > /tmp/oracle_main.rs`
Expected: written.

- [ ] **Step 2: codegen lib.rs — remove loop lowering + the it.1 setter only**

Delete the `Term::Loop` and `Term::Recur` lowering arms, the `loop_frames` field, `struct LoopFrame`, its init and clear, and the synth-shim Loop/Recur arms. The it.1 `self.block_terminated = true` at the old ~1996 (inside the deleted Recur arm) goes with the arm. Verify the four tail-driven setters survive: `grep -nc 'block_terminated = true' crates/ailang-codegen/src/lib.rs` must equal the `1ff7e81` count → Run: `grep -c 'block_terminated = true' /tmp/oracle_cg.rs` first and record it; the post-edit count must equal it.

- [ ] **Step 3: escape.rs + lambda.rs + main.rs — remove Loop/Recur arms + loop_frames save/restore**

Delete the escape-walk Loop/Recur arms, the `lambda.rs` `loop_frames` save/restore + arms, and the `ail/src/main.rs` Loop/Recur arms. Diff each against its oracle.

- [ ] **Step 4: Whole workspace builds; codegen grep clean**

Run: `grep -rnc 'Term::Loop\|Term::Recur\|loop_frames\|LoopFrame' crates/ailang-codegen/src crates/ail/src | grep -v ':0'`
Expected: no output.
Run: `cargo build --workspace 2>&1 | tail -3`
Expected: builds clean (entire workspace now compiles post-revert).

---

## Task 8: Delete pin files, surgical e2e/pin-arm removal, carve_out_inventory, fixtures, bench

**Files:**
- Delete: `crates/ailang-check/tests/loop_recur_pin.rs`, `crates/ailang-check/tests/structural_recursion_pin.rs`
- Modify: `crates/ail/tests/e2e.rs` (it.1 cases ~2831-2845+), `crates/ail/tests/codegen_import_map_fallback_pin.rs` (arms ~104-110)
- Modify: `crates/ailang-core/tests/carve_out_inventory.rs` (doc ~17-23, `EXPECTED` entries+comments ~48-56 — **string-set, NOT a count**: remove the 7 it.1/it.2 `test_*.ail.json` entries + 2 comment lines)
- Delete (16 net-new fixtures): `examples/loop_counter.ail`, `examples/loop_in_lambda_e2e.ail`, `examples/loop_needs_diverge.ail`, `examples/loop_nested_in_lambda.ail`, `examples/loop_smoke.ail`, `examples/struct_rec_foldl_sum.ail`, `examples/struct_rec_list_len.ail`, `examples/struct_rec_sum_e2e.ail`, `examples/struct_rec_tree_forest.ail`, `examples/test_loop_missing_diverge.ail.json`, `examples/test_mutual_cross_family.ail.json`, `examples/test_non_structural_recursion.ail.json`, `examples/test_recur_arity_mismatch.ail.json`, `examples/test_recur_not_in_tail_position.ail.json`, `examples/test_recur_outside_loop.ail.json`, `examples/test_recur_type_mismatch.ail.json`
- Restore: `examples/rc_pin_recurse_implicit.ail`, `examples/rc_let_alias_implicit_param.ail` (to `git show 1ff7e81:`)
- Delete: `bench/it3-oracle/` (MANIFEST.tsv + 40 `.out`)
- KEEP (explicit no-op): `bench/orchestrator-stats/2026-05-15-iter-it.{1,2,3}.json` — historical record like journals/plans (Boss decision)

- [ ] **Step 1: Re-verify the net-new vs modified fixture split against the live diff**

Run: `git diff --stat 1ff7e81 HEAD -- examples/ bench/ | grep -E 'examples/|bench/'`
Expected: the 16 named fixtures + every `bench/it3-oracle/*` show as net-new (`| N +` only); `rc_pin_recurse_implicit.ail`/`rc_let_alias_implicit_param.ail` show partial-modify (deletions present); `mut_counter.ail`/`mut_sum_floats.ail` absent (already `1ff7e81`). If any file contradicts this, STOP and report (do not guess).

- [ ] **Step 2: Delete the two whole pin files + the 16 fixtures + bench/it3-oracle/**

Run: `rm -f crates/ailang-check/tests/loop_recur_pin.rs crates/ailang-check/tests/structural_recursion_pin.rs examples/loop_counter.ail examples/loop_in_lambda_e2e.ail examples/loop_needs_diverge.ail examples/loop_nested_in_lambda.ail examples/loop_smoke.ail examples/struct_rec_foldl_sum.ail examples/struct_rec_list_len.ail examples/struct_rec_sum_e2e.ail examples/struct_rec_tree_forest.ail examples/test_loop_missing_diverge.ail.json examples/test_mutual_cross_family.ail.json examples/test_non_structural_recursion.ail.json examples/test_recur_arity_mismatch.ail.json examples/test_recur_not_in_tail_position.ail.json examples/test_recur_outside_loop.ail.json examples/test_recur_type_mismatch.ail.json && rm -rf bench/it3-oracle`
Expected: exit 0.
(Note: plain `rm` only — the implementer never touches the git index. These tracked-file deletions stay as working-tree changes; the Boss's `git add -A` at iter-commit time captures them. NOT a commit.)

- [ ] **Step 3: Restore the two modified RC fixtures to `1ff7e81`**

Run: `git show 1ff7e81:examples/rc_pin_recurse_implicit.ail > examples/rc_pin_recurse_implicit.ail && git show 1ff7e81:examples/rc_let_alias_implicit_param.ail > examples/rc_let_alias_implicit_param.ail && git diff --stat 1ff7e81 -- examples/rc_pin_recurse_implicit.ail examples/rc_let_alias_implicit_param.ail`
Expected: empty (both now byte-identical to `1ff7e81`).

- [ ] **Step 4: Surgically revert e2e.rs + codegen_import_map_fallback_pin.rs to `1ff7e81`**

Diff `crates/ail/tests/e2e.rs` against `git show 1ff7e81:crates/ail/tests/e2e.rs` and remove exactly the it.1 added cases (loop_counter / loop-in-lambda / loop_frames E2E). Diff `crates/ail/tests/codegen_import_map_fallback_pin.rs` against its `1ff7e81` oracle and remove the `Term::Loop`/`Term::Recur` arms in `contains_xmod_show_var`. Verify each region matches the oracle.

- [ ] **Step 5: carve_out_inventory.rs — remove the 7 string entries + 2 comment lines**

Capture oracle: `git show 1ff7e81:crates/ailang-core/tests/carve_out_inventory.rs > /tmp/oracle_carve.rs`. Edit `EXPECTED` so its entry set + the doc comment is byte-identical to the oracle (last entry returns to `test_mut_var_captured_by_lambda.ail.json`). Diff against `/tmp/oracle_carve.rs` — Expected: empty.

- [ ] **Step 6: Confirm bench/orchestrator-stats kept; full suite green**

Run: `ls bench/orchestrator-stats/2026-05-15-iter-it.1.json bench/orchestrator-stats/2026-05-15-iter-it.2.json bench/orchestrator-stats/2026-05-15-iter-it.3.json && ls bench/it3-oracle 2>&1`
Expected: the three stats json exist; `ls bench/it3-oracle` → "No such file or directory".
Run: `cargo test --workspace 2>&1 | tail -8`
Expected: PASS (whole suite green — the pinned-surface tests are gone, no orphan refs).

---

## Task 9: DESIGN.md Decision 3 + clause-3 de-claim + F1/F4 note + brainstorm SKILL.md de-claim + doc-presence test

**Files:**
- Modify: `docs/DESIGN.md` (Decision 3 it.2 prose ~168-176 → `1ff7e81` placeholder; clause-3 worked-example sentence ~105-110 de-claim; F1/F4 note added in §"Local mutable state" vicinity ~2918)
- Modify: `skills/brainstorm/SKILL.md` (~107 worked-example reference de-claim)
- Create: doc-presence test (a new `#[test]` in `crates/ailang-core/tests/design_schema_drift.rs`)

- [ ] **Step 1: Restore Decision 3 to the `1ff7e81` placeholder (hunk-only)**

Run: `git show 1ff7e81:docs/DESIGN.md | sed -n '/## Decision 3/,/## Decision 4/p' > /tmp/oracle_d3.md`
Replace the current Decision 3 body (the it.2 paragraph "As of iter it.2 … unbounded `loop`.") with the `1ff7e81` placeholder text from `/tmp/oracle_d3.md` (the placeholder is the single sentence: `In the MVP only the effects IO and Diverge (for infinite loops) are wired up.`). Edit ONLY between `## Decision 3` and `## Decision 4`. Verify: `diff <(sed -n '/## Decision 3/,/## Decision 4/p' docs/DESIGN.md) /tmp/oracle_d3.md` → empty.

- [ ] **Step 2: De-claim the clause-3 worked example (KEEP the block, de-claim the sentence)**

Locate the clause-3 sentence at DESIGN.md ~105-110: *"The canonical worked example is the iteration story: a bare `while` over mutable state passes 1 and 2 yet fails 3 (it reinstates iterated-mutable-state reasoning); structural recursion over an acyclic ADT and a named loop/recur point pass all three (the bug class is closed by construction, not by advice)."*
Replace with (hypothetical-illustration form — the `loop`/`recur` machinery no longer exists, so it must not read as shipped):

> The criterion's discriminator is whether the wrong code fails to typecheck, not whether a guideline advises against it. Worked example: a bare `while` over mutable state would pass clauses 1 and 2 yet fail clause 3 (it reinstates iterated-mutable-state reasoning the pure core exists to remove); a hypothetical "all repetition is either structurally-decreasing recursion over an acyclic ADT or an explicit named loop" iteration story would, *if it could be built without a documented-unenforced precondition*, pass all three — and the fact that the 2026-05 attempt could not (it forced a silent-divergence precondition; see `docs/specs/0032-iteration-discipline-revert.md`) is itself the clause-3 mechanism working as intended.

- [ ] **Step 3: De-claim the brainstorm SKILL.md reference in lockstep**

In `skills/brainstorm/SKILL.md` ~107, change "(with the canonical worked example — the iteration story)" to "(with the canonical worked example — see the criterion text)" so it no longer asserts the iteration story shipped. Verify: `grep -n 'iteration story' skills/brainstorm/SKILL.md` → no line claims it as a shipped example.

- [ ] **Step 4: Add the F1/F4 documentation note**

In `docs/DESIGN.md`, in the §"Local mutable state" vicinity (after the bullet ending `Spec: docs/specs/0029-mut-local.md.` ~line 2918, before "Pipeline regression smoke tests:"), insert verbatim:

> **Accumulator-over-iteration shape (documented idiom, not an enforced rule).** `mut`/`var`/`assign` removes friction from *straight-line* and *cascade-of-if* accumulators, but does **not** by itself express the "one running total updated once per loop iteration" shape: a `var` cannot be captured into a lambda (the seal) and there is no loop construct in the language. Until a future iteration-totality milestone lands (gated behind `Nat`/refinement types — see the deferred roadmap entry), the canonical pattern for the per-iteration accumulator is a tail-recursive helper that threads the running total as an explicit parameter; `examples/mut_counter.ail` is the reference. This is a documented authoring idiom, not a typecheck-enforced constraint — wrong code here does not fail to compile, it is merely less ergonomic, which is exactly why this is documentation and not a language rule.

- [ ] **Step 5: Add the doc-presence guard test**

In `crates/ailang-core/tests/design_schema_drift.rs` add:

```rust
#[test]
fn design_md_documents_the_accumulator_over_iteration_idiom() {
    let design = std::fs::read_to_string(
        concat!(env!("CARGO_MANIFEST_DIR"), "/../../docs/DESIGN.md"),
    )
    .expect("read DESIGN.md");
    assert!(
        design.contains("Accumulator-over-iteration shape (documented idiom, not an enforced rule)"),
        "the F1/F4 documented-idiom note must not be silently dropped from DESIGN.md"
    );
    assert!(
        design.contains("examples/mut_counter.ail is the reference")
            || design.contains("`examples/mut_counter.ail` is the reference"),
        "the F1/F4 note must keep pointing at the canonical fallback fixture"
    );
}
```

- [ ] **Step 6: Verify clause 3 still present, Decision 3 reverted, note guarded**

Run: `grep -c 'clauses 1 and 2 yet fail clause 3\|fails clause 3\|fail 3' docs/DESIGN.md`
Expected: `≥1` (clause-3 block retained, only de-claimed).
Run: `cargo test -p ailang-core --test design_schema_drift design_md_documents_the_accumulator_over_iteration_idiom 2>&1 | tail -3`
Expected: PASS.

---

## Task 10: Roadmap + superseded spec header + journal

**Files:**
- Modify: `docs/roadmap.md` (remove the Iteration-discipline `[~]` entry + blocking-fork sub-section, contiguous ~43-178; add one deferred entry; KEEP F3 ~375-382 + Stateful-islands untouched)
- Modify: `docs/specs/0031-iteration-discipline.md` (add superseded header — file is already clean; the A1 discard was done Boss-side pre-plan, so this is the ONLY edit to this file)
- Create: `docs/journals/2026-05-16-iter-revert.md` (the iter journal — written by the implement skill at iter close)
- Modify: `docs/journals/INDEX.md` (append the new journal line)

- [ ] **Step 1: Remove the Iteration-discipline roadmap block, add the deferred entry**

Delete the contiguous `- [~] **[milestone]** Iteration discipline …` entry and its "blocking fork" sub-section (recon: roadmap.md ~43-178, ending just before `- [x] **[milestone]** Heap-Str ABI`). In its place under **P2 — Medium-term** (or P3 if judged not decision-ready — Boss/implementer keeps it P2 as a decided-but-deferred direction), insert verbatim:

```markdown
- [ ] **\[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/0032-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).
```

- [ ] **Step 2: Verify F3 + Stateful-islands untouched**

Run: `grep -n 'zero-arg\|(app f)\|Stateful islands\|Stateful-islands' docs/roadmap.md | head`
Expected: the F3 todo and the Stateful-islands milestone entry are still present and unchanged (the only roadmap removal is the Iteration-discipline block; the deleted block's internal "supersedes the while-loops-legal scope bullet" cross-reference disappears with it, which is correct).

- [ ] **Step 3: Add the superseded header to the old spec**

At the very top of `docs/specs/0031-iteration-discipline.md` (before `# Iteration discipline — Design Spec`), insert verbatim:

```markdown
> **SUPERSEDED 2026-05-16 by
> `docs/specs/0032-iteration-discipline-revert.md`.** This
> milestone was reverted in full (it.1 + it.2 backed out forward;
> it.3 never ran). It is retained as a historical record of the
> attempt and the design-thesis gap it surfaced; it is **not** the
> current iteration-story design. Do not plan or implement from this
> file.

```

- [ ] **Step 4: Confirm working tree of the old spec is otherwise clean**

Run: `git diff --stat 1ff7e81 -- docs/specs/0031-iteration-discipline.md`
Expected: shows ONLY the spec's committed history relative to `1ff7e81` plus the new header line as a working-tree change — i.e. there is no residual A1 amendment (it was discarded Boss-side pre-plan). If unexpected uncommitted body changes appear, STOP and report.

---

## Task 11: Behavioural-equivalence sweep + whole-suite gate (the correctness gate)

The structural revert is necessary but not sufficient; this task
proves the *compiler behaviour* returned to `1ff7e81` for the
surviving corpus.

**Files:** none modified — verification only.

- [ ] **Step 1: Build the `1ff7e81` reference compiler in a throwaway worktree**

Run: `git worktree add /tmp/ail-1ff7e81 1ff7e81 && (cd /tmp/ail-1ff7e81 && cargo build -p ail 2>&1 | tail -1)`
Expected: a built `ail` at `/tmp/ail-1ff7e81/target/debug/ail`.

- [ ] **Step 2: Capture the `1ff7e81` corpus behaviour oracle**

Run:
```
mkdir -p /tmp/eqv_old
for f in $(cd /tmp/ail-1ff7e81 && ls examples/*.ail); do
  base=$(basename "$f")
  (cd /tmp/ail-1ff7e81 && ./target/debug/ail check "examples/$base" > "/tmp/eqv_old/$base.check" 2>&1; \
   ./target/debug/ail run "examples/$base" > "/tmp/eqv_old/$base.run" 2>&1; echo "$? " > "/tmp/eqv_old/$base.rc")
done
ls /tmp/eqv_old | wc -l
```
Expected: a non-zero count of captured `.check`/`.run`/`.rc` triples (the `1ff7e81` corpus).

- [ ] **Step 3: Run the post-revert compiler over the same corpus and diff**

Run:
```
cargo build -p ail 2>&1 | tail -1
fail=0
for f in $(ls examples/*.ail); do
  base=$(basename "$f")
  [ -f "/tmp/eqv_old/$base.check" ] || continue   # only fixtures that existed at 1ff7e81
  ./target/debug/ail check "examples/$base" > "/tmp/new.check" 2>&1
  ./target/debug/ail run "examples/$base" > "/tmp/new.run" 2>&1
  diff -q "/tmp/eqv_old/$base.check" "/tmp/new.check" >/dev/null 2>&1 || { echo "CHECK DRIFT: $base"; fail=1; }
  diff -q "/tmp/eqv_old/$base.run"   "/tmp/new.run"   >/dev/null 2>&1 || { echo "RUN DRIFT: $base"; fail=1; }
done
echo "fail=$fail"
```
Expected: `fail=0`, no "DRIFT" lines (every surviving fixture behaves byte-identically to `1ff7e81`). Any drift is a revert defect — fix it before the task closes (do NOT ratify; the spec mandates *pristine*, not ratified).

- [ ] **Step 4: Whole-suite gate + worktree cleanup**

Run: `cargo test --workspace 2>&1 | tail -8`
Expected: PASS (all crates green).
Run: `grep -rnc 'Term::Loop\|Term::Recur\|verify_structural_recursion\|term_contains_loop\|loop_stack\|loop_frames\|RecurOutsideLoop\|NonStructuralRecursion' crates/ docs/DESIGN.md crates/ailang-core/specs/form_a.md | grep -v ':0' | grep -v 'docs/specs\|docs/journals\|docs/plans'`
Expected: no output (zero residual it.1/it.2 production surface; historical docs under specs/journals/plans legitimately still mention them).
Run: `git worktree remove --force /tmp/ail-1ff7e81`
Expected: worktree removed.

---

## Notes for the implement skill

- This is a revert: for every *Modify-to-restore* step the oracle
  `git show 1ff7e81:<path>` is the authority on every byte; the
  line numbers in this plan are the recon map (drift as edits land —
  re-locate by symbol name). For *Delete* and *net-add* steps the
  plan gives the exact command / verbatim text (Iron Law satisfied:
  nothing is undecided).
- Tasks 2-7 will not individually `cargo build` green until the
  whole check/codegen surface is removed (the variants are raised
  before they are deleted); each such task's gate is its grep, and
  the first full-workspace build gate is **Task 7 Step 4**. This is
  intentional task clustering on the recon lockstep pairs, not a
  plan defect.
- No `git commit` anywhere. All deletions/edits stay in the working
  tree (incl. `git rm` staged deletes); the Boss commits the whole
  revert as one cohesive iter at the end.
- The iter journal `docs/journals/2026-05-16-iter-revert.md` +
  `INDEX.md` line are written by the implement skill at iter close
  (Task 10 lists them so the file-map is complete; they are not a
  numbered code step).
