plan: design-ledger-formal-links.1 — clause-5 + 7 conversions + 2 disposition-(b) + honesty-rule positive-half
Single iteration covering the whole milestone (same shape as
rolesplit.1; spec is small, no build-atomicity issue — clause-5 is
isolated additive code, prose edits don't affect compilation).
Five tasks, each at task-level review granularity:
Task 1: clause-5 added to design_index_pin.rs RED-first via
identity-stubbed strip_fences (synthetic vector FAILS) →
correct toggle-on-fence impl (GREEN); module //!-header
extended to name clause-5. Test count 4 → 5.
Task 2: 7 prose-ref conversions to file-relative Markdown links
(float-semantics:69/100, embedding-abi:45, memory-model:44/
105, scope-boundaries:48/88 with the source+Pipeline split).
Exact verbatim before→after for every line. clause-5 stays
GREEN by construction (every introduced link resolves
durable).
Task 3: 2 disposition-(b) homeless-ref removals (pipeline:60-61
in-fence CLI block + authoring-surface:178-181 prose).
Pointer dropped, behavioural prose preserved.
Task 4: honesty-rule.md pin-safe positive-half paragraph between
L14 and the existing L15-blank (the two docs_honesty_pin
phrases at L14/L19 each stay verbatim on their physical
line; the additive insertion splits neither). The new
paragraph names design_index_pin.rs clause-5 by name
(nominal mention, not a link — consistent with the
existing 'Ratified by:' footer style and with §Scope).
Task 5: Whole-suite gate (646 → 647) + 5 grep+diff verifications
(no docs/ link, no #fragment, link count = 8, in-fence
schema-annotations intact, INDEX.md + decision-records
journal + clauses 1-4 byte-unchanged).
Self-review 8/8: spec coverage complete; zero placeholders; names
consistent; bite-sized; no commit steps; pin contiguity verified
(L14 + L19 stay verbatim on their physical lines, replacement body
shows L14 contiguous); no signature change ⇒ no compile-gate
ordering issue; every cargo-test filter substring is a real test
name + unfiltered runs have explicit count assertions.
This commit is contained in:
@@ -0,0 +1,518 @@
|
||||
# design-ledger-formal-links.1 — Implementation Plan
|
||||
|
||||
> **Parent spec:** `docs/specs/2026-05-19-design-ledger-formal-links.md` (HEAD `42ff44a`; grounding-check PASS ×3 across two corpus-grounded amendments — clause-6 + cross-ref definition, then clause-5 fence-skip + closed convert-set enumeration)
|
||||
>
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement` to run this plan. Steps use `- [ ]` checkboxes for tracking.
|
||||
|
||||
**Goal:** Ship the formal-cross-links milestone in one iteration: add `design_index_pin.rs` clause-5 (with `strip_fences` + RED-first synthetic vectors), convert the seven Acceptance-3 prose refs to file-relative Markdown links, apply disposition-(b) to the two PROSE_ROUNDTRIP homeless pointers, append the pin-safe positive-half sentence to `honesty-rule.md`, and ratify the clause-3 ∘ clause-5 composition invariant.
|
||||
|
||||
**Architecture:** Iteration `.1` is the whole milestone (the spec is small; same single-iter shape as the immediately-preceding rolesplit.1). Build-atomicity is not a concern — clause-5 is a new isolated test, prose edits do not affect compilation. The ordering concern is RED-first (Task 1's synthetic vector must demonstrably FAIL on an identity-stub of `strip_fences` before the implementer fixes it).
|
||||
|
||||
**Tech Stack:** `crates/ailang-core/tests/design_index_pin.rs` (new clause-5 + `//!`-header extension); seven `design/contracts/*.md` + two `design/models/*.md` prose edits.
|
||||
|
||||
---
|
||||
|
||||
**Files this plan creates or modifies:**
|
||||
|
||||
- Modify: `crates/ailang-core/tests/design_index_pin.rs:1-5` — extend `//!` header to name clause-5.
|
||||
- Modify: `crates/ailang-core/tests/design_index_pin.rs:253-` — append clause-5 `design_body_links_are_durable_and_resolve` (with `strip_fences`, `targets`, `is_durable`, RED-first synthetic vectors, tree walk).
|
||||
- Modify: `design/contracts/float-semantics.md:69` — convert §"Prelude (built-in) classes" → `[Prelude (built-in) classes](typeclasses.md)`.
|
||||
- Modify: `design/contracts/float-semantics.md:100` — convert bare-path `design/contracts/str-abi.md` → `[Str ABI](str-abi.md)`.
|
||||
- Modify: `design/contracts/embedding-abi.md:45` — convert "Frozen value layout" below → `[Frozen value layout](frozen-value-layout.md)`; drop stale "below".
|
||||
- Modify: `design/contracts/memory-model.md:44` — convert §"Data model" → `[Data model](data-model.md)`.
|
||||
- Modify: `design/contracts/memory-model.md:105-106` — convert §"Method dispatch" below → `[Method dispatch](typeclasses.md)`; drop stale "below" (target is in typeclasses.md, not in this file).
|
||||
- Modify: `design/contracts/scope-boundaries.md:48-49` — convert "Str ABI" → `[Str ABI](str-abi.md)`.
|
||||
- Modify: `design/contracts/scope-boundaries.md:88` — convert mixed-referent: `ailang-core::desugar` → source link, "Pipeline" → `../models/pipeline.md`; drop stale "above".
|
||||
- Modify: `design/models/pipeline.md:60-61` — disposition-(b): drop the `(see docs/PROSE_ROUNDTRIP.md)` continuation line; preserve CLI prose.
|
||||
- Modify: `design/models/authoring-surface.md:180-181` — disposition-(b): drop the `see docs/PROSE_ROUNDTRIP.md for the six-step cycle and` clause; preserve the surrounding `ail merge-prose` sentence.
|
||||
- Modify: `design/contracts/honesty-rule.md:14` — append pin-safe positive-half paragraph after line 14, before blank line 15.
|
||||
- Untouched (assert): `design/INDEX.md`; clauses 1–4 of `design_index_pin.rs`; `docs/journals/2026-05-19-design-decision-records.md`; `data-model.md` lines 38/66/79/206/226 (in-fence schema annotations — out of scope); `embedding-abi.md:48` (`docs_honesty_pin.rs:172`-pinned phrase — sits between the :45 conversion and the :51 stay-prose, must be byte-identical); `embedding-abi.md:51` (no-title-no-(see)-form — out of scope); every intra-file "above/below".
|
||||
|
||||
---
|
||||
|
||||
### Task 1: clause-5 hard gate (RED-first via synthetic vectors)
|
||||
|
||||
**Files:**
|
||||
- Modify: `crates/ailang-core/tests/design_index_pin.rs:1-5` — extend `//!` header.
|
||||
- Modify: `crates/ailang-core/tests/design_index_pin.rs:253-` — append clause-5.
|
||||
|
||||
- [ ] **Step 1: Append clause-5 with `strip_fences` STUBBED as identity (RED setup).**
|
||||
|
||||
Append to the END of the file (after the existing line 252 `}`):
|
||||
|
||||
```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 {
|
||||
// INTENTIONALLY-STUBBED IDENTITY for RED-first (Step 2);
|
||||
// Step 3 replaces this body with the real toggle-on-fence impl.
|
||||
md.to_string()
|
||||
}
|
||||
|
||||
// link target := first capture of \]\(([^)]+)\)
|
||||
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
|
||||
}
|
||||
|
||||
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"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run the test to verify it FAILS (RED) on the identity-stubbed `strip_fences`.**
|
||||
|
||||
Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`
|
||||
|
||||
Expected: **FAIL** at the first fence assertion: `assert!(targets(&strip_fences("```\nsee [E](e.md)\n```\n")).is_empty())` — because identity-`strip_fences` returns the input unchanged, `targets` extracts `"e.md"`, the vec is non-empty, the assert panics. Output contains `assertion failed: targets(&strip_fences(...)).is_empty()`.
|
||||
|
||||
- [ ] **Step 3: Implement `strip_fences` correctly (toggle on fence lines) — GREEN.**
|
||||
|
||||
Replace ONLY the `strip_fences` function body (the `INTENTIONALLY-STUBBED IDENTITY` block) with:
|
||||
|
||||
```rust
|
||||
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
|
||||
}
|
||||
```
|
||||
|
||||
(Drop the comment about INTENTIONALLY-STUBBED.)
|
||||
|
||||
- [ ] **Step 4: Run the test to verify it PASSES (GREEN).**
|
||||
|
||||
Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`
|
||||
|
||||
Expected: **PASS**. All four synthetic assertions hold; the tree walk runs over `design/contracts/*.md` + `design/models/*.md`, the current corpus has **zero** `](` markdown links in design/ body (bash-verified pre-plan), so `targets(&strip_fences(&raw))` returns empty for every file ⇒ no resolve/durable assertions trigger ⇒ vacuously GREEN.
|
||||
|
||||
- [ ] **Step 5: Extend the `//!` module header to name clause-5.**
|
||||
|
||||
Replace lines 1–5:
|
||||
|
||||
```rust
|
||||
//! 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.
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```rust
|
||||
//! 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, 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.
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Run the whole suite to confirm nothing else regressed.**
|
||||
|
||||
Run: `cargo test -p ailang-core --test design_index_pin`
|
||||
|
||||
Expected: **5 passed; 0 failed; 0 ignored**. The four pre-existing clauses (`design_md_is_gone`, `every_index_link_resolves`, `every_contract_names_a_resolvable_ratifying_test`, `contracts_carry_no_decision_record_prose`) plus the new `design_body_links_are_durable_and_resolve`. End count: **5 tests, 5 passed**.
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Convert the seven Acceptance-3 prose refs to file-relative Markdown links
|
||||
|
||||
**Files:**
|
||||
- Modify: `design/contracts/float-semantics.md:69, 100`
|
||||
- Modify: `design/contracts/embedding-abi.md:45`
|
||||
- Modify: `design/contracts/memory-model.md:44, 105-106`
|
||||
- Modify: `design/contracts/scope-boundaries.md:48, 88`
|
||||
|
||||
Verbatim before→after for each. Iter-provenance suffixes do not appear on any of these seven (they were all on the in-fence data-model refs, which are out of scope). Each edit is a single-line or two-adjacent-line text replacement; no surrounding line is perturbed.
|
||||
|
||||
- [ ] **Step 1: `float-semantics.md:69` — §"Prelude (built-in) classes" → `[Prelude (built-in) classes](typeclasses.md)`.**
|
||||
|
||||
Replace verbatim:
|
||||
|
||||
```
|
||||
internally — see §"Prelude (built-in) classes" for the Show ship).
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
internally — see [Prelude (built-in) classes](typeclasses.md) for the Show ship).
|
||||
```
|
||||
|
||||
(`float-semantics.md` and `typeclasses.md` are siblings in `design/contracts/` ⇒ file-relative target is `typeclasses.md`. The label retains the original section title verbatim; Markdown handles balanced parens in labels.)
|
||||
|
||||
- [ ] **Step 2: `float-semantics.md:100` — bare path `design/contracts/str-abi.md` → `[Str ABI](str-abi.md)`.**
|
||||
|
||||
Replace verbatim (line 100; line 101 unchanged):
|
||||
|
||||
```
|
||||
a fresh heap-Str slab at the call site (see design/contracts/str-abi.md
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
a fresh heap-Str slab at the call site (see [Str ABI](str-abi.md)
|
||||
```
|
||||
|
||||
- [ ] **Step 3: `embedding-abi.md:45` — "Frozen value layout" below → `[Frozen value layout](frozen-value-layout.md)` (drop stale "below").**
|
||||
|
||||
Replace verbatim line 45:
|
||||
|
||||
```
|
||||
layout is **frozen as of M3** (see "Frozen value layout" below); the
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
layout is **frozen as of M3** (see [Frozen value layout](frozen-value-layout.md)); the
|
||||
```
|
||||
|
||||
(Lines 44 and 46–52 untouched. **Critical:** line 48 carries the `docs_honesty_pin.rs:172`-pinned phrase `Export parameters are written **bare**: a scalar type carries no` — must remain byte-identical. Line 51 `the frozen value layout below specifies` is also untouched: §Scope out-of-scope rule — no quoted title, no `(see …)` form.)
|
||||
|
||||
- [ ] **Step 4: `memory-model.md:44` — §"Data model" → `[Data model](data-model.md)`.**
|
||||
|
||||
Replace verbatim line 44:
|
||||
|
||||
```
|
||||
parallel to `params` and `ret` (see §"Data model" for the JSON
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
parallel to `params` and `ret` (see [Data model](data-model.md) for the JSON
|
||||
```
|
||||
|
||||
- [ ] **Step 5: `memory-model.md:105-106` — §"Method dispatch" below → `[Method dispatch](typeclasses.md)` (drop stale "below"; target heading lives in typeclasses.md:227, not in memory-model.md — the genuine cross-file stale-direction wart).**
|
||||
|
||||
Replace verbatim lines 105–106:
|
||||
|
||||
```
|
||||
Method dispatch is type-driven post-mq.3 (see §"Method dispatch"
|
||||
below): synth resolves a `Term::Var { name: "show" }` by consulting
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
Method dispatch is type-driven post-mq.3 (see [Method dispatch](typeclasses.md)):
|
||||
synth resolves a `Term::Var { name: "show" }` by consulting
|
||||
```
|
||||
|
||||
(Line 107 onward unchanged. The pre-existing `post-mq.3` iter-token is *not* on the converted ref itself and is *not* a clause-3 trip under the current implementation — pre-existing, not introduced by this milestone, left as-is.)
|
||||
|
||||
- [ ] **Step 6: `scope-boundaries.md:48-49` — "Str ABI" → `[Str ABI](str-abi.md)`.**
|
||||
|
||||
Replace verbatim line 48 (line 49 unchanged):
|
||||
|
||||
```
|
||||
call time and return it with `ret_mode: Own`; see "Str ABI" for
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
call time and return it with `ret_mode: Own`; see [Str ABI](str-abi.md) for
|
||||
```
|
||||
|
||||
- [ ] **Step 7: `scope-boundaries.md:88` — mixed-referent split (source link + Pipeline cross-file link, drop stale "above").**
|
||||
|
||||
Replace verbatim line 88:
|
||||
|
||||
```
|
||||
typecheck/codegen — see `ailang-core::desugar` and Pipeline above.
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
typecheck/codegen — see [desugar](../../crates/ailang-core/src/desugar.rs) and [Pipeline](../models/pipeline.md).
|
||||
```
|
||||
|
||||
(`scope-boundaries.md` is in `design/contracts/`; `../../` reaches the repo root; `../models/pipeline.md` reaches `design/models/pipeline.md`. Both targets verified existing pre-plan. "above" dropped: stale-direction (Pipeline lives in a different file, not above in this one).)
|
||||
|
||||
- [ ] **Step 8: Run clause-5 to confirm all seven new links resolve into the durable tier — still GREEN.**
|
||||
|
||||
Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`
|
||||
|
||||
Expected: **PASS**. Each of the seven introduced `](path)` resolves from its containing file's directory to a real `design/`-or-source file (`typeclasses.md`, `str-abi.md`, `frozen-value-layout.md`, `data-model.md`, `typeclasses.md`, `str-abi.md`, `../../crates/ailang-core/src/desugar.rs` + `../models/pipeline.md`). All targets are in the durable tier (`design/` or `crates/`). No `#fragment`. No in-fence link.
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Disposition-(b) on the two PROSE_ROUNDTRIP homeless refs
|
||||
|
||||
**Files:**
|
||||
- Modify: `design/models/pipeline.md:60-61` (in-fence CLI block; pointer removed regardless — the in-fence carve-out preserves *schema documentation* annotations, not cross-tier pointers).
|
||||
- Modify: `design/models/authoring-surface.md:178-181`.
|
||||
|
||||
- [ ] **Step 1: `pipeline.md:60-61` — drop the `(see docs/PROSE_ROUNDTRIP.md)` continuation line.**
|
||||
|
||||
Replace verbatim lines 60–61:
|
||||
|
||||
```
|
||||
— compose the LLM-mediator prompt for the prose round-trip
|
||||
(see docs/PROSE_ROUNDTRIP.md)
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
— compose the LLM-mediator prompt for the prose round-trip
|
||||
```
|
||||
|
||||
(One line removed. The CLI command row's behavioural prose — "compose the LLM-mediator prompt for the prose round-trip" — is preserved verbatim; the ledger now describes the behaviour without pointing into the non-durable tier.)
|
||||
|
||||
- [ ] **Step 2: `authoring-surface.md:178-181` — drop the cross-tier pointer clause, preserve the surrounding `ail merge-prose` sentence.**
|
||||
|
||||
Replace verbatim lines 178–181:
|
||||
|
||||
```
|
||||
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.
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
by construction (Decision 6 constraint 2); form (B) deliberately is
|
||||
not. Re-integrating prose edits requires an external LLM mediator,
|
||||
not a compiler pass — the prompt template `ail merge-prose`
|
||||
composes the six-step cycle.
|
||||
```
|
||||
|
||||
(The cross-tier pointer `see \`docs/PROSE_ROUNDTRIP.md\` for the` is removed; the substantive present-tense content — that `ail merge-prose` composes the six-step cycle — is preserved and slightly rearranged into one sentence on the ledger side, no pointer needed.)
|
||||
|
||||
- [ ] **Step 3: Run clause-5 again — confirm no new failures (Task 3 introduces no `](` links; clause-5 stays GREEN).**
|
||||
|
||||
Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`
|
||||
|
||||
Expected: **PASS** (Task 3 only *removes* prose; introduces no link).
|
||||
|
||||
---
|
||||
|
||||
### Task 4: honesty-rule.md positive-half sentence (pin-safe)
|
||||
|
||||
**Files:**
|
||||
- Modify: `design/contracts/honesty-rule.md:14` — append a new paragraph after line 14, before blank line 15.
|
||||
|
||||
- [ ] **Step 1: Insert the positive-half paragraph between line 14 and line 15.**
|
||||
|
||||
The two `docs_honesty_pin.rs`-pinned phrases sit at line 14 (`the honesty rule it holds itself to`) and line 19 (`whether the document asserts something exists, works, or changed that does not`). The pin uses whitespace-normalised `.contains()` over the whole file — any insertion that does not *split* either phrase is safe. Inserting a new paragraph after L14 (before the existing blank L15) leaves both phrases byte-identical on their physical lines.
|
||||
|
||||
Replace verbatim:
|
||||
|
||||
```
|
||||
the honesty rule it holds itself to.
|
||||
|
||||
The single legitimate exception is a present-tense reserved or
|
||||
```
|
||||
|
||||
with:
|
||||
|
||||
```
|
||||
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
|
||||
```
|
||||
|
||||
(A new paragraph block of 5 wrapped lines is inserted between L14's end and the existing L16 `The single legitimate exception …`. The original blank L15 is preserved between L14 and the new paragraph; a second blank is added between the new paragraph and the previous L16. Net: file grows by ~6 lines; both pinned phrases remain on their own physical lines verbatim.)
|
||||
|
||||
The new paragraph contains no clause-3-PHRASES tripwire (verified against the PHRASES list in `design_index_pin.rs:173-179`: none of `we rejected` / `an earlier draft` / `was retired` / etc. appears) and no Sweep-1 anchor (no `Iter <n>` / `Family <n>` / `pre-<n>` / `**Status:` / `21.g` / `<n> sketch` / date in line-prefix-before-`/`). The phrase `the rule above` is intra-file directional prose, explicitly allowed by the milestone's §Scope out-of-scope rule (intra-file directional ≠ cross-reference). The naming of `design_index_pin.rs` is a nominal "ratified-by"-style mention, NOT a navigational pointer (per §Scope) — stays as backtick code, not a link, consistent with line 21's pre-existing `Ratified by: \`crates/ailang-core/tests/docs_honesty_pin.rs\`.`.
|
||||
|
||||
- [ ] **Step 2: Run the docs-honesty pin to confirm both pinned phrases still match (whitespace-normalised).**
|
||||
|
||||
Run: `cargo test -p ailang-core --test docs_honesty_pin design_md_present_tense_anchors_present`
|
||||
|
||||
Expected: **PASS** (both `.contains()` assertions hold; the additive paragraph splits neither phrase).
|
||||
|
||||
- [ ] **Step 3: Run clause-3 to confirm no decision-record-prose tripwire was introduced.**
|
||||
|
||||
Run: `cargo test -p ailang-core --test design_index_pin contracts_carry_no_decision_record_prose`
|
||||
|
||||
Expected: **PASS** (the new paragraph contains no clause-3 PHRASE substring and no Sweep-1 anchor on any physical line).
|
||||
|
||||
- [ ] **Step 4: Run clause-5 — confirm the new paragraph introduces no `](` link (it doesn't — the only path mention is `design_index_pin.rs` inside backticks, no markdown link).**
|
||||
|
||||
Run: `cargo test -p ailang-core --test design_index_pin design_body_links_are_durable_and_resolve`
|
||||
|
||||
Expected: **PASS**.
|
||||
|
||||
---
|
||||
|
||||
### Task 5: Whole-suite gate + composition-invariant assertion
|
||||
|
||||
**Files:**
|
||||
- None modified in this task — final verification only. The composition-invariant note is recorded in the per-iter journal (orchestrator's Boss-commit step), not as a code change.
|
||||
|
||||
- [ ] **Step 1: Run the whole workspace test suite.**
|
||||
|
||||
Run: `cargo test --workspace`
|
||||
|
||||
Expected: **all GREEN**. Pre-milestone baseline (HEAD `42ff44a`) is 646 passed / 0 failed; post-milestone count is **647 passed** (the +1 from clause-5). Zero failures, zero ignored beyond the standing baseline. (If the count is anything other than 647, investigate immediately — a regression masked as a filter mismatch would be the hazard.)
|
||||
|
||||
- [ ] **Step 2: Independently grep design/contracts + design/models for any surviving `docs/` link target (Acceptance 4 spot-check beyond clause-5).**
|
||||
|
||||
Run:
|
||||
```
|
||||
grep -rnE '\]\(([^)]*docs/[^)]*)\)' design/contracts design/models
|
||||
```
|
||||
|
||||
Expected: **zero matches** (no `](…docs/…)` link in design/ body — Acceptance 4).
|
||||
|
||||
- [ ] **Step 3: Independently grep design/contracts + design/models for any `#fragment` link.**
|
||||
|
||||
Run:
|
||||
```
|
||||
grep -rnE '\]\(#' design/contracts design/models
|
||||
```
|
||||
|
||||
Expected: **zero matches** (no in-file anchor link — commitment 1, Acceptance 4).
|
||||
|
||||
- [ ] **Step 4: Count `](` links introduced in design/ body (sanity — should match the convert-set: 7 from Task 2 + 1 from Task 2 Step 7's source-link sub-target = 8 navigational links).**
|
||||
|
||||
Run:
|
||||
```
|
||||
grep -rhoE '\]\([^)]+\)' design/contracts design/models | grep -vE '\]\((https?://|mailto:)' | wc -l
|
||||
```
|
||||
|
||||
Expected: **8** (the 7 Task-2 conversions including the source-link, plus the Pipeline link — i.e. seven prose refs, but scope-boundaries:88 carries TWO links in one conversion, so total `](` token count = 7 refs + 1 second link = 8). No fewer (would mean a conversion was missed); no more (would mean an unintended link was introduced).
|
||||
|
||||
- [ ] **Step 5: Verify the in-fence data-model refs are still informal (out-of-scope assertion).**
|
||||
|
||||
Run:
|
||||
```
|
||||
grep -nE 'see §"Class names"|see §"Type::Con name scoping"|see §"Embedding ABI"' design/contracts/data-model.md
|
||||
```
|
||||
|
||||
Expected: **5 lines** (the lines 38, 66, 79, 206, 226) — confirming the in-fence schema annotations were untouched (out of scope per §Scope; clause-5 skips fences regardless).
|
||||
|
||||
- [ ] **Step 6: Confirm INDEX.md and the decision-records journal are byte-unchanged vs HEAD (commitment 4 / Acceptance 5).**
|
||||
|
||||
Run:
|
||||
```
|
||||
git diff --quiet HEAD -- design/INDEX.md && echo INDEX.md unchanged
|
||||
git diff --quiet HEAD -- docs/journals/2026-05-19-design-decision-records.md && echo journal unchanged
|
||||
```
|
||||
|
||||
Expected: both `unchanged` lines printed (each `git diff --quiet` exits 0 ⇒ the `&& echo` fires).
|
||||
|
||||
- [ ] **Step 7: Confirm clauses 1–4 of `design_index_pin.rs` are byte-unchanged (only the `//!` header and the appended clause-5 changed).**
|
||||
|
||||
Run:
|
||||
```
|
||||
git diff HEAD -- crates/ailang-core/tests/design_index_pin.rs | grep -cE '^-[^-]'
|
||||
```
|
||||
|
||||
Expected: **`1`** — exactly one removed line (the original `//! is resurrected. Spec: docs/specs/2026-05-19-design-md-rolesplit.md.` being replaced by the extended four-line `//!` block). Any value > 1 means content was removed from clauses 1–4 — STOP and inspect.
|
||||
Reference in New Issue
Block a user