832375f2ac
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.
488 lines
24 KiB
Markdown
488 lines
24 KiB
Markdown
# Formal Cross-Links in the design/ Ledger — Design Spec
|
||
|
||
**Date:** 2026-05-19
|
||
**Status:** Draft — awaiting user spec review
|
||
**Authors:** Brummel (orchestrator) + Claude
|
||
|
||
## Goal
|
||
|
||
Make the `design/` ledger a browsable wiki: replace informal prose
|
||
cross-references between contracts/models (`(see "Str ABI" below)`,
|
||
`(see design/contracts/0011-str-abi.md …)`, `see §"Data model"`) with
|
||
**formal, machine-checkable, file-level Markdown links**, and add an
|
||
in-tree hard gate that turns a broken cross-link into a same-commit
|
||
test failure instead of silent rot.
|
||
|
||
This is the positive completion of the DESIGN.md → design/ split.
|
||
That milestone established the negative half — *history prose must
|
||
not live in a contract* (`contracts_carry_no_decision_record_prose`,
|
||
clause-3). This milestone establishes the positive half — *what
|
||
cross-reference prose remains must resolve to a durable file*.
|
||
Together they form a complete invariant: every cross-reference in a
|
||
contract is **either** a resolving file-relative link into
|
||
`design/`-or-source, **or** decision-record prose that clause-3
|
||
already forbids.
|
||
|
||
The milestone has **no authoring surface**: it changes no `.ail`
|
||
program, no language construct, no compiler/checker/codegen path. It
|
||
is a documentation-form + Rust-test-infrastructure milestone, the
|
||
same category as the immediately-preceding DESIGN.md → design/ split
|
||
(`docs/specs/0045-design-md-rolesplit.md`), whose concrete code
|
||
was likewise the structural Rust pin plus the ledger shape, audited
|
||
clean. The no-`.ail` disposition is therefore precedent-governed and
|
||
reasoned, not an omission (see Concrete code shapes §"No authoring
|
||
surface").
|
||
|
||
## Architecture
|
||
|
||
Four locked design commitments (converged interactively this
|
||
session; the INDEX sub-choice was resolved by user MC during
|
||
brainstorm Step 2). They are the spec's input contract, not open
|
||
forks:
|
||
|
||
1. **File-level granularity, no in-file anchors.** A link addresses a
|
||
whole file. Finer reference granularity is achieved by *splitting
|
||
a file*, never by a `#fragment`. A link whose target is a pure
|
||
`#anchor`, or carries a `#fragment` suffix, is a contract
|
||
violation the hard gate rejects.
|
||
|
||
2. **Durable-tier targets only.** A formal link may target a file
|
||
under `design/` (contracts + models) **or** a source file
|
||
(`crates/**`, `runtime/**`). It may **never** target `docs/`
|
||
(specs, plans, journals, or standing docs like
|
||
`docs/PROSE_ROUNDTRIP.md`) — that is the *potentially ephemeral
|
||
tracking* tier; linking the durable ledger into it re-couples
|
||
exactly what the split decoupled.
|
||
|
||
3. **Standard relative Markdown link syntax** `[label](path)`. Not
|
||
Obsidian `[[wikilinks]]`: wikilink name-fuzzy resolution is the
|
||
prose-reference problem in another costume (it contradicts
|
||
content-addressing), is not native on Gitea (the platform we
|
||
actually use, self-hosted) nor in GitHub repo view, and does not
|
||
uniformly address source files.
|
||
|
||
4. **File-relative path form for body prose; the INDEX spine stays
|
||
repo-root-relative.** Body cross-links are
|
||
`[label](./sibling.md)` / `[label](../models/x.md)` /
|
||
`[label](../../crates/ailang-core/src/desugar.rs)`, resolved
|
||
relative to the *containing file*. This is the universal standard
|
||
(resolves in Gitea blob, GitHub, VS Code preview, Obsidian,
|
||
static generators), serving the named primary goal (browsability)
|
||
on every platform; its move-fragility is real but mechanically
|
||
bounded by the resolve-check. The earlier "root-relative = the
|
||
content-addressing thesis" argument was retracted as
|
||
principle-overreach (the thesis kills name-fuzzy wikilinks;
|
||
between two deterministic path forms it is an engineering trade).
|
||
The **INDEX registry table** (`design/INDEX.md`) is a
|
||
*deliberately-distinct structured tier*: it is machine-parsed as
|
||
a table, and it is the one `design/` file whose path is
|
||
structurally invariant (it is THE spine — it does not move). The
|
||
move-fragility rationale that drove body prose to file-relative is
|
||
*absent* for the spine, so the INDEX keeps its repo-root-relative
|
||
cells and `design_index_pin.rs` clause-1 is **unchanged**.
|
||
Forcing the body rule onto the spine would itself be the
|
||
principle-overreach pattern this session already retracted once.
|
||
|
||
Derived scope boundaries (forced by the commitments + Step-1 recon,
|
||
not forks):
|
||
|
||
- **Intra-file directional prose is out of scope.** "the constraints
|
||
below", "Direction 1 above", "see §X below" *within the same
|
||
document* is not a cross-*file* reference; it has no file-link
|
||
form (same file) and anchors are forbidden by commitment 1.
|
||
These stay as prose. The remedy for "this file is too big for
|
||
'above' to be clear" is commitment 1's file-split — a separate
|
||
future concern, explicitly NOT this milestone.
|
||
- **Homeless references — three dispositions (clause-6), in
|
||
priority order.** A cross-reference whose referent has no durable
|
||
home is: **(a)** retargeted to the durable `design/`-or-source
|
||
file that *actually carries that content* today, if one exists;
|
||
else **(b)** the parenthetical pointer is removed while the
|
||
surrounding present-tense behavioural prose is preserved verbatim
|
||
— the ledger *describes* the behaviour, it does not point into
|
||
the non-durable tier; else **(c)** deleted outright **only if the
|
||
reference itself is relitigation residue** (clause-3-class).
|
||
Recon-confirmed canonical instance:
|
||
`design/models/0003-pipeline.md:61` and
|
||
`design/models/0001-authoring-surface.md:180`, both
|
||
`(see docs/PROSE_ROUNDTRIP.md)`, cite the *prose-mediator cycle /
|
||
`ail merge-prose`* — content
|
||
`design/contracts/0009-roundtrip-invariant.md` does **not** carry
|
||
(that contract is the orthogonal hashing / parse-determinism
|
||
invariant). (a) is therefore unavailable and the content is real
|
||
current behaviour, not relitigation, so the disposition is
|
||
**(b)**: drop the `(see docs/PROSE_ROUNDTRIP.md)` pointer, keep
|
||
the surrounding `ail merge-prose` prose intact. Enumerating every
|
||
instance is the recon's job; the *policy* is the spec's.
|
||
- **A cross-*file* `§"Section"` reference becomes a file-link.**
|
||
`(see §"Data model" …)` pointing at content in *another* file
|
||
becomes `[Data model](data-model.md)` — the section title may
|
||
survive only as the human link *label*, never as a `#fragment`
|
||
(commitment 1). The reader lands on the file and finds the section
|
||
by reading; that is file-level granularity working as designed,
|
||
not a loss. (A `§"Section"` that names a section of the *same*
|
||
file is intra-file directional prose — the bullet above — and is
|
||
untouched.)
|
||
|
||
## Scope — what counts as a cross-reference
|
||
|
||
The milestone formalises **navigational cross-references**, not
|
||
every nominal mention of a file or symbol. The discriminator:
|
||
|
||
- **In scope (a cross-reference):** a *navigational pointer* — the
|
||
reader is being told to go look elsewhere. Forms: `(see "Title"
|
||
…)`, `(see §"Section" …)`, `(see <path>)`, `… as in <X> …`, a
|
||
bare in-prose path inside a `(see …)`, or a directional `…
|
||
below`/`… above` whose named target is a **different file**.
|
||
These convert to file-relative `[label](path)` Markdown links.
|
||
- **Out of scope (not a cross-reference):** a *nominal mention* of
|
||
a source symbol or path in explanatory prose — "the desugarer
|
||
`ailang-core::desugar` lowers loop-recur", "the box layout in
|
||
`crates/ail-embed/src/lib.rs`". Naming the implementation is not
|
||
pointing the reader at a document to read. Linkifying these would
|
||
make the milestone "hyperlink the whole ledger" rather than
|
||
"formalise the cross-references" — a different, unrequested
|
||
change. They stay as prose. Commitment 2 *permits* a `crates/**`
|
||
link target only for the case where a genuine navigational `(see
|
||
crates/… )` pointer exists — e.g. `scope-boundaries.md:88` `see
|
||
\`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):
|
||
|
||
- **Iter-provenance suffixes are stripped on conversion.** A
|
||
reference like `see §"Class names" / mq.1` carries a
|
||
clause-3-class iter token. Converting yields
|
||
`[Class names](typeclasses.md)` — the ` / mq.1` provenance is
|
||
removed (it is exactly what clause-3 forbids; the
|
||
clause-3 ∘ clause-5 composition requires its removal, not its
|
||
survival riding on a now-formal link).
|
||
- **A directional phrase with no quoted title and no `(see …)`
|
||
form stays prose.** `embedding-abi.md:51` "the frozen value
|
||
layout below specifies" has no title to become a link label and
|
||
is not a `(see …)` pointer — descriptive directional prose, left
|
||
untouched. (The genuine cross-file pointer for that referent is
|
||
the *separate* `embedding-abi.md:45` `(see "Frozen value layout"
|
||
below)`, which does convert.)
|
||
|
||
## Concrete code shapes
|
||
|
||
### No authoring surface — the delivered concrete code
|
||
|
||
There is no `.ail` because the milestone touches nothing in the
|
||
language: no construct, no type rule, no codegen, no fixture. Per
|
||
the brainstorm infra clause, the rationalisation to refuse is "no
|
||
surface so no code to show"; the honest response is to show the
|
||
concrete code the milestone *does* deliver. For a docs-form +
|
||
test-infra milestone that is (a) the new Rust hard-gate clause,
|
||
(b) the real prose-reference before→after, (c) the explicit
|
||
no-change statement for clause-1. All three are shown below. The
|
||
governing precedent is the immediately-preceding same-category
|
||
milestone (DESIGN.md → design/ split), whose spec led its concrete
|
||
code with the Rust pin and the ledger shape and was audited clean —
|
||
this milestone follows that precedent exactly.
|
||
|
||
### (a) The new hard gate — `design_index_pin.rs` clause-5
|
||
|
||
A new test in the existing `crates/ailang-core/tests/design_index_pin.rs`
|
||
(sibling of clauses 1–4; reuses the file's `root()` helper and the
|
||
clause-3 `fs::read_dir` walk idiom verbatim). It is RED-first via an
|
||
embedded synthetic-vector self-check before it asserts the live tree
|
||
(a tree-scan test cannot be RED-first by tree state alone — the tree
|
||
is GREEN-vacuous until the first ref is converted — so the RED proof
|
||
is an in-test malformed-link vector, the honest mechanism for
|
||
"scan-the-tree" gates).
|
||
|
||
```rust
|
||
#[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 \]\(([^)]+)\)
|
||
// (matches [x](y) and the ](y) of image  alike — every
|
||
// parenthesised target must resolve; conservative on purpose)
|
||
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
|
||
}
|
||
|
||
// classify a repo-relative resolved path
|
||
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; // external — out of the ledger's scope
|
||
}
|
||
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"
|
||
);
|
||
// resolve RELATIVE TO THE CONTAINING 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"
|
||
);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
`#[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
|
||
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
|
||
|
||
Three real, in-tree references (recon-confirmed line numbers; exact
|
||
bytes are the planner's):
|
||
|
||
```
|
||
design/contracts/0005-float-semantics.md:100 (the tidy precedent, still
|
||
half-formal: bare root path in prose, not a Markdown link)
|
||
- before: … a fresh heap-Str slab at the call site (see
|
||
design/contracts/0011-str-abi.md for the dual realisation).
|
||
- after: … a fresh heap-Str slab at the call site (see
|
||
[Str ABI](str-abi.md) for the dual realisation).
|
||
# float-semantics.md and str-abi.md are siblings in
|
||
# design/contracts/ ⇒ file-relative target is `str-abi.md`
|
||
|
||
design/contracts/0003-embedding-abi.md:45 (informal AND stale-direction
|
||
— the exact wart class motivating the milestone: "below" but the
|
||
target is a different file)
|
||
- before: … layout is **frozen as of M3** (see "Frozen value
|
||
layout" below); …
|
||
- after: … layout is **frozen as of M3** (see
|
||
[Frozen value layout](frozen-value-layout.md)); …
|
||
|
||
design/models/0003-pipeline.md:61 + design/models/0001-authoring-surface.md:180
|
||
(the canonical HOMELESS references — `(see docs/PROSE_ROUNDTRIP.md)`
|
||
into the non-durable tier; recon proved roundtrip-invariant.md
|
||
does NOT carry the merge-prose-cycle content ⇒ clause-6
|
||
disposition (b), not a retarget)
|
||
- before (shape): … the prompt template `ail merge-prose`
|
||
composes (see docs/PROSE_ROUNDTRIP.md).
|
||
- after (shape): … the prompt template `ail merge-prose`
|
||
composes.
|
||
# disposition (b): the (see docs/…) pointer is removed;
|
||
# the surrounding present-tense behavioural prose about
|
||
# `ail merge-prose` is preserved verbatim. Exact bytes
|
||
# (recon-confirmed pipeline.md:60-61,
|
||
# authoring-surface.md:180-181) are the planner's per
|
||
# the planner Iron Law.
|
||
```
|
||
|
||
### (c) clause-1 is unchanged (commitment 4, INDEX = spine)
|
||
|
||
```
|
||
crates/ailang-core/tests/design_index_pin.rs
|
||
fn link_target_exists(cell) { … root().join(path).exists() } // UNCHANGED
|
||
fn every_index_link_resolves() { … } // UNCHANGED
|
||
```
|
||
|
||
The INDEX table cells stay repo-root-relative
|
||
(`design/contracts/0011-str-abi.md`); clause-1 keeps resolving them via
|
||
`root().join`. No INDEX content edit, no clause-1 resolver edit.
|
||
One resolution rule per *tier* (registry-spine: root-relative;
|
||
movable body prose: file-relative), each locally justified — not
|
||
one rule everywhere, by deliberate design.
|
||
|
||
## Components
|
||
|
||
| Component | Disposition |
|
||
|---|---|
|
||
| `crates/ailang-core/tests/design_index_pin.rs` | **Modify** — add clause-5 (`design_body_links_are_durable_and_resolve`); clauses 1–4 byte-unchanged; module `//!` extended one sentence to name clause-5. |
|
||
| `design/contracts/*.md`, `design/models/*.md` | **Modify** — every genuine cross-*file* prose reference rewritten to a file-relative Markdown link; homeless refs retargeted-or-deleted; intra-file "above/below" untouched (out of scope). |
|
||
| `design/INDEX.md` | **Untouched** — spine tier, commitment 4. |
|
||
| `docs/journals/2026-05-19-design-decision-records.md` | **Untouched** — monolithic append-only relitigation archive; never a link target (commitment 2/7). |
|
||
| `design/contracts/0007-honesty-rule.md` | **Modify (prose only)** — one sentence stating cross-references are formal file-relative links into the durable tier (the positive-half companion to the clause-3 sentence it already carries). Pin-safe: the two `docs_honesty_pin.rs` phrases stay verbatim+contiguous. |
|
||
| `bench/architect_sweeps.sh` | **Untouched** — clause-5 ("does every link resolve into the durable tier?") and Sweep-1 ("does any line carry a history anchor: `Iter <n>`, a date, `**Status:`?") are orthogonal predicates over the same files. A Markdown link `[label](file.md)` contains no Sweep-1 anchor token, so converting prose to links cannot trip Sweep-1; clause-5 shares no regex or scope with it. No sweep change. |
|
||
|
||
## Data flow
|
||
|
||
`cargo test` → clause-5 reads each `design/contracts/*.md` and
|
||
`design/models/*.md` → extracts every `](…)` target → for each:
|
||
external? skip. `#`-fragment or empty file-part? fail (commitment
|
||
1). Else resolve against the *containing file's directory*,
|
||
canonicalize, strip the repo root → repo-relative path → assert
|
||
under `design/`|`crates/`|`runtime/` (commitment 2). The synthetic
|
||
vector runs first and unconditionally (RED-first proof). clause-1
|
||
independently resolves INDEX cells root-relative (unchanged).
|
||
|
||
## Error handling
|
||
|
||
Every failure is a `panic!`/`assert!` naming the file, the offending
|
||
target, and which commitment it violates ("non-durable tier",
|
||
"in-file #anchor — split the file", "does not resolve"). No silent
|
||
skips except the three external schemes. A homeless reference that
|
||
recon cannot map to a durable home is not worked around in the test
|
||
— it is deleted from the prose as clause-3-class content (the test
|
||
then passes because the reference is gone, which is the correct
|
||
outcome, not a dodge).
|
||
|
||
## Testing strategy
|
||
|
||
- **RED-first:** clause-5's embedded synthetic-vector block asserts
|
||
the extractor + durable classifier reject a `docs/specs/…` target
|
||
and recognise a `#anchor` *before* the tree walk. Planner scripts
|
||
the first task as: add clause-5 (RED on the synthetic vector if
|
||
the classifier is stubbed wrong) → make the vector GREEN → then
|
||
convert prose refs until the tree walk is GREEN.
|
||
- **Composition with clause-3:** a per-milestone assertion (journal,
|
||
not code) that on the post-milestone tree, for every
|
||
`design/contracts/*.md`, clause-3 GREEN ∧ clause-5 GREEN — i.e.
|
||
every cross-reference is a resolving durable link or it is gone.
|
||
- **Whole-suite gate:** `cargo test --workspace` GREEN, count
|
||
unchanged except +1 test. `design_index_pin` 5/5.
|
||
- **No bench surface:** zero codegen/runtime change ⇒ the regression
|
||
trio is expected pristine; any firing is the tracked-P2 noise,
|
||
causally exonerable as in the M2/M3/M5 and the just-closed split.
|
||
|
||
## Acceptance criteria
|
||
|
||
1. `crates/ailang-core/tests/design_index_pin.rs` has a 5th test
|
||
`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, 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
|
||
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/0003-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
|
||
decision-records journal is byte-unchanged and unlinked.
|
||
6. Intra-file "above/below" directional prose is untouched
|
||
(recon-enumerated as out of scope, not missed).
|
||
7. `cargo test --workspace` GREEN; bench trio exit 0 or
|
||
causally-exonerated per the standing P2 disposition.
|
||
8. `honesty-rule.md` carries the positive-half sentence; its two
|
||
`docs_honesty_pin.rs` pinned phrases remain verbatim+contiguous.
|