Files
AILang/docs/plans/0093-design-md-rolesplit.1.md
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

34 KiB
Raw Permalink Blame History

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:AB" 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

//! 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:618 (## Goal), :1955 (## Project ecosystem intro), :8392 (## 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:93164
  • authoring-surface.md ← :230240 + :268297 + :298331 (the three contract ###s of Decision 6 per Appendix; :241243 and all ### Why…/### …does not do/### Implementation outline/### Form refinements/### Empirical addendum go to Task 4)
  • roundtrip-invariant.md ← :21132208 (whole block, 3 ###s kept)
  • memory-model.md ← :11231150 + :11511312 + :13131351 + :13941412
    • :14131509 (the five binding ###s per Appendix)
  • data-model.md ← :23772615 (whole ## Data model + 4 ###s)
  • float-semantics.md ← :27002838 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 ← :15781639 + :17531823 + :18241855 + :18561917
    • :19181964 + :20012042 (the six contract ###s of Decision 11 per Appendix)
  • tail-calls.md ← :731787 (whole ## Decision 8)
  • frozen-value-layout.md ← :23342376
  • honesty-rule.mdrewritten (next step — do NOT raw-move :5682)
  • embedding-abi.md ← :22662333
  • str-abi.md ← :20432088 (### Heap-Str primitives) + the **Str ABI.** paragraph lifted from :2802
  • scope-boundaries.md ← :28393020 (## What is not (yet) supported)
  • verification.md ← :26872699

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:5682. 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):

# 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 ← :788852 + :853913 + :914977 + :10151122

    • :13781393 (Decision 9/10 narrative ###s per Appendix)
  • typeclasses.md ← :15361577 + :16401752 (Decision 11 narrative)

  • effects.md ← :190204 (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 ← :332471 + :590674 (### Candidate notations + ### Form (B) — the docs_honesty_pin.rs Form-B placeholder present-anchor retargets here in Task 5)

  • pipeline.md ← :26162662 (## Pipeline) + :26632686 (## 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: :165176, :177189, :205213, :214229, :241243, :245267, :472486, :487519, :520536, :537589, :675730, :9781014, :13521377, :15101519, :15201535, :19652000, :20892112, plus any genuine "why" sentence extracted from the source-link ##s (## Mangling scheme :22092218, ## Convention: qualified cross-module references :22192236, ## Env construction :22372265 — per Appendix, behaviour sentences are dropped as code-is-SoT; only rationale sentences move here).

Header:

# 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 2837 and data_model_section_is_bounded 411430; 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:

- 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 2837) and fn data_model_section_is_bounded() (lines 411430). 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 (1519) and norm() (2426) 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 (:104137): 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 (:94101): reads docs/PROSE_ROUNDTRIP.mdunchanged.

  • Step 3: Retarget effect_doc_honesty_pin.rs:21

design_md_effect_prose_is_true (:1934), line 21:

- 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) supportedcontracts/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 23; 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 ~872876, Show branch ~885893)

  • 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:

-                     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:

-                     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 4142:

-        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 4748:

-        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

- DESIGN="docs/DESIGN.md"
+ INDEX="design/INDEX.md"
+ DESIGN_GLOB="design/contracts design/models"

Guard (2528):

- 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):

-     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 (:199212) → 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 56).

  • 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 24 = 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 112. 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 25 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 18 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.