Files
AILang/docs/plans/0059-iter-form-a.0.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

9.4 KiB

Iter form-a.0 — Prelude Pilot — Implementation Plan

Parent spec: docs/specs/0025-form-a-default-authoring.md

For agentic workers: REQUIRED SUB-SKILL: use skills/implement to run this plan. Steps use - [ ] checkboxes for tracking.

Goal: Render examples/prelude.ail.json to its Form-A sibling examples/prelude.ail and prove that the existing roundtrip-CI gate (every_ail_fixture_matches_its_json_counterpart) plus the parallel idempotency test both pass on the new file, validating the migration mechanism on the hardest single file before iter 1 commits to the bulk test-infrastructure refactor.

Architecture: One CLI invocation (ail render) produces the Form-A text from the canonical JSON-AST. The two relevant tests in crates/ailang-surface/tests/round_trip.rs auto-discover fixtures via read_dir(examples/); the freshly added .ail lands in their corpus on the next cargo test run with no test- code changes. Iter 0 deliberately keeps prelude.ail.json on disk alongside the new .ail — the JSON counterpart is the byte-witness the gate uses, and that gate is the safety net that makes the pilot worth running.

Tech Stack: ail render CLI subcommand (Form-A printer in ailang-surface::print), crates/ailang-surface/tests/round_trip.rs (auto-discovering gate + idempotency tests), docs/journals/ append convention.

Files this plan creates or modifies:

  • Create: examples/prelude.ail — Form-A rendering of examples/prelude.ail.json, ~6386 bytes / 116 lines, produced verbatim by ail render.
  • Create: docs/journals/2026-05-13-iter-form-a.0.md — per-iter journal entry.
  • Modify: docs/journals/INDEX.md:53 (append-only, one new line at line 54).

No source-code edits, no test edits, no CLAUDE.md / DESIGN.md edits. The two existing roundtrip tests pick up the new fixture automatically because both use read_dir(examples/) discovery.


Task 1: Render examples/prelude.ail and verify both auto-discovering roundtrip tests pass

Files:

  • Create: examples/prelude.ail

  • Test (existing, no edit): crates/ailang-surface/tests/round_trip.rs:120-204 (every_ail_fixture_matches_its_json_counterpart) and crates/ailang-surface/tests/round_trip.rs:216-277 (parse_then_print_then_parse_is_idempotent_on_every_ail_fixture).

  • Step 1: Render the prelude via ail render

Run from the workspace root:

cargo run -q --bin ail -- render examples/prelude.ail.json > examples/prelude.ail

The CLI dispatch lives at crates/ail/src/main.rs:424-427:

Cmd::Render { path } => {
    let m = ailang_surface::load_module(&path)?;
    print!("{}", ailang_surface::print(&m));
}

render takes a single positional path argument and writes Form-A text to stdout; shell redirection captures it to the target file.

Expected: exit 0, no stderr output, examples/prelude.ail created.

  • Step 2: Sanity-check the rendered file's shape

Run:

wc -lc examples/prelude.ail

Expected output (verified during recon): approximately 116 6386 examples/prelude.ail — exactly 116 lines, 6386 bytes. A different count by more than ±1 line is a signal that the renderer drifted between recon and execution; investigate before continuing.

