Boss-dispatched fieldtest after audit-mut-local closed. Six AIL
Surface (.ail) fixtures under examples/fieldtest/ exercise the
shipped mut-local surface from a downstream-LLM-author's perspective
(no compiler-source access; DESIGN.md + public examples only).
Positive fixtures all run end-to-end first-try:
- mut-local_1_factorial.ail: straight-line Int accumulator
unroll (5!), prints 120.
- mut-local_2_classify_temp.ail: nested-if assigns into a Unit-
typed mut block, prints classification code.
- mut-local_3_horner.ail: Float mut accumulator for polynomial
evaluation, prints 18.
- mut-local_4_has_small_factor.ail: Bool mut flag via four
if-then-assign checks, prints true.
Negative probes confirm diagnostics fire as documented:
- mut-local_5_lambda_capture_probe.ail: [mut-var-captured-by-lambda].
- mut-local_6_diag_probe.ail: [mut-var-unsupported-type].
Findings:
[friction] F1 — mut without iteration: the accumulator-over-an-
iteration shape never materializes. Without while/for, the LLM-
author still writes a tail-recursive helper (the very pattern
the milestone Goal said mut would replace). examples/mut_counter.ail
illustrates the degeneration. Routing: planner for a while/for
iteration OR tighten DESIGN.md to name the gap honestly.
[friction] F2 — all four mut-related diagnostics emit their
bracketed [code] twice ('error: [code] fn-name: [code] message').
Mechanical bug: the #[error('[code] ...')] Display attributes I
authored in mut.2/mut.4-tidy include the bracketed prefix in the
message body, and the cli-diag-human formatter adds another from
CheckError::code(). Routing: debug (mechanical message-body
cleanup).
[friction] F3 — no surface form to call a zero-arg fn ('(app f)'
rejected at parse with 'expected at least one argument').
Orthogonal to mut-local but surfaced building the closure-factory
probe. Routing: planner for a small tidy iter.
[spec_gap] F4 — DESIGN.md does not name the 'use a tail-rec
helper instead' workaround for the iteration-over-accumulator
shape that mut alone cannot express. Routing: ratify in DESIGN.md
alongside F1's resolution.
[working] W1/W2/W3 — surface reachable on first read; diagnostics
pinpoint cause (mut-assign-out-of-scope even lists available
vars); composes cleanly with if + lambda-without-capture +
final-expression position.
Spec: docs/specs/2026-05-15-fieldtest-mut-local.md (333 lines).
Refs: docs/specs/2026-05-15-mut-local.md, audit-mut-local close
at 8685e96.
error: [mut-var-unsupported-type] main: [mut-var-unsupported-type] mut-var `s : Str` is not supported in this milestone — ...
error: [mut-assign-out-of-scope] main: [mut-assign-out-of-scope] cannot assign to `y` here — not declared as a mut-var in the enclosing (mut ...) block. Available mut-vars in scope: [x]
```
The fn-name prefix (`make_bumper:`, `main:`) IS useful — it tells the
LLM-author which fn the error is in. The second `[code]` is pure
noise: the bracketed prefix already supplies it. An LLM reading the
diagnostic without prior calibration will likely treat the doubled
code as a structured signal it should pay attention to, slowing
remediation rather than helping it.
- Where it surfaced: all four diagnostic probes (fieldtest #5, #6,
and the transient `/tmp/probe_oos.ail`).
- Recommended downstream action: `debug` — the message bodies
registered for these four codes evidently include the bracketed
prefix in their own text (probably as a leftover from a pre-
cli-diag-human format where messages carried their own
bracketed prefix). The fix is mechanical: strip the leading
`[code]` from each message body so the formatter's prefix
appears exactly once.
### [friction] F3 — no surface form to call a zero-arg fn from AILang
This is orthogonal to mut-local but surfaced while building the
closure-factory probe for fieldtest #5. Form-a's grammar is
`(app FN ARG+)` — at least one argument required. A fn declared as
### [working] W3 — mut composes cleanly with `if`, with lambda bodies (no capture), and with the final-expression-position
The two non-trivial composition cases — `if` cascades inside a mut
block (fieldtest #2, #4) and a mut block inside a lambda body
whose mut-vars do not escape (transient `/tmp/probe_mut_in_lam.ail`)
— both work without surprise. Also: `(assign x 5)` is a legitimate
final expression of a mut block (the block's type is then Unit),
and the parser accepts a mut block whose body is one such final
expression with no preceding statements. The composition story
is consistent.
- Where it surfaced: fieldtest #2, #4; transient probes.
- Recommended downstream action: `carry-on`. The composition
invariants ("mut-vars do not escape" / "mut-vars shadow let-bound
names of the same identifier") are spec'd in
`2026-05-15-mut-local.md` §Term::Var resolution and behave as
specified.
## Recommendation summary
| Finding | Class | Action |
|---|---|---|
| F1 — accumulator-in-a-loop shape inexpressible | friction | `plan` (add `while`/`for`) OR tighten DESIGN.md to name the gap (less work, more honest) |
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.