# design-ledger-formal-links.1 — Implementation Plan

> **Parent spec:** `docs/specs/0046-design-ledger-formal-links.md` (HEAD `42ff44a`; grounding-check PASS ×3 across two corpus-grounded amendments — clause-6 + cross-ref definition, then clause-5 fence-skip + closed convert-set enumeration)
>
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement` to run this plan. Steps use `- [ ]` checkboxes for tracking.

**Goal:** Ship the formal-cross-links milestone in one iteration: add `design_index_pin.rs` clause-5 (with `strip_fences` + RED-first synthetic vectors), convert the seven Acceptance-3 prose refs to file-relative Markdown links, apply disposition-(b) to the two PROSE_ROUNDTRIP homeless pointers, append the pin-safe positive-half sentence to `honesty-rule.md`, and ratify the clause-3 ∘ clause-5 composition invariant.

**Architecture:** Iteration `.1` is the whole milestone (the spec is small; same single-iter shape as the immediately-preceding rolesplit.1). Build-atomicity is not a concern — clause-5 is a new isolated test, prose edits do not affect compilation. The ordering concern is RED-first (Task 1's synthetic vector must demonstrably FAIL on an identity-stub of `strip_fences` before the implementer fixes it).

**Tech Stack:** `crates/ailang-core/tests/design_index_pin.rs` (new clause-5 + `//!`-header extension); seven `design/contracts/*.md` + two `design/models/*.md` prose edits.

---

**Files this plan creates or modifies:**

- Modify: `crates/ailang-core/tests/design_index_pin.rs:1-5` — extend `//!` header to name clause-5.
- Modify: `crates/ailang-core/tests/design_index_pin.rs:253-` — append clause-5 `design_body_links_are_durable_and_resolve` (with `strip_fences`, `targets`, `is_durable`, RED-first synthetic vectors, tree walk).
- Modify: `design/contracts/0005-float-semantics.md:69` — convert §"Prelude (built-in) classes" → `[Prelude (built-in) classes](typeclasses.md)`.
- Modify: `design/contracts/0005-float-semantics.md:100` — convert bare-path `design/contracts/0011-str-abi.md` → `[Str ABI](str-abi.md)`.
- Modify: `design/contracts/0003-embedding-abi.md:45` — convert "Frozen value layout" below → `[Frozen value layout](frozen-value-layout.md)`; drop stale "below".
- Modify: `design/contracts/0008-memory-model.md:44` — convert §"Data model" → `[Data model](data-model.md)`.
- Modify: `design/contracts/0008-memory-model.md:105-106` — convert §"Method dispatch" below → `[Method dispatch](typeclasses.md)`; drop stale "below" (target is in typeclasses.md, not in this file).
- Modify: `design/contracts/0010-scope-boundaries.md:48-49` — convert "Str ABI" → `[Str ABI](str-abi.md)`.
- Modify: `design/contracts/0010-scope-boundaries.md:88` — convert mixed-referent: `ailang-core::desugar` → source link, "Pipeline" → `../models/0003-pipeline.md`; drop stale "above".
- Modify: `design/models/0003-pipeline.md:60-61` — disposition-(b): drop the `(see docs/PROSE_ROUNDTRIP.md)` continuation line; preserve CLI prose.
- Modify: `design/models/0001-authoring-surface.md:180-181` — disposition-(b): drop the `see docs/PROSE_ROUNDTRIP.md for the six-step cycle and` clause; preserve the surrounding `ail merge-prose` sentence.
- Modify: `design/contracts/0007-honesty-rule.md:14` — append pin-safe positive-half paragraph after line 14, before blank line 15.
- Untouched (assert): `design/INDEX.md`; clauses 1–4 of `design_index_pin.rs`; `docs/journals/2026-05-19-design-decision-records.md`; `data-model.md` lines 38/66/79/206/226 (in-fence schema annotations — out of scope); `embedding-abi.md:48` (`docs_honesty_pin.rs:172`-pinned phrase — sits between the :45 conversion and the :51 stay-prose, must be byte-identical); `embedding-abi.md:51` (no-title-no-(see)-form — out of scope); every intra-file "above/below".

---

### Task 1: clause-5 hard gate (RED-first via synthetic vectors)

