diff --git a/docs/plans/design-ledger-formal-links.1.md b/docs/plans/design-ledger-formal-links.1.md new file mode 100644 index 0000000..d450358 --- /dev/null +++ b/docs/plans/design-ledger-formal-links.1.md @@ -0,0 +1,518 @@ +# design-ledger-formal-links.1 — Implementation Plan + +> **Parent spec:** `docs/specs/2026-05-19-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/float-semantics.md:69` — convert §"Prelude (built-in) classes" → `[Prelude (built-in) classes](typeclasses.md)`. +- Modify: `design/contracts/float-semantics.md:100` — convert bare-path `design/contracts/str-abi.md` → `[Str ABI](str-abi.md)`. +- Modify: `design/contracts/embedding-abi.md:45` — convert "Frozen value layout" below → `[Frozen value layout](frozen-value-layout.md)`; drop stale "below". +- Modify: `design/contracts/memory-model.md:44` — convert §"Data model" → `[Data model](data-model.md)`. +- Modify: `design/contracts/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/scope-boundaries.md:48-49` — convert "Str ABI" → `[Str ABI](str-abi.md)`. +- Modify: `design/contracts/scope-boundaries.md:88` — convert mixed-referent: `ailang-core::desugar` → source link, "Pipeline" → `../models/pipeline.md`; drop stale "above". +- Modify: `design/models/pipeline.md:60-61` — disposition-(b): drop the `(see docs/PROSE_ROUNDTRIP.md)` continuation line; preserve CLI prose. +- Modify: `design/models/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/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 { + 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/2026-05-19-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/2026-05-19-design-ledger-formal-links.md). Spec for +//! the split: docs/specs/2026-05-19-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/float-semantics.md:69, 100` +- Modify: `design/contracts/embedding-abi.md:45` +- Modify: `design/contracts/memory-model.md:44, 105-106` +- Modify: `design/contracts/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/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/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/pipeline.md). +``` + +(`scope-boundaries.md` is in `design/contracts/`; `../../` reaches the repo root; `../models/pipeline.md` reaches `design/models/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/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/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/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/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 ` / `Family ` / `pre-` / `**Status:` / `21.g` / ` 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/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/2026-05-19-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.