From 42ff44adf6cbac4fe5cbdf12eb407304ae8de9bc Mon Sep 17 00:00:00 2001 From: Brummel Date: Tue, 19 May 2026 23:14:56 +0200 Subject: [PATCH] =?UTF-8?q?spec:=20design-ledger-formal-links=20=E2=80=94?= =?UTF-8?q?=20corpus-grounded=20amendment=20(clause-5=20fence-skip=20+=20c?= =?UTF-8?q?losed=20enumeration)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Plan-time corpus verification surfaced a third distinct gap the brainstorm-sample missed: data-model.md:38/66/79/206/226 are 'see §"..."' annotations inside fenced jsonc code blocks (verified: fences at 30-87 and 203-228; the 5 refs fall inside), where a Markdown link is literal text — not a navigable link on any renderer. Per the "two+ surfaced ⇒ ground the spec properly once, don't iter-patch" discipline: amendment 2 is the definitive corpus-grounded pass, not a fourth-patch returning later. Every convert-set ref's prose-vs-fence status + exact bytes personally verified before amending. - clause-5 (Concrete a): adds a strip_fences helper toggling on triple-backtick / tilde lines; the per-file scan runs targets(&strip_fences(&raw)); the RED-first synthetic vector gains two fence-skip assertions (identity-stub of strip_fences ⇒ first assert fails ⇒ genuine RED). Contract grows from 3 to 4 predicates (+ fenced-code-not-scanned). - Scope section: third out-of-scope carve-out — a 'see §"..."' inside a fenced code block is schema-example documentation = 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). Clean extension of the converged navigational-vs-nominal principle. - Acceptance 3: convert-set CLOSED and exhaustive — 8 prose refs (float-semantics:69/100, embedding-abi:45, memory-model:44/105, scope-boundaries:48/88), 2 disposition-(b) homeless removals (pipeline:61, authoring-surface:180), 6 stay-prose (data-model in-fence x5, embedding-abi:51). Iter-provenance suffixes stripped on conversion. scope-boundaries:88 splits into a source link plus a Pipeline cross-file link. grounding-check third dispatch on the amended bytes: PASS, all 8 assumptions ratified (incl. corpus-state-verified A4-A7 per the rolesplit precedent for text-enumeration claims). --- .../2026-05-19-design-ledger-formal-links.md | 86 +++++++++++++++---- 1 file changed, 70 insertions(+), 16 deletions(-) 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