Architect drift review surfaces:
- 2x [high]: rigid-var refinement misses type-unification leg
(refine_multi_candidate_residual filters declared constraints on
class only, breaks forall a b. Show a, Show b => ... shapes);
same-module bare-class qualifier Show.show unreachable because
qualifier_is_class_shape = q.contains('.') excludes the no-dot
case (contradicts mq.1 canonical-form symmetry).
- 2x [medium]: class-method-shadowed-by-fn warning over-fires on
locals shadowing prelude method names without
class-candidate-for-arg-type check; DESIGN.md Data Model schema
fragments don't carry the canonical-form rule for class-ref
fields (only the prose at 1156-1162 does).
- 1x [medium] acknowledged debt without fix: synth(...) grown to
10 mut-ref parameters — refactor cost disproportionate to gain.
- 2x [low] roadmap backlog: Trajectories B + D have unit-test
coverage only; collect_mono_targets rebuilds env without
active_declared_constraints (currently latent).
Bench: compile_check.py + cross_lang.py exit 0; check.py exit 1
across 4 consecutive re-runs with metric identity shifting
between runs — 5th-consecutive audit observation of the same
noise envelope. Baseline pristine.
Resolution: mq.tidy iter covers 4 actionable items; 3 deferred.
8.3 KiB
audit-mq — milestone close (module-qualified-class-names)
Date: 2026-05-13
Milestone: module-qualified-class-names
Iters covered: mq.1 (0eb3323) → mq.2 (2e6a4ca) → mq.3 (99d3968)
Range from previous close: 0dcdaab..99d3968 (audit-ct-tidy → mq.3)
Status: drift_found — mq.tidy queued
Summary
The module-qualified-class-names milestone landed in three iters:
mq.1 lifted the canonical-form rule to the three class-ref schema
fields and qualified all workspace-internal class-name keys; mq.2
installed the type-driven dispatch mechanism without exercising it
end-to-end; mq.3 retired WorkspaceLoadError::MethodNameCollision,
re-keyed class_methods to a (class, method) tuple, plumbed
Env.active_declared_constraints, added the
class-method-shadowed-by-fn warning, and shipped three positive
E2E fixtures exercising trajectories C (ambiguous), E (explicit
qualifier), and the class-fn shadow case.
Architect drift review surfaces seven items: two [high] (rigid-var
refinement type-unification leg missing; same-module bare-class
qualifier shape unreachable), three [medium] (warning over-fires on
unrelated locals shadowing prelude method names; DESIGN.md Data
Model schema fragments don't reflect the canonical-form rule for
class-refs; synth(...) has grown to 10 mut-ref parameters), and
two [low] backlog items (trajectories B + D covered by unit tests
only — no E2E fixture; collect_mono_targets rebuilds env without
active_declared_constraints, currently latent).
Bench: check.py exit 1 across four consecutive runs with metric
identity shifting between runs (3 → 1 → 1 → 0 regressions across
re-runs, different metrics each time; one run showed only
improvements). Pattern is consistent with 5th-consecutive audit
noise-class observation. compile_check.py exit 0 (24/24 stable);
cross_lang.py exit 0 (25/25 stable). Baseline left pristine per
the 5th-consecutive conservative-call convention; see Bench notes
below.
Resolution path: mq.tidy iteration covers the four actionable
drift items (two [high] + two [medium]). The 5th [medium]
(synth(...) signature growth) is acknowledged as accepted
post-mq.3 cost and not fixed — refactoring synth(...) would
ripple through 15+ recursive callsites and 5 external callers,
disproportionate to the readability gain. The two [low] items
join the roadmap backlog.
Architect drift items
[high-1] Rigid-var refinement misses type-unification leg
Path: crates/ailang-check/src/lib.rs:2163-2168
The refine_multi_candidate_residual rigid-var branch filters
declared constraints on dc.class == c only. Spec §"Constraint-
discharge refinement" (line 130-138) requires the filter to also
unify the declared constraint's type_ with the residual's
type_. Two same-class declared constraints on different typevars
(forall a b. Show a, Show b => (a, b) -> String style) cannot
currently be discriminated; the filter returns the first match
regardless of which typevar the residual actually came from. No
unit test pins the type-unification leg.
Outcome: fix in mq.tidy.
[high-2] Same-module bare-class qualifier shape unreachable
Path: crates/ailang-check/src/lib.rs:2570-2575
qualifier_is_class_shape = q.contains('.') makes same-module bare-
class qualifiers (Show.show) unreachable; the gate only admits
cross-module class qualifiers (<module>.<Class>.<method>). This
contradicts the canonical-form rule's symmetry — bare class refs
are legal at every other class-ref schema field (per mq.1's rule).
Trajectory E E2E coverage is cross-module-only as a consequence.
Outcome: fix in mq.tidy.
[medium-1] class-method-shadowed-by-fn over-fires
Path: crates/ailang-check/src/lib.rs:2479-2502
The warning fires whenever any class declares the method, not
when a class candidate for the actual arg type exists per spec
§"Class-fn collisions" (line 161-162). Will spam on unrelated
locals shadowing prelude method names (e.g. let show = … inside
an arbitrary body where show has no Show-context).
Outcome: fix in mq.tidy.
[medium-2] DESIGN.md Data Model schema fragments don't reflect canonical-form rule for class-refs
Path: docs/DESIGN.md:2139, 2152, 2273
Data Model schema fragments for InstanceDef, Constraint,
SuperclassRef describe class-ref <id> without flagging the
canonical-form rule. Only the prose at lines 1156-1162 carries the
rule. A schema-section reader without the prose context sees
bare-only.
Outcome: fix in mq.tidy (doc-only).
[medium-3] synth(...) signature growth (accepted)
Path: crates/ailang-check/src/lib.rs:2415
synth(...) now carries 10 mut-ref parameters (added warnings
in mq.3). Two re-entries (mono.rs:614, lift.rs:700) discard
the warnings channel by convention.
Outcome: acknowledged debt, NOT fixed in mq.tidy. Rationale:
refactoring synth(...) (e.g. into a SynthCtx struct carrying
the mut-refs) would ripple through 15+ recursive callsites and 5
external callers, with the only payoff a marginal readability
gain. The mut-ref pattern is consistent with the rest of the
crate (check_in_workspace's Vec<CheckError> accumulator), so
this is project-style rather than drift.
[low-1] Trajectories B + D no E2E
Paths: spec §"Data flow" trajectories B (type-driven narrow-
to-one) + D (rigid-var narrow-to-one) are covered by unit tests
only — tests/method_dispatch_pin.rs 5-step rule tests. No
on-disk fixture exercises either path end-to-end.
Outcome: roadmap backlog (P3). Single-class-per-method workspaces don't exercise these branches; the real coverage gap opens when downstream code starts producing the multi-class shapes the unit tests already pin.
[low-2] collect_mono_targets rebuilds env without active_declared_constraints
Path: crates/ailang-check/src/mono.rs:574-625
Structural divergence from check_fn's env setup. Currently
latent: mono's residuals are concrete-type so the rigid-var
filter never runs. Env-shape invariant unpinned.
Outcome: roadmap backlog (P3). Test that pins the invariant
(env.active_declared_constraints populated consistently
wherever Env is built) belongs with the [low-1] E2E work for
trajectory D.
Bench
bench/check.py: exit 1 across four consecutive re-runs with metric identity shifting between runs:- Run 1: 1 regression (
latency.implicit_at_rc.p99_over_median), 5 improvements (acrossbench_list_sum.gc_over_bump+bench_closure_chain.gc_over_bump+ 3latency.explicit_at_rc.*metrics). - Run 2: 3 regressions (
bench_list_sum.bump_s3rd-consecutive sighting + 2latency.implicit_at_rc.*max-tail), 3 improvements (samelatency.explicit_at_rccluster). - Run 3: 1 regression (different metric again), 5 improvements.
- Run 4: 0 regressions, 2 improvements
(
bench_list_sum.gc_over_bump+bench_closure_chain.gc_over_bump). - Pattern: metric identity is not stable; the recurring
bench_list_sum.bump_s3rd-consecutive sighting per mq.3 journal lineage shows up in 1 of 4 runs only. Audit-ct-tidy (2026-05-12), audit-eob (2026-05-12), audit-ms (2026-05-12), audit-cma (2026-05-12) all left baseline pristine on the samelatency.explicit_at_rcimprovement cluster reappearing across milestones — this is the 5th consecutive audit observation of the same noise envelope. - Conservative call: baseline pristine for 5th consecutive audit. The latency cluster has narrowed-then-widened-then- narrowed across audits, which is itself attribution evidence that this is variance, not signal. If signal were present the metric would not migrate between runs at this rate.
- Run 1: 1 regression (
bench/compile_check.py: exit 0; 24/24 stable.bench/cross_lang.py: exit 0; 25/25 stable.
No --update-baseline invocation this audit.
Resolution
mq.tidyiter to land four drift fixes:[high-1],[high-2],[medium-1],[medium-2]. Routes throughplanner→implement.[medium-3](synth(...)signature growth): acknowledged debt, no action.[low-1](Trajectory B + D E2E) +[low-2](mono env shape): roadmap backlog P3. Same scope — single follow-up iter when the multi-class workspace shape lands.
Files touched
docs/journals/2026-05-13-audit-mq.md(new, this file)docs/journals/INDEX.md(one new line)
No production code or test edits in this audit; all
recommendations land in mq.tidy.