design: ratify form (B) prose projection (iter 20e tidy)
Family-20 tidy-iter: ailang-architect drift review surfaced DESIGN.md silence on the new prose surface as the highest-leverage gap. Decision 6 now has a 'Form (B) — human prose projection' subsection naming the lossy-vs-lossless contract, the no-parser- by-design choice, and the LLM-mediated round-trip. CLI block gained 'ail prose' and 'ail merge-prose'. No code changes. Form (B) does not weaken any Decision 6 invariant: JSON-AST remains the only hashable artefact, form (A) the canonical authoring surface, 30-production grammar unchanged. Two architect findings deferred (prompt-template lockstep test; snapshot-fixture cross-family coupling). Family 20 closes. JOURNAL queue: empty.
This commit is contained in:
@@ -454,6 +454,64 @@ JSON identical to their corresponding `.ail.json` files.
|
||||
constraint-1 budget. No new lexical rule (`tail-app` / `tail-do`
|
||||
are bare ident tokens; no special casing).
|
||||
|
||||
### Form (B) — human prose projection (Family 20)
|
||||
|
||||
**Status: shipped.** Family 20 (Iter 20a–20d, 2026-05-08) ships a
|
||||
second textual projection of the AST: `ailang-prose`, a one-way
|
||||
projection from `Module → human-readable text`. It is **not** an
|
||||
authoring surface; it is the "display" projection that Decision 6's
|
||||
architectural pin (line 167–176) explicitly anticipated:
|
||||
|
||||
> *"Future projections are explicitly anticipated: a visual /
|
||||
> graphical front-end is a plausible second projection for human
|
||||
> review and inspection (display being the one case where non-AI
|
||||
> eyes matter). The architecture leaves room: any producer of
|
||||
> well-formed `ailang-core::ast::Module` values is a valid
|
||||
> front-end."*
|
||||
|
||||
Form (B) targets the specific failure mode where a human reviewer
|
||||
needs to read an AILang module quickly. Form (A) was designed to
|
||||
fit a 30-production EBNF spec and to be parsed zero-shot by foreign
|
||||
LLMs; that prioritisation makes it dense and visually noisy for
|
||||
human readers. Form (B) inverts the trade-offs:
|
||||
|
||||
- **Rust-flavoured surface.** Braces and `=>` for match arms,
|
||||
Rust-aligned 4-level operator precedence, infix arithmetic
|
||||
(`a + b`, not `+(a, b)`), unary `!` for `not`.
|
||||
- **Lossy by design.** Projection elides machinery the LLM can
|
||||
re-derive: `(con T)` wrappers (`(con Int)` → `Int`), the
|
||||
`(fn-type (params ...) (ret ...))` wrap, `(term-ctor T C ...)`
|
||||
collapses to `C(...)`, redundant parens. Only the AST machinery
|
||||
whose information is recoverable from typecheck context.
|
||||
- **Lossless on load-bearing detail.** Mode annotations
|
||||
(`own T`, `borrow T`), effects (`with IO`), explicit `clone`,
|
||||
`reuse-as`, doc strings, type annotations on signatures and
|
||||
lambdas, the `tail` flag — all preserved verbatim.
|
||||
|
||||
Critically, **form (B) has no parser**. Form (A) is round-trippable
|
||||
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.
|
||||
|
||||
Form (B) does not weaken any Decision 6 invariant:
|
||||
|
||||
- The JSON-AST remains the only hashable artefact. Prose is not
|
||||
hashed, not content-addressed, not load-bearing for any
|
||||
cross-module reference.
|
||||
- Form (A) remains the canonical authoring surface. Foreign LLMs
|
||||
still author against form (A); humans review and edit through
|
||||
form (B).
|
||||
- The 30-production grammar of form (A) is unchanged.
|
||||
- `ailang-check` and `ailang-codegen` remain projection-agnostic;
|
||||
`ailang-prose` is a downstream consumer of `ailang-core::ast`,
|
||||
parallel to `ailang-surface` but in the rendering direction only.
|
||||
|
||||
The CLI gains `ail prose <m.ail.json>` (the deterministic
|
||||
projection) and `ail merge-prose <m.ail.json> <edited.prose.txt>`
|
||||
(the mediator-prompt composer); both are listed in the CLI section
|
||||
below.
|
||||
|
||||
## Decision 7: redundancy removal — `Term::If` is not a primitive
|
||||
|
||||
**Status: REVERTED in Iter 14g.** This decision was made on shaky grounds —
|
||||
@@ -1311,6 +1369,10 @@ ail manifest <module.ail.json> — table: name :: type !effects [hash]
|
||||
ail describe <module> <name> — detail of a definition (form-A body)
|
||||
ail render <module.ail.json> — JSON-AST → form-A text (exact inverse of `parse`)
|
||||
ail parse <module.ailx> — form-A text → canonical JSON-AST
|
||||
ail prose <module.ail.json> — JSON-AST → form-B (lossy human prose, no parser; Family 20)
|
||||
ail merge-prose <m.ail.json> <m.prose.txt>
|
||||
— compose the LLM-mediator prompt for the prose round-trip
|
||||
(see docs/PROSE_ROUNDTRIP.md)
|
||||
ail deps <module.ail.json> — list cross-module references
|
||||
ail diff <a.ail.json> <b.ail.json> — content-addressed def-level diff
|
||||
ail workspace <entry.ail.json> — list all modules transitively reachable from entry
|
||||
|
||||
@@ -9167,3 +9167,80 @@ practice the family stays as-shipped.
|
||||
validates the merge product; `merge-prose` only composes the
|
||||
prompt.
|
||||
|
||||
|
||||
## 2026-05-08 — Family-20 tidy-iter (Iter 20e)
|
||||
|
||||
Per CLAUDE.md "Tidy-iter at family boundaries" — every named iter
|
||||
family closes with a tidy-iter. `ailang-architect` ran a drift
|
||||
review of family 20 and reported three actionable items, plus two
|
||||
non-actionable observations.
|
||||
|
||||
### Architect findings
|
||||
|
||||
1. **DESIGN.md silent on form (B).** Decision 6 documents form (A)
|
||||
extensively but never names the new prose surface. The CLI
|
||||
section (`docs/DESIGN.md` "## CLI") lists `render`/`parse` but
|
||||
not `prose`/`merge-prose`. JOURNAL has the design pinning, but
|
||||
DESIGN.md is the canonical spec — this is the highest-leverage
|
||||
fix.
|
||||
2. **Prompt-template drift hazard.** `compose_merge_prose_prompt`
|
||||
in `crates/ail/src/main.rs` and the prompt block in
|
||||
`docs/PROSE_ROUNDTRIP.md` are byte-equivalent twins maintained
|
||||
by hand. No test enforces equality. Bounded — the comment in
|
||||
`main.rs` line 240–242 already names the lockstep — but real.
|
||||
3. **Snapshot-fixture coupling.** `ailang-prose/tests/snapshot.rs`
|
||||
binds prose tests to four 18-family RC fixtures. Any future
|
||||
iter touching those examples must re-render the `.prose.txt`.
|
||||
Not actionable until a real refactor forces it.
|
||||
|
||||
### Resolved this iter
|
||||
|
||||
**Item 1 — ratified.** Two edits to `docs/DESIGN.md`:
|
||||
|
||||
- New subsection at the end of Decision 6 (after Iter 14e
|
||||
refinements, before Decision 7): `### Form (B) — human prose
|
||||
projection (Family 20)`. ~50 lines documenting form (B) as the
|
||||
"display" projection Decision 6's architectural pin already
|
||||
anticipated, the lossy-vs-lossless contract, and the fact that
|
||||
form (B) has no parser by design (round-trip is LLM-mediated,
|
||||
not compiler-passed).
|
||||
- CLI block extended with `ail prose` and `ail merge-prose` lines.
|
||||
|
||||
The new subsection explicitly does NOT weaken any Decision 6
|
||||
invariant: JSON-AST remains the only hashable artefact, form (A)
|
||||
remains the canonical authoring surface, the 30-production grammar
|
||||
is unchanged, and `check`/`codegen` stay projection-agnostic.
|
||||
|
||||
### Deferred
|
||||
|
||||
**Item 2 (prompt-template lockstep)** — the existing
|
||||
`// Keep this template in lockstep with docs/PROSE_ROUNDTRIP.md`
|
||||
comment at `main.rs:240–242` is the audit trail. Adding an
|
||||
equality test would require parsing the Markdown block in the
|
||||
doc; brittle. Stays queued; will close if the next iter touching
|
||||
the prompt actually surfaces the drift.
|
||||
|
||||
**Item 3 (snapshot-fixture coupling)** — not actionable; documented
|
||||
as known consequence of cross-family snapshot binding.
|
||||
|
||||
### Family 20 final state
|
||||
|
||||
- 20a: prose renderer skeleton + `ail prose` CLI
|
||||
- 20b: infix + paren elision + doc-wrap + nested-match
|
||||
- 20c: rolled into 20a
|
||||
- 20d: `ail merge-prose` + `docs/PROSE_ROUNDTRIP.md`
|
||||
- 20e: this tidy-iter — DESIGN.md ratification
|
||||
|
||||
Three deferred polishes from 20b (let-inlining, `do io/print_int`
|
||||
→ `print` sugar, doc-wrap widow control) remain queued; all three
|
||||
need real-corpus signal to know whether the heuristic is right.
|
||||
|
||||
JOURNAL queue: empty again.
|
||||
|
||||
### Files touched this iter
|
||||
|
||||
- `docs/DESIGN.md` — Form (B) subsection in Decision 6, CLI block
|
||||
extension. ~60 lines added.
|
||||
- `docs/JOURNAL.md` — this entry.
|
||||
|
||||
No code changes. Build/test status unchanged from 20d (green).
|
||||
|
||||
Reference in New Issue
Block a user