# rpe.1 — Retire per-type print effect-ops — Implementation Plan

> **Parent spec:** `docs/specs/0027-retire-per-type-print-effects.md`
>
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement`
> to run this plan. Steps use `- [ ]` checkboxes for tracking.

**Goal:** Delete `io/print_int`, `io/print_bool`, `io/print_float`
from the compiler and migrate every corpus call site to the
polymorphic `print` helper shipped in iter 24.3.

**Architecture:** Four-site lockstep deletion (check builtins,
codegen lower_effect_op, codegen synth.rs effect_op_ret table,
plus three doc-comment touch-ups), preceded by exhaustive corpus
migration so that the deletion does not strand any live caller.
A RED test pins the deletion gate; per-task `cargo test --workspace`
gates each step. Bench-latency drift is expected per spec §C4 (a)
and is ratified at iter close.

**Tech Stack:** `ailang-check`, `ailang-codegen`, `ailang-surface`,
`ailang-prose`, `ailang-core`, `ail` CLI; the snapshot-regenerated
`.prose.txt` artefacts; `bench/run.sh` latency harness baselines.

---

## Errata vs. spec (incorporated below; spec is unchanged)

Recon surfaced four spec-vs-code mismatches; this plan absorbs
them without amending the spec:

1. **Spec §C2 says "three install_io_print_<T>_signature tests
   (lines ~593–633)".** Only `install_io_print_float_signature`
   exists (builtins.rs:600). The plan deletes the one that
   exists, not three.
2. **Spec §D1 says "98 examples/*.ail files".** Actual: 92 `.ail`
   + 6 `.prose.txt` snapshot siblings = 98. The plan migrates
   both, but a `.prose.txt` is regenerated, not text-substituted.
3. **`crates/ailang-codegen/src/synth.rs:215`** carries a parallel
   effect-op `ret` table (`builtin_effect_op_ret`) the spec §C2
   does not name. Lockstep delete with the codegen arms.
4. **Five test bodies + four doc-comment sites** carry
   `io/print_int` as incidental scaffolding (`crates/ailang-check/src/lib.rs`,
   `crates/ailang-core/tests/spec_drift.rs`, `design_schema_drift.rs`,
   `crates/ailang-surface/{src/lex.rs, src/parse.rs}`,
   `crates/ailang-prose/src/lib.rs`, `crates/ail/src/main.rs`,
   `crates/ailang-core/specs/form_a.md`, `runtime/str.c`
   comment). Plan covers them in Task 5 and Task 6.

The spec's §B/§C1/§D1 invariants are unaffected.

---

## Files this plan creates or modifies

**Create:**

- `crates/ailang-check/tests/no_per_type_print_ops.rs` — RED test
  (Task 1). Model on `crates/ailang-check/tests/show_dispatch_pin.rs`.

**Modify — corpus (Task 2 + Task 3):**

- `examples/<name>.ail` × 92 — bulk substitution. Full list at
  `/tmp/migration_targets.txt` (built by Task 2 Step 1; see Task 2
  for the canonical query the LLM re-runs).
- `examples/<name>.prose.txt` × 6 — regenerate via `ail prose`.
  Files: `bench_list_sum.prose.txt`, `ordering_match.prose.txt`,
  `rc_app_let_partial_drop_leak.prose.txt`,
  `rc_match_arm_partial_drop_leak.prose.txt`,
  `rc_own_param_drop.prose.txt`,
  `test_22b3_default_e2e.prose.txt`.

**Modify — compiler core (Task 4):**

- `crates/ailang-check/src/builtins.rs`:
  - line 5 — module-doc-comment swap `io/print_int` → `io/print_str`.
  - line 40 — `EffectOpSig.ret` doc swap.
  - lines 269-276 — delete `env.effect_ops.insert("io/print_int", ...)`.
  - lines 277-284 — delete `env.effect_ops.insert("io/print_bool", ...)`.
  - lines 293-302 — delete floats-iter comment + `io/print_float` insert.
  - line 349 — delete `io/print_int` row in `list()`.
  - line 350 — delete `io/print_bool` row.
  - line 352 — delete `io/print_float` row. (Line 351 — `io/print_str` — survives.)
  - lines 593-635 — delete `install_io_print_float_signature` test fn (incl. its 7-line doc-comment).
- `crates/ailang-codegen/src/lib.rs`:
  - lines 2302-2323 — delete `"io/print_int" => { ... }` arm.
  - lines 2324-2345 — delete `"io/print_float" => { ... }` arm.
  - lines 2373-2411 — delete `"io/print_bool" => { ... }` arm.
  - lines 3580-3617 — delete `lowers_io_print_float` test (incl. doc-comment).
  - format-string interns (`"fmt_int"`, `"%lld\n"`, `"fmt_float"`, `"%g\n"`, `"fmt_true"`, `"fmt_false"`) orphan and may be deleted in the same edit if they are no longer referenced. Verify by `grep` post-edit.
- `crates/ailang-codegen/src/synth.rs:215` — remove `"io/print_int" | "io/print_bool" | "io/print_float"` from the `match name` arm; keep `"io/print_str"`.

**Modify — incidental-scaffolding test bodies (Task 5):**

- `crates/ailang-check/src/lib.rs`:
  - lines 3871-3877 (undeclared-effect-diagnostic test) — swap `op: "io/print_int"` → `op: "io/print_str"`; if the arg type was `Int`, change it to a `Str` literal that types as Str.
  - lines 5047-5051 (tail-call-in-tail-position test) — same shape.
- `crates/ailang-core/tests/spec_drift.rs` lines 64-71 — swap to `io/print_str` with a `Str` arg.
- `crates/ailang-core/tests/design_schema_drift.rs` lines 87-94 — same.
- `crates/ailang-surface/src/lex.rs`:
  - lines 18-20 — module-doc-comment swap.
  - line 320 — `tokenize("+ == <= io/print_int std_list.map")` swap to `io/print_str`.
  - line 328 — corresponding `assert_eq!` vector swap.
- `crates/ailang-prose/src/lib.rs` lines 1585-1593 — round-trip test asserting `render_term(&t) == "do io/print_int(5)"`: swap to `io/print_str` with a Str arg + matching assert.

**Modify — doc-comment touch-ups (Task 6):**

- `crates/ailang-surface/src/parse.rs:1377` — diagnostic example swap.
- `crates/ail/src/main.rs:30-31, 1419-1420` — CLI doc-comments swap.
- `runtime/str.c:117-118` — re-anchor on `%g` directly.
- `crates/ailang-core/specs/form_a.md:433-434` — surface-spec example: migrate to either `(do io/print_str s)` with a `Str` literal, or `(app print xs)` — pick the latter to advertise the post-iter idiom.

**Modify — DESIGN.md (Task 7):**

- `docs/DESIGN.md`:
  - line 331 — Decision 11 example swap.
  - lines 1990-1992 — past-tense rewrite ("retired 2026-05-14 in iter rpe.1").
  - lines 2024-2025 — Heap-Str primitives output sentence rewrite per spec §C3.
  - line 2331 — effect-op invocation comment example swap.
  - lines 2565-2577 — Float NaN-rendering paragraph rewrite (anchor on `float_to_str`).
  - line 2679 — "What is supported (effect ops)" list, keep only `io/print_str`.
  - line 2699 — "(IO effect ops)" list, keep only `io/print_str`.

**Modify — incidental E2E test comments (Task 8):**

- `crates/ail/tests/eq_ord_e2e.rs:66, 76` — update comments to name `print` semantics.
- `crates/ail/tests/e2e.rs:2674-2683` (`int_arg_to_effect_op_does_not_rc_track`) — fixture migrates in Task 2; doc-comment migrates here.
- `crates/ail/tests/floats_e2e.rs:7` — module doc-comment swap.

**Modify — bench baselines (Task 9, conditional):**

- `bench/baseline.json` "latency" block (lines 70-92) — ratify if drift.
- `bench/baseline.json" throughput block (lines 1-69, 93) — ratify if drift.
- `bench/baseline_cross_lang.json` — ratify if drift.
- `bench/baseline_compile.json` — expected exit-0; ratify only if unexpected drift.

