fieldtest-form-a: 4 fixtures + spec report; agent doctrine update

Boss-dispatched fieldtest at milestone close. Agent authored 4 .ail
fixtures (factorial smoke + Show user-ADT + user-class Describe with
two instances + two-module workspace) from DESIGN.md + form_a.md only
(no compiler-source reads, no spec-of-milestone reads). All four
fixtures `ail check` ok and `ail run` produces expected stdout.

Findings (1 bug, 1 friction, 2 spec_gaps, 3 working):

- [bug] instance-method-body unbound-var bypasses `ail check` —
  forma_3 first attempt called `str_concat` inside the instance
  method body; `ail check` returned ok, `ail build` died with the
  monomorphise_workspace "unknown identifier" diagnostic. Same
  shape at fn-body level correctly fires `[unbound-var]` at check
  time. Next: debug skill, RED-first against `ail check`.

- [friction] no `str_concat` primitive. Every realistic Show MyType
  body wants string concatenation; the absence forced the agent to
  shape examples around bare int_to_str / ctor-arity-1 patterns.
  Next: small planner tidy iter wiring `str_concat` symmetric to
  `str_clone` and `int_to_str`.

- [spec_gap] form_a.md has zero references to class, instance,
  constraint, or method productions — pre-22 surface only. The
  form-a-default-authoring milestone made .ail the authoring form,
  but the form_a spec doc remains pre-typeclass.

- [spec_gap] form_a.md leaves the class-qualifier ambiguity in
  `(instance (class X))` unspecified — bare-class-name vs canonical-
  form rule from mq.1 is not documented at the surface level. The
  agent picked the bare-name reading from the corpus; the canonical-
  form reading is equally plausible from the schema.

