tidy setup: retire effect-op-arg-modes bundle; split out effect-doc-honesty + AILang-code spec discipline
The effect-op-arg-modes brainstorm spec bundled a real, self-contained DESIGN.md honesty fix with speculative build-ahead infra (an EffectOpSig arg-mode field whose only consumer was a synthetic test op). Recon had already destroyed the "effect-handler infrastructure" premise: the effect raise/declare/subset/propagation machinery is fully generic over any effect string; the only real prerequisite was one struct field, not a milestone. The user rejected the bundle as incoherent and the build-ahead half as the iteration-discipline trap repeated. - Retire the uncommitted effect-op-arg-modes spec (deleted from the working tree; never committed). - roadmap: drop the false "sub-ms-2 = effect-handler infrastructure" framing; mark Stateful-islands sub-milestone sequencing as UNDER RE-THINK with the user (no !Mut planning until that conversation); add the split-out effect-doc-honesty as an in-flight P2 [~] todo. - plan docs/plans/2026-05-16-effect-doc-honesty.md: placeholder-free tidy plan (the three DESIGN.md fictions + form_a.md + main.rs + a new doc-presence pin) over recon-mapped exact byte sites. - brainstorm SKILL: specs and design talk LEAD with the AILang (.ail) program that should work (= the feature-acceptance clause-1 evidence); the Rust implementation shape is secondary, never a substitute; a no-surface infra milestone still shows concrete .ail (often a must-fail fixture). Codifies user feedback from this session.
This commit is contained in:
@@ -0,0 +1,356 @@
|
||||
# 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<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 `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. ✓
|
||||
+56
-7
@@ -221,6 +221,33 @@ clean. Pick the next milestone from P2.)_
|
||||
- [ ] **\[todo\]** 21'h iteration — final 21' carry-over (latency
|
||||
methodology pass). Numbering kept for continuity with the 21' arc.
|
||||
|
||||
- [~] **\[todo\]** DESIGN.md effect-prose is fiction — standalone
|
||||
documentation-honesty tidy (split out 2026-05-16 from the rejected
|
||||
effect-op-arg-modes bundle; in flight via planner→implement). Three
|
||||
load-bearing effect-system claims in DESIGN.md are presently false
|
||||
and mislead every future brainstorm + the architect, who read
|
||||
DESIGN.md as truth: (a) DESIGN.md:172/2722 say `IO` **and**
|
||||
`Diverge` are wired-up built-in ops — `Diverge` has zero code in
|
||||
any crate (not registered, never injected, no string literal);
|
||||
reconcile to "only `IO` (`io/print_str`) is wired up; `Diverge`
|
||||
reserved/unimplemented", modelled on Decision 4's
|
||||
reserved-but-opaque refinements precedent. (b) DESIGN.md:171-172
|
||||
"the effect set is row-polymorphic (`![IO | r]`)" — no row variable
|
||||
/ `EffectRow` exists anywhere; effect sets are flat string
|
||||
collections unified by set-equality; remove the claim (a flat
|
||||
closed set is *more* locally legible — Decision 3 pillar — so this
|
||||
is honesty, not a concession; pillar-doc edit, flag at review).
|
||||
(c) `crates/ailang-core/src/ast.rs:438-439` `Term::Do` doc-comment
|
||||
"resolved against the effect-handler table at link time" — there is
|
||||
no handler table; it is an `IndexMap` lookup at typecheck + a
|
||||
literal codegen `match`; correct it. Lockstep: `form_a.md:226,358`
|
||||
+ CLI-help `main.rs:289` Diverge mentions reconciled. Guard: a new
|
||||
doc-presence test (false strings absent, corrected text present).
|
||||
No language/codegen change; pure documentation truth. Grounding
|
||||
triple-verified 2026-05-16 (three `ailang-grounding-check` PASSes).
|
||||
- context: surfaced by the effect-subsystem recon during the
|
||||
retired effect-op-arg-modes brainstorm; see the Stateful-islands
|
||||
"UNDER RE-THINK" note for why the bundle was rejected.
|
||||
- [ ] **\[todo\]** `io/print_float` always-emit-`.0` — surface
|
||||
printer always emits `.` or `e/E` so re-lex routes to Float;
|
||||
the runtime printer (`printf("%g\n", v)`) doesn't, so `2.0`
|
||||
@@ -330,13 +357,35 @@ clean. Pick the next milestone from P2.)_
|
||||
fn signatures stay pure while LLM authors can write imperative
|
||||
accumulators directly. Spec `docs/specs/2026-05-15-mut-local.md`;
|
||||
iters mut.1/mut.2/mut.3/mut.4-tidy (commits 7b92719, b24718a,
|
||||
03fb633, 20add51). Remaining sub-milestones (to be brainstormed
|
||||
separately, in order): (2) effect-handler infrastructure as a
|
||||
prerequisite for any non-IO/non-Diverge effect; (3) `!Mut` effect
|
||||
+ `ref a` first-class type that can escape mut blocks under
|
||||
uniqueness-tracking; (4) mutable-array primitive `MutArray a`;
|
||||
(5) `Stateful a b` sealed callable type + `pipe` combinator with
|
||||
zero-allocation composition + `runST`-equivalent escape discharge.
|
||||
03fb633, 20add51).
|
||||
|
||||
**Sub-milestone sequencing is UNDER RE-THINK with the user
|
||||
(2026-05-16) — no further sub-milestone is planned or brainstormed
|
||||
until that conversation happens.** Why: the 2026-05-15 decomposition
|
||||
named "(2) effect-handler infrastructure as a prerequisite for any
|
||||
non-IO/non-Diverge effect" as the next step. A read-only recon of
|
||||
the live effect subsystem (2026-05-16) showed that premise is false:
|
||||
the effect raise / declared-set / `UndeclaredEffect`-subset / call-
|
||||
propagation machinery is already fully generic over an arbitrary
|
||||
effect string; `!Mut` needs **no** effect-handler machinery. The
|
||||
only real code prerequisite the recon found is one hard-coded
|
||||
assumption (`linearity.rs` walks every `Term::Do` arg as `Borrow`;
|
||||
`EffectOpSig` has no per-arg mode field) — a single struct field,
|
||||
not a milestone. A brainstorm spec that tried to make sub-ms-2 a
|
||||
standalone deliverable bundled that speculative one-field capability
|
||||
with an unrelated DESIGN.md honesty fix; the user correctly rejected
|
||||
the bundle as incoherent and the build-ahead-of-consumer half as the
|
||||
iteration-discipline trap repeated. **Resolution:** (a) the DESIGN.md
|
||||
effect-honesty correction is split out and runs now as a standalone
|
||||
documentation tidy (see the P2 `[todo]` "DESIGN.md effect-prose is
|
||||
fiction" below); (b) the `arg_modes` finding is recorded as recon
|
||||
context for the future `!Mut` design, NOT built ahead — it is
|
||||
first-iteration material of whatever the `!Mut` milestone turns out
|
||||
to be, validated there against a real consuming op, never a
|
||||
synthetic test op; (c) the old "(2)…(5)" sequence (effect-handler
|
||||
infra → `!Mut`+`ref a` → `MutArray a` → `Stateful a b`+`pipe`) is
|
||||
no longer treated as settled — the whole ordering and granularity
|
||||
is what the pending user conversation re-decides.
|
||||
|
||||
**Motivation.** AILang's signature-as-contract thesis is *better*
|
||||
served by an explicit `Stateful a b` + `!Mut` annotation than by
|
||||
|
||||
Reference in New Issue
Block a user