iter loop-recur.tidy GREEN: reject lambda-captures-loop-binder at check + milestone-close audit resolution
GREEN side of the RED audit-trail commit 39380d3. The loop/recur
milestone-close audit found a [high] correctness defect — a lambda
capturing a loop binder passed `ail check` then panicked
unreachable!() at codegen (crates/ailang-codegen/src/lambda.rs:102)
on type-correct input. Fixed symmetrically to mut.4-tidy: new
CheckError::LoopBinderCapturedByLambda (code
loop-binder-captured-by-lambda, bracket-free F2 Display), the
Term::Lam escape guard gained a parallel loop_stack pass. Minimal
mechanism: loop_stack element Vec<Type> -> Vec<(String,Type)> so
the already-threaded per-loop frame carries binder names; recur
still reads .1 by position (Boss-call-2 positional invariant
preserved verbatim — the name is a second field for the escape
guard only, a consumer iter-2 did not anticipate). Codegen
byte-unchanged (typecheck-only fix). carve_out 17->18 + ct1-F2 +
DESIGN.md note lockstep.
Folded in (Boss-side, audit resolution): the two [medium]
doc-honesty edits the audit surfaced (mut_var_allocas rustdoc now
states its mut-var+loop-binder dual use; Term::Loop doc-comment
now describes the real shipped state, not iter-1's stale
"per-binder phi" forward-look) + a [low] P2 roadmap todo
(plan-recon undercount countermeasure, pairs with planner Step-5
items 7+8). Bench gate: all 3 scripts exit 0, 25 metrics 0
regressed — pristine, carry-on (no baseline/ratify).
cargo test --workspace 619 -> 622 / 0 red (Boss-reran
independently, hash_pin 11/0). The loop/recur milestone is
audit-clean; fieldtest is the only remaining step before close.
This commit is contained in:
+4
-1
@@ -2451,7 +2451,10 @@ them as entry-block allocas, and `Term::Assign` emits a `store`
|
||||
yielding the canonical Unit SSA. Mut-vars must be of one of the
|
||||
stack-resident scalar types `{Int, Float, Bool, Unit}`; capturing a
|
||||
mut-var into a lambda body is rejected at typecheck via
|
||||
`CheckError::MutVarCapturedByLambda` (iter mut.4-tidy). See
|
||||
`CheckError::MutVarCapturedByLambda` (iter mut.4-tidy). Loop binders
|
||||
are alloca-resident under the same scheme, so capturing a `loop`
|
||||
binder into a lambda body is rejected by the symmetric
|
||||
`CheckError::LoopBinderCapturedByLambda` (iter loop-recur.tidy). See
|
||||
`docs/specs/2026-05-15-mut-local.md`.
|
||||
|
||||
**`Literal`**:
|
||||
|
||||
@@ -0,0 +1,133 @@
|
||||
# iter loop-recur.tidy — lambda capturing a loop binder must fail `ail check`, not panic codegen
|
||||
|
||||
**Date:** 2026-05-18
|
||||
**Started from:** 39380d361d530a95a4f1c718c0e201f8312d3ac4
|
||||
**Status:** DONE
|
||||
**Tasks completed:** 1 of 1
|
||||
|
||||
## Summary
|
||||
|
||||
A `Term::Lam` whose body captured an enclosing `Term::Loop` binder
|
||||
passed `ail check` (exit 0) and then panicked `unreachable!()` at
|
||||
`crates/ailang-codegen/src/lambda.rs:102` — type-correct input
|
||||
crashing the compiler. This iter adds the symmetric escape guard:
|
||||
such a capture now fails `ail check` with the stable kebab code
|
||||
`loop-binder-captured-by-lambda`, exactly as mut.4-tidy did for
|
||||
`mut-var-captured-by-lambda`. The fix is the minimal mechanism that
|
||||
reuses the scope structure the `Term::Loop` arm already maintains.
|
||||
|
||||
## Per-task notes
|
||||
|
||||
- iter loop-recur.tidy.1: GREEN side of the debug RED test
|
||||
`lambda_capturing_loop_binder_emits_loop_binder_captured_by_lambda`.
|
||||
Added `CheckError::LoopBinderCapturedByLambda { name }`
|
||||
(bracket-free F2 Display), its `code()` arm
|
||||
(`loop-binder-captured-by-lambda`), and its `ctx()` arm
|
||||
(`{name}`, mirroring `MutVarCapturedByLambda`). Extended
|
||||
`loop_stack`'s element type from `Vec<Type>` to
|
||||
`Vec<(String, Type)>` so the existing per-loop scope frame also
|
||||
carries binder names (the `Term::Recur` arm reads `.1` for the
|
||||
positional type check; the `Term::Lam` escape guard reads `.0`).
|
||||
The `Term::Lam` arm's existing free-var scan (shared
|
||||
`free_vars_in_term` walker) gained a parallel pass over
|
||||
`loop_stack` next to the `mut_scope_stack` pass; its gate widened
|
||||
to `!mut_scope_stack.is_empty() || !loop_stack.is_empty()`.
|
||||
Mirrored the two mut.4-tidy in-source unit tests
|
||||
(kebab-code test + synth-level capturing test). Lockstep:
|
||||
`carve_out_inventory.rs` 17→18 (header count word + new bullet +
|
||||
EXPECTED entry), `ct1_check_cli.rs` mut-F2 sibling `cases` gained
|
||||
the new fixture (where `mut-var-captured-by-lambda` is asserted —
|
||||
the exact precedent). `docs/DESIGN.md` one-line note adjacent to
|
||||
the `MutVarCapturedByLambda` mention.
|
||||
|
||||
## Root cause
|
||||
|
||||
The `Term::Lam` escape guard iterated only `mut_scope_stack` to
|
||||
reject lambda captures of alloca-resident locals. Loop binders are
|
||||
inserted into the ordinary `locals` by the `Term::Loop` arm and
|
||||
never entered any escape-checked scope, so a lambda body referencing
|
||||
a loop binder was accepted by `ail check`. Codegen's lambda capture
|
||||
resolver searches only `self.locals` after `mut_var_allocas` is
|
||||
`std::mem::take`-emptied at the lambda frame boundary, so the
|
||||
loop-binder reference resolved to nothing and hit `unreachable!()`.
|
||||
Loop binders are alloca-resident under the same scheme as mut-vars
|
||||
and obey the same no-escape-into-lambda rule.
|
||||
|
||||
## Design note — why extend `loop_stack` rather than add a parallel stack
|
||||
|
||||
The carrier offered "reuse whatever scope structure the `Term::Loop`
|
||||
arm already maintains" as an explicit minimal option. `loop_stack`
|
||||
is already threaded through every `synth` recursive call and is
|
||||
pushed/popped at exactly the loop scope boundary. Extending its
|
||||
frame element from `Type` to `(String, Type)` preserves all existing
|
||||
push/pop discipline and `Term::Recur` positional-type semantics
|
||||
unchanged — it is not a redesign. The alternative (a parallel
|
||||
`mut_scope_stack`-shaped parameter) would have touched all 35
|
||||
recursive `synth` call sites plus declarations, strictly larger.
|
||||
Element-type extension is the minimal correct mechanism.
|
||||
|
||||
## Concerns
|
||||
|
||||
(none)
|
||||
|
||||
## Known debt
|
||||
|
||||
(none)
|
||||
|
||||
## Files touched
|
||||
|
||||
- `crates/ailang-check/src/lib.rs` — variant, code/ctx/Display, the
|
||||
`Term::Loop` push + `Term::Recur` read + `Term::Lam` escape guard,
|
||||
two in-source unit tests
|
||||
- `crates/ailang-check/src/builtins.rs` — `loop_stack` decl type
|
||||
- `crates/ailang-check/src/lift.rs` — `loop_stack` decl type
|
||||
- `crates/ailang-check/src/mono.rs` — `loop_stack` decl type (x2)
|
||||
- `crates/ailang-core/tests/carve_out_inventory.rs` — 17→18 lockstep
|
||||
- `crates/ail/tests/ct1_check_cli.rs` — mut-F2 sibling `cases`
|
||||
- `docs/DESIGN.md` — one-line symmetric-rule note
|
||||
|
||||
## Milestone-close audit resolution (Boss-side)
|
||||
|
||||
The loop/recur milestone-close `audit` (architect drift review +
|
||||
bench gate) produced four items; this tidy commit resolves all:
|
||||
|
||||
- **`[high]` lambda-captures-loop-binder codegen crash** — fixed by
|
||||
this iter's GREEN (the symmetric `loop-binder-captured-by-lambda`
|
||||
guard). RED test committed separately as the audit trail
|
||||
(`39380d3`); GREEN + the items below fold into this commit.
|
||||
- **`[medium]` `mut_var_allocas` rustdoc lied about dual use** —
|
||||
`crates/ailang-codegen/src/lib.rs` rustdoc said "Populated when
|
||||
entering a `Term::Mut` block" / "matching the typecheck-side
|
||||
mut-scope-stack precedence"; as of iter-3 loop binders ride the
|
||||
same map. Boss-edited to present-tense dual-use truth (mut-vars
|
||||
AND loop binders; the shared map is a representation detail, not
|
||||
a scope-precedence claim). Doc-honesty class.
|
||||
- **`[medium]` `Term::Loop` doc-comment stale "per-binder phi"** —
|
||||
`crates/ailang-core/src/ast.rs` still described iter-1's
|
||||
forward-looking `synth`/`lower_term` `Internal` stubs + "per-binder
|
||||
phi"; iters 2/3 shipped real typecheck + alloca+mem2reg lowering
|
||||
(Boss-call-1 explicitly rejected hand-emitted phi). Boss-edited to
|
||||
the real shipped state. Doc-honesty class.
|
||||
- **`[low]` recon-undercount 3× pattern** — added a P2 `[todo]` to
|
||||
`docs/roadmap.md` (`ailang-plan-recon` cross-crate-caller-undercount
|
||||
countermeasure; pairs with the planner Step-5 items 7+8 added this
|
||||
milestone — those scrub the plan, this scrubs the recon). Not a
|
||||
milestone-blocking fix; tracked forward.
|
||||
- **Bench gate (audit Step 2)** — `bench/check.py` /
|
||||
`compile_check.py` / `cross_lang.py` all exit 0; 25 metrics, 0
|
||||
regressed, 25 stable. **Pristine** — exactly as the spec's
|
||||
acceptance criteria expect for a strictly-additive surface no
|
||||
hot-path bench exercises; the pre-existing P2 `*.bump_s`
|
||||
hardware-staleness drift did not trip this run. **carry-on**: no
|
||||
baseline update, no ratify entry (Iron Law — nothing intentionally
|
||||
moved a metric).
|
||||
|
||||
**Milestone-loop-recur tidy: resolved.** Architect drift cleared
|
||||
(the `[high]` is fixed code, the two `[medium]` are doc-honest, the
|
||||
`[low]` is roadmap-tracked); bench pristine carry-on. The loop/recur
|
||||
milestone is audit-clean; the only remaining pipeline step before
|
||||
full close is `fieldtest` (user-visible Form-A surface).
|
||||
|
||||
## Stats
|
||||
|
||||
bench/orchestrator-stats/2026-05-17-iter-loop-recur.tidy.json
|
||||
@@ -84,3 +84,4 @@
|
||||
- 2026-05-17 — iter loop-recur.1: standalone-`loop`/`recur` milestone (1 of 3) — the strictly-additive AST-node foundation. `Term::Loop { binders: Vec<LoopBinder>, body }` / `Term::Recur { args }` / `struct LoopBinder` (mirrors `MutVar`'s `(name,type,init)` triple; `binders` no `skip_serializing_if`) added end-to-end across all six crates' no-`_`-wildcard exhaustive `Term` matches (~30 sites incl. one recon-undercount integration-test pin): Form-A `parse_loop`/`parse_recur` (binder/body boundary disambiguated by an `is_term_head_kw` guard — the plan's flagged sole parser judgement) + print + `form_a.md` grammar/notes + prose render/free-var/subst lockstep + canonical-JSON serde/round-trip + DESIGN.md §Data-model `"t":"loop"`/`"t":"recur"` blocks + design_schema_drift/spec_drift/schema_coverage anchors + a new `loop_recur` hash pin (additivity proven: iter13a + new pin both green, pre-existing canonical-JSON bytes byte-stable) + `examples/loop_sum_to.ail` round-trip fixture. NO typecheck semantics + NO real codegen by design: the two semantic dispatch points stubbed `synth`→`CheckError::Internal` / `lower_term`→`CodegenError::Internal` exactly as mut.1; pure analysis walkers (escape/lambda/`synth_with_extras`) get real structural pass-through. Two binding Boss design calls (mirrored into the journal): (1) `verify_tail_positions` "byte-unchanged" = tail-app *role* unchanged not source-frozen — it is an exhaustive no-wildcard match so two additive descent-only arms are mandatory; acceptance evidence is the tail-app non-regression test (all `tail`-filtered tests byte-identical), mut.1 set the precedent; (2) codegen IS iter-1 scope as stubs/pass-throughs (no real loop-header/phi/back-edge — that is iter 3). Three plan-pseudo-vs-reality substitutions, all intent-preserving, recurring `feedback_specs_need_concrete_code`/`feedback_plan_pseudo_vs_reality` class: serde `Type::Con` literal `{"t":"con",…,"args":[]}`→real `{"k":"con","name":"Int"}`; bare `Int`→`(con Int)` in binder triples (bare `Int` parses as `Type::Var`); `ailang_core::ast::LoopBinder`→unqualified inside ailang-core. Boss forward-fix folded in: Concern 2 surfaced that the committed specs themselves (`2026-05-17-loop-recur.md` headline + `bad_recur`, `2026-05-17-llm-surface-discipline.md` §5) wrote bare `Int` — corrected all three snippets to `(con Int)` (doc-honesty, same class as the mono.rs header; no design/schema/assumption change, no re-brainstorm). `cargo test --workspace` 600→608 / 0 red (Boss-reran independently). Component 4 (typecheck) = iter 2; Component 5 (codegen) + positive/deep-`n` E2E = iter 3 → 2026-05-17-iter-loop-recur.1.md
|
||||
- 2026-05-17 — iter loop-recur.2: standalone-`loop`/`recur` milestone (2 of 3) — Component 4 typecheck semantics. The iter-1 `synth` `CheckError::Internal` stub for `Term::Loop`/`Term::Recur` is replaced with real binder typing (each init synthed in outer scope + already-declared binder names of THIS loop via ordinary `locals` save/restore mirroring `Term::Let` verbatim; loop's static type = body type) + positional `recur` arity/per-arg-type checking via a new `loop_stack: &mut Vec<Vec<Type>>` frame threaded exactly as mut.2's `mut_scope_stack` (every recursive + cross-module + test synth caller, compile-swept). New private `verify_loop_body(t,in_loop_tail)` pass — a SIBLING of the byte-frozen `verify_tail_positions` (0 deletions, tail-app role untouched), invoked in `check_fn` after it so synth-raised codes take precedence by pass ordering. Four `Recur*` `CheckError` variants (`RecurOutsideLoop`, `RecurArityMismatch{expected,got}`, `RecurTypeMismatch{position,expected,got}`, `RecurNotInTailPosition`) + `code()` + 2 `ctx()` arms, bracket-`[code]`-free Display (F2); four negative `.ail.json` fixtures fire their codes point-exactly (`assert_eq!`, non-vacuous) via a new `loop_recur_typecheck_pin.rs` (7 tests) + a ct1 F2 human-mode sibling; `carve_out_inventory` 13→17 with the pre-existing mut.4-tidy "Twelve"-vs-13 stale-header drift corrected in passing. iter-1's `loop_sum_to.ail` round-trip fixture now ALSO typechecks clean (one fixture, two properties — no near-duplicate); `loop_forever.ail` (loop whose only path is `recur`) typechecks clean, asserting the spec "no termination claim is made or enforced". Three binding Boss design calls (mirrored into the journal): (1) `RecurTypeMismatch` is an Assign-style `subst.apply`-both-then-structural-`!=` pre-check, NOT a `unify`-propagate (a propagate would surface generic `type-mismatch` and fail the point-exact-code acceptance); (2) `loop_stack` element type is positional `Vec<Type>` NOT name-keyed `IndexMap` (recur rebinds by position; binder names live in `locals`); (3) diagnostic-code precedence is by pass ordering (synth before verify_loop_body), no explicit logic. NO codegen (iter-1 `lower_term` `CodegenError::Internal` stub stays — iter 3); NO `Diverge`/`verify_structural_recursion`/guardedness (spec deliberate boundary). Two DONE_WITH_CONCERNS, both Task 2, both journalled: (a) a plan-ordering defect — Task 2's `cargo build` 0-errors gate is unsatisfiable while `check_fn` (deferred by the plan to Task 4) is an unthreaded caller; orchestrator pulled the byte-identical declaration+threading forward to Task 2, left only the `verify_loop_body` invocation in Task 4 (no semantic change, only sequencing); (b) recon-undercount of cross-module synth callers (`builtins.rs`×2, `lift.rs:746`, `mono.rs:720`/`:1361`), the recurring mut.2-class, resolved via the plan's own designated compile-sweep oracle. Boss systemic fix folded into this commit: planner SKILL.md Step-5 self-review gains item 7 (compile-gate vs. deferred-caller ordering) so the Concern-(a) class is scrubbed at plan time, not discovered at execution. `cargo test --workspace` 608→616 / 0 red (Boss-reran independently); `verify_tail_positions`/tail-app byte-frozen confirmed by diff-hunk inspection. Component 5 (codegen loop-header/phi/back-edge) + the positive RUN-to-value / deep-`n` E2E = iteration 3 → 2026-05-17-iter-loop-recur.2.md
|
||||
- 2026-05-17 — iter loop-recur.3: standalone-`loop`/`recur` milestone (3 of 3, TERMINAL) — Component 5 codegen + run-to-value E2E. The iter-1 `lower_term` `CodegenError::Internal` stub for `Term::Loop`/`Term::Recur` is replaced with real LLVM-IR lowering: loop binders are loop-carried values lowered as entry-block allocas (the mut.3 `pending_entry_allocas` mechanism) registered in the EXISTING `mut_var_allocas` map so the EXISTING `Term::Var` load path resolves them with zero new Var code (representation-sharing only — Var-lowering byte-unchanged); a fresh `loop.header.<id>` block reached by an unconditional `br` from the pre-header; `recur` lowers ALL args to SSA BEFORE any store (simultaneous positional rebind), stores into the binder allocas, back-edges `br` to the header, and sets the SINGLE existing `block_terminated` field at its OWN new emit site (a parallel SET call-site) so the loop's exit value is the emergent product of the existing `if`/`match` join — no separate loop-result phi/exit-block. A new `loop_frames` codegen stack lets `recur` find its target header+slots; saved/reset/restored at the single lambda-lowering boundary exactly as mut.3's `mut_var_allocas` triple. `clang -O2` mem2reg promotes the binder allocas to the phi nodes the spec's secondary implementation-shape describes. Four binding Boss design calls (mirrored into the journal), all on architectural-consistency / spec-pinned-invariant grounds (NOT effort): (1) alloca+mem2reg NOT hand-emitted phi (the linear-emit architecture has no phi-with-body-discovered-back-edge-predecessor precedent; the `if` arm sidesteps by opening its join last — a loop header cannot be opened last; mem2reg yields identical optimized output; spec's "phi" is the explicitly-secondary impl-shape the spec subordinates to the planner's exact-bytes authority); (2) reuse `mut_var_allocas` + the existing `Term::Var` load path (byte-unchanged; representation-sharing, the iter-2 AST/typecheck binder distinction is post-typecheck-irrelevant to codegen); (3) emergent loop-exit via the existing if/match join once recur terminates its block, no separate result phi; (4) reuse the single `block_terminated` field, recur sets it at its own emit site, ZERO edits to any existing SET/READ site (a second field would force editing every READ site — the opposite of the spec-pinned "tail-app's use byte-unchanged"). Diff confirmed surgical by hunk-header inspection: codegen/lib.rs = exactly 3 hunks (field + init + the stub→2-arms replacement), codegen/lambda.rs = exactly 2 hunks (save + restore); NO hunk at any existing `block_terminated` SET/READ site, at tail-app/tail-do lowering, or at `verify_tail_positions`. Three new `.ail` fixtures: `loop_sum_to_run.ail`→`55` (run-to-value), `loop_sum_to_deep.ail`→`500000500000` (1e6 iterations via the back-edge, no stack growth — the spec clause-2 correctness claim made executable), `loop_forever_build.ail` (infinite loop, no non-recur exit, `ail build` exit 0 — "typechecks AND compiles, no termination claim"; never executed). Codegen-ONLY iteration: no schema/AST/serde/typecheck change; hash pins (`loop_recur` + iter13a) + drift trio + `carve_out_inventory` confirmed green UNTOUCHED (not modified); the 3 new `.ail` fixtures are round-trip-auto-covered, NOT carve-outs. One DONE_WITH_CONCERNS (T3): the plan's literal `cargo test --workspace tail` filter resolves to ZERO tests (real guards are `*_tail_position_*`/`*musttail*`); orchestrator ran the gate via real names + the authoritative full-suite 619/0 which subsumes it — recurring `feedback_plan_pseudo_vs_reality` class, no behaviour change. Boss systemic fix folded into this commit: planner SKILL.md Step-5 gains item 8 (verification-command filter strings must resolve to ≥1 named test, or use the unfiltered suite + a count assertion) — the SECOND planner-meta-gap this milestone surfaced (iter-2 added item 7). `cargo test --workspace` 616→619 / 0 red (Boss-reran independently); the 3 loop/recur e2e explicitly green (55 / 500000500000 / infinite-compiles). **All three components shipped — the loop/recur milestone is structurally complete; milestone-close (mandatory `audit`, then `fieldtest` since loop/recur is user-visible Form-A surface) is the Boss's next pipeline step.** → 2026-05-17-iter-loop-recur.3.md
|
||||
- 2026-05-18 — iter loop-recur.tidy: milestone-close audit resolution (RED `39380d3` + this GREEN). Architect drift review found a `[high]` correctness defect: a `(lam ...)` body capturing an enclosing `Term::Loop` binder passed `ail check` (exit 0) then panicked `unreachable!()` at `crates/ailang-codegen/src/lambda.rs:102` on type-correct input — a DESIGN.md "Robustness against hallucinations" violation. Root cause (debugger-confirmed RED-first): the `Term::Lam` escape guard at `crates/ailang-check/src/lib.rs:3608` iterated only `mut_scope_stack`; loop binders live in the ordinary `locals` (iter-2 design) and never entered any escape-checked scope, so the mut.4-tidy `MutVarCapturedByLambda`-class rejection had no loop-binder counterpart. Fix (GREEN, implement mini-mode): new `CheckError::LoopBinderCapturedByLambda { name }` (bracket-`[code]`-free F2 Display) + `code()` `loop-binder-captured-by-lambda` + `ctx() {name}`, symmetric to `MutVarCapturedByLambda`; the `Term::Lam` escape guard gained a parallel pass over `loop_stack` (gate widened to `!mut_scope_stack.is_empty() || !loop_stack.is_empty()`). Minimal-mechanism design call: `loop_stack`'s frame element extended `Vec<Type>` → `Vec<(String, Type)>` so the already-threaded per-loop scope frame also carries binder names (`Term::Recur` still reads `.1` BY POSITION — Boss-call-2's positional-recur invariant preserved verbatim, the name is a second field consumed only by the escape guard, a consumer iter-2 did not anticipate; the parallel-stack alternative would touch all 35 `synth` call sites, strictly larger). Codegen byte-unchanged (typecheck-only fix; the lambda.rs:102 `unreachable!()` is now genuinely unreachable, kept as a defensive assertion). Two mut.4-tidy-mirrored in-source unit tests; lockstep `carve_out_inventory` 17→18 + `ct1_check_cli` mut-F2 sibling `cases`; DESIGN.md one-line symmetric-rule note. Boss folded into this GREEN commit: the two `[medium]` doc-honesty edits the audit also surfaced (`mut_var_allocas` rustdoc now states its mut-var+loop-binder dual use truthfully; `Term::Loop` doc-comment now describes the real shipped iter-2/3 typecheck+alloca+mem2reg state, not iter-1's stale "per-binder phi" stub forward-look) + a `[low]` P2 roadmap `[todo]` (the recon-undercount 3×-pattern → an `ailang-plan-recon` agent-definition countermeasure; pairs with planner Step-5 items 7+8). Bench gate (audit Step 2): `check.py`/`compile_check.py`/`cross_lang.py` all exit 0, 25 metrics 0 regressed — **pristine** (strictly-additive surface, no hot-path; the pre-existing P2 `*.bump_s` hardware-staleness drift did not trip) → carry-on, no baseline/ratify. `cargo test --workspace` 619→622 / 0 red (Boss-reran independently incl. hash_pin 11/0 — the ast.rs/codegen doc-comment edits moved no canonical-JSON hash). **The loop/recur milestone is audit-clean** — architect drift cleared, bench pristine; the only remaining pipeline step before full close is `fieldtest` (loop/recur is user-visible Form-A surface) → 2026-05-17-iter-loop-recur.tidy.md
|
||||
|
||||
+27
-6
@@ -36,12 +36,16 @@ work progresses.
|
||||
## P0 — In flight
|
||||
|
||||
- [~] **\[milestone\]** Standalone `loop` / `recur` — strictly-additive
|
||||
iteration surface lowering to the existing `tail-app` back-edge, with
|
||||
one closed rule (`recur` valid only in tail position of its enclosing
|
||||
`loop`). De-bundled re-attempt of the reverted Iteration-discipline
|
||||
it.1 core only: no totality claim, no guardedness pass, no `Diverge`,
|
||||
`tail-app` byte-unchanged. Spec written + grounding-checked + user-
|
||||
approved 2026-05-17 (97c1ed1).
|
||||
iteration surface, `recur` valid only in tail position. All three
|
||||
iterations + a tidy shipped: iter 1 `a179ec3` (additive AST nodes),
|
||||
iter 2 `1566ce0` (typecheck), iter 3 `edd2558` (codegen + run-to-value
|
||||
E2E), tidy (lambda-captures-loop-binder rejected at check, symmetric
|
||||
to mut.4-tidy). Milestone-close `audit` clean: architect drift
|
||||
resolved (the `[high]` codegen crash fixed in the tidy + two
|
||||
`[medium]` doc-honesty edits folded in), bench pristine (25 metrics,
|
||||
0 regressed — strictly-additive surface, no hot-path touched).
|
||||
**Remaining: `fieldtest`** (loop/recur is user-visible Form-A
|
||||
surface) — the only step before full close.
|
||||
- context: `docs/specs/2026-05-17-loop-recur.md`; principles entry
|
||||
`docs/specs/2026-05-17-llm-surface-discipline.md` §6.2; revert
|
||||
rationale `docs/specs/2026-05-16-iteration-discipline-revert.md`.
|
||||
@@ -254,6 +258,23 @@ work progresses.
|
||||
intra-crate links). All predate the design-md-consolidation
|
||||
milestone; treat as a one-off sweep.
|
||||
- context: JOURNAL 2026-05-10 ("Audit close: design-md-consolidation").
|
||||
- [ ] **\[todo\]** `ailang-plan-recon` cross-crate-caller-undercount
|
||||
countermeasure — the recon agent hand-lists exhaustive-`match`/caller
|
||||
sites and has under-counted the true blast radius **three times in
|
||||
one milestone** (loop-recur.1 walker arms, loop-recur.2 cross-module
|
||||
`synth` callers, loop-recur.tidy implicit). Each was caught only by
|
||||
the implement orchestrator's compile-driven sweep, not by the plan.
|
||||
The pattern is structural: a hand-enumerated site list for a
|
||||
workspace-wide signature/exhaustive-match change is inherently
|
||||
lossy. Tighten `skills/planner/agents/ailang-plan-recon.md` so that
|
||||
for any signature-change / additive-enum-variant scope the recon
|
||||
REPORTS the compile-driven enumeration command as the authoritative
|
||||
site set (and frames its own hand-list as advisory), rather than
|
||||
presenting the hand-list as complete. No language change; an
|
||||
agent-definition discipline fix.
|
||||
- context: loop/recur milestone-close audit (architect `[low]`,
|
||||
2026-05-18); pairs with the planner Step-5 items 7+8 added the
|
||||
same milestone (those scrub the *plan*; this scrubs the *recon*).
|
||||
- [ ] **\[todo\]** `design_schema_drift.rs` fidelity widening —
|
||||
current test checks anchor *presence* anywhere in DESIGN.md;
|
||||
the audit found that `[high]` schema gaps in §"Data model"
|
||||
|
||||
Reference in New Issue
Block a user