Boss-side cleanup at commit time:
- Deleted 8 stale .ail.json sidecars in examples/fieldtest/ (4
  forma_* derived by the fieldtester per old dual-form workflow + 4
  pre-existing floats_* from the prior fieldtest milestone that
  iter form-a.1 T8 missed because the bash deletion loop globbed
  only examples/*.ail.json direct children).
- Updated skills/fieldtest/agents/ailang-fieldtester.md to remove
  the now-obsolete "generate canonical JSON sidecar" step (Phase 3
  rewritten + Iron Law + Reading list + Common Rationalisations +
  Red Flags all aligned to the post-form-a single-form doctrine).

cargo test --workspace: 557 passed, 0 failed, 3 ignored (unchanged
from audit-form-a — the fieldtest fixtures are standalone, not
referenced by any test).

Findings deferred to follow-up iters; the milestone close itself is
still clean.
This commit is contained in:
2026-05-13 11:48:58 +02:00
parent eb4cb25b0c
commit 8698d897b6
12 changed files with 466 additions and 27 deletions
+1
View File
@@ -54,3 +54,4 @@
- 2026-05-13 — iter form-a.0: prelude pilot for Form-A-as-default-authoring milestone — examples/prelude.ail rendered (116 lines / 6386 bytes) via `ail render`; two auto-discovering roundtrip tests (every_ail_fixture_matches_its_json_counterpart + parse_then_print_then_parse_is_idempotent_on_every_ail_fixture) green on the new fixture without test-code changes; prelude.ail.json retained per spec §A2 (singular dual-form iter, deletion lands iter 1 with bulk test-infra refactor); cargo test --workspace green at 558 tests → 2026-05-13-iter-form-a.0.md
- 2026-05-13 — iter form-a.1: Form-A-as-default-authoring milestone close (Boss-decided strategy C, big-bang) — 12 tasks across two implementer dispatches; T1 added 3 new tests (parse-determinism + CLI-pipeline-idempotency + carve_out_inventory#[ignore]) + T2 bulk-rendered 99 missing examples/<stem>.ail (corpus 58→157 .ail) + T3-4 migrated 26 fixture-loading test files (Group A load_workspace→ailang_surface::load_workspace + Group B subprocess suffix flip) + T5 relocated 23 #[cfg(test)] mod tests blocks from ailang-core/-codegen production source to integration test crates with ailang-surface dev-dep (hash_pin.rs + workspace_pin.rs + eq_primitives_pin.rs + prose snapshot migration) + T6 bench-driver suffix flips (4 Python + run.sh, both compile_check.py + cross_lang.py exit 0) + T7 re-authored 4 raw-JSON-inspect e2e tests via `ail parse`-derived tempfiles + retired 1 (render_parse_round_trip_canonical subsumed by T1) + T8 deleted 156 non-carve-out .ail.json (inventory 8 carve-outs + 157 .ail post-T8, alphabetical: broken_unbound + prelude + 3× test_22b2_* + 3× test_ct1_*) + 20 forward-pulled stale-.ail.json-ref repairs missed by T1-5 recon + T9 retired 3 obsolete roundtrip tests (print_then_parse_round_trips_every_fixture + every_ail_fixture_matches_its_json_counterpart + cli_render_then_parse_preserves_canonical_bytes_on_every_fixture) + dead helpers (list_json_fixtures ×2, round_trip_one, strip_trailing_newlines) + flipped schema_coverage corpus to .ail (forward-pulled into T8 for green-gate) + T10 §C3 DESIGN.md §Roundtrip Invariant restated with parse-determinism + idempotency + CLI-pipeline-idempotency + carve-out-anchor framing (5 surviving enforcement tests named) + T11 §A4 CLAUDE.md + DESIGN.md doctrine sentences replaced (canonical form is JSON-AST; authoring form is .ail) + T12 WhatsNew milestone-close entry + roadmap [milestone] form-a struck [x] (Closed 2026-05-13 by iter form-a.1). 4 carve-outs added beyond the original 7 §C4(a): only 1 — prelude.ail.json — landed as §C4(b) compile-time-embed (rationale: include_str! at workspace.rs:417 + main.rs:474; resolution queued as separate milestone Prelude embed: Form-A as compile-time source). Spec amendment + grounding-recheck PASS at 9fcda8b. Final tests: 557 green + 3 ignored (561 from T1 4 retired = 557); bench compile_check.py + cross_lang.py exit 0; check.py noise envelope continues 11th consecutive audit, baseline pristine. Milestone structurally closed → 2026-05-13-iter-form-a.1.md
- 2026-05-13 — audit-form-a: milestone close (Form-A as the default authoring surface) — architect status drift_found with 4 documentary-only items (2× [medium] spec + plan "seven carve-outs" orphans contradicting the §C4(b) amendment commit 9fcda8b — the implementation is eight-carve-out-correct; 2× [low] hash.rs:57 empty `mod tests {}` placeholder after T5 relocation + round_trip.rs:16 stale "Direction 2" docstring contradicting T10's framing rewrite); architect explicit recommendation carry-on — no form-a.tidy queued, drift deferred to documentary cleanup at next opportunity (architect verbatim: "the implementation is correct and the spec orphans are post-hoc retro-clean-up, not active drift"). Bench: all 3 scripts exit 0, baseline pristine for the 12th consecutive audit. check.py 2 regressions in tail-latency max_us (latency.explicit_at_rc + latency.implicit_at_rc, both >120% over baseline, both paired with p99 improvements on same metric stem) — 12th consecutive observation of the metric-identity-migrating noise envelope since audit-cma; compile_check.py 2 regressions in sub-millisecond check_ms on hello + borrow_own_demo — timing-jitter on the corpus's two smallest fixtures, iter form-a.1 T6 already flagged the same pair as transient; cross_lang.py 25/25 stable, bench_tree_walk.rc_over_c improved 2.74→2.59 still inside noise. What holds (verified): §C3 §Roundtrip Invariant restated with 4 properties + 5 surviving enforcement tests named verbatim; §A4 CLAUDE.md + DESIGN.md doctrine reworded per spec; 8-carve-out inventory matches spec post-amendment (alphabetical: broken_unbound + prelude + 3× test_22b2_* + 3× test_ct1_*); 156 non-carve-out .ail.json deleted; 157 .ail post-iter; cargo test --workspace 557 green + 3 ignored; WhatsNew editorial-clean (user-facing); roadmap milestone struck [x] with close attribution + follow-up [Prelude embed] milestone present with motivation + 3 resolution options. Milestone fully closed → 2026-05-13-audit-form-a.md
- 2026-05-13 — fieldtest-form-a: 4 hand-authored `.ail` fixtures (factorial smoke, Show user-ADT, user-class Describe with two instances, two-module workspace) + spec report. Findings: 1 bug (instance-method-body unbound-var bypasses `ail check`; surfaces at mono with degraded "monomorphise_workspace: unknown identifier" diagnostic — `ail check` should fire `[unbound-var]` like at fn-body level → debug RED-first), 1 friction (`str_concat` primitive missing — first attempt at user-Show body wanted "prefix=" ++ int_to_str-style concatenation, repaired by dropping prefix → planner tidy symmetric to `str_clone`/`int_to_str` wiring), 2 spec_gaps (`form_a.md` has zero `class`/`instance`/`constraint`/`method` sections — the form-a milestone made `.ail` the authoring form but the form_a spec doc still only covers pre-22 surface; canonical-form rule for `(instance (class X))` class-qualifier not in form_a — `class X` vs `class some_module.X` reading is ambiguous from the spec, observed canonical reading via examples corpus). Plus 3 working (4 fixtures, 3 first-shot-clean + 1 first-attempt-bug-then-clean). Boss-side cleanup at commit time: deleted 8 stale `.ail.json` sidecars in `examples/fieldtest/` (4 forma_* derived by fieldtester per old workflow, 4 pre-existing floats_* from prior milestone that T8 missed because the bash deletion loop globbed only `examples/*.ail.json` direct children); plus updated `skills/fieldtest/agents/ailang-fieldtester.md` Phase 3 to remove the now-obsolete "generate canonical JSON" step (the agent was calibrated to dual-form pre-form-a; post-form-a `.ail` is the sole authoring form). Findings deferred to follow-up iters: bug → next `debug` dispatch, friction → small planner tidy, spec_gaps → form_a.md tightening pass → 2026-05-13-fieldtest-form-a.md
+284
View File
@@ -0,0 +1,284 @@
# Fieldtest — form-a-default-authoring — 2026-05-13
**Status:** Draft — awaiting orchestrator triage
**Author:** ailang-fieldtester (dispatched by skills/fieldtest)
## Scope
Form A (`.ail`) became the sole authoring surface across the working
tree. 156 non-carve-out `.ail.json` files were rendered to `.ail`
siblings and deleted (8 carve-outs remain). The Roundtrip Invariant
was restated as parse-determinism + idempotency-under-print + CLI-
pipeline-idempotency. CLI subcommands `ail check / build / run /
parse / render` accept `.ail` directly via extension dispatch (since
iter ext-cli.1). This fieldtest probes whether a downstream LLM-author
working only from DESIGN.md + `crates/ailang-core/specs/form_a.md` +
the public corpus can hand-author programs that exercise the new
authoring surface, the milestone-23/24 typeclass machinery (`Eq`,
`Ord`, `Show`, `print`), and multi-module workspaces.
## Examples
### `examples/fieldtest/forma_1_factorial.ail` — tail-recursive factorial
- What it does: two top-level fns (`fact_acc/2`, `fact/1`) plus `main`
prints `fact 5` and `fact 10`. Exercises `tail-app`, `if`, primitive
arithmetic, no class machinery. The LLM-natural shape for "is the
basic authoring path frictionless?".
- Why it fits the milestone: pure-arithmetic smoke for the
authoring-ergonomics axis. No class or workspace surface; just
exercises parser → checker → codegen on a spontaneous LLM-author
shape derived from the form_a.md few-shot corpus (sum_demo /
hello).
- Outcome: `ail check``ok (23 symbols across 2 modules)`; `ail build`
`built`; stdout `120\n3628800\n` (expected). First-shot clean.
### `examples/fieldtest/forma_2_show_color.ail` — `instance Show Color` + polymorphic `print`
- What it does: declares `data Color { Red | Green | Blue }`, an
`instance prelude.Show Color` whose body pattern-matches and returns
a `Str` literal, and calls `print` three times. Exercises milestone
24's polymorphic `print` end-to-end on a user ADT with a manually
written Show instance.
- Why it fits the milestone: the Show+print axis. Tests that the
iter-24.3 cross-module-references-in-synthesised-bodies invariants
fire correctly on a user-defined enum-like ADT.
- Outcome: `ail check` → ok; `ail build` → built; stdout
`Red\nGreen\nBlue\n` (expected). First-shot clean.
### `examples/fieldtest/forma_3_user_class_describe.ail` — user-defined `class Describe` with two instances
- What it does: declares `class Describe a where describe : (borrow a)
-> Str`, ships `instance Describe Int` and `instance Describe Shape`
(a 2-variant ADT), then a polymorphic free fn `announce : forall a.
Describe a => (borrow a) -> Unit !IO` that calls `describe` then
`io/print_str`. `main` calls `announce` at three concrete types.
- Why it fits the milestone: stresses the user-typeclass authoring
surface, `(forall (vars a) (constraints (constraint Describe a))
...)` shape, monomorphisation of a polymorphic free fn through a
user constraint, and dispatch to two distinct user instances.
- Outcome: **first attempt failed** because I reached for `str_concat`
(a builtin I assumed existed for `"Circle r=" ++ int_to_str r`-style
bodies); `ail check` returned `ok` but `ail build` died with
`monomorphise_workspace: unknown identifier: str_concat`. Repaired
by dropping the prefix-string concatenation. **Repaired version
runs clean**: stdout `42\n3\n5\n` (expected). See findings below.
### `examples/fieldtest/forma_4_intmath_lib.ail` + `forma_4_intmath_main.ail` — two-module workspace
- What it does: `forma_4_intmath_lib` exports three pure-`Int` fns
(`square`, `cube`, `abs_int`); `forma_4_intmath_main` imports it
and calls each via qualified references (`forma_4_intmath_lib.square
7`, etc.) inside `main`.
- Why it fits the milestone: stresses extension-dispatch on a
workspace entry plus canonical-form qualified cross-module refs
(ct.1 / mq.1 mature features) on hand-authored `.ail`.
- Outcome: `ail check examples/fieldtest/forma_4_intmath_main.ail` →
`ok (24 symbols across 3 modules)` (lib + main + prelude); `ail
build` → built; stdout `49\n27\n42\n` (expected). First-shot clean.
## Findings
### [bug] `ail check` does NOT catch unbound identifiers inside `(instance ... (method (body (lam ... (body APP)))))`
- Example: forma_3 first attempt; isolated repro in spec body below.
- What happened (verbatim, isolated repro):
```
(module ft_concat_in_instance
(class Describe (param a)
(method describe (type (fn-type (params (borrow a)) (ret (con Str))))))
(instance (class Describe) (type (con Int))
(method describe (body (lam (params (typed n (con Int))) (ret (con Str))
(body (app str_concat "n=" (app int_to_str n))))))))
(fn main (type (fn-type (params) (ret (con Unit)) (effects IO))) (params)
(body (do io/print_str (app describe 5)))))
```
`ail check`: `ok (23 symbols across 2 modules)`.
`ail build`: `Error: monomorphise_workspace: unknown identifier: str_concat`.
The same shape at fn-body level (a plain `(fn ... (body (app str_concat ...)))`)
correctly fires `error: [unbound-var] greet: unknown identifier:
str_concat` at `ail check`. So the typechecker has the diagnostic
machinery; it does not walk the lambda body inside an instance
method's `(method (body (lam ... (body ...))))` when looking up
identifiers (or it walks but does not produce an error at the
unbound-var). The error eventually fires at the monomorphisation
pass, whose diagnostic ("monomorphise_workspace: unknown identifier")
is significantly less helpful than the typechecker's
`[unbound-var]` — no source location, no symbol kind, no `did you
mean ...?`.
- Why it is a bug: `ail check` is documented as the typecheck step
whose purpose is exactly to catch this class of error before
codegen runs. The split-pipeline diagnostic surface is a regression
in the user-facing contract.
- One-line repro: see the four-line isolated repro above.
- Recommended downstream action: `debug` → write a RED test asserting
`ail check` fires `[unbound-var]` on the instance-method-body shape,
then `implement` mini-mode walks the lambda body during instance
validation. (Plausible cause: instance method bodies are validated
later than fn bodies, after the registry build, and the unbound-var
check is not threaded through that path.)
### [spec_gap] `crates/ailang-core/specs/form_a.md` does not document the typeclass surface
- Example(s): forma_2 (Show user ADT) and forma_3 (user-defined class
+ constraint).
- What happened: I needed to write `(class ...)`, `(instance ...)`,
`(method ...)`, and `(constraints (constraint ClassName tyvar))`
inside a `forall`. `form_a.md` — the document explicitly billed as
"the **complete LLM-targeted specification**. If you are an LLM and
this is in your context, you have everything you need to produce
valid Form-A" — has zero references to "class", "instance",
"constraint", or "method". The grep is empty. I reverse-engineered
the surface entirely from `examples/show_user_adt.ail`,
`examples/bench_mono_dispatch.ail`, and
`examples/mq1_xmod_constraint_class.ail`.
- Reading I picked: the `(class NAME (param TYVAR) (method NAME (type
FN-TYPE)))` form for class declarations; `(instance (class CLASS-
REF) (type TYPE) (method NAME (body LAM-TERM)))` for instances;
`(constraints (constraint CLASS-REF TYVAR))` inside a `(forall ...)`
schema. These work but are not in the spec; another reading (e.g.
whether `(class CLASS-REF)` must be qualified vs. bare for same-
module instances) was not determinable from the spec text and had
to be derived by example-mining.
- Why spec_gap: a foreign LLM-author with form_a.md in context but
without the example corpus cannot author class / instance / poly-
constrained-fn programs at all. DESIGN.md §Decision-11 documents
the JSON-AST schema for `ClassDef` / `InstanceDef` / `FnDef.type`
constraints (lines ~1526-1582) but the Form-A textual projection
for any of those keywords is not anchored anywhere. The milestone-
24 close ratified `print` + four `Show` instances ship in the
prelude — without a documented Form-A surface for user-class
authoring, the LLM-utility criterion for typeclasses (Decision 11
→ "LLM author reaches for it unprompted") cannot be tested on
foreign LLMs by the procedure that cma.3 / ms.2 used for non-class
programs.
- Recommended downstream action: `tighten DESIGN.md` (or extend
`form_a.md`) — `plan` an iter that adds three sections to
form_a.md: "Class declarations", "Instance declarations", and
"Constraints on polymorphic fns". Each with the EBNF production
and one or two few-shot fixtures lifted from the corpus
(`show_user_adt.ail`, `mq1_xmod_constraint_class.ail`).
### [spec_gap] Form-A grammar for `prelude.Show` qualifier in `(instance (class ...))`: when is the prefix required?
- Example: forma_2 (`(instance (class prelude.Show) (type (con Color))
...)`).
- What happened: I copied the qualified `prelude.Show` form from
`examples/show_user_adt.ail`. It works. But I also note that
`examples/mq3_class_eq_vs_fn_eq_classmod.ail` writes `(instance
(class MyEq) ...)` bare (no prefix) — because the class is declared
in the SAME module. The rule appears to be the mq.1 canonical-form
rule (qualify for cross-module, bare for same-module). DESIGN.md
§mq.1 makes this explicit at the JSON-AST `InstanceDef.class` field
level, but not at the Form-A surface level.
- Reading I picked: a class reference inside `(instance (class ...))`
follows the same canonical-form rule as `Type::Con.name` — bare for
same-module, `module.Class` for cross-module. Another plausible
reading: always-qualified (since `prelude` is auto-imported, bare
`Show` could resolve to prelude.Show — but the empirical evidence
in `show_user_adt.ail` says no, qualifier is required for cross-
module).
- Why spec_gap: a downstream LLM-author cannot determine whether
`(instance (class Show) (type (con MyType)) ...)` (bare, same module
as class? cross-module to prelude?) is well-formed without reading
the corpus. form_a.md doesn't carry the rule; DESIGN.md carries
it at the JSON-AST level but not for the surface.
- Recommended downstream action: same iter as the previous gap —
one paragraph in form_a.md's new "Class declarations" / "Instance
declarations" sections naming the canonical-form rule for
`Constraint.class`, `InstanceDef.class`, `SuperclassRef.class` at
the Form-A surface.
### [friction] No primitive `str_concat` / `str_append` in builtins
- Example: forma_3 first attempt.
- What happened: while writing a `Describe Shape` instance, I reached
for a string-concatenation primitive to format `"Circle r=" ++
int_to_str r`. `ail builtins` lists `int_to_str`, `bool_to_str`,
`str_clone`, `float_to_str` — no `str_concat`, no `str_append`,
no `++`. The natural `Show MyType` body shape for any non-trivial
ADT (a record-like print, a `name=value` style, anything with a
label) requires string concatenation. Without it, the LLM-author
either writes degenerate instance bodies (return just the inner
Int as if it were the whole repr — what forma_3's repaired version
does) or punts on milestone-24 Show-of-rich-ADTs entirely.
- Why friction: milestone 24 shipped polymorphic `print` and four
prelude `Show` instances on primitives; the natural LLM-author
follow-up is `Show MyRecord = "MyRecord { x = " ++ show x ++ "
}"`-shaped instance bodies. With no concatenation primitive, the
shipped Show feature is observably under-powered for the very task
it was advertised to enable (user-ADT pretty-printing through
`print`). The friction is real for the milestone's downstream
utility, not just for hypothetical foreign LLMs.
- Recommended downstream action: `plan` a small follow-up iter that
adds `str_concat : (Str, Str) -> Str` as a runtime + checker +
codegen primitive (symmetric to `str_clone` / `int_to_str` plumbing
in 24.1 / hs.4). The cost surface is small (single-fn slab-
allocating C helper + four-site lockstep registration); the LLM-
utility payoff is high because every realistic `Show MyType` body
needs it.
### [working] User-defined typeclass with `forall + Constraint` polymorphic fn dispatches correctly
- Example: forma_3 (repaired).
- What happened: declared `class Describe a where describe : (borrow
a) -> Str`, shipped two instances (`Int` and `Shape`), wrote
`announce : forall a. Describe a => (borrow a) -> Unit !IO`, called
it three times at two concrete types in `main`. `ail check`
reported ok across 2 modules (entry + prelude); `ail build`
produced a binary; stdout was correct. The mq.3 type-driven
dispatch + the iter-23.4 unified mono pass + the milestone-24
`print`-style polymorphic-fn pipeline all visibly cooperate on a
workload they were not specifically calibrated against. No
diagnostic surfaced spuriously; the surface was discoverable from
three corpus examples in ~5 minutes.
- Why working: the full Decision-11 trajectory (class declaration +
multiple instances + polymorphic free fn over a constraint set +
mono-pass synthesis of `describe__Int` and `describe__Shape` +
call-site rewrite) ran end-to-end on a hand-authored `.ail`
workspace without any compiler crate hint. This is the LLM-utility
acceptance test for milestone 22+23+24 cumulative, run on one
spontaneous fresh-author shape, and it passes.
- Recommended downstream action: `carry-on`. Worth pinning the
fixture so a future regression cannot silently break the
trajectory. (Already a working-tree artefact;
`crates/ailang-core/tests/schema_coverage.rs`'s dynamic walk picks
it up automatically per the form-a.1 refactor.)
### [working] Multi-module workspace via `(import ...)` + qualified refs runs end-to-end on `.ail`
- Example: forma_4_intmath_main + forma_4_intmath_lib.
- What happened: two `.ail` modules in `examples/fieldtest/`, one
importing the other with `(import forma_4_intmath_lib)` and using
qualified `forma_4_intmath_lib.square` / `.cube` / `.abs_int`
references inside `main`. `ail check
examples/fieldtest/forma_4_intmath_main.ail` correctly resolved
the import (workspace prefers `.ail` sibling over `.ail.json` per
the ext-cli.1 doc), typechecked across 3 modules (main + lib +
prelude), built, and ran. No friction from the canonical-form rule
— qualified names worked first-shot.
- Why working: the ext-cli.1 + form-a.1 stack (extension-dispatching
workspace loader + `.ail`-preferred sibling resolution) functions
exactly as DESIGN.md promises for a hand-authored two-module
workspace.
- Recommended downstream action: `carry-on`.
### [working] Polymorphic `print` on user ADT through `instance prelude.Show MyType`
- Example: forma_2.
- What happened: declared `data Color { Red | Green | Blue }`, wrote
`instance prelude.Show Color` with a 3-arm match body returning a
Str literal, called `(app print (term-ctor Color Red))` three times
in main. Compiled clean; ran clean; produced `Red\nGreen\nBlue\n`.
The whole iter-24.3 cross-module-references-in-synthesised-bodies
trajectory (the three invariants in DESIGN.md §Cross-module
references in synthesised bodies — canonical-form type_args,
import_map-bypass fallback, FreeFnCall residual push) ran on a
user-ADT that the implementation tests did not specifically pin.
- Why working: the LLM-utility acceptance test for milestone-24's
flagship feature (polymorphic `print` reaching user ADTs through a
user-written Show instance) passes on a first-shot LLM-author
example.
- Recommended downstream action: `carry-on`.
## Recommendation summary
| Finding | Class | Action |
|---|---|---|
| `ail check` misses unbound-var inside instance method lambda body | bug | `debug` (RED test on instance-body unbound-var; mini `implement`) |
| `form_a.md` lacks class/instance/constraint grammar | spec_gap | `plan` (form_a.md addendum: 3 new sections + corpus fixtures) |
| Form-A canonical-form rule for `(instance (class X))` qualifier not anchored | spec_gap | `plan` (paragraph in form_a.md's new class-section; tighten DESIGN.md §Decision 11 surface anchor) |
| No `str_concat` builtin | friction | `plan` (small follow-up iter; adds runtime + checker + codegen + lockstep wiring symmetric to `str_clone`) |
| User-typeclass + constraint poly fn end-to-end | working | `carry-on` |
| Multi-module `.ail` workspace + qualified refs | working | `carry-on` |
| `print` on user ADT through user `instance prelude.Show` | working | `carry-on` |