Files
AILang/docs/plans/0013-design-md-consolidation-2-reverted-and-migration.md
T
Brummel 832375f2ac convention: counter-prefix file naming across docs/specs/, docs/plans/, design/contracts/, design/models/
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.
2026-05-28 13:31:31 +02:00

30 KiB
Raw Blame History

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
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
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
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).

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

Expected: empty.

  • Step 5: Commit
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
grep -n '^## Decision 9' docs/DESIGN.md

Note the line number; then:

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:

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:

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
grep -nE '[Oo]riginally framed|Originally, every ADT box|--alloc=bump.*measurement tool' docs/DESIGN.md

Expected: empty.

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
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
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
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
grep -n '^### Migration plan' docs/DESIGN.md

Expected: empty.

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
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
grep -n 'empirically-grounded version' docs/DESIGN.md

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

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
grep -nE 'empirically-grounded version|original rationale|refutes that specific claim' docs/DESIGN.md

Expected: empty.

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
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
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
grep -nE 'A future (iter|milestone|Prelude) may|A future iter that|a later iter \(deferred\)|Concrete design deferred' docs/DESIGN.md

Expected: empty.

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
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
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.

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
cargo test --workspace 2>&1 | grep -E "test result:|FAILED" | head -25

Expected: every line ends 0 failed.

  • Step 3: Bench gates
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
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:


## 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
git add docs/JOURNAL.md docs/roadmap.md
git commit -m "design-md-consolidation 2: journal entry + roadmap sweep-2 closed"