spec: design-ledger-formal-links — corpus-grounded amendment (clause-5 fence-skip + closed enumeration)

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).
This commit is contained in:
2026-05-19 23:14:56 +02:00
parent b1a0364bf2
commit 42ff44adf6
@@ -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 3087 fence; 206/226 inside
the 203228 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 14 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 14 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