From 48e7774d01fc34a9584cbd6f9d783841bafe1f67 Mon Sep 17 00:00:00 2001 From: Brummel Date: Mon, 18 May 2026 01:10:20 +0200 Subject: [PATCH] =?UTF-8?q?audit:=20prose-loop-binders=20milestone=20CLOSE?= =?UTF-8?q?=20=E2=80=94=20clean=20/=20carry-on?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- docs/WhatsNew.md | 6 ++ .../2026-05-18-audit-prose-loop-binders.md | 91 +++++++++++++++++++ docs/journals/INDEX.md | 1 + docs/roadmap.md | 29 +++--- 4 files changed, 115 insertions(+), 12 deletions(-) create mode 100644 docs/journals/2026-05-18-audit-prose-loop-binders.md diff --git a/docs/WhatsNew.md b/docs/WhatsNew.md index dd65338..364880f 100644 --- a/docs/WhatsNew.md +++ b/docs/WhatsNew.md @@ -192,3 +192,9 @@ It was field-tested by writing real programs from scratch against the documentat Two unrelated, pre-existing minor surface gaps were noticed while testing and written down for later; neither affects loops. The milestone is closed and fully verified — built across three steps plus a follow-up safety fix, drift-reviewed, benchmarked clean, and field-tested. The next milestone is a fresh decision; that is the natural place to pick up when you are back. + +## 2026-05-18 — Loops read correctly in the prose view + +The readable prose rendering of a loop no longer misleads. Until now, a loop's carried values were printed as plain assignment lines inside the loop body — which reads exactly like "reset these to their starting values on every pass", the opposite of what a loop actually does. The starting values now sit in parentheses on the loop keyword itself, the way function parameters do, and the body block contains only the body. This also makes the loop header line up one-to-one with the re-entry form that supplies the next round's values, so the relationship between the two is visible at a glance instead of obscured. + +Nothing about how loops are written, type-checked, compiled, or run changed — this is purely the human-reading projection telling the truth about the code it shows. You raised this directly; it is a one-change milestone, drift-reviewed clean and benchmarked clean (the one flagged benchmark metric is the same pre-existing machine-drift item already on the list, not caused by this). diff --git a/docs/journals/2026-05-18-audit-prose-loop-binders.md b/docs/journals/2026-05-18-audit-prose-loop-binders.md new file mode 100644 index 0000000..ea35322 --- /dev/null +++ b/docs/journals/2026-05-18-audit-prose-loop-binders.md @@ -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. diff --git a/docs/journals/INDEX.md b/docs/journals/INDEX.md index 2c6c1e6..825295e 100644 --- a/docs/journals/INDEX.md +++ b/docs/journals/INDEX.md @@ -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` → `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 diff --git a/docs/roadmap.md b/docs/roadmap.md index be99145..7639a5f 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -35,19 +35,24 @@ work progresses. ## P0 — In flight -- [~] **\[milestone\]** Prose `loop` binders — projection redesign. - Form-B prose currently renders loop binders as `name = init;` - statements *inside* the `loop { … }` body, which misreads as - C/Rust re-init-every-iteration semantics. Move them to a - parenthesised init-list on the keyword — `loop(acc = 0, i = 1) - { … }` — positionally isomorphic to `recur(...)`. Projection-only: - one `ailang-prose` `write_term` `Term::Loop` arm + 2 snapshot - fixtures/tests; loop/recur AST, Form A, JSON-AST, typecheck, - codegen, and the Form-A↔JSON round-trip invariant all unchanged. - Spec'd + approved 2026-05-18; Step-7.5 grounding-check PASS. Plan - next. +- [x] **\[milestone\]** Prose `loop` binders — projection redesign — + CLOSED 2026-05-18. Form-B prose now renders loop binders as a + parenthesised init-list on the keyword (`loop(acc = 0, i = 1) + { … }`, positionally isomorphic to `recur(...)`) instead of bare + `name = init;` statements inside the body block (which misread as + C/Rust re-init-every-iteration). Single-iteration, projection-only: + one `ailang-prose` `write_term` `Term::Loop` arm + 2 committed + byte-equality `.prose.txt` snapshots/tests; loop/recur AST, Form A, + JSON-AST, typecheck, codegen, and the Form-A↔JSON round-trip + invariant all byte-unchanged. brainstorm→plan→implement + (`c657e74`/`6533134`/`c9355d7`); milestone-close `audit` clean + (architect zero drift; bench carry-on — sole firing is the tracked + P2 `*.bump_s` environmental staleness, latency-tail proven sampling + noise by re-run); no fieldtest (projection-only, no authoring- + surface change). Stays here briefly for context; remove once stale. - context: `docs/specs/2026-05-18-prose-loop-binders.md`; - surfaced from the loop/recur prose review (2026-05-18 chat). + `docs/journals/2026-05-18-audit-prose-loop-binders.md`; surfaced + from the loop/recur prose review (2026-05-18 chat). - [x] **\[milestone\]** Standalone `loop` / `recur` — CLOSED 2026-05-18. Strictly-additive strict-iteration surface, `recur` tail-position-only,