**Modify — roadmap (Task 10):**

- `docs/roadmap.md` P2 section lines 80-90 — strike entry with `- [x]` + date.

---

## Task 1: RED test — `no_per_type_print_ops.rs`

**Files:**
- Create: `crates/ailang-check/tests/no_per_type_print_ops.rs`

- [ ] **Step 1: Write the failing test**

```rust
//! Hard gate for iter rpe.1: the per-type print effect-ops
//! `io/print_int`, `io/print_bool`, `io/print_float` are retired
//! and must NOT appear in the builtin registry. The polymorphic
//! `print` helper (prelude, iter 24.3) is the canonical replacement;
//! `io/print_str` survives as the byte-channel primitive.

use ailang_check::build_check_env;
use ailang_surface::load_workspace;
use std::path::PathBuf;

fn fixture_dir() -> PathBuf {
    let mut d = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
    d.pop();
    d.pop();
    d.join("examples")
}

#[test]
fn per_type_print_effect_ops_are_retired() {
    // Any workspace will do; we only inspect the post-install
    // builtin registry. `hello.ail` is the smallest surviving
    // fixture (uses `io/print_str` only).
    let ws_path = fixture_dir().join("hello.ail");
    let ws = load_workspace(&ws_path).expect("workspace loads");
    let env = build_check_env(&ws);

    for retired in ["io/print_int", "io/print_bool", "io/print_float"] {
        assert!(
            !env.effect_ops.contains_key(retired),
            "{retired} must not appear in builtin registry post-rpe.1; \
             use polymorphic `print` (Show-based) instead"
        );
    }

    // Survivor pin: io/print_str stays.
    assert!(
        env.effect_ops.contains_key("io/print_str"),
        "io/print_str must survive — it is the canonical byte-channel \
         primitive and used internally by `print`"
    );
}
```

