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.
36 KiB
Iteration-discipline revert — Implementation Plan
Parent spec:
docs/specs/0032-iteration-discipline-revert.mdFor agentic workers: REQUIRED SUB-SKILL: use
skills/implementto 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 unitsterm_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
Termenum + 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(variantsRecurOutsideLoop~706,RecurArityMismatch~711,RecurTypeMismatch~716-721,RecurNotInTailPosition~727,NonStructuralRecursion~737 incl. doc lines;code()arms ~791-795; dedicatedctx()armsRecurArityMismatch~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
CheckErrorvariants (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.rsis 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 ofNonStructuralRecursion(~3264), helpersmutual_structural_group(~3373) /adt_families(~3336) /referenced_con_names(~3310) iff absent at1ff7e81,term_contains_loop(~2871-2917) + its three callers / Diverge-injection sites (FnDef body ~2074-2077, lam-arrow ~4314/4331-4334, LetRec ~4412/4431-4432), theverify_structural_recursioncall 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_recursionand 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_loopand restore the three Diverge-injection sites to1ff7e81
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—synthsignature (loop_stackparam ~3612) + ~30 threaded call sites +let mut loop_stacklocal decls (recon list: ~2045-2046, 3579-3580, 4001-4654 cluster, 5043/5082/5124/5239/5280/5320/5423/8163); synthTerm::Looparm ~4599-4628, synthTerm::Recurarm ~4641-;clone_termrebuild arms ~267-289; post-mono walker arms ~3131-3145 / 3491 / 3549 / 4494-4495;verify_loop_body~2819-2869 (whole it.1 helper);verify_tail_positionsit.1 arms ~2798-2815. -
Step 1: Confirm the
1ff7e81synthsignature
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::Recurarms andverify_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_stackparameter fromsynthand 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-checkcompiles
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_stackdecl 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.rsandworkspace.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 unitsparses_loop_with_one_binder_and_recur_body~2665-2680,parses_recur_zero_args~2682-2688) -
Modify:
crates/ailang-surface/src/print.rs(Term::Looparm ~597-633,Term::Recurarm ~634-644) -
Modify:
crates/ailang-prose/src/lib.rs(write_termLoop/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_framesfield ~746,struct LoopFrame~755-760, init ~880, clear ~1086,Term::Looparm ~1873-1972,Term::Recurarm ~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_framessave ~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,EXPECTEDentries+comments ~48-56 — string-set, NOT a count: remove the 7 it.1/it.2test_*.ail.jsonentries + 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(togit 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 →1ff7e81placeholder; 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]incrates/ailang-core/tests/design_schema_drift.rs) -
Step 1: Restore Decision 3 to the
1ff7e81placeholder (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
whileover 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; seedocs/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/assignremoves 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: avarcannot be captured into a lambda (the seal) and there is no loop construct in the language. Until a future iteration-totality milestone lands (gated behindNat/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.ailis 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
1ff7e81reference 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
1ff7e81corpus 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 buildgreen 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 commitanywhere. All deletions/edits stay in the working tree (incl.git rmstaged 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.mdline 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).