Two tasks. Task 1 threads a TypeDef scope through the free-fn mono path: a scope: Option<String> field on FreeFnCall + MonoTarget::FreeFn, populated Some(prefix) only on the type-scoped T.f resolution branch (lib.rs:3535, gated on type_home.is_some()), None everywhere else; consumed at the two mono-symbol mint sites via a scoped_base helper (both read the same MonoTarget → lockstep) and folded into mono_target_key. Result: StubT.peek @ Int mints StubT_peek__Int, not the colliding bare peek__Int; existing bare-resolved symbols unchanged (669-gate is the no-regression backstop). Task 2 adds the StubT.peek ratifier op to the stub source, extends the bijection collector to expand a polymorphic type-scoped intrinsic over its TypeDef's param-in set (building the same T_f__<elem> strings via mono_symbol_n on Type::Con elems, so collector and mint agree byte-for-byte), registers StubT_peek__Int/Float entries + emit fns, updates kernel_stub_module_round_trips, and adds a mono-symbol integration test (borrow-param calls, no Term::New, no codegen). plan-recon resolved the make-or-break unknown favourably: the TypeDef scope is discarded at lib.rs:3535 but fully available there (prefix + type_home live) — a clean thread-through, not a resolution re-architecture. Three orchestrator design calls folded in: peek's emit is a plausible signature-correct stub, unit-ratified only (never instantiated in .3, no Term::New to build a StubT; RawBuf's emits in .4 are the E2E-verified ones; peek retires in .5); the exact llvm_type(borrow StubT Int) string is an implementer verify-first step (eq__Unit precedent); scope-trigger is the call path (not signature, which would perturb bare-called fns like length), scope-value is the prefix, collector infers from signature — they agree by construction for single-TypeDef kernel modules, bijection + 669-gate backstop. peek Form-A verified to parse+check this session under a probe module (ok, 32 symbols / 3 modules). Final gate 670 (669 + 1 new mono test); .ll snapshots stay green (peek polymorphic → no instantiation without a call site).
26 KiB
raw-buf.3 — Type-scoped polymorphic intrinsic mechanism — Implementation Plan
Parent spec:
docs/specs/0054-raw-buf.mdFor agentic workers: REQUIRED SUB-SKILL: use the
implementskill to run this plan. Steps use- [ ]checkboxes for tracking.
Goal: Make a type-scoped polymorphic top-level (intrinsic)
op mint a scope-qualified mono symbol (StubT_peek__Int, not
peek__Int) and have the intrinsic↔intercept bijection express
one such polymorphic marker as its N per-param-in-element entries
— ratified standalone on the stub via a new StubT.peek op.
Architecture: Two tasks. Task 1 threads a TypeDef-scope through
the existing free-fn mono path: a new scope: Option<String> field
on FreeFnCall and MonoTarget::FreeFn, populated only when a call
resolved via the type-scoped T.f spelling, consumed at the two
mono-symbol mint sites (which already read the same MonoTarget, so
they stay in lockstep) and folded into the dedup key. Task 2 adds
the StubT.peek ratifier op, extends the bijection collector to
expand a polymorphic type-scoped intrinsic over its TypeDef's
param-in set (computing the identical scoped strings via the
same mono_symbol_n), registers the two StubT_peek__* entries,
and adds the mono-symbol + round-trip tests. No RawBuf, no
Term::New, no running E2E this iteration (constructing a StubT
needs the Term::New desugar that lands in raw-buf.4); ratification
is unit/integration tests + the bijection pin.
Tech Stack: Rust. crates/ailang-check/src/lib.rs (resolution +
FreeFnCall), crates/ailang-check/src/mono.rs (mint + key),
crates/ailang-codegen/src/intercepts.rs (collector + entries +
emit), crates/ailang-kernel/src/kernel_stub/source.ail (the
ratifier op), crates/ailang-core/tests/design_schema_drift.rs
(round-trip), a new mono-symbol test.
Files this plan creates or modifies:
- Modify:
crates/ailang-check/src/lib.rs:2741— addscope: Option<String>toFreeFnCall. - Modify:
crates/ailang-check/src/lib.rs:3535(+ every otherfree_fn_owner = Some(..)site) — carry the scope through the resolution tuple; populateSome(prefix)only on the type-scoped dotted branch. - Modify:
crates/ailang-check/src/lib.rs:3602— setscopeon theFreeFnCallpush. - Modify:
crates/ailang-check/src/mono.rs:577— addscope: Option<String>toMonoTarget::FreeFn. - Modify:
crates/ailang-check/src/mono.rs:600-607— foldscopeintomono_target_key. - Modify:
crates/ailang-check/src/mono.rs:857— setscope: fc.scope.clone()at theMonoTarget::FreeFnconstruction. - Modify:
crates/ailang-check/src/mono.rs:521(nearby) — add ascoped_basehelper. - Modify:
crates/ailang-check/src/mono.rs:1080+:1176— mint viascoped_base(scope, name)at both sites. - Modify:
crates/ailang-kernel/src/kernel_stub/source.ail— append thepeekop. - Modify:
crates/ailang-codegen/src/intercepts.rs:480-509(workspace_intrinsic_markers) — type-scoped-poly expansion arm. - Modify:
crates/ailang-codegen/src/intercepts.rs:35-174(INTERCEPTS) + after:459(emit fns) —StubT_peek__Int,StubT_peek__Float. - Modify:
crates/ailang-core/tests/design_schema_drift.rs:744-766(kernel_stub_module_round_trips) — coverpeek. - Test:
crates/ail/tests/mono_scoped_symbol.rs— new; asserts a type-scopedStubT.peekcall monomorphises toStubT_peek__Int/StubT_peek__Float.
Task 1: Thread the TypeDef scope into the free-fn mono symbol
Files:
-
Modify:
crates/ailang-check/src/lib.rs:2741, 3535(+siblings), 3602 -
Modify:
crates/ailang-check/src/mono.rs:521, 577, 600-607, 857, 1080, 1176 -
Step 1: Add the
scopefield toFreeFnCall.
Edit crates/ailang-check/src/lib.rs. In the FreeFnCall struct (the metas field is last, at ~2755), append:
/// The TypeDef scope the call resolved through, when it was
/// spelled type-scoped (`T.f`, prep.1) — `Some("StubT")` for
/// `StubT.peek`, `Some("RawBuf")` for `RawBuf.get`. `None` for a
/// bare / dot-qualified / local resolution. raw-buf.3: drives the
/// scope-qualified mono symbol `T_f__<elem>` so two types' ops
/// of the same name never collide.
pub scope: Option<String>,
- Step 2: Carry the scope through the resolution tuple
free_fn_owner.
The local free_fn_owner is Option<(String, String)> (owner_module, unqualified_name), set in several resolution branches and destructured at lib.rs:3549. Change its element type to (String, String, Option<String>) (owner_module, unqualified_name, scope). The compiler flags every free_fn_owner = Some((..)) site (and the destructure at 3549). Update each:
-
The type-scoped dotted branch at
lib.rs:3535, currently:(qualified, Some((target_module, suffix.to_string())))becomes:
let scope = if type_home.is_some() { Some(prefix.to_string()) } else { None }; (qualified, Some((target_module, suffix.to_string(), scope)))(
type_homeandprefixare both live here —type_home.is_some()is exactly "resolved via the TypeDef-first ladder"; the legacy module-as-receiver sub-case hastype_home == None→scope = None.) -
Every other
free_fn_owner = Some((m, n))assignment (the local / same-module / implicit-import branches the compiler flags): add, Noneas the third element. These are not type-scoped, soscope = None— existing bare/dot-qualified symbols stay byte-identical. -
Step 3: Destructure and thread the scope to the
FreeFnCallpush.
At lib.rs:3549, the if let (Type::Forall { .. }, Some((owner, unqualified_name))) = (&raw, &free_fn_owner) destructure gains the third binding:
if let (Type::Forall { vars, constraints, body }, Some((owner, unqualified_name, scope))) =
(&raw, &free_fn_owner)
At the FreeFnCall push (lib.rs:3602), add the field:
free_fn_calls.push(FreeFnCall {
name: unqualified_name.clone(),
owner_module: owner.clone(),
forall_vars: vars.clone(),
metas: metas.clone(),
scope: scope.clone(),
});
- Step 4: Add the
scopefield toMonoTarget::FreeFn+ thescoped_basehelper.
Edit crates/ailang-check/src/mono.rs. In MonoTarget::FreeFn (the defining_module field is last, ~583), append:
/// The TypeDef scope (`Some("StubT")`) when the call resolved
/// type-scoped; `None` for a bare free fn. Part of the dedup
/// key and the minted symbol base.
scope: Option<String>,
Add the helper next to mono_symbol_n (after its definition, ~528):
/// raw-buf.3: the symbol base for a free-fn mono target. A
/// type-scoped op (`scope = Some("StubT")`, name `"peek"`) bases on
/// `"StubT_peek"`, so `mono_symbol_n` mints `StubT_peek__Int`; a
/// bare free fn (`scope = None`) keeps its bare base.
pub fn scoped_base(scope: &Option<String>, name: &str) -> String {
match scope {
Some(t) => format!("{t}_{name}"),
None => name.to_string(),
}
}
- Step 5: Fold
scopeintomono_target_key.
Edit mono_target_key (mono.rs:600-607). The MonoTarget::FreeFn arm currently keys on ("free", name.clone(), joined). Include the scope so a scoped op and a bare op of the same name never dedup-collide:
MonoTarget::FreeFn { name, type_args, scope, .. } => {
let joined = type_args
.iter()
.map(ailang_core::canonical::type_hash)
.collect::<Vec<_>>()
.join("|");
("free".into(), scoped_base(scope, name), joined)
}
- Step 6: Set
scopeat theMonoTarget::FreeFnconstruction.
At mono.rs:857 (collect_mono_targets's out.push(MonoTarget::FreeFn { .. })), add scope: fc.scope.clone(),.
-
Step 7: Mint the scope-qualified symbol at both mint sites.
-
mono.rs:1080(synthesise_mono_fn_for_free_fn): the symbol ismono_symbol_n(name, type_args.as_slice()). This fn must receive the scope. Add ascope: &Option<String>parameter tosynthesise_mono_fn_for_free_fn, pass&target.scope(or the destructuredscope) from its caller (theMonoTarget::FreeFnmatch where it is invoked), and change the mint to:name: mono_symbol_n(&scoped_base(scope, name), type_args.as_slice()), -
mono.rs:1176(rewrite_mono_calls): theMonoTarget::FreeFn { name: fn_name, type_args, defining_module }arm — bindscopetoo and change:MonoTarget::FreeFn { name: fn_name, type_args, defining_module, scope } => { (mono_symbol_n(&scoped_base(scope, fn_name), type_args.as_slice()), defining_module.as_str()) }Both sites read the same
MonoTarget, so the minted def name and the rewritten call name stay byte-identical. -
Step 8: Build the workspace.
Run: cargo build --workspace 2>&1 | tail -8
Expected: build succeeds; the compiler-flagged free_fn_owner / MonoTarget::FreeFn / FreeFnCall sites are all threaded (a missed site is a hard compile error here).
- Step 9: Full suite — no regression (existing symbols unchanged).
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. No type-scoped polymorphic intrinsic exists yet (peek arrives in Task 2), and all existing free-fn calls resolve with scope = None → bare base → byte-identical symbols. mono_hash_stability (six prelude eq__*/compare__* hashes) and the bijection test stay green, confirming no existing symbol moved. (If any existing test exercised a Type.fn-spelled poly-free-fn call and pinned its bare symbol, it would flip here — investigate and report, do not adjust the assertion.)
Task 2: StubT.peek ratifier + bijection expansion + entries + tests
Files:
-
Modify:
crates/ailang-kernel/src/kernel_stub/source.ail -
Modify:
crates/ailang-codegen/src/intercepts.rs(collector +INTERCEPTS+ emit fns) -
Modify:
crates/ailang-core/tests/design_schema_drift.rs:744-766 -
Test:
crates/ail/tests/mono_scoped_symbol.rs(new) -
Step 1: Append the
peekop to the stub source.
Edit crates/ailang-kernel/src/kernel_stub/source.ail. The file currently ends with the answer fn whose final line is (intrinsic))) (the two closing parens close answer and the module). Insert the peek fn between answer and the module close: change answer's last line from (intrinsic))) to (intrinsic)) (close only answer), then append peek and re-close the module. The fn body (verified to parse + check clean in a kernel module this session):
(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 of 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)))
The final file ends with (intrinsic))) (closing peek + the module). Verify with git diff that only the answer close-paren count changed and peek was added — new/answer bodies are otherwise byte-untouched (a perturbation there would move the .ll snapshots).
- Step 2: Extend the bijection collector with a type-scoped-poly arm.
Edit crates/ailang-codegen/src/intercepts.rs, workspace_intrinsic_markers() (~480). The current Def::Fn(f) if matches!(f.body, Term::Intrinsic) arm (~490) inserts the bare f.name. Insert a MORE-SPECIFIC arm before it that handles a polymorphic (Type::Forall) intrinsic fn type-scoped to a same-module TypeDef:
// raw-buf.3: a polymorphic top-level (intrinsic) op
// type-scoped to a same-module TypeDef T (its
// signature mentions `(con T …)`) expands to one
// marker per element type in T's `param-in` set —
// the same `T_f__<elem>` strings the mono pass mints
// (mono::scoped_base + mono_symbol_n). One marker → N
// entries.
Def::Fn(f)
if matches!(f.body, Term::Intrinsic)
&& matches!(f.ty, Type::Forall { .. }) =>
{
match scope_typedef_and_elems(f, &module) {
Some((tdef, elems)) => {
for elem in elems {
markers.insert(mono_symbol_n(
&format!("{tdef}_{}", f.name),
std::slice::from_ref(&elem),
));
}
}
// a Forall intrinsic not scoped to a
// param-in TypeDef keeps the bare name (no
// such case ships today; future-proofing).
None => {
markers.insert(f.name.clone());
}
}
}
Add the helper scope_typedef_and_elems in the same file (or a private mod):
/// raw-buf.3: for a type-scoped polymorphic intrinsic fn `f`, return
/// `(TypeDef-name, element-types)` where the TypeDef is the unique
/// same-module `Def::Type` referenced via `(con T …)` in `f`'s
/// signature and the element types are its `param-in` set expressed as
/// `Type::Con` values (so `mono_symbol_n` produces the same suffix the
/// mono pass mints). `None` if `f` references zero or several
/// same-module TypeDefs.
fn scope_typedef_and_elems(
f: &ailang_core::ast::FnDef,
module: &ailang_core::ast::Module,
) -> Option<(String, Vec<Type>)> {
use ailang_core::ast::{Def, Type as T};
let typedef_names: std::collections::BTreeSet<&str> = module
.defs
.iter()
.filter_map(|d| match d {
Def::Type(td) => Some(td.name.as_str()),
_ => None,
})
.collect();
// collect Con-heads referenced anywhere in f's type
let mut referenced: std::collections::BTreeSet<String> = Default::default();
collect_con_heads(&f.ty, &mut referenced);
let scoped: Vec<&String> = referenced
.iter()
.filter(|n| typedef_names.contains(n.as_str()))
.collect();
let tdef = match scoped.as_slice() {
[one] => (*one).clone(),
_ => return None,
};
let td = module.defs.iter().find_map(|d| match d {
Def::Type(td) if td.name == tdef => Some(td),
_ => None,
})?;
// param-in is keyed by the type var; take its allowed surface
// names and build `Type::Con` element types.
let allowed = td.param_in.values().next()?; // single-var TypeDefs
let elems: Vec<T> = allowed
.iter()
.map(|s| T::Con { name: s.clone(), args: vec![] })
.collect();
Some((tdef, elems))
}
(collect_con_heads walks a Type, pushing every Type::Con { name, .. } head into the set; recurse through Forall.body, Fn.params/ret, Borrow/Own inner, and Con.args. Mirror the existing Type traversal in this crate. The implementer must confirm the exact Type variant names — Type::Con { name, args }, the borrow/own wrappers — against crates/ailang-core/src/ast.rs and the param_in field type BTreeMap<String, BTreeSet<String>> against the TypeDef struct; build a Type::Con exactly as mono::type_to_mono_suffix expects so the suffix matches.)
- Step 3: Register the two
StubT_peek__*entries + emit fns.
Append to INTERCEPTS (intercepts.rs:35-174):
Intercept {
name: "StubT_peek__Int",
expected_params: &["ptr"], // borrow (con StubT Int) lowers to ptr
expected_ret: "i64",
wants_alwaysinline: false,
emit: emit_stubt_peek_int,
},
Intercept {
name: "StubT_peek__Float",
expected_params: &["ptr"],
expected_ret: "double",
wants_alwaysinline: false,
emit: emit_stubt_peek_float,
},
Add the emit fns after emit_answer (~459). Mirror emit_answer's shape (read the single param SSA from the tail of emitter.locals, load the Stub payload field, return it). Plausible bodies:
/// raw-buf.3 ratifier emit. NOTE: never instantiated to codegen this
/// iteration (no Term::New to build a StubT; unit-test-ratified only).
/// The load offset/correctness is exercised end-to-end only by
/// raw-buf.4's RawBuf path; here the entry exists to satisfy the
/// bijection. Retires with the stub in raw-buf.5.
pub(crate) fn emit_stubt_peek_int(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let s = emitter.locals[n - 1].1.clone();
let v = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {v} = load i64, ptr {s}\n"));
emitter.body.push_str(&format!(" ret i64 {v}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
pub(crate) fn emit_stubt_peek_float(emitter: &mut Emitter<'_>) -> Result<()> {
let n = emitter.locals.len();
let s = emitter.locals[n - 1].1.clone();
let v = emitter.fresh_ssa();
emitter.body.push_str(&format!(" {v} = load double, ptr {s}\n"));
emitter.body.push_str(&format!(" ret double {v}\n"));
emitter.body.push_str("}\n\n");
emitter.block_terminated = true;
Ok(())
}
(The implementer must confirm against emit_answer / emit_eq_str: the exact way to read a param SSA (emitter.locals[n-1].1 vs another accessor), the fresh_ssa name, and that expected_params/expected_ret match what llvm_type delivers for borrow (con StubT Int) → "ptr" and Int→"i64" / Float→"double" — the eq__Unit entry documents a past sig-mismatch from exactly this. Since the emit is never run this iteration, signature-correctness for check_sig is the bar, not load-offset correctness.)
- Step 4: Update
kernel_stub_module_round_tripsto coverpeek.
Edit crates/ailang-core/tests/design_schema_drift.rs (kernel_stub_module_round_trips, ~744). After the existing StubT.param_in assertions, add an assertion that the round-tripped module contains a peek Def::Fn whose body is Term::Intrinsic:
let peek = recovered.defs.iter().find_map(|d| match d {
Def::Fn(f) if f.name == "peek" => Some(f),
_ => None,
}).expect("kernel_stub ships the peek ratifier op (raw-buf.3)");
assert!(matches!(peek.body, Term::Intrinsic), "peek is an (intrinsic) marker");
(Confirm the test's in-scope names for Def / Term / the recovered-module binding against the existing body of the test before editing.)
- Step 5: Add the mono-symbol integration test.
Create crates/ail/tests/mono_scoped_symbol.rs. Mirror the workspace-build + monomorphise_workspace harness used by crates/ail/tests/mono_hash_stability.rs (read it for the exact API: how it constructs a Workspace from source modules incl. the auto-injected kernel_stub, and how it runs monomorphisation). A consumer module that calls StubT.peek at both Int and Float (each via a borrow-param fn, so no StubT value need be constructed — no Term::New):
//! raw-buf.3: a type-scoped polymorphic intrinsic call `StubT.peek`
//! monomorphises to the scope-qualified symbol `StubT_peek__<elem>`
//! (not the bare `peek__<elem>`). Proves the scope threads
//! resolution → FreeFnCall → MonoTarget → mint. No codegen / no
//! Term::New: assertion is on the monomorphised def names.
// <imports + harness mirrored from mono_hash_stability.rs>
const CONSUMER: &str = "(module peek_consumer\n (fn at_int\n (type (fn-type (params (borrow (con StubT (con Int)))) (ret (con Int))))\n (params s)\n (body (app StubT.peek s)))\n (fn at_float\n (type (fn-type (params (borrow (con StubT (con Float)))) (ret (con Float))))\n (params s)\n (body (app StubT.peek s))))";
#[test]
fn stubt_peek_monomorphises_to_scope_qualified_symbols() {
// build a workspace from CONSUMER (kernel_stub auto-injected),
// monomorphise, collect every synthesised mono FnDef name.
let names: Vec<String> = /* mirror mono_hash_stability harness */ ;
assert!(names.iter().any(|n| n == "StubT_peek__Int"),
"expected scope-qualified StubT_peek__Int; got: {names:?}");
assert!(names.iter().any(|n| n == "StubT_peek__Float"),
"expected scope-qualified StubT_peek__Float; got: {names:?}");
assert!(!names.iter().any(|n| n == "peek__Int"),
"bare peek__Int must NOT be minted (scope-qualified only)");
}
(The <imports + harness> and the names collection are filled by mirroring mono_hash_stability.rs verbatim for the workspace-build + monomorphise calls — that file is the authoritative harness; do not invent a new monomorphisation entry point. The three asserts are the load-bearing content.)
- Step 6: Run the new + affected tests.
Run: cargo test -p ail --test mono_scoped_symbol stubt_peek_monomorphises_to_scope_qualified_symbols
Expected: PASS — both scope-qualified symbols present, bare peek__Int absent.
Run: cargo test -p ailang-codegen intercepts_bijection_with_intrinsic_markers
Expected: PASS — the peek marker expands to exactly StubT_peek__Int + StubT_peek__Float, both matched by the new INTERCEPTS entries (1-marker→2-entries).
Run: cargo test -p ailang-core --test design_schema_drift kernel_stub_module_round_trips
Expected: PASS — the stub (now with peek) round-trips and peek is an (intrinsic).
- Step 7: Final full suite.
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 — baseline 669 + the one new mono_scoped_symbol test. The .ll snapshot tests stay green: peek is polymorphic, so codegen never instantiates a StubT_peek__* define without a call site, and this iteration adds no codegen consumer (the mono test stops at monomorphisation).
Self-review
-
Spec coverage.
- § Architecture pt 3 "scope-qualified mono symbols": Task 1.
- § Architecture pt 3 "bijection expansion over param-in": Task 2 Step 2.
- § Concrete "StubT.peek ratifier": Task 2 Step 1 + Step 3.
- § Concrete "scope-qualified mono symbol" (mono.rs:521/the mint sites): Task 1 Steps 4-7.
- § Data-flow "Mechanism": Task 1 (mint) + Task 2 Step 2 (collector).
- § Testing raw-buf.3 bullets (mono unit test, bijection green, round-trip updated, no count change beyond +1): Task 2 Steps 4-7.
-
Placeholder scan. No "TBD/TODO/implement later/similar to/add appropriate". The
<imports + harness>andnamescollection in Task 2 Step 5 are an explicit "mirror file X verbatim" instruction naming the authoritative source (mono_hash_stability.rs), with the load-bearing asserts given in full — not a placeholder. -
Type consistency.
scope: Option<String>consistent acrossFreeFnCall/MonoTarget::FreeFn/mono_target_key/ construction / mint.scoped_baseused at both mint sites + the key. Symbol stringsStubT_peek__Int/StubT_peek__Floatidentical across source-derived (collector), mono-minted (Task 1), and the entry names (Task 2 Step 3) — all flow throughmono_symbol_nwith aType::Conelement, guaranteeing byte-match. -
Step granularity. Each step is one struct edit, one threaded field, one helper, one fn, or one command. Task 1 Step 2's "every other
free_fn_ownersite" is compiler-enumerated (a tuple-shape change), bounded and mechanical. -
No commit steps. None.
-
Pin/replacement substring contiguity. The asserted substrings (
StubT_peek__Int,StubT_peek__Float,peek__Int-absent) are produced at runtime bymono_symbol_n, not by a verbatim file body the plan also ships — no soft-wrap split risk. Thepeeksource.ail body is byte-checked bykernel_stub_module_round_trips(structural), not by a substring pin. -
Compile-gate vs. deferred-caller ordering. Task 1 changes three shapes (
FreeFnCallfield,MonoTarget::FreeFnfield,free_fn_ownertuple) that break compilation until all sites are threaded; ALL are inside Task 1, which ends withcargo build --workspace(Step 8) + full test (Step 9). No caller deferred past the gate. Task 2 adds entries/fns/tests with no cross-task signature break. -
Verification-command filter strings. Task 2 Steps 6 filters name tests this plan creates (
stubt_peek_monomorphises_to_scope_qualified_symbols, in the file created Step 5) or that exist per recon (intercepts_bijection_with_intrinsic_markers,kernel_stub_module_round_trips). Task 1 Step 9 + Task 2 Step 7 use the unfiltered suite with an explicit count assertion (awk), so "0 ran" cannot pass as success. -
Parse-the-bytes gate. The one surface-language body inlined is the
peekop (Task 2 Step 1), fencedtext—kernel_stubis a reserved built-in module name (ail checkstandalone →reserved-module-name), so the configuredailparser cannot validate it in isolation; the gate is N/A for it. Its Form-A was nonetheless verified this session under a non-reserved probe module (ok (32 symbols across 3 modules)), and it is structurally ratified bykernel_stub_module_round_trips(Task 2 Step 4) + theimplementcompile gate. All other inlined bodies are Rust — caught by the compile gate, not the parse gate.