# intrinsic-bodies.1 — the mechanism — Implementation Plan

> **Parent spec:** `docs/specs/0055-intrinsic-bodies.md` (committed 5b66de7)
>
> **For agentic workers:** REQUIRED SUB-SKILL: use the `implement`
> skill to run this plan. Steps use `- [ ]` checkboxes for tracking.

**Goal:** Introduce `Term::Intrinsic`, a new leaf term that is the body
of a compiler-supplied definition, and wire it through surface, checker,
and codegen so a kernel-tier `(intrinsic)` function builds and runs via
the intercept registry — ratified by a throwaway `answer : () -> Int`
intrinsic in the `kernel_stub` fixture.

**Architecture:** `Term::Intrinsic` is a unit variant on the `Term`
enum (`{"t":"intrinsic"}`, additive, hash-stable like `Term::Recur`).
`FnDef.body` / `Term::Lam.body` keep type `Term` / `Box<Term>`; a def is
intrinsic iff `matches!(body, Term::Intrinsic)`. The parser maps
`(intrinsic)` (a top-level fn body-slot clause, or a lambda positional
body) to `Term::Intrinsic`; the printer inverts it. The checker treats
such a body as signature-only and rejects it outside kernel-tier /
prelude. Codegen routes it through `intercepts::lookup` instead of
`lower_term`. The cross-crate `match`-on-`Term` walkers gain a leaf arm,
enumerated by the compiler (no-wildcard exhaustive matches).

**Tech Stack:** `crates/ailang-core` (ast + canonical/hash/visit),
`crates/ailang-surface` (parse/print), `crates/ailang-check`
(lib + the analysis walkers), `crates/ailang-codegen` (lib +
intercepts + escape/lambda), `crates/ailang-prose`,
`crates/ailang-kernel-stub`, `crates/ail/tests` (E2E),
`design/contracts/0002-data-model.md`.

---

## Files this plan creates or modifies

- Modify: `crates/ailang-core/src/ast.rs:602-607` — add `Term::Intrinsic` unit variant after `New`.
- Modify: `crates/ailang-core/src/` canonical/hash/visit walkers — leaf arm (compile-driven, Task 1).
- Modify: `crates/ailang-core/tests/schema_coverage.rs:78` — `EXPECTED_VARIANTS` + corpus observation.
- Modify: `crates/ailang-surface/src/parse.rs` — `parse_fn` (572-655), `parse_lam` (1501-1539).
- Modify: `crates/ailang-surface/src/print.rs` — fn-def (211-213), lam (526-554).
- Modify: `crates/ailang-check/src/lib.rs` — `env` kernel-tier flag (~1846), sig-only check (~2095/2244), `CheckError::IntrinsicOutsideKernelTier` + `code()` (772-819).
- Modify: `crates/ailang-check/src/{lift,reuse_shape,pre_desugar_validation,linearity,uniqueness,mono}.rs` — leaf arms (compile-driven, Task 3).
- Modify: `crates/ailang-codegen/src/lib.rs` — routing (1320-1324), and the `lower_term` Term arm (~2075 neighbourhood).
- Modify: `crates/ailang-codegen/src/{escape,lambda}.rs` — leaf arms (compile-driven, Task 3).
- Modify: `crates/ailang-prose/src/lib.rs` — leaf arms (937/1150/1307 neighbourhood, Task 3).
- Modify: `crates/ailang-codegen/src/intercepts.rs:35-168` — `answer` registry entry.
- Modify: `crates/ailang-kernel-stub/src/lib.rs:27-37` — `answer` intrinsic fn in `STUB_AIL`.
- Create: `examples/kernel_answer.ail` — consumer that calls `(answer)`, for the E2E ratifier.
- Modify: `crates/ail/tests/e2e.rs` — `answer` build+run test.
- Modify: `design/contracts/0002-data-model.md:106-194` — `{ "t": "intrinsic" }` Term entry.

---

## Task 1: `Term::Intrinsic` variant + in-core walkers

**Files:**
- Modify: `crates/ailang-core/src/ast.rs:602-607`
- Modify: core walkers the compiler flags (canonical / hash / visit / pretty)

- [ ] **Step 1: Add the variant**

In `crates/ailang-core/src/ast.rs`, after the `New { ... }` variant
(ends line 606) and before the `Term` enum's closing brace (line 607),
add:

