# design-md-consolidation iter 2 — sweep 2: REVERTED + migration plans + correction history + future speculations

> **Parent spec:** `docs/specs/0004-design-md-consolidation.md`
> §"Sweep 2 — REVERTED + migration plans out".
>
> **For agentic workers:** REQUIRED SUB-SKILL: use `skills/implement`
> to run this plan. Steps use `- [ ]` checkboxes for tracking.

**Goal:** Remove the REVERTED-decision body, the completed migration
plan, the correction-history paragraph, and the non-binding future
speculations from `docs/DESIGN.md`; condense binding-prohibition
forward-looking statements into assertive form.

**Architecture:** Documentation-only refactor. Each task targets one
self-contained section or one decision class. "RED" is a pre-task
grep / line-range read showing the target sites; "GREEN" is the
post-task grep returning empty (or only inside the binding-
prohibition contexts the spec wants preserved). No code changes.

**Tech Stack:** Edit (`docs/DESIGN.md`), grep, `cargo test
--workspace`, `bench/check.py`, `bench/compile_check.py`.

**Lesson from Sweep 1.** The acceptance grep must cover the spec's
full intent, not one specific anchor form. This plan's composite
acceptance grep is therefore the union of all variant phrasings the
spec section names — encoded once at Step 1 of Task 6 — so the
sweep cannot ship while any variant survives.

**Files this plan modifies:**
- Modify: `docs/DESIGN.md` — five edit tasks (sections / paragraphs).
- Append: `docs/JOURNAL.md` — one iter entry.
- Modify: `docs/roadmap.md` — flip Sweep 2 entry from `[ ]` → `[x]`.

---

## Task 1: Delete Decision 7 (`Term::If` removal — REVERTED)

**Files:** Modify: `docs/DESIGN.md` (delete lines 593-624 inclusive at
iter start; line numbers may shift slightly).

The entire Decision 7 block is REVERTED-and-restored audit-trail
text. `Term::If` exists in the language; the witness is the Term
schema in the Data-model section. The decision body has no
state-only content to preserve — every claim it makes describes a
removal that was rolled back.

- [ ] **Step 1: Pre-test — verify decision boundaries**

```bash
grep -n '^## Decision 7\|^## Decision 8' docs/DESIGN.md
```

Expected: two adjacent matches; the first is line ~593
(`## Decision 7: redundancy removal — \`Term::If\` is not a
primitive`), the second is line ~625 (`## Decision 8: explicit,
verified tail calls`). The block to delete spans the first up to
(but not including) the second.

- [ ] **Step 2: Read the block to confirm scope**

```bash
sed -n '593,624p' docs/DESIGN.md
```

Expected output: 32 lines starting with `## Decision 7: …` and
ending with `… the new hashes become the new identity.` followed
by a blank line. The next line is `## Decision 8: …`.

- [ ] **Step 3: Delete the Decision 7 block**

Use the Edit tool. The `old_string` is the full 32-line block from
line 593 (`## Decision 7: redundancy removal — \`Term::If\` is not
a primitive`) through line 623 (`max3\`) is intentional; the new
hashes become the new identity.`) plus the trailing blank line at
624 — i.e. delete the entire block AND the blank line that follows
it, so line 625's `## Decision 8: …` becomes contiguous with
whatever previously preceded line 593 plus a single blank-line
separator.

The exact `old_string` to pass to Edit (32 lines):

