# Effect-prose documentation-honesty tidy — Implementation Plan > **Parent:** no spec — this is a **tidy iteration** (documentation > drift correction). Scope authority is the `docs/roadmap.md` P2 > `[~] [todo]` "DESIGN.md effect-prose is fiction" (split out > 2026-05-16 from the retired effect-op-arg-modes bundle). Facts > triple-verified by three prior `ailang-grounding-check` PASSes; > exact byte sites mapped by `ailang-plan-recon` 2026-05-16. > > **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement` > to run this plan. Steps use `- [ ]` checkboxes. **Goal:** Make three false effect-system claims in the project's documentation true, in lockstep with their two satellite mentions, guarded by one new doc-presence pin. No language, checker, or codegen change. **Architecture:** Pure documentation edits across four files (`docs/DESIGN.md`, `crates/ailang-core/src/ast.rs` doc-comment, `crates/ailang-core/specs/form_a.md`, `crates/ail/src/main.rs` internal CONTRACT string) plus one new regression pin (`crates/ailang-core/tests/effect_doc_honesty_pin.rs`). Recon confirmed no existing drift/coverage test scans the effect-prose region, so the only RED risk is the new pin, by construction; it is authored first (TDD: RED before the corrections, GREEN after). **Tech Stack:** `ailang-core` (doc-comment + form_a.md + the new integration test), `ail` (main.rs CONTRACT string), `docs/DESIGN.md`. --- ## Files this plan creates or modifies - Create: `crates/ailang-core/tests/effect_doc_honesty_pin.rs` — one test asserting corrected strings present + false strings absent across the four edited files. - Modify: `docs/DESIGN.md:171-173` — Decision 3 effect-row + Diverge claims. - Modify: `docs/DESIGN.md:2722` — "What is not (yet) supported" Diverge fragment. - Modify: `crates/ailang-core/src/ast.rs:438-439` — `Term::Do` doc-comment. - Modify: `crates/ailang-core/specs/form_a.md:226` and `:356-358` — the two Diverge mentions. - Modify: `crates/ail/src/main.rs:288-290` — merge-prose CONTRACT effect-annotation example. --- ## Task 1: The doc-presence pin (RED first) **Files:** - Create: `crates/ailang-core/tests/effect_doc_honesty_pin.rs` - [ ] **Step 1: Write the pin** Robust-substring strategy (single-line fragments only — no cross-line-wrap matches; the iter-revert journal documented that a wrap-spanning grep fragment silently mismatches). Each of the four files is read at runtime via `CARGO_MANIFEST_DIR`-relative paths (the pattern the F1/F4 sibling `design_schema_drift.rs:434-449` uses). ```rust //! Regression pin: the effect-system documentation must stay true. //! //! Three load-bearing effect claims were fiction until the //! 2026-05-16 effect-doc-honesty tidy: a non-existent //! row-polymorphic effect row, `Diverge` advertised as a wired-up //! built-in op (zero code in any crate), and a `Term::Do` //! doc-comment naming a non-existent link-time effect-handler //! table. This pin fails if any of the fiction strings reappears //! or any corrected anchor is dropped. use std::fs; fn read(rel: &str) -> String { let p = concat!(env!("CARGO_MANIFEST_DIR"), "/../../"); fs::read_to_string(format!("{p}{rel}")) .unwrap_or_else(|e| panic!("read {rel}: {e}")) } #[test] fn design_md_effect_prose_is_true() { let d = read("docs/DESIGN.md"); // fiction absent assert!(!d.contains("row-polymorphic"), "DESIGN.md: the effect row is NOT row-polymorphic (no EffectRow / row var exists)"); assert!(!d.contains("`IO` and `Diverge` (for infinite"), "DESIGN.md: `Diverge` is not a wired-up MVP effect"); assert!(!d.contains("the built-in IO and Diverge ops"), "DESIGN.md §What-is-not-supported: there is no built-in Diverge op"); // corrected anchors present assert!(d.contains("flat, unordered, closed set of effect names"), "DESIGN.md Decision 3 must describe the real (flat set-equality) effect model"); assert!(d.contains("`Diverge` (for non-termination) is reserved"), "DESIGN.md Decision 3 must state Diverge is reserved/unimplemented"); } #[test] fn term_do_doc_comment_is_true() { let a = read("crates/ailang-core/src/ast.rs"); assert!(!a.contains("resolved against the effect-handler table at link time"), "ast.rs Term::Do: there is no effect-handler table and no link-time resolution"); assert!(a.contains("resolved at typecheck against `Env::effect_ops`"), "ast.rs Term::Do doc must describe the real resolution mechanism"); } #[test] fn form_a_spec_effect_names_are_true() { let f = read("crates/ailang-core/specs/form_a.md"); assert!(!f.contains("`Diverge` for `diverge/*`"), "form_a.md: there is no `diverge/*` op namespace"); assert!(!f.contains("currently `IO` and `Diverge`"), "form_a.md: `Diverge` is reserved, not a currently-usable effect"); assert!(f.contains("The only built-in effect op is `io/print_str`"), "form_a.md rule 3 must name the real single built-in effect op"); } #[test] fn merge_prose_contract_example_is_true() { let m = read("crates/ail/src/main.rs"); assert!(!m.contains("`(effects Diverge)`"), "main.rs CONTRACT: do not use a non-existent effect as the example"); } ``` - [ ] **Step 2: Run — expect RED** Run: `cargo test --workspace -p ailang-core --test effect_doc_honesty_pin` Expected: FAIL — `design_md_effect_prose_is_true` (and the others) panic on the still-present fiction strings / still-absent corrected anchors. --- ## Task 2: `docs/DESIGN.md` corrections **Files:** - Modify: `docs/DESIGN.md:170-173`, `docs/DESIGN.md:2722` - [ ] **Step 1: Decision 3 (lines 170-173)** Replace exactly: ``` The default is total, pure functions. Effects are declared as a set in the function type: `(Int) -> Int ![IO]`. The effect set is row-polymorphic (`![IO | r]`). In the MVP only the effects `IO` and `Diverge` (for infinite loops) are wired up. ``` with: ``` The default is total, pure functions. Effects are declared as a set in the function type: `(Int) -> Int ![IO]`. The effect set is a flat, unordered, closed set of effect names, unified by set-equality — there is no effect row variable; a signature lists exactly the effects its body may perform. In the MVP only the effect `IO` is wired up (its sole op is `io/print_str`). `Diverge` (for non-termination) is reserved as an effect name but is unimplemented — no op, no codegen, no checker injection — in the same sense Decision 4 reserves refinements. ``` (Lines 168-169 header/blank and 175-176 stay untouched.) - [ ] **Step 2: "What is not (yet) supported" (line 2722)** Replace exactly: ``` - No effect handlers — only the built-in IO and Diverge ops. ``` with: ``` - No effect handlers — only the built-in `IO` op (`io/print_str`). `Diverge` is a reserved effect name with no op and no codegen. ``` - [ ] **Step 3: Run the pin — `design_md_effect_prose_is_true` GREEN** Run: `cargo test --workspace -p ailang-core --test effect_doc_honesty_pin design_md_effect_prose_is_true` Expected: PASS. --- ## Task 3: `crates/ailang-core/src/ast.rs` `Term::Do` doc-comment **Files:** - Modify: `crates/ailang-core/src/ast.rs:438-439` - [ ] **Step 1: Replace the two doc lines** Replace exactly: ``` /// Effect operation invocation (e.g. `do print "hi"`). The `op` is /// resolved against the effect-handler table at link time. ``` with: ``` /// Effect operation invocation (e.g. `do io/print_str "hi"`). The /// `op` is resolved at typecheck against `Env::effect_ops` (an /// `IndexMap`) and lowered by a literal /// `match` in codegen (`lower_effect_op`); there is no /// effect-handler table and no link-time resolution. ``` (Lines 440-441 — blank `///` and the Iter 14e cross-ref — and the `Do { ... }` variant body 442-447 stay untouched.) - [ ] **Step 2: Run the pin — `term_do_doc_comment_is_true` GREEN** Run: `cargo test --workspace -p ailang-core --test effect_doc_honesty_pin term_do_doc_comment_is_true` Expected: PASS. --- ## Task 4: `crates/ailang-core/specs/form_a.md` Diverge mentions **Files:** - Modify: `crates/ailang-core/specs/form_a.md:226`, `:356-358` - [ ] **Step 1: Line 226 (valid-effect-name enumeration)** Replace exactly: ``` `EFFECT-NAME` is a bare identifier — currently `IO` and `Diverge`. ``` with: ``` `EFFECT-NAME` is a bare identifier. The only effect with a built-in op today is `IO` (op `io/print_str`); `Diverge` is a reserved name (no op, unimplemented). ``` (Line 227 "Effects are a set; order is irrelevant." stays — and is now corroborated by the DESIGN.md set-equality wording.) - [ ] **Step 2: Lines 356-358 (rule 3 op→effect mapping)** Replace exactly: ``` 3. **Effects on side-effecting fns.** A function whose body uses `(do ...)` MUST list every effect operation's effect in its `(effects ...)` clause. `IO` for `io/print_*`; `Diverge` for `diverge/*`. ``` with: ``` 3. **Effects on side-effecting fns.** A function whose body uses `(do ...)` MUST list every effect operation's effect in its `(effects ...)` clause. The only built-in effect op is `io/print_str`, whose effect is `IO`. ``` - [ ] **Step 3: Run the pin — `form_a_spec_effect_names_are_true` GREEN** Run: `cargo test --workspace -p ailang-core --test effect_doc_honesty_pin form_a_spec_effect_names_are_true` Expected: PASS. --- ## Task 5: `crates/ail/src/main.rs` merge-prose CONTRACT example **Files:** - Modify: `crates/ail/src/main.rs:288-290` - [ ] **Step 1: Replace the effect-annotation bullet** Replace exactly: ``` - Effect annotations on return types (`(effects IO)`, `(effects Diverge)`). Prose shows these as `with IO` etc.; if uncertain, keep what the original had. ``` with: ``` - Effect annotations on return types (e.g. `(effects IO)`). Prose shows these as `with IO` etc.; if uncertain, keep what the original had. ``` - [ ] **Step 2: Run the pin — `merge_prose_contract_example_is_true` GREEN** Run: `cargo test --workspace -p ail --test effect_doc_honesty_pin merge_prose_contract_example_is_true` (Note: the pin is an `ailang-core` integration test; run the whole pin here.) Run: `cargo test --workspace -p ailang-core --test effect_doc_honesty_pin` Expected: PASS (all four tests). --- ## Task 6: Full verification - [ ] **Step 1: Whole pin GREEN** Run: `cargo test --workspace -p ailang-core --test effect_doc_honesty_pin` Expected: PASS — 4/4. - [ ] **Step 2: No collateral drift** Run: `cargo test --workspace` Expected: PASS — the prior green count + 4 new pin tests; in particular `design_schema_drift.rs`, `spec_drift.rs`, `schema_coverage.rs` stay green (recon confirmed none scans the effect-prose region; this step proves it empirically). - [ ] **Step 3: Build sanity (no behavioural change)** Run: `cargo build --workspace` Expected: clean. No codegen / checker code was touched — `ail check` / `ail run` behaviour is byte-unchanged by construction (doc-comment + markdown + an internal guidance string only). --- ## Self-review (planner Step 5, inline) 1. **Scope coverage:** every roadmap-`[todo]` clause (a)/(b)/(c) + form_a.md ×2 + main.rs + guard test has a task. ✓ 2. **Placeholder scan:** no "TBD"/"TODO"/"similar to"/"appropriate"; every code-changing step carries exact before→after text. ✓ 3. **Type/string consistency:** the pin's asserted corrected substrings (`"flat, unordered, closed set of effect names"`, `` "`Diverge` (for non-termination) is reserved" ``, `"resolved at typecheck against `Env::effect_ops`"`, `"The only built-in effect op is `io/print_str`"`) appear verbatim in the Task 2/3/4 replacement bodies; the asserted absent substrings (`"row-polymorphic"`, `"the built-in IO and Diverge ops"`, `"resolved against the effect-handler table at link time"`, `` "`Diverge` for `diverge/*`" ``, `"currently `IO` and `Diverge`"`, `` "`(effects Diverge)`" ``) are exactly the strings removed. Cross-checked task-by-task. ✓ 4. **Step granularity:** every step is one edit or one command, 2-5 min. ✓ 5. **No commit steps.** ✓ (Boss commits the whole tidy.) 6. **Line-wrap robustness:** every pin substring is single-line (no `\n`-spanning fragment) — the documented iter-revert grep-wrap failure mode is avoided. ✓