```rust
    /// The body of a compiler-supplied ("intrinsic") definition. Legal
    /// only as the body of a `FnDef` or a `Term::Lam`, and only in a
    /// `(kernel)`-tier module or the prelude (enforced at typecheck,
    /// `IntrinsicOutsideKernelTier`). Never reduces to a value: codegen
    /// consumes it via `intercepts::lookup` on the def's mangled name;
    /// the typechecker treats a def with this body as signature-only.
    /// A def is intrinsic iff `matches!(body, Term::Intrinsic)`. Additive
    /// `"t":"intrinsic"` tag (unit variant via the enum's
    /// `rename_all = "lowercase"`); pre-existing fixtures hash
    /// bit-identically — none carry the tag. Precedent: `Term::Recur`
    /// (a non-reducing control-transfer leaf).
    Intrinsic,
```

- [ ] **Step 2: Build core; let the compiler enumerate the broken exhaustive matches**

Run: `cargo build -p ailang-core 2>&1 | grep -E "not covered|non-exhaustive|E0004" | head -40`
Expected: a list of `match`-on-`Term` sites in `ailang-core/src/` that
lack a wildcard (canonical serialise, hash, any visit/walk). Each is a
site Step 3 fixes.

- [ ] **Step 3: Add the leaf arm at each flagged core site**

For each site the compiler flags, add a `Term::Intrinsic` arm by kind:
- **canonical / serialise** (the function that turns a `Term` into
  canonical bytes / JSON): `Term::Intrinsic` serialises as the unit
  variant `{"t":"intrinsic"}` — serde derives this automatically, so a
  hand-written canonicaliser arm (if any) emits the same tag with no
  children.
- **hash**: hash the discriminant/tag only (no sub-terms) — mirror the
  shape of the `Term::Recur` arm's tag-hash if hashing is hand-rolled;
  if hashing goes through serde/canonical bytes, no arm is needed beyond
  serialise.
- **visit / walk / map over sub-terms**: `Term::Intrinsic => {}` (no
  sub-terms to recurse into) — for a `map`-style walker that must return
  a `Term`, `Term::Intrinsic => Term::Intrinsic`.
- **pretty (`crates/ailang-core/src/pretty.rs`)**, if it matches `Term`
  exhaustively: emit the diagnostic string `intrinsic`.

- [ ] **Step 4: Build core green**

Run: `cargo build -p ailang-core`
Expected: compiles, 0 errors.

---

## Task 2: Surface — parse + print `(intrinsic)` ↔ `Term::Intrinsic`

**Files:**
- Modify: `crates/ailang-surface/src/parse.rs` (`parse_fn` 572-655, `parse_lam` 1501-1539)
- Modify: `crates/ailang-surface/src/print.rs` (fn-def 211-213, lam 526-554)
- Test: round-trip rides the existing `crates/ailang-surface/tests/round_trip.rs` corpus gate (a fixture lands in Task 6).

- [ ] **Step 1: Accept `(intrinsic)` as a fn body-slot clause in `parse_fn`**

In `parse_fn` (parse.rs:572-655): today an unknown attribute head is
rejected at 617-626 (the `unknown fn attribute` error), and a `(body
...)` is mandatory at 641-645. Add an `intrinsic` arm to the
attribute-dispatch so that a `(intrinsic)` clause sets the fn body to
`Term::Intrinsic`, and relax the mandatory-`(body)` enforcement so that
*exactly one* of `(body X)` or `(intrinsic)` is present (both-absent
stays an error; the grammar offers no way to write both, since each
fills the single body slot). Concretely: track the body as it is parsed;
when the `intrinsic` head is seen, set `body = Some(Term::Intrinsic)`;
at 641-645 require `body.is_some()` rather than specifically a `(body)`
clause.

- [ ] **Step 2: Accept `(intrinsic)` at the lambda positional body in `parse_lam`**

In `parse_lam` (parse.rs:1501-1539): the positional body term is read at
1530. Where the body term is parsed, if the token form is `(intrinsic)`,
produce `Term::Intrinsic` instead of recursing into general term
parsing. (The lambda's body is positional, so `(intrinsic)` simply *is*
the body term form here — no attribute loop.)