- [ ] **Step 2: Verify the test fails RED**

Run: `cargo test --workspace -p ailang-check --test no_per_type_print_ops`
Expected: FAIL on the first `assert!` because `io/print_int`
still appears in `env.effect_ops` post-Task-2.

This gate flips to GREEN at Task 4 close.

---

## Task 2: Corpus `.ail` migration — 92 fixtures

**Files:**
- Modify: 92 `examples/<name>.ail` files (full list deterministic
  from `grep -rl "io/print_int\|io/print_bool\|io/print_float"
  examples/ | grep "\.ail$"`).

- [ ] **Step 1: Build the canonical target list**

Run:
```bash
grep -rl "io/print_int\|io/print_bool\|io/print_float" examples/ \
  | grep "\.ail$" > /tmp/rpe1_targets.txt && \
  wc -l /tmp/rpe1_targets.txt
```
Expected: `92 /tmp/rpe1_targets.txt`.

- [ ] **Step 2: Bulk-substitute each fixture**

For each path in `/tmp/rpe1_targets.txt`, transform every
occurrence of:
```
(do io/print_int <ARG>)
```
to:
```
(app print <ARG>)
```
And the same for `io/print_bool`, `io/print_float`.

The transformation is regular: parenthesis-balance is preserved
(both `do` and `app` consume their parentheses identically),
the head is replaced (`io/print_<T>` → `print`), and the leading
keyword is replaced (`do` → `app`). The argument expression is
copied verbatim.

Implementation: read each file, apply the regex
`\(do io/print_(int|bool|float) ` → `(app print `, write back.
Or process file-by-file with Edit-tool calls; either is fine.

- [ ] **Step 3: Verify each migrated fixture parses + round-trips**

Run:
```bash
cargo run -p ail -- check examples/<each-fixture> 2>&1
```
Expected: exit 0. The pre-iter compiler still knows
`io/print_int|bool|float`, so existing fixtures stay green; the
migrated form (using polymorphic `print`) ALSO type-checks because
`print` is in the prelude (iter 24.3). Both paths are green
simultaneously in this iter window.

Pragmatic shortcut: run `cargo test --workspace` at Step 4 instead
of per-fixture `ail check`.

- [ ] **Step 4: `cargo test --workspace` green**

Run: `cargo test --workspace`
Expected: 0 failures. The migrated fixtures' E2E test wrappers
still produce the same stdout (output bytes identical between
`(do io/print_<T> x)` and `(app print x)` per spec §"Data flow").