**Files:**
- Modify: `crates/ailang-core/tests/design_index_pin.rs:1-5` — extend `//!` header.
- Modify: `crates/ailang-core/tests/design_index_pin.rs:253-` — append clause-5.

- [ ] **Step 1: Append clause-5 with `strip_fences` STUBBED as identity (RED setup).**

Append to the END of the file (after the existing line 252 `}`):

```rust


#[test]
fn design_body_links_are_durable_and_resolve() {
    // clause 5 — every inline Markdown link in design/ body prose
    // (contracts/ + models/, NOT INDEX.md — the spine is the
    // structured registry tier resolved by clause-1, commitment 4)
    // resolves, relative to its CONTAINING file, to an existing
    // file under design/ or source (crates/** | runtime/**); never
    // under docs/; never an in-file #anchor. Fenced code blocks are
    // not scanned (a `](` inside ``` is literal text, not a link).
    // Composes with clause-3: a surviving cross-reference is a
    // resolving durable file-link or it is clause-3-forbidden
    // decision-record prose.

    // Fenced code (``` … ``` / ~~~ … ~~~) is literal text, not
    // Markdown — a `](` inside a fence is NOT a navigable link on
    // any renderer. Strip fenced regions before scanning (gate
    // correctness: prevents false extraction of code-example byte
    // sequences and of non-rendering in-fence links).
    fn strip_fences(md: &str) -> String {
        // INTENTIONALLY-STUBBED IDENTITY for RED-first (Step 2);
        // Step 3 replaces this body with the real toggle-on-fence impl.
        md.to_string()
    }

    // link target := first capture of  \]\(([^)]+)\)
    fn targets(md: &str) -> Vec<String> {
        let mut out = Vec::new();
        let b = md.as_bytes();
        let mut i = 0;
        while i + 1 < b.len() {
            if b[i] == b']' && b[i + 1] == b'(' {
                if let Some(end) = md[i + 2..].find(')') {
                    out.push(md[i + 2..i + 2 + end].trim().to_string());
                }
            }
            i += 1;
        }
        out
    }

    fn is_durable(repo_rel: &str) -> bool {
        repo_rel.starts_with("design/")
            || repo_rel.starts_with("crates/")
            || repo_rel.starts_with("runtime/")
    }

    // RED-first synthetic vectors (proves the gate bites before it
    // is pointed at the live tree).
    {
        let t = targets("see [x](../docs/specs/foo.md) and [y](#sec) and [z](./gone.md)");
        assert_eq!(t, vec!["../docs/specs/foo.md", "#sec", "./gone.md"]);
        assert!(!is_durable("docs/specs/foo.md"));   // durable-tier reject
        assert!("#sec".starts_with('#'));            // in-file anchor reject
        // fenced code is not a link surface (gate correctness)
        assert!(targets(&strip_fences("```\nsee [E](e.md)\n```\n")).is_empty());
        assert_eq!(targets(&strip_fences("[k](k.md)\n```\n[n](n.md)\n```")), vec!["k.md"]);
    }

    let bases = ["design/contracts", "design/models"];
    for base in bases {
        let dir = root().join(base);
        for entry in fs::read_dir(&dir).expect("design/ subdir exists") {
            let p = entry.unwrap().path();
            if p.extension().and_then(|e| e.to_str()) != Some("md") {
                continue;
            }
            let raw = fs::read_to_string(&p).unwrap();
            let fname = format!("{base}/{}", p.file_name().unwrap().to_string_lossy());
            for tgt in targets(&strip_fences(&raw)) {
                if tgt.starts_with("http://")
                    || tgt.starts_with("https://")
                    || tgt.starts_with("mailto:")
                {
                    continue;
                }
                let file_part = tgt.split('#').next().unwrap_or(&tgt);
                assert!(
                    !file_part.is_empty(),
                    "clause-5: in-file #anchor link {tgt:?} in {fname:?} — \
                     commitment 1 forbids fragments; split the file"
                );
                let resolved = p.parent().unwrap().join(file_part);
                let canon = resolved
                    .canonicalize()
                    .unwrap_or_else(|e| panic!(
                        "clause-5: link {tgt:?} in {fname:?} does not resolve: {e}"
                    ));
                let repo_rel = canon
                    .strip_prefix(root().canonicalize().unwrap())
                    .unwrap_or(&canon)
                    .to_string_lossy()
                    .replace('\\', "/");
                assert!(
                    is_durable(&repo_rel),
                    "clause-5: link {tgt:?} in {fname:?} targets the \
                     non-durable tier ({repo_rel:?}); commitment 2 \
                     permits design/ + crates/ + runtime/ only"
                );
            }
        }
    }
}
```

- [ ] **Step 2: Run the test to verify it FAILS (RED) on the identity-stubbed `strip_fences`.**

Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`