- [ ] **Step 3: Print `Term::Intrinsic` in fn-def position**

In `print.rs` fn-def printer (211-213, the `(body …)` emit): when the
fn's body is `Term::Intrinsic`, emit `(intrinsic)` in the body slot
instead of `(body <term>)`.

- [ ] **Step 4: Print `Term::Intrinsic` in lambda body position**

In `print.rs` lam printer (526-554, body emit at 552-553): when the
lambda body is `Term::Intrinsic`, emit `(intrinsic)` at the positional
body slot.

- [ ] **Step 5: Add a print arm if the surface term-printer matches `Term` exhaustively**

Run: `cargo build -p ailang-surface 2>&1 | grep -E "not covered|non-exhaustive" | head`
Expected: if a general term-printer in print.rs matches `Term`
exhaustively, it is flagged. Add `Term::Intrinsic => …` emitting
`(intrinsic)` (this is the same emit Steps 3-4 use; a general
term-printer reaching `Term::Intrinsic` only happens via a fn/lam body,
so emitting `(intrinsic)` is correct).

- [ ] **Step 6: Build surface green**

Run: `cargo build -p ailang-surface`
Expected: compiles, 0 errors.

---

## Task 3: Cross-crate walker sweep (check + codegen + prose)

**Files:**
- Modify: `crates/ailang-check/src/{lift,reuse_shape,pre_desugar_validation,linearity,uniqueness,mono}.rs`
- Modify: `crates/ailang-codegen/src/{escape,lambda}.rs`
- Modify: `crates/ailang-prose/src/lib.rs`

(Codegen's `lib.rs` `lower_term` arm and the checker's body-check arm
are NOT here — those are the *meaningful* arms in Tasks 4-5. This task
is only the mechanical leaf arms in the generic walkers.)

- [ ] **Step 1: Build the workspace; enumerate the remaining broken matches**

Run: `cargo build --workspace 2>&1 | grep -E "not covered|non-exhaustive|E0004" | head -60`
Expected: a list of `match`-on-`Term` sites across check / codegen /
prose lacking a wildcard. These are exactly the sites that match
`Term::New` today (verified set: `lift.rs`, `reuse_shape.rs`,
`pre_desugar_validation.rs`, `linearity.rs`, `uniqueness.rs`,
`mono.rs:1284/1666`, `escape.rs:205/409/548`, `lambda.rs:498`,
`prose/src/lib.rs:937/1150/1307`, `workspace.rs:1285/1428`). Sites that
have a `_ =>` wildcard will NOT appear — do not add arms there.

- [ ] **Step 2: Add the leaf arm at each flagged site, by walker kind**

`Term::Intrinsic` carries no sub-terms, so every arm here is a leaf:
- **analysis walkers** (linearity, uniqueness, escape, reuse_shape,
  pre_desugar_validation — these compute over expression structure):
  `Term::Intrinsic => { /* no sub-terms; no vars, captures, or
  ownership obligations */ }` returning the walker's empty/identity
  value (e.g. `false` for an "any subterm satisfies P" predicate,
  `Ok(())` for a validation pass, an empty set for a free-vars
  collector).
- **`mono.rs:1284/1666`** (mono cursor walk): `Term::Intrinsic => {}` —
  no mono targets inside an intrinsic body. (Note: `synthesise_mono_fn`
  at 957-970 is NOT touched — its lambda destructure `Term::Lam {
  params, body, .. } => (params, *body)` already passes an inner
  `Term::Intrinsic` straight through to the synthesised `FnDef.body`.)
- **`lambda.rs:498`** (lambda/closure lowering walk): `Term::Intrinsic
  => {}` — an intrinsic body captures nothing and lifts no lambda.
- **`prose/src/lib.rs`** (Form-B projection): `Term::Intrinsic` renders
  as the prose phrase `compiler-supplied (intrinsic)` (mirror the
  terse style of the surrounding arms; prose is lossy and human-facing).
- **`workspace.rs:1285/1428`** (if flagged — term walk during workspace
  load): `Term::Intrinsic => {}` / `Term::Intrinsic` identity.

- [ ] **Step 3: Build the workspace green (lower_term + check body arms still stubbed)**