If any fixture has a stdout-comparison test that now produces
different bytes, that is a real divergence and the spec's
"output bytes identical" claim has a counterexample — STOP
and investigate before continuing.

---

## Task 3: Prose snapshot regeneration — 6 `.prose.txt` files

**Files:**
- Modify: `examples/bench_list_sum.prose.txt`,
  `examples/ordering_match.prose.txt`,
  `examples/rc_app_let_partial_drop_leak.prose.txt`,
  `examples/rc_match_arm_partial_drop_leak.prose.txt`,
  `examples/rc_own_param_drop.prose.txt`,
  `examples/test_22b3_default_e2e.prose.txt`.

- [ ] **Step 1: Regenerate each prose snapshot**

For each fixture above, run:
```bash
cargo run -p ail -- prose examples/<stem>.ail \
  > examples/<stem>.prose.txt
```

The `ail prose` subcommand emits Form-B; this file becomes the
committed snapshot used by `crates/ailang-prose/tests/snapshot.rs`.

- [ ] **Step 2: `cargo test --workspace -p ailang-prose` green**

Run: `cargo test --workspace -p ailang-prose`
Expected: 0 failures. The snapshot test will compare the
regenerated `.prose.txt` to the prose-renderer's current output
of the migrated fixture; they must match because we generated
them with the very command the test uses.

- [ ] **Step 3: `cargo test --workspace` green**

Run: `cargo test --workspace`
Expected: 0 failures (no regression in any other crate).

---

## Task 4: Compiler-side deletion (lockstep)

**Files:**
- Modify: `crates/ailang-check/src/builtins.rs`
  (lines 5, 40, 269-302, 349, 350, 352, 593-635)
- Modify: `crates/ailang-codegen/src/lib.rs`
  (lines 2302-2323, 2324-2345, 2373-2411, 3580-3617)
- Modify: `crates/ailang-codegen/src/synth.rs:215`

- [ ] **Step 1: Delete the three `env.effect_ops.insert` blocks**

In `crates/ailang-check/src/builtins.rs`, delete:
- lines 269-276 (`io/print_int` block)
- lines 277-284 (`io/print_bool` block)
- lines 293-302 (floats-iter comment + `io/print_float` block)

Keep the surviving `io/print_str` block (between or after; verify
by reading the file post-edit).

- [ ] **Step 2: Delete the three `list()` rows**

In `crates/ailang-check/src/builtins.rs`, delete:
- line 349 (`io/print_int` row)
- line 350 (`io/print_bool` row)
- line 352 (`io/print_float` row)

Keep line 351 (`io/print_str` row). Verify by re-reading.

- [ ] **Step 3: Delete the `install_io_print_float_signature` test**

In `crates/ailang-check/src/builtins.rs`, delete lines 593-635
(test fn + its 7-line doc-comment). This is the ONE test that
exists matching the spec's "three install_io_print_<T>_signature"
description — see Errata #1 above.

- [ ] **Step 4: Swap the module-doc-comments**

In `crates/ailang-check/src/builtins.rs`:
- line 5 — swap `io/print_int` reference to `io/print_str`.
- line 40 — same swap in `EffectOpSig.ret` doc.

- [ ] **Step 5: Delete the three codegen `lower_effect_op` arms**

In `crates/ailang-codegen/src/lib.rs`:
- lines 2302-2323 — delete `"io/print_int" => { ... }` arm.
- lines 2324-2345 — delete `"io/print_float" => { ... }` arm.
- lines 2373-2411 — delete `"io/print_bool" => { ... }` arm.

Keep the `"io/print_str"` arm (lines 2346-2372). Verify by
re-reading.

After deletion, search the file for orphaned format-string
interns (`"fmt_int"`, `"%lld\n"`, `"fmt_float"`, `"%g\n"`,
`"fmt_true"`, `"fmt_false"`). If they are no longer referenced
anywhere in the crate, delete the corresponding `out.push_str`
or `intern` calls too. If they ARE still referenced (e.g.
`"%g\n"` shared with another path), leave them.

- [ ] **Step 6: Delete the `lowers_io_print_float` test**

In `crates/ailang-codegen/src/lib.rs`, delete lines 3580-3617
(test + doc-comment).

