Documents the three iter-24.3 strengthenings as load-bearing invariants and tightens two error-handling sites: T1: DESIGN.md gains new subsection §Cross-module references in synthesised bodies (between Resolution-and-monomorphisation and Defaults-and-superclasses) documenting three invariants installed in iter 24.3 — (1) MonoTarget::FreeFn::type_args carries canonical types post-collection via normalize_type_for_lookup; (2) post-mono synthesised body cross-module refs may bypass the source template's import_map (codegen falls back to module_user_fns / module_def_ail_types); (3) FreeFnCall synth pushes one ResidualConstraint per declared forall-constraint with rigid vars substituted by fresh metavars. T2: codegen_import_map_fallback_pin.rs (integration test) asserts the synthesised prelude.print__<IntBox> body references show_user_adt.show__<IntBox> AND prelude module's imports do not contain show_user_adt — proving the cross-module ref bypasses import_map at codegen. T3: polyfn_dot_qualified_branch_pin.rs (integration test) asserts bare-name print f (f : Int -> Int) fires exactly one no-instance diagnostic at typecheck with zero unknown-variable diagnostics — proving the bare-name resolution reaches the dot-qualified synth branch where the constraint-residual push fires. T4: check/lib.rs:2858 unwrap_or_default() replaced with .expect() carrying the registry-coherence message — class_methods index drift now surfaces explicitly rather than rendering NoInstance with an empty method name. T5: mono.rs gains apply_subst_and_normalize helper (Option<Type> return) extracted from two byte-identical call sites at collect_mono_targets and collect_residuals_ordered. Each call site retains its own rigid-var / unit-default policy in the None arm (site 1: rigid → has_rigid+break, unbound → Type::unit; site 2: non-concrete → Type::unit). Byte-identity invariant on mono-symbol hashes enforced by construction. Tests: 558 passed (was 556 + 2 new pins). No production semantic change — pure documentation + test pin + error-handling tightening + helper refactor. bench/cross_lang exit 0; bench/compile_check + bench/check exit 0 this run (latency.implicit_at_rc / latency.explicit_at_rc / bench_list_sum.bump_s noise envelope unobserved, lineage continues at 10th consecutive observation without firing this run).
8.1 KiB
iter 24.tidy — close 5 actionable drift items from audit-24
Date: 2026-05-13
Started from: 0e27533
Status: DONE
Tasks completed: 6 of 6
Summary
Closes the 5 actionable drift items audit-24 surfaced (3× [high] +
2× [medium]): T1 adds a new DESIGN.md subsection
### Cross-module references in synthesised bodies under
## Decision 11's ### Resolution and monomorphisation, anchoring
the three iter-24.3 strengthenings as load-bearing lockstep
invariants (canonical-form type_args / import_map-fallback / FreeFnCall
constraint-residual push). T2 pins the codegen import_map-fallback
path with an integration test asserting the synthesised
prelude.print__IntBox body contains a show_user_adt.show__IntBox
Var AND the prelude source module does not import show_user_adt.
T3 pins the bare-name poly-fn dot-qualified-branch invariant by
asserting show_no_instance.ail.json produces exactly one
no-instance diagnostic and zero unknown-variable /
internal diagnostics. T4 tightens
crates/ailang-check/src/lib.rs:2852 from unwrap_or_default() to
.expect(...) with a registry-coherence panic message; the strict
tightening surfaced no existing violations (558 tests pass post-edit).
T5 extracts the duplicated subst.apply + is_fully_concrete + normalize_type_for_lookup body into a single apply_subst_and_normalize
helper returning Option<Type>, replacing the two byte-identical
sites at mono.rs::collect_mono_targets (685-714) and
::collect_residuals_ordered (1284-1303); the byte-identity
invariant is now enforced by construction, mono_hash_stability
green on both primitive Show + Eq/Ord hashes. T6 verifies: 558
tests pass (was 556 + 2 new pins, exactly the expected count);
bench/cross_lang.py exit 0 (25/25 stable); bench/compile_check.py
exit 0 with 4 check_ms.* noise-class regressions in the documented
audit-24 lineage; bench/check.py exit 0 with 3 latency.implicit_at_rc.*
- 4
latency.explicit_at_rc.*noise-class observations in the same lineage (10th consecutive observation; baseline pristine).
Per-task notes
-
iter 24.tidy.1 (T1): DESIGN.md +68 lines, new
### Cross-module references in synthesised bodiessubsection inserted between### Resolution and monomorphisation(ends line 1693) and the re-anchored### Defaults and superclasses. Subsection enumerates three lockstep invariants with cross-references to source files (mono.rs::collect_mono_targets/::collect_residuals_ordered,codegen/lib.rs::resolve_top_level_fn/::lower_app/::synth_with_extras,check/lib.rssynth FreeFnCall arm) and to the protective test pins. Closing paragraph names the lockstep partnership and the regression cost of loosening any one. -
iter 24.tidy.2 (T2): New
crates/ail/tests/codegen_import_map_fallback_pin.rs. Loadsexamples/show_user_adt.ail.json, runscheck_workspace+monomorphise_workspace, finds the synthesisedDef::Fnwhose name starts withprint__in thepreludepost-mono module, recursively walks its body looking for aTerm::Var { name }whose name starts withshow_user_adt.and containsshow__, and confirms the prelude source module'sVec<Import>does NOT includeshow_user_adt. Plan draft adapted at three points: (a)Term::Appfield name iscalleenotfn(the latter is the serde rename; Rust field iscallee); (b)imports.iter()gives&Importnot&Stringso the check is onimp.module; (c) recursive walker extended withSeq/Clone/ReuseAs/LetRec/If/Match/Ctorarms for exhaustiveness — all data-shape adaptations the plan explicitly authorised. Test passes first-shot (1 passed). -
iter 24.tidy.3 (T3): New
crates/ail/tests/polyfn_dot_qualified_branch_pin.rs. Loadsexamples/show_no_instance.ail.json, asserts exactly oneno-instancediagnostic AND zerounknown-variable/internaldiagnostics. The plan's draftd.codefield accesses match the actualDiagnosticstruct (verified againstcrates/ailang-check/src/diagnostic.rs:131). Test passes first-shot. -
iter 24.tidy.4 (T4): One-line tightening at
crates/ailang-check/src/lib.rs:2858—.unwrap_or_default()→.expect("class_methods registry coherence ...")with the verbatim plan message. The.expect(...)covers the registry-coherence invariant: a declared constraint's class is expected to be inenv.class_methodsbecause the pre-passMissingClassdiagnostic should have rejected it earlier; reaching this point withNonemeans workspace-registry / class-index drift. No existing test violates the new invariant — 558 tests pass. -
iter 24.tidy.5 (T5): New
apply_subst_and_normalizehelper atcrates/ailang-check/src/mono.rs:555-583(immediately precedingcollect_mono_targets). Signaturefn apply_subst_and_normalize(env: &crate::Env, module_name: &str, m: &Type, subst: &crate::Subst) -> Option<Type>. ReturnsSome(normalised)ifsubst.apply(m)is fully concrete (viacrate::is_fully_concrete); elseNone. Two call sites retrofit: site 1 atcollect_mono_targets(now ~ lines 707-727) keeps its rigid-var / Unit-default branching at the call site (helper-None → checkcontains_rigid_var(&subst.apply(m)); if rigid sethas_rigid = trueand break; else pushType::unit()); site 2 atcollect_residuals_ordered(now ~ lines 1284-1290) usesapply_subst_and_normalize(&env, ...).unwrap_or_else(Type::unit). Site 1 needed&env(local ownedEnv) where the plan draft had bareenv— one-character edit.mono_hash_stabilityboth tests pass (primitive Show + Eq/Ord mono-symbol hashes bit-identical), full workspace 558 green. Byte-identity invariant now enforced by construction at the helper boundary; each call site explicitly documents its post-helper-None policy. -
iter 24.tidy.6 (T6): Integration verification. Full workspace
cargo test --workspace --no-fail-fast= 558 passed, 0 failed (matches plan's prediction: 556 baseline + 2 new pins). No FAILED entries among milestone-24 specific test groups (show_/print_/prelude_free_fns/mono_hash_stability/typeclass_22b/mq3). Bench:cross_lang.pyexit 0, 25/25 stable;compile_check.pyexit 0 with 4check_ms.*ms-level noise-class regressions (hello,borrow_own_demo,bench_tree_walk,bench_hof_pipeline);check.pyexit 0 with 3 noise-class regressions (latency.implicit_at_rc.{p99_9_us, max_us}first-sightings in this iter) + 4 noise-class improvements (latency.explicit_at_rc.{p99_us, p99_over_median}improvements continuing the documented audit-cma → audit-24 lineage). 10th consecutive observation of metric-identity-migrating noise; baseline pristine, conservative-call convention holds per the documented lineage.
Concerns
-
T5 pre-extraction reading vs plan: the plan said site 1's unbound- meta path "likely a
continuewithout push" — actual pre-extraction code didtype_args.push(Type::unit())for unbound metas (iter 23.4 behaviour, comment in place). I preserved the actual behaviour, not the plan's guess. Lockstep with site 2 (which has the same Unit-default policy) is improved post-extraction. Observation, not correctness. -
T2 plan draft had three data-shape mismatches against actual Rust schema (
Term::App.calleevs plan'sfn,Vec<Import>vsVec<String>, exhaustive Term variants). The plan explicitly flagged these as "implementer adapts to the actual field name" cases; not plan defects but recon imprecisions.
Known debt
- audit-24 [medium-3] negative-test coverage breadth → deferred to P3 downstream-corpus-migration milestone (carried over from audit-24, not closed here).
- audit-24 [low-1] bench/architect_sweeps.sh noise → carry-on, pre-milestone-24 lines not new drift.
Files touched
docs/DESIGN.md— new subsection (T1), +68 lines.crates/ail/tests/codegen_import_map_fallback_pin.rs— new pin (T2).crates/ail/tests/polyfn_dot_qualified_branch_pin.rs— new pin (T3).crates/ailang-check/src/lib.rs— one-line tightening at :2858 (T4).crates/ailang-check/src/mono.rs— new helper + two call-site replacements (T5).
Stats
bench/orchestrator-stats/2026-05-13-iter-24.tidy.json