Run: `cargo build --workspace 2>&1 | grep -E "not covered|non-exhaustive" | head`
Expected: empty — OR only the two intentional sites left for Tasks 4-5
(codegen `lower_term`'s `Term` match and the checker's per-fn body
check). If those two are flagged, add a temporary
`Term::Intrinsic => unreachable!("handled in Task 4/5")` so the build is
green; Tasks 4-5 replace them. If they already have wildcards, nothing
to do.

- [ ] **Step 4: Full build green**

Run: `cargo build --workspace`
Expected: compiles, 0 errors.

---

## Task 4: Checker — signature-only + `intrinsic-outside-kernel-tier`

**Files:**
- Modify: `crates/ailang-check/src/lib.rs` (env flag ~1846, `check_fn` body-check ~2095/2244, `CheckError` 393-818, `code()` 772-819)
- Test: `crates/ailang-check` in-source `#[cfg(test)] mod tests` OR `crates/ail/tests/` E2E (the accept case is the kernel_stub ratifier in Task 7; the reject case is here)

- [ ] **Step 1: Add the `CheckError` variant**

In `crates/ailang-check/src/lib.rs` `CheckError` enum (393-818), add a
variant alongside the most-recent additive exemplar
(`ParamNotInRestrictedSet` at ~817):

```rust
    /// An `(intrinsic)` body appears in a module that is neither
    /// kernel-tier nor the prelude. User code may not declare a body as
    /// compiler-supplied — the honesty-rule guard at the workspace
    /// boundary (design/contracts/0007-honesty-rule.md).
    IntrinsicOutsideKernelTier { def: String, module: String },
```

- [ ] **Step 2: Register its code in `code()`**

In the `code()` registry (772-819), add the arm (mirror the
`ParamNotInRestrictedSet` arm shape):

```rust
            CheckError::IntrinsicOutsideKernelTier { .. } => "intrinsic-outside-kernel-tier",
```

- [ ] **Step 3: Thread a kernel-tier flag into `env`**

Where `env.current_module` is set (lib.rs:1846), also set a new
`env.current_module_kernel_tier: bool` computed from the module `m`
being checked: `m.kernel || m.name == "prelude"`. Add the field to the
`Env` struct (default `false`). This avoids threading `m` through every
`check_fn`/`check_def` call.

- [ ] **Step 4: Signature-only + scope-guard at the per-fn body check**

At the per-fn body check (the synth/verify over `&f.body`, ~2244): when
`matches!(f.body, Term::Intrinsic)` (top-level fn) OR the fn's body is a
`Term::Lam` whose inner body is `Term::Intrinsic` (synthesised
instance-method), skip body inference (the signature is already
validated from `f.ty`). Before skipping, enforce the scope guard: if
`!env.current_module_kernel_tier`, push
`CheckError::IntrinsicOutsideKernelTier { def: f.name.clone(), module:
env.current_module.clone() }`. (For the instance-method case the
synthesised fn's home module is the instance's module, already reflected
in `env.current_module`.)

- [ ] **Step 5: Write the reject test (subprocess, temp file)**

Add to `crates/ail/tests/e2e.rs` a reject test modelled **exactly** on
the verified `check_json_unbound_var` pattern (e2e.rs:1236-1260): it
writes a user-module `.ail` to a temp dir, runs `ail check <file>
--json` via `Command`, asserts exit code 1, parses stdout as a JSON
diagnostics array, and asserts an `error`-severity diagnostic with the
expected `code`. A temp file (not an `examples/` fixture) is used so the
reject case does not enter the round-trip corpus or need a carve-out.
Test source (Rust):

```rust
#[test]
fn intrinsic_in_user_module_is_rejected() {
    let tmp = std::env::temp_dir().join(format!(
        "ailang_intrinsic_reject_{}",
        std::process::id()
    ));
    std::fs::create_dir_all(&tmp).unwrap();
    let src = tmp.join("intr_user.ail");
    std::fs::write(
        &src,
        "(module intr_user (fn f (type (fn-type (params (con Int)) (ret (con Int)))) (params n) (intrinsic)))",
    )
    .unwrap();

    let output = Command::new(ail_bin())
        .args(["check", src.to_str().unwrap(), "--json"])
        .output()
        .expect("ail check --json failed to run");

    let code = output.status.code().expect("process terminated by signal");
    assert_eq!(
        code, 1,
        "expected exit 1, stderr: {}",
        String::from_utf8_lossy(&output.stderr)
    );
    let stdout = String::from_utf8(output.stdout).expect("stdout utf8");
    let diags: serde_json::Value =
        serde_json::from_str(stdout.trim()).expect("stdout must be valid JSON");
    let arr = diags.as_array().expect("diagnostics must be a JSON array");
    assert!(
        arr.iter().any(|d| {
            d.get("severity").and_then(|v| v.as_str()) == Some("error")
                && d.get("code").and_then(|v| v.as_str()) == Some("intrinsic-outside-kernel-tier")
        }),
        "expected an error with code intrinsic-outside-kernel-tier; got: {stdout}"
    );
}
```