- [ ] **Step 7: Update `synth.rs` builtin_effect_op_ret**

In `crates/ailang-codegen/src/synth.rs:215`, remove
`"io/print_int" | "io/print_bool" | "io/print_float"` from the
match arm pattern. Keep `"io/print_str"`. The match arm body
(returning `Type::unit()`) stays unchanged for the surviving
op.

- [ ] **Step 8: RED test flips GREEN**

Run: `cargo test --workspace -p ailang-check --test no_per_type_print_ops`
Expected: PASS. The Task-1 RED test is now satisfied because
the three retired ops no longer appear in `env.effect_ops`.

- [ ] **Step 9: `cargo test --workspace` green**

Run: `cargo test --workspace`
Expected: 0 failures. Any failure here means a stale `io/print_<T>`
reference survived in a fixture or test body — those are addressed
in Task 5; expect Task-5 sites to fail and proceed to Task 5.

---

## Task 5: In-tree test-body migration

**Files:**
- Modify: `crates/ailang-check/src/lib.rs:3871-3877, 5047-5051`
- Modify: `crates/ailang-core/tests/spec_drift.rs:64-71`
- Modify: `crates/ailang-core/tests/design_schema_drift.rs:87-94`
- Modify: `crates/ailang-surface/src/lex.rs:320, 328`
- Modify: `crates/ailang-prose/src/lib.rs:1585-1593`
- Modify: `crates/ail/tests/e2e.rs` — six IR-shape tests at lines 1408,
  1628, 1710, 1800, 1936, 2013 (Cat B test-harness patch, see below).

**Cat B test-harness patch (added 2026-05-14 after the rpe.1 BLOCKED
orchestrator run):** Six IR-shape tests in `crates/ail/tests/e2e.rs`
build their own `lifted_ws` via a `lift_letrecs` loop and lower it
via `lower_workspace_with_alloc`, skipping `monomorphise_workspace`.
Pre-migration this was fine — the fixtures used `(do io/print_int x)`,
a directly-recognised effect-op. Post-migration they use `(app print x)`
(polymorphic free fn), which requires mono to resolve to
`print__Int`. Without the mono pass, lowering errors with
`UnknownVar("print")`. The fix is identical at all six sites: replace
the home-rolled `desugar+lift` loop with a single call to
`ailang_check::monomorphise_workspace(&ws)?` and lower the
resulting `MonomorphisedWorkspace`. Sites to patch:

- `reuse_as_demo_under_rc_uses_inplace_rewrite` (line 1408)
- `alloc_rc_borrow_only_recursive_list_drop` (line 1628)
- `alloc_rc_partial_drop_skips_moved_keeps_wildcarded` (line 1710)
- `alloc_rc_own_param_dec_at_fn_return` (line 1800)
- `iter18e_drop_iterative_emits_worklist_body_no_self_recursion` (line 1936)
- `iter18e_no_annotation_keeps_recursive_drop_body` (line 2013)

- [ ] **Step 1: Migrate `ailang-check/src/lib.rs:3871-3877`**

