audit: prose-loop-binders milestone CLOSE — clean / carry-on
Architect drift review clean (zero drift/debt, diff-verified: single Term::Loop write_term hunk, no non-render site / Term::Recur / doc / lockstep touched). Bench: compile_check 0/24 + cross_lang 0/25 exit 0; check.py's latency-tail firings proven single-sample jitter by an identical confirmatory re-run (p99.9 +37%→+10% ok, max +114%→+22% ok); the sole persistent firing is the pre-tracked P2 *.bump_s environmental staleness, causally impossible to attribute to a projection-only ailang-prose change. All items carry-on — no fix, no baseline ratify. Fieldtest not applicable (no authoring-surface change). Roadmap P0 flipped to closed; WhatsNew user-facing entry appended (user present → notification suppressed per policy).
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
# audit prose-loop-binders — Milestone-prose-loop-binders tidy (clean)
|
||||
|
||||
**Date:** 2026-05-18
|
||||
**Milestone:** prose-loop-binders (single-iteration)
|
||||
**Commit range:** `6cbd0fe..c9355d7` (prev close = loop/recur at `6cbd0fe`)
|
||||
**Status:** CLOSED — clean, carry-on (no fix, no ratify)
|
||||
|
||||
## Step 1 — Architect drift review
|
||||
|
||||
`ailang-architect` over `6cbd0fe..HEAD`: **clean**, zero drift, zero
|
||||
debt. Verified against the diff (not journal trust):
|
||||
|
||||
- Single hunk in `crates/ailang-prose/src/lib.rs` (the `Term::Loop`
|
||||
arm of `write_term`, lines 936–953). The two non-render
|
||||
`Term::Loop` sites (`count_free_var`, `subst_var_with_term`) and
|
||||
all `Term::Recur` arms are absent from the diff — untouched.
|
||||
- DESIGN.md and PROSE_ROUNDTRIP.md make **no** claim about how
|
||||
prose renders `loop` (grep: no match), so the spec's "no doc
|
||||
edit needed" is correct, not a skipped obligation. `DESIGN.md:714`
|
||||
`loop(n-1)` is an ordinary recursive-call illustration in
|
||||
Decision 8, unrelated to the `Term::Loop` construct — not stale.
|
||||
- Diff touches only the four named code/fixture files plus expected
|
||||
docs (spec/plan/journal/INDEX/roadmap/stats). No carve-out /
|
||||
hash-pin / round-trip lockstep bypassed (prose is off the
|
||||
Form-A↔JSON round-trip path by construction).
|
||||
- The `architect_sweeps.sh` Sweep-1 DESIGN.md hits all pre-date
|
||||
`6cbd0fe` and are outside this milestone's diff (zero DESIGN.md
|
||||
lines changed) — flagged for the record, not attributable here.
|
||||
|
||||
Architect recommendation: carry on as planned.
|
||||
|
||||
## Step 2 — Bench-regression check
|
||||
|
||||
- `bench/compile_check.py` → **exit 0**, 24 metrics, 0 regressed.
|
||||
- `bench/cross_lang.py` → **exit 0**, 25 metrics, 0 regressed.
|
||||
Notably its independent `bench_list_sum.ail_bump_s` reads +1.50%
|
||||
(±15% tol) → **ok** — corroborates that check.py's bump_s firing
|
||||
is baseline-staleness, not a real bump regression.
|
||||
- `bench/check.py` → exit 1. First run: 3 regressed
|
||||
(`bench_list_sum.bump_s` +13.17%; `latency.implicit_at_rc.p99_9_us`
|
||||
+37.30%; `latency.implicit_at_rc.max_us` +114.14%), 4 improved,
|
||||
56 stable. Confirmatory identical re-run (no code change):
|
||||
`p99_9_us` +10.22% → **ok**, `max_us` +22.17% → **ok** (median
|
||||
and p99 within tol on both runs) — the two latency-tail firings
|
||||
were single-sample 5-run RC-mode jitter, proven by re-run.
|
||||
`bench_list_sum.bump_s` persisted at +11.79% (consistent with
|
||||
the +13.17% first run): summary `63 metrics; 1 regressed`.
|
||||
|
||||
## Step 3 — Classification
|
||||
|
||||
| Item | Class | Rationale |
|
||||
|------|-------|-----------|
|
||||
| Architect drift | carry-on | clean — nothing actionable |
|
||||
| `throughput.bench_list_sum.bump_s` +~12% | carry-on | The already-tracked P2 roadmap todo "`*.bump_s` throughput baseline is stale vs current hardware" — byte-oracle-proven environmental (not codegen) at the 2026-05-16 iteration-discipline-revert audit. Causally impossible to attribute to a projection-only `ailang-prose` change (prose is not in the bench runtime path; architect verified the diff is one render-arm + test fixtures). |
|
||||
| `latency.implicit_at_rc` p99.9 / max | carry-on | Single-sample 5-run RC-mode tail jitter; vanished on identical re-run with median/p99 always within tolerance. Not a regression; the queued P3 "latency methodology rework" (histogram-based) exists precisely to de-noise this metric family. |
|
||||
|
||||
## Step 4 — Resolution
|
||||
|
||||
**Carry-on** on every item. No fix iteration, no baseline ratify.
|
||||
|
||||
Explicitly **not ratified**: the `*.bump_s` baseline is NOT bumped
|
||||
here. The P2 roadmap todo scopes a holistic recapture+recalibration
|
||||
of the entire `*.bump_s` set on current hardware from a known-clean
|
||||
commit as a dedicated bench-harness iteration; opportunistically
|
||||
bumping one metric's baseline inside an unrelated projection-only
|
||||
milestone is exactly the "bump the baseline, the regression is
|
||||
expected" rationalisation the audit Iron Law forbids, and would
|
||||
fragment the recapture the P2 todo owns. The pointer stands; the
|
||||
todo is unchanged.
|
||||
|
||||
## Fieldtest applicability
|
||||
|
||||
**Not applicable / not dispatched.** `fieldtest` is Boss-dispatched
|
||||
for a milestone that touched the *authoring* surface — the
|
||||
fieldtester writes `.ail` Form-A programs and runs them through the
|
||||
public `ail` CLI. This milestone changed only the Form-B *reading*
|
||||
projection (`ail prose` output); the Form-A authoring surface, the
|
||||
typechecker, codegen, and runtime are byte-unchanged. A fieldtester
|
||||
authoring `.ail` programs would not exercise the prose-render change
|
||||
at all — there is no friction/bug/spec-gap axis for it to probe.
|
||||
The two committed whole-file byte-equality `.prose.txt` snapshots +
|
||||
the live-CLI `ail prose | diff` acceptance checks (iter journal) are
|
||||
the end-to-end gate for a projection-only milestone.
|
||||
|
||||
## Milestone close
|
||||
|
||||
The **prose-loop-binders** milestone is **CLOSED, clean**. Architect
|
||||
drift clear; bench carry-on (sole persistent firing is the tracked
|
||||
P2 `*.bump_s` environmental staleness, latency-tail proven sampling
|
||||
noise by re-run). Roadmap P0 entry flipped to closed; no follow-up
|
||||
iteration spawned.
|
||||
@@ -87,3 +87,4 @@
|
||||
- 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
|
||||
- 2026-05-18 — fieldtest loop-recur (milestone CLOSE, clean on all milestone axes): post-audit downstream-LLM-author field test of the shipped loop/recur surface. The fieldtester (DESIGN.md + public examples only, never compiler source) wrote 3 real iterative programs — integer Newton sqrt (multi-binder, recur buried in if/let/if, loop result into let/seq), Collatz length (recur in match-arm tails), Euclidean gcd (loop as a value sub-expression in argument position) — plus 5 plausible-mistake negatives and 2 no-termination probes, all run through the public `ail` CLI. **0 bugs; 4 working findings, all on the milestone's own axes**: the five rejection diagnostics (recur-outside-loop / -arity / -type / -not-in-tail / loop-binder-captured-by-lambda) are point-exact AND self-fixing (name the fn, describe the fix, no false positives, no crash — the clause-2 silent-failure-class-becomes-compile-error claim made real); recur tail-position threads correctly through match-arm/let/outer-if (the spec only demonstrated `if` — generalises exactly as an author assumes); loop composes as a value sub-expression everywhere + every loop/recur fixture is `ail parse|render|parse` byte-identical (Roundtrip Invariant holds in practice); the no-termination boundary is exactly as specified (infinite loop check+build clean, zero spurious diagnostics — the precise difference from the reverted Iteration-discipline milestone). This empirically substantiates the milestone's "an LLM author can now write iterative programs with loop/recur" claim — the gate fieldtest exists to provide. Two ORTHOGONAL non-blocking findings surfaced incidentally while probing the no-termination/negative axes, neither in loop/recur scope, both routed to P2 todos (not a loop/recur tidy — refusing the scope creep): (spec_gap) niladic `(app f)` is unrepresentable in Form A and DESIGN.md's `Term::App args:[Term...]` states no minimum — INDEPENDENTLY re-confirms the existing mut-local-F3 roadmap todo (two fieldtests now hit it; the accept-`(app f)`-vs-ratify-DESIGN.md decision is a genuine design fork deliberately NOT auto-ratified under autonomous /boss — parked, priority-strengthened); (friction) the module-level `(doc …)` rejection lists valid def heads but omits that doc attaches inside fn/data — a one-line diagnostic-hint tidy. Independent Boss verification: `loop_recur_4_gcd_value_pos.ail` re-run → stdout `27`; `loop_recur_3a` re-run → `ail check` exit 1 `[recur-outside-loop] countdown: …`. **The standalone loop/recur milestone is fully ratified and CLOSED**: 3 iterations + a tidy shipped, audit clean (architect drift resolved, bench pristine carry-on), fieldtest clean on every milestone axis. Fixtures `examples/fieldtest/loop_recur_*.ail` + spec `docs/specs/2026-05-18-fieldtest-loop-recur.md` committed; roadmap P0 entry marked closed; the two orthogonal findings tracked as P2 todos → 2026-05-18-fieldtest-loop-recur.md
|
||||
- 2026-05-18 — iter prose-loop-binders.1 (single-iteration milestone, DONE): Form-B prose `loop` now renders binders as a parenthesised init-list on the keyword line — `loop(acc = 0, i = 1) { … }` — instead of bare `name = init;` statements inside the body block, which had implied C/Rust re-init-every-iteration semantics. Projection-only single-arm rewrite of the `Term::Loop` case of `write_term` (`crates/ailang-prose/src/lib.rs`); init exprs render at `level` (keyword line, not the indented block), mirroring the untouched adjacent `Term::Recur` `enumerate()`+`", "` idiom — making the binder list positionally isomorphic to `recur(...)`, which teaches the correct "recur re-binds these positionally" model instead of obscuring it. RED-first via the existing stem-explicit `check_snapshot` harness: two new committed `examples/{loop_sum_to_run,loop_forever_build}.prose.txt` whole-file byte-equality fixtures + two `snapshot_loop_*` `#[test]` fns, verified `0 passed; 2 failed` against the old renderer before the arm rewrite turned them green. Loop/recur AST, Form-A, JSON-AST, typecheck, codegen and the Form-A↔JSON round-trip invariant untouched by construction (no signature change, no caller touched, prose is a one-way lossy projection off the round-trip path — spec §Architecture states this explicitly so milestone-close audit does not chase a phantom). Full `ailang-prose` suite 10/0; both `ail prose | diff` live-CLI byte-matches `MATCH`; post-cleanup porcelain exactly the expected paths. Surfaced from the 2026-05-18 user prose review of the just-closed loop/recur milestone; brainstorm spec `docs/specs/2026-05-18-prose-loop-binders.md` (Step-7.5 grounding-check PASS), plan `docs/plans/prose-loop-binders.1.md`. Both implement review phases first-pass, 0 re-loops. Milestone-close `audit` is the Boss's next pipeline step (projection-only, no Form-A surface change → no fieldtest). → 2026-05-18-iter-prose-loop-binders.1.md
|
||||
- 2026-05-18 — audit prose-loop-binders (milestone CLOSE, clean / carry-on): milestone-close tidy for the single-iteration prose-loop-binders milestone (range `6cbd0fe..c9355d7`). Architect drift review **clean** — zero drift, zero debt, verified against the diff not journal trust: single `Term::Loop` `write_term` hunk in `crates/ailang-prose/src/lib.rs`, the two non-render `Term::Loop` sites + all `Term::Recur` arms absent from the diff, DESIGN.md/PROSE_ROUNDTRIP.md make no claim about prose `loop` rendering (so the spec's "no doc edit needed" is correct not a skipped obligation), no carve-out/hash-pin/round-trip lockstep bypassed (prose is off the Form-A↔JSON round-trip path by construction). Bench: `compile_check.py` 0/24 + `cross_lang.py` 0/25 both **exit 0** (cross_lang's independent `ail_bump_s` +1.50% ±15% ok corroborates check.py's bump_s firing is baseline-staleness not a real regression); `check.py` exit 1, first run 3 regressed but a confirmatory identical re-run (no code change) returned `latency.implicit_at_rc.p99_9_us` +37.30%→+10.22% **ok** and `.max_us` +114.14%→+22.17% **ok** (median/p99 within tol both runs) — the two latency-tail firings proven single-sample 5-run RC-mode jitter by re-run; `bench_list_sum.bump_s` persisted ~+12% = the already-tracked P2 roadmap todo (byte-oracle-proven environmental at the 2026-05-16 iteration-discipline-revert audit, causally impossible to attribute to a projection-only `ailang-prose` change). **All items carry-on**: no fix iteration, no baseline ratify — explicitly NOT bumping the `*.bump_s` baseline opportunistically inside an unrelated projection-only milestone (the P2 todo owns a holistic recapture; piecemeal bump is the "regression is expected" rationalisation the Iron Law forbids). Fieldtest **not applicable / not dispatched**: projection-only change to the Form-B *reading* surface; the fieldtester authors `.ail` Form-A and would not exercise the prose-render change (no friction/bug/spec-gap axis); the two whole-file byte-equality `.prose.txt` snapshots + live-CLI `ail prose|diff` checks are the projection-only E2E gate. Milestone **CLOSED clean**; roadmap P0 entry flipped `[~]`→`[x]`, WhatsNew.md user-facing entry appended (user present → no notify.sh fired per notification policy). → 2026-05-18-audit-prose-loop-binders.md
|
||||
|
||||
Reference in New Issue
Block a user