(`ail_bin()` and the `Command`/`serde_json` imports are already present
in e2e.rs — this test reuses the exact harness `check_json_unbound_var`
uses.)

- [ ] **Step 6: Run the reject test**

Run: `cargo test -p ail --test e2e intrinsic_in_user_module_is_rejected`
Expected: PASS — exit 1 with code `intrinsic-outside-kernel-tier`.

---

## Task 5: Codegen — route `Term::Intrinsic` through the intercept registry

**Files:**
- Modify: `crates/ailang-codegen/src/lib.rs` (the fn-body emit at 1320-1324; the `lower_term` `Term` match neighbourhood ~2075)

- [ ] **Step 1: Skip body-lowering for an intrinsic fn body**

At the fn-body emit (lib.rs:1320-1324): the path today is
`body_was_intercepted = self.try_emit_primitive_instance_body(&f.name,
…)`; then `if !body_was_intercepted { let (val, val_ty) =
self.lower_term(&f.body)?; … }`. Add: when `matches!(f.body,
Term::Intrinsic)` and `!body_was_intercepted` (no name-based intercept
fired), attempt the registry directly via `intercepts::lookup(&f.name)`;
on hit, emit the intercept (the same call shape
`try_emit_primitive_instance_body` uses) and treat the body as emitted;
on miss, return the existing deferral-style `CodegenError::Internal`
naming the unregistered intrinsic. Critically: never fall through to
`self.lower_term(&f.body)` when the body is `Term::Intrinsic`.

  (Implementer note: `try_emit_primitive_instance_body` already consults
  `intercepts::lookup` internally as of raw-buf.1. Confirm whether
  `answer` — a top-level nullary fn, name `answer`, not a mangled
  `method__Type` — is reached by that existing call with `f.name ==
  "answer"`. If yes, Step 1 reduces to: ensure an intrinsic body whose
  intercept fired is treated as complete, and an intrinsic body whose
  intercept did NOT fire is an error rather than a `lower_term` attempt.
  If the existing call only matches mangled instance names, add the
  explicit `intercepts::lookup(&f.name)` branch here.)

- [ ] **Step 2: Handle `Term::Intrinsic` in the `lower_term` match**

In `lower_term`'s `Term` match (the same match that holds the
`Term::New` deferral at ~2075), add:

```rust
            Term::Intrinsic => Err(CodegenError::Internal(
                "Term::Intrinsic must be consumed by the intercept route in fn-body \
                 emission, not lowered as an expression; reaching lower_term means an \
                 intrinsic body escaped its definition slot".into(),
            )),
```

(This is the "reaching `lower_term` is a codegen-internal error" clause
from the spec § Architecture point 4. It also satisfies the temporary
`unreachable!` if Task 3 Step 3 left one here.)

- [ ] **Step 3: Build codegen green**

Run: `cargo build -p ailang-codegen`
Expected: compiles, 0 errors.

---

## Task 6: `answer` intercept + kernel_stub fixture + schema-coverage

**Files:**
- Modify: `crates/ailang-codegen/src/intercepts.rs:35-168` (INTERCEPTS table) + the `Intercept` struct neighbourhood (27-33)
- Modify: `crates/ailang-kernel-stub/src/lib.rs:27-37` (STUB_AIL)
- Modify: `crates/ailang-core/tests/schema_coverage.rs:78` (EXPECTED_VARIANTS + corpus note)

- [ ] **Step 1: Register the `answer` intercept**

In `crates/ailang-codegen/src/intercepts.rs`, add an entry to
`INTERCEPTS` (35-168) following the existing entry shape (`name`,
`expected_params`, `expected_ret`, `wants_alwaysinline`, `emit`):

