Files
AILang/docs/plans/0074-iter-revert.md
T
Brummel 832375f2ac convention: counter-prefix file naming across docs/specs/, docs/plans/, design/contracts/, design/models/
All 176 files in the four accumulating directories now use a
zero-padded 4-digit counter prefix that reflects creation order
(`NNNN-slug.md`). The counter is assigned per directory in strict
git-log creation order; ties broken alphabetically by original name.
The old `YYYY-MM-DD-` prefix on docs/specs/ and docs/plans/ files is
dropped — the date is recoverable from git log and the counter
carries the ordering.

A file's counter is stable for the life of the file: never reassigned,
never reused, never compacted. Deleted files retire their counter;
subsequent files do not fill the gap. This is the property that lets
cross-references stay literal — refs use the full filename including
the counter (`design/contracts/0007-honesty-rule.md`) so they grep
cleanly and resolve directly without a glob step.

313 cross-references updated across .md/.rs/.toml/.c/.json files
(test pins, include_str! paths, design-INDEX entries, baseline notes,
runtime C comments, inter-contract markdown links incl. bare basename
and `../models/foo.md` forms).

CLAUDE.md gets a new "File-naming convention" section spelling out
the rule and rationale. skills/brainstorm/SKILL.md and
skills/planner/SKILL.md updated so new spec/plan creation produces
counter-prefixed names from the start.

The full test suite (cargo test --workspace) passes.
2026-05-28 13:31:31 +02:00

36 KiB

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.rsverify_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.rssynth 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:

#[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:

- [ ] **\[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:

> **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).