# iter pd.3 — prelude.ail.json deleted; prelude-decouple milestone closed **Date:** 2026-05-14 **Started from:** c0dd2bc334d490e5e90ad10501c33e48076ff2bd **Status:** DONE **Tasks completed:** 8 of 8 ## Summary pd.3 closes the **prelude-decouple milestone** (pd.1 + pd.2 + pd.3) by deleting `examples/prelude.ail.json` from the working tree, sweeping the three remaining `include_str!` consumers (one in-mod test in `workspace.rs`, the cross-form-identity preflight in `prelude_module_hash_pin.rs`, the defensive include in `ail/src/main.rs`'s `migrate-canonical-types` subcommand) and the lockstep skip-branch the defensive include necessitated, dropping the §C4 (b) entry from `carve_out_inventory.rs` (8 → 7 named carve-outs), retiring §C4 (b) of `docs/specs/2026-05-13-form-a-default-authoring.md` in place with a "Status: RETIRED 2026-05-14" marker that preserves the original framing as historical context, and adding `prelude_decouple_carve_out_pin.rs` as the milestone's permanent guard against regression. At milestone close: the prelude exists on disk only as `examples/prelude.ail`; core embeds zero prelude bytes; the `migrate-canonical-types` subcommand no longer auto-injects a synthetic prelude entry; the doctrine "authors write `.ail`; the build derives the JSON-AST in-process via `ailang_surface::parse`" (CLAUDE.md) holds without exception. Test count: 573 green workspace-wide (preserved from pd.2; +1 carve-out pin -1 cross-form-identity preflight +1 relocated `build_workspace_accepts...` test -1 in-mod twin = net 0). `bench/cross_lang.py` + `bench/compile_check.py` exit 0; `bench/check.py` exit 1 with the same envelope-noise pattern observed since audit-cma (14th consecutive observation; pd.3 is a file-system + spec-text + test deletion + dead-code cleanup with zero codegen / runtime / typecheck touch, so cannot be the source). ## Per-task notes - iter pd.3.1: `build_workspace_accepts_assembled_modules_and_runs_validation` RELOCATED from `crates/ailang-core/src/workspace.rs::tests` (in-mod) to `crates/ailang-core/tests/workspace_pin.rs` (integration crate) instead of the literal call-site swap the plan prescribed. The plan specified "use the fully-qualified `ailang_surface::parse_prelude()` at the call site" claiming it was the workaround pd.2 used; in fact pd.2 worked around this same dev-dep cycle by inlining `serde_json::from_str(include_str!("prelude.ail.json"))` precisely BECAUSE `parse_prelude()` doesn't typecheck from in-mod (the lib-test target sees two distinct `ailang-core` compilations and the `Module` types fail to unify). pd.3 cannot use the JSON workaround because the JSON file is being deleted. The structural fix is the relocation pattern pd.2.4 already used for 10 sibling tests — same crate edge, same outcome. A 6-line stub-comment in `workspace.rs::tests` documents the dev-dep cycle so future maintainers don't try to put the test back. New test `load_one_adapter` helper at the top of `workspace_pin.rs` mirrors the in-mod `load_one` exactly (Io/Schema mapping). DONE_WITH_CONCERNS: plan defect (treated under pd.2.4 lesson recommendation but not caught at planning time). - iter pd.3.2: 5 deletions in `prelude_module_hash_pin.rs` (cross-form- identity preflight test fn, `PRELUDE_JSON` const, `PRELUDE_AIL` import, `const _: &str = PRELUDE_AIL;` shape-pin, lines 1-15 of original module doc-comment); module doc-comment replaced verbatim with the surviving-only version (preserves the historical record of why the preflight existed, what it discharged, when it was retired). Hash `3abe0d3fa3c11c99` still asserted by the surviving `prelude_parse_yields_canonical_hash`. Zero orphan-import warnings. First-shot clean. - iter pd.3.3: defensive `include_str!` block (12 lines) deleted from `Cmd::MigrateCanonicalTypes` arm; lockstep skip-branch (4 lines) deleted from the rewrite loop's Pass 2. The plan referenced the subcommand by its variant tag `MigrateBareCrossModuleRefs` but the user-facing subcommand name is `migrate-canonical-types` — spot-test ran under the correct name and the help text printed. DONE_WITH_CONCERNS: plan referenced obsolete variant name; doc-comment update on `MigrateCanonicalTypes` (one paragraph) was needed beyond strict task text because the prelude-fallback capability that the doc-comment asserted ("Prelude is consulted as an implicit last-resort fallback when no listed import owns the name") was silently removed by Step 2 — leaving the doc-comment in would have been an active mis-statement in `--help` output. Update names pd.3 as the retirement point. - iter pd.3.4: 3 edits to `carve_out_inventory.rs` (header sentence "eight" → "seven"; comment block re-published per plan; `"prelude.ail.json"` entry + its `// §C4 (b) — compile-time-embed` comment dropped from EXPECTED). Test RED-on-purpose post-edit (file still on disk); flips GREEN at Task 7. - iter pd.3.5: Status marker inserted verbatim above §C4 (b) heading in `docs/specs/2026-05-13-form-a-default-authoring.md`. Original §C4 (b) text preserved as historical context (per the spec's convention for retired sections). Reads coherently top-to-bottom; the marker reads as a forward-pointer to the resolution and the original text reads as historical motivation. - iter pd.3.6: `crates/ailang-surface/tests/prelude_decouple_carve_out_pin.rs` created verbatim per the plan. RED-on-purpose at this task (file still exists); flips GREEN at Task 7. Failure message names the four things a future maintainer must do to legitimately reintroduce the file — pre-empts the "it's just one file" rationalisation. - iter pd.3.7: pre-deletion verification clean (zero `include_str!` consumers; the two non-`include_str!` `.rs` references are inert — `print_no_leak_pin.rs:110` is a string-literal in an assertion message, `prelude_injection_pin.rs:28` constructs a tempdir-relative path that does not read the in-tree file). `rm examples/prelude.ail.json`. Both pin tests flip RED → GREEN at this step. Workspace test count unchanged at 573 green. - iter pd.3.8: `bench/compile_check.py` exit 0; `bench/cross_lang.py` exit 0; `bench/check.py` exit 1 with the established noise envelope (the `bench_list_sum.bump_s` and `implicit_at_rc` tail-latency oscillation observed since audit-cma — 14th consecutive observation). Acceptance criterion sweep: zero `include_str!` of `prelude.ail.json` workspace-wide; zero literal-`"prelude"` in `crates/ailang-core/src/` production scope (the matches in `workspace.rs` are all inside `#[cfg(test)] mod tests`); CLAUDE.md "Authors write `.ail`" sentence is consistent with the post-milestone state without modification (the prelude exception it implicitly tolerated pre-milestone is gone); `docs/DESIGN.md` carries no production-doctrine assertions about core embedding the prelude or referencing `prelude.ail.json`. ## Concerns - pd.3.1: plan defect — the task text instructed a call-site swap ("In each affected test fn body, replace the pattern ... with `ailang_surface::parse_prelude()`") that doesn't typecheck due to the dev-dep cycle. The plan also misattributed pd.2's actual workaround (`serde_json::from_str(include_str!(...))`) as successful use of `parse_prelude()` from in-mod. Recommend planner recon-check: when a plan proposes a call-site change involving `ailang_surface::*` from in-mod tests, verify the test target can resolve the dev-dep edge (the lib-test target generally cannot; the integration-test crate generally can — same lesson pd.2.4 recorded but didn't propagate to the planner skill). - pd.3.3: plan referenced subcommand by its internal variant tag (`MigrateBareCrossModuleRefs`) instead of its user-facing name (`migrate-canonical-types`). Trivial repair (test under correct name) but recommend planner check: verify subcommand-arm references in spot-test commands by enumeration-after-rename. - pd.3.3 (deeper): the plan's "Architecture" preamble called the defensive include "a documented no-op" because the inserted entry was unconditionally skipped by the lockstep guard at lines 500-505. This is correct about WRITING the rewritten module file back, but not about CONSULTING `local_types["prelude"]` for fallback rewrites (which the inserted entry populated, lines 487-497, before the Pass 2 skip). Removing both blocks silently retired the prelude-fallback capability of the rewrite logic. The plan called the change net-zero behaviour; it isn't. The doc-comment update I made on `MigrateCanonicalTypes` makes the new behaviour visible. The rewrite-helper's prelude-fallback branch at lines ~2436-2446 in `main.rs` is now dead-by-construction (the `local_types` map will never have a `"prelude"` key, since no prelude module is loaded by this subcommand) — left in place per the implementer's no-surrounding-cleanup discipline; tagged below as known debt. ## Known debt - `crates/ail/src/main.rs:~2436-2446`: the `local_types.get("prelude")` fallback in `rewrite_def`'s `rewrite_type_con_owner` (or analogue) is dead-by-construction post-pd.3 since the `local_types` map for the `migrate-canonical-types` subcommand no longer contains a `"prelude"` key. Out of pd.3's literal scope (no surrounding cleanup); candidate for a follow-up tidy iter or for the post-pd.3 audit's drift sweep. - 3 cargo doc warnings on dangling `[`load_workspace`]` references in `Workspace` and `Registry` rustdoc — pre-existing baseline carried forward from pd.2; out of pd.3 scope. (Identical entry was in the pd.2 known-debt section.) - The `migrate-canonical-types` subcommand's substantive behaviour (rewriting bare cross-module refs across a directory of `.ail.json` files) hasn't been exercised since the canonical-type-names milestone. No in-tree fixtures known to need rewriting today; spot- tested via `--help` only. If a future migration is requested, the prelude-fallback removal + the rewrite-helper dead-branch should be reviewed together. ## Files touched Modified: - `crates/ail/src/main.rs` (delete defensive include 472-484, delete skip-branch 500-505, update `MigrateCanonicalTypes` doc-comment to reflect retired prelude-fallback) - `crates/ailang-core/src/workspace.rs` (delete in-mod `build_workspace_accepts_assembled_modules_and_runs_validation` test; replace with 6-line stub comment documenting the dev-dep cycle as invariant) - `crates/ailang-core/tests/carve_out_inventory.rs` (header sentence + comment block + EXPECTED list — 8 → 7) - `crates/ailang-core/tests/workspace_pin.rs` (add `load_one_adapter` helper at top; append the relocated test at end) - `crates/ailang-surface/tests/prelude_module_hash_pin.rs` (delete cross-form-identity preflight + supporting `PRELUDE_JSON` / `PRELUDE_AIL` references; trim doc-comment to surviving-only framing) - `docs/specs/2026-05-13-form-a-default-authoring.md` (insert "Status: RETIRED" marker above §C4 (b) heading) New: - `crates/ailang-surface/tests/prelude_decouple_carve_out_pin.rs` (single test asserting `examples/prelude.ail.json` does not exist) Deleted: - `examples/prelude.ail.json` (the milestone's primary artefact retirement) ## Stats `bench/orchestrator-stats/2026-05-14-iter-pd.3.json` ## Note on pre-existing dirty INDEX.md When this iter dispatched, `docs/journals/INDEX.md` was already modified in the working tree with one Boss-side line for pd.2 (the prior iter); that edit was not committed before pd.3 dispatch. This agent did NOT touch INDEX.md (it is Boss-only per skill discipline). The Boss should commit the pd.2 INDEX line either with the pd.3 commit or as a separate prior commit — both options are clean.