```rust
    Intercept {
        name: "answer",
        expected_params: &[],
        expected_ret: "i64",
        wants_alwaysinline: false,
        emit: emit_answer,
    },
```

And add the free emit fn alongside the other `emit_*` fns:

```rust
/// Ratifier intrinsic for intrinsic-bodies.1: `answer : () -> Int`
/// returns the constant 42. Exercises the Term::Intrinsic → registry
/// route end-to-end. May be retired once a real kernel-tier intrinsic
/// (raw-buf) lands.
fn emit_answer(e: &mut Emitter) -> Result<()> {
    e.body.push_str("  ret i64 42\n");
    Ok(())
}
```

(The implementer confirms `Result`/`Emitter` imports and the exact
`e.body.push_str` idiom against the sibling emit fns; `answer` is
purely additive and does NOT break `registry_contains_all_legacy_arms`,
which only asserts the 18 legacy names resolve — it does not assert the
table contains *only* them.)

- [ ] **Step 2: Add the `answer` intrinsic to STUB_AIL**

In `crates/ailang-kernel-stub/src/lib.rs` `STUB_AIL` (27-37), add an
`answer` fn to the `kernel_stub` module body. The stub already carries
`(kernel)`, so `answer` is in a kernel-tier module and the scope guard
passes. Add (Form-A, inside the module):

```scheme
  (fn answer
    (doc "Ratifies the intrinsic mechanism end-to-end: codegen intercept answer emits ret i64 42.")
    (type (fn-type (params) (ret (con Int))))
    (params)
    (intrinsic))
```

- [ ] **Step 3: Verify the stub round-trips with the intrinsic fn**

Run: `cargo test -p ailang-core --test design_schema_drift kernel_stub_module_round_trips`
Expected: PASS — the stub (now carrying an intrinsic fn) parses, prints,
re-parses to canonical-byte equality. This is the round-trip ratifier
for `Term::Intrinsic` on both parse and print.

- [ ] **Step 4: Observe `Term::Intrinsic` in the schema-coverage corpus**

Run: `cargo test -p ailang-core --test schema_coverage every_ast_variant_is_observed`
Expected: this may FAIL with `Term::Intrinsic` not observed, because the
corpus scans `examples/*.ail` and the stub is an in-crate const, not an
`examples/` fixture. If it fails: add `Term::Intrinsic` handling so the
corpus sees it — the `examples/kernel_answer.ail` fixture created in
Task 7 Step 1 carries a consumer of `answer` but NOT an intrinsic body
itself (the intrinsic lives in the stub). Per the recon note,
`Term::New` is in `visit_term` but absent from `EXPECTED_VARIANTS`
(schema_coverage.rs:252-255) precisely because no `examples/` fixture
exercises it. To avoid repeating that gap, add a minimal `examples/`
fixture that DOES carry an intrinsic body in a kernel-tier module:

Create `examples/kernel_intrinsic_smoke.ail`:

```scheme
(module kernel_intrinsic_smoke
  (kernel)
  (fn smoke
    (doc "Schema-coverage fixture: exercises Term::Intrinsic in a kernel-tier module.")
    (type (fn-type (params) (ret (con Int))))
    (params)
    (intrinsic)))
```

Then add `Term::Intrinsic` to `EXPECTED_VARIANTS` (schema_coverage.rs:78)
so the corpus gate asserts it is observed.

- [ ] **Step 5: Re-run schema-coverage green**

Run: `cargo test -p ailang-core --test schema_coverage every_ast_variant_is_observed`
Expected: PASS.

NOTE: `examples/kernel_intrinsic_smoke.ail` also enters the round-trip
corpus gate (`round_trip.rs` reads `examples/*.ail`), so it additionally
ratifies parse∘print=id for a top-level intrinsic fn. Confirm
`cargo test -p ailang-surface --test round_trip` stays green after
adding it.

---

## Task 7: E2E ratifier — `answer` builds and runs, observing 42

**Files:**
- Create: `examples/kernel_answer.ail`
- Modify: `crates/ail/tests/e2e.rs`

- [ ] **Step 1: Create the consumer fixture**

