Comprehensive usability re-test of the RawBuf kernel extension in natural LLM-author decompositions the prior field test (0058) did not exercise: a Float RawBuf in a user ADT read through two borrow helpers, a Bool flag buffer filled with a computed value, an Int fib buffer whose fill reads its own writes plus two composed borrow summaries, and a forbidden core-primitive element. All three working fixtures build, run to expected values, and report live=0 across Float/Bool/Int widths — the core RawBuf surface (borrow-helper composition, fill loops, RawBuf-in-ADT, element widths) genuinely holds together. Orchestrator triage corrected the fieldtester's run, which executed a stale target/release/ail (built before the #50 fix). Every outcome was re-verified against a fresh build: - F2 (bare RawBuf ADT field unresolved) — RETRACTED: a stale-binary false positive. On a fresh build the bare name resolves in every configuration (ctor name == or != type name; builds/runs live=0). The #50 fix is correct and general. - F7 (NEW, issue #51) — a `check`-clean program crashes `build`: a buffer whose element type is never observed by a later get/set defaults its element to Unit in monomorphisation and hits an unregistered `RawBuf_new__Unit` intercept. Affects a legitimate `RawBuf<Int>` used only for its size AND a forbidden locally- constructed element. Root: the `(new T ...)` desugar drops the explicit element annotation; honouring it reverses the milestone's § Term::New desugar decision, so it routes through brainstorm. - F8 (process) — the field test ran stale; the fieldtester agent now builds from the current tree before running (plugin fix). F1 (spec_gap) resolved here: the design ledger's §Series substrate ctor wrote the storage field `(own (RawBuf a))`, which does not parse (`own` is fn-param/ret only). Rewritten to the verified-authorable `(con RawBuf a)`. The rest of the §Series listing is illustrative and its element- less `(new RawBuf lookback)` construction is entangled with #51; a full compile-verification of that listing belongs with the Series-substrate work. F5 (param-in rejects a forbidden core primitive, message names the set) and F4/F3 (borrow composition; substrate composite) carry on. F6 (param-in set rendered as a Rust-debug list) filed as #52. refs #7
16 KiB
Fieldtest — raw-buf (comprehensive re-test) — 2026-05-30
Status: Triaged — orchestrator re-verified every outcome against a fresh build. Author: fieldtester (dispatched by fieldtest skill); finding triage and corrections by the orchestrator.
Verification note (orchestrator)
The fieldtester ran against target/release/ail, which was stale —
built at 17:22, before the #50 fix landed at 17:48. Every outcome below
was therefore re-verified against a freshly built binary during triage.
The re-verification changed one finding materially: F2 does not
reproduce on a fresh build and is retracted (it was an artefact of the
stale binary). It also surfaced a real defect class the stale run masked
(F7, now issue #51) and a process gap (F8). All other outcomes
(F1, F3, F4, F5, F6) reproduce on the fresh binary and stand.
Scope
A comprehensive usability re-test of the RawBuf kernel base extension
and the kernel-extension surface as it ships at HEAD (4ad003d..HEAD),
after this session's usability repairs (#46 borrow-receiver, #47
fill-loop build, #48 param-in message, #50 RawBuf-in-ADT bare name,
B5 loop-result drop, #49 superseded-heap-binder dec). The brief was
explicitly not to re-run the prior four fixtures
(docs/specs/0058-fieldtest-raw-buf.md) but to push into natural
decompositions an LLM author would reach for that the prior test did
NOT exercise — RawBuf wrapped in a user ADT with bookkeeping (the
"Series substrate"), Bool-width fills through a loop, two borrow helpers
composed over one buffer, and a forbidden core-primitive element type.
Element width was varied across examples (Float, Bool, Int). All work
was done as a downstream consumer with only ail check / build --alloc=rc / run, AILANG_RC_STATS=1, and the public interface
(design/INDEX.md, design/models/0007-kernel-extensions.md, the
examples/ corpus). The prior four rawbuf fixtures were also re-run as
a regression spot-check — all still build, run to their expected values,
and report live=0.
Examples
examples/fieldtest/rbx_1_float_window_stats.ail — Float window in a user ADT, two borrow helpers
- A
WindowADT wraps aRawBuf<Float>plus an Intcount.build_windowallocates a 4-slot Float buffer, fills it via aloop/recur(oneRawBuf.setper slot,int_to_float (2*(i+1))), and wraps it inW. Twoborrow (Window)helpers —meanandwmax— eachmatchthe ADT and read the buffer throughRawBuf.geton a borrow. - Why it fits: the §Series substrate shape (RawBuf in a user ADT field, axes 1+2+3+4 combined) on the Float width, with the borrow-receiver read behind a match and composed across two helpers — none of which the prior test exercised together.
- Outcome: builds + runs + leak-clean (
allocs=4 frees=4 live=0, prints5.0/8.0). One surface adjustment was needed: theownmode cannot appear on the storage field (the ledger's(own (RawBuf a))substrate form does not parse — F1), so the field is written(con raw_buf.RawBuf (con Float)). The bare(con RawBuf (con Float))field also resolves on a fresh build (the #50 fix); the fixture keeps the qualified spelling but the bare name is no longer required (retracted F2).
examples/fieldtest/rbx_2_bool_sieve.ail — Bool flag buffer, computed fill, borrow count
mark_evenfills an N-slotRawBuf<Bool>via a loop with a computed Bool (eq (i % 2) 0) per slot;count_set(aborrow (RawBuf Bool)helper) reads the flags back throughRawBuf.getused directly as anifcondition and accumulates an Int.- Why it fits: the i1/i8 Bool packing edge driven through a runtime-N
fill loop with a computed (not literal
true) value — the prior W1 only stored one literal Bool at a literal index — plus the #46 borrow-receiver fix on the Bool width. - Outcome: builds + runs + leak-clean (
allocs=2 frees=2 live=0, prints3). Clean on first try. See finding F4.
examples/fieldtest/rbx_3_unit_element_reject.ail — forbidden core-primitive element
(new RawBuf (con Unit) n)— a RawBuf ofUnit, a core primitive not in{Int, Float, Bool}.- Why it fits: the prior test rejected
Str(heap primitive) andTick(user ADT); this covers the third class, a core primitive, and re-verifies the #48 message fix names the allowed set. - Outcome: rejects at
ail check(exit 1) with[param-not-in-restricted-set] … type-arg \Unit` … (allowed: one of ["Bool", "Float", "Int"])`. The B3/#48 fix is visible. See findings F5, F6.
examples/fieldtest/rbx_4_int_two_borrow_helpers.ail — Int fib buffer, fill reads itself, two borrow summaries
fill(own→own, across a fn boundary) fills slot i with fib(i), reading prior slots viaRawBuf.geton the owned loop binderb.totalandargmaxare two distinctborrow (RawBuf Int)helpers, called back-to-back on the same owned buffer inmain;argmaxthreads a 3-binder loop (best_i,best_v,i).- Why it fits: composition of the #46 borrow fix (two borrows of one buffer in sequence, buffer still owned + dropped by main), plus a fill loop that reads its own in-place writes.
- Outcome: builds + runs + leak-clean (
allocs=3 frees=3 live=0, prints20/5). Clean on first try. See finding F4.
Findings
[spec_gap] F1 — the ledger's §Series substrate form (ctor … (own (RawBuf a)) …) does not parse
- Examples:
rbx_1_float_window_stats.ail(original draft);/tmpprobes confirming generality. design/models/0007§Series writes the storage field of the Series ADT as(ctor S (own (RawBuf a)) (con Int) …)(line 448). Authoring that verbatim is rejected at the surface:This is general, not RawBuf-specific: anerror: [surface-parse-error] parse error in type: `own` may only appear inside fn-type params or retown-mode field on any plain user ADT ((data Outer (ctor O (own (con Inner))))) is rejected identically. So the canonical Series-substrate example in the design ledger cannot be authored as written, and the ledger gives no in-interface signpost to the working form.- Why spec_gap: the parser rule ("
ownonly in fn-type params/ret") and the ledger's §Series ctor-field syntax are two incompatible readings of what a substrate ADT looks like; the milestone shipped the parser rule but the ledger still documents the forbidden form. A downstream author copying the Series example hits a parse error with no ledger guidance. - Recommended action: ratify / tighten the ledger — done this cycle:
the §Series ctor field is rewritten
(con RawBuf a)(verified authorable; the mode lives on fn params, not ADT fields).own-mode ADT fields are not a planned feature, so no grammar work. Note: the rest of the §Series listing is an illustrative sketch and its construction(new RawBuf lookback)omits the element type — that shape is entangled with F7/#51, so a full compile-verification of the §Series listing belongs with the Series-substrate work, not here.
[retracted] F2 — bare (con RawBuf (con Int)) in an ADT field — FALSE POSITIVE (stale binary)
- The fieldtester reported that a bare
(con RawBuf (con Int))ADT field still failed to resolve despite the #50 fix, citing[type-mismatch] expected RawBuf<Int>, got raw_buf.RawBuf<Int>. - Retracted on triage. The finding was produced against a stale
target/release/ail(built 17:22, before the #50 fix at 17:48). On a freshly built binary the bare-name field resolves in every configuration tested:- ctor name == type name (
(data Box (ctor Box (con RawBuf (con Int))))) — checks ok. - ctor name != type name (
(data Box (ctor B (con RawBuf (con Int)))), the fieldtester's exact repro) — checks ok. examples/raw_buf_adt_field_bare.ail— checks/builds/runs →42,live=0.
- ctor name == type name (
- Confirmed mechanism: the stale binary reproduces the error; the
rebuilt binary does not. The #50 fix (
a92bcaf) is correct and general. No code action; the real lesson is the process gap (F8).
[working] F3 — the Series-substrate composite works end-to-end (with the qualified-name workaround)
- Example:
rbx_1_float_window_stats.ail. - Once the field is spelled
(con raw_buf.RawBuf (con Float)), the full composite the §Series substrate describes works: a Float RawBuf filled via loop, wrapped in aWindowADT with an Intcount, read back through TWOborrow (Window)helpers that eachmatchthe ADT and drive a read loop off the bookkeeping count viaRawBuf.geton a borrow. Builds, prints5.0/8.0, and is leak-clean (allocs=4 frees=4 live=0) — the Window ADT's drop cascade frees the buffer slab. This is the substrate the pending Series milestone needs, and (modulo F1/F2 surface friction) it holds together. - Recommended action: carry-on (and use this as the Series-milestone smoke fixture).
[working] F4 — borrow-receiver helpers compose; Bool/Int fills + reads are correct across widths
- Examples:
rbx_2_bool_sieve.ail,rbx_4_int_two_borrow_helpers.ail. - The #46 borrow-receiver fix holds under composition:
rbx_4borrows one owned Int buffer twice (totalthenargmax), each helper doing multipleRawBuf.get/.sizecalls on its borrow param, with the buffer still owned and dropped by main afterwards — clean,live=0, prints20/5.rbx_2drives the i1/i8 Bool edge through a runtime-N fill loop storing a computed Bool (eq (i%2) 0) and reads it back as anifcondition in a borrow helper — correct (3),live=0. A fill loop that reads its own in-place writes (rbx_4fib:RawBuf.get b (i-1)inside the same loop that wrote slot i-1) works — the in-placeown→ownmutation makes prior slots visible to later iterations. - These are the milestone's load-bearing wins for the downstream Series decomposition; all clean on first or second try with no surprises.
- Recommended action: carry-on.
[working] F5 — param-in rejects a forbidden core primitive (Unit), message now names the allowed set
- Example:
rbx_3_unit_element_reject.ail. (new RawBuf (con Unit) n)is rejected atail check(exit 1):This extends the prior W2 (Str, user ADT) to the third class — a core primitive — and empirically confirms the #48/B3 fix: the allowed set now travels in the human-readable message. The restriction is on the real element type regardless of its tier (core / heap / user ADT).[param-not-in-restricted-set] make_presence: type-arg `Unit` is not in the restricted set for type-variable `a` of `raw_buf.RawBuf` (allowed: one of ["Bool", "Float", "Int"])- Caveat (re-verified): this reject fires because the forbidden
element appears in a written fn-type position (
make_presence'srettype). The restriction is NOT enforced at the bare construction site — see F7. So F5 is "works when the type is written in a signature", which the fixture happens to do. - Recommended action: carry-on (the signature-position reject is correct; the construction-site gap is F7 / issue #51).
[friction] F6 — param-in message renders the allowed set as a Rust-debug list, not the ledger's {Int, Float, Bool}
- Example:
rbx_3_unit_element_reject.ail(and the re-runrawbuf_4_paramin_reject.ail). - The message renders
(allowed: one of ["Bool", "Float", "Int"])— a RustVec<String>Debug rendering (square brackets, double-quoted, alphabetised). The ledger (design/models/0007§4) and the prior B3 framing write the set as{Int, Float, Bool}. The information is now present (that was the #48 win), but the surface form is a leaked internal representation: quotes and brackets an LLM author would not expect, and the alphabetical order obscures the natural Int/Float/Bool grouping. - Why friction: the task (self-correct from the diagnostic) completes, but the rendering is verbose/leaky vs. the clean set notation the ledger implies.
- Recommended action: plan (tidy) — render the set as
{Int, Float, Bool}(brace-set, no quotes) to match the ledger. Filed as issue #52.
[bug] F7 — (new T …) drops the explicit element annotation; an unobserved element type crashes codegen (issue #51)
- Surfaced during triage of F2/F5 against the fresh binary, not in the fieldtester's original run (the stale binary masked it).
- A buffer whose element type is never observed by a later
get/setpassesail checkand then crashesail build:This hits two shapes: a legitimate allowed element used only for its size ((let b (new RawBuf (con Int) 3) (app print (app RawBuf.size b))) ; ail check -> ok ; ail build --alloc=rc -> Error: module raw_buf: def RawBuf_new__Unit: ; internal: intrinsic fn RawBuf_new__Unit has no registered interceptRawBuf<Int>above — the author wrote(con Int), build still crashes), and a forbidden element constructed locally ((new RawBuf (con Unit) 3)— not rejected at check, same crash). - Root: the
(new T …)desugar dropsNewArg::Type(crates/ailang-core/src/desugar.rs:1053);RawBuf.new'saappears only in its result, so the call-site args never pin it; an unpinned forall var defaults toUnitin monomorphisation (crates/ailang-codegen/src/subst.rs:56), andRawBuf_new__Unithas no registered intercept. Verified end-to-end. - Why bug: a
check-clean program crashesbuild(the exact class the raw-buf usability mandate set out to eliminate), and the author's explicit type annotation is silently discarded — contrary to the language identity (mandatory annotations; every definition carries its full type). - Recommended action: brainstorm — honouring the annotation reverses the milestone's "recover element by inference" decision (§ Term::New desugar) and there is no type-ascription term in the AST today, so this is a spec-level decision, not a one-line patch. Full analysis and candidate directions in issue #51.
[process] F8 — the field test ran against a stale release binary
- The fieldtester executed
target/release/ailwithout rebuilding it, so its entire run reflected pre-#50 code. This produced one false positive (F2) and masked a real defect class (F7) until the orchestrator re-verified against a fresh build. - Why it matters: a field test that runs stale silently inverts its own purpose — it reports the previous shipped state as current. Every "works"/"fails" line is suspect unless the binary under test is known to include the changes the test claims to exercise.
- Recommended action: plan (process) — the field-test harness must
build the binary from the current tree before running (a
cargo build --release -p ailstep, or run throughcargo run), so the consumer always exercises HEAD. Tracked as a skill/profile fix to the fieldtester dispatch.
Recommendation summary
| Finding | Class | Action |
|---|---|---|
F1 §Series own-field form unparsable |
spec_gap | ratify / tighten ledger (done this cycle) |
| F2 bare RawBuf ADT field unresolved despite #50 | retracted | none — stale-binary false positive |
| F3 Series-substrate composite works (qualified) | working | carry-on |
| F4 borrow helpers compose; Bool/Int fills correct | working | carry-on |
| F5 param-in rejects core primitive; names set | working | carry-on (signature-position only; cf. F7) |
| F6 param-in set rendered as Rust-debug list | friction | plan (tidy) — issue #52 |
F7 (new T …) drops element annotation; unobserved element crashes codegen |
bug | brainstorm — issue #51 |
| F8 field test ran against a stale release binary | process | plan (harness fix) |
Orthogonal observations (not raw-buf findings, noted in passing)
- Float comparison in a RawBuf read loop needs
float_gt(notgt); theno-instancediagnostic is exemplary — it names the missing instance, the design rationale (IEEE-754 partial order), the exact replacement ops (float_gtand siblings), and the contract ref. A model diagnostic; no action. - The known #49 Str-accumulator-loop leak was not hit — every fill loop
here uses
RawBuf.set(in-place), so no superseded-heap-binder dec is needed and all four new fixtures reportlive=0.