```
## Decision 7: redundancy removal — `Term::If` is not a primitive

This decision was made on shaky grounds —
applying CLAUDE.md's "no redundancies" rule to a case that turned out to
be primitive control flow, not redundancy. The post-removal match-on-Bool
form (3× the tokens, asymmetric `pat-wild` for the false case) was
worse for token economy and worse for the natural shape of the language.
`Term::If` is restored. The text below is preserved for the audit trail.

`Term::If { cond, then, else_ }` is semantically a subset of
`Term::Match` on `Bool`. Per CLAUDE.md the language must contain no
redundancies; two AST nodes for the same operation produces an
authoring decision with no semantic content and an extra codegen
path. `Term::If` was removed. Migration shape on the JSON side:

  {"t":"if","cond":C,"then":A,"else":B}
    →
  {"t":"match","scrutinee":C,
    "arms":[
      {"pat":{"p":"lit","lit":{"kind":"bool","value":true}},"body":A},
      {"pat":{"p":"wild"},"body":B}]}

The wildcard arm satisfies the typechecker's
`primitive-needs-wildcard` rule. A future iter may upgrade the
exhaustiveness check to recognise the `true`+`false` arm pair as
covering Bool without a wildcard; until then, wildcard is the
canonical migration target.

No schema version bump (no third-party consumes `ailang/v0`).
Hash invalidation for the three migrated fixtures (`sum`, `sort`,
`max3`) is intentional; the new hashes become the new identity.

```

`new_string` is the empty string (the entire block is deleted, including the trailing blank line; section boundary collapses to whatever blank-line separator already existed before line 593).

- [ ] **Step 4: Post-test**

```bash
grep -n '^## Decision' docs/DESIGN.md | head -15
```

Expected: Decision 7 absent; Decision 6 followed directly by
Decision 8. Decisions 1, 2, 3, 4, 5, 6, 8, 9, 10, 11 — note that
Decision numbering is NOT renumbered (Decision 8 stays Decision 8;
the gap is intentional and matches existing JOURNAL references).

```bash
grep -n 'REVERTED\|preserved for the audit trail\|primitive-needs-wildcard.*future iter' docs/DESIGN.md
```

Expected: empty.

- [ ] **Step 5: Commit**

```bash
git add docs/DESIGN.md
git commit -m "design-md-consolidation 2.1: delete Decision 7 (Term::If REVERTED block)"
```

---

## Task 2: Condense Decision 9 narrative-of-changes opener

**Files:** Modify: `docs/DESIGN.md` (lines ~682-689 at iter start;
shifted by -32 after Task 1; re-locate at Step 1).

Decision 9's current opener is a three-frame narrative ("Originally
framed as X; then re-framed as Y; the revision flips Z"). This
recounts how the framing changed across three points in time. State-
only form: one sentence stating what Decision 9 currently is, with
the "Boehm as parity oracle" rationale preserved (it is the
substantive content; only the narrative-of-changes wrapping is cut).

- [ ] **Step 1: Pre-test — locate the paragraph**

```bash
grep -n '^## Decision 9' docs/DESIGN.md
```

Note the line number; then:

```bash
sed -n '<line>,<line+15>p' docs/DESIGN.md
```

Expected: the opener reads "Originally framed as 'transitional
Boehm until the RC pipeline lands and the default flips'; then
re-framed as a symmetric dual-allocator policy with Boehm as the
CLI default. The revision flips the asymmetry to match Decision 10:"
followed by the three RC/Boehm/bump bullets.

- [ ] **Step 2: Replace the narrative-of-changes opener**

Edit:
- old (the paragraph between the `## Decision 9` heading and the
  three bullet points):
```
Originally framed
as "transitional Boehm until the RC pipeline lands and the default flips"; then re-framed
as a symmetric dual-allocator policy with Boehm as
the CLI default. The revision flips the asymmetry to
match Decision 10:
```
- new:
```
AILang ships two allocator backends with an asymmetric role:
```

The substantive content (RC canonical, Boehm parity oracle, bump
leak-only) follows immediately as the existing three bullets, so
the new opener leads directly into them.

- [ ] **Step 3: Verify the surrounding sentences scan**

After the edit, the paragraph reads:

```
## Decision 9: dual allocator — RC canonical, Boehm parity oracle

AILang ships two allocator backends with an asymmetric role:

- **RC is canonical.** `--alloc=rc` is the CLI default …
- **Boehm stays as a parity oracle.** …
- **`--alloc=bump`** is unchanged: a leak-only bench instrument, …
```

