From 647121cb8f2077fca807bb41ef4b944dccaaf95c Mon Sep 17 00:00:00 2001 From: Brummel Date: Fri, 29 May 2026 11:17:30 +0200 Subject: [PATCH] =?UTF-8?q?plan:=20raw-buf.2-kernel-manifest=20=E2=80=94?= =?UTF-8?q?=207-task=20crate-rename=20+=20RawBuf=20submodule=20+=20checker?= =?UTF-8?q?-side=20surface=20(refs=20#7)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Decomposes raw-buf.2 (parent spec docs/specs/0054-raw-buf.md § Architecture point 2, § Components row 2, § Testing strategy "raw-buf.2") into 7 atomic tasks: Task 1 — crate rename + reshape: crates/ailang-kernel-stub/ → crates/ailang-kernel/, restructured as a family-crate with src/kernel_stub/{mod.rs,source.ail}; 13 steps covering 8 compile-checked rename sites plus design/INDEX.md + CLAUDE.md path updates. Task 2 — add raw_buf submodule: src/raw_buf/{mod.rs,source.ail} with the spec's Form-A source (4 fns + 1 TypeDef with param-in (a Int Float Bool)); RAW_BUF_AIL re-export. Task 3 — wire raw_buf into ailang-surface: parse_raw_buf fn, re-export at lib.rs:39, workspace injection alongside the existing kernel_stub block, workspace_pin count bump 4→5. Task 4 — round-trip test raw_buf_module_round_trips, verbatim structural clone of kernel_stub_module_round_trips. Task 5 — E2E raw_buf_check_e2e: (con RawBuf (con Int)) consumer checks clean. Task 6 — E2E raw_buf_param_in_reject_e2e: (con RawBuf (con Str)) consumer rejected at check with param-not-in-restricted-set. Task 7 — E2E raw_buf_build_intercept_missing_e2e: (new RawBuf …) consumer accepted at check, rejected at build with the existing Term::New deferral message. Three substantive plan-time decisions resolving plan-recon's open questions: 1. The intercept-not-registered diagnostic is NOT introduced. The spec hedged ("intercept-not-registered: new__RawBuf__Int (or the existing Term::New-deferral diagnostic)") between shipping a fresh diagnostic and reusing the existing deferral at crates/ailang-codegen/src/lib.rs:2075-2078. Plan picks reuse: the deferral already names "milestone raw-buf" and is self-describing; a new diagnostic would exist for ~1 iter (raw-buf.2 → raw-buf.3) before going away — disposable infra. Task 7's test asserts on the substring "Term::New requires the type's" which is stable in the existing message. 2. In-tree references to crates/ailang-kernel-stub/ AFTER the rename — split-scope between raw-buf.2 and raw-buf.3. The path changes in raw-buf.2 (the crate moves now), so path-references in design/INDEX.md:112 and the CLAUDE.md Code-layout table get updated in Task 1 Steps 10-11. The retire-language (stub as "ratifying fixture for kernel- extension-mechanics" → "retired by raw-buf") stays for raw-buf.3 close, per spec § Components row 3. 3. parse_raw_buf gets a mirror re-export at ailang-surface/ src/lib.rs:39 alongside parse_kernel_stub. Recon's natural reading; load-bearing because the new round-trip test calls ailang_surface::parse_raw_buf() directly. Plan honours the project's compile-gate discipline. Task 1's 13 steps thread all 8 rename sites inside the same task — no deferred caller across the build gate. Task 3 bundles the workspace_pin count bump with the loader injection that drives it; the assertion goes 4→5 in the same task that makes it true. Test-count trajectory: 667 (post-raw-buf.1 baseline) → 667 (Tasks 1, 2, 3 — refactor + wiring, no new test) → 668 (Task 4 round-trip) → 669 (Task 5 check E2E) → 670 (Task 6 reject E2E) → 671 (Task 7 deferral E2E). Handoff target: skills/implement on docs/plans/0105-raw-buf.2-kernel-manifest.md --- docs/plans/0105-raw-buf.2-kernel-manifest.md | 702 +++++++++++++++++++ 1 file changed, 702 insertions(+) create mode 100644 docs/plans/0105-raw-buf.2-kernel-manifest.md diff --git a/docs/plans/0105-raw-buf.2-kernel-manifest.md b/docs/plans/0105-raw-buf.2-kernel-manifest.md new file mode 100644 index 0000000..6c01606 --- /dev/null +++ b/docs/plans/0105-raw-buf.2-kernel-manifest.md @@ -0,0 +1,702 @@ +# raw-buf.2 — RawBuf kernel-tier manifest + checker side — Implementation Plan + +> **Parent spec:** `docs/specs/0054-raw-buf.md` +> +> **For agentic workers:** REQUIRED SUB-SKILL: use the +> `implement` skill to run this plan. Steps use `- [ ]` +> checkboxes for tracking. + +**Goal:** Rename `crates/ailang-kernel-stub/` → `crates/ailang-kernel/`, reshape as a family-crate (one Cargo crate hosting all kernel-tier modules as sibling submodules under `src/`), add the new `raw_buf` submodule with the spec's Form-A source, wire `parse_raw_buf` + workspace injection through `ailang-surface`, and ratify the checker-visible surface via four new tests (round-trip + 3 E2E). + +**Architecture:** Three concerns layered into seven tasks. Task 1 does the wholesale crate-rename + reshape (kernel_stub re-housed under `src/kernel_stub/`, public-symbol surface preserved via `pub use kernel_stub::SOURCE as STUB_AIL;`). Task 2 adds the `raw_buf` submodule + `RAW_BUF_AIL` re-export. Task 3 wires raw_buf into `ailang-surface` (parse fn + re-export + workspace injection + workspace_pin bump). Tasks 4-7 land the four tests. The codegen path stays *unchanged* — the iter raw-buf.2 acceptance is the existing Term::New-deferral diagnostic firing precisely on `(new RawBuf …)` consumer code (a new `intercept-not-registered` diagnostic was hedged in the spec but rejected at plan time as disposable infra; the existing deferral already names "milestone raw-buf" and is self-describing). + +**Tech Stack:** Rust 1.x (stable), `crates/ailang-kernel/` (new family-crate), `crates/ailang-surface/` (parse + workspace injection), `crates/ailang-core/tests/` (round-trip + workspace_pin), `crates/ail/tests/` (three E2E), `examples/` (three fixtures). + +--- + +**Files this plan creates or modifies:** + +- Create: `crates/ailang-kernel/Cargo.toml` — renamed manifest, package name `ailang-kernel`. +- Create: `crates/ailang-kernel/src/lib.rs` — family-crate hub with `mod` declarations and re-exports. +- Create: `crates/ailang-kernel/src/kernel_stub/mod.rs` — `pub const SOURCE: &str = include_str!("source.ail");` + the rustdoc lifted from the old `STUB_AIL` rustdoc. +- Create: `crates/ailang-kernel/src/kernel_stub/source.ail` — the Form-A text currently embedded in `crates/ailang-kernel-stub/src/lib.rs:27-37` (the `r#"…"#` body), now an `.ail` file. +- Create: `crates/ailang-kernel/src/raw_buf/mod.rs` — same shape as `kernel_stub/mod.rs`. +- Create: `crates/ailang-kernel/src/raw_buf/source.ail` — the new Form-A text per spec § Concrete code shapes "Primary — the raw-buf kernel-tier module" (lines 158-201 of the spec). +- Delete: `crates/ailang-kernel-stub/Cargo.toml` — replaced by the renamed file at `crates/ailang-kernel/Cargo.toml`. +- Delete: `crates/ailang-kernel-stub/src/lib.rs` — content fans out into the new `lib.rs` (re-exports) + `kernel_stub/{mod.rs, source.ail}`. +- Delete: `crates/ailang-kernel-stub/` (whole folder, after contents are evacuated by the above). +- Modify: `Cargo.toml:7` — workspace member rename. +- Modify: `Cargo.toml:37` — workspace dep alias rename. +- Modify: `crates/ailang-surface/Cargo.toml:9` — dep name rename. +- Modify: `crates/ailang-surface/src/loader.rs:45-57` — rename references `ailang_kernel_stub::STUB_AIL` → `ailang_kernel::STUB_AIL` (rustdoc L48, call L55, expect-message L56) AND add `parse_raw_buf` fn after `parse_kernel_stub`. +- Modify: `crates/ailang-surface/src/loader.rs:123-138` — extend the workspace injection in `load_workspace` to also inject `raw_buf`. +- Modify: `crates/ailang-surface/src/lib.rs:39` — extend the re-export to also include `parse_raw_buf`. +- Modify: `crates/ailang-core/tests/workspace_pin.rs:52-58` — bump count `4 → 5`, extend the auto-injects comment, add `contains_key("raw_buf")` assertion. +- Modify: `design/INDEX.md:112` — update the path-ref in the kernel-extensions Models row (`ailang-kernel-stub` → `ailang-kernel/src/kernel_stub`). Retire-language stays for raw-buf.3. +- Modify: `CLAUDE.md` — update the "Code layout" table row for the renamed crate (path + one-line role description; the role transitions from "carries STUB_AIL only" to "family-crate hosting all kernel-tier modules"). +- Test: `crates/ailang-core/tests/design_schema_drift.rs:765+` — add `raw_buf_module_round_trips` after the existing `kernel_stub_module_round_trips` test. +- Test: `examples/raw_buf_consumer_int.ail` — fixture for the check-passes E2E. +- Test: `examples/raw_buf_consumer_str_reject.ail` — fixture for the param-in reject E2E. +- Test: `examples/raw_buf_consumer_build_defer.ail` — fixture for the Term::New-deferral E2E. +- Test: `crates/ail/tests/raw_buf_check_e2e.rs` — `raw_buf_check_e2e_consumer_passes` — `ail check` exit 0 on the int-element consumer. +- Test: `crates/ail/tests/raw_buf_param_in_reject_e2e.rs` — `raw_buf_param_in_reject_consumer_fails_at_check` — `ail check` exits non-zero with `param-not-in-restricted-set` substring. +- Test: `crates/ail/tests/raw_buf_build_intercept_missing_e2e.rs` — `raw_buf_build_term_new_defers_to_raw_buf_3` — `ail build` exits non-zero with `Term::New requires the type's` substring. + +--- + +### Task 1: Crate rename + family-crate reshape — kernel_stub re-housed, public-symbol surface preserved + +**Files:** +- Move: `crates/ailang-kernel-stub/` → `crates/ailang-kernel/` (and reshape `src/` inside) +- Modify: `Cargo.toml:7`, `Cargo.toml:37` +- Modify: `crates/ailang-surface/Cargo.toml:9` +- Modify: `crates/ailang-surface/src/loader.rs:45-57` +- Modify: `design/INDEX.md:112` +- Modify: `CLAUDE.md` (Code-layout table row) + +- [ ] **Step 1: Move the crate folder.** + +Run: `git mv crates/ailang-kernel-stub crates/ailang-kernel` +Expected: folder rename succeeds; `git status` shows the renames. + +- [ ] **Step 2: Rewrite `crates/ailang-kernel/Cargo.toml` package name.** + +Edit `crates/ailang-kernel/Cargo.toml`. Change the `name = "ailang-kernel-stub"` line to `name = "ailang-kernel"`. Leave all other lines (edition, version, dependencies — currently empty `[dependencies]`) unchanged. The package description (if any) may stay verbatim or get an inline updated phrase: "Family-crate hosting the embedded Form-A sources of every kernel-tier module." + +- [ ] **Step 3: Carve out `kernel_stub/source.ail` from the current `lib.rs` body.** + +Create the new file `crates/ailang-kernel/src/kernel_stub/source.ail` with the exact Form-A text currently embedded between the `r#"` and `"#` markers in `crates/ailang-kernel/src/lib.rs` at lines 27-37 (the `STUB_AIL` const body). Verbatim — preserve every newline, indent, and trailing newline. The expected content is: + +``` +(module kernel_stub + (kernel) + (data StubT (vars a) + (ctor Stub a) + (param-in (a Int Float))) + (fn new + (doc "Construct StubT from an Int. Exercises Term::New end-to-end.") + (type (fn-type (params (con Int)) (ret (con StubT (con Int))))) + (params x) + (body (term-ctor StubT Stub x)))) +``` + +(Verify against the source bytes before writing — the implementer must `git show HEAD:crates/ailang-kernel-stub/src/lib.rs` if the current `lib.rs` body diverges; the source is authoritative.) + +- [ ] **Step 4: Create `crates/ailang-kernel/src/kernel_stub/mod.rs`.** + +Write the following exact content: + +```rust +//! Kernel-stub submodule — minimal ratifying fixture for the +//! kernel-extension-mechanics milestone (prep.3). The Form-A +//! text lives in the sibling `source.ail`. +//! +//! No domain content. The stub may be retired or repositioned +//! when a real second kernel-tier consumer (the `raw-buf` +//! milestone) lands; until then it stays as the +//! ratifying-fixture anchor. + +/// Source-of-truth Form-A text for the stub kernel module. Parsed +/// by `ailang_surface::parse_kernel_stub` (and round-trip-pinned by +/// `kernel_stub_module_round_trips` in `design_schema_drift.rs`). +/// +/// Ships a `(fn new ...)` def so consumers can spell `(new StubT 42)`, +/// the worked-consumer example from prep.3 of the spec — without it, +/// the stub's `Term::New` end-to-end ratification story is broken +/// (per fieldtest F4, 2026-05-28). +pub const SOURCE: &str = include_str!("source.ail"); +``` + +- [ ] **Step 5: Rewrite `crates/ailang-kernel/src/lib.rs` as the family-crate hub.** + +Replace the entire contents of `crates/ailang-kernel/src/lib.rs` with: + +```rust +//! Kernel-tier modules — family-crate hosting the Form-A source +//! of every kernel-tier `.ail` module the AILang workspace +//! auto-injects. One Cargo crate, one Rust submodule per +//! kernel-tier module, one `pub const SOURCE` per submodule. +//! +//! The acyclic dependency chain is preserved: +//! `ailang-surface → ailang-kernel → ailang-core`. Per-module +//! parse hops live in `ailang-surface` (`parse_kernel_stub`, +//! `parse_raw_buf`, …) so this crate carries zero parser code. + +mod kernel_stub; + +pub use kernel_stub::SOURCE as STUB_AIL; +``` + +(Task 2 will add a `mod raw_buf;` line and a matching re-export — leave them out of this task; the kernel_stub submodule alone must build clean here so the rename can land as a strict refactor.) + +- [ ] **Step 6: Update `Cargo.toml:7` workspace member.** + +Edit the workspace `Cargo.toml`. Find line 7 (the member entry currently reading `"crates/ailang-kernel-stub",`) and change it to `"crates/ailang-kernel",`. Preserve the trailing comma and surrounding whitespace. + +- [ ] **Step 7: Update `Cargo.toml:37` workspace dep alias.** + +Edit the workspace `Cargo.toml`. Find line 37 (the dep alias currently reading `ailang-kernel-stub = { path = "crates/ailang-kernel-stub" }`) and change it to `ailang-kernel = { path = "crates/ailang-kernel" }`. + +- [ ] **Step 8: Update `crates/ailang-surface/Cargo.toml:9` dep name.** + +Edit `crates/ailang-surface/Cargo.toml`. Find line 9 (`ailang-kernel-stub.workspace = true`) and change to `ailang-kernel.workspace = true`. + +- [ ] **Step 9: Update `crates/ailang-surface/src/loader.rs` use-paths + rustdoc + expect-message.** + +Three edits in this file: + + (a) Line 48 (rustdoc): change `Source-of-truth: \`ailang_kernel_stub::STUB_AIL\`.` to `Source-of-truth: \`ailang_kernel::STUB_AIL\`.` + + (b) Line 55 (call): change `crate::parse(ailang_kernel_stub::STUB_AIL)` to `crate::parse(ailang_kernel::STUB_AIL)`. + + (c) Line 56 (expect-message): change `"ailang_kernel_stub::STUB_AIL must parse as a Module"` to `"ailang_kernel::STUB_AIL must parse as a Module"`. + +- [ ] **Step 10: Update `design/INDEX.md:112`.** + +Edit `design/INDEX.md`. The Models-section row for `kernel-extensions` (line 112) currently contains the literal `ailang-kernel-stub` referring to the crate path. Change every `ailang-kernel-stub` occurrence in that row to `ailang-kernel/src/kernel_stub` (the new submodule path). The retire-language ("planned for retirement when raw-buf lands a real second consumer") stays — raw-buf.3 close updates that sentence. + +- [ ] **Step 11: Update `CLAUDE.md` "Code layout" table row for the renamed crate.** + +Edit `CLAUDE.md`. Locate the "Code layout" table; find the row that currently reads `crates/ailang-kernel-stub/` in the path column. Change the path to `crates/ailang-kernel/`. Update the role-description column to: + +> Family-crate hosting the Form-A source of every kernel-tier `.ail` module. Sub-modules under `src/` carry one `pub const SOURCE` each (`kernel_stub`, `raw_buf` from raw-buf.2 onwards). Parse hops live in `ailang-surface`; this crate is the zero-dependency leaf that lets `ailang-surface → ailang-kernel → ailang-core` stay acyclic. + +The "Zero-dependency leaf … `kernel_stub` ratifying-fixture" language is retired now that the crate hosts more than one submodule. The `raw-buf` milestone retire-language stays in raw-buf.3 close. + +- [ ] **Step 12: Build the workspace.** + +Run: `cargo build --workspace 2>&1 | tail -8` +Expected: build succeeds; zero errors, zero warnings about `ailang-kernel-stub` (the crate is fully renamed; no dangling refs). + +- [ ] **Step 13: Run the full workspace test suite to confirm zero behavioural change.** + +Run: `cargo test --workspace 2>&1 | grep -E "^test result:" | awk -F"[: ;]+" '{p+=$4; f+=$6; i+=$8} END{print "passed:",p," failed:",f," ignored:",i}'` +Expected: `passed: 667 failed: 0 ignored: 2` (same as the post-raw-buf.1 baseline; the rename is pure refactor, no test count change yet). + +--- + +### Task 2: Add raw_buf submodule + RAW_BUF_AIL re-export + +**Files:** +- Create: `crates/ailang-kernel/src/raw_buf/mod.rs` +- Create: `crates/ailang-kernel/src/raw_buf/source.ail` +- Modify: `crates/ailang-kernel/src/lib.rs` (add `mod raw_buf;` + re-export) + +- [ ] **Step 1: Create `crates/ailang-kernel/src/raw_buf/source.ail`.** + +Write the Form-A source verbatim per spec § "Primary — the raw-buf kernel-tier module" (spec lines 158-201). The content is: + +``` +(module raw_buf + (kernel) + (data RawBuf (vars a) + (ctor B (storage-tag)) + (param-in (a Int Float Bool))) + + (fn new + (doc "Allocate uninitialised RawBuf of capacity n. Codegen + intercept new__RawBuf__ emits @ailang_rc_alloc + the + size header. Element bytes are uninitialised — caller + must (set …) before (get …).") + (type (fn-type (params (con Int)) (ret (own (RawBuf a))))) + (params n) + (body (term-ctor RawBuf B n))) + + (fn get + (doc "Indexed read. UB if i >= (size b); caller checks + bounds. Codegen intercept RawBuf_get__ emits + getelementptr + load.") + (type (fn-type + (params (borrow (RawBuf a)) (con Int)) + (ret (con a)))) + (params b i) + (body (term-ctor RawBuf B 0))) + + (fn set + (doc "Indexed write. Linear: own in, own out. Under + uniqueness, in-place; under shared, copy-on-write + (Issue #22 territory). Codegen intercept + RawBuf_set__ emits getelementptr + store.") + (type (fn-type + (params (own (RawBuf a)) (con Int) (con a)) + (ret (own (RawBuf a))))) + (params b i v) + (body b)) + + (fn size + (doc "Element count. Codegen intercept RawBuf_size__ + emits a single i64 load from the slab header.") + (type (fn-type (params (borrow (RawBuf a))) (ret (con Int)))) + (params b) + (body 0))) +``` + +End the file with a single newline (no trailing blank lines). The placeholder bodies (`(term-ctor RawBuf B n)`, `(term-ctor RawBuf B 0)`, `b`, `0`) match the existing convention from `examples/prelude.ail` — codegen never executes them (the intercept registry will override in raw-buf.3); they exist solely to satisfy the round-trip invariant. + +- [ ] **Step 2: Create `crates/ailang-kernel/src/raw_buf/mod.rs`.** + +Write: + +```rust +//! Raw-buf submodule — embeds the Form-A source of the `raw_buf` +//! kernel-tier module. Parsed by `ailang_surface::parse_raw_buf` +//! (and round-trip-pinned by `raw_buf_module_round_trips` in +//! `design_schema_drift.rs`). + +/// Source-of-truth Form-A text for the `raw_buf` kernel module. +/// +/// Declares `(data RawBuf (vars a) (ctor B …) (param-in (a Int Float Bool)))` +/// plus `new`, `get`, `set`, `size` fns with placeholder bodies. +/// The codegen intercept entries that replace the placeholder +/// bodies at lowering time ship in raw-buf.3. +pub const SOURCE: &str = include_str!("source.ail"); +``` + +- [ ] **Step 3: Extend `crates/ailang-kernel/src/lib.rs` to register the new submodule + re-export its `SOURCE`.** + +Edit `crates/ailang-kernel/src/lib.rs`. After the existing `mod kernel_stub;` line, add `mod raw_buf;`. After the existing `pub use kernel_stub::SOURCE as STUB_AIL;` line, add `pub use raw_buf::SOURCE as RAW_BUF_AIL;`. The final file content is: + +```rust +//! Kernel-tier modules — family-crate hosting the Form-A source +//! of every kernel-tier `.ail` module the AILang workspace +//! auto-injects. One Cargo crate, one Rust submodule per +//! kernel-tier module, one `pub const SOURCE` per submodule. +//! +//! The acyclic dependency chain is preserved: +//! `ailang-surface → ailang-kernel → ailang-core`. Per-module +//! parse hops live in `ailang-surface` (`parse_kernel_stub`, +//! `parse_raw_buf`, …) so this crate carries zero parser code. + +mod kernel_stub; +mod raw_buf; + +pub use kernel_stub::SOURCE as STUB_AIL; +pub use raw_buf::SOURCE as RAW_BUF_AIL; +``` + +- [ ] **Step 4: Build the workspace.** + +Run: `cargo build --workspace 2>&1 | tail -8` +Expected: build succeeds. A dead-code warning on `RAW_BUF_AIL` may fire (the re-export is consumed only after Task 3 wires it through `ailang-surface`); accept it. + +- [ ] **Step 5: Full workspace test run — no test count change yet.** + +Run: `cargo test --workspace 2>&1 | grep -E "^test result:" | awk -F"[: ;]+" '{p+=$4; f+=$6; i+=$8} END{print "passed:",p," failed:",f," ignored:",i}'` +Expected: `passed: 667 failed: 0 ignored: 2`. Same as Task 1 (the raw_buf submodule exists but no test consumes it). + +--- + +### Task 3: Wire raw_buf into ailang-surface — parse fn + re-export + workspace injection + workspace_pin bump + +**Files:** +- Modify: `crates/ailang-surface/src/loader.rs` (add `parse_raw_buf` after `parse_kernel_stub`; extend `load_workspace` injection) +- Modify: `crates/ailang-surface/src/lib.rs:39` (extend re-export) +- Modify: `crates/ailang-core/tests/workspace_pin.rs:52-58` (count bump + comment + new assertion) + +- [ ] **Step 1: Add `parse_raw_buf` in `crates/ailang-surface/src/loader.rs`.** + +Insert the following block immediately after the existing `parse_kernel_stub` fn (which currently ends at line 57). Place it before the `fn is_ail_source(path: &Path) -> bool {` line at L59: + +```rust +/// raw-buf.2 (raw-buf milestone): parse the embedded raw_buf +/// kernel-tier module bytes into a `Module`. Mirror of +/// [`parse_kernel_stub`]. +/// +/// Source-of-truth: `ailang_kernel::RAW_BUF_AIL`. Declares the +/// `RawBuf` TypeDef with `param-in (a Int Float Bool)` plus the +/// four operation signatures (`new`, `get`, `set`, `size`) with +/// placeholder bodies that codegen replaces via the intercept +/// registry (raw-buf.3). +/// +/// Panics on parse failure — the source is build-time-validated +/// by every drift test run. +pub fn parse_raw_buf() -> Module { + crate::parse(ailang_kernel::RAW_BUF_AIL) + .expect("ailang_kernel::RAW_BUF_AIL must parse as a Module") +} +``` + +- [ ] **Step 2: Extend the workspace injection in `load_workspace` (loader.rs).** + +The current injection block at lines 123-132 reserves the name `kernel_stub` and inserts `parse_kernel_stub()` into the modules map. Insert an analogous block for `raw_buf` immediately AFTER the kernel_stub block (after line 132, before the existing `// Derive the implicit-imports list …` comment at L134): + +```rust + // parse_raw_buf() injects the raw_buf kernel-tier base + // extension — declares the `RawBuf` TypeDef with the + // `param-in (a Int Float Bool)` element-type restriction and + // the four operation signatures. Carries `(kernel)`, so the + // kernel-flag filter below picks it up automatically. Per + // raw-buf milestone (Gitea #7); codegen intercepts ship in + // raw-buf.3. + if modules.contains_key("raw_buf") { + return Err(WorkspaceLoadError::ReservedModuleName { + name: "raw_buf".to_string(), + }); + } + modules.insert("raw_buf".to_string(), parse_raw_buf()); +``` + +- [ ] **Step 3: Extend the re-export in `crates/ailang-surface/src/lib.rs:39`.** + +Edit `crates/ailang-surface/src/lib.rs`. Locate line 39 — the `pub use loader::{load_module, load_workspace, parse_kernel_stub, parse_prelude, PRELUDE_AIL};` block. Insert `parse_raw_buf` into the list (alphabetically between `parse_prelude` and `PRELUDE_AIL`, or anywhere in the brace-list — the order does not matter functionally). Result: + +```rust +pub use loader::{load_module, load_workspace, parse_kernel_stub, parse_prelude, parse_raw_buf, PRELUDE_AIL}; +``` + +- [ ] **Step 4: Bump `workspace_pin.rs:52-58` — count, comment, and the new `contains_key` assertion.** + +Edit `crates/ailang-core/tests/workspace_pin.rs`. Lines 52-58 currently read: + +```rust + // the loader auto-injects two built-in kernel-tier modules + // (`prelude` and `kernel_stub` — see prep.3 of the + // kernel-extension-mechanics milestone), so the count is the + // user's two modules plus those two. + assert_eq!(ws.modules.len(), 4); + assert!(ws.modules.contains_key("prelude")); + assert!(ws.modules.contains_key("kernel_stub")); +``` + +Replace with: + +```rust + // the loader auto-injects three built-in kernel-tier modules + // (`prelude`, `kernel_stub`, and `raw_buf` — the last added + // in raw-buf.2 of the raw-buf milestone), so the count is + // the user's two modules plus those three. raw-buf.3 will + // retire `kernel_stub`, dropping the count back to 4. + assert_eq!(ws.modules.len(), 5); + assert!(ws.modules.contains_key("prelude")); + assert!(ws.modules.contains_key("kernel_stub")); + assert!(ws.modules.contains_key("raw_buf")); +``` + +- [ ] **Step 5: Build the workspace.** + +Run: `cargo build --workspace 2>&1 | tail -8` +Expected: build succeeds; the `RAW_BUF_AIL` dead-code warning (from Task 2) is gone (the re-export is now consumed by `parse_raw_buf`). + +- [ ] **Step 6: Run the workspace_pin test specifically to confirm the count assertion.** + +Run: `cargo test -p ailang-core --test workspace_pin loads_example_workspace_happy_path` +Expected: PASS — `ws.modules.len()` is now 5, all three contains_key assertions hold. + +- [ ] **Step 7: Full workspace test run.** + +Run: `cargo test --workspace 2>&1 | grep -E "^test result:" | awk -F"[: ;]+" '{p+=$4; f+=$6; i+=$8} END{print "passed:",p," failed:",f," ignored:",i}'` +Expected: `passed: 667 failed: 0 ignored: 2`. No test count change yet (the bump is a 4→5 edit on an existing assertion, not a new test). + +--- + +### Task 4: Round-trip test `raw_buf_module_round_trips` + +**Files:** +- Modify: `crates/ailang-core/tests/design_schema_drift.rs:765+` (append after the existing kernel_stub round-trip test which ends at L764) + +- [ ] **Step 1: Append `raw_buf_module_round_trips` after L764.** + +Edit `crates/ailang-core/tests/design_schema_drift.rs`. After the closing `}` of `kernel_stub_module_round_trips` at line 764, insert the following test: + +```rust + +/// raw-buf.2 (raw-buf milestone): pin the JSON byte-shape of the +/// raw_buf kernel-tier base-extension module. Mirror of +/// `kernel_stub_module_round_trips`. Catches regressions in +/// `Module.kernel`, `TypeDef.param-in` (with three elements: +/// Int / Float / Bool), and the ctor + four-fn manifest shape +/// the spec lists in § Concrete code shapes lines 158-201. +#[test] +fn raw_buf_module_round_trips() { + let m = ailang_surface::parse_raw_buf(); + assert!(m.kernel, "raw_buf module is kernel-tier"); + assert_eq!(m.name, "raw_buf"); + + let json = serde_json::to_value(&m).expect("serialise raw_buf module"); + assert_eq!(json["kernel"], true); + assert_eq!(json["name"], "raw_buf"); + + let recovered: Module = + serde_json::from_value(json).expect("raw_buf module round-trips through serde"); + assert!(recovered.kernel); + assert_eq!(recovered.name, "raw_buf"); + + // Locate the RawBuf TypeDef and confirm param_in is intact + // for all three element types Int / Float / Bool (spec § + // Concrete code shapes line 163: `(param-in (a Int Float Bool))`). + let td = recovered.defs.iter().find_map(|d| match d { + Def::Type(t) if t.name == "RawBuf" => Some(t), + _ => None, + }).expect("RawBuf TypeDef present in raw_buf module"); + let allowed = td.param_in.get("a").expect("RawBuf.a restricted"); + assert!(allowed.contains("Int")); + assert!(allowed.contains("Float")); + assert!(allowed.contains("Bool")); +} +``` + +- [ ] **Step 2: Run the new round-trip test.** + +Run: `cargo test -p ailang-core --test design_schema_drift raw_buf_module_round_trips` +Expected: PASS — the parsed raw_buf module serialises to JSON, round-trips through serde, and the `param_in` for `a` contains all three element types. + +- [ ] **Step 3: Full workspace test run — count goes up by one.** + +Run: `cargo test --workspace 2>&1 | grep -E "^test result:" | awk -F"[: ;]+" '{p+=$4; f+=$6; i+=$8} END{print "passed:",p," failed:",f," ignored:",i}'` +Expected: `passed: 668 failed: 0 ignored: 2` (+1 from the new round-trip). + +--- + +### Task 5: E2E `raw_buf_check_e2e` — consumer with `(con RawBuf (con Int))` checks clean + +**Files:** +- Create: `examples/raw_buf_consumer_int.ail` +- Create: `crates/ail/tests/raw_buf_check_e2e.rs` + +- [ ] **Step 1: Create `examples/raw_buf_consumer_int.ail`.** + +Write: + +``` +(module raw_buf_consumer_int + (fn read_first + (doc "Consumer demonstrating (con RawBuf (con Int)) in a type slot. Kernel-tier auto-import (prep.3) makes RawBuf visible without an explicit (import raw_buf). The body calls RawBuf.get via the type-scoped namespacing landed in prep.1.") + (type (fn-type (params (borrow (RawBuf (con Int)))) (ret (con Int)))) + (params buf) + (body (app RawBuf.get buf 0)))) +``` + +- [ ] **Step 2: Create `crates/ail/tests/raw_buf_check_e2e.rs`.** + +Write: + +```rust +//! E2E: a consumer fixture that mentions `(con RawBuf (con Int))` +//! in a type signature and calls `RawBuf.get` must type-check +//! clean. raw-buf.2 ratification — the kernel-tier auto-import +//! path (prep.3) makes `RawBuf` visible without an explicit +//! `(import raw_buf)`, and type-scoped namespacing (prep.1) +//! resolves `RawBuf.get` to the kernel-tier fn. + +use std::path::PathBuf; +use std::process::Command; + +#[test] +fn raw_buf_check_e2e_consumer_passes() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let repo_root = PathBuf::from(manifest_dir).join("../.."); + let fixture = repo_root.join("examples/raw_buf_consumer_int.ail"); + + let check = Command::new(env!("CARGO_BIN_EXE_ail")) + .arg("check") + .arg(&fixture) + .output() + .expect("ail check failed to spawn"); + assert!( + check.status.success(), + "ail check failed: stdout={} stderr={}", + String::from_utf8_lossy(&check.stdout), + String::from_utf8_lossy(&check.stderr) + ); +} +``` + +- [ ] **Step 3: Run the new test.** + +Run: `cargo test -p ail --test raw_buf_check_e2e raw_buf_check_e2e_consumer_passes` +Expected: PASS — `ail check` exits 0 on the consumer fixture. + +- [ ] **Step 4: Full workspace test run — count goes up by one.** + +Run: `cargo test --workspace 2>&1 | grep -E "^test result:" | awk -F"[: ;]+" '{p+=$4; f+=$6; i+=$8} END{print "passed:",p," failed:",f," ignored:",i}'` +Expected: `passed: 669 failed: 0 ignored: 2` (+1 from this E2E). + +--- + +### Task 6: E2E `raw_buf_param_in_reject_e2e` — consumer with `(con RawBuf (con Str))` rejected at check + +**Files:** +- Create: `examples/raw_buf_consumer_str_reject.ail` +- Create: `crates/ail/tests/raw_buf_param_in_reject_e2e.rs` + +- [ ] **Step 1: Create `examples/raw_buf_consumer_str_reject.ail`.** + +Write: + +``` +(module raw_buf_consumer_str_reject + (fn cant_str_raw + (doc "Must-fail fixture. RawBuf's TypeDef ships `(param-in (a Int Float Bool))` — instantiating with Str must be rejected by the checker with the prep.3 `param-not-in-restricted-set` diagnostic. Mirrors fieldtest F8 on the prep.3 stub.") + (type (fn-type (params (borrow (RawBuf (con Str)))) (ret (con Str)))) + (params buf) + (body (app RawBuf.get buf 0)))) +``` + +- [ ] **Step 2: Create `crates/ail/tests/raw_buf_param_in_reject_e2e.rs`.** + +Write: + +```rust +//! E2E: a consumer fixture that instantiates `(con RawBuf (con Str))` +//! in any type slot must be rejected by `ail check`, because +//! RawBuf's TypeDef declares `(param-in (a Int Float Bool))` — +//! Str is outside the restricted set. raw-buf.2 ratification of +//! the param-in mechanism on a real-payload extension (the +//! prep.3 stub fixture exercised it only on the StubT type). + +use std::path::PathBuf; +use std::process::Command; + +#[test] +fn raw_buf_param_in_reject_consumer_fails_at_check() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let repo_root = PathBuf::from(manifest_dir).join("../.."); + let fixture = repo_root.join("examples/raw_buf_consumer_str_reject.ail"); + + let check = Command::new(env!("CARGO_BIN_EXE_ail")) + .arg("check") + .arg(&fixture) + .output() + .expect("ail check failed to spawn"); + assert!( + !check.status.success(), + "ail check unexpectedly succeeded: stdout={} stderr={}", + String::from_utf8_lossy(&check.stdout), + String::from_utf8_lossy(&check.stderr) + ); + let stderr = String::from_utf8_lossy(&check.stderr); + assert!( + stderr.contains("param-not-in-restricted-set"), + "expected `param-not-in-restricted-set` substring in stderr; got: {stderr}" + ); +} +``` + +- [ ] **Step 3: Run the new test.** + +Run: `cargo test -p ail --test raw_buf_param_in_reject_e2e raw_buf_param_in_reject_consumer_fails_at_check` +Expected: PASS — `ail check` exits non-zero with the `param-not-in-restricted-set` diagnostic code in stderr. + +(If the diagnostic format wraps the code (e.g. `[param-not-in-restricted-set]` with brackets, or `error[param-not-in-restricted-set]:`), the substring assertion still hits — the test asserts on the bare code string. If the actual diagnostic uses a different code spelling (e.g. underscore-separated), update the assertion substring to match the reality; the prep.3 fieldtest F8 verified the code as written above.) + +- [ ] **Step 4: Full workspace test run — count goes up by one.** + +Run: `cargo test --workspace 2>&1 | grep -E "^test result:" | awk -F"[: ;]+" '{p+=$4; f+=$6; i+=$8} END{print "passed:",p," failed:",f," ignored:",i}'` +Expected: `passed: 670 failed: 0 ignored: 2`. + +--- + +### Task 7: E2E `raw_buf_build_intercept_missing_e2e` — Term::New deferral at build + +**Files:** +- Create: `examples/raw_buf_consumer_build_defer.ail` +- Create: `crates/ail/tests/raw_buf_build_intercept_missing_e2e.rs` + +- [ ] **Step 1: Create `examples/raw_buf_consumer_build_defer.ail`.** + +Write: + +``` +(module raw_buf_consumer_build_defer + (fn main + (doc "Term::New invocation: the checker accepts (new RawBuf …) because raw_buf ships (fn new …), but the codegen defers Term::New until raw-buf.3 ships the desugar pass. `ail check` clean; `ail build` fails with the existing deferral message naming `milestone raw-buf`. raw-buf.3 inverts this test to PASS once the desugar lands.") + (type (fn-type (params) (ret (con Int)))) + (params) + (body + (let buf (new RawBuf (con Int) 3) + (app RawBuf.get buf 0))))) +``` + +- [ ] **Step 2: Create `crates/ail/tests/raw_buf_build_intercept_missing_e2e.rs`.** + +Write: + +```rust +//! E2E: a consumer fixture that invokes `(new RawBuf (con Int) 3)` +//! type-checks clean (RawBuf has a `(fn new …)` def, satisfying +//! prep.2's checker) but fails at `ail build` because the +//! codegen Term::New arm still emits the deferral diagnostic +//! that prep.2 introduced and raw-buf.3 will retire. This test +//! pins the manifest/codegen separation that is raw-buf.2's +//! load-bearing acceptance: the checker accepts but the binary +//! does not yet build. raw-buf.3 inverts this test to PASS +//! (delete + replace with an end-to-end build-and-run variant). + +use std::path::PathBuf; +use std::process::Command; + +#[test] +fn raw_buf_build_term_new_defers_to_raw_buf_3() { + let manifest_dir = env!("CARGO_MANIFEST_DIR"); + let repo_root = PathBuf::from(manifest_dir).join("../.."); + let fixture = repo_root.join("examples/raw_buf_consumer_build_defer.ail"); + let bin = repo_root.join("target/debug/raw_buf_consumer_build_defer_test_bin"); + + let build = Command::new(env!("CARGO_BIN_EXE_ail")) + .arg("build") + .arg(&fixture) + .arg("-o") + .arg(&bin) + .output() + .expect("ail build failed to spawn"); + assert!( + !build.status.success(), + "ail build unexpectedly succeeded: stdout={} stderr={}", + String::from_utf8_lossy(&build.stdout), + String::from_utf8_lossy(&build.stderr) + ); + let stderr = String::from_utf8_lossy(&build.stderr); + assert!( + stderr.contains("Term::New requires the type's"), + "expected Term::New deferral message; got: {stderr}" + ); +} +``` + +- [ ] **Step 3: Run the new test.** + +Run: `cargo test -p ail --test raw_buf_build_intercept_missing_e2e raw_buf_build_term_new_defers_to_raw_buf_3` +Expected: PASS — `ail check` accepts the fixture, then `ail build` errors with the Term::New deferral substring. + +- [ ] **Step 4: Final full workspace test run.** + +Run: `cargo test --workspace 2>&1 | grep -E "^test result:" | awk -F"[: ;]+" '{p+=$4; f+=$6; i+=$8} END{print "passed:",p," failed:",f," ignored:",i}'` +Expected: `passed: 671 failed: 0 ignored: 2` — baseline 667 + 4 new tests (round-trip in Task 4 + three E2E in Tasks 5/6/7). + +--- + +## Self-review + +1. **Spec coverage.** + - § Architecture point 2 ("Rename `crates/ailang-kernel-stub/` to `crates/ailang-kernel/` and re-shape it as a family-crate"): Task 1. + - § Architecture point 2 ("the renamed crate carries `src/kernel_stub/{mod.rs, source.ail}` and gains `src/raw_buf/{mod.rs, source.ail}`"): Tasks 1 + 2. + - § Architecture point 2 ("`src/lib.rs` re-exports each submodule's `SOURCE` const under a stable name"): Tasks 1 + 2. + - § Architecture point 2 ("`parse_kernel_stub` joined by `parse_raw_buf` in `ailang-surface`"): Task 3. + - § Architecture point 2 ("Checker accepts a consumer-fixture `(con RawBuf (con Int))` in a type slot; `ail check` passes"): Task 5. + - § Architecture point 2 ("`ail build` fails precisely with `intercept-not-registered: new__RawBuf__Int` (or the existing Term::New-deferral diagnostic)"): Task 7 (resolves the spec's "or" hedge by picking the Term::New-deferral path — no new diagnostic code; the existing message ships). + - § Components row 2: covered by Tasks 1 + 2 + 3. + - § Testing strategy raw-buf.2 bullet 1 (raw_buf_check_e2e): Task 5. + - § Testing strategy raw-buf.2 bullet 2 (raw_buf_param_in_reject_e2e): Task 6. + - § Testing strategy raw-buf.2 bullet 3 (raw_buf_build_intercept_missing_e2e): Task 7. + - § Testing strategy raw-buf.2 bullet 4 (round-trip): Task 4. + - § Testing strategy raw-buf.2 bullet 5 (workspace_pin 4→5): Task 3. + +2. **Placeholder scan.** No "TBD", no "TODO", no "implement later", no "similar to Task". The Task 6 Step 3 parenthetical ("If the diagnostic format wraps the code …") is a real fallback the implementer may need; it names the concrete adjustment, not a placeholder. + +3. **Type consistency.** Symbols used across tasks: `STUB_AIL`, `RAW_BUF_AIL`, `SOURCE` (per submodule), `kernel_stub`, `raw_buf`, `parse_kernel_stub`, `parse_raw_buf`, `kernel_stub_module_round_trips`, `raw_buf_module_round_trips`, `ailang-kernel-stub` (legacy), `ailang-kernel` (new). All consistent. + +4. **Step granularity.** Task 1 has 13 steps; each is one file edit or one command. Task 2: 5 steps. Task 3: 7 steps. Tasks 4-7: 3-4 steps each. All steps 2-5 min. Task 1 is the largest, but it must be atomic (the crate rename can't be half-done). + +5. **No commit steps.** None. + +6. **Pin/replacement substring contiguity.** Three substrings are asserted by tests against expected file content / stderr: + - `param-not-in-restricted-set` — asserted by Task 6's test against `ail check` stderr. Not generated by any file content this plan ships; sourced from the existing checker diagnostic. No contiguity risk within plan-shipped text. + - `Term::New requires the type's` — asserted by Task 7's test against `ail build` stderr. Sourced from the existing codegen deferral at `crates/ailang-codegen/src/lib.rs:2075-2078`. No contiguity risk. + - The `workspace_pin.rs` `assert_eq!(ws.modules.len(), 5);` substring — the bumped value. The replacement-body text in Task 3 Step 4 contains this assertion verbatim and contiguously (no soft-wrap split). + +7. **Compile-gate vs. deferred-caller ordering.** + - Task 1 changes the dep name `ailang-kernel-stub` → `ailang-kernel`. The compile-driven set is 8 sites across 6 files; ALL are threaded inside Task 1. No deferred caller. Task 1 ends with `cargo build --workspace` + `cargo test --workspace` green. + - Task 3 changes the workspace count (injection adds raw_buf — `workspace_pin` count assertion goes from 4 to 5). The bump is bundled into Task 3 Step 4 — no deferred fix across the compile gate. + - Task 5 / 6 / 7 introduce new tests; no signature changes elsewhere. + +8. **Verification-command filter strings must resolve.** Every `cargo test … ` invocation uses a fn name THIS PLAN ships (Tasks 4-7) or an existing fn confirmed by recon (`loads_example_workspace_happy_path` in Task 3 Step 6). Zero filter-zero-match risk: the new tests are written in the same task that runs them, and the `loads_example_workspace_happy_path` test exists at `crates/ailang-core/tests/workspace_pin.rs:46` per recon. Unfiltered `cargo test --workspace` at the end of each task uses a counter-based assertion (the awk pipeline), so "zero tests ran" cannot masquerade as success.