# cma-revival.1 — Cross-model harness corpus revival — Implementation Plan > **Parent spec:** `docs/specs/0069-cma-corpus-revival.md` > > **For agentic workers:** REQUIRED SUB-SKILL: use the `implement` skill to > run this plan. Steps use `- [ ]` checkboxes for tracking. **Goal:** Bring the schema-dead corpus of the cross-model authoring-form harness (`experiments/2026-05-12-cross-model-authoring/`) up to today's language and get both crates' `cargo test` suites green in mock mode — no live IONOS call. **Architecture:** The corpus `.ail.json` files lost validity when the implicit-cutover made `param_modes`/`ret_mode` mandatory. Most files already carry their `param_modes`; the repair completes the missing fields in place (preserving deliberate `borrow` annotations), adds two new author-facing examples (`loop`/`recur`, `new`) plus two `spec.md` sections, removes the deleted `ParamMode::Implicit` from the completeness test while allowlisting the non-authorable `Term::Intrinsic` out, and migrates the mock fixture's embedded programs so the harness score assertions still hold. **Tech Stack:** `experiments/2026-05-12-cross-model-authoring/` (render + harness Cargo crates outside the root workspace, built via `--manifest-path`); `ail check`/`ail parse` from `target/debug/ail` as the per-file oracle. **Files this plan creates or modifies:** - Modify: `experiments/.../master/examples/*.ail.json` (13) — complete schema fields - Modify: `experiments/.../master/tasks/*.reference.ail.json` (4) — complete schema fields - Modify: `experiments/.../master/examples/param_modes_all.ail.json` — rewrite (own/borrow only) - Create: `experiments/.../master/examples/loop_sum.ail.json` — Loop/Recur example - Create: `experiments/.../master/examples/new_rawbuf.ail.json` — New example - Modify: `experiments/.../master/spec.md` — §4 rewrite + two new sections - Modify: `experiments/.../render/tests/spec_completeness.rs` — variant coverage + Intrinsic allowlist - Modify: `experiments/.../rendered/json.md`, `experiments/.../rendered/ail.md` — regenerated - Modify: `experiments/.../harness/tests/fixtures/mock_full_run.json` — migrate embedded programs Throughout, `AIL` = `target/debug/ail` (built, mtime 2026-06-02) and `XP` = `experiments/2026-05-12-cross-model-authoring`. --- ### Task 1: Complete schema fields in the 13 examples + 4 reference solutions **Files:** - Modify: `$XP/master/examples/*.ail.json` (all 13 except `param_modes_all`, which is Task 2) - Modify: `$XP/master/tasks/*.reference.ail.json` (all 4) The transformation, applied to every `"type": { "k": "fn", … }` object in each file: **preserve any existing `param_modes` verbatim**, then ensure both mode fields are present: - `param_modes`: if absent, set `[]` for a 0-parameter fn, else one `"own"` per parameter (the natural consume default). If present, leave unchanged. - `ret_mode`: if absent, set `"own"` (`borrow`-return is forbidden by contract 0015, so `own` is the only legal value). Field placement: `param_modes` directly after `params`, `ret_mode` directly after `ret`, matching the canonical key order in the shipped `examples/` fixtures. - [ ] **Step 1: Worked example A — `fn_returns_int.ail.json` (0-param)** The `answer` fn's `type` block changes from: ```json "type": { "k": "fn", "params": [], "ret": { "k": "con", "name": "Int" }, "effects": [] }, ``` to: ```json "type": { "k": "fn", "params": [], "param_modes": [], "ret": { "k": "con", "name": "Int" }, "ret_mode": "own", "effects": [] }, ``` - [ ] **Step 2: Worked example B — `data_with_match.ail.json` (existing `borrow`, add `ret_mode`)** Both fns already carry `"param_modes": ["borrow"]` over `List`. `List` is a boxed ADT, so `borrow` is legal here — leave it untouched; add `"ret_mode": "own"` directly after each `ret`. Do NOT rewrite `borrow` to `own`. (This is the only legal `borrow` in the corpus; the illegal `(borrow Int)` lived only in `param_modes_all`, handled in Task 2. `borrow` over an unboxed value type is a `borrow-over-value` error — `ail check` in Step 4 catches any such slip.) - [ ] **Step 3: Apply the transformation to all remaining corpus files** Files needing only `ret_mode` (already have `param_modes`): `data_simple.ail.json`. Files needing `param_modes` + `ret_mode`: `bool_str`, `fn_calls_prelude`, `fn_returns_int`, `fn_with_do_seq`, `fn_with_lambda`, `forall_polymorphic`, `match_literal_pattern`, and the 4 `tasks/*.reference.ail.json`. Files with no fn defs (no change to a fn type, but still verify they load): `class_def`, `floats`, `instance_def`. For each value-taking fn, default each parameter to `"own"`. - [ ] **Step 4: Verify every file checks green** Run: ``` for f in $XP/master/examples/*.ail.json $XP/master/tasks/*.reference.ail.json; do target/debug/ail check "$f" >/dev/null 2>&1 && echo "OK $f" || echo "FAIL $f" done ``` Expected: every line `OK` (param_modes_all may FAIL here — it is rewritten in Task 2; re-run after Task 2). A `FAIL` on a value-taking fn means a wrong mode guess — flip that parameter `own`↔`borrow` per the check error and re-run. --- ### Task 2: Rewrite `param_modes_all` and spec.md §4 (Implicit is deleted) **Files:** - Modify: `$XP/master/examples/param_modes_all.ail.json` - Modify: `$XP/master/spec.md` (§4 "Mode annotations", lines ~134-193) **Why not "two Int functions":** the typechecker rejects `(borrow Int)` with `borrow-over-value` — unboxed value types (`Int`/`Bool`/`Float`/`Unit`) are always `own`; `borrow` is legal only over a boxed type (an ADT). So the example carries a small boxed ADT and borrows over *that*. The `.ail` below is verified to parse + check green (parse-gate, 2026-06-02). - [ ] **Step 1: Author the rewritten example as `.ail` and canonicalise** Write `/tmp/param_modes_all.ail` (module name MUST be `param_modes_all` — the checker enforces module-name = filename): ``` (module param_modes_all (data List (ctor Nil) (ctor Cons (con Int) (con List))) (fn f_own_value (doc "(own Int) — unboxed value types (Int/Bool/Float/Unit) are always own; borrow over them is a borrow-over-value error.") (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params x) (body x)) (fn f_borrow_boxed (doc "(borrow List) — boxed types may be borrowed; the callee reads without consuming.") (type (fn-type (params (borrow (con List))) (ret (own (con Int))))) (params xs) (body (match xs (case (pat-ctor Nil) 0) (case (pat-ctor Cons h t) (app + 1 (app f_borrow_boxed t))))))) ``` Run: `target/debug/ail parse /tmp/param_modes_all.ail > $XP/master/examples/param_modes_all.ail.json` - [ ] **Step 2: Verify it checks green** Run: `target/debug/ail check $XP/master/examples/param_modes_all.ail.json` Expected: exit 0. (Canonical output carries `f_own_value` param_modes `["own"]`, `f_borrow_boxed` param_modes `["borrow"]`, both `ret_mode "own"`.) - [ ] **Step 3: Rewrite spec.md §4 prose** In `$XP/master/spec.md` §4 "Mode annotations", remove every mention of a defaulted/omitted/`implicit` mode. Present the exhaustive rule: every `Type::Fn` parameter carries exactly one of `own` (callee consumes) or `borrow` (callee may not consume); `param_modes` is a list with one entry per parameter; `ret_mode` is always `own`. **State the value/boxed rule:** `own` is mandatory for unboxed value types (`Int`/`Bool`/`Float`/`Unit`) — `borrow` over them is a `borrow-over-value` error; `borrow` is available only for boxed types (ADTs), as the `f_borrow_boxed`/`List` example shows. Leave the `{example: param_modes_all}` marker in place. --- ### Task 3: Add the two new author-facing examples + spec.md sections **Files:** - Create: `$XP/master/examples/loop_sum.ail.json` - Create: `$XP/master/examples/new_rawbuf.ail.json` - Modify: `$XP/master/spec.md` (two new sections) The module name in each `.ail` MUST equal its target filename stem (`loop_sum`, `new_rawbuf`) — the checker enforces module-name = filename. Both `.ail` bodies below are verified parse + check green (parse-gate, 2026-06-02). - [ ] **Step 1: Author `loop_sum.ail` and canonicalise** Write `/tmp/loop_sum.ail`: ``` (module loop_sum (fn sum_to (type (fn-type (params (own (con Int))) (ret (own (con Int))))) (params n) (body (loop (acc (con Int) 0) (i (con Int) 1) (if (app gt i n) acc (recur (app + acc i) (app + i 1))))))) ``` Run: `target/debug/ail parse /tmp/loop_sum.ail > $XP/master/examples/loop_sum.ail.json` Then: `target/debug/ail check $XP/master/examples/loop_sum.ail.json` Expected: parse exit 0, check exit 0. - [ ] **Step 2: Author `new_rawbuf.ail` and canonicalise** Write `/tmp/new_rawbuf.ail`: ``` (module new_rawbuf (fn main (type (fn-type (params) (ret (own (con Unit))) (effects IO))) (params) (body (let b (new RawBuf (con Int) 3) (app print (app RawBuf.size b)))))) ``` Run: `target/debug/ail parse /tmp/new_rawbuf.ail > $XP/master/examples/new_rawbuf.ail.json` Then: `target/debug/ail check $XP/master/examples/new_rawbuf.ail.json` Expected: parse exit 0, check exit 0. - [ ] **Step 3: Add two spec.md sections with example markers** In `$XP/master/spec.md`, after the existing function/ADT material and before §"The prelude", add two sections: - **§ Iteration (`loop` / `recur`)** — prose: a `loop` declares one or more typed binders with initial values; the body re-enters the loop via `recur` with new positional values for those binders, or falls through with a final value; `recur` is valid only in tail position. Add a `{form-only: json}` block, a `{form-only: ail}` block, and the marker `{example: loop_sum}`. - **§ Kernel-extension types (`new`)** — prose: `(new T arg+)` constructs a value of an extension type `T` by calling `T`'s `new` definition; args are types or values per the def's signature. Add a `{form-only: json}` block, a `{form-only: ail}` block, and the marker `{example: new_rawbuf}`. Keep `{form-only: …}` token volume roughly balanced across the two cohorts (the `token_balance` test allows ±5%). --- ### Task 4: Update `spec_completeness.rs` — variant coverage + Intrinsic allowlist **Files:** - Modify: `$XP/render/tests/spec_completeness.rs` - [ ] **Step 1: Update the `VariantTag` enum (:22-64)** After ` TermReuseAs,` insert: ```rust TermLoop, TermRecur, TermNew, ``` Remove the line ` ParamModeImplicit,` (in the `// ParamMode` group). - [ ] **Step 2: Update `EXPECTED_VARIANTS` (:70-105)** After ` VariantTag::TermReuseAs,` insert: ```rust VariantTag::TermLoop, VariantTag::TermRecur, VariantTag::TermNew, ``` Remove the line ` VariantTag::ParamModeImplicit,`. Do NOT add a tag for `Intrinsic` — it is allowlisted out (Step 4). - [ ] **Step 3: Remove the `Implicit` arm in `visit_param_mode` (:298-310)** Delete: ```rust ParamMode::Implicit => { observed.insert(VariantTag::ParamModeImplicit); } ``` The remaining `Own`/`Borrow` arms make the match exhaustive against the current two-variant `ParamMode`. - [ ] **Step 4: Add the new `visit_term` arms (after the `ReuseAs` arm, :220-224)** Immediately before the closing `}` of the `match t` in `visit_term`, add: ```rust Term::Loop { binders, body } => { observed.insert(VariantTag::TermLoop); for b in binders { visit_type(&b.ty, observed); visit_term(&b.init, observed); } visit_term(body, observed); } Term::Recur { args } => { observed.insert(VariantTag::TermRecur); for a in args { visit_term(a, observed); } } Term::New { type_name: _, args } => { observed.insert(VariantTag::TermNew); for a in args { match a { NewArg::Type(t) => visit_type(t, observed), NewArg::Value(v) => visit_term(v, observed), } } } // `Intrinsic` is the compiler-supplied body marker — non-authorable // (absent from the parser's term dispatch; typecheck enforces // kernel-tier-only via IntrinsicOutsideKernelTier). A foreign-author // mini-spec must not show it and no master example can legally // contain it, so it is allowlisted OUT of completeness: no VariantTag, // no EXPECTED entry, a no-op arm here so the match compiles without // claiming coverage. Term::Intrinsic => {} ``` Add `NewArg` to the `ailang_core::ast::{…}` import line at the top of the file if it is not already imported (the build will name it if missing). - [ ] **Step 5: Compile-gate the test crate** Run: `cargo test --manifest-path $XP/render/Cargo.toml --no-run` Expected: compiles with 0 errors (the `Implicit` unknown-variant error and the non-exhaustive `visit_term` match are both resolved). If the build names a wrong field (e.g. `LoopBinder.init`), correct it per the compiler message. --- ### Task 5: Regenerate `rendered/` and green the render suite **Files:** - Modify: `$XP/rendered/json.md`, `$XP/rendered/ail.md` - [ ] **Step 1: Run the renderer** Run: ``` cargo run --manifest-path $XP/render/Cargo.toml -- \ --master $XP/master --rendered $XP/rendered ``` Expected: exit 0, no load crash (it crashed before on the first example's missing `param_modes`). - [ ] **Step 2: Run the full render test suite** Run: `cargo test --manifest-path $XP/render/Cargo.toml` Expected: PASS — `example_roundtrip` (every example loads, prints to AIL, reparses to the same canonical bytes), `spec_completeness` (every `EXPECTED_VARIANTS` tag observed, including `TermLoop`/`TermRecur`/`TermNew`), `token_balance` (±5%), `splitter_unit`. If `spec_completeness` reports a tag not exercised, the corresponding new example or spec section is missing its construct. --- ### Task 6: Migrate `mock_full_run.json` and green the harness suite **Files:** - Modify: `$XP/harness/tests/fixtures/mock_full_run.json` The fixture maps `cohort → task → turn → {content, usage}`, where `content` is a string-embedded `.ail.json` program (simulated model output). All are legacy-schema and now fail `ail check`. Migrate the embedded programs to the current schema, **preserving the `usage` fields byte-for-byte** (budget_abort depends on them). - [ ] **Step 1: Migrate the programs that must reach green** The hard assertions in `mock_full_run.rs` are `json,t3_main_prints,false,2` and `ail,t1_add_three,false,INF`. So: - `json.t3_main_prints.2.content` (turn 2) MUST become a valid current-schema program that passes `ail check` + `ail build` + run — give its fn types the `param_modes`/`ret_mode` fields per Task 1's rule. (Leave `json.t3_main_prints.1.content` as the intentional `"defs":[]` broken first attempt.) - Migrate the other green-intended embedded programs (the first-attempt-green cohort×task cells) the same way, so the fixture is an honest simulation rather than a uniform schema-fail. - `ail.t1_add_three.*.content` stays failing to the turn limit (its `INF` row); let its miss be content-driven, not merely a schema artefact. - [ ] **Step 2: Verify each migrated embedded program checks green standalone** For each `content` you migrated to be green, extract it to a temp file and run `target/debug/ail check /tmp/x.ail.json` — expected exit 0. (The harness will also `ail build` it; a green check that fails build means the program is schema-valid but not runnable — fix the body.) - [ ] **Step 3: Run the full harness test suite** Run: `cargo test --manifest-path $XP/harness/Cargo.toml` Expected: PASS — `mock_full_run` (8 rows; `json,t3_main_prints,false,2` and `ail,t1_add_three,false,INF` both present; `RUN_STATUS=ok`), `budget_abort` (`RUN_STATUS=budget_exceeded`, ≥1 `budget_abort` row), `verify_references` (every `master/tasks/*.reference.ail.json` drives green through real ail+clang), `strip_locations` (5 .stderr fixtures, including `missing field \`type\``).