iter form-a.1 (Tasks 6-12): milestone close
Second half of the form-a-default-authoring milestone-close iter
(Boss-decided strategy C, big-bang). All seven tasks DONE; cargo
test --workspace green at every per-task boundary.
T6 — Bench-driver suffix flip from .ail.json to .ail across 4
Python scripts + run.sh. compile_check.py + cross_lang.py exit 0.
T7 — Re-author e2e.rs raw-JSON-inspect tests:
- diff_detects_changed_def — derive sum.ail.json on-the-fly via
`ail parse examples/sum.ail` into tempdir, then mutate + diff.
- borrow_own_demo_modes_are_metadata_only — same pattern.
- reuse_as_demo_under_rc_uses_inplace_rewrite — same pattern.
- render_parse_round_trip_canonical — RETIRED (subsumed by T1's
cli_parse_then_render_then_parse_is_idempotent over whole corpus).
- ail_run_accepts_ail_source_with_same_stdout_as_ail_json —
re-authored to derive hello.ail.json in a per-process tempdir
from hello.ail via `ail parse`, then assert dual-form stdout.
T8 — Bulk-delete 156 non-carve-out .ail.json. Inventory:
8 .ail.json (carve-outs, alphabetical: broken_unbound + prelude
+ 3× test_22b2_* + 3× test_ct1_*) + 157 .ail. carve_out_inventory
test un-#[ignore]'d and green. Forward-pulled 20 repairs that the
T1-5 dispatch's recon missed (12 Group-B suffix + 5 Group-A
load_workspace + 3 ail_run sites). Also forward-pulled T9 Step 5
(schema_coverage corpus flip from .ail.json to .ail) to satisfy
T8's green-gate.
T9 — Retire obsolete roundtrip tests:
- print_then_parse_round_trips_every_fixture (round_trip.rs)
- every_ail_fixture_matches_its_json_counterpart (round_trip.rs)
- cli_render_then_parse_preserves_canonical_bytes_on_every_fixture
- Dead helpers: list_json_fixtures ×2, round_trip_one,
strip_trailing_newlines.
Schema-coverage corpus already flipped in T8 (forward-pull).
T10 — DESIGN.md §"Roundtrip Invariant" (lines 2027-2109) restated
with parse-determinism + idempotency + CLI-pipeline-idempotency +
carve-out-anchor framing. Five surviving enforcement tests named.
§"Float literals" and §"Why anchored at top level" preserved.
T11 — §A4 doctrine edits: CLAUDE.md:5-6 + DESIGN.md:465-466.
Canonical form remains JSON-AST; authoring projection is .ail;
build derives JSON-AST in-process via ailang_surface::parse.
T12 — Milestone close:
- WhatsNew entry: user-facing language, lead with the change.
- Roadmap: [milestone] form-a struck [x] with closing note.
- Final inventory verified: 8 .ail.json + 157 .ail.
- Final cargo test --workspace: 557 passed, 0 failed, 3 ignored.
- bench/compile_check.py + bench/cross_lang.py: exit 0.
Test math: pre-iter 558 baseline + 3 new T1 tests = 561, − 1
(T7 retire) − 3 (T9 retire) = 557 final.
INDEX.md appended with the full iter summary covering T1-T12 (the
T1-5 commit at 77b28ad deferred the INDEX line to full-iter close).
Milestone [Form-A as the default authoring surface] structurally
closed. The compile-time-embed carve-out (prelude.ail.json) is
the subject of the queued follow-up milestone [Prelude embed:
Form-A as compile-time source]. audit-form-a runs as the next
dispatch.
This commit is contained in:
+55
-37
@@ -462,8 +462,14 @@ on the shelf for a future iter only if both fail.
|
||||
|
||||
### What this Decision deliberately does not do
|
||||
|
||||
- It does not promote form (A) to the source of truth. Form (A) is
|
||||
one projection; the JSON-AST stays canonical.
|
||||
- It does not change which form is canonical: the JSON-AST remains
|
||||
the hashable, content-addressed representation, and all hashing,
|
||||
content-addressing, and cross-module references flow through it
|
||||
unchanged. What did change in 2026-05-13 (this milestone) is
|
||||
which form is authored: authors now write Form A; the JSON-AST
|
||||
is materialised in-process by callers that need it. The two
|
||||
forms are byte-isomorphic by the round-trip invariant. Form (A)
|
||||
is one projection; the JSON-AST stays canonical.
|
||||
- It does not foreclose visual or graphical front-ends. The crate
|
||||
layout (`core` owns AST; `surface`/`visual`/... are siblings)
|
||||
reserves that lane.
|
||||
@@ -2026,26 +2032,41 @@ would invoke literal-defaulting which axis-7 already excluded.
|
||||
|
||||
## Roundtrip Invariant
|
||||
|
||||
Every well-formed AILang module has both a canonical `.ail.json`
|
||||
representation and a textual `.ail` representation, and the two
|
||||
are exact projections of the same AST. Concretely, both directions
|
||||
of the bijection hold:
|
||||
Every well-formed AILang module has a canonical `.ail.json`
|
||||
representation (the hashable, content-addressed JSON-AST) and a
|
||||
hand-authored `.ail` representation (Form A, the authoring
|
||||
projection). The JSON-AST is derived from `.ail` in-process by
|
||||
consumers that need it; the round-trip is now the *property* of
|
||||
that derivation, not the byte-level agreement of two on-disk forms.
|
||||
|
||||
1. **JSON → text → JSON.** For every valid `.ail.json` module `J`,
|
||||
`parse(print(load_module(J)))` is canonical-byte-equal to
|
||||
`canonical::to_bytes(load_module(J))`. The textual surface is
|
||||
the inverse of the loader composed with the printer; no
|
||||
information is lost across the round-trip.
|
||||
2. **text → JSON → text.** For every well-formed `.ail` text `t`,
|
||||
`parse(t)` is a complete AST, and re-printing `print(parse(t))`
|
||||
produces a text that re-parses to the same AST (modulo
|
||||
formatting). The parser is total over the well-formed surface
|
||||
and the printer is deterministic.
|
||||
Concretely:
|
||||
|
||||
1. **Parse-determinism.** For every well-formed `.ail` text `t`,
|
||||
`ailang_surface::parse(t)` produces a unique AST. The parser is
|
||||
a pure function of input — no randomness, no time dependence,
|
||||
no environment leak. Hashing `canonical::to_bytes(parse(t))` is
|
||||
therefore well-defined for any `.ail` source.
|
||||
|
||||
2. **Idempotency under print.** For every well-formed `.ail` text
|
||||
`t`, `canonical(parse(t)) == canonical(parse(print(parse(t))))`.
|
||||
The printer is a left-inverse of the parser modulo canonical
|
||||
form: print-then-parse is a no-op on the canonical bytes.
|
||||
|
||||
3. **CLI-pipeline idempotency.** For every `.ail` fixture, the
|
||||
public CLI pipeline `ail parse | ail render | ail parse` is
|
||||
byte-identical to direct `ail parse` of the source `.ail`.
|
||||
Pins drift that crate-internal tests cannot see.
|
||||
|
||||
4. **Carve-out anchor.** Eight `.ail.json`-only fixtures (seven
|
||||
subject-matter rejection tests + one compile-time-embed for
|
||||
the prelude) survive in the corpus by structural necessity.
|
||||
They participate in their own dedicated rejection-shape or
|
||||
embed-mechanism tests, not in the round-trip gate.
|
||||
|
||||
Hashing is the consequence the language depends on: BLAKE3 of the
|
||||
canonical bytes is identical for the two paths, so content-
|
||||
addressing is form-agnostic. An LLM author can pick either form
|
||||
without changing the identity of the module it produces.
|
||||
canonical bytes is well-defined for any `.ail` source via parse-
|
||||
determinism. An LLM author writes `.ail`; the build derives the
|
||||
canonical hash without ambiguity.
|
||||
|
||||
### Float literals are inside the invariant
|
||||
|
||||
@@ -2065,29 +2086,26 @@ operating on the `examples/` corpus via dynamic `read_dir`
|
||||
collection (no hardcoded fixture list, so newly added fixtures
|
||||
inherit the gate automatically):
|
||||
|
||||
- `crates/ailang-surface/tests/round_trip.rs::print_then_parse_round_trips_every_fixture`
|
||||
— for every `.ail.json`, `print` then `parse` produces canonical-
|
||||
byte-equal output. Direction 1 above.
|
||||
- `crates/ailang-surface/tests/round_trip.rs::parse_is_deterministic_over_every_ail_fixture`
|
||||
— for every `.ail`, parse twice and assert canonical-byte
|
||||
equality between the two ASTs. Direction 1 above.
|
||||
- `crates/ailang-surface/tests/round_trip.rs::parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`
|
||||
— for every `.ail` text `t`, `parse(t)` and `parse(print(parse(t)))`
|
||||
produce canonical-byte-equal AST. Direction 2 above.
|
||||
- `crates/ailang-surface/tests/round_trip.rs::every_ail_fixture_matches_its_json_counterpart`
|
||||
— for every `.ail` with a same-stem `.ail.json` counterpart,
|
||||
`parse` of the text yields canonical bytes equal to the JSON
|
||||
counterpart. Pins the hand-authored `.ail` corpus against
|
||||
drift between the two forms at the fixture level.
|
||||
- `crates/ail/tests/roundtrip_cli.rs::cli_parse_then_render_then_parse_is_idempotent`
|
||||
— for every `.ail`, the public CLI pipeline `ail parse | ail render | ail parse`
|
||||
reproduces canonical bytes byte-identical to direct `ail parse`.
|
||||
Pins drift internal tests cannot see.
|
||||
- `crates/ailang-core/tests/schema_coverage.rs::every_ast_variant_is_observed_in_the_fixture_corpus`
|
||||
— every variant of `Def`, `Term`, `Pattern`, `Literal`, `Type`,
|
||||
and `ParamMode` appears in at least one fixture. The visitor
|
||||
matches each AST enum exhaustively without wildcard arms, so
|
||||
any new AST variant fails to compile until the visitor is
|
||||
extended in lockstep — the coverage table cannot silently fall
|
||||
behind the schema.
|
||||
- `crates/ail/tests/roundtrip_cli.rs::cli_render_then_parse_preserves_canonical_bytes_on_every_fixture`
|
||||
— for every `.ail.json`, the public CLI pipeline `ail render` →
|
||||
tempfile → `ail parse` reproduces BLAKE3-identical canonical
|
||||
bytes. Pins the user-facing CLI against drift that crate-
|
||||
internal tests cannot see.
|
||||
and `ParamMode` appears in at least one `.ail` fixture (corpus
|
||||
flipped from `.ail.json` to `.ail` at iter form-a.1). New AST
|
||||
variants fail compile until the visitor and corpus are extended
|
||||
in lockstep.
|
||||
- `crates/ailang-core/tests/carve_out_inventory.rs::examples_ail_json_inventory_matches_carve_outs`
|
||||
— exactly the eight named carve-out files exist under
|
||||
`examples/*.ail.json` at any commit. A new `.ail.json` or a
|
||||
missing carve-out fails the test.
|
||||
|
||||
A new fixture or a new AST variant that violates the invariant
|
||||
fails one of these tests; the fix is in render or parse code,
|
||||
|
||||
Reference in New Issue
Block a user