# 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.
