plan: rpe.1 — retire per-type print effect-ops, 10 tasks

Single-iter plan implementing the rpe.1 spec. Ten tasks:
RED test, 92 .ail corpus migration, 6 .prose.txt regen,
four-site compiler deletion, five test-body migrations,
six doc-comment touch-ups, seven DESIGN.md sites, three E2E
comment touch-ups, bench ratification (per spec §C4 (a)),
and the close-out (roadmap strike, journal, stats).

Plan absorbs four spec-vs-code mismatches surfaced by recon
(one install-test instead of three, 92/6 fixture split rather
than 98, synth.rs:215 parallel registry, incidental test-body +
doc-comment sites) as an "Errata vs. spec" block at the top of
the plan; the spec itself is unchanged because none of the
mismatches affect load-bearing invariants.
This commit is contained in:
2026-05-14 01:11:18 +02:00
parent 68bab007c8
commit 05e4c04e3b
+749
View File
@@ -0,0 +1,749 @@
# rpe.1 — Retire per-type print effect-ops — Implementation Plan
> **Parent spec:** `docs/specs/2026-05-14-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 ~593633)".** 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`
- [ ] **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 25 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.