diff --git a/docs/plans/design-md-rolesplit.1.md b/docs/plans/design-md-rolesplit.1.md new file mode 100644 index 0000000..f03ff98 --- /dev/null +++ b/docs/plans/design-md-rolesplit.1.md @@ -0,0 +1,795 @@ +# DESIGN.md → `design/` role-split — Implementation Plan (iter 1, the whole milestone) + +> **Parent spec:** `docs/specs/2026-05-19-design-md-rolesplit.md` +> (incl. the authoritative **Appendix — Boss-adjudicated relocation +> map**; grounding-check PASS ×2). +> +> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement` +> to run this plan. Steps use `- [ ]` checkboxes for tracking. + +**Goal:** Replace the 3020-line `docs/DESIGN.md` with the `design/` +ledger (`INDEX.md` + 15 `contracts/` + 5 `models/`), move +decision-records to a journal, retarget every live reference, and +ship the RED-first `design_index_pin.rs` anti-regrowth guard — in a +build-atomic shape. + +**Architecture:** The spec Appendix table is the per-`##`/`###` +relocation map (line ranges → destination); this plan executes it. +Build-atomicity is achieved *by task ordering*: `design/contracts/data-model.md` +is created (Task 2) and `design_schema_drift.rs`'s `include_str!` +retargeted to it (Task 5) **before** `docs/DESIGN.md` is deleted +(Task 9), so the workspace compiles at every task boundary. The +single milestone commit is the Boss's; tasks are review units, not +commits. + +**Tech Stack:** Markdown (`design/`), Rust tests +(`crates/ailang-core/tests/`, `crates/ail/tests/`), `crates/ailang-check/src/lib.rs` +(2 diagnostic strings — the only logic edit), `bench/architect_sweeps.sh`, +skill/agent Markdown. + +**Authoritative map:** every "move DESIGN.md:A–B" below is the spec +Appendix row for that range. The Appendix is placeholder-free; this +plan does not restate the 70-row table — it cites it. Re-levelling +rule (all moved prose): the source `##`/`###` heading becomes the +file's `#` H1 (or `##` for a subsection under a file's single H1); +no content edits except (a) heading level, (b) `honesty-rule.md` is +*rewritten* per spec §Architecture-4, (c) the OQ7 cite deletion. + +--- + +## Task 1: RED-first `design_index_pin.rs` (the anti-regrowth spine) + +**Files:** +- Create: `crates/ailang-core/tests/design_index_pin.rs` + +- [ ] **Step 1: Write the 4-clause guard test** + +```rust +//! Structural pin for the design/ ledger. Sibling of +//! docs_honesty_pin.rs. Fails RED the instant the design/ split +//! re-conflates contract + rationale + narrative, an INDEX row +//! dangles, a contract loses its ratifying test, or docs/DESIGN.md +//! is resurrected. Spec: docs/specs/2026-05-19-design-md-rolesplit.md. + +use std::fs; +use std::path::{Path, PathBuf}; + +fn root() -> PathBuf { + PathBuf::from(concat!(env!("CARGO_MANIFEST_DIR"), "/../../")) +} + +fn read(rel: &str) -> String { + fs::read_to_string(root().join(rel)) + .unwrap_or_else(|e| panic!("read {rel}: {e}")) +} + +fn norm(s: &str) -> String { + s.split_whitespace().collect::>().join(" ") +} + +/// Parse the two pipe-tables out of design/INDEX.md. +/// Returns (contracts, models) as rows of trimmed cells. +fn index_tables() -> (Vec>, Vec>) { + let idx = read("design/INDEX.md"); + let mut contracts = Vec::new(); + let mut models = Vec::new(); + let mut section = ""; + for line in idx.lines() { + let t = line.trim(); + if t.starts_with("## Contracts") { + section = "c"; + continue; + } + if t.starts_with("## Models") { + section = "m"; + continue; + } + if !t.starts_with('|') { + continue; + } + let cells: Vec = t + .trim_matches('|') + .split('|') + .map(|c| c.trim().to_string()) + .collect(); + // skip header + separator rows + if cells.iter().any(|c| c.starts_with("---")) { + continue; + } + if cells.first().map(|c| c.as_str()) == Some("id") { + continue; + } + match section { + "c" => contracts.push(cells), + "m" => models.push(cells), + _ => {} + } + } + (contracts, models) +} + +/// A link cell may be a design/ path, a source path, a dual-link +/// "A + B", or carry a trailing "(in-source ...)" / "§..." note. +/// Resolve to the first concrete path token and check it exists. +fn link_target_exists(cell: &str) -> bool { + let first = cell.split(" + ").next().unwrap_or(cell).trim(); + // strip a trailing parenthetical or §-note + let path = first + .split(" (") + .next() + .unwrap_or(first) + .split(" §") + .next() + .unwrap_or(first) + .trim() + .trim_end_matches("//!") + .trim(); + !path.is_empty() && root().join(path).exists() +} + +#[test] +fn design_md_is_gone() { + // clause 4 — clean-cut pin + assert!( + !root().join("docs/DESIGN.md").exists(), + "docs/DESIGN.md was resurrected; the split is clean-cut" + ); +} + +#[test] +fn every_index_link_resolves() { + // clause 1 + let (contracts, models) = index_tables(); + assert!(contracts.len() >= 15, "expected >=15 contract rows, got {}", contracts.len()); + assert!(models.len() >= 5, "expected >=5 model rows, got {}", models.len()); + for row in contracts.iter().chain(models.iter()) { + let link = row.last().expect("row has a link cell"); + assert!( + link_target_exists(link), + "INDEX link does not resolve: {:?} (row {:?})", + link, + row + ); + } +} + +#[test] +fn every_contract_names_a_resolvable_ratifying_test() { + // clause 2 — ratifying-test token resolves to a real file under + // crates/**/tests, crates/**/src (in-source #[cfg(test)] mod + // tests are first-class ratifiers — spec OQ1/OQ2), bench/, or + // skills/**/SKILL.md. + let (contracts, _) = index_tables(); + for row in &contracts { + // columns: id | consumer/lifetime | ratifying-test | link + let rt = &row[2]; + // take the path token (before any " (" note) + let path = rt.split(" (").next().unwrap_or(rt).trim(); + assert!( + root().join(path).exists(), + "ratifying-test does not resolve to a real file: {:?} (contract {:?})", + path, + row[0] + ); + } +} + +#[test] +fn contracts_carry_no_decision_record_prose() { + // clause 3 — the conflation tripwire. + let dir = root().join("design/contracts"); + let markers = [ + "we rejected", + "an earlier draft", + "Why not other", + "was retired in iter", + "rollback plan", + "previously all", + ]; + for entry in fs::read_dir(&dir).expect("design/contracts/ exists") { + let p = entry.unwrap().path(); + if p.extension().and_then(|e| e.to_str()) != Some("md") { + continue; + } + let body = norm(&fs::read_to_string(&p).unwrap()); + for m in &markers { + assert!( + !body.contains(m), + "decision-record marker {:?} found in contract {:?} — re-conflation", + m, + p.file_name().unwrap() + ); + } + } +} +``` + +- [ ] **Step 2: Run to verify it is RED** + +Run: `cargo test -p ailang-core --test design_index_pin 2>&1 | tail -20` +Expected: FAIL — `design_md_is_gone` fails ("was resurrected"; DESIGN.md +still present) and `every_index_link_resolves` / the others panic on +`read design/INDEX.md: ... No such file`. The test **file compiles** +(build stays green); only the assertions are RED. This is the +required RED-first state. + +--- + +## Task 2: `design/INDEX.md` + the 15 `design/contracts/*.md` + +**Files:** +- Create: `design/INDEX.md` +- Create: `design/contracts/{feature-acceptance,authoring-surface,roundtrip-invariant,memory-model,data-model,float-semantics,typeclasses,tail-calls,frozen-value-layout,honesty-rule,embedding-abi,str-abi,scope-boundaries,verification}.md` + (14 prose files; `mangling`, `env-construction`, `qualified-xref` + are source-link-only — **no file**, INDEX rows only) + +- [ ] **Step 1: Write `design/INDEX.md`** + +Use the spec §"Concrete code shapes" item 1 INDEX block **verbatim** +(it already carries the corrected 15-row Contracts table incl. +`qualified-xref`, `str-abi`, `scope-boundaries`, and the 5-row +Models table). Header preamble = the spec's INDEX preamble text +PLUS, per spec Appendix OQ6, a short "## Project framing" +sub-section absorbing DESIGN.md:6–18 (`## Goal`), :19–55 +(`## Project ecosystem` intro), :83–92 (`## Project language: +English`) — moved whole, re-levelled to `###` under the preamble. + +- [ ] **Step 2: Run the INDEX-shape assertion** + +Run: `cargo test -p ailang-core --test design_index_pin every_index_link_resolves 2>&1 | tail -5` +Expected: still FAIL, but now past the parse (panics on a contract +file not yet existing, not on "read design/INDEX.md"). Confirms +INDEX.md parses and has ≥15 contract / ≥5 model rows. + +- [ ] **Step 3: Create the 14 prose contract files (one per Appendix contract row)** + +For each file below, concatenate the cited DESIGN.md line ranges +**from the spec Appendix table**, re-level the top heading to `#`, +and route every `### …` the Appendix tags `D` to Task 4 (NOT into +this file). Files and their Appendix-sourced ranges: + +- `feature-acceptance.md` ← DESIGN.md:93–164 +- `authoring-surface.md` ← :230–240 + :268–297 + :298–331 (the three + contract `###`s of Decision 6 per Appendix; :241–243 and all + `### Why…`/`### …does not do`/`### Implementation outline`/`### + Form refinements`/`### Empirical addendum` go to Task 4) +- `roundtrip-invariant.md` ← :2113–2208 (whole block, 3 `###`s kept) +- `memory-model.md` ← :1123–1150 + :1151–1312 + :1313–1351 + :1394–1412 + + :1413–1509 (the five binding `###`s per Appendix) +- `data-model.md` ← :2377–2615 (whole `## Data model` + 4 `###`s) +- `float-semantics.md` ← :2700–2838 **except** the inline + `**Str ABI.**` paragraph at :2802 (that one para → `str-abi.md`, + the single sanctioned sentence-level move per Appendix OQ4) +- `typeclasses.md` ← :1578–1639 + :1753–1823 + :1824–1855 + :1856–1917 + + :1918–1964 + :2001–2042 (the six contract `###`s of Decision 11 + per Appendix) +- `tail-calls.md` ← :731–787 (whole `## Decision 8`) +- `frozen-value-layout.md` ← :2334–2376 +- `honesty-rule.md` ← **rewritten** (next step — do NOT raw-move :56–82) +- `embedding-abi.md` ← :2266–2333 +- `str-abi.md` ← :2043–2088 (`### Heap-Str primitives`) + the + `**Str ABI.**` paragraph lifted from :2802 +- `scope-boundaries.md` ← :2839–3020 (`## What is not (yet) supported`) +- `verification.md` ← :2687–2699 + +Each file starts `# ` and ends with a line +`Ratified by: <the INDEX ratifying-test path for this contract>.` + +- [ ] **Step 4: Write the rewritten `design/contracts/honesty-rule.md`** + +Do NOT raw-move DESIGN.md:56–82. Rewrite so the rule names the new +home. The two `docs_honesty_pin.rs:70,72` anchors MUST appear as +**contiguous substrings** (planner item-6 contiguity — no soft-wrap +splitting them): + +```markdown +# The honesty rule this ledger holds itself to + +`design/` describes what AILang **is now**: schema, semantics, +invariants, runtime contracts. It is present-tense by construction. +Two things never belong in a contract or model file: + +- **Forward intent** ("planned / will back / on the path to") — that + lives in `docs/roadmap.md`. +- **History and rationale** ("an earlier draft said / previously / + why X was chosen / why Y was rejected / retired in iter Z") — that + lives in `docs/journals/`. Decision-records are journal content, + not ledger content; this is the honesty rule it holds itself to. + +The single legitimate exception is a present-tense reserved or +deliberately-excluded claim that is explicitly and correctly +labelled. The discriminator is not whether a sentence mentions past +or future, but whether the document asserts something exists, works, or changed that does not. + +Ratified by: `crates/ailang-core/tests/docs_honesty_pin.rs`. +``` + +(The two pinned phrases — `the honesty rule it holds itself to` and +`whether the document asserts something exists, works, or changed +that does not` — each sit on a single physical line above; `norm()` +in `docs_honesty_pin.rs` is whitespace-tolerant but contiguity is +kept anyway to satisfy the raw-`grep` advisory sweep.) + +- [ ] **Step 5: Verify clause-3 (no decision-record prose leaked into contracts)** + +Run: `cargo test -p ailang-core --test design_index_pin contracts_carry_no_decision_record_prose 2>&1 | tail -5` +Expected: PASS (every `###` the Appendix tags `D` was routed to +Task 4, not into these files). If a marker fires, a `### Why…` +subsection was wrongly included — move it to the Task 4 journal. + +--- + +## Task 3: the 5 `design/models/*.md` whitepapers + +**Files:** +- Create: `design/models/{rc-uniqueness,typeclasses,effects,authoring-surface,pipeline}.md` + +- [ ] **Step 1: Create the 5 model files (Appendix model-tagged ranges)** + +- `rc-uniqueness.md` ← :788–852 + :853–913 + :914–977 + :1015–1122 + + :1378–1393 (Decision 9/10 narrative `###`s per Appendix) +- `typeclasses.md` ← :1536–1577 + :1640–1752 (Decision 11 narrative) +- `effects.md` ← :190–204 (Decision 3 — the + `effect_doc_honesty_pin.rs` present-anchors live here; that pin's + read retargets to this file in Task 5) +- `authoring-surface.md` ← :332–471 + :590–674 (`### Candidate + notations` + `### Form (B)` — the `docs_honesty_pin.rs` Form-B + placeholder present-anchor retargets here in Task 5) +- `pipeline.md` ← :2616–2662 (`## Pipeline`) + :2663–2686 (`## CLI`) + +Each starts `# <Title>`. Models carry no `Ratified by:` line (no +test pins a model — clause 2 is contracts-only). + +- [ ] **Step 2: Verify models resolve in INDEX** + +Run: `cargo test -p ailang-core --test design_index_pin every_index_link_resolves 2>&1 | tail -5` +Expected: closer to PASS — model links now resolve; remaining +failures are only source-link contract rows whose targets already +exist (`runtime/str.c`, `crates/ailang-codegen/src/lib.rs`, …) so +this should now PASS for all 20 link rows. If it still fails, the +failing row's link path is mistyped in INDEX.md — fix the INDEX +cell. + +--- + +## Task 4: decision-record journal + journals INDEX pointer + +**Files:** +- Create: `docs/journals/2026-05-19-design-decision-records.md` +- Modify: `docs/journals/INDEX.md` (append one pointer line) + +- [ ] **Step 1: Create the relitigation-guard archive** + +Concatenate, in DESIGN.md order, every range the spec Appendix tags +`D`: :165–176, :177–189, :205–213, :214–229, :241–243, :245–267, +:472–486, :487–519, :520–536, :537–589, :675–730, :978–1014, +:1352–1377, :1510–1519, :1520–1535, :1965–2000, :2089–2112, plus +any genuine "why" sentence extracted from the source-link `##`s +(`## Mangling scheme` :2209–2218, `## Convention: qualified +cross-module references` :2219–2236, `## Env construction` +:2237–2265 — per Appendix, behaviour sentences are *dropped* as +code-is-SoT; only rationale sentences move here). + +Header: + +```markdown +# Design decision-records — relitigation guard (migrated 2026-05-19) + +Why-X-chosen / why-Y-rejected / deliberately-does-not-do / rollback +/ empirical-addendum prose, migrated out of the former +`docs/DESIGN.md` by the design-md-rolesplit milestone. A future +brainstorm reads this so it does not re-propose a settled-and- +rejected idea. Order preserved from the source. This file is +append-only history; it is NOT a contract surface. +``` + +- [ ] **Step 2: Append the journals INDEX pointer** + +Append to `docs/journals/INDEX.md` (newest-last, one line): + +``` +- 2026-05-19 — design-decision-records (migration): relitigation-guard archive — every why/rejected/does-not-do/rollback/empirical ### moved out of the former docs/DESIGN.md by the design-md-rolesplit milestone. Companion to spec 2026-05-19-design-md-rolesplit. +``` + +- [ ] **Step 3: Verify acceptance criterion 2 (nothing dropped)** + +Run: `python3 - <<'EOF' +import re +src=open("docs/DESIGN.md").read().splitlines() +heads=[(i+1,l) for i,l in enumerate(src) if re.match(r'^#{2,3} ',l)] +print(len(heads),"headings in DESIGN.md;", + "expect every line range covered by spec Appendix") +EOF` +Expected: prints the heading count; cross-check by eye that every +`##`/`###` appears in exactly one of Task 2/3/4 (the Appendix is the +checklist). This is a human-verifiable completeness gate, not an +automated assertion (the automated guarantee is clause-3 + +acceptance grep in Task 9). + +--- + +## Task 5: retarget the three doc-reading tests (GREEN against `design/`) + +**Files:** +- Modify: `crates/ailang-core/tests/design_schema_drift.rs:22` (+ remove `data_model_section` 28–37 and `data_model_section_is_bounded` 411–430; repoint 7 anchor tests) +- Modify: `crates/ailang-core/tests/docs_honesty_pin.rs` (the `read("docs/DESIGN.md")` calls + the :70/:72 anchors) +- Modify: `crates/ailang-core/tests/effect_doc_honesty_pin.rs:21` + +- [ ] **Step 1: Retarget `design_schema_drift.rs`** + +Line 22, exact before → after: + +```rust +- const DESIGN_MD: &str = include_str!("../../../docs/DESIGN.md"); ++ const DATA_MODEL: &str = include_str!("../../../design/contracts/data-model.md"); +``` + +Delete `fn data_model_section()` (lines 28–37) and +`fn data_model_section_is_bounded()` (lines 411–430). In the 7 +anchor tests (`design_md_anchors_every_term_variant` :43, +`…pattern…` :185, `…type…` :214, `…literal…` :248, `…def_kind` +:277, `…parammode…` :348, `…nested_struct_keys` :373) replace every +`data_model_section()` call with the const `DATA_MODEL`. Update the +module rustdoc (:1,3,11) "DESIGN.md §Data model" → "design/contracts/data-model.md". + +- [ ] **Step 2: Retarget `docs_honesty_pin.rs` per-sentence home** + +The `read()` helper (15–19) and `norm()` (24–26) are unchanged. +Replace each `read("docs/DESIGN.md")` with a read of the design/ +file where that test's pinned strings now live (per spec Appendix): +- `design_md_has_no_wunschdenken` (:30) and + `design_md_has_no_doc_archaeology` (:51): absent-pins span + Boehm/float/typeclasses/effects/Form-B — read the concatenation + of `design/models/rc-uniqueness.md`, `design/contracts/float-semantics.md`, + `design/contracts/typeclasses.md`, `design/models/effects.md`, + `design/models/authoring-surface.md` (absent-pins must be absent + from all → join the reads). +- `design_md_present_tense_anchors_present` (:68): split per anchor + to its home file; **:70** `"the honesty rule it holds itself to"` + and **:72** `"whether the document asserts something exists, + works, or changed that does not"` → read `design/contracts/honesty-rule.md`; + the Form-B placeholder anchor (:90 area) → `design/models/authoring-surface.md`; + the remaining present-anchors → their Appendix home file. +- `form_a_scalar_param_carveout_present_and_old_rule_absent` + (:104–137): the `FORM_A_SPEC` read is unchanged; the + `read("docs/DESIGN.md")` at :112 → `design/contracts/embedding-abi.md` + (the `:135` pin `Export parameters are written **bare**…` lives + in `## Embedding ABI`, Appendix → embedding-abi.md). +- `prose_roundtrip_md_has_no_wunschdenken` (:94–101): reads + `docs/PROSE_ROUNDTRIP.md` — **unchanged**. + +- [ ] **Step 3: Retarget `effect_doc_honesty_pin.rs:21`** + +`design_md_effect_prose_is_true` (:19–34), line 21: + +```rust +- let d = read("docs/DESIGN.md"); ++ let d = norm(&[read("design/models/effects.md"), ++ read("design/contracts/scope-boundaries.md")].join("\n")); +``` + +(Decision 3 effect prose → `models/effects.md`; the +`"the built-in IO and Diverge ops"` absent-pin's home +`## What is not (yet) supported` → `contracts/scope-boundaries.md`. +Adjust the existing `norm()` usage in the test so the join is +normalised once; keep every pinned string assertion unchanged.) + +- [ ] **Step 4: Verify the three tests GREEN (DESIGN.md still present)** + +Run: `cargo test -p ailang-core --test design_schema_drift --test docs_honesty_pin --test effect_doc_honesty_pin 2>&1 | tail -15` +Expected: PASS for all three (they now read `design/` files which +exist from Tasks 2–3; `docs/DESIGN.md` still on disk but no longer +read by these tests). `cargo build --workspace` still green +(`include_str!` now points at the existing `design/contracts/data-model.md`). + +--- + +## Task 6: retarget the 2 diagnostics + 2 E2Es (lockstep) + +**Files:** +- Modify: `crates/ailang-check/src/lib.rs` (Float branch ~872–876, Show branch ~885–893) +- Modify: `crates/ail/tests/eq_float_noinstance.rs:41-42` +- Modify: `crates/ail/tests/show_no_instance_e2e.rs:47-48` + +- [ ] **Step 1: Retarget the Float-branch diagnostic** + +`crates/ailang-check/src/lib.rs` Float branch, exact: + +```rust +- orderability per IEEE-754); see DESIGN.md §\"Float semantics\".", ++ orderability per IEEE-754); see design/contracts/float-semantics.md.", +``` + +- [ ] **Step 2: Retarget the Show-branch diagnostic (contiguity-safe)** + +`crates/ailang-check/src/lib.rs` Show branch. The path +`design/contracts/typeclasses.md` MUST contain no interior literal +whitespace and must not be split by a `\`-continuation in a way +that injects a space (Rust `\`-continuation strips the newline + +leading indent, so keep the full path token on one physical source +line). Exact: + +```rust +- the prelude; see DESIGN.md §\"Prelude (built-in) \ +- classes\". User types declare their own \ ++ the prelude; see design/contracts/typeclasses.md. \ ++ User types declare their own \ +``` + +(Runtime string becomes `…the prelude; see design/contracts/typeclasses.md. User types declare their own …` — the path is one contiguous run; the E2E `contains("design/contracts/typeclasses.md")` holds.) + +- [ ] **Step 3: Retarget the two E2E assertions (lockstep)** + +`crates/ail/tests/eq_float_noinstance.rs` lines 41–42: + +```rust +- no_inst.message.contains("Float semantics") || no_inst.message.contains("DESIGN"), +- "expected NoInstance message to cross-reference DESIGN.md §Float semantics, got: {:?}", ++ no_inst.message.contains("design/contracts/float-semantics.md"), ++ "expected NoInstance message to cross-reference the float-semantics contract, got: {:?}", +``` + +`crates/ail/tests/show_no_instance_e2e.rs` lines 47–48: + +```rust +- no_inst.message.contains("Prelude (built-in) classes"), +- "expected DESIGN.md §Prelude (built-in) classes cross-reference, got message: {:?}", ++ no_inst.message.contains("design/contracts/typeclasses.md"), ++ "expected design/contracts/typeclasses.md cross-reference, got message: {:?}", +``` + +Also update the module-rustdoc / comment lines in both E2E files +(`eq_float_noinstance.rs:5,38`, `show_no_instance_e2e.rs:5,43-45`) +that name "DESIGN.md §…" → the new contract path. + +- [ ] **Step 4: Verify the lockstep E2Es GREEN** + +Run: `cargo test -p ail --test eq_float_noinstance --test show_no_instance_e2e 2>&1 | tail -10` +Expected: PASS — each asserts the new contiguous pointer substring. +(Both filters resolve: `eq_float_noinstance` has +`eq_at_float_fires_float_aware_noinstance`; `show_no_instance_e2e` +has its Show-noinstance test — verified present in the tree.) + +--- + +## Task 7: retarget `bench/architect_sweeps.sh` + +**Files:** +- Modify: `bench/architect_sweeps.sh:22,25-28,34` (+ the 5 `run_sweep` scan target) + +- [ ] **Step 1: Repoint the spine + guard** + +```sh +- DESIGN="docs/DESIGN.md" ++ INDEX="design/INDEX.md" ++ DESIGN_GLOB="design/contracts design/models" +``` + +Guard (25–28): + +```sh +- if [[ ! -f "$DESIGN" ]]; then +- echo "architect_sweeps: $DESIGN not found (run from repo root)" >&2 ++ if [[ ! -f "$INDEX" ]]; then ++ echo "architect_sweeps: $INDEX not found (run from repo root)" >&2 + exit 2 + fi +``` + +- [ ] **Step 2: Repoint the grep target (multi-file scan)** + +Line 34, before → after (recursive over the design/ prose set; +sweep regexes themselves **unchanged** per spec §3): + +```sh +- matches=$(grep -nE "$pattern" "$DESIGN" || true) ++ matches=$(grep -rnE "$pattern" $DESIGN_GLOB || true) +``` + +Exit-code contract preserved: 0 = all sweeps clean, 1 = an anchor +matched, 2 = `design/INDEX.md` not found. + +- [ ] **Step 3: Verify exit-0 clean and exit-2 on missing spine** + +Run: `bash bench/architect_sweeps.sh; echo "exit=$?"` +Expected: prints the per-sweep lines and `exit=0` (the migrated +`design/` prose is honest — no wunschdenken/post-mortem; if Sweep 5 +fires, a moved file kept a stripped-prose marker → fix the file, +not the sweep). + +Run: `mv design/INDEX.md /tmp/IX.bak; bash bench/architect_sweeps.sh; echo "exit=$?"; mv /tmp/IX.bak design/INDEX.md` +Expected: `architect_sweeps: design/INDEX.md not found` and `exit=2`. + +--- + +## Task 8: retarget buckets (b)(c)(d)(e) — agent lists, SKILL bodies, CLAUDE.md, README, comment xrefs + +**Files (from plan-recon's exhaustive 5-bucket set):** +- Modify (b) ~12 agent files: `skills/audit/agents/ailang-architect.md`, `skills/audit/agents/ailang-bencher.md`, `skills/brainstorm/agents/ailang-grounding-check.md`, `skills/debug/agents/ailang-debugger.md`, `skills/docwriter/agents/ailang-docwriter.md`, `skills/fieldtest/agents/ailang-fieldtester.md`, `skills/implement/agents/ailang-implementer.md`, `skills/implement/agents/ailang-implement-orchestrator.md`, `skills/implement/agents/ailang-quality-reviewer.md`, `skills/implement/agents/ailang-spec-reviewer.md`, `skills/implement/agents/ailang-tester.md`, `skills/planner/agents/ailang-plan-recon.md` +- Modify (c) 5 SKILL bodies: `skills/audit/SKILL.md`, `skills/brainstorm/SKILL.md`, `skills/docwriter/SKILL.md`, `skills/fieldtest/SKILL.md`, `skills/boss/SKILL.md` +- Modify (d) `CLAUDE.md`, `skills/README.md` +- Modify (e) code/C/.ail comment xrefs (full list below) + +- [ ] **Step 1: Rewrite the architect reading-list bullet (the representative)** + +`skills/audit/agents/ailang-architect.md:30`, exact: + +``` +- 2. `docs/DESIGN.md` — the canonical specification. Drift is measured against ++ 2. `design/INDEX.md` — the typed contract ledger and sole spine. ++ Walk the Contracts table; drift is measured against each row's ++ `link` target (a `design/contracts/` file or the source `//!` ++ it names). `design/models/` is context, not a drift surface. +``` + +Also `ailang-architect.md:73,81,83,118,123` (the `architect_sweeps.sh` +exit-2 / Sweep-5 references): replace "DESIGN.md" with +"design/INDEX.md" and keep the exit-2 / sweep semantics text +(Task 7 preserved them). + +- [ ] **Step 2: Rewrite the remaining (b) agent reading lists to role** + +Per spec §4: `grounding-check` (`:34`) and architect read +`design/contracts/`; `fieldtester` (`:41`, no-edit limit `:255` +preserved) reads `design/INDEX.md` + `design/models/`; `bencher` +(`:38`) reads `design/models/rc-uniqueness.md` (Decision-9/Boehm +narrative); `debugger` (`:29`), `tester` (`:26`), `implementer` +(`:30`), `implement-orchestrator` (`:35`), `spec-reviewer` (`:34`), +`quality-reviewer` (`:32`), `plan-recon` (`:34,72,147`), +`docwriter` (`:35` + the 8 other refs) → name `design/INDEX.md` as +the spine. Replace every literal `docs/DESIGN.md` token in these +files; preserve each bullet's role descriptor. + +- [ ] **Step 3: Rewrite (c) SKILL bodies + (d) CLAUDE.md / README** + +Replace every live `docs/DESIGN.md` reference in +`skills/{audit,brainstorm,docwriter,fieldtest,boss}/SKILL.md` and +`skills/README.md` with `design/INDEX.md` (or the specific +`design/contracts/<x>.md` where the prose names a section, e.g. +`boss/SKILL.md:284` "Canonical feature-acceptance criterion" → +`design/contracts/feature-acceptance.md`). `CLAUDE.md`: `:45` +code-layout table row, the `## Roles of docs/DESIGN.md …` heading +and its paragraph (:199–212) → rewrite to describe the `design/` +ledger (DESIGN = current-state mirror discipline preserved, just +re-homed); `:78,178` inline refs → `design/INDEX.md`. + +- [ ] **Step 4: Retarget (e) code/C/.ail comment xrefs + OQ7 deletion** + +Replace `DESIGN.md §"X"` → the Appendix destination +(`design/contracts/<x>.md` or `design/models/<x>.md`) in: `ail-embed/src/lib.rs:4,35,91` +(drop the DESIGN.md line-number cites — `→ design/contracts/frozen-value-layout.md`); +`crates/ailang-check/src/lib.rs:449,861,882,2647,6336`; +`crates/ailang-check/tests/duplicate_ctor_pin.rs:4` (→ env-construction +source-link note); `crates/ailang-codegen/src/drop.rs:88,114`, +`lib.rs:178,685`, `match_lower.rs:107`; +`crates/ailang-codegen/tests/embed_record_layout_pin.rs:1`, +`embed_staticlib_lowering.rs:47`; `crates/ailang-core/src/ast.rs:3` +(→ `design/contracts/data-model.md`), `:211`; +`crates/ailang-surface/src/lib.rs:7`, `parse.rs:78,81`, +`print.rs:128`; `crates/ailang-surface/tests/round_trip.rs:10,72` +(→ `design/contracts/roundtrip-invariant.md`); +`crates/ail/src/main.rs:1393` (→ qualified-xref source-link), +`:1572`; `crates/ail/tests/codegen_import_map_fallback_pin.rs:2`, +`polyfn_dot_qualified_branch_pin.rs:2` (→ +`design/contracts/typeclasses.md`), `e2e.rs:2843` (→ +`design/contracts/str-abi.md`), `eq_ord_e2e.rs:89,110` (→ mangling +source-link), `embed/record_roundtrip.c:2`, `embed/tick_roundtrip.c:6` +(→ `design/contracts/frozen-value-layout.md`); +`examples/fieldtest/floats_3_safe_division.ail:19`, +`floats_4_float_to_str_reach.ail:6` (→ +`design/contracts/float-semantics.md`); `runtime/rc.c:41` (→ +`design/contracts/frozen-value-layout.md`), `runtime/str.c:172,198` +(→ `design/contracts/str-abi.md`); +`crates/ailang-core/specs/form_a.md:89`. + +**OQ7 — delete, not retarget:** `crates/ailang-codegen/src/lib.rs:103` +"Iter 13b notes in `DESIGN.md`" — rewrite the comment to state the +behaviour directly and **drop the cite entirely** (no `design/` or +journal pointer; the referenced notes exist nowhere — a pointer +would be fiction, itself an honesty-rule violation). + +- [ ] **Step 5: Verify build still green (doc/comment-only task)** + +Run: `cargo build --workspace 2>&1 | tail -3` +Expected: `Finished` (this task edits only Markdown + comments + +docstrings; no logic). `docs/DESIGN.md` still present here. + +--- + +## Task 9: the clean cut — delete `docs/DESIGN.md` + whole-tree gate + +**Files:** +- Delete: `docs/DESIGN.md` + +- [ ] **Step 1: Delete the file** + +Run: `git rm -q docs/DESIGN.md && echo deleted` +Expected: `deleted`. (`git rm` stages the deletion; the Boss owns +the final commit shape — this only removes it from the working +tree + index, no commit.) + +- [ ] **Step 2: Workspace build gate (build-atomicity proof)** + +Run: `cargo build --workspace 2>&1 | tail -3` +Expected: `Finished …` with **0 errors**. This proves +build-atomicity: the only compile-time consumer +(`design_schema_drift.rs` `include_str!`) was retargeted in Task 5 +to the now-existing `design/contracts/data-model.md`, so deleting +`docs/DESIGN.md` does not break the build. + +- [ ] **Step 3: Whole-suite GREEN incl. the design_index_pin spine** + +Run: `cargo test --workspace 2>&1 | tail -25` +Expected: all green. Specifically `design_index_pin`'s four tests +now ALL pass: `design_md_is_gone` (DESIGN.md deleted), +`every_index_link_resolves`, `every_contract_names_a_resolvable_ratifying_test`, +`contracts_carry_no_decision_record_prose`. `design_schema_drift`, +`docs_honesty_pin`, `effect_doc_honesty_pin`, `eq_float_noinstance`, +`show_no_instance_e2e` green (Tasks 5–6). + +- [ ] **Step 4: Acceptance-gate grep (recon-undercount countermeasure)** + +Run: `grep -rIn 'DESIGN\.md' . --exclude-dir=.git --exclude-dir=target | grep -vE '^\./docs/(journals/|specs/|plans/|roadmap\.md|WhatsNew\.md|journal-archive\.md)|^\./bench/orchestrator-stats/' | grep -v 'design-md-rolesplit' || echo "CLEAN: zero live DESIGN.md references"` +Expected: `CLEAN: zero live DESIGN.md references`. Any line printed +is a missed live reference (code/script/agent/comment) — retarget +it (Task 8 bucket) before the milestone is done. The exclude list +is exactly the spec acceptance-criterion-8 append-only history set; +the `design-md-rolesplit` filter drops this milestone's own +spec/plan/journal self-references. + +- [ ] **Step 5: architect_sweeps clean on the final tree** + +Run: `bash bench/architect_sweeps.sh; echo "exit=$?"` +Expected: `exit=0` (all five sweeps clean against the migrated +`design/` prose — the honesty discipline survived the move). + +--- + +## Self-review (planner Step 5) + +1. **Spec coverage:** Task 1 = §"Concrete code shapes" pin; Tasks + 2–4 = the Appendix relocation map (every `##`/`###`); Task 5 = + §3 test retargets; Task 6 = §2 diagnostics; Task 7 = §3 + architect_sweeps; Task 8 = §4 agent/contract rewrites + bucket + (e); Task 9 = §"Clean cut" + acceptance criteria 1–12. Every + spec section has a task. ✓ +2. **Placeholder scan:** no TBD/TODO/"similar to"/"add appropriate". + "Concatenate the Appendix-cited ranges" is an exact executable + instruction (the Appendix is the placeholder-free byte map). ✓ +3. **Type/path consistency:** `DATA_MODEL` const name consistent + (Task 5 def ↔ Task 5 anchor-test use); `design/contracts/<x>.md` + paths consistent across Tasks 2/5/6/8 and INDEX.md; ratifier + paths match the grounding-PASS'd INDEX. ✓ +4. **Step granularity:** each step is one file-group action / + one Run; the largest (Task 2 Step 3 = 14 files) is one mechanical + move-per-file pass, each file 2–5 min. ✓ +5. **No commit steps:** none. Task 9 uses `git rm` (stages a + deletion in the working tree; the Boss commits the iter). ✓ +6. **Pin/replacement contiguity:** Task 2 Step 4 keeps the two + `docs_honesty_pin.rs:70,72` phrases each on one physical line; + Task 6 Step 2 keeps `design/contracts/typeclasses.md` a + whitespace-free contiguous token across the `\`-continuation + (the recurring grep/line-wrap family — scrubbed). ✓ +7. **Compile-gate vs deferred-caller:** the only compile-time + coupling is `design_schema_drift.rs` `include_str!` ↔ + `docs/DESIGN.md` deletion. Task 5 retargets the `include_str!` + to the already-created `design/contracts/data-model.md` (Task 2) + **before** Task 9 deletes the file. No task's build/test gate + depends on a step a later task performs: Tasks 1–8 keep + `docs/DESIGN.md` on disk, so every intermediate `cargo build` is + green; Task 9's gate is the first (and only) that requires the + deletion, and by then its sole consumer was moved. The plan is + build-atomic by construction, not by a monster task. ✓ +8. **Verification filter strings resolve:** Task 1/2/3/5 use + `--test design_index_pin` / `--test design_schema_drift` / + `--test docs_honesty_pin` / `--test effect_doc_honesty_pin` + (files created/existing — resolve). Task 6 `--test + eq_float_noinstance --test show_no_instance_e2e` resolve + (existing). Task 9 Step 3 uses the **unfiltered** `cargo test + --workspace` (no filter to mis-resolve) + a named-test + expectation. Task 9 Step 4 grep is unfiltered (`-rIn`, no + `--include`) per the recon-undercount countermeasure with an + explicit `CLEAN:` sentinel so "nothing matched" cannot + masquerade. ✓ + +Plan is placeholder-free and build-atomic. Hand off to `implement`.