diff --git a/bench/orchestrator-stats/2026-05-14-iter-pd.3.json b/bench/orchestrator-stats/2026-05-14-iter-pd.3.json new file mode 100644 index 0000000..10813b8 --- /dev/null +++ b/bench/orchestrator-stats/2026-05-14-iter-pd.3.json @@ -0,0 +1,22 @@ +{ + "iter_id": "pd.3", + "date": "2026-05-14", + "mode": "standard", + "outcome": "DONE", + "tasks_total": 8, + "tasks_completed": 8, + "reloops_per_task": { + "1": 0, + "2": 0, + "3": 0, + "4": 0, + "5": 0, + "6": 0, + "7": 0, + "8": 0 + }, + "review_loops_spec": 0, + "review_loops_quality": 1, + "blocked_reason": null, + "notes": "Task 1 implementer-phase had one structural correction loop (call-site swap → relocation) when the plan-prescribed mechanism failed to typecheck across the dev-dep cycle; counted as an in-phase repair, not a re-loop. Task 1 quality phase had one Minor (verbose stub comment); fixed inline by trimming, then re-approved — counted as the single quality re-loop. Task 4 + Task 6 had RED-on-purpose intermediate states (carve-out inventory test fails until Task 7 deletes the file; carve-out pin RED-on-purpose until same); both flipped GREEN at Task 7 as planned — not counted as failures. Task 3 doc-comment edit on MigrateCanonicalTypes was an unrequested-but-justified correction (capability-claim falsification by the strict task work); marked DONE_WITH_CONCERNS. pd.3 closes the prelude-decouple milestone (pd.1 + pd.2 + pd.3)." +} diff --git a/crates/ail/src/main.rs b/crates/ail/src/main.rs index 01cca82..b6cae0a 100644 --- a/crates/ail/src/main.rs +++ b/crates/ail/src/main.rs @@ -231,9 +231,11 @@ enum Cmd { /// /// Disambiguation: the first import (in declaration order) that /// owns a matching type wins. Ties are NOT diagnosed — the - /// migration silently picks the first match. Prelude is consulted - /// as an implicit last-resort fallback when no listed import owns - /// the name. + /// migration silently picks the first match. (The prelude + /// last-resort fallback was retired in pd.3 alongside the + /// `prelude.ail.json` embed; bare cross-module refs that resolve + /// only via the prelude are now left bare and surfaced by the + /// validator instead.) MigrateCanonicalTypes { /// Directory containing `*.ail.json` to migrate (typically /// `examples/`). @@ -469,20 +471,6 @@ fn main() -> Result<()> { modules.insert(m.name.clone(), (path, m)); } - // Include the embedded prelude (so the implicit fallback - // matches the loader's behaviour). - let prelude_json: &str = include_str!( - "../../../examples/prelude.ail.json" - ); - let prelude: Module = serde_json::from_str(prelude_json) - .expect("embedded prelude must parse"); - if !modules.contains_key("prelude") { - modules.insert( - "prelude".to_string(), - (PathBuf::new(), prelude), - ); - } - // Pre-pass: per-module local-types index. let mut local_types: BTreeMap> = BTreeMap::new(); @@ -497,12 +485,8 @@ fn main() -> Result<()> { } // Pass 2: rewrite each module's bare cross-module refs. - // Skip the synthetic prelude entry (no path to write to). let mut rewritten = 0usize; for (mod_name, (path, m)) in modules.iter_mut() { - if mod_name == "prelude" && path.as_os_str().is_empty() { - continue; - } let import_names: Vec = m.imports.iter().map(|i| i.module.clone()).collect(); let owning = mod_name.clone(); diff --git a/crates/ailang-core/src/workspace.rs b/crates/ailang-core/src/workspace.rs index 369a23f..cd84245 100644 --- a/crates/ailang-core/src/workspace.rs +++ b/crates/ailang-core/src/workspace.rs @@ -2630,39 +2630,12 @@ mod tests { assert!(modules.contains_key("main")); } - /// pd.1 Task 2: `build_workspace` accepts a pre-assembled modules - /// map (with the caller having already injected any implicit - /// modules) and runs the three-stage validation pipeline. The - /// `implicit_imports` slice is what the diagnostic helpers consult - /// for fallback candidate suggestions (Task 3 wires it through). - #[test] - fn build_workspace_accepts_assembled_modules_and_runs_validation() { - let dir = tempfile::tempdir().unwrap(); - write_module(dir.path(), "main", &[]); - let entry = dir.path().join("main.ail.json"); - - let (entry_name, root_dir, mut modules) = - load_modules_with(&entry, load_one).expect("load"); - - // Caller-side prelude inject — pd.2 deletes `load_prelude` from - // production, but this test still needs SOMETHING in the - // `prelude` slot to demonstrate the caller-injects pattern. - // Read the JSON sidecar via the same path the retired - // `load_prelude` used. Surface owns the production inject path - // (via `ailang_surface::parse_prelude`). - const PRELUDE_JSON: &str = - include_str!("../../../examples/prelude.ail.json"); - let prelude: Module = serde_json::from_str(PRELUDE_JSON) - .expect("examples/prelude.ail.json must parse as a Module"); - modules.insert("prelude".to_string(), prelude); - - let ws = build_workspace(entry_name, root_dir, modules, &["prelude"]) - .expect("build"); - - assert_eq!(ws.entry, "main"); - assert!(ws.modules.contains_key("main")); - assert!(ws.modules.contains_key("prelude")); - } + // Note: tests that need a real prelude module via + // `ailang_surface::parse_prelude()` live in + // `crates/ailang-core/tests/workspace_pin.rs` (integration crate) + // because the dev-dep cycle (`ailang-core` -> `ailang-surface` -> + // `ailang-core`) leaves the lib-test target with two distinct + // `ailang-core` compilations whose `Module` types do not unify. fn module_with_bare_classref(name: &str, class_ref: &str) -> Module { // A module containing a single Def::Instance whose `class` field is diff --git a/crates/ailang-core/tests/carve_out_inventory.rs b/crates/ailang-core/tests/carve_out_inventory.rs index ddf7bcc..994da38 100644 --- a/crates/ailang-core/tests/carve_out_inventory.rs +++ b/crates/ailang-core/tests/carve_out_inventory.rs @@ -1,14 +1,14 @@ //! Carve-out inventory check — §T4 of the form-a-default-authoring -//! spec. Asserts that exactly the eight named carve-out files exist +//! spec. Asserts that exactly the seven named carve-out files exist //! under `examples/*.ail.json` after milestone close. The list is //! hardcoded; any change is a deliberate, brainstorm-level decision. //! -//! Eight carve-outs at iter form-a.1 close: +//! Seven carve-outs post prelude-decouple (2026-05-14): //! - §C4 (a) subject-matter: 7 fixtures (canonical-form rejection / unbound / class-def rejection). -//! - §C4 (b) compile-time-embed: 1 fixture (prelude.ail.json). -//! -//! When the prelude-embed-refactor milestone retires §C4 (b), this -//! test's pass-count drops to seven and `EXPECTED` is re-published. +//! - §C4 (b) compile-time-embed: retired 2026-05-14 by milestone +//! prelude-decouple; the prelude is now embedded as `prelude.ail` +//! in `ailang-surface` and parsed at compile time via +//! `ailang_surface::parse_prelude`. use std::collections::BTreeSet; @@ -21,8 +21,6 @@ const EXPECTED: &[&str] = &[ "test_ct1_bad_qualifier.ail.json", "test_ct1_bare_xmod_rejected.ail.json", "test_ct1_qualified_class_rejected.ail.json", - // §C4 (b) — compile-time-embed - "prelude.ail.json", ]; fn examples_dir() -> std::path::PathBuf { diff --git a/crates/ailang-core/tests/workspace_pin.rs b/crates/ailang-core/tests/workspace_pin.rs index 21df9f5..bb65935 100644 --- a/crates/ailang-core/tests/workspace_pin.rs +++ b/crates/ailang-core/tests/workspace_pin.rs @@ -21,6 +21,22 @@ use ailang_core::workspace::{Registry, RegistryEntry, WorkspaceLoadError}; use ailang_surface::load_workspace; use std::path::{Path, PathBuf}; +// Adapter so this integration crate can call `load_modules_with` with +// the same Io/Schema mapping the in-mod test helper used. +fn load_one_adapter(path: &Path) -> Result { + match ailang_core::load_module(path) { + Ok(m) => Ok(m), + Err(ailang_core::Error::Io(e)) => Err(WorkspaceLoadError::Io { + path: path.to_path_buf(), + source: e, + }), + Err(e) => Err(WorkspaceLoadError::Schema { + path: path.to_path_buf(), + source: e, + }), + } +} + fn examples_dir() -> PathBuf { let manifest_dir = env!("CARGO_MANIFEST_DIR"); Path::new(manifest_dir).parent().unwrap().parent().unwrap().join("examples") @@ -486,3 +502,42 @@ fn ct1_fixture_qualified_class_orphan_post_mq1() { other => panic!("expected OrphanInstance, got {other:?}"), } } + +/// pd.1 Task 2 + pd.3 Task 1 relocation: `build_workspace` accepts a +/// pre-assembled modules map (with the caller having already injected +/// any implicit modules) and runs the three-stage validation pipeline. +/// The `implicit_imports` slice is what the diagnostic helpers consult +/// for fallback candidate suggestions. Relocated from the in-mod +/// `tests` of `ailang-core/src/workspace.rs` because the caller-side +/// prelude inject now uses `ailang_surface::parse_prelude()`, which +/// only typechecks across the dev-dep edge from the integration-test +/// crate (the lib-test crate sees two distinct `ailang-core` +/// compilations and fails to unify the `Module` types). +#[test] +fn build_workspace_accepts_assembled_modules_and_runs_validation() { + let dir = tempfile::tempdir().unwrap(); + write_simple_module_json(dir.path(), "main", &[]); + let entry = dir.path().join("main.ail.json"); + + let (entry_name, root_dir, mut modules) = + ailang_core::workspace::load_modules_with(&entry, load_one_adapter) + .expect("load"); + + // Caller-side prelude inject — surface owns the production inject + // path; this test consumes it via the dev-dep edge to demonstrate + // the caller-injects pattern with the same source of truth. + let prelude = ailang_surface::parse_prelude(); + modules.insert("prelude".to_string(), prelude); + + let ws = ailang_core::workspace::build_workspace( + entry_name, + root_dir, + modules, + &["prelude"], + ) + .expect("build"); + + assert_eq!(ws.entry, "main"); + assert!(ws.modules.contains_key("main")); + assert!(ws.modules.contains_key("prelude")); +} diff --git a/crates/ailang-surface/tests/prelude_decouple_carve_out_pin.rs b/crates/ailang-surface/tests/prelude_decouple_carve_out_pin.rs new file mode 100644 index 0000000..79d0f37 --- /dev/null +++ b/crates/ailang-surface/tests/prelude_decouple_carve_out_pin.rs @@ -0,0 +1,29 @@ +//! pd.3 (`prelude-decouple` milestone): pin that +//! `examples/prelude.ail.json` does NOT exist on disk. Surfaces +//! immediately if the file is recreated by accident or a future +//! refactor reverts the embed-source swap. +//! +//! Companion to the `examples_ail_json_inventory_matches_carve_outs` +//! test in `crates/ailang-core/tests/carve_out_inventory.rs`: that +//! one asserts the carve-out list is exactly seven; this one +//! asserts the eighth (retired) file specifically does not exist. +//! Either test detects an accidental re-introduction; this one +//! pinpoints the file by name in the error message. + +use std::path::Path; + +#[test] +fn prelude_ail_json_does_not_exist_on_disk() { + let path = Path::new("../../examples/prelude.ail.json"); + assert!( + !path.exists(), + "examples/prelude.ail.json must NOT exist after the prelude-\ + decouple milestone (closed 2026-05-14). The prelude is now \ + embedded as prelude.ail in ailang-surface. If you intentionally \ + re-introduced this file, you must also: (a) remove this pin, \ + (b) restore the §C4 (b) entry in carve_out_inventory.rs, \ + (c) revert the §C4 (b) status marker in \ + docs/specs/2026-05-13-form-a-default-authoring.md, and \ + (d) record the rationale in a per-iter journal." + ); +} diff --git a/crates/ailang-surface/tests/prelude_module_hash_pin.rs b/crates/ailang-surface/tests/prelude_module_hash_pin.rs index 00f9f45..bfd2763 100644 --- a/crates/ailang-surface/tests/prelude_module_hash_pin.rs +++ b/crates/ailang-surface/tests/prelude_module_hash_pin.rs @@ -1,46 +1,17 @@ -//! pd.2 (`prelude-decouple` milestone): pin the canonical identity of -//! the parsed prelude. +//! pd.2/pd.3 (`prelude-decouple` milestone): pin the canonical +//! identity of the parsed prelude as a literal hex hash. Updates +//! intentionally on prelude-content changes (record the why in the +//! per-iter journal); accidental drift trips immediately. //! -//! Two tests: -//! 1. `prelude_ail_and_json_parse_to_identical_module` — cross-form- -//! identity preflight. Asserts that parsing `examples/prelude.ail` -//! via `ailang_surface::parse` produces a `Module` with the same -//! `module_hash` as deserialising `examples/prelude.ail.json` via -//! `serde_json::from_str`. This is the load-bearing assumption -//! that justifies pd.3's deletion of the JSON file. pd.3 drops -//! this test along with the JSON. -//! 2. `prelude_parse_yields_canonical_hash` — long-term anchor. -//! Asserts the literal hex hash of the parsed Module. Updates -//! intentionally on prelude-content changes (with a JOURNAL entry -//! naming why); accidental drift trips immediately. +//! pd.2 also carried a `prelude_ail_and_json_parse_to_identical_module` +//! cross-form-identity preflight that compared `parse(prelude.ail)` +//! against `serde_json::from_str(prelude.ail.json)`. The preflight +//! discharged its purpose at pd.2 close (PASSED at hash +//! `3abe0d3fa3c11c99`) and was retired in pd.3 alongside the +//! `prelude.ail.json` file. use ailang_core::workspace::module_hash; -use ailang_surface::{parse_prelude, PRELUDE_AIL}; - -const PRELUDE_JSON: &str = include_str!("../../../examples/prelude.ail.json"); - -#[test] -fn prelude_ail_and_json_parse_to_identical_module() { - // Form A path: parse prelude.ail via ailang_surface::parse. - let from_ail = parse_prelude(); - - // Form B path: deserialise prelude.ail.json via serde_json (the - // pre-pd.2 path; matches what `ailang_core::workspace::load_prelude` - // did before retirement). - let from_json: ailang_core::ast::Module = serde_json::from_str(PRELUDE_JSON) - .expect("examples/prelude.ail.json must deserialise"); - - let h_ail = module_hash(&from_ail); - let h_json = module_hash(&from_json); - - assert_eq!( - h_ail, h_json, - "cross-form-identity preflight failed:\n parse(prelude.ail) hash: {}\n deserialize(prelude.ail.json) hash: {}\n\ - The two parse paths produce structurally different `Module` values for the same prelude content. \ - pd.3 must NOT delete prelude.ail.json until this is investigated.", - h_ail, h_json, - ); -} +use ailang_surface::parse_prelude; #[test] fn prelude_parse_yields_canonical_hash() { @@ -55,6 +26,3 @@ fn prelude_parse_yields_canonical_hash() { hex below + record the why in the per-iter journal." ); } - -// PRELUDE_AIL re-exported for any future test that wants the raw bytes. -const _: &str = PRELUDE_AIL; diff --git a/docs/journals/2026-05-14-iter-pd.3.md b/docs/journals/2026-05-14-iter-pd.3.md new file mode 100644 index 0000000..ea85097 --- /dev/null +++ b/docs/journals/2026-05-14-iter-pd.3.md @@ -0,0 +1,209 @@ +# 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. diff --git a/docs/journals/INDEX.md b/docs/journals/INDEX.md index a9ec90c..0124288 100644 --- a/docs/journals/INDEX.md +++ b/docs/journals/INDEX.md @@ -67,3 +67,5 @@ - 2026-05-14 — iter rpe.1.tidy: subst.rs preserves Type::Fn modes through rebuild → 2026-05-14-iter-rpe.1.tidy.md - 2026-05-14 — iter cli-diag-human: WorkspaceLoadError thru diagnostic in non-JSON CLI path → 2026-05-14-iter-cli-diag-human.md - 2026-05-14 — iter pd.1: core API split — load_modules_with + build_workspace + implicit_imports threading; load_workspace_with kept as 3-line shim so surface unchanged; production literal-"prelude" count in workspace.rs dropped 8→4 → 2026-05-14-iter-pd.1.md +- 2026-05-14 — iter pd.2: surface owns prelude embed (PRELUDE_AIL + parse_prelude in ailang-surface); pd.1 shim load_workspace_with retired along with PRELUDE_JSON / load_prelude / load_one; cross-form-identity preflight PASSED at module_hash 3abe0d3fa3c11c99; production literal-"prelude" in core/src/ now zero → 2026-05-14-iter-pd.2.md +- 2026-05-14 — iter pd.3: prelude.ail.json deleted from working tree; cross-form-identity preflight + supporting bytes deleted from prelude_module_hash_pin (purpose discharged); migrate-bare-cross-module-refs defensive include + lockstep skip-branch deleted; carve_out_inventory.rs §C4(b) row dropped (8→7); form-a-default-authoring spec §C4(b) RETIRED status marker added; new prelude_decouple_carve_out_pin.rs asserts JSON file does not exist; milestone prelude-decouple closed → 2026-05-14-iter-pd.3.md diff --git a/docs/specs/2026-05-13-form-a-default-authoring.md b/docs/specs/2026-05-13-form-a-default-authoring.md index 21f9249..c875ee5 100644 --- a/docs/specs/2026-05-13-form-a-default-authoring.md +++ b/docs/specs/2026-05-13-form-a-default-authoring.md @@ -267,6 +267,15 @@ Justification per file: (changing the diagnostic) or refuse to encode at all (changing the test subject). +> **Status: RETIRED 2026-05-14 by milestone prelude-decouple.** The +> §C4 (b) compile-time-embed carve-out is empty post that milestone; +> the prelude is now authored exclusively as `examples/prelude.ail` +> and parsed at compile time via `ailang_surface::parse_prelude`. The +> original §C4 (b) framing below is preserved as historical context; +> it documents the doctrine inconsistency that prelude-decouple +> resolved. See `docs/specs/2026-05-14-prelude-decouple.md` and the +> per-iter journals 2026-05-14-iter-pd.{1,2,3}.md for the resolution. + **(b) Compile-time-embed carve-outs (one file as of 2026-05-13):** ``` diff --git a/examples/prelude.ail.json b/examples/prelude.ail.json deleted file mode 100644 index ce3e55a..0000000 --- a/examples/prelude.ail.json +++ /dev/null @@ -1,559 +0,0 @@ -{ - "schema": "ailang/v0", - "name": "prelude", - "imports": [], - "defs": [ - { - "kind": "type", - "name": "Ordering", - "vars": [], - "doc": "Result of a three-way comparison: LT (less than), EQ (equal), GT (greater than). Ships in milestone 23 as the codomain of Ord.compare.", - "ctors": [ - { "name": "LT", "fields": [] }, - { "name": "EQ", "fields": [] }, - { "name": "GT", "fields": [] } - ] - }, - { - "kind": "class", - "name": "Eq", - "param": "a", - "doc": "Structural equality. Ships in milestone 23 alongside Ord. The primitive `==` operator stays as the surface-level comparator this iter; routing `==` through `Eq.eq` is the declared P2 follow-up (see docs/specs/2026-05-10-23-eq-ord-prelude.md, `Out of scope`).", - "methods": [ - { - "name": "eq", - "type": { - "k": "fn", - "params": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "param_modes": ["borrow", "borrow"], - "ret": { "k": "con", "name": "Bool" }, - "effects": [] - } - } - ] - }, - { - "kind": "instance", - "class": "Eq", - "type": { "k": "con", "name": "Int" }, - "doc": "Eq Int. Body lowers to `icmp eq i64` via the existing primitive `==` dispatch in `lower_eq`.", - "methods": [ - { - "name": "eq", - "body": { - "t": "lam", - "params": ["x", "y"], - "paramTypes": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "retType": { "k": "con", "name": "Bool" }, - "body": { - "t": "app", - "fn": { "t": "var", "name": "==" }, - "args": [ - { "t": "var", "name": "x" }, - { "t": "var", "name": "y" } - ] - } - } - } - ] - }, - { - "kind": "instance", - "class": "Eq", - "type": { "k": "con", "name": "Bool" }, - "doc": "Eq Bool. Body lowers to `icmp eq i1` via the existing primitive `==` dispatch in `lower_eq`.", - "methods": [ - { - "name": "eq", - "body": { - "t": "lam", - "params": ["x", "y"], - "paramTypes": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "retType": { "k": "con", "name": "Bool" }, - "body": { - "t": "app", - "fn": { "t": "var", "name": "==" }, - "args": [ - { "t": "var", "name": "x" }, - { "t": "var", "name": "y" } - ] - } - } - } - ] - }, - { - "kind": "instance", - "class": "Eq", - "type": { "k": "con", "name": "Str" }, - "doc": "Eq Str. The lambda body shape mirrors Int/Bool for round-trip stability, but the codegen intercept in `emit_fn` overrides the body to call `@ail_str_eq` directly — see `try_emit_primitive_instance_body` in `crates/ailang-codegen/src/lib.rs`.", - "methods": [ - { - "name": "eq", - "body": { - "t": "lam", - "params": ["x", "y"], - "paramTypes": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "retType": { "k": "con", "name": "Bool" }, - "body": { - "t": "app", - "fn": { "t": "var", "name": "==" }, - "args": [ - { "t": "var", "name": "x" }, - { "t": "var", "name": "y" } - ] - } - } - } - ] - }, - { - "kind": "class", - "name": "Ord", - "param": "a", - "superclass": { "class": "Eq", "type": "a" }, - "doc": "Total ordering. Ships in milestone 23 alongside Eq. `compare x y` returns LT, EQ, or GT (the three-ctor Ordering ADT also in the prelude). Decision 11's single-superclass closure requires `instance Eq T` for every `instance Ord T` — the three Ord instances below pair with the three Eq instances shipped in iter 23.2.3.", - "methods": [ - { - "name": "compare", - "type": { - "k": "fn", - "params": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "param_modes": ["borrow", "borrow"], - "ret": { "k": "con", "name": "Ordering" }, - "effects": [] - } - } - ] - }, - { - "kind": "instance", - "class": "Ord", - "type": { "k": "con", "name": "Int" }, - "doc": "Ord Int. The lambda body shape is a placeholder for round-trip stability; the codegen intercept `try_emit_primitive_instance_body::\"compare__Int\"` emits a three-way `icmp slt` / `icmp eq` branch ladder constructing LT / EQ / GT.", - "methods": [ - { - "name": "compare", - "body": { - "t": "lam", - "params": ["x", "y"], - "paramTypes": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "retType": { "k": "con", "name": "Ordering" }, - "body": { - "t": "ctor", - "type": "Ordering", - "ctor": "EQ", - "args": [] - } - } - } - ] - }, - { - "kind": "instance", - "class": "Ord", - "type": { "k": "con", "name": "Bool" }, - "doc": "Ord Bool. Body lowered via `try_emit_primitive_instance_body::\"compare__Bool\"` — `icmp ult i1` LT-test, `icmp eq i1` EQ-test, GT default.", - "methods": [ - { - "name": "compare", - "body": { - "t": "lam", - "params": ["x", "y"], - "paramTypes": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "retType": { "k": "con", "name": "Ordering" }, - "body": { - "t": "ctor", - "type": "Ordering", - "ctor": "EQ", - "args": [] - } - } - } - ] - }, - { - "kind": "instance", - "class": "Ord", - "type": { "k": "con", "name": "Str" }, - "doc": "Ord Str. Body lowered via `try_emit_primitive_instance_body::\"compare__Str\"` — `call i32 @ail_str_compare(ptr, ptr)` then branch on slt-0 / eq-0 against the normalised {-1, 0, +1} return.", - "methods": [ - { - "name": "compare", - "body": { - "t": "lam", - "params": ["x", "y"], - "paramTypes": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "retType": { "k": "con", "name": "Ordering" }, - "body": { - "t": "ctor", - "type": "Ordering", - "ctor": "EQ", - "args": [] - } - } - } - ] - }, - { - "kind": "class", - "name": "Show", - "param": "a", - "doc": "Producer of a human-readable Str representation. Ships in milestone 24 with primitive instances for Int/Bool/Str/Float; user types declare their own instance.", - "methods": [ - { - "name": "show", - "type": { - "k": "fn", - "params": [{ "k": "var", "name": "a" }], - "param_modes": ["borrow"], - "ret": { "k": "con", "name": "Str" }, - "ret_mode": "own", - "effects": [] - } - } - ] - }, - { - "kind": "instance", - "class": "Show", - "type": { "k": "con", "name": "Int" }, - "methods": [ - { - "name": "show", - "body": { - "t": "lam", - "params": ["x"], - "paramTypes": [{ "k": "con", "name": "Int" }], - "retType": { "k": "con", "name": "Str" }, - "body": { - "t": "app", - "fn": { "t": "var", "name": "int_to_str" }, - "args": [{ "t": "var", "name": "x" }] - } - } - } - ] - }, - { - "kind": "instance", - "class": "Show", - "type": { "k": "con", "name": "Bool" }, - "methods": [ - { - "name": "show", - "body": { - "t": "lam", - "params": ["x"], - "paramTypes": [{ "k": "con", "name": "Bool" }], - "retType": { "k": "con", "name": "Str" }, - "body": { - "t": "app", - "fn": { "t": "var", "name": "bool_to_str" }, - "args": [{ "t": "var", "name": "x" }] - } - } - } - ] - }, - { - "kind": "instance", - "class": "Show", - "type": { "k": "con", "name": "Str" }, - "methods": [ - { - "name": "show", - "body": { - "t": "lam", - "params": ["x"], - "paramTypes": [{ "k": "con", "name": "Str" }], - "retType": { "k": "con", "name": "Str" }, - "body": { - "t": "app", - "fn": { "t": "var", "name": "str_clone" }, - "args": [{ "t": "var", "name": "x" }] - } - } - } - ] - }, - { - "kind": "instance", - "class": "Show", - "type": { "k": "con", "name": "Float" }, - "methods": [ - { - "name": "show", - "body": { - "t": "lam", - "params": ["x"], - "paramTypes": [{ "k": "con", "name": "Float" }], - "retType": { "k": "con", "name": "Str" }, - "body": { - "t": "app", - "fn": { "t": "var", "name": "float_to_str" }, - "args": [{ "t": "var", "name": "x" }] - } - } - } - ] - }, - { - "kind": "fn", - "name": "ne", - "doc": "Polymorphic disequality. `ne x y` ≡ not (eq x y). Ships in milestone 23 as the Eq-class free helper.", - "type": { - "k": "forall", - "vars": ["a"], - "constraints": [{ "class": "Eq", "type": { "k": "var", "name": "a" } }], - "body": { - "k": "fn", - "params": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "param_modes": ["borrow", "borrow"], - "ret": { "k": "con", "name": "Bool" }, - "effects": [] - } - }, - "params": ["x", "y"], - "body": { - "t": "app", - "fn": { "t": "var", "name": "not" }, - "args": [{ - "t": "app", - "fn": { "t": "var", "name": "eq" }, - "args": [ - { "t": "var", "name": "x" }, - { "t": "var", "name": "y" } - ] - }] - } - }, - { - "kind": "fn", - "name": "lt", - "doc": "Polymorphic strict-less-than. `lt x y` ≡ case compare x y of LT -> True; _ -> False. Ships in milestone 23 as the Ord-class free helper.", - "type": { - "k": "forall", - "vars": ["a"], - "constraints": [{ "class": "Ord", "type": { "k": "var", "name": "a" } }], - "body": { - "k": "fn", - "params": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "param_modes": ["borrow", "borrow"], - "ret": { "k": "con", "name": "Bool" }, - "effects": [] - } - }, - "params": ["x", "y"], - "body": { - "t": "match", - "scrutinee": { - "t": "app", - "fn": { "t": "var", "name": "compare" }, - "args": [ - { "t": "var", "name": "x" }, - { "t": "var", "name": "y" } - ] - }, - "arms": [ - { - "pat": { "p": "ctor", "ctor": "LT", "fields": [] }, - "body": { "t": "lit", "lit": { "kind": "bool", "value": true } } - }, - { - "pat": { "p": "wild" }, - "body": { "t": "lit", "lit": { "kind": "bool", "value": false } } - } - ] - } - }, - { - "kind": "fn", - "name": "le", - "doc": "Polymorphic less-than-or-equal. `le x y` ≡ case compare x y of GT -> False; _ -> True. Ships in milestone 23 as the Ord-class free helper.", - "type": { - "k": "forall", - "vars": ["a"], - "constraints": [{ "class": "Ord", "type": { "k": "var", "name": "a" } }], - "body": { - "k": "fn", - "params": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "param_modes": ["borrow", "borrow"], - "ret": { "k": "con", "name": "Bool" }, - "effects": [] - } - }, - "params": ["x", "y"], - "body": { - "t": "match", - "scrutinee": { - "t": "app", - "fn": { "t": "var", "name": "compare" }, - "args": [ - { "t": "var", "name": "x" }, - { "t": "var", "name": "y" } - ] - }, - "arms": [ - { - "pat": { "p": "ctor", "ctor": "GT", "fields": [] }, - "body": { "t": "lit", "lit": { "kind": "bool", "value": false } } - }, - { - "pat": { "p": "wild" }, - "body": { "t": "lit", "lit": { "kind": "bool", "value": true } } - } - ] - } - }, - { - "kind": "fn", - "name": "gt", - "doc": "Polymorphic strict-greater-than. `gt x y` ≡ case compare x y of GT -> True; _ -> False. Ships in milestone 23 as the Ord-class free helper.", - "type": { - "k": "forall", - "vars": ["a"], - "constraints": [{ "class": "Ord", "type": { "k": "var", "name": "a" } }], - "body": { - "k": "fn", - "params": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "param_modes": ["borrow", "borrow"], - "ret": { "k": "con", "name": "Bool" }, - "effects": [] - } - }, - "params": ["x", "y"], - "body": { - "t": "match", - "scrutinee": { - "t": "app", - "fn": { "t": "var", "name": "compare" }, - "args": [ - { "t": "var", "name": "x" }, - { "t": "var", "name": "y" } - ] - }, - "arms": [ - { - "pat": { "p": "ctor", "ctor": "GT", "fields": [] }, - "body": { "t": "lit", "lit": { "kind": "bool", "value": true } } - }, - { - "pat": { "p": "wild" }, - "body": { "t": "lit", "lit": { "kind": "bool", "value": false } } - } - ] - } - }, - { - "kind": "fn", - "name": "ge", - "doc": "Polymorphic greater-than-or-equal. `ge x y` ≡ case compare x y of LT -> False; _ -> True. Ships in milestone 23 as the Ord-class free helper.", - "type": { - "k": "forall", - "vars": ["a"], - "constraints": [{ "class": "Ord", "type": { "k": "var", "name": "a" } }], - "body": { - "k": "fn", - "params": [ - { "k": "var", "name": "a" }, - { "k": "var", "name": "a" } - ], - "param_modes": ["borrow", "borrow"], - "ret": { "k": "con", "name": "Bool" }, - "effects": [] - } - }, - "params": ["x", "y"], - "body": { - "t": "match", - "scrutinee": { - "t": "app", - "fn": { "t": "var", "name": "compare" }, - "args": [ - { "t": "var", "name": "x" }, - { "t": "var", "name": "y" } - ] - }, - "arms": [ - { - "pat": { "p": "ctor", "ctor": "LT", "fields": [] }, - "body": { "t": "lit", "lit": { "kind": "bool", "value": false } } - }, - { - "pat": { "p": "wild" }, - "body": { "t": "lit", "lit": { "kind": "bool", "value": true } } - } - ] - } - }, - { - "kind": "fn", - "name": "print", - "doc": "Polymorphic console-print helper. `print x` ≡ `do io/print_str (show x)` with an explicit let-binder around `show x` for heap-Str RC discipline per eob.1 Str carve-out. Ships in milestone 24 as the second half of the Show prelude.", - "type": { - "k": "forall", - "vars": ["a"], - "constraints": [{ "class": "Show", "type": { "k": "var", "name": "a" } }], - "body": { - "k": "fn", - "params": [{ "k": "var", "name": "a" }], - "param_modes": ["borrow"], - "ret": { "k": "con", "name": "Unit" }, - "effects": ["IO"] - } - }, - "params": ["x"], - "body": { - "t": "let", - "name": "s", - "value": { - "t": "app", - "fn": { "t": "var", "name": "show" }, - "args": [{ "t": "var", "name": "x" }] - }, - "body": { - "t": "do", - "op": "io/print_str", - "args": [{ "t": "var", "name": "s" }] - } - } - } - ] -}