# DESIGN.md → `design/` role-split — Implementation Plan (iter 1, the whole milestone)

> **Parent spec:** `docs/specs/0045-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/0002-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/0045-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::<Vec<_>>().join(" ")
}

/// Parse the two pipe-tables out of design/INDEX.md.
/// Returns (contracts, models) as rows of trimmed cells.
fn index_tables() -> (Vec<Vec<String>>, Vec<Vec<String>>) {
    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<String> = 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 `# <Title>` and ends with a line
`Ratified by: <the INDEX ratifying-test path for this contract>.`

- [ ] **Step 4: Write the rewritten `design/contracts/0007-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/0002-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/0002-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/0004-rc-uniqueness.md`, `design/contracts/0005-float-semantics.md`,
  `design/contracts/0013-typeclasses.md`, `design/models/0002-effects.md`,
  `design/models/0001-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/0007-honesty-rule.md`;
  the Form-B placeholder anchor (:90 area) → `design/models/0001-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/0003-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/0002-effects.md"),
+               read("design/contracts/0010-scope-boundaries.md")].join("\n"));
```

(Decision 3 effect prose → `models/0002-effects.md`; the
`"the built-in IO and Diverge ops"` absent-pin's home
`## What is not (yet) supported` → `contracts/0010-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/0002-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/0005-float-semantics.md.",
```

- [ ] **Step 2: Retarget the Show-branch diagnostic (contiguity-safe)**

`crates/ailang-check/src/lib.rs` Show branch. The path
`design/contracts/0013-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/0013-typeclasses.md. \
+                     User types declare their own \
```

(Runtime string becomes `…the prelude; see design/contracts/0013-typeclasses.md. User types declare their own …` — the path is one contiguous run; the E2E `contains("design/contracts/0013-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/0005-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/0013-typeclasses.md"),
+        "expected design/contracts/0013-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/0004-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/0004-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/0006-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/0002-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/0009-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/0013-typeclasses.md`), `e2e.rs:2843` (→
`design/contracts/0011-str-abi.md`), `eq_ord_e2e.rs:89,110` (→ mangling
source-link), `embed/record_roundtrip.c:2`, `embed/tick_roundtrip.c:6`
(→ `design/contracts/0006-frozen-value-layout.md`);
`examples/fieldtest/floats_3_safe_division.ail:19`,
`floats_4_float_to_str_reach.ail:6` (→
`design/contracts/0005-float-semantics.md`); `runtime/rc.c:41` (→
`design/contracts/0006-frozen-value-layout.md`), `runtime/str.c:172,198`
(→ `design/contracts/0011-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/0002-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/0013-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/0002-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`.
