Files
AILang/docs/plans/0095-design-ledger-formal-links.1.md
T
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

25 KiB
Raw Blame History

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



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

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

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

//! 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.


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

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

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

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 theis removed; the substantive present-tense content — thatail 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 14 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 14 — STOP and inspect.