Expected: **FAIL** at the first fence assertion: `assert!(targets(&strip_fences("```\nsee [E](e.md)\n```\n")).is_empty())` — because identity-`strip_fences` returns the input unchanged, `targets` extracts `"e.md"`, the vec is non-empty, the assert panics. Output contains `assertion failed: targets(&strip_fences(...)).is_empty()`.

- [ ] **Step 3: Implement `strip_fences` correctly (toggle on fence lines) — GREEN.**

Replace ONLY the `strip_fences` function body (the `INTENTIONALLY-STUBBED IDENTITY` block) with:

```rust
    fn strip_fences(md: &str) -> String {
        let mut out = String::new();
        let mut in_fence = false;
        for line in md.lines() {
            let t = line.trim_start();
            if t.starts_with("```") || t.starts_with("~~~") {
                in_fence = !in_fence;
                continue; // drop the fence marker line itself
            }
            if !in_fence {
                out.push_str(line);
                out.push('\n');
            }
        }
        out
    }
```

(Drop the comment about INTENTIONALLY-STUBBED.)

- [ ] **Step 4: Run the test to verify it PASSES (GREEN).**

Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`

Expected: **PASS**. All four synthetic assertions hold; the tree walk runs over `design/contracts/*.md` + `design/models/*.md`, the current corpus has **zero** `](` markdown links in design/ body (bash-verified pre-plan), so `targets(&strip_fences(&raw))` returns empty for every file ⇒ no resolve/durable assertions trigger ⇒ vacuously GREEN.

- [ ] **Step 5: Extend the `//!` module header to name clause-5.**

Replace lines 1–5:

```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.
```

with:

```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, docs/DESIGN.md is
//! resurrected, or a design/ body cross-link fails to resolve into
//! the durable tier (clause-5; spec
//! docs/specs/0046-design-ledger-formal-links.md). Spec for
//! the split: docs/specs/0045-design-md-rolesplit.md.
```

- [ ] **Step 6: Run the whole suite to confirm nothing else regressed.**

Run: `cargo test -p ailang-core --test design_index_pin`

Expected: **5 passed; 0 failed; 0 ignored**. The four pre-existing clauses (`design_md_is_gone`, `every_index_link_resolves`, `every_contract_names_a_resolvable_ratifying_test`, `contracts_carry_no_decision_record_prose`) plus the new `design_body_links_are_durable_and_resolve`. End count: **5 tests, 5 passed**.

---

### Task 2: Convert the seven Acceptance-3 prose refs to file-relative Markdown links

**Files:**
- Modify: `design/contracts/0005-float-semantics.md:69, 100`
- Modify: `design/contracts/0003-embedding-abi.md:45`
- Modify: `design/contracts/0008-memory-model.md:44, 105-106`
- Modify: `design/contracts/0010-scope-boundaries.md:48, 88`

Verbatim before→after for each. Iter-provenance suffixes do not appear on any of these seven (they were all on the in-fence data-model refs, which are out of scope). Each edit is a single-line or two-adjacent-line text replacement; no surrounding line is perturbed.

- [ ] **Step 1: `float-semantics.md:69` — §"Prelude (built-in) classes" → `[Prelude (built-in) classes](typeclasses.md)`.**

Replace verbatim:

```
internally — see §"Prelude (built-in) classes" for the Show ship).
```

with:

```
internally — see [Prelude (built-in) classes](typeclasses.md) for the Show ship).
```

(`float-semantics.md` and `typeclasses.md` are siblings in `design/contracts/` ⇒ file-relative target is `typeclasses.md`. The label retains the original section title verbatim; Markdown handles balanced parens in labels.)

- [ ] **Step 2: `float-semantics.md:100` — bare path `design/contracts/0011-str-abi.md` → `[Str ABI](str-abi.md)`.**

