T1 [high-1]: refine_multi_candidate_residual rigid-var filter now requires class + type-unification via constraint_type_matches, so forall a b. prelude.Show a, userlib.Show b => ... shapes discriminate by which typevar the residual is on. Discharge-time only; synth-time twin doesn't have a residual type to unify against (fresh metavar constructed after dispatch). T2 [high-2]: qualifier_is_class_shape extracted as pub(crate) helper adjacent to parse_method_qualifier; broadened to accept PascalCase single-segment qualifiers like Show.show alongside module-qualified ones. Same-module bare-class call sites now reachable; symmetric to mq.1 canonical-form rule. T3 [medium-1]: any_candidate_class_has_instance pub(crate) helper gates the class-method-shadowed-by-fn warning closure on registry instance presence (any candidate class, any type). Conservative tightening of the spec rule per Boss Q2 decision. T4 [medium-2]: four DESIGN.md Data Model schema fragments (SuperclassRef, InstanceDef.class, Type::Con.name, Constraint.class) annotated with canonical-form cross-references. Two trip-wires fixed inline: parse_method_qualifier docstring coherence, mq3 in-crate test fixture-repair (registry instance injection — analogous to mq.3 typeclass_22b3 pattern). 5/5 tasks. 548 tests green (was 545 + 3 new mq_tidy_* unit tests). bench/compile_check.py + cross_lang.py exit 0; check.py exit 0 both runs with noise-class metric migration on latency.implicit_at_rc.* max-tail (6th-consecutive observation since audit-cma). Baseline pristine.
12 KiB
iter mq.tidy — Close 4 actionable drift items from audit-mq
Date: 2026-05-13
Started from: 64d3feeb97
Status: DONE
Tasks completed: 5 of 5
Summary
mq.tidy closes the four actionable drift items audit-mq surfaced
(commit f382931), moving the module-qualified-class-names milestone
from "structurally complete" to "drift-clean ready for next milestone".
T1 extends the discharge-time refine_multi_candidate_residual
rigid-var filter from class-only to class + type-unification via the
existing constraint_type_matches helper, so two same-class declared
constraints on different typevars are correctly discriminated by the
residual's typevar identity (high-1). T2 extracts the inline
qualifier_is_class_shape predicate as a free pub(crate) helper and
broadens it to accept PascalCase single-segment qualifiers
("Show.show") alongside module-qualified ones ("prelude.Show.show")
— same-module bare-class call sites are now reachable, symmetric to
the canonical-form rule at the schema level (high-2). T3 introduces
the any_candidate_class_has_instance predicate and gates the
class-method-shadowed-by-fn warning closure on it, so class methods
with zero registry instances anywhere no longer fire the warning;
conservative approximation of the full spec rule per Boss Q2 (the arg
type required for the strict version is unavailable at the Var-arm)
(medium-1). T4 annotates the four Data Model schema fragments
(SuperclassRef, InstanceDef.class, Type::Con.name,
Constraint.class) with the canonical-form cross-reference, so a
schema-section reader without the prose context no longer sees
bare-only (medium-2).
T5 verification: 548 tests green (was 545 pre-iter + 3 new
mq_tidy_* unit tests). bench/compile_check.py exit 0 (24/24 stable
after a one-run noise blip absorbed under tolerance on re-run).
bench/cross_lang.py exit 0 (25/25 stable). bench/check.py exit 0
both runs; metric-identity-migrating noise on the
latency.implicit_at_rc.* max-tail cluster — 5th-consecutive audit
observation per audit-mq's lineage, baseline pristine. The
mq3_class_method_shadowed_by_fn_warning_fires in-crate test was a
trip-wire: pre-tidy it used Registry::default() (no instances) and
the warning fired anyway because the old gate didn't check instances;
post-tidy it correctly suppresses, so the fixture was updated to ship
a clsmod.Show Int registry instance — fixture-repair as a coherence
consequence of the deliberately tightened spec rule. mq3 E2E (3 tests)
- typeclass_22b3 (18 tests) all stay green; the E2E fixtures already ship instances for the shadowed class.
Per-task notes
-
mq.tidy.1 — Rigid-var refinement type-unification leg.
refine_multi_candidate_residualfilter at lib.rs:2163-2180 extends fromdc.class == ctodc.class == c && constraint_type_matches(&dc.type_, &residual.type_). New unit testmq_tidy_rigid_var_filter_uses_type_unificationpins the same-class-different-typevar discrimination (prelude.Show a+userlib.Show bdeclared, residual on Vara→Resolved("prelude.Show"),userlib.Showdrops because its declared typeVar bdoesn't unify with the residual'sVar a). Architecture decision per plan: only the discharge-time call site needs the type-unification leg; the synth-timeresolve_method_dispatchis invoked withconcrete_arg_type: Noneand constructs the residual's metavar AFTER the dispatch call, so the rigid-var leg there has no residual type to unify against (fresh metavar would trivially unify with any declared-constraint type, degenerating to class-only filter). The class-only filter at synth time is therefore semantically correct, not drift. -
mq.tidy.2 — Bare-class qualifier shape accepted. Inline
qualifier_is_class_shapepredicate at the synth Var-arm dispatch entry (originally lib.rs:2570-2575) extracted as a freepub(crate) fn qualifier_is_class_shape(&Option<String>) -> boolhelper adjacent toparse_method_qualifier. Predicate body:None→ true (bare method);Some(q)with inner dot → true (<module>.<Class>);Some(q)single-segment → first char uppercase (PascalCase). Bare-fn qualifiers ("std_list", lowercase) correctly reject and fall through to the cross-module-fn arm. Call site at the original location reduced to a one-liner. New unit testmq_tidy_bare_class_qualifier_shape_acceptedcovers all four shapes. -
mq.tidy.3 —
class-method-shadowed-by-fnwarning tightened. Newpub(crate) fn any_candidate_class_has_instance(...)helper using BTreeMap range-scan (O(log n) per candidate, O(|candidates|- log n) total) checks the workspace registry for at least one
instance under any candidate class. Warning closure at
lib.rs:2479-2502 (now ~2498-2540 post-T2-insertion-shift) builds
a
registry_unit_view: BTreeMap<(String, String), ()>per closure invocation and calls the helper before emitting. New unit testmq_tidy_shadow_warning_suppressed_when_no_instancepins both directions (empty registry → suppress; one instance → fire). The existingmq3_class_method_shadowed_by_fn_warning_firesin-crate test was a trip-wire — its fixture usedRegistry::default()(no instances) and pre-tidy the warning fired anyway because the old gate didn't check instances. Post-tidy the spec rule correctly suppresses; fixture repaired by injecting aclsmod.Show Intregistry entry directly intows.registry.entriesso the test continues to assert positive-fire behaviour under the post-tidy rule. The three mq3 E2E fixtures (mq3_class_eq_vs_fn_eq
mq3_two_show_*) already ship instances on the shadowed class, so they stayed green without modification.
- log n) total) checks the workspace registry for at least one
instance under any candidate class. Warning closure at
lib.rs:2479-2502 (now ~2498-2540 post-T2-insertion-shift) builds
a
-
mq.tidy.4 — DESIGN.md schema fragments. Four trailing-comment annotations:
SuperclassRef("superclass"field at line 2139),InstanceDef.class(line 2152),Type::Con.name(line 2253), andConstraint.class(line 2273). Class-ref annotations cross-reference §"Class names" / mq.1; the Type::Con annotation cross-references §"Type::Con name scoping" / ct.1 (the actual section title — the plan's "§Type names" / "Canonical type names" search did not match exactly; used the canonical anchor title verified via grep). Doc count of "canonical form" went from 1 → 5 (+4 as planned). All annotations are inline trailing-comment appends; no line-count change in DESIGN.md (2678 → 2678). -
mq.tidy.5 — Integration verification.
cargo test --workspace --no-fail-fast→ 548 passed, 0 failed (was 545 pre-iter + 3 new mq_tidy_* unit tests).bench/compile_check.pyexit 0; first run showed 1 regression (build_O0_ms.bench_list_sum_explicit+5.43% within 20.0% tolerance, classifiedok; the summary's "1 regressed" count is a false-positive on the first run, second run shows 0 regressed, 24/24 stable).bench/cross_lang.pyexit 0 both runs, 25/25 stable.bench/check.pyexit 0 both runs; run 1 had 3 regressed / 3 improved (alllatency.implicit_at_rc.*max-tail metrics — same noise class the audit-mq journal documented across 4 consecutive runs); run 2 had 0 regressed / 1 improved / 62 stable — exactly the metric-identity-migration pattern the audit named as variance, not signal. Baseline pristine per the conservative-call convention (6th-consecutive observation now). Plan Step 5 (prelude roundtrip viacargo run --bin ail -- check examples/prelude.ail.json) returned exit 1 with "module name 'prelude' is reserved" — but this is identical to pre-edit behaviour (verified viagit stash), not a regression from this iter. The plan's expectation was inaccurate; the actual sanity gate (DESIGN.md doc edits do not break prelude consistency) is satisfied by the workspace test suite passing 548/548.
Concerns
-
Plan Task 3 Step 6 anticipation was off for one in-crate test. The plan said: "Expected: PASS — the existing positive fixtures (
mq3_class_eq_vs_fn_eq,typeclass_22b3::rewrite_walker_skips_locally_shadowed_class_method) all ship registry instances for the shadowed class, so the tightened predicate continues to admit the warning emission." This was correct for those two fixtures (both E2E), but the in-crate unit testmq3_class_method_shadowed_by_fn_warning_firesbuilds its workspace withRegistry::default()(zero instances) and pre-tidy relied on the looser rule. Post-tidy it correctly suppresses; the fixture was repaired inline by injecting aclsmod.Show Intregistry entry. This is exactly analogous to thetypeclass_22b3::rewrite_walker_skips_locally_shadowed_class_methodtrip-wire mq.3 documented — same pattern, different test. Fixture-repair as a coherence consequence of a deliberately tightened spec rule; the test's intent ("warning fires when a free fn shadows a class method") is unchanged. -
Plan Task 5 Step 5 prelude check expectation was off. The plan said
cargo run --bin ail -- check examples/prelude.ail.jsonexpectedokexit 0; the actual behaviour is exit 1 with the "module name 'prelude' is reserved (auto-injected by the loader)" error. This is identical to pre-edit behaviour (verified viagit stash); the prelude file is the loader's auto-injected module, not a workspace entry, soail checkagainst it has always failed by design. The right sanity gate for "DESIGN.md doc edits do not break prelude consistency" is the workspace test pass (548/548), which exercises every workspace test that uses the prelude. The plan's prescribed command can be removed from future tidy plans. -
T2 docstring update on
parse_method_qualifierwas not explicitly in the plan. The plan asked for adding the newqualifier_is_class_shapehelper but did not enumerate the paragraph update onparse_method_qualifier's docstring. The old docstring said "Callers gate on the qualifier shape: a class qualifier is always qualified per mq.1's canonical-form rule" — which the broadened gate directly contradicts (bare PascalCase qualifiers now go to class-dispatch). Updating the existing helper's documentation to reference the new helper and remove the now-incorrect claim is strictly required to keep the helper's documentation coherent with the requested behaviour change; it is the docstring analog of "code edits strictly required to make the requested changes compile." Defensible as a coherence consequence, not unrequested scope creep.
Known debt
-
bench/check.py max-tail noise envelope. 6th-consecutive observation of the
latency.implicit_at_rc.*max-tail metric identity migrating between runs. The conservative-call convention has held the baseline pristine across 5 prior audits; this iter adds run 6 to the lineage. A future audit may consider ratification via--update-baselineif the pattern persists for another 2-3 audits; until then, the lineage is the documentation. -
mq.tidy did not deliver the [low-1] (Trajectory B + D E2E) or [low-2] (mono env shape) roadmap-backlog items. Same scope as audit-mq's deferral; these wait for the multi-class workspace shape to land downstream.
-
mq.tidy did not refactor
synth(...)'s 10-mut-ref signature. audit-mq's [medium-3] item was explicitly acknowledged-debt, not in tidy scope. Same disposition; future iter may revisit if a natural refactor opportunity arises.
Files touched
Code:
- crates/ailang-check/src/lib.rs
Docs:
- docs/DESIGN.md
- docs/journals/2026-05-13-iter-mq.tidy.md (this file, new)
No new test files; the three new mq_tidy_* unit tests are inline in
crates/ailang-check/src/lib.rs's mod tests block.
Stats
bench/orchestrator-stats/2026-05-13-iter-mq.tidy.json