- [ ] **Step 4: Also condense the second narrative paragraph**

The current text after the bullets contains a second narrative
paragraph (around line 717 originally; shifted): "Originally,
every ADT box, lambda env, and closure pair was allocated with
bare `malloc` and never freed…" — this is also pre-Decision-9
history. Read its full span:

```bash
grep -n 'Originally, every ADT box' docs/DESIGN.md
```

If present, the paragraph and the surrounding two paragraphs (the
"`--alloc=bump` mode introduced for the bench is a measurement
tool, not a production target." sentence and the ADT-box history)
together describe how the GC arrived in AILang. The state-only
content is in the bullets above; this is purely history.

Edit: delete from the line "The `--alloc=bump` mode introduced for
the bench is a measurement tool, not a production target." through
the line "both make a collector necessary." (the end of the
"Originally, every ADT box…" paragraph). The next paragraph
beginning **Choice: Boehm-Demers-Weiser conservative GC.** is the
state-only content and stays.

Verify the precise text by reading the region first:
```bash
sed -n '<bullets-end>,<Choice-line-1>p' docs/DESIGN.md
```

The block to delete is approximately:

```

The `--alloc=bump` mode introduced for the bench is a
measurement tool, not a production target.

Originally, every ADT box, lambda env, and closure pair was
allocated with bare `malloc` and never freed. That worked for the
17 test fixtures (all small, all short-lived) but is incompatible
with any real workload — a stdlib `fold` over a million-element
list would leak a million boxes. The "Goal" section's "no GC for
the MVP" framing predates the parameterised-ADT pipeline and the
explicit-recursion expectation; both make a collector necessary.

```

The next non-blank line (`**Choice: Boehm-Demers-Weiser conservative GC.**`) is the start of the state-only Boehm description and stays.

- [ ] **Step 5: Post-test**

```bash
grep -nE '[Oo]riginally framed|Originally, every ADT box|--alloc=bump.*measurement tool' docs/DESIGN.md
```

Expected: empty.

```bash
grep -n '## Decision 9' docs/DESIGN.md
```

Read 30 lines forward; verify the section reads as state ("AILang
ships two allocator backends … RC is canonical … Boehm parity
oracle … bump leak-only … Choice: Boehm-Demers-Weiser conservative
GC …").

- [ ] **Step 6: Commit**

```bash
git add docs/DESIGN.md
git commit -m "design-md-consolidation 2.2: condense Decision 9 narrative-of-changes opener + drop pre-Decision-9 history paragraphs"
```

---

## Task 3: Delete Decision 10 §"Migration plan"

**Files:** Modify: `docs/DESIGN.md` (lines ~1384-1414 at iter start
in the original file; shifted further by Tasks 1-2; re-locate at
Step 1).

Decision 10's §"Migration plan" is a 7-point list of past iters
(annotation feature → RC runtime → uniqueness inference → reuse
hints → drop-iterative → validation bench → advisory lint).
Migration is complete; the end state is what the rest of Decision
10 already describes. The list is pure history.

- [ ] **Step 1: Pre-test — locate the section**

```bash
grep -n '^### Migration plan\|^### Adjacent extensions' docs/DESIGN.md
```

Expected: two matches. The first (Migration plan) is the section
to delete; the second (Adjacent extensions) is what comes after.

- [ ] **Step 2: Read the block to confirm scope**

```bash
grep -n '^### Migration plan' docs/DESIGN.md   # gives <start>
grep -n '^### Adjacent extensions' docs/DESIGN.md  # gives <end>
sed -n '<start>,<end-1>p' docs/DESIGN.md
```

Expected: 30+ lines starting with `### Migration plan` and ending
just before the `### Adjacent extensions for mutability …` header.
The 7 numbered items follow the section header.

- [ ] **Step 3: Delete the entire `### Migration plan` block**

Use the Edit tool with `old_string` set to the entire Migration
plan section (header + 7 items + the trailing blank line that
separates it from the next subsection). The `new_string` is the
empty string. After the edit, `### Codegen contract` (or whatever
the prior subsection is) is followed directly by
`### Adjacent extensions for mutability …` separated by one blank
line.

- [ ] **Step 4: Post-test**

```bash
grep -n '^### Migration plan' docs/DESIGN.md
```

Expected: empty.

```bash
grep -n '^### Adjacent extensions for mutability' docs/DESIGN.md
```

Expected: still present (this was NOT deleted; it follows directly
after whatever subsection precedes it).

- [ ] **Step 5: Commit**

```bash
git add docs/DESIGN.md
git commit -m "design-md-consolidation 2.3: delete Decision 10 §Migration plan (7-point completed-iter list)"
```

---

## Task 4: Condense Decision 11 §"Why mono, not virtual dispatch"

**Files:** Modify: `docs/DESIGN.md` (lines ~1603-1618 at iter start;
shifted by Tasks 1-3; re-locate at Step 1).

The current §"Why mono, not virtual dispatch (the empirically-
grounded version)" paragraph is structured as a correction history:
"The original rationale implicitly argued X. A micro-benchmark
refutes that specific claim. The correct rationale is one level up:
Y." State-only form skips the correction step: only Y stays.

- [ ] **Step 1: Pre-test — locate the paragraph**

```bash
grep -n 'empirically-grounded version' docs/DESIGN.md
```

Expected: 1 line, around `**Why mono, not virtual dispatch (the
empirically-grounded version).**`.

```bash
sed -n '<line>,<line+20>p' docs/DESIGN.md
```

Expected: the correction-history paragraph as currently formed
(starts with `**Why mono, not virtual dispatch (the empirically-
grounded version).** The original rationale implicitly argued …`,
ends with `… while the older "saves an indirect call" framing
does not.`).

- [ ] **Step 2: Replace with the state-only form**

Edit:
- old (the full correction-history paragraph):
```
**Why mono, not virtual dispatch (the empirically-grounded version).**
The original rationale implicitly argued that mono saves a per-call
indirect-jump cost. A mono-vs-vdisp micro-benchmark
(`bench/mono_dispatch.py`, JOURNAL bench-notes entry) refutes
that specific claim: on a saturating branch predictor with a
monomorphic indirect target, indirect dispatch is free relative to
the same-shape non-inlined direct call.
The correct rationale is one level up: **mono makes the call target
visible to the optimiser, unlocking inlining and downstream loop
transformations that virtual dispatch prevents in principle.** The
measured end-to-end win on a tight LCG hot loop is substantial vs
non-inlinable direct call and larger still vs polymorphic vdisp (4 distinct
targets cycling). On larger callee bodies or cold call sites the
inlining win shrinks toward zero, but the architectural claim — "mono
enables optimisations vdisp forbids" — holds across the spectrum,
while the older "saves an indirect call" framing does not.
```
- new:
```
**Why mono, not virtual dispatch.** Monomorphisation makes the call
target visible to the optimiser, unlocking inlining and downstream
loop transformations that virtual dispatch prevents in principle.
On a saturating branch predictor with a monomorphic indirect
target, the indirect call itself is comparable in cost to a
non-inlined direct call — the win is in what the optimiser can do
with the visible target, not in the call instruction. The
end-to-end gain shrinks toward zero on larger callee bodies and
cold call sites, but the architectural claim — "mono enables
optimisations vdisp forbids" — holds across the spectrum
(`bench/mono_dispatch.py` and the corresponding JOURNAL bench-notes
entry record the measured ratios).
```

This drops "(the empirically-grounded version)" from the header,
removes "the original rationale … refutes" framing, and folds the
indirect-call-cost observation into a state-only form (it is true,
in the present tense, that the indirect call is comparable in
cost). The bench reference becomes a parenthetical pointer to
JOURNAL, not a claim-and-refutation narrative.

- [ ] **Step 3: Post-test**

```bash
grep -nE 'empirically-grounded version|original rationale|refutes that specific claim' docs/DESIGN.md
```

Expected: empty.

```bash
grep -n 'mono makes the call target\|optimiser, unlocking inlining' docs/DESIGN.md
```

Expected: at least one match — the substantive rationale survived.

- [ ] **Step 4: Commit**

```bash
git add docs/DESIGN.md
git commit -m "design-md-consolidation 2.4: condense Decision 11 mono-vs-vdisp correction history to state-only rationale"
```

---

## Task 5: Future-iter speculations — per-site decisions

**Files:** Modify: `docs/DESIGN.md` (5 sites at iter start: lines
616, 762, 1063, 1134, 1424, 1750. Site 616 is moot — it lives
inside Decision 7's body, deleted by Task 1. Verify at Step 1.)

Per-site decisions:

| Site (iter-start line) | Text fragment | Decision |
|------------------------|---------------|----------|
| 616 | `A future iter may upgrade the exhaustiveness check to recognise the \`true\`+\`false\` arm pair…` | MOOT — inside Decision 7 deleted by Task 1; verify the line is gone. |
| 762 | `A future iter may layer a per-fn-arena optimisation on top: when a fn's return type contains no boxed ADT…` | CONDENSE — the per-fn arena is already shipped (Decision 9 §"Per-fn arena via stack `alloca`"). Drop the "future iter may layer" framing; the layering exists. |
| 1063 | `A future iter that proposes any of laziness, recursive value bindings, shared mutable state, or any feature that creates cycles must either prove the cycle is collectible by an extension (e.g. linear ownership) or be rejected at design time.` | KEEP, tighten — this is a binding language constraint; restructure as an assertive forward-looking rule (drop "A future iter that proposes" wrapper; lead with the prohibition). |
| 1134 | `a later iter (deferred) makes the explicit annotation mandatory and rejects \`Implicit\` for boxed parameter types.` | REMOVE — non-binding aspiration; if it ever ships, the language at that point describes itself. |
| 1424 | `Concrete design deferred until the need materialises with a real workload.` | KEEP, condense — the binding part (no tracing GC backstop, ownership/linear extension) precedes; the "deferred" sentence is the punchline. Condense to remove the "until the need materialises" framing. |
| 1750 | `A future milestone may add a Prelude when concrete LLM-author code surfaces a case that benefits from \`Show a\` / \`Eq a\` / \`Ord a\` over per-type primitive functions. Until then, primitive output goes through \`io/print_int\` / \`io/print_bool\` / \`io/print_str\` directly.` | REMOVE the first sentence; KEEP the second (the second describes current state). |

- [ ] **Step 1: Pre-test — locate surviving sites**

```bash
grep -nE 'A future (iter|milestone|Prelude) may|A future iter that|a later iter \(deferred\)|Concrete design deferred' docs/DESIGN.md
```

Expected: 5 sites (line 616 from the original is gone; the
remaining 5 sites have shifted by -32-30-? lines after Tasks 1-3).

If 6 sites match, line 616's content survived Task 1 — investigate
before continuing.

- [ ] **Step 2: Site 762 (per-fn arena)**

Read the surrounding paragraph (typically a 10-line block opening
with `A future iter may layer a per-fn-arena optimisation on top`).
The next subsection (`### Per-fn arena via stack \`alloca\``)
already describes the implemented arena. The "future iter may
layer" paragraph is announcement; the next subsection is the spec.
Delete the whole paragraph (the announcement); the implementation
description stands alone.

Edit:
- old (the announcement paragraph):
```
A future iter may layer a per-fn-arena optimisation on top: when
a fn's return type contains no boxed ADT, ADT boxes allocated
inside that fn cannot escape, so an arena freed at fn return is
sound by construction. That requires
escape analysis, the corresponding AST/IR plumbing, and is its
own design pass. Boehm-everything is the floor; arena is an
optimisation above it.

```
- new: (empty — delete entire paragraph; the next subsection
  header `### Per-fn arena via stack \`alloca\`` follows directly.)

Note: the paragraph may already differ slightly from the example
above due to Sweep 1's history-anchor strips. Verify by reading
the actual current text first.

- [ ] **Step 3: Site 1063 (binding prohibition — KEEP, tighten)**

Edit:
- old (current form):
```
A future iter that proposes any of laziness, recursive value
bindings, shared mutable state, or any feature that creates
cycles must either prove the cycle is collectible by an
extension (e.g. linear ownership) or be rejected at design time.
```
- new:
```
Laziness, recursive value bindings, shared mutable state, or any
feature that creates cycles is rejected at design time unless the
proposal proves the cycle is collectible by an extension (e.g.
linear ownership).
```

This restructures from "a future iter that proposes X must Y" to
"X is rejected unless Y" — the same constraint, in assertive
present-tense form.

- [ ] **Step 4: Site 1134 (REMOVE)**

Read the surrounding paragraph; the sentence to remove is in the
list under "Schema additions" (the Type::Fn parameter modes
paragraph). The current text reads:

```
The legacy `(con T)` form is treated as `(own T)` semantically
throughout existing fixtures; a later iter (deferred) makes the
explicit annotation mandatory and rejects `Implicit` for boxed
parameter types.
```

Edit:
- old: ` a later iter (deferred) makes the explicit annotation mandatory and rejects \`Implicit\` for boxed parameter types.`
- new: `` (empty)

The remaining sentence reads "The legacy `(con T)` form is treated
as `(own T)` semantically throughout existing fixtures." — this is
state-only and stands alone.

Adjust capitalisation/period: the ", a later iter" was preceded by
a semicolon; after removal, the sentence ends at "fixtures." and
the semicolon becomes a period. Verify the final form scans:

```
The legacy `(con T)` form is treated as `(own T)` semantically
throughout existing fixtures.
```

- [ ] **Step 5: Site 1424 (KEEP, condense)**

Read the surrounding paragraph (section "Adjacent extensions for
mutability"). The current text reads:

```
If future workloads need mutable arrays, hash tables, or other
inherently mutable primitives, the answer is **not** a tracing GC
backstop. The answer is a separate ownership/linear extension
that gates mutability behind static single-owner discipline. RC
+ uniqueness is the universal floor; ownership extends it for
specific high-performance primitives without rebreaking the
acyclicity invariant. Concrete design deferred until the need
materialises with a real workload.
```

Edit:
- old: `Concrete design deferred until the need materialises with a real workload.`
- new: (empty — drop the entire sentence; the prior content carries
  the binding constraint, the "deferred" sentence adds nothing.)

The paragraph now ends at "without rebreaking the acyclicity
invariant." which is a clean state-only conclusion.

- [ ] **Step 6: Site 1750 (REMOVE first sentence, KEEP second)**

Edit:
- old:
```
A future milestone may add a Prelude when concrete LLM-author code
surfaces a case that benefits from `Show a` / `Eq a` / `Ord a`
over per-type primitive functions. Until then, primitive output
goes through `io/print_int` / `io/print_bool` / `io/print_str`
directly.
```
- new:
```
Primitive output goes through `io/print_int` / `io/print_bool` /
`io/print_str` directly.
```

The second sentence describes current state (how primitive output
works today); the first is pure aspiration for an unbuilt feature.

- [ ] **Step 7: Post-test**

```bash
grep -nE 'A future (iter|milestone|Prelude) may|A future iter that|a later iter \(deferred\)|Concrete design deferred' docs/DESIGN.md
```

Expected: empty.

```bash
grep -nE 'Laziness, recursive value bindings, shared mutable state' docs/DESIGN.md
```

Expected: 1 line — the binding prohibition (Site 1063, in
restructured form) survived.

- [ ] **Step 8: Commit**

```bash
git add docs/DESIGN.md
git commit -m "design-md-consolidation 2.5: future-iter speculations — remove aspirations, tighten binding prohibitions"
```

---

## Task 6: Final validation + JOURNAL entry + roadmap update

**Files:** Read: `docs/DESIGN.md`. Append: `docs/JOURNAL.md`.
Modify: `docs/roadmap.md`.

- [ ] **Step 1: Composite acceptance grep**

```bash
grep -nE 'REVERTED|preserved for the audit trail|Migration plan|[Oo]riginally framed|original rationale|empirically-grounded version|A future (iter|milestone|Prelude) may|A future iter that|a later iter \(deferred\)|Concrete design deferred' docs/DESIGN.md
```

Expected: empty.

```bash
grep -n 'Term::If' docs/DESIGN.md
```

Expected: at least one match in the Term-schema section (line ~1909
region, `{ "t": "if",  "cond": Term, "then": Term, "else": Term }`)
— `Term::If` exists in the language; the schema shows it. The
Decision 7 *block* is gone, but `Term::If` itself is still
described in the schema.

- [ ] **Step 2: cargo test sweep**

```bash
cargo test --workspace 2>&1 | grep -E "test result:|FAILED" | head -25
```

Expected: every line ends `0 failed`.

- [ ] **Step 3: Bench gates**

```bash
python3 bench/check.py 2>&1 | tail -5
python3 bench/compile_check.py 2>&1 | tail -5
```

Expected: each summary line shows `0 regressed`.

- [ ] **Step 4: Sweep-1 acceptance grep stays clean**

```bash
grep -nE 'Iter [0-9]+[a-z]?(\.[0-9]+)?|Family [0-9]+|^[^/]*2026-[0-9]{2}-[0-9]{2}|\*\*Status: |pre-[0-9]+[a-z]?|[0-9]+[a-z]? sketch|21.g' docs/DESIGN.md
```

Expected: empty (no regression of Sweep-1's invariant).

- [ ] **Step 5: Append JOURNAL entry**

Append to `docs/JOURNAL.md`. Template:

```markdown

## 2026-05-10 — Iteration design-md-consolidation 2: REVERTED + migration + correction history + future speculations

Second iteration of the milestone defined in
`docs/specs/0004-design-md-consolidation.md`. Sweep 2 closes
the "REVERTED + migration plans + correction history + future
speculations" item: the audit-trail preservation of Decision 7,
the 7-point Decision 10 Migration plan, the Decision 9
narrative-of-changes opener, the Decision 11 mono-vs-vdisp
correction history, and the non-binding future-iter speculations
are removed or condensed. DESIGN.md now describes only the current
state plus the timeless rationale; the discarded narratives live
in JOURNAL (this entry plus the original iter entries that
recorded each change at the time).

Sections deleted entirely:
- Decision 7 (Term::If REVERTED block) — 32 lines. `Term::If`
  exists in the language; the witness is the Term-schema
  section. Decision numbering preserves the gap (Decision 6 →
  Decision 8) so JOURNAL cross-references remain stable.
- Decision 10 §"Migration plan" — 7-point list of past iters
  (annotation feature → RC runtime → uniqueness inference →
  reuse hints → drop-iterative → validation bench → advisory
  lint). The end state is described in the rest of Decision 10.

Paragraphs condensed:
- Decision 9 narrative-of-changes opener ("Originally framed as
  X; then re-framed as Y; the revision flips Z") → one-line
  state opener leading into the existing three RC/Boehm/bump
  bullets.
- Decision 9 pre-Decision-9 history paragraphs ("Originally,
  every ADT box, lambda env, and closure pair was allocated
  with bare malloc and never freed…") removed.
- Decision 11 §"Why mono, not virtual dispatch (the
  empirically-grounded version)" → state-only form. The
  substantive rationale ("mono enables optimisations vdisp
  forbids") survives; the correction history (`The original
  rationale implicitly argued … the 2026-05-10 micro-benchmark
  refutes that specific claim`) is dropped.

Future-iter speculations — per-site decisions:
- Site `A future iter may layer a per-fn-arena optimisation`
  REMOVED — the per-fn arena is shipped; the announcement is
  history.
- Site `A future iter that proposes any of laziness…` KEPT,
  TIGHTENED — restructured from "future iter that proposes X
  must Y" to "X is rejected unless Y" (assertive present-tense
  form of the same binding constraint).
- Site `a later iter (deferred) makes the explicit annotation
  mandatory` REMOVED — non-binding aspiration.
- Site `Concrete design deferred until the need materialises`
  REMOVED — the binding content (no tracing GC backstop,
  ownership/linear extension) precedes; the "deferred" sentence
  was the unneeded punchline.
- Site `A future milestone may add a Prelude` REMOVED first
  sentence (aspiration), KEPT second sentence (current-state
  description of how primitive output works today).

Acceptance:
- composite grep
  `grep -nE 'REVERTED|preserved for the audit trail|Migration plan|[Oo]riginally framed|original rationale|empirically-grounded version|A future (iter|milestone|Prelude) may|A future iter that|a later iter \(deferred\)|Concrete design deferred' docs/DESIGN.md`
  returns empty.
- Sweep-1 invariant grep stays empty (no regression).
- `cargo test --workspace` 0 FAILED.
- `bench/check.py` 0 regressed; `bench/compile_check.py` 0 regressed.
- `Term::If` in the Term schema (Data-model section) still
  present; only the Decision 7 *block* is gone, not the AST node.
- DESIGN.md size: <X> lines (down from 2253 — record the actual
  number).

Tasks (commit subjects):

- design-md-consolidation 2.1: delete Decision 7 (Term::If REVERTED block)
- design-md-consolidation 2.2: condense Decision 9 narrative-of-changes opener + drop pre-Decision-9 history paragraphs
- design-md-consolidation 2.3: delete Decision 10 §Migration plan (7-point completed-iter list)
- design-md-consolidation 2.4: condense Decision 11 mono-vs-vdisp correction history to state-only rationale
- design-md-consolidation 2.5: future-iter speculations — remove aspirations, tighten binding prohibitions

Carried into sweep 3: schema SoT inversion (DESIGN.md §Data-model
becomes the canonical schema; ast.rs gains a doc-comment pointing
to it; new drift test `design_schema_drift.rs` enforces structural
agreement). Carried into sweep 4: workflow / cross-reference
cleanup (Project ecosystem `agents/` path correction; Verification
section workflow detail; "What is not (yet) supported" §Recently
lifted gates removal).

Process note: this iter's plan-time composite grep matched the
spec's full intent on the first attempt — no fixup commits for
missed anchor variants. The Sweep 1 lesson held.
```

Adjust the template's specifics to match what was actually edited;
record the actual final DESIGN.md line count.

- [ ] **Step 6: Update roadmap.md**

Edit `docs/roadmap.md`:
- old:
```
    - [x] Sweep 1 — remove history anchors
    - [ ] Sweep 2 — REVERTED + migration plans out
    - [ ] Sweep 3 — schema SoT inversion + data-model hardening
```
- new:
```
    - [x] Sweep 1 — remove history anchors
    - [x] Sweep 2 — REVERTED + migration plans out
    - [ ] Sweep 3 — schema SoT inversion + data-model hardening
```

- [ ] **Step 7: Commit**

```bash
git add docs/JOURNAL.md docs/roadmap.md
git commit -m "design-md-consolidation 2: journal entry + roadmap sweep-2 closed"
```