Then verify the file begins with the expected (module prelude header by reading the first 5 lines:

head -n 5 examples/prelude.ail

Expected first line: (module prelude. Expected second line: (data Ordering. If the file does not start this way, the render is wrong; do not continue.

  • Step 3: Run the gate test in isolation

This is the spec's named witness for iter 0 (§A2).

Run:

cargo test -p ailang-surface --test round_trip every_ail_fixture_matches_its_json_counterpart

Expected: PASS. The test (crates/ailang-surface/tests/round_trip.rs:120-204) walks every .ail file under examples/, looks up the same-stem .ail.json counterpart, and asserts that parsing the .ail produces canonical bytes byte-equal to loading the JSON. The freshly added prelude.ail (with its retained prelude.ail.json counterpart) is the new corpus element this iter introduces; the test must report at least one more fixture passing than the previous run (or the same count if the test summary doesn't print per-file).

If the test FAILS on the new prelude: the failure mode is E1 from the spec (a fixture round-trips before migration but the migrated .ail fails to parse, or parses to different bytes). The bug is in ail render or ailang_surface::parse; this iter halts and the bug fix takes precedence. Do NOT delete or alter prelude.ail to make the test pass.

  • Step 4: Run the parallel idempotency test

The recon flagged this test as the second one that auto-discovers .ail fixtures and would react to the new file. It checks a different property (idempotency under print) and must also pass.

Run:

cargo test -p ailang-surface --test round_trip parse_then_print_then_parse_is_idempotent_on_every_ail_fixture

Expected: PASS. The test (crates/ailang-surface/tests/round_trip.rs:216-277) walks every .ail file and asserts canonical(parse(t)) == canonical(parse(print(parse(t)))). If this fails on the new prelude while the gate test passes, the Form-A printer is non-idempotent on some feature prelude uses — a deeper render bug that the gate test does not catch. Same halt rule: do not alter the rendered file.

  • Step 5: Run the full workspace test suite for regression safety

Run:

cargo test --workspace

Expected: PASS, identical pass count to pre-iter baseline plus zero (no new tests are added in this iter). The recon explicitly verified that no .ail.json-walking test (print_then_parse_round_trips_every_fixture, every_ast_variant_is_observed_in_the_fixture_corpus, cli_render_then_parse_preserves_canonical_bytes_on_every_fixture) has its corpus changed by adding a sibling .ail, so the only tests that should react are the two run in Steps 3-4; the full suite is the sanity check that nothing unexpected fires.


Task 2: Write the per-iter journal entry and append the INDEX line

Files:

  • Create: docs/journals/2026-05-13-iter-form-a.0.md

  • Modify: docs/journals/INDEX.md (append one line at the end of file — current last line is 53).

  • Step 1: Write the journal file

Create docs/journals/2026-05-13-iter-form-a.0.md with the following content. The 116 lines / 6386 bytes figures are the recon-observed values; if Task 1 Step 2 observed different values (it should not, the renderer is byte-stable), use the actual wc -lc output and adjust the INDEX line in Step 2 to match.

# Iter form-a.0 — Prelude Pilot

**Date:** 2026-05-13
**Parent spec:** `docs/specs/0025-form-a-default-authoring.md`
**Parent plan:** `docs/plans/0059-iter-form-a.0.md`

## Outcome

`examples/prelude.ail` rendered from `examples/prelude.ail.json` via
`ail render` (116 lines / 6386 bytes). The two auto-discovering
roundtrip tests in `crates/ailang-surface/tests/round_trip.rs` both
pass on the new fixture without test-code changes:

- `every_ail_fixture_matches_its_json_counterpart` — green; the
  `prelude.ail` parses to canonical bytes byte-equal to
  `prelude.ail.json`.
- `parse_then_print_then_parse_is_idempotent_on_every_ail_fixture`
  — green; the Form-A printer is idempotent over prelude.

`prelude.ail.json` is retained on disk per spec §A2; iter 1 deletes
it alongside the bulk test-infrastructure refactor. This is the
only iteration in the milestone where a fixture is dual-form.

## Why this iter is the pilot

Spec §A2 names prelude as the pilot because it exercises the widest
cross-section of language features in a single file: typeclasses
with superclasses (`Ord extends Eq`), polymorphic free functions
with constraints (`forall<a> where Show a fn print`), IO-effect
signatures, primitive ctor patterns, and the loader's auto-injection
path. A green roundtrip on prelude is strong evidence the bulk
migration in iter 1+ will not surface render or parse bugs.

## Verifications run

- `cargo test -p ailang-surface --test round_trip every_ail_fixture_matches_its_json_counterpart` — PASS
- `cargo test -p ailang-surface --test round_trip parse_then_print_then_parse_is_idempotent_on_every_ail_fixture` — PASS
- `cargo test --workspace` — PASS, no regression

## Scope NOT touched

Per spec §"Iteration scope": no test-infrastructure changes, no
CLAUDE.md / DESIGN.md edits, no deletion of `prelude.ail.json`,
no migration script yet. Those land in iter 1.
  • Step 2: Append the INDEX line

The last existing line in docs/journals/INDEX.md is line 53 (iter 24.tidy). Append one new line at the end of file:

- 2026-05-13 — iter form-a.0: prelude pilot for Form-A-as-default-authoring milestone — examples/prelude.ail rendered (116 lines / 6386 bytes) via `ail render`; two auto-discovering roundtrip tests (every_ail_fixture_matches_its_json_counterpart + parse_then_print_then_parse_is_idempotent_on_every_ail_fixture) green on the new fixture without test-code changes; prelude.ail.json retained per spec §A2 (singular dual-form iter, deletion lands iter 1 with bulk test-infra refactor); cargo test --workspace green → 2026-05-13-iter-form-a.0.md

Adjust the byte/line counts in the line if Task 1 Step 2 observed different numbers. Convention (per INDEX.md header lines 1-7): append-only, one line per iter, newest at the bottom, em-dash between iter id and summary, arrow before the journal filename.

No other files modify in this step; INDEX append is the terminal working-tree edit of this iter.