Replace verbatim (line 100; line 101 unchanged):

```
a fresh heap-Str slab at the call site (see design/contracts/0011-str-abi.md
```

with:

```
a fresh heap-Str slab at the call site (see [Str ABI](str-abi.md)
```

- [ ] **Step 3: `embedding-abi.md:45` — "Frozen value layout" below → `[Frozen value layout](frozen-value-layout.md)` (drop stale "below").**

Replace verbatim line 45:

```
layout is **frozen as of M3** (see "Frozen value layout" below); the
```

with:

```
layout is **frozen as of M3** (see [Frozen value layout](frozen-value-layout.md)); the
```

(Lines 44 and 46–52 untouched. **Critical:** line 48 carries the `docs_honesty_pin.rs:172`-pinned phrase `Export parameters are written **bare**: a scalar type carries no` — must remain byte-identical. Line 51 `the frozen value layout below specifies` is also untouched: §Scope out-of-scope rule — no quoted title, no `(see …)` form.)

- [ ] **Step 4: `memory-model.md:44` — §"Data model" → `[Data model](data-model.md)`.**

Replace verbatim line 44:

```
parallel to `params` and `ret` (see §"Data model" for the JSON
```

with:

```
parallel to `params` and `ret` (see [Data model](data-model.md) for the JSON
```

- [ ] **Step 5: `memory-model.md:105-106` — §"Method dispatch" below → `[Method dispatch](typeclasses.md)` (drop stale "below"; target heading lives in typeclasses.md:227, not in memory-model.md — the genuine cross-file stale-direction wart).**

Replace verbatim lines 105–106:

```
Method dispatch is type-driven post-mq.3 (see §"Method dispatch"
below): synth resolves a `Term::Var { name: "show" }` by consulting
```

with:

```
Method dispatch is type-driven post-mq.3 (see [Method dispatch](typeclasses.md)):
synth resolves a `Term::Var { name: "show" }` by consulting
```

(Line 107 onward unchanged. The pre-existing `post-mq.3` iter-token is *not* on the converted ref itself and is *not* a clause-3 trip under the current implementation — pre-existing, not introduced by this milestone, left as-is.)

- [ ] **Step 6: `scope-boundaries.md:48-49` — "Str ABI" → `[Str ABI](str-abi.md)`.**

Replace verbatim line 48 (line 49 unchanged):

```
  call time and return it with `ret_mode: Own`; see "Str ABI" for
```

with:

```
  call time and return it with `ret_mode: Own`; see [Str ABI](str-abi.md) for
```

- [ ] **Step 7: `scope-boundaries.md:88` — mixed-referent split (source link + Pipeline cross-file link, drop stale "above").**

Replace verbatim line 88:

```
  typecheck/codegen — see `ailang-core::desugar` and Pipeline above.
```

with:

```
  typecheck/codegen — see [desugar](../../crates/ailang-core/src/desugar.rs) and [Pipeline](../models/0003-pipeline.md).
```

(`scope-boundaries.md` is in `design/contracts/`; `../../` reaches the repo root; `../models/0003-pipeline.md` reaches `design/models/0003-pipeline.md`. Both targets verified existing pre-plan. "above" dropped: stale-direction (Pipeline lives in a different file, not above in this one).)

- [ ] **Step 8: Run clause-5 to confirm all seven new links resolve into the durable tier — still GREEN.**

Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`

Expected: **PASS**. Each of the seven introduced `](path)` resolves from its containing file's directory to a real `design/`-or-source file (`typeclasses.md`, `str-abi.md`, `frozen-value-layout.md`, `data-model.md`, `typeclasses.md`, `str-abi.md`, `../../crates/ailang-core/src/desugar.rs` + `../models/0003-pipeline.md`). All targets are in the durable tier (`design/` or `crates/`). No `#fragment`. No in-fence link.

---

### Task 3: Disposition-(b) on the two PROSE_ROUNDTRIP homeless refs

**Files:**
- Modify: `design/models/0003-pipeline.md:60-61` (in-fence CLI block; pointer removed regardless — the in-fence carve-out preserves *schema documentation* annotations, not cross-tier pointers).
- Modify: `design/models/0001-authoring-surface.md:178-181`.

