diff --git a/bench/orchestrator-stats/2026-05-19-iter-design-ledger-formal-links.1.json b/bench/orchestrator-stats/2026-05-19-iter-design-ledger-formal-links.1.json new file mode 100644 index 0000000..338f64f --- /dev/null +++ b/bench/orchestrator-stats/2026-05-19-iter-design-ledger-formal-links.1.json @@ -0,0 +1,12 @@ +{ + "iter_id": "design-ledger-formal-links.1", + "date": "2026-05-19", + "mode": "standard", + "outcome": "DONE", + "tasks_total": 5, + "tasks_completed": 5, + "reloops_per_task": { "1": 0, "2": 0, "3": 0, "4": 0, "5": 0 }, + "review_loops_spec": 0, + "review_loops_quality": 0, + "blocked_reason": null +} diff --git a/crates/ailang-core/tests/design_index_pin.rs b/crates/ailang-core/tests/design_index_pin.rs index 7044351..3bbbad1 100644 --- a/crates/ailang-core/tests/design_index_pin.rs +++ b/crates/ailang-core/tests/design_index_pin.rs @@ -1,8 +1,11 @@ //! 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. +//! 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. use std::fs; use std::path::PathBuf; @@ -250,3 +253,118 @@ fn contracts_carry_no_decision_record_prose() { } } } + + +#[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 { + 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 + } + + // 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" + ); + } + } + } +} diff --git a/design/contracts/embedding-abi.md b/design/contracts/embedding-abi.md index 21f8ea7..332e5b9 100644 --- a/design/contracts/embedding-abi.md +++ b/design/contracts/embedding-abi.md @@ -42,7 +42,7 @@ exact. The swarm artefact is data-race-free, sanitiser-verified. The staticlib swarm artefact is **RC-only**: `ail build --emit=staticlib` rejects `--alloc=gc`/`--alloc=bump` (the shared Boehm collector is not swarm-safe). The value/record -layout is **frozen as of M3** (see "Frozen value layout" below); the +layout is **frozen as of M3** (see [Frozen value layout](frozen-value-layout.md)); the ctx-threaded C signature is the M2 shape. Export parameters are written **bare**: a scalar type carries no diff --git a/design/contracts/float-semantics.md b/design/contracts/float-semantics.md index b9bce20..e1dd54f 100644 --- a/design/contracts/float-semantics.md +++ b/design/contracts/float-semantics.md @@ -66,7 +66,7 @@ no `f32` variant. The runtime / codegen contract: The same libc-`%g` rendering applies to `show 1.5` / `show nan` / `show inf` via `instance Show Float` (which calls `float_to_str` -internally — see §"Prelude (built-in) classes" for the Show ship). +internally — see [Prelude (built-in) classes](typeclasses.md) for the Show ship). The NaN-spelling caveat above is observable via `do print x` for Float-typed `x`; the rendering is libc-version-dependent and target-libc-specific. AILang does NOT canonicalise Float textual @@ -97,7 +97,7 @@ LLM-author wants. Use ordering operators (`<`, `>`, ...) and `float_to_str` (Float → Str) and `int_to_str` (Int → Str) are fully wired through checker, codegen, and runtime. Both allocate -a fresh heap-Str slab at the call site (see design/contracts/str-abi.md +a fresh heap-Str slab at the call site (see [Str ABI](str-abi.md) for the dual realisation) and carry `ret_mode: Own` so the let-binder for the call result is RC-tracked and the slab is freed at scope close. diff --git a/design/contracts/honesty-rule.md b/design/contracts/honesty-rule.md index 34480c4..92f5467 100644 --- a/design/contracts/honesty-rule.md +++ b/design/contracts/honesty-rule.md @@ -13,6 +13,12 @@ Two things never belong in a contract or model file: Decision-records are journal content, not ledger content; this is 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 deliberately-excluded claim that is explicitly and correctly labelled. The discriminator is not whether a sentence mentions past diff --git a/design/contracts/memory-model.md b/design/contracts/memory-model.md index fc647fb..c1e7fa8 100644 --- a/design/contracts/memory-model.md +++ b/design/contracts/memory-model.md @@ -41,7 +41,7 @@ The form-A surface for fn signatures gains mode wrappers: Internally, this is *not* a new `Type` variant. Modes are metadata on `Type::Fn` — `paramModes` and `retMode` fields run -parallel to `params` and `ret` (see §"Data model" for the JSON +parallel to `params` and `ret` (see [Data model](data-model.md) for the JSON schema). The substantive reasons for per-position metadata over a `Type::Borrow` / `Type::Own` variant approach: @@ -102,8 +102,8 @@ Three schema fields carry class references in this form: `ClassDef.name` itself stays bare (defining-site context, like `TypeDef.name`). -Method dispatch is type-driven post-mq.3 (see §"Method dispatch" -below): synth resolves a `Term::Var { name: "show" }` by consulting +Method dispatch is type-driven post-mq.3 (see [Method dispatch](typeclasses.md)): +synth resolves a `Term::Var { name: "show" }` by consulting the workspace's method-to-candidate-class index, filtering by argument type (concrete) or by declared constraint (rigid-var), and routing the residual through the registry at fn-body-end discharge. diff --git a/design/contracts/scope-boundaries.md b/design/contracts/scope-boundaries.md index 8d031ca..5acce24 100644 --- a/design/contracts/scope-boundaries.md +++ b/design/contracts/scope-boundaries.md @@ -45,7 +45,7 @@ What **is** supported (and used as the smoke test for the pipeline): `float_to_int_truncate : (Float) -> Int` (saturating, NaN → 0), `float_to_str : (Float) -> Str`, `int_to_str : (Int) -> Str` (both allocate a heap-Str slab at - call time and return it with `ret_mode: Own`; see "Str ABI" for + call time and return it with `ret_mode: Own`; see [Str ABI](str-abi.md) for the dual heap-/static-Str realisation); inspection `is_nan : (Float) -> Bool` (LLVM `fcmp uno`); Float bit-pattern constants `nan : Float`, @@ -85,7 +85,7 @@ What **is** supported (and used as the smoke test for the pipeline): another `Ctor`, or a literal. The desugar pass flattens nested Ctor patterns into a chain of let + match and rewrites every `Pattern::Lit` (top-level or sub-) to a `Term::If` on `==` before - typecheck/codegen — see `ailang-core::desugar` and Pipeline above. + typecheck/codegen — see [desugar](../../crates/ailang-core/src/desugar.rs) and [Pipeline](../models/pipeline.md). - Literal patterns at top level and inside Ctor sub-patterns (via desugar). `(pat-lit 0)` and `(pat-ctor Cons (pat-lit 0) _)` both parse and lower; the rewrite is to `Term::If { cond = (== sv lit) }`, diff --git a/design/models/authoring-surface.md b/design/models/authoring-surface.md index ac37979..f8a9bba 100644 --- a/design/models/authoring-surface.md +++ b/design/models/authoring-surface.md @@ -177,8 +177,8 @@ human readers. Form (B) inverts the trade-offs: Critically, **form (B) has no parser**. Form (A) is round-trippable 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. +not a compiler pass — the prompt template `ail merge-prose` +composes the six-step cycle. Form (B) does not weaken any Decision 6 invariant: diff --git a/design/models/pipeline.md b/design/models/pipeline.md index cb5511b..e5f4dfc 100644 --- a/design/models/pipeline.md +++ b/design/models/pipeline.md @@ -58,7 +58,6 @@ ail parse — form-A text → canonical JSON-AST ail prose — JSON-AST → form-B (lossy human prose, no parser) ail merge-prose — compose the LLM-mediator prompt for the prose round-trip - (see docs/PROSE_ROUNDTRIP.md) ail deps — list cross-module references ail diff — content-addressed def-level diff ail workspace — list all modules transitively reachable from entry diff --git a/docs/journals/2026-05-19-iter-design-ledger-formal-links.1.md b/docs/journals/2026-05-19-iter-design-ledger-formal-links.1.md new file mode 100644 index 0000000..b9da768 --- /dev/null +++ b/docs/journals/2026-05-19-iter-design-ledger-formal-links.1.md @@ -0,0 +1,109 @@ +# iter design-ledger-formal-links.1 — formal cross-links + hard-gate clause-5 + +**Date:** 2026-05-19 +**Started from:** 36599be +**Status:** DONE +**Tasks completed:** 5 of 5 + +## Summary + +Positive-half completion of the DESIGN.md → design/ split: `design/` +body cross-references are now formal, file-relative Markdown links +into the durable tier (`design/` or source), and a new in-tree hard +gate (`design_index_pin.rs` clause-5, +`design_body_links_are_durable_and_resolve`) walks every +`design/contracts/*.md` + `design/models/*.md`, strips fenced code, +extracts every `](path)`, and asserts the target resolves +file-relative to a real file under `design/`-or-`crates/`-or- +`runtime/` — never `docs/`, never an in-file `#anchor`. RED-first was +established via four embedded synthetic-vector asserts on an +identity-stubbed `strip_fences`; replacing the stub with the real +toggle-on-fence impl turns the test GREEN. Seven prose cross-refs +were converted to `[label](path)` links (8 link tokens total — the +mixed-referent `scope-boundaries.md:88` split carries two), the two +homeless `(see docs/PROSE_ROUNDTRIP.md)` pointers were resolved via +clause-6 disposition (b) (pointer removed, surrounding behavioural +prose preserved), and a pin-safe positive-half paragraph was inserted +into `honesty-rule.md` after the existing clause-3 paragraph, +explicitly naming `design_index_pin.rs` clause-5 as the gate and +keeping both `docs_honesty_pin.rs`-pinned phrases byte-identical. +`design/INDEX.md` and the decision-records journal are byte-unchanged +(commitment 4 / Acceptance 5). Whole-workspace tests: **647 passed / +0 failed** (+1 from the pre-milestone 646: the new clause-5). + +**Composition-invariant assertion** (Acceptance 5; spec +testing-strategy §Composition with clause-3): on the post-milestone +tree, for every `design/contracts/*.md`, clause-3 GREEN ∧ clause-5 +GREEN — independently verified by running both tests in the same +suite. Every cross-reference in a contract is therefore either a +resolving durable file-link **or** it is gone (clause-3 would +otherwise have rejected it as decision-record prose). The two +clauses now form the complete invariant the split promised. + +## Per-task notes + +- iter design-ledger-formal-links.1.1: clause-5 hard gate added to + `crates/ailang-core/tests/design_index_pin.rs` (RED-first via + identity-stubbed `strip_fences`; GREEN after real toggle-on-fence + impl; `//!` header extended one sentence; 5/5 tests pass). +- iter design-ledger-formal-links.1.2: seven prose cross-refs + converted to file-relative `[label](path)` Markdown links + (`float-semantics.md:69, :100`; `embedding-abi.md:45`; + `memory-model.md:44, :105-106`; `scope-boundaries.md:48, :88` — + the last splits into two links, source-link + cross-models link). +- iter design-ledger-formal-links.1.3: clause-6 disposition (b) + applied to the two homeless `(see docs/PROSE_ROUNDTRIP.md)` + pointers (`pipeline.md:60-61`, `authoring-surface.md:178-181`) — + pointer removed, present-tense behavioural prose about + `ail merge-prose` and the six-step cycle preserved. +- iter design-ledger-formal-links.1.4: positive-half paragraph + inserted into `honesty-rule.md` between lines 14 and 15 — names + formal links into the durable tier as the surviving + cross-reference form and `design_index_pin.rs` clause-5 as the + gate. Pin-safe (both `docs_honesty_pin.rs`-pinned phrases stay + byte-identical, no clause-3 PHRASE or Sweep-1 anchor introduced). +- iter design-ledger-formal-links.1.5: whole-workspace gate + + acceptance-criteria spot-checks (zero `docs/`-targeting links, + zero `#fragment` links, exactly 8 `](path)` tokens introduced, 5 + in-fence refs untouched, INDEX + decision-records journal + byte-unchanged). + +## Concerns + +- Task 5 Step 7 planner predicted-count discrepancy: the plan + expected exactly **1** removed line in the test-file diff + (interpreting Step 5's `//!` header replacement as a single-line + edit), but the actual diff shows **2** removed lines because Step + 5's verbatim before→after rewords lines 4 *and* 5 of the original + `//!` block (5-line block → 8-line block). Clauses 1–4 source + content (line range 7+ of the file) is byte-unchanged, which is + the load-bearing Acceptance 1; the 2-vs-1 mismatch is purely in + the planner's predicted byte-count of the `//!` header rewording + it itself mandated. Plan-pseudo-vs-reality class (cf. + feedback_plan_pseudo_vs_reality), Step-5 verbatim taking precedence + over Step-7 predicted-count. + +## Known debt + +- (none — the milestone closes the positive-half side of the + contract/decision-record split; the spec's named acceptance set is + exhaustive and closed; intra-file directional prose remains + explicitly out of scope, with file-split as the documented future + remedy under commitment 1 if a file ever grows enough that + "above"/"below" loses clarity). + +## Files touched + +- `crates/ailang-core/tests/design_index_pin.rs` (header + appended + clause-5) +- `design/contracts/float-semantics.md` (lines 69, 100) +- `design/contracts/embedding-abi.md` (line 45) +- `design/contracts/memory-model.md` (lines 44, 105-106) +- `design/contracts/scope-boundaries.md` (lines 48, 88) +- `design/contracts/honesty-rule.md` (new paragraph after L14) +- `design/models/pipeline.md` (drop line 61) +- `design/models/authoring-surface.md` (lines 178-181 reworded) + +## Stats + +`bench/orchestrator-stats/2026-05-19-iter-design-ledger-formal-links.1.json` diff --git a/docs/journals/INDEX.md b/docs/journals/INDEX.md index de280c5..b0b99d7 100644 --- a/docs/journals/INDEX.md +++ b/docs/journals/INDEX.md @@ -118,3 +118,4 @@ - 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. → 2026-05-19-design-decision-records.md - 2026-05-19 — audit design-md-rolesplit (milestone close): DRIFT (one tidy iteration) + bench causally-exonerated (baseline pristine). Architect drift_found, relocation byte-faithful (MIXED Decisions / dual-link / source-link / rewritten honesty-rule all match the spec Appendix vs deeffb1; build-atomicity holds; design_index_pin 4/4; honesty-rule.md correct + pins retargeted; agent contracts coherent) — one [medium] spirit-finding: faithfully-migrated decision-record/history prose in design/contracts/{typeclasses,str-abi,scope-boundaries}.md DODGES the 6 literal clause-3 markers (case+wording variance) but IS the relitigation content the split's spirit sends to journals; plus [low] float-semantics.md stale-direction "see Str ABI below". Both routed items adjudicated strippable-doc-archaeology-to-TIDY-not-ratify (the str-abi.md:23 advisory Sweep-1 exit-1 correctly diagnoses real pre-existing history residue, byte-identical DESIGN.md@deeffb1:2062-2065, faithfully migrated — not split-introduced). Bencher: NO-ratify, causally-exonerated DECISIVE on byte-evidence — emitted IR AND final -O2 binaries byte-identical 176821c vs pre-milestone dd5b183 for all 6 check.py firings (sha256+cmp), all ~11 changed code/runtime files audited comment/docstring/diagnostic-string-only, zero IR-path change; the firings are tracked-P2 (*.bump_s staleness, *_at_rc.max_us -n5 tail jitter, *.gc_s Boehm-scan variance — NOT the M5 gc_rss trio); compile_check 24/24 + cross_lang 25/25 pristine corroborate; baseline untouched (identical to M2/M3/M5). Resolution: one tidy iter design-md-rolesplit.tidy — (1) move the history prose to the decision-record journal, (2) fix the stale "below", (3) re-scope architect_sweeps honesty sweeps to design/contracts only (models/ is the explicitly-narrative tier — scanning it for history-anchors is a post-split category error), (4) widen design_index_pin.rs clause-3 to SUBSUME Sweep-1's history-anchor regex over contracts/ (invariant: clause-3 GREEN ⟹ Sweep-1 clean in contracts/ — the hard gate enforces the spirit, closing the literal-marker dodge permanently). No fieldtest (zero authoring-surface change). Milestone closes after the tidy lands Boss-verified. → 2026-05-19-audit-design-md-rolesplit.md - 2026-05-19 — iter design-md-rolesplit.tidy (DONE 7/7): resolves the milestone-close audit DRIFT (2ba5e16). Gate-first TDD: widened design_index_pin.rs clause-3 to a hand-rolled FAITHFUL Sweep-1 superset — case-sensitive digit-anchored Sweep-1 line anchors (confirmed ZERO across all contracts) + Sweep-1's `^[^/]*` path-EXCLUDED date_anchor (so docs/specs/2026-.. citations are not flagged) + the audit-named decision-record PHRASES (case-insensitive, closing the capital-variance dodge); NO regex dep, the blanket case-insensitive iter-detector REJECTED as unworkable (it conflated the memory-model rule-names "Iter A/B" + ordinary "pre-existing/pre-set/pre-Boehm" with provenance). Sentence-level strip of the faithfully-migrated history/decision-record prose out of 5 contract files (typeclasses/str-abi/scope-boundaries — the audit's spot-check — PLUS roundtrip-invariant.md:73 + data-model.md:149,161, the 2 the exhaustive plan-time scan found the spot-check missed) into docs/journals/2026-05-19-design-decision-records.md, each removed sentence replaced by its present-tense contract equivalent; float-semantics.md stale `(see "Str ABI" below)` → `(see design/contracts/str-abi.md)`; architect_sweeps.sh honesty sweeps re-scoped design/contracts+design/models → design/contracts only (models/ is the explicitly-narrative tier; scanning it for history-anchors is a post-split category error) + skills/audit/agents/ailang-architect.md lockstep. Invariant established: clause-3 GREEN ⟹ Sweep-1 finds nothing in contracts/. The prior dispatch correctly BLOCKED on a real plan defect (iso_date lacked Sweep-1's path-exclusion → over-fired on legit spec-path citations); per the "two+ defects in one iteration ⇒ fix the upstream artifact, not a third patch" discipline the audit Resolution mechanism+scope were corrected in lockstep (f2cdd67) before re-dispatch. Three docs_honesty_pin pinned-byte runs survived contiguous (str-abi.md:19, typeclasses.md:297, scope-boundaries.md:8 — shifted from :9 by the authorized :5-6 collapse, content byte-untouched). Boss-verified independently: cargo test --workspace 646/0, design_index_pin 4/4 (clause-3 RED→GREEN), architect_sweeps.sh exit 0 "All five sweeps clean" (acceptance criterion 9 met), acceptance grep CLEAN, 3 pins each exactly 1 contiguous match. Zero spec/quality re-loops. This is the FINAL design-md-rolesplit iteration — the milestone (DESIGN.md → design/ ledger role-split) is now functionally complete, audited, drift-resolved, and the in-code hard gate enforces the honesty spirit. → 2026-05-19-iter-design-md-rolesplit.tidy.md +- 2026-05-19 — iter design-ledger-formal-links.1 (DONE 5/5, whole milestone): positive-half completion of the DESIGN.md → design/ split — design/ body cross-references are now formal, file-relative Markdown links `[label](path)` into the durable tier (design/ + crates/** + runtime/**), and a new in-tree hard gate `design_index_pin.rs` **clause-5** (`design_body_links_are_durable_and_resolve`) walks every design/contracts/*.md + design/models/*.md, strips fenced code (`strip_fences` toggles on ```/~~~), extracts every `](path)`, and asserts it resolves file-relative to a real durable file — never docs/, never an in-file #anchor. RED-first via identity-stubbed strip_fences (four embedded synthetic vectors — first FAILS) → real toggle-on-fence impl → GREEN. **clause-5 ∘ clause-3 = complete invariant** the milestone delivers: every contract cross-reference is either a resolving durable file-link OR clause-3-forbidden decision-record prose. Convert-set (recon-and-corpus-verified, closed): 7 prose refs / 8 link tokens — float-semantics:69/100, embedding-abi:45, memory-model:44/105 (the genuine cross-file stale-direction wart: "Method dispatch below" but the heading lives in typeclasses.md:227, not memory-model.md), scope-boundaries:48/88 (mixed-referent split: `ailang-core::desugar` → source link + Pipeline → ../models/pipeline.md). Disposition-(b) on the 2 PROSE_ROUNDTRIP homeless pointers (pipeline.md:61, authoring-surface.md:180) — pointer removed, ail merge-prose behavioural prose preserved (recon proved roundtrip-invariant.md does NOT carry the merge-prose-cycle content; the prior clause-6 sample's "(a) retarget" was thus wrong, corrected in spec amendment 1 to the three-disposition rule). honesty-rule.md positive-half paragraph inserted pin-safe between L14 and L16; both `docs_honesty_pin.rs` phrases byte-identical. Spec went through TWO corpus-grounded amendments before plan-write (grounding-check PASS ×3): (1) recon-driven — clause-6 incomplete + missing navigational/nominal cross-reference definition + OQ2/OQ3 byte-policies; (2) plan-time-corpus-driven — clause-5 fence-skip + the in-fence schema-annotation carve-out (data-model.md 38/66/79/206/226 are inside ```jsonc fences 30–87 + 203–228, where Markdown links are literal text on every renderer; the inline analog of the nominal-mention carve-out — schema-example documentation, not browsable cross-references). Per the "two+ surfaced ⇒ ground properly once, don't iter-patch" discipline, amendment 2 was the definitive corpus-grounded pass (every convert-set ref's prose-vs-fence status + exact bytes personally verified before amending), not a fourth patch returning later. INDEX-spine sub-fork resolved by user MC during brainstorm Step 2: spine stays repo-root-relative (registry tier is structurally invariant — move-fragility absent), clause-1 byte-unchanged; body uses file-relative (the converged-design path-form decision). Boss-verified independently: `cargo test --workspace` 647/0 (+1: clause-5), `design_index_pin` 5/5, `docs_honesty_pin` 5/5, design/INDEX.md + decision-records journal byte-unchanged, clauses 1–4 source byte-unchanged (the 2 `-` lines are the two-line //! header rewrite Task 1 Step 5 itself delivers — clauses are #[test] fns, untouched), 0 docs/ link targets, 0 #fragments, 8 `](` link tokens (= the closed convert-set), embedding-abi.md:48 docs_honesty_pin pinned phrase byte-identical. One Concerns item: Task-5 Step-7 plan-predicted `-`-line count was 1, actual 2 — planner self-review-item-8 arithmetic miss on the //! header rewrite (Steps 5 + 7 inconsistent within Task 1+5 about how many lines the header rewrite removes); harmless (substantive assertion *clauses 1–4 source unchanged* fully holds), recorded as lesson. Next: mandatory milestone-close audit; no fieldtest (zero authoring-surface change — reasoned exclusion). → 2026-05-19-iter-design-ledger-formal-links.1.md