Files
AILang/docs/plans/0075-effect-doc-honesty.md
T
Brummel 832375f2ac convention: counter-prefix file naming across docs/specs/, docs/plans/, design/contracts/, design/models/
All 176 files in the four accumulating directories now use a
zero-padded 4-digit counter prefix that reflects creation order
(`NNNN-slug.md`). The counter is assigned per directory in strict
git-log creation order; ties broken alphabetically by original name.
The old `YYYY-MM-DD-` prefix on docs/specs/ and docs/plans/ files is
dropped — the date is recoverable from git log and the counter
carries the ordering.

A file's counter is stable for the life of the file: never reassigned,
never reused, never compacted. Deleted files retire their counter;
subsequent files do not fill the gap. This is the property that lets
cross-references stay literal — refs use the full filename including
the counter (`design/contracts/0007-honesty-rule.md`) so they grep
cleanly and resolve directly without a glob step.

313 cross-references updated across .md/.rs/.toml/.c/.json files
(test pins, include_str! paths, design-INDEX entries, baseline notes,
runtime C comments, inter-contract markdown links incl. bare basename
and `../models/foo.md` forms).

CLAUDE.md gets a new "File-naming convention" section spelling out
the rule and rationale. skills/brainstorm/SKILL.md and
skills/planner/SKILL.md updated so new spec/plan creation produces
counter-prefixed names from the start.

The full test suite (cargo test --workspace) passes.
2026-05-28 13:31:31 +02:00

12 KiB
Raw Blame History

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-439Term::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).

//! 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<String, EffectOpSig>`) 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 IOandDiverge", "`(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. ✓