# Fieldtest — raw-buf milestone (milestone-7) — 2026-06-02

**Status:** Draft — awaiting orchestrator triage
**Author:** fieldtester (dispatched by fieldtest skill, milestone-scope variant)

## Scope

The raw-buf milestone (#7) ships `RawBuf T` — AILang's first
kernel-tier *base* extension: a content-addressed, mode-tracked,
fixed-size flat buffer of primitive elements. The milestone promises
that an LLM author can write realistic RawBuf-centric programs and
hit no surprises: construct via `(new RawBuf <elem> <size>)`, fill
and read via `RawBuf.set` / `RawBuf.get`, use size-only buffers via
`RawBuf.size`, vary the element type across `{Int, Float, Bool}`,
have a forbidden element type (e.g. `Unit`) rejected at `check`
rather than crash at codegen (the #51 fix), and thread an owned
buffer through functions under the uniqueness/mode discipline.

This is the milestone-close fieldtest. Four end-to-end scenarios
were derived top-down from the milestone promise — each a realistic
downstream task — rather than re-running the per-cycle axes the
earlier per-iteration fieldtests (specs 0058, 0059) already covered.

Binary exercised: `cargo run -q -p ail` against the current working
tree (dev profile, freshly built from HEAD). Full native build of
scenario 1 additionally went through `ail build -o … + clang`.

## Examples

### examples/fieldtest/mrb_1_histogram.ail — Int fill/read loop with computed result
- Allocates a 5-slot `Int` RawBuf, fills it via an `own → own`
  recursive `fill` helper (slots 0,7,14,21,28), then sums it back
  through a `borrow` reader and prints the total.
- Fits the "buffer fill/read loop" + "threading through a function
  under uniqueness/mode" axes: `fill` consumes and returns the owned
  buffer per `RawBuf.set`; `sum` reads it through a borrow.
- Outcome: `check` ok (exit 0); `run` printed `70` (expected 70);
  full `build` to native binary printed `70`. Matched.

### examples/fieldtest/mrb_2_float_smooth.ail — Float element variety + threading
- Allocates a 2-slot `Float` RawBuf, stores 3.0 and 4.0 via an
  `own → own` `store_pair` helper, reads both slots through a
  `borrow` `average` helper, prints the mean.
- Fits the "element-type variety (Float)" + "threading a buffer
  through a function" axes; the buffer crosses two function
  boundaries under own/borrow.
- Outcome: `check` ok; `run` printed `3.5` (expected 3.5). Matched.

### examples/fieldtest/mrb_3_bool_capacity.ail — size-only Bool buffer (#51 legitimate case)
- Allocates an 8-slot `Bool` RawBuf and prints its capacity via
  `RawBuf.size` only — never `get`/`set`.
- Fits the "size-only buffer" + "element-type variety (Bool)" axes:
  an explicit `Bool` element type is named at construction but only
  `.size` is ever observed — the #51 legitimate-but-unobserved case.
- Outcome: `check` ok; `run` printed `8` (expected 8). Matched.

### examples/fieldtest/mrb_4_unit_element_reject.ail — forbidden element type (Unit)
- `main` names `Unit` as the RawBuf element type, then only sizes
  the buffer and prints the size.
- Fits the "forbidden element type — must be rejected at check, not
  crash at build" axis (the #51 fix).
- Outcome: `check` rejected at exit 1 with `param-not-in-restricted-set`
  naming `Unit`; `build` also stopped at the same check error,
  codegen never reached, no crash. Matched the promise exactly.

## Findings

### [working] RawBuf end-to-end is clean across all three permitted element types
- Examples: mrb_1 (Int), mrb_2 (Float), mrb_3 (Bool), plus the
  build-to-native path on mrb_1.
- What happened: construct / set / get / size all behaved correctly;
  computed results were exact (`70`, `3.5`, `8`); `check` reported
  `ok (N symbols across 3 modules)`; the native binary produced the
  same output as `run`. The `own → own` threading of a buffer
  through `fill` / `store_pair` and the `borrow` reads in `sum` /
  `average` / `capacity` all checked clean with no spurious
  use-after-consume or clone friction — the author never had to
  reach for a `clone` or an awkward re-bind beyond the natural
  `(let buf (app … buf …) …)` linear-threading idiom shown in the
  example corpus.
- Why working: the new surface was reached for naturally and
  produced correct results on the first try once the fixtures were
  in canonical form. This is the central milestone-promise win.
- Recommended action: carry-on.

### [working] Forbidden element type is rejected at check with a precise diagnostic (the #51 fix)
- Example: mrb_4.
- What happened, verbatim:
  `error: [param-not-in-restricted-set] main: type-arg `Unit` is not in the restricted set for type-variable `a` of `raw_buf.RawBuf` (allowed: one of {Bool, Float, Int})`
  The `--json` form carries a structured `ctx`
  (`{"type":"raw_buf.RawBuf","var":"a","found":"Unit","allowed":["Bool","Float","Int"]}`).
  `ail build` stops at the same error — codegen is never reached, no
  crash, no `RawBuf_new__Unit`-style unregistered-intercept failure.
- Why working: this is precisely the #51 promise — reject at `check`,
  not crash at codegen. The diagnostic names the offending type, the
  type-variable, the type, and the allowed set; it is actionable.
- Recommended action: carry-on.

### [spec_gap] The kernel-extensions whitepaper shows bare-mode value params that the parser now rejects
- Surfaced while drafting mrb_1 / mrb_2 (first run before fix).
- What happened: I wrote scalar value params without a mode —
  `(params (own (con RawBuf (con Int))) (con Int) (con Int) (con Int))`
  — copying the shape shown in the public whitepaper
  `design/models/0007-kernel-extensions.md` (the `sum_window_step` /
  `emit_if_full` / `Series.at` signatures at lines 60-69, 84-86 use
  bare `(con Int)` / `(con Float)` slots). The parser rejected it:
  `error: [surface-parse-error] parse error in fn-type-slot: fn-type slot requires a mode: write (own T) or (borrow T) at byte 310`.
  Adding `(own …)` to every value slot fixed it and the examples
  then checked and ran clean.
- Why spec_gap: the parser is internally consistent — *every*
  fn-type slot requires a mode, and that is a defensible contract
  (it is the `bare_slot_reject` behaviour). But the whitepaper, which
  is part of the public interface an LLM author reads to learn the
  surface, still displays the bare-mode form throughout its worked
  examples. An author who pattern-matches the whitepaper writes
  programs that fail to parse. Two readings were plausible (value
  params get an implied mode vs. every slot must be explicit); the
  shipped reading is "every slot explicit", but the whitepaper
  teaches the other.
- Recommended action: tighten the design ledger — update the
  `design/models/0007-kernel-extensions.md` worked examples to put
  explicit `(own …)` / `(borrow …)` on every value param slot so the
  public surface matches the shipped parser contract. (Whitepaper
  drift, not a code bug.)

## Recommendation summary

| Finding | Class | Action |
|---|---|---|
| RawBuf end-to-end clean across Int/Float/Bool | working | carry-on |
| Unit element rejected at check (#51 fix) | working | carry-on |
| Whitepaper shows bare-mode value params parser rejects | spec_gap | tighten the design ledger (models/0007) |
