Second re-carve. Planning raw-buf.3 (via plan-recon) surfaced that
RawBuf's ops are a THIRD intrinsic shape the intrinsic-bodies mechanism
never handled: type-scoped polymorphic top-level intrinsics (forall a,
param-in {Int,Float,Bool}, called as RawBuf.get). The existing bijection
+ symbol story covers only monomorphic top-level intrinsics (answer,
float_*) and per-type class-method instances (eq__Int).
The gap (both confirmed in source by recon + grounding-check):
- mono_symbol_n mints <base>__<T> from the bare fn name, so RawBuf.get
@ Int → get__Int — the RawBuf scope never enters the symbol;
new/get/set/size @ Int would collide with any other poly free fn of
those (very common) names.
- the bijection collector's Def::Fn arm records the bare name "get" as
one marker, matching neither the per-type entries nor their symbols;
one polymorphic marker must map to N per-element entries, which the
1-marker-to-1-entry bijection cannot express.
So raw-buf.3 is no longer "add 12 table rows" — it is a new language
mechanism. Re-carve (3 remaining iterations):
raw-buf.3 — type-scoped polymorphic intrinsic mechanism: scope-
qualified mono symbols (RawBuf_get__Int) + bijection-
collector expansion over param-in. Built and ratified
standalone on the stub (a StubT.peek op + its 2 scoped
entries + mono/bijection unit tests), the prep pattern
prep.1/.2/.3 used. No RawBuf, no Term::New dependency.
raw-buf.4 — RawBuf payload (module + 12 scoped entries + drop) +
Term::New desugar ((new RawBuf ...) sugar, removes both
deferral arms). Pure consumer of .3. Worked program → 60.
raw-buf.5 — kernel_stub retirement (peek + answer + stub leave;
RawBuf carries all roles).
Decided design (Option A): scope-qualified symbols, not bare. Rationale
is semantic — the type-scoped call convention RawBuf.get should carry
through to a collision-free, IR-legible symbol; bare get__Int is a
latent collision landmine on the most-reused op names. Removing that
collision class is itself feature-acceptance criterion-3 evidence.
The raw_buf module Form-A in § Concrete code shapes is gate-verified
(ail check → ok, 35 symbols / 3 modules). Grounding-check PASS: all 10
current-behaviour assumptions ratified by named green tests; the
Term::New codegen-deferral (lib.rs ~2096 + ~3298) is the same
about-to-be-deleted prep.2 transient, reported-not-blocked (consistent
with the override logged on 3ec406e).
27 KiB
raw-buf — Design Spec
Date: 2026-05-29 (revised twice 2026-05-29) Status: Draft — awaiting user spec review Authors: orchestrator + Claude
Revision 1 note (post intrinsic-bodies). raw-buf.1 (intercept registry) shipped (
140a0c0). Theintrinsic-bodiesmilestone (#9) then landed and removed the placeholder-body convention the original spec relied on — kernel-tier fn bodies are now typechecked, soRawBuf.getcannot ship a type-lying placeholder body; it must be(intrinsic). And an(intrinsic)marker now requires a matchingINTERCEPTSentry as a lockstep pair, so a "manifest-only / codegen-deferred" intermediate state is forbidden. The kernel-manifest and codegen therefore land together.Revision 2 note (post raw-buf.2 + plan-recon). raw-buf.2 (the family-crate rename) shipped (
fbdbe74). Planning raw-buf.3 then surfaced a deeper gap: RawBuf's operations are a third intrinsic shape theintrinsic-bodiesmechanism never handled. The bijection
- symbol story was built for (a) monomorphic top-level intrinsics (
answer,float_*) and (b) per-type class-method instances (eq__Int). RawBuf's ops are type-scoped polymorphic top-level intrinsics (forall awithparam-in {Int,Float,Bool}, called asRawBuf.get). For that shape:
mono_symbol_nmints<base>__<T>from the bare fn name, soRawBuf.get@ Int becomesget__Int— theRawBufscope never enters the symbol.new/get/set/size@ Int would collide with any other polymorphic free fn of those (very common) names.- The bijection collector's
Def::Fnarm records the bare name"get"as one marker — matching neither the per-type entries nor their symbols. One polymorphic marker must map to N per-element entries, which the current 1-marker-to-1-entry bijection cannot express.So raw-buf.3 is no longer "add 12 table rows" — it is a new language mechanism (type-scoped polymorphic intrinsics) that RawBuf is the first consumer of. The remaining work is re-carved into a mechanism prep (raw-buf.3), the RawBuf payload (raw-buf.4), and the stub retirement (raw-buf.5). The decided naming scheme is scope-qualified mono symbols (
RawBuf_get__Int); the mechanism is ratified standalone on the stub (the prep pattern that prep.1/.2/.3 used) before RawBuf consumes it. The Goal, slab layout, and feature-acceptance argument are unchanged.
Goal
Ship RawBuf a — the first kernel-tier base extension — as a
mutable, indexed, bounded-size flat buffer of primitive elements
({Int, Float, Bool}). RawBuf is the storage primitive that
unblocks two downstream needs both already captured in the
backlog:
- Series (milestone #8) — a bounded ring buffer with
financial-style indexing, implemented as a library extension
(pure AILang) wrapping
RawBuf aplus bookkeeping fields. - Embedding-ABI batch FFI (subsumes closed #2) — the M5 friction-harvest measured per-tick FFI at ~206 ns/tick on real EURUSD volume (~658 ms / 3.19M ticks). A contiguous primitive slice amortises that per-tick cost via a batch crossing. RawBuf is that primitive.
The milestone also fulfils the architectural commitment from the kernel-extensions whitepaper § "Plugin contract": the migration of the hardcoded primitive-instance intercepts into one registry, triggered by the first real base extension shipping. raw-buf.1 already discharged that migration; RawBuf is now the first real-payload consumer of that registry — and the first consumer of the type-scoped polymorphic intrinsic mechanism raw-buf.3 adds.
Architecture
Five iterations. raw-buf.1 and raw-buf.2 are done; .3/.4/.5 remain.
-
Intercept registry (raw-buf.1) — DONE (
140a0c0). Lifted the hard-codedtry_emit_primitive_instance_bodymatch into the registry tableINTERCEPTSincrates/ailang-codegen/src/intercepts.rs; the dispatch site is anintercepts::lookupcall. (The subsequentintrinsic-bodiesmilestone added the(intrinsic)marker, theTerm::Intrinsicleaf, and the marker ↔ entry bijection pin on top of it.) -
Kernel family-crate rename (raw-buf.2) — DONE (
fbdbe74). Renamedcrates/ailang-kernel-stub/→crates/ailang-kernel/, reshaped as a family-crate (src/lib.rshub re-exportingsrc/kernel_stub/{mod.rs, source.ail}). Public symbolSTUB_AILpreserved; zero behavioural change. -
Type-scoped polymorphic intrinsic mechanism (raw-buf.3) — The new language mechanism RawBuf needs, built and ratified standalone on the stub (no RawBuf yet), mirroring how prep.1/.2/.3 built kernel-extension mechanisms ratified by the stub. Two coupled pieces:
- Scope-qualified mono symbols. When a type-scoped call
T.f(prep.1 spelling) resolves to a polymorphic opfof TypeDefT, monomorphisation mintsT_f__<elem>(e.g.StubT_peek__Int) — the TypeDef scope is part of the symbol, so two different types' identically-named ops never collide, and the emitted IR is legible (it reads as "theT.fspecialised to<elem>"). The codegen intercept dispatch keys on this scoped symbol. - Bijection expansion over
param-in. The bijection collector (intercepts::tests::workspace_intrinsic_markers) gains an arm: a top-level(intrinsic)op that is polymorphic and type-scoped to a TypeDef withparam-in (a S1 S2 …)expands to one markerT_f__Siper allowed element typeSi. One polymorphic marker → N per-element entries, all pinned byintercepts_bijection_with_intrinsic_markers. Ratified by adding ONE polymorphic type-scoped(intrinsic)op to the stub'sStubT(StubT.peek : (borrow (StubT a)) -> a) plus its 2 scope-qualifiedINTERCEPTSentries (StubT_peek__Int,StubT_peek__Float— StubT'sparam-inis{Int, Float}) and a unit test asserting the scoped symbols + the bijection.peekretires with the stub in raw-buf.5, exactly asanswerdoes. No running E2E here (constructing aStubTneeds the Term::New desugar that lands in raw-buf.4); the mechanism is unit-test ratified.
- Scope-qualified mono symbols. When a type-scoped call
-
RawBuf payload + Term::New desugar (raw-buf.4) — RawBuf as a clean consumer of the raw-buf.3 mechanism, plus the
(new …)construction sugar. Adds:- The
raw_bufsubmodule (src/raw_buf/{mod.rs, source.ail}) with theRawBufTypeDef (param-in (a Int Float Bool)) and the four opsnew/get/set/size, each an(intrinsic)marker. - The 12 scope-qualified
INTERCEPTSentries (RawBuf_{new,get,set,size}__{Int,Float,Bool}) + emit fns (@ailang_rc_alloc+ getelementptr + load/store) — mechanical now that raw-buf.3 built the naming + bijection machinery. - The
Term::Newdesugar(new T <types…> <values…>)→(app T.new <values…>)(general —(new StubT 42)benefits too), removing BOTH codegenTerm::Newdeferral arms (crates/ailang-codegen/src/lib.rs~2094lower_term+ ~3296 synth path) that prep.2 left marked "milestone raw-buf". - Drop for primitive-element RawBuf: a single
@ailang_rc_releaseon the slab pointer. ailang-surfacewiring:parse_raw_buf+ workspace injection (raw_buf kernel-tier auto-imported; workspace count 4 → 5). The worked consumer program runs end-to-end and prints60.kernel_stub(withpeek+answer) stays alongside (count 5).
- The
-
kernel_stub retirement (raw-buf.5) — RawBuf now subsumes every ratification role the stub held:
Term::Newend-to-end (the(new RawBuf …)consumer),param-inreject (the Str reject fixture), kernel-tier auto-import, the monomorphic intrinsic path (RawBuf has no nullary intrinsic, but the path is exercised by the prelude'sfloat_*), and — crucially — the type-scoped polymorphic intrinsic mechanism (RawBuf's four ops). Deletesrc/kernel_stub/+ its re-export, dropparse_kernel_stub+ its injection, remove theanswerand the twoStubT_peek__*INTERCEPTSentries + their emit fns (their markers leave with the stub — the bijection lockstep holds), deletekernel_stub_module_round_trips, re-baselineworkspace_pinto 4 (raw_buf in, kernel_stub out), and updatedesign/INDEX.md+design/models/0007-kernel-extensions.md. Theailang-kernelcrate stays as the family-crate home for future kernel-tier modules.
Ordering rationale: .3 isolates the risky mono+bijection mechanism
change (proven on the stub) from RawBuf's payload diff; .4 lands
RawBuf as a mechanical consumer plus the orthogonal (new …)
desugar; .5 removes the now-redundant stub without touching the new
RawBuf code. Each bijection-registry change is isolated to one diff
(peek entries added in .3, 12 RawBuf entries in .4, peek+answer
removed in .5).
Concrete code shapes
Primary — the raw-buf.3 mechanism ratifier (StubT.peek)
raw-buf.3 adds one polymorphic type-scoped (intrinsic) op to the
stub to prove the mechanism. The op and its scope-qualified entries
are the iteration's concrete deliverable (the must-pass evidence
that scoped symbols + bijection expansion work). The fn is appended
to the existing STUB_AIL source (crates/ailang-kernel/src/kernel_stub/source.ail):
(fn peek
(doc "Ratifies the type-scoped polymorphic intrinsic mechanism: StubT.peek is polymorphic over a (param-in {Int,Float}) and type-scoped to StubT. Codegen intercepts StubT_peek__Int / StubT_peek__Float emit a load from the Stub payload. Retires with the stub in raw-buf.5.")
(type (forall (vars a) (fn-type (params (borrow (con StubT a))) (ret a))))
(params s)
(intrinsic))
(Shown text, not ail: kernel_stub is a reserved built-in
module name, so the source cannot be ail check-ed standalone —
reserved-module-name. Ratification is the round-trip test + the
bijection/mono unit tests, not a standalone parse.) Its two
scope-qualified entries (StubT_peek__Int, StubT_peek__Float)
land in INTERCEPTS together with the bijection-collector
extension; the bijection test pins the 1-marker→2-entries mapping.
Primary — the raw_buf kernel-tier module (raw-buf.4, gate-verified)
The exact Form-A source the raw_buf submodule embeds. It parses
and type-checks against the current tool (ail check → ok); the
four (intrinsic) markers are legal because the module is
(kernel). RawBuf.get returns the bare element type a; set
is linear (own in, own out).
(module raw_buf
(kernel)
(data RawBuf (vars a)
(doc "Mutable, indexed, fixed-size flat buffer of primitive elements. Opaque single-ctor handle; codegen intercepts emit raw alloc/load/store over an @ailang_rc_alloc slab. Element type restricted to {Int, Float, Bool} via param-in.")
(ctor B a)
(param-in (a Int Float Bool)))
(fn new
(doc "Allocate an uninitialised RawBuf of capacity n. Compiler-supplied (intrinsic) body; codegen intercept RawBuf_new__<T> emits @ailang_rc_alloc plus the i64 size header. Element bytes are uninitialised; caller must set before get.")
(type (forall (vars a) (fn-type (params (con Int)) (ret (own (con RawBuf a))))))
(params n)
(intrinsic))
(fn get
(doc "Indexed read. UB if i >= (size b); caller checks bounds via size. Compiler-supplied (intrinsic) body; codegen intercept RawBuf_get__<T> emits getelementptr plus load.")
(type (forall (vars a) (fn-type (params (borrow (con RawBuf a)) (con Int)) (ret a))))
(params b i)
(intrinsic))
(fn set
(doc "Indexed write. Linear: own in, own out. Under uniqueness in-place; under shared copy-on-write (Issue #22). Compiler-supplied (intrinsic) body; codegen intercept RawBuf_set__<T> emits getelementptr plus store.")
(type (forall (vars a) (fn-type (params (own (con RawBuf a)) (con Int) a) (ret (own (con RawBuf a))))))
(params b i v)
(intrinsic))
(fn size
(doc "Element count. Compiler-supplied (intrinsic) body; codegen intercept RawBuf_size__<T> emits a single i64 load from the slab header.")
(type (forall (vars a) (fn-type (params (borrow (con RawBuf a))) (ret (con Int)))))
(params b)
(intrinsic)))
The (ctor B a) is a nominal fiction for the type system: it
gives RawBuf an identity and keeps a non-phantom. Real code
never constructs it — every operation is codegen-intercepted, and
(new RawBuf …) desugars to (app RawBuf.new …), not to a
(term-ctor RawBuf B …).
Primary — the worked consumer program (raw-buf.4 acceptance)
The program an LLM author naturally writes for "I need a small
mutable indexed buffer to score N values and read them back." It is
the feature-acceptance criterion's empirical evidence. It uses the
(new RawBuf …) construction sugar (available once raw-buf.4 lands
the Term::New desugar). It references the raw_buf module, injected
only from raw-buf.4 — so it does not check against the current
tool (RawBuf is not yet a workspace module). Left unlabeled
deliberately: it describes post-raw-buf.4 surface.
(module raw_buf_demo
(fn main
(type (fn-type (params) (ret (con Int))))
(params)
(body
(let buf (new RawBuf (con Int) 3)
(let buf (app RawBuf.set buf 0 10)
(let buf (app RawBuf.set buf 1 20)
(let buf (app RawBuf.set buf 2 30)
(app + (app RawBuf.get buf 0)
(app + (app RawBuf.get buf 1)
(app RawBuf.get buf 2))))))))))
; ail check && ail build && ail run → prints 60
No (import raw_buf) needed: RawBuf is visible via kernel-tier
auto-import (prep.3). RawBuf.set/.get/.new resolve via
type-scoped namespacing (prep.1) and mono to the scope-qualified
symbols RawBuf_set__Int / RawBuf_get__Int / RawBuf_new__Int.
The element type a = Int is fixed by inference: it flows from the
(app RawBuf.set buf 0 10) value arg (10 : Int) through the
linear own (RawBuf a) threading. (For the direct-call equivalent
without the (new …) sugar — (let buf (app RawBuf.new 3) …) —
the same inference applies; the desugar reduces (new RawBuf (con Int) 3) to exactly that.)
Primary — the param-in reject fixture (raw-buf.4 must-fail)
(module raw_buf_reject_str
(fn cant_str
(type (fn-type (params (borrow (con RawBuf (con Str)))) (ret (con Str))))
(params buf)
(body (app RawBuf.get buf 0))))
; ail check → param-not-in-restricted-set: type Str not in {Int, Float, Bool}
Must fail at the checker with the ParamNotInRestrictedSet
diagnostic shipped in prep.3 (fieldtest F8) — the must-fail evidence
that param-in is alive on a real-payload extension. Depends on
raw_buf being injected, so not gate-checkable today (unlabeled).
Secondary — scope-qualified mono symbol (raw-buf.3, implementation)
Current behaviour: mono_symbol_n(base, types)
(crates/ailang-check/src/mono.rs:521) joins base and each
type's mono suffix with __. For a bare-named poly free fn get
it yields get__Int — no type scope. The bijection collector's
Def::Fn arm (intercepts.rs ~490) inserts the bare f.name.
raw-buf.3 change (shape — exact threading mapped by plan-recon):
a type-scoped call T.f carries the resolved TypeDef T; mono
uses a scope-qualified base T_f so the symbol is T_f__<elem>
(StubT_peek__Int, later RawBuf_get__Int). The bijection
collector gains an arm that, for a polymorphic top-level
(intrinsic) op scoped to a TypeDef with param-in (a S1 …),
emits T_f__Si for each Si. Both the mono mint and the collector
must compute the identical string (the bijection test is the pin).
Secondary — INTERCEPTS entry shape (raw-buf.4, implementation)
The registry struct (raw-buf.1) is Intercept { name, expected_params, expected_ret, wants_alwaysinline, emit }. One
representative of the 12 RawBuf entries (the exact symbol strings
are produced by the raw-buf.3 mechanism and pinned by the bijection
test):
// crates/ailang-codegen/src/intercepts.rs — appended to INTERCEPTS
Intercept {
name: "RawBuf_new__Int", // scope-qualified mono symbol of RawBuf.new @ Int
expected_params: &["i64"], // capacity n
expected_ret: "ptr", // slab base pointer
wants_alwaysinline: false,
emit: emit_rawbuf_new_int, // @ailang_rc_alloc(8 + n*8); store i64 n at offset 0
},
// + RawBuf_new__{Float,Bool}, RawBuf_get__{Int,Float,Bool},
// RawBuf_set__{Int,Float,Bool}, RawBuf_size__{Int,Float,Bool}
Secondary — Term::New desugar (raw-buf.4, implementation)
Term::New { type_name, args } with args: Vec<NewArg>
(NewArg::Type | NewArg::Value, both parsed + round-trip ratified).
The desugar rewrites it to an application of the type's new fn,
passing only the NewArg::Value args:
(new RawBuf (con Int) 3) ; Term::New { type_name: "RawBuf",
; args: [Type(con Int), Value(3)] }
──desugar──▶
(app RawBuf.new 3) ; a = Int resolved by inference from use
Important: the AST has no type-ascription term and Term::App
carries no type-args, so the desugar drops the NewArg::Type
and the element type is recovered by ordinary inference (it flows
from how the result is used — see the worked program). This
suffices for every real program that uses the buffer; an isolated
(new RawBuf (con Int) 3) with no constraining use would leave a
ambiguous, which is acceptable (no real program does that). After
this pass no Term::New survives into codegen; both deferral arms
(lib.rs ~2094 lower_term, ~3296 synth) are removed. The prep.2
checker already accepts Term::New (it parses, round-trips, and
typechecks via the new def's signature); whether it needs any
extension for the NewArg::Type-bearing form is a raw-buf.4
verify-first task (plan-recon maps the current Term::New typecheck
path).
Secondary — slab layout (raw-buf.4)
[ size : i64 ][ elem_0 ][ elem_1 ] ... [ elem_{size-1} ]
^ ^ ^
offset 0 offset 8 offset 8 + size*sizeof(T)
Element widths: Int = 8, Float = 8, Bool = 1. Header is i64,
8-byte aligned; element payload follows directly. @ailang_rc_alloc
is called with 8 + size * sizeof(T) and returns the slab base
ptr. The size header is self-describing, so drop and RawBuf.size
need no side-table. Drop is a single @ailang_rc_release on the
slab pointer — primitive elements carry no recursive drops; the
per-TypeDef drop generation gets a "primitive-element-RawBuf" arm.
Components
| Component | Iter | Files |
|---|---|---|
| Intercept registry | raw-buf.1 (DONE) | crates/ailang-codegen/src/intercepts.rs + dispatch in lib.rs |
| Kernel family-crate rename | raw-buf.2 (DONE) | crates/ailang-kernel/ (renamed); ailang-surface dep + loader.rs; doc/ledger rows |
| Type-scoped polymorphic intrinsic mechanism | raw-buf.3 | scope-qualified mono mangling in crates/ailang-check/src/mono.rs (+ wherever prep.1 type-scoped resolution records the TypeDef, per recon); bijection-collector expansion arm in crates/ailang-codegen/src/intercepts.rs (workspace_intrinsic_markers); StubT.peek fn added to crates/ailang-kernel/src/kernel_stub/source.ail + 2 StubT_peek__* entries + emit fns; kernel_stub_module_round_trips updated for the new fn; mono/bijection unit tests |
| RawBuf payload + Term::New desugar | raw-buf.4 | new crates/ailang-kernel/src/raw_buf/{mod.rs, source.ail} + lib.rs re-export; parse_raw_buf + injection in crates/ailang-surface/src/loader.rs + re-export in lib.rs; 12 entries + emit fns in intercepts.rs; Term::New desugar in crates/ailang-core/src/desugar.rs + removal of both codegen deferral arms in crates/ailang-codegen/src/lib.rs; primitive-element-RawBuf drop arm; workspace_pin 4 → 5; round-trip + E2E tests |
| kernel_stub retirement | raw-buf.5 | delete crates/ailang-kernel/src/kernel_stub/ + re-export; drop parse_kernel_stub + injection; remove answer + StubT_peek__* entries + emit fns in intercepts.rs; delete kernel_stub_module_round_trips; workspace_pin 5 → 4; design/INDEX.md + design/models/0007-kernel-extensions.md |
Data flow
Mechanism (raw-buf.3):
- A consumer call
T.f(type-scoped spelling, prep.1) resolves to the polymorphic opfof TypeDefT, recordingTas the resolution scope. - Monomorphisation at element type
Simints the scope-qualified symbolT_f__Si; codegen emits a fn of that name; the intercept dispatch keys on it. - The bijection collector expands each polymorphic type-scoped
(intrinsic)op over its TypeDef'sparam-inset into the sameT_f__Sistrings, and requires each to resolve to anINTERCEPTSentry (and vice versa, minusOPTIMISATION_ONLY).
Build-time (raw-buf.4 onwards):
- CLI loads workspace →
ailang-surfaceinjectsraw_bufas a kernel-tier module (kernel: true). checkwalks consumer code;(con RawBuf (con Int))resolves toraw_buf.RawBufvia kernel-tier auto-import (prep.3).param-inonarejects non-{Int,Float,Bool} instantiations via the existingParamNotInRestrictedSetdiagnostic.
Codegen-time (raw-buf.4 onwards):
- The desugar replaces every
Term::Newwith(app T.new <values>); the element type is inferred from use. - Codegen reaches the registry dispatch site; if the fn's
scope-qualified mono symbol resolves via
intercepts::lookup, the entry'semitruns (the(intrinsic)marker has no body). RawBuf_new__Intemits the alloc;RawBuf_set__Intastore i64;RawBuf_get__Intaload i64;RawBuf_size__Inta header load.
Workspace module count: prelude + kernel_stub + 2 test fixtures
= 4 today. raw-buf.4 adds raw_buf → 5. raw-buf.5 retires kernel_stub
→ 4. The workspace_pin test pins each transition.
Error handling
Two consumer-facing diagnostics; both already shipped, no new arms:
param-not-in-restricted-set(prep.3, fieldtest F8) — fires on(con RawBuf (con Str))or any non-{Int,Float,Bool} element.new-type-not-constructible(prep.3, fieldtest F5) — fires on(new SomeType …)whenSomeTypehas no(fn new …). raw_buf shipsnew, so the worked program passes.
The codegen Term::New deferral arms (lib.rs ~2094 + ~3296,
message "… milestone raw-buf") are removed in raw-buf.4 once the
desugar eliminates Term::New before codegen. Neither is
user-facing — both are the prep.2 deferral marker. They have no test
pinning them today (internal CodegenError::Internal); raw-buf.4's
build-and-run E2E (the worked program printing 60) is the forward
ratification.
Bounds checks are not in scope: RawBuf.get/.set are UB if `i
= size(b)
; caller checks viaRawBuf.size`. A future axis, no backlog item yet.
Testing strategy
raw-buf.3 (mechanism):
- Mono unit test: a type-scoped poly intrinsic op mints the
scope-qualified symbol (
StubT_peek__Int,StubT_peek__Float) — asserts the new mangling directly, no running program needed. - Bijection:
intercepts_bijection_with_intrinsic_markersstays green with thepeekmarker expanded to its 2 scope-qualified entries (proves the 1-marker→N-entries expansion). kernel_stub_module_round_tripsupdated for the addedpeekfn (parse → serialise → round-trip holds).- Full suite green; no workspace-count change (peek is added to the existing stub, no new module).
raw-buf.4 (RawBuf payload + desugar):
- Round-trip
raw_buf_module_round_trips(mirrorkernel_stub_module_round_trips): assertkernel == true, nameraw_buf,RawBufTypeDefparam_inforacontains Int/Float/Bool. - E2E
raw_buf_int_e2e: the worked program —ail check && ail build && ail run→ prints60. - E2E
raw_buf_float_e2e/raw_buf_bool_e2e: Float and Bool variants (Bool catches any i1/i8 packing question). - E2E
raw_buf_param_in_reject_e2e: the Str reject fixture —ail checkexits non-zero withparam-not-in-restricted-set. - Term::New desugar ratified by the
(new RawBuf …)form building and running (it would hit the now-removed deferral arm otherwise). - Drop ratification: allocate a RawBuf, let it go out of scope, no leak (re-uses the existing leak-check harness).
- Bijection green with the 12 RawBuf entries added.
workspace_pincount 4 → 5 (+contains_key("raw_buf")).
raw-buf.5 (retirement):
workspace_pinre-baselines 5 → 4 (raw_buf in, kernel_stub out).kernel_stub_module_round_tripsdeleted;raw_buf_module_round_tripscarries the invariant forward.- Bijection green after
answer+StubT_peek__*markers + entries leave together with the stub. - Full suite green; the diff is a removal.
Acceptance criteria
The milestone ships when:
- The worked consumer program runs end-to-end:
ail check && ail build && ail runprints60(raw-buf.4). - The param-in reject fixture exits at
ail checkwith the prep.3param-not-in-restricted-setdiagnostic (raw-buf.4). - RawBuf's four ops are
(intrinsic)markers, each mono'd to a scope-qualified symbol (RawBuf_<op>__<T>) with a matchingINTERCEPTSentry;intercepts_bijection_with_intrinsic_markersis green (the mechanism in raw-buf.3, the 12 entries in raw-buf.4). Term::Newno longer reaches codegen — the desugar eliminates it and both deferral arms (lib.rs~2094 + ~3296) are removed (raw-buf.4).kernel_stubis retired andraw_buftakes over its ratification roles (raw-buf.5).- Workspace test suite green at each iteration boundary, plus the new tests in § Testing strategy.
Feature-acceptance argument
Applied per design/contracts/0004-feature-acceptance.md:
-
LLM author reaches for it naturally. RawBuf is the only path to mutable indexed storage in AILang — the language has no other "fill N slots and read them back" primitive. The worked consumer program is what an LLM author writes for that need; there is no alternative shape to reach for.
-
Measurable improvement. Embedding-ABI M5 measured per-tick FFI at ~206 ns/tick (~658 ms / 3.19M EURUSD ticks). RawBuf as the batch-FFI primitive amortises that per-tick cost across one crossing. The Series milestone (#8) is also blocked on RawBuf; shipping raw-buf removes that block.
-
No reintroduced bug class. Mutation stays mode-tracked:
RawBuf.setisown → own, not effect-tracked; noRawBufeffect is declared. Uniqueness inference rewrites in-place; shared ownership falls back to copy-on-write (Issue #22). The pure-core invariant survives (code without a declared effect is pure); local reasoning survives (a fn takingborrow (RawBuf Int)and returningIntis pure from the caller's view). The new scope-qualified mono symbols remove a latent bug class — theget__Int-style collision between different types' identically named polymorphic ops.