The site is an undeclared-effect-diagnostic test. The op name
is incidental — the test asserts "calling an unknown effect-op
produces diagnostic X". Swap `op: "io/print_int"` → `op:
"io/print_str"` AND change the arg from an `Int` literal to a
`Str` literal so the swapped op type-checks. Then change the
expected diagnostic's `effect_op` field if it's pinned by name.

If the test is asserting "unknown effect-op", the spirit is
preserved by inventing a never-registered name like
`"io/print_unknown"` and the test continues to assert "diagnostic
produced". Use whichever shape is closest to the original intent
— inspect the test body to decide.

- [ ] **Step 2: Migrate `ailang-check/src/lib.rs:5047-5051`**

Tail-call-in-tail-position test. Same shape as Step 1; the op
is incidental. Swap to `io/print_str` + `Str` arg.

- [ ] **Step 3: Migrate `spec_drift.rs:64-71`**

JSON-shape drift test on `Term::Do`. The op identity is the
serialised payload's key, not the load-bearing assertion.
Swap to `io/print_str` + `Str` arg.

- [ ] **Step 4: Migrate `design_schema_drift.rs:87-94`**

Same shape as Step 3.

- [ ] **Step 5: Migrate `lex.rs:320, 328`**

`tokenize("+ == <= io/print_int std_list.map")` exercises that
the lexer treats `io/print_int` as a single identifier-with-slash
token. The op name is incidental; the assertion is on the token
shape. Swap to `io/print_str`. Update the `assert_eq!` vector
at line 328 to match.

- [ ] **Step 6: Migrate `prose/src/lib.rs:1585-1593`**

`do_renders_with_keyword_and_op` round-trip test. Asserts
`render_term(&t) == "do io/print_int(5)"`. Swap to
`render_term(&t) == "do io/print_str(\"5\")"` (with `Str` literal
arg) OR pick a different surviving op if `io/print_str` makes
the test awkward.

- [ ] **Step 7: `cargo test --workspace` green**

Run: `cargo test --workspace`
Expected: 0 failures. Task 5 closes the test-body migration set.

---

## Task 6: Documentation-comment touch-ups

**Files:**
- Modify: `crates/ailang-surface/src/lex.rs:18-20`
- Modify: `crates/ailang-surface/src/parse.rs:1377`
- Modify: `crates/ail/src/main.rs:30-31, 1419-1420`
- Modify: `runtime/str.c:117-118`
- Modify: `crates/ailang-core/specs/form_a.md:433-434`

- [ ] **Step 1: Swap `lex.rs:18-20` module-doc-comment**

Replace the `io/print_int` example with `io/print_str` in the
qualified-name-tokenisation explanation.

- [ ] **Step 2: Swap `parse.rs:1377` diagnostic example**

`expect_ident("effect op (e.g. `io/print_int`)")` → swap to
`io/print_str`.

- [ ] **Step 3: Swap `main.rs:30-31` and `:1419-1420`**

`main.rs:30-31`: `builtins` subcommand doc — list a single
`io/print_str` (drop the per-type ops).
`main.rs:1419-1420`: `walk_term` Do-arm format-string comment
— swap to `io/print_str`.

- [ ] **Step 4: Re-anchor `runtime/str.c:117-118`**

Original: `matches io/print_float's libc rendering for IEEE
consistency`. Rewrite to: `matches the same %g format used
throughout the runtime for Float text rendering` (or similar —
the anchor is the format, not the now-retired effect-op).

- [ ] **Step 5: Migrate `form_a.md:433-434`**

The surface-spec example shows `(do io/print_int n)`. Migrate
to `(app print n)` to advertise the post-iter canonical idiom.

- [ ] **Step 6: `cargo test --workspace` green**

Run: `cargo test --workspace`
Expected: 0 failures. (Doc-comment changes can break tests if
they're embedded in doc-tests; verify.)

---

## Task 7: DESIGN.md sweep

**Files:**
- Modify: `docs/DESIGN.md` lines 331, 1990-1992, 2024-2025, 2331,
  2565-2577, 2679, 2699.

- [ ] **Step 1: Line 331 — Decision 11 example**

Original (excerpt): "names like `io/print_int`". Swap to
`io/print_str`.

- [ ] **Step 2: Lines 1990-1992 — Polymorphic print past-tense rewrite**

Original:
> Routing through `print` replaces the ad-hoc `io/print_int|bool|float`
> idiom for new code; retiring the per-type effect-ops is queued
> as a P2 follow-up.

Rewrite:
> Routing through `print` replaces the ad-hoc per-type print
> effect-ops; the `io/print_int`, `io/print_bool`, `io/print_float`
> primitives were retired 2026-05-14 in iter rpe.1, leaving
> `io/print_str` as the only surviving direct-output effect-op.

- [ ] **Step 3: Lines 2024-2025 — Heap-Str primitives output sentence**

Original:
> Primitive output goes through `io/print_int` / `io/print_bool` /
> `io/print_str` directly.

Rewrite:
> Primitive output for `Str` values goes through `io/print_str`
> directly; values of other primitive types route through the
> polymorphic `print` helper (§"Polymorphic print"), which feeds
> the heap-Str result of `show x` into `io/print_str`. The
> per-type effect-ops `io/print_int`, `io/print_bool`,
> `io/print_float` were retired in iter rpe.1 (2026-05-14).

- [ ] **Step 4: Line 2331 — Effect-op invocation comment example**

Original: `// Effect-op invocation. \`op\` is "<eff>/<op>"
(e.g. "io/print_int").`
Rewrite: `// Effect-op invocation. \`op\` is "<eff>/<op>"
(e.g. "io/print_str").`

- [ ] **Step 5: Lines 2565-2577 — Float NaN-rendering paragraph**

The paragraph anchors NaN textual semantics on `io/print_float`
and contrasts with `do print x`. After iter rpe.1, both ends of
that contrast reach `%g` via `float_to_str` + `io/print_str`.

Rewrite the paragraph to anchor on `float_to_str` (the canonical
heap-Str producer for Float) rather than the now-retired
`io/print_float`. The `%g` semantics are unchanged; this is a
re-anchoring, not a semantic change.

- [ ] **Step 6: Line 2679 — "What is supported (effect ops)"**

Original: `(\`io/print_int\`, \`io/print_bool\`, \`io/print_str\`)`
Rewrite: `(\`io/print_str\`)`. Add a sentence: `Per-type print
ops (\`io/print_int\`, \`io/print_bool\`, \`io/print_float\`)
were retired in iter rpe.1; polymorphic \`print\` is the canonical
output path.`

- [ ] **Step 7: Line 2699 — "(IO effect ops)" list**

Original: `the IO effect ops (\`io/print_int|bool|str|float\`)`
Rewrite: `the IO effect op \`io/print_str\``.

- [ ] **Step 8: `cargo test --workspace` green**

Run: `cargo test --workspace`
Expected: 0 failures. The `design_schema_drift` test (mentioned
in Task 5) reads DESIGN.md; verify it still passes after these
swaps.

---

## Task 8: Incidental E2E test-comment touch-ups

**Files:**
- Modify: `crates/ail/tests/eq_ord_e2e.rs:66, 76`
- Modify: `crates/ail/tests/e2e.rs:2674-2683`
- Modify: `crates/ail/tests/floats_e2e.rs:7`

- [ ] **Step 1: Update `eq_ord_e2e.rs` comments**

Lines 66 and 76 reference `io/print_int` emitting `<int>\n`.
The asserted stdout is unchanged (post-migration `(app print x)`
at `x : Int` also emits `<int>\n`). Update the comments to
reference `print` semantics. The test body itself does not
change (it was migrated in Task 2 via the fixture migration).

- [ ] **Step 2: Update `e2e.rs:2674-2683` (int_arg_to_effect_op test)**

The test pins the RC-tracking property "Int args passed to an
effect-op are not consumed". The fixture `int_to_print_int_borrow.ail`
migrates in Task 2. The test name and doc-comment lose
grounding once `io/print_int` is gone.

Two options:
- Update the doc-comment to explain that the test now pins
  "Int args passed to `print` (the polymorphic helper) are not
  consumed" — the property is the same.
- Rename the test if Boss prefers (e.g.
  `int_arg_to_print_does_not_rc_track`). Renaming changes a
  test identity; lean toward keeping the name and updating the
  doc.

Pick keep-the-name-and-update-the-doc. The test pins the
property, not the identifier.

- [ ] **Step 3: Update `floats_e2e.rs:7` module doc-comment**

Lists `io/print_float` as a covered op. Swap to `print` / Show
Float.

- [ ] **Step 4: `cargo test --workspace` green**

Run: `cargo test --workspace`
Expected: 0 failures.

---

## Task 9: Bench run + conditional ratification

**Files:**
- Conditional Modify: `bench/baseline.json`, `bench/baseline_cross_lang.json`,
  `bench/baseline_compile.json`.

- [ ] **Step 1: Run `bench/check.py`**

Run: `python3 bench/check.py`
Expected exit code: `0` (compile-time-symmetric). If `1`
(drift), investigate before ratifying — the spec did not
anticipate a check-perf change.

- [ ] **Step 2: Run `bench/compile_check.py`**

Run: `python3 bench/compile_check.py`
Expected exit code: `0`. Same rationale.

- [ ] **Step 3: Run `bench/cross_lang.py`**

Run: `python3 bench/cross_lang.py`
Expected exit code: `0` OR `1` (legitimate drift if any bench
fixture's hot loop now allocates a heap-Str per print).

- [ ] **Step 4: Conditional ratification — if any script returned `1`**

For each script that returned `1`:
1. Inspect the per-fixture report to identify which fixtures
   moved.
2. Run `python3 <script> --update-baseline` to write the new
   baseline.
3. Note in the per-iter journal: which baseline was updated,
   the magnitude of the drift, and the language reason
   (post-iter `print` heap-alloc-per-iteration is the canonical
   output path; old baseline reflected the retired direct-printf
   path).

- [ ] **Step 5: All scripts green at final state**

After any ratification, re-run all three scripts; all must
return `0`.

---

## Task 10: Roadmap strike + per-iter journal + stats

**Files:**
- Modify: `docs/roadmap.md` P2 lines 80-90 (entry strike).
- Create: `docs/journals/2026-05-14-iter-rpe.1.md` (per-iter
  journal).
- Create: `bench/orchestrator-stats/2026-05-14-iter-rpe.1.json`
  (stats file per `implement/SKILL.md`).

- [ ] **Step 1: Strike the roadmap entry**

In `docs/roadmap.md` lines 80-90 (the
`Retire io/print_int / io/print_bool / io/print_float` P2
entry), change `- [ ]` → `- [x]` and append a one-line tail
naming this iter:

```markdown
- [x] **\[milestone\]** Retire `io/print_int` / `io/print_bool` /
  `io/print_float` effect-ops + migrate example corpus to `print`.
  Shipped 2026-05-14 as iter rpe.1.
  - context: per-iter journal `docs/journals/2026-05-14-iter-rpe.1.md`.
```

- [ ] **Step 2: Write the per-iter journal**

Standard journal shape: `# iter rpe.1 — Retire per-type print
effect-ops`, sections: Summary / Decisions / Files touched /
Verification / Concerns / Known debt.

The journal records the §C4 (a) decision (migrate all,
including bench-latency fixtures) and any bench-baseline
ratifications from Task 9.

- [ ] **Step 3: Write the stats file**

JSON file at `bench/orchestrator-stats/2026-05-14-iter-rpe.1.json`
recording: tasks completed, retries per task, blocked count,
duration, `cargo test --workspace` final count.

- [ ] **Step 4: Final `cargo test --workspace` + clippy + doc**

Run, in parallel:
- `cargo test --workspace`
- `cargo clippy --workspace --all-targets -- -D warnings`
- `cargo doc --workspace --no-deps 2>&1 | grep -c '^warning:'`

Expected: tests green; clippy zero (the iter-clippy-sweep
baseline holds); doc zero (the rustdoc-sweep baseline holds).

Any clippy / rustdoc regression introduced by this iter is
itself a Task-N debt; fix in-task before declaring DONE.

---

## Self-review

1. **Spec coverage:** every §C/§D/§G/§I-named element has a
   task — RED test (Task 1, spec §G2), corpus migration (Task 2,
   spec §C1 + §D1), prose snapshot regen (Task 3, §D1 extension
   via Errata #2), four-site deletion (Task 4, spec §C2 + Errata
   #3), test-body migration (Task 5, Errata #4), doc-comment
   touch-ups (Task 6, Errata #4), DESIGN.md sweep (Task 7, spec
   §C3), E2E-comment touch-ups (Task 8, partial Errata #4), bench
   ratification (Task 9, spec §C4 (a) + §G3), roadmap strike +
   journal + stats (Task 10, spec §I task 7).

2. **Placeholder scan:** zero "TBD" / "TODO" / "similar to Task".

3. **Type consistency:** `env.effect_ops` (the BTreeMap on `Env`),
   `lower_effect_op` (the codegen dispatcher), `build_check_env`
   (entry point used by the RED test) all match real symbol names
   in the workspace.

4. **Step granularity:** every numbered step is 2–5 minutes.
   Task 2 Step 2 is "92 file edits" but they are mechanical and
   the LLM can stream them with one Edit-tool call per file.

5. **No commit steps:** zero `git commit` calls in any task
   template. The orchestrator-agent (or implement-skill) leaves
   the working tree dirty; Boss commits.