- [ ] **Step 1: `pipeline.md:60-61` — drop the `(see docs/PROSE_ROUNDTRIP.md)` continuation line.**

Replace verbatim lines 60–61:

```
                                         — compose the LLM-mediator prompt for the prose round-trip
                                           (see docs/PROSE_ROUNDTRIP.md)
```

with:

```
                                         — compose the LLM-mediator prompt for the prose round-trip
```

(One line removed. The CLI command row's behavioural prose — "compose the LLM-mediator prompt for the prose round-trip" — is preserved verbatim; the ledger now describes the behaviour without pointing into the non-durable tier.)

- [ ] **Step 2: `authoring-surface.md:178-181` — drop the cross-tier pointer clause, preserve the surrounding `ail merge-prose` sentence.**

Replace verbatim lines 178–181:

```
by construction (Decision 6 constraint 2); form (B) deliberately is
not. Re-integrating prose edits requires an external LLM mediator,
not a compiler pass — see `docs/PROSE_ROUNDTRIP.md` for the
six-step cycle and the prompt template `ail merge-prose` composes.
```

with:

```
by construction (Decision 6 constraint 2); form (B) deliberately is
not. Re-integrating prose edits requires an external LLM mediator,
not a compiler pass — the prompt template `ail merge-prose`
composes the six-step cycle.
```

(The cross-tier pointer `see \`docs/PROSE_ROUNDTRIP.md\` for the` is removed; the substantive present-tense content — that `ail merge-prose` composes the six-step cycle — is preserved and slightly rearranged into one sentence on the ledger side, no pointer needed.)

- [ ] **Step 3: Run clause-5 again — confirm no new failures (Task 3 introduces no `](` links; clause-5 stays GREEN).**

Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`

Expected: **PASS** (Task 3 only *removes* prose; introduces no link).

---

### Task 4: honesty-rule.md positive-half sentence (pin-safe)

**Files:**
- Modify: `design/contracts/0007-honesty-rule.md:14` — append a new paragraph after line 14, before blank line 15.

- [ ] **Step 1: Insert the positive-half paragraph between line 14 and line 15.**

The two `docs_honesty_pin.rs`-pinned phrases sit at line 14 (`the honesty rule it holds itself to`) and line 19 (`whether the document asserts something exists, works, or changed that does not`). The pin uses whitespace-normalised `.contains()` over the whole file — any insertion that does not *split* either phrase is safe. Inserting a new paragraph after L14 (before the existing blank L15) leaves both phrases byte-identical on their physical lines.

Replace verbatim:

```
  the honesty rule it holds itself to.

The single legitimate exception is a present-tense reserved or
```

with:

```
  the honesty rule it holds itself to.

A cross-reference that does belong stays: it is a formal,
file-relative Markdown link into the durable tier (`design/` or
source), enforced by `design_index_pin.rs` clause-5. A reference
that cannot be expressed as such a link is, by that fact, the
history-or-rationale prose the rule above removes.

The single legitimate exception is a present-tense reserved or
```

(A new paragraph block of 5 wrapped lines is inserted between L14's end and the existing L16 `The single legitimate exception …`. The original blank L15 is preserved between L14 and the new paragraph; a second blank is added between the new paragraph and the previous L16. Net: file grows by ~6 lines; both pinned phrases remain on their own physical lines verbatim.)

The new paragraph contains no clause-3-PHRASES tripwire (verified against the PHRASES list in `design_index_pin.rs:173-179`: none of `we rejected` / `an earlier draft` / `was retired` / etc. appears) and no Sweep-1 anchor (no `Iter <n>` / `Family <n>` / `pre-<n>` / `**Status:` / `21.g` / `<n> sketch` / date in line-prefix-before-`/`). The phrase `the rule above` is intra-file directional prose, explicitly allowed by the milestone's §Scope out-of-scope rule (intra-file directional ≠ cross-reference). The naming of `design_index_pin.rs` is a nominal "ratified-by"-style mention, NOT a navigational pointer (per §Scope) — stays as backtick code, not a link, consistent with line 21's pre-existing `Ratified by: \`crates/ailang-core/tests/docs_honesty_pin.rs\`.`.

- [ ] **Step 2: Run the docs-honesty pin to confirm both pinned phrases still match (whitespace-normalised).**

Run: `cargo test -p ailang-core --test docs_honesty_pin design_md_present_tense_anchors_present`

Expected: **PASS** (both `.contains()` assertions hold; the additive paragraph splits neither phrase).

- [ ] **Step 3: Run clause-3 to confirm no decision-record-prose tripwire was introduced.**

Run: `cargo test -p ailang-core --test design_index_pin contracts_carry_no_decision_record_prose`

Expected: **PASS** (the new paragraph contains no clause-3 PHRASE substring and no Sweep-1 anchor on any physical line).

- [ ] **Step 4: Run clause-5 — confirm the new paragraph introduces no `](` link (it doesn't — the only path mention is `design_index_pin.rs` inside backticks, no markdown link).**

Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`

Expected: **PASS**.

---

### Task 5: Whole-suite gate + composition-invariant assertion

**Files:**
- None modified in this task — final verification only. The composition-invariant note is recorded in the per-iter journal (orchestrator's Boss-commit step), not as a code change.

- [ ] **Step 1: Run the whole workspace test suite.**

Run: `cargo test --workspace`

Expected: **all GREEN**. Pre-milestone baseline (HEAD `42ff44a`) is 646 passed / 0 failed; post-milestone count is **647 passed** (the +1 from clause-5). Zero failures, zero ignored beyond the standing baseline. (If the count is anything other than 647, investigate immediately — a regression masked as a filter mismatch would be the hazard.)

- [ ] **Step 2: Independently grep design/contracts + design/models for any surviving `docs/` link target (Acceptance 4 spot-check beyond clause-5).**

Run:
```
grep -rnE '\]\(([^)]*docs/[^)]*)\)' design/contracts design/models
```

Expected: **zero matches** (no `](…docs/…)` link in design/ body — Acceptance 4).

- [ ] **Step 3: Independently grep design/contracts + design/models for any `#fragment` link.**

Run:
```
grep -rnE '\]\(#' design/contracts design/models
```

Expected: **zero matches** (no in-file anchor link — commitment 1, Acceptance 4).

- [ ] **Step 4: Count `](` links introduced in design/ body (sanity — should match the convert-set: 7 from Task 2 + 1 from Task 2 Step 7's source-link sub-target = 8 navigational links).**

Run:
```
grep -rhoE '\]\([^)]+\)' design/contracts design/models | grep -vE '\]\((https?://|mailto:)' | wc -l
```

Expected: **8** (the 7 Task-2 conversions including the source-link, plus the Pipeline link — i.e. seven prose refs, but scope-boundaries:88 carries TWO links in one conversion, so total `](` token count = 7 refs + 1 second link = 8). No fewer (would mean a conversion was missed); no more (would mean an unintended link was introduced).

- [ ] **Step 5: Verify the in-fence data-model refs are still informal (out-of-scope assertion).**

Run:
```
grep -nE 'see §"Class names"|see §"Type::Con name scoping"|see §"Embedding ABI"' design/contracts/0002-data-model.md
```

Expected: **5 lines** (the lines 38, 66, 79, 206, 226) — confirming the in-fence schema annotations were untouched (out of scope per §Scope; clause-5 skips fences regardless).

- [ ] **Step 6: Confirm INDEX.md and the decision-records journal are byte-unchanged vs HEAD (commitment 4 / Acceptance 5).**

Run:
```
git diff --quiet HEAD -- design/INDEX.md && echo INDEX.md unchanged
git diff --quiet HEAD -- docs/journals/2026-05-19-design-decision-records.md && echo journal unchanged
```

Expected: both `unchanged` lines printed (each `git diff --quiet` exits 0 ⇒ the `&& echo` fires).

- [ ] **Step 7: Confirm clauses 1–4 of `design_index_pin.rs` are byte-unchanged (only the `//!` header and the appended clause-5 changed).**

Run:
```
git diff HEAD -- crates/ailang-core/tests/design_index_pin.rs | grep -cE '^-[^-]'
```

Expected: **`1`** — exactly one removed line (the original `//! is resurrected. Spec: docs/specs/0045-design-md-rolesplit.md.` being replaced by the extended four-line `//!` block). Any value > 1 means content was removed from clauses 1–4 — STOP and inspect.
