diff --git a/docs/specs/2026-05-19-design-ledger-formal-links.md b/docs/specs/2026-05-19-design-ledger-formal-links.md index 5313e2b..c400b53 100644 --- a/docs/specs/2026-05-19-design-ledger-formal-links.md +++ b/docs/specs/2026-05-19-design-ledger-formal-links.md @@ -148,6 +148,18 @@ every nominal mention of a file or symbol. The discriminator: \`ailang-core::desugar\`` converts to `[desugar](../../crates/ailang-core/src/desugar.rs)` — never for a nominal mention. +- **Out of scope (schema-example documentation):** a `see §"…"` + annotation *inside a fenced code block* — recon+corpus-confirmed: + the `//`-comments in `data-model.md`'s fenced `jsonc` schema + examples (lines 38/66/79 inside the 30–87 fence; 206/226 inside + the 203–228 fence) — documents the schema example, not the + browsable ledger. It is the inline-annotation analog of the + nominal-mention carve-out: a `//` comment in a code example is + code documentation, not a "go browse there" pointer. It stays as + the informal `see §"…"` annotation, untouched. clause-5 + correspondingly does not scan fenced regions (a `](` inside a + fence is literal text, not a navigable link — a gate-correctness + requirement independent of this carve-out). Two byte-policies the conversion applies (Boss-adjudicated from recon Open Questions; the planner does not re-open them): @@ -203,9 +215,33 @@ fn design_body_links_are_durable_and_resolve() { // 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. Composes with clause-3: - // a surviving cross-reference is a resolving durable file-link - // or it is clause-3-forbidden decision-record prose. + // 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 \]\(([^)]+)\) // (matches [x](y) and the ](y) of image ![x](y) alike — every @@ -239,6 +275,9 @@ fn design_body_links_are_durable_and_resolve() { 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"]; @@ -251,7 +290,7 @@ fn design_body_links_are_durable_and_resolve() { } let raw = fs::read_to_string(&p).unwrap(); let fname = format!("{base}/{}", p.file_name().unwrap().to_string_lossy()); - for tgt in targets(&raw) { + for tgt in targets(&strip_fences(&raw)) { if tgt.starts_with("http://") || tgt.starts_with("https://") || tgt.starts_with("mailto:") @@ -291,8 +330,9 @@ fn design_body_links_are_durable_and_resolve() { `#[test]` count in the file goes 4 → 5; clauses 1–4 are byte- unchanged. The exact byte form of the extractor/regex is the planner's to finalise against this shape; the *contract* is the -three predicates (resolves-from-containing-file; durable-tier; -no-fragment) and the RED-first synthetic vector. +four predicates (resolves-from-containing-file; durable-tier; +no-fragment; fenced-code-not-scanned) and the RED-first synthetic +vector. ### (b) Real prose-reference before → after @@ -406,21 +446,35 @@ outcome, not a dodge). `design_body_links_are_durable_and_resolve`; clauses 1–4 are byte-identical to HEAD `2ee4087`. 2. clause-5 is demonstrably RED-able (the synthetic vector fails if - the durable classifier or the anchor check is wrong) and GREEN on - the final tree. + the durable classifier, the anchor check, or the fence-skip is + wrong) and GREEN on the final tree. 3. Every *navigational* cross-*file* reference (per §"Scope — what counts as a cross-reference") in `design/contracts/` and `design/models/` is a file-relative `[label](path)` Markdown link resolving (from its containing file) to an existing `design/`-or-source file — OR, for a homeless reference, is - disposed per clause-6 (a)/(b)/(c). Nominal source-symbol - mentions are NOT linkified (out of scope). Iter-provenance - suffixes on converted refs are stripped. The recon enumeration - is the authoritative touch-set: float-semantics:100, - float-semantics:69, embedding-abi:45, data-model:38/66/79/206/226, - memory-model:44/105, scope-boundaries:48/88 convert; the two - PROSE_ROUNDTRIP refs take disposition (b); embedding-abi:51 and - every intra-file "above/below" stay prose. + disposed per clause-6 (a)/(b)/(c). Nominal source-symbol mentions + and in-fence schema annotations are NOT linkified (out of scope). + Iter-provenance suffixes on converted refs are stripped. The + recon-and-corpus-verified touch-set is **closed and exhaustive**: + - **convert (8 prose refs):** `float-semantics.md:69` + (§"Prelude (built-in) classes" → `typeclasses.md`), + `float-semantics.md:100` (bare path → `str-abi.md`), + `embedding-abi.md:45` ("Frozen value layout" → + `frozen-value-layout.md`), `memory-model.md:44` (§"Data model" + → `data-model.md`), `memory-model.md:105` (§"Method dispatch" + → `typeclasses.md`), `scope-boundaries.md:48` ("Str ABI" → + `str-abi.md`), `scope-boundaries.md:88` (splits: + `ailang-core::desugar` → + `../../crates/ailang-core/src/desugar.rs` AND "Pipeline" → + `../models/pipeline.md`). + - **disposition (b):** `pipeline.md:61`, + `authoring-surface.md:180` — the `(see docs/PROSE_ROUNDTRIP.md)` + pointer removed, surrounding `ail merge-prose` prose kept. + - **stay prose (out of scope):** `data-model.md` + 38/66/79/206/226 (in-fence `jsonc` schema annotations), + `embedding-abi.md:51` (no quoted title / no `(see)` form), + every intra-file "above/below". 4. No reference in `design/` targets `docs/` (any subtree) or carries a `#fragment`. 5. `design/INDEX.md` and clause-1 are byte-unchanged; the