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.
19 KiB
Iteration-discipline revert — Design Spec
Date: 2026-05-16 Status: Draft — awaiting user spec review Authors: Brummel (orchestrator) + Claude
Goal
Back the Iteration-discipline milestone out of the working language
and restore the pre-milestone iteration story, by a single forward
iteration (main is sacrosanct — no git reset/git revert of the
shipped commits 96db54d/a4be1e5).
Why this is a milestone, not a tidy iteration: the milestone being removed was an identity-level change (a new AST node family, a new checker pass, a new effect, DESIGN.md Decision-3 prose). Reverting it is an equally identity-level decision about what AILang's iteration story is, and it touches DESIGN.md invariants. It earns a spec.
Root cause being corrected (recorded so a future reader does not re-derive the same mistake):
- The milestone was scoped from fieldtest finding F1
(
docs/specs/0030-fieldtest-mut-local.md), which was tagged[friction]and whose own summary table named the honest minimal action: "tighten DESIGN.md to name the gap — less work, more honest". It was instead escalated into "retiretail-app, recursion is total-by-construction, addDiverge". - That thesis recognised exactly one total-by-construction
scheme (ADT-structural over an acyclic ADT). The it.3 corpus sweep
proved it incomplete:
build(d: Int) = if d==0 then Leaf else Node(1, build(d-1), build(d-1))— the single most LLM-natural tree-builder — is neither ADT-structural norloop/recur-able, so the milestone as specified made a trivially-total, maximally-natural function inexpressible (it.3 BLOCKED). - The only in-thesis escape (A1 / it.2b — Int-bounded recursion as a
second total class) requires conceding the language's first
documented-but-unenforced totality precondition (
build(-1)diverges silently; Decision 4 keeps refinements opaque with no SMT, sop >= 0is unenforceable today by construction). That is a dilution of a purity pillar the user personally co-designed. loop/recur(it.1) was never run through the feature-acceptance gate for LLM-naturalness in its own right; it rode in on the thesis. it.2'sNonStructuralRecursionchecker is not a stable additive stopping point: it rejects bare non-structural recursion while grandfatheringtail-marked calls, i.e. it half-enforces a dichotomy the language only commits to once the destructive it.3 completes. "Mechanically additive" (nothing deleted) is not the same as "a coherent end-state".
User decision (2026-05-16): full revert to the pre-milestone state.
Both it.1 and it.2 come out. tail-app / structural recursion remain
the iteration mechanism exactly as before 9973546. The genuine
iteration-totality ambition is not abandoned — it is re-sequenced
behind a future Nat/refinement-types milestone, because doing it
properly (enforced non-negativity) requires type machinery the
language has not built. Stateful-islands (P2, the real descendant of
the user's ~/sma_factory.myc streaming analysis) is untouched: that
analysis motivates the streaming milestone, not the iteration story.
The revert is gate-satisfied by subtraction: the milestone fails feature-acceptance clause 3 (via A1); the revert restores the pre-milestone state, which already passed all three clauses. The only addition is a DESIGN.md documentation note for F1/F4 — and the criterion states verbatim that "a documentation note is not a reshape", so it introduces no bug class and asserts nothing at the typecheck level.
Architecture
One forward iteration. The target tree state for the
iteration/checker/codegen/AST/surface/fixture surface is the
state at commit 1ff7e81 (the last commit before milestone-start
9973546). The revert is surgical, not a range revert, because
three hunks landed with the milestone but are independently sound
and are kept:
| Landed in | Hunk | Disposition |
|---|---|---|
9973546 |
DESIGN.md feature-acceptance clause 3 | KEEP — independently sound; it is the very tool that diagnosed the over-escalation. |
9973546 |
skills/brainstorm/SKILL.md Step-4 clause-3 wording |
KEEP — part of the clause-3 strengthening (see §Components for the worked-example caveat). |
9973546 |
docs/roadmap.md Iteration-discipline milestone entry |
REVERT — replaced by a deferred future entry. |
4079f09 |
docs/roadmap.md F3 (zero-arg (app f)) P2 todo |
KEEP — independent of the iteration story; stays a P2 todo. |
git revert 9973546..a4be1e5 is therefore forbidden twice over:
(1) it is a history-rewind class operation on main; (2) it would
also delete clause 3 and the F3 todo. The revert is reconstructed
forward, file by file, against the 1ff7e81 tree as the reference,
and committed as new main history.
Components
The exact removal / restoration / addition surface. The planner turns
this into bite-sized tasks; the implementer executes against
1ff7e81 as the byte reference (git show 1ff7e81:<path> is the
oracle for every restored file).
Remove (it.1 + it.2 production surface)
- AST (
crates/ailang-core/src/ast.rs).Term::Loop { binders, body },Term::Recur { args },struct LoopBinder, and their serde round-trip unit tests. Restore theTermenum and its serde to the1ff7e81shape. - Schema-drift / coverage anchors.
crates/ailang-core/tests/design_schema_drift.rs,schema_coverage.rs,spec_drift.rs,crates/ailang-core/tests/carve_out_inventory.rs: remove theTerm::Loop → "loop"/Term::Recur → "recur"anchors and the it.2 carve-out additions; recompute theEXPECTEDinventory count back to its1ff7e81value. - Surface (
crates/ailang-surface).parse.rs:parse_loop/parse_recur, the dispatch arms, the keyword-list entries, the EBNF block.print.rs: theTerm::Loop/Term::Recurarms. - Prose (
crates/ailang-prose/src/lib.rs). The read + rebuild arms forTerm::Loop/Term::Recur. - Form-A grammar (
crates/ailang-core/specs/form_a.md). Theloop/recurgrammar entries. - Check (
crates/ailang-check/src/lib.rs).Term::Looptyping,Term::Recurunification, theloop_stackthreading,verify_loop_body; the wholeverify_structural_recursionpass (it.2, ~772 lines) includingterm_contains_loop, theDivergeinjection at theFnDef/lam-arrow/LetReceffect- reconciliation sites, thetail==falsegrandfather and the no-ADT-candidate skip.verify_tail_positionsis restored to its pure pre-it.1tail-approle (it.1 repurposed it to also enforcerecur-in-tail-position; that addition is removed — the function itself stays, only the it.1 arms are deleted). Walker arms forTerm::Loop/Term::Recurremoved fromdesugar.rs,lift.rs,mono.rs,linearity.rs,uniqueness.rs,reuse_shape.rs,pre_desugar_validation.rs,builtins.rs(~25 sites, the it.1 blast radius in reverse). CheckError(crates/ailang-check). Remove the five variantsRecurOutsideLoop,RecurArityMismatch,RecurTypeMismatch,RecurNotInTailPosition,NonStructuralRecursion, including theircode()arms and theirctx()arms. Thectx()removal is not uniform (the original draft wrongly said noctx()arms exist — corrected here after Step-7.5 grounding):RecurArityMismatch,RecurTypeMismatch, andNonStructuralRecursioneach carry a dedicatedctx()arm (crates/ailang-check/src/lib.rs~870–878) which must be deleted;RecurOutsideLoopandRecurNotInTailPositionfall into the_ =>catch-all (lib.rs~879) and need noctx()edit. The diagnostic-code registry isCheckError::code()itself; the//!"Stable codes" doc-list incrates/ailang-check/src/diagnostic.rsends at mq.3 and never gainedloop/recur/non-structural-recursionentries (it.1 already proved the "add codes to a doc-list" premise false — see project memoryfeedback_plan_pseudo_vs_reality), so there is nothing indiagnostic.rsfor the revert to touch.- Codegen (
crates/ailang-codegen).lib.rs: the loop-header basic-block + per-binderphilowering, theTerm::Recurback-edgebr, and the new parallelblock_terminatedsetter it.1 added — theblock_terminatedseam returns to being solely tail-driven (emit_call/emit_indirect_call/lower_effect_opontail: true), exactly as at1ff7e81.escape.rs: theTerm::Loop/Term::Recurescape-walk arms.lambda.rs: theloop_framessave/restore.crates/ail/src/main.rs: any it.1 loop wiring.crates/ailang-core/src/workspace.rs: it.1 additions. - CLI / e2e wiring.
crates/ail/tests/e2e.rs,crates/ail/tests/codegen_import_map_fallback_pin.rs,crates/ailang-check/tests/loop_recur_pin.rs,crates/ailang-check/tests/structural_recursion_pin.rs: the it.1 and it.2 pin files are deleted (they pin the removed surface).
DESIGN.md
Handling rule (forestalls the obvious failure mode). DESIGN.md is edited in place, hunk by hunk — it is never wholesale- restored to
git show 1ff7e81:docs/DESIGN.md, because the1ff7e81DESIGN.md does not contain clause 3 (clause 3 landed with the milestone in9973546and is explicitly KEPT). Only the two named hunks below are reverted, using1ff7e81as the per-hunk byte oracle for those hunks only.
- Decision 3 restored to its pre-
9973546placeholder wording (Divergeis nominal/wired-up-only, not "the real effect ofloop-bearing code"). Use the Decision-3 section ofgit show 1ff7e81:docs/DESIGN.mdas the per-hunk byte oracle — diff-and-apply that one section, do not touch the rest of the file. - The §"Data model"
loop/recurentries removed. - Decision 8 is untouched — it.3 never ran, so
tail-app/ Decision 8 are already exactly in their pre-milestone state. - Feature-acceptance criterion clause 3 is kept (the
9973546DESIGN.md hunk for clause 3 stays). One caveat: the clause-3 canonical worked example added in9973546uses "the iteration story" (structural recursion + namedloop/recurpassing all three clauses) as its illustration. After the revert there is noloop/recurin the language, so any sentence that reads as "we shipped X" must be rephrased to "consider X" / hypothetical-illustration form. The example is retained as a teaching illustration of the criterion (it does not depend on the machinery existing); it is only de-claimed from "shipped" to "illustrative". This is the one prose edit inside the kept clause-3 block; the brainstorm SKILL.md Step-4 wording that points at it needs the same de-claiming pass. - Add the F1/F4 note (the only net addition). A short, honest
paragraph in the DESIGN.md §"Local mutable state" / iteration
vicinity: the accumulator-over-an-iteration shape from the
mut-local motivation is not achievable by
mutalone; until a future iteration-totality milestone (gated behindNat/refinement types) lands, the canonical pattern for that shape is the tail-recursive accumulator-parameter helper, andexamples/mut_counter.ailis the reference. Phrased explicitly as a documented idiom, not an enforced rule (gate-honest per the criterion's "a documentation note is not a reshape"). This discharges F1 and F4 by the fieldtester's own named minimal action.
Fixtures
- Delete (it.1 / it.2 net-new):
examples/loop_counter.ail,examples/loop_in_lambda_e2e.ail,examples/loop_nested_in_lambda.ail,examples/loop_smoke.ail,examples/loop_needs_diverge.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_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,examples/test_loop_missing_diverge.ail.json,examples/test_non_structural_recursion.ail.json,examples/test_mutual_cross_family.ail.json. (The planner's recon re-derives the exact file set offgit diff --stat 1ff7e81 HEAD -- examples/; this list is the spec's best enumeration, not a substitute for that diff.) - Restore (pre-milestone fixtures that it.2 modified — must be
reverted to content, not deleted):
examples/rc_pin_recurse_implicit.ail,examples/rc_let_alias_implicit_param.ail. Byte oracle:git show 1ff7e81:examples/<file>.examples/mut_counter.ail/examples/mut_sum_floats.ailwere not migrated by it.1/it.2 (migration was the never-run it.3) — verify they are already at their1ff7e81content and leave them. - Delete
bench/it3-oracle/in its entirety (40.outfiles +MANIFEST.tsv) — the it.3 Task-1 deliverable, dead weight once the milestone is reverted. Forward delete (the revert commit movesmainforward; nothing is rewound).
Working tree / docs hygiene
- Discard the uncommitted A1 amendment to
docs/specs/0031-iteration-discipline.md(git checkout -- docs/specs/0031-iteration-discipline.md), then add a superseded header to that committed spec pointing to this one (specs are per-milestone historical records — superseded, not deleted). The it.1/it.2/it.3 plans underdocs/plans/and the it.1/it.2/it.3 journals stay as historical record (append-only discipline; they record what happened). - Roadmap (
docs/roadmap.md). Remove the Iteration-discipline P1 milestone entry and the "blocking fork" section. Add one deferred entry:[idea]/[milestone]"Iteration-totality story — structural + Int-bounded total recursion with enforced non-negativity",depends on:a futureNat/refinement-types milestone,context:this spec + the it.3 journal (so the genuine ambition and why it was deferred are not lost). Stateful-islands and the F3 P2 todo are left exactly as they are.
Data flow
After the revert the pipeline is byte-for-byte the 1ff7e81 path:
.ail → ailang_surface::parse → JSON-AST (no Term::Loop /
Term::Recur node exists) → ailang-check: typecheck → the
verify_tail_positions pass in its pre-it.1 tail-app-only role →
effect reconciliation without any Diverge-injection (no
guardedness pass at all) → desugar / lift / mono (walkers with no
Loop/Recur arms) → ailang-codegen (no loop-header path; the
block_terminated seam is solely tail: true-driven) → LLVM text →
clang -O2. There is no new back-edge class; the only iteration in
generated IR is tail-call / structural recursion exactly as before
the milestone.
Error handling
No new diagnostics — the revert removes six CheckError variants
(five Recur* + NonStructuralRecursion), their code() arms, and
the three dedicated ctx() arms for RecurArityMismatch /
RecurTypeMismatch / NonStructuralRecursion (the other two
Recur* variants use the ctx() catch-all and need no edit — see
§Components/CheckError for the exact split). UndeclaredEffect
returns to its pre-milestone behaviour (no Diverge antecedent
path). verify_tail_positions emits exactly the
diagnostics it emitted at 1ff7e81 (the tail-app tail-position
checks). The schema-drift / diagnostic-code surface is validated by
the existing drift tests once they are reverted to their 1ff7e81
anchor set.
Testing strategy
- Revert-completeness pins (RED → GREEN for the revert).
crates/ailang-core/tests/design_schema_drift.rspasses with noloop/recuranchors and with the1ff7e81anchor set. This is the structural proof the AST/schema surface is fully reverted.structural_recursion_pin.rsandloop_recur_pin.rsare deleted; their absence + a greencargo test --workspaceis the proof the pinned surface is gone (no orphan references).carve_out_inventoryEXPECTEDequals its1ff7e81value.
- Behavioural-equivalence pins (the correctness gate). Every
example fixture that existed at
1ff7e81and still exists mustail checkandail runto byte-identical stdout vs. its1ff7e81behaviour — in particular the two restored fixtures (rc_pin_recurse_implicit.ail,rc_let_alias_implicit_param.ail) and the RC-regression guards they exist for. This proves the revert reintroduced no behavioural drift. Drive it off a corpusail check/ail runsweep, diffing againstgit show 1ff7e81:behaviour (the planner scripts the exact comparison; thebench/it3-oracle/snapshots are not the oracle — they are deleted;1ff7e81is the oracle). - Doc-presence anchor. A test (mirroring the existing DESIGN.md drift-anchor mechanism) asserts the F1/F4 note is present in DESIGN.md, so a future careless edit cannot silently drop the documented fallback the fieldtest finding required.
- Full suite.
cargo test --workspacegreen. Milestone-closeauditruns the three bench regression scripts; the baseline is expected pristine (the revert returns codegen to the1ff7e81path — any non-pristine result is a revert defect, not a ratifiable delta).
Acceptance criteria
Term::Loop/Term::Recur/LoopBinder/verify_structural_ recursion/term_contains_loop/ theDiverge-injection / the fiveRecur*+NonStructuralRecursionCheckErrorvariants / the loop codegen path / the it.1 parallelblock_terminatedsetter are all absent;verify_tail_positionsis byte-equivalent to its1ff7e81role.design_schema_drift.rsgreen with the1ff7e81anchor set and noloop/recuranchors;structural_recursion_pin.rs/loop_recur_pin.rsdeleted;cargo test --workspacegreen.- DESIGN.md Decision 3 is back to its placeholder wording; the §"Data model" loop/recur entries are gone; Decision 8 is unchanged; feature-acceptance clause 3 is retained, with its worked example de-claimed from "shipped" to "illustrative" (and the matching brainstorm SKILL.md wording de-claimed in lockstep).
- The F1/F4 documentation note is present in DESIGN.md, phrased as a
documented idiom (not an enforced rule), naming the tail-recursive
accumulator-parameter fallback and
examples/mut_counter.ail; a doc-presence test guards it. - Every
1ff7e81-era fixtureail check+ail runbyte-identical to its1ff7e81behaviour;rc_pin_recurse_implicit.ail/rc_let_alias_implicit_param.ailrestored to1ff7e81content;bench/it3-oracle/deleted. - The uncommitted A1 spec amendment is discarded; the
2026-05-15-iteration-discipline.mdspec carries a superseded header pointing here; the it.1/it.2/it.3 journals and plans remain as historical record. docs/roadmap.md: the Iteration-discipline milestone entry and the blocking-fork section removed; one deferred entry added for the iteration-totality ambition,depends on:a futureNat/ refinement milestone; Stateful-islands and the F3 P2 todo untouched.- Milestone-close
audit: bench baseline pristine (not ratified — pristine), drift review clean against the restored DESIGN.md. - Net effect: AILang's iteration story is exactly the pre-
9973546state (structural / tail recursion;tail-appintact), plus one honest documentation note, plus a retained-and-de-claimed feature-acceptance clause 3. The genuine total-Int-recursion ambition is preserved on the roadmap, correctly sequenced behind the type machinery it needs, not diluted to fit today.