The `answer` intrinsic lives in `kernel_stub`, which is auto-imported
into every workspace (kernel-tier, prep.3). A consumer module calls it
unqualified-or-qualified per the kernel-tier import rule. Create
`examples/kernel_answer.ail`:

```scheme
(module kernel_answer
  (fn main
    (doc "E2E ratifier: prints the intrinsic answer (42).")
    (type (fn-type (params) (ret (con Int))))
    (params)
    (body (do io/print_int (app kernel_stub.answer)))))
```

NOTE: the implementer verifies the exact call form for a kernel-tier
nullary fn against an existing kernel_stub consumer test (how
`kernel_stub.new` is called in the prep.3 / kernel-extension E2E
fixtures) and against the `(app f)` nullary-call form
(`Term::App.args = []`). If `io/print_int` is not the exact effect-op
name, model the print on an existing `examples/*.ail` that prints an Int
(grep `examples/` for `io/print`).

- [ ] **Step 2: Verify it parses + checks**

Run: `cargo run -q -p ail --bin ail -- check examples/kernel_answer.ail`
Expected: `ok (...)` — exit 0. (The `answer` intrinsic resolves through
the auto-imported kernel_stub; the consumer body is an ordinary call.)

- [ ] **Step 3: Write the build+run E2E test**

Add to `crates/ail/tests/e2e.rs`, modelled on the existing
`build_and_run` helper (13-38) and an existing
`*_compiles_and_runs`/`*_prints_*` test
(e.g. `eq_primitives_smoke_compiles_and_runs`):

```rust
#[test]
fn answer_intrinsic_builds_and_runs_printing_42() {
    let out = build_and_run("kernel_answer.ail");
    assert_eq!(out.trim(), "42");
}
```

(`build_and_run(example: &str) -> String` is verified at e2e.rs:13-38:
it takes the fixture filename *with* `.ail` extension — existing calls
are `build_and_run("sum.ail")` — internally asserts build+run success,
and returns stdout. The `.trim()` + `assert_eq!` shape matches the
existing `*_prints_*` tests.)

- [ ] **Step 4: Run the ratifier**

Run: `cargo test -p ail --test e2e answer_intrinsic_builds_and_runs_printing_42`
Expected: PASS — the intrinsic mechanism works from source to native.

---

## Task 8: Data-model contract entry

**Files:**
- Modify: `design/contracts/0002-data-model.md` (Term section, 106-194)

- [ ] **Step 1: Document `Term::Intrinsic`**

In `design/contracts/0002-data-model.md`, in the Term (expression)
section, after the `new` entry (ends ~194), add:

````markdown
// intrinsic: the body of a compiler-supplied definition. Legal only as
// a FnDef/Lam body, only in a (kernel)-tier module or the prelude
// (typecheck: intrinsic-outside-kernel-tier). Never reduces to a value;
// codegen consumes it via the intercept registry. A def is intrinsic
// iff its body is this term. Strictly additive (no skip_serializing_if;
// pre-existing fixtures hash bit-identically — none carry the tag).
{ "t": "intrinsic" }
````

Also add a clause to the `fn` and `lam` prose noting the body may be
`{ "t": "intrinsic" }`, in which case it is compiler-supplied.

- [ ] **Step 2: Verify the schema-drift mirror stays green**

Run: `cargo test -p ailang-core --test design_schema_drift`
Expected: PASS — the contract's documented Term shapes match the AST.

---

## Final gate

- [ ] **Step F1: Full workspace test**

Run: `cargo test --workspace`
Expected: all green. Baseline was 667 (post-raw-buf.1); this iteration
adds: `intrinsic_in_user_module_is_rejected`,
`answer_intrinsic_builds_and_runs_printing_42`, plus the corpus/round-trip
fixtures ride existing dynamic gates (no new named test). Expected
≈669 passing, 0 failed. Pre-existing hash pins
(`crates/ailang-core/tests/hash_pin.rs`,
`crates/ailang-surface/tests/prelude_module_hash_pin.rs`) MUST stay
green — `Term::Intrinsic` is additive and no existing fixture carries
it, so no hash moves in .1. (The prelude is NOT migrated in .1 — that is
.2.)

- [ ] **Step F2: Regression scripts**

Run: `bench/check.py && bench/compile_check.py`
Expected: no regression versus baseline.
