bcd41810f4
Reader-facing prose and rustdoc carried opaque shorthand like
"Decision 10", "clause-5", "mq.1", "ct.1", "eob.1", "rpe.1",
"post-mq.3", and "Iter 22b.1:" with no in-repo definition the reader
could follow. This commit replaces every such occurrence in the
durable tier the reader is most likely to land on (design/ ledger +
source //! module headers + the central /// public-item rustdoc) with
an inline content phrase plus, where applicable, a Markdown link to
the file that defines the referenced concept.
design/ ledger — 16 files:
Definition-site headings demoted from "Decision N: <title>" to
"<title>": authoring-surface, tail-calls, memory-model section in
rc-uniqueness.md, dual-allocator section, typeclass design,
effects "pure core + algebraic effects".
Cross-reference sites: "Decision 1" -> canonical-schema principle
(data-model); "Decision 3/4" -> effects + scope-boundaries; "Decision
6" -> authoring-surface; "Decision 8" -> tail-calls; "Decision 9" ->
rc-uniqueness (dual-allocator); "Decision 10" -> memory-model;
"Decision 11" -> typeclasses (model). "clause-5" -> body-link
durability gate. "clause-3" (in language-constraints) ->
bug-class-reintroduction discriminator. "mq.1/2/3", "ct.1/4",
"eob.1", "rpe.1" -> the canonical-form rule / the type-driven
dispatch / the Str carve-out / etc. "post-mq.3" -> "type-driven".
design/contracts/feature-acceptance.md: file-local "clauses 1/2/3"
-> "criteria 1/2/3" (sprachliche Kohärenz mit der File-Überschrift
"Feature-acceptance criterion"); "the clause-3 mechanism" -> "the
bug-class-reintroduction discriminator".
Source //! module headers — 24 files:
Stripped "Iter X.Y:" prefixes and "(Decision N)" / "(mq.X)" tags
from spec_drift, uniqueness, reuse_shape, migrate_canonical_types,
typeclass_22b{2,3,c}, suppress_filter, lift, mono, linearity,
diagnostic, method_dispatch_pin, method_collision_pin,
no_per_type_print_ops, mq3_multi_class_e2e, print_mono_body_shape,
print_no_leak_pin, cli_diag_human_workspace_load_error,
ct1_check_cli, prose snapshot, unbound_in_instance_method_pin,
mono_xmod_ctor_pattern, desugar.
Central /// public-item rustdoc:
ast.rs (full sweep — every "Iter X" + "Decision N" prefix
reformulated; mode/Type::Fn rustdoc now points at memory-model.md;
Constraint / SuperclassRef / InstanceDef / ClassDef rustdoc points
at typeclasses contract).
diagnostic.rs (all "(Iter X)" / "(mq.X)" tags on diagnostic codes
removed).
lib.rs (FORM_A_SPEC rustdoc points at authoring-surface.md
instead of "Decision 6").
canonical.rs (type_hash + Float-literal rustdoc).
Still outstanding (for a follow-up commit): ~500 inline `//`
code-body comments with `Iter X.Y` markers across the workspace, and
a handful of `///` rustdoc items in hash_pin / workspace_pin / lift /
mono / suppress_filter test-pin and internal-function bodies. Code
identifiers (test filenames like `mq3_multi_class_e2e.rs`, function
names like `iter18e_drop_iterative_default_preserves_hashes`) stay
verbatim per the user's "code identifiers stay verbatim" rule.
Tests: design_index_pin 5/5 + docs_honesty_pin 5/5; workspace builds
clean; full `cargo test --workspace` previously green (every
`test result: ok` line, no FAILED line).
100 lines
4.8 KiB
Markdown
100 lines
4.8 KiB
Markdown
# Roundtrip Invariant
|
|
|
|
## Roundtrip Invariant
|
|
|
|
Every well-formed AILang module has a canonical `.ail.json`
|
|
representation (the hashable, content-addressed
|
|
[JSON-AST](data-model.md)) and a hand-authored `.ail` representation
|
|
(Form A, the [authoring projection](authoring-surface.md)). 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.
|
|
|
|
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.** Seven `.ail.json`-only fixtures
|
|
(subject-matter rejection tests) survive in the corpus by
|
|
structural necessity. They participate in their own dedicated
|
|
rejection-shape tests, not in the round-trip gate. (The prelude is
|
|
embedded as `examples/prelude.ail` in `ailang-surface` and parsed
|
|
at compile time via `ailang_surface::parse_prelude`.)
|
|
|
|
Hashing is the consequence the language depends on: BLAKE3 of the
|
|
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
|
|
|
|
Float literals carry an IEEE-754 bit pattern, not a decimal
|
|
approximation. The canonical encoding is
|
|
`{"kind":"float","bits":"<16-lowercase-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](float-semantics.md), "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 five 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::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/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 `.ail` fixture. 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 seven 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,
|
|
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 choice. The [authoring surface](authoring-surface.md)
|
|
introduces the `.ail` 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 the authoring-surface contract makes the property
|
|
quotable on its own and reviewable by the architect agent at every
|
|
milestone close.
|
|
|
|
Ratified by: `crates/ailang-surface/tests/round_trip.rs`.
|