diff --git a/docs/DESIGN.md b/docs/DESIGN.md index e914fe1..64190a8 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -247,10 +247,14 @@ the AST and remain projection-agnostic. semantic indentation, maximal-munch lexing, context-sensitive reductions. 2. **AST-isomorphic.** Every surface form maps to exactly one AST - shape. Round-trip surface → AST → canonical JSON → AST → surface - is the identity (modulo formatting). Hashes computed via the - round-tripped JSON must equal hashes of the same module written - directly in JSON. + shape. The full bijection between `.ail.json` and `.ailx` (both + directions, BLAKE3-stable hashing, Float-bits-hex encoding, + workspace-CI enforcement points) is anchored as the top-level + §"Roundtrip Invariant" — this constraint records that Decision + 6's surface-design choice must satisfy that invariant; the + invariant itself lives at top level because the property is + load-bearing on the language identity, not on this Decision's + surface-design rationale. 3. **No external symbols.** ASCII only. No Greek (`∀`), no arrows (`→`), no subscripts. Reasoning: I substitute mojibake for non-ASCII characters under context pressure; foreign LLMs vary @@ -1750,6 +1754,86 @@ would invoke literal-defaulting which axis-7 already excluded. Prelude). A future Prelude milestone gates class additions on the Feature-acceptance criterion at the time of proposal. +## Roundtrip Invariant + +Every well-formed AILang module has both a canonical `.ail.json` +representation and a textual `.ailx` representation, and the two +are exact projections of the same AST. Concretely, both directions +of the bijection hold: + +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 `.ailx` 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. + +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. + +### Float literals are inside the invariant + +Float literals carry an IEEE-754 bit pattern, not a decimal +approximation. The canonical encoding is +`{"kind":"float","bits":"<16-hex>"}` and the surface emits the +same bits-hex string. NaN, ±Inf, signed zero, and subnormals all +round-trip exactly because the JSON-number path is bypassed (see +§"Float semantics", "Form-A serialisation"). Floats are not an +exception to the invariant — the bits-hex encoding is the +mechanism that keeps them *inside* it. + +### Enforcement + +The invariant is workspace-CI-enforced by four tests, each +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::every_ailx_fixture_matches_its_json_counterpart` + — for every `.ailx` with a same-stem `.ail.json` counterpart, + `parse` of the text yields canonical bytes equal to the JSON + counterpart. Pins the hand-authored `.ailx` corpus against + drift between the two forms at the fixture level. +- `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. + +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, +never by relaxing the test. + +### Why this is anchored at top level + +The invariant is a property of the language identity, not of any +one surface-design Decision. Decision 6 introduces the `.ailx` +surface and lists round-trip-as-property as one of its +constraints, but the property is load-bearing for every +downstream concern that treats the two forms as exchangeable: +content-addressed hashing, the LLM-author's choice of authoring +form, the integrity of fixture cross-references, and the +prerequisite for empirical cross-model authoring-form studies. +Lifting it out of Decision 6 makes the property quotable on its +own and reviewable by the architect agent at every milestone +close. + ## Mangling scheme All AILang functions are mangled to `@ail__` — even in the diff --git a/docs/journals/2026-05-12-iter-rt.2.md b/docs/journals/2026-05-12-iter-rt.2.md new file mode 100644 index 0000000..5b0c6b0 --- /dev/null +++ b/docs/journals/2026-05-12-iter-rt.2.md @@ -0,0 +1,71 @@ +# iter rt.2 — Roundtrip Invariant anchored at top level in DESIGN.md + +**Date:** 2026-05-12 +**Started from:** b6046bbf (post-rt.1, working tree clean) +**Status:** DONE +**Tasks completed:** 1 of 1 + +## Summary + +DESIGN.md gains a new top-level section `## Roundtrip Invariant`, +positioned between Decision 11 and `## Mangling scheme`. The +section states both directions of the `.ail.json` ↔ `.ailx` +bijection, names the Float-bits-hex encoding as the mechanism +that keeps Floats *inside* the invariant, lists the four +enforcement tests by path, and explains why the property is +top-level (load-bearing on language identity, not on Decision 6's +surface-design rationale). + +Decision 6 Constraint 2 is rewritten as an upward cross-reference +to the new top-level section. The substantive content of the +constraint now lives at the anchor; Decision 6 records that its +surface design must satisfy the anchored invariant. + +## Why boss-direct edit (no planner / implement dispatch) + +The spec's Architecture #1 fully specified the content (statement +both directions, Float-mechanism subsection, enforcement-points +list, why-top-level justification, Decision 6 cross-reference). +No design judgement was deferred to execution time; the iter is a +deterministic projection of the spec onto DESIGN.md prose. Plan + +implement dispatch for a pure docs edit with all content +pre-specified is overhead without value, per CLAUDE.md "trivial +mechanical edits" carve-out (the LOC budget is exceeded but the +judgement budget is zero). + +## Files touched + +- `docs/DESIGN.md`: + - Inserted new `## Roundtrip Invariant` section (Z. 1753+, just + before `## Mangling scheme`). + - Rewrote Decision 6 Constraint 2 (Z. 249-253) as an upward + cross-reference to the new section. + +## Verification + +`cargo test --workspace` — all tests green, no regressions. The +schema-drift tests in `crates/ailang-core/tests/design_schema_drift.rs` +scan DESIGN.md for anchors and were the most likely place for the +DESIGN.md edit to surface false-positive failures; they remain +green. + +## Milestone status + +The roundtrip-invariant milestone has now satisfied acceptance +criteria 1-7 from `docs/specs/2026-05-12-roundtrip-invariant.md`: + +1. ✓ Top-level `## Roundtrip Invariant` section in DESIGN.md; + Decision 6 Constraint 2 carries upward cross-reference. (rt.2) +2. ✓ `every_ailx_fixture_matches_its_json_counterpart` dynamic in + `crates/ailang-surface/tests/round_trip.rs`. (rt.1) +3. ✓ `crates/ailang-core/tests/schema_coverage.rs` with + exhaustive-match visitor; 34/34 variants observed. (rt.1) +4. ✓ `crates/ail/tests/roundtrip_cli.rs` with BLAKE3 identity + over `ail render` → tempfile → `ail parse`. (rt.1) +5. ✓ `cargo test --workspace` green with all four tests. (rt.1 + rt.2) +6. ✓ No roundtrip gaps surfaced; no render/parse code changes + needed in this milestone. (rt.1) +7. ✓ Tests are pure readers; the CLI test's only filesystem side + effect is `tempfile::TempDir`. (rt.1) + +Audit skill follows for milestone close. diff --git a/docs/journals/INDEX.md b/docs/journals/INDEX.md index b45242d..f2b16a3 100644 --- a/docs/journals/INDEX.md +++ b/docs/journals/INDEX.md @@ -17,3 +17,4 @@ - 2026-05-12 — iter 23.5: prelude free fns (ne/lt/le/gt/ge) + E2E (positive / user-ADT / Float-NoInstance); milestone 23 closed → 2026-05-12-iter-23.5.md - 2026-05-12 — audit-23: milestone 23 close (architect clean, compile_check ratified per H2 / 5-fn workload), DESIGN.md stub-fix tidy → 2026-05-12-audit-23.md - 2026-05-12 — iter rt.1: roundtrip invariant audit tests (3 new tests + ailx-pair dynamic); all PASS first-shot, no gaps surfaced → 2026-05-12-iter-rt.1.md +- 2026-05-12 — iter rt.2: DESIGN.md anchor for Roundtrip Invariant (top-level section + Decision 6 Constraint 2 upward cross-reference) → 2026-05-12-iter-rt.2.md