# 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 (across `bench_list_sum.gc_over_bump` +
    `bench_closure_chain.gc_over_bump` + 3
    `latency.explicit_at_rc.*` metrics).
  - Run 2: 3 regressions (`bench_list_sum.bump_s` 3rd-consecutive
    sighting + 2 `latency.implicit_at_rc.*` max-tail), 3
    improvements (same `latency.explicit_at_rc` cluster).
  - 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_s` 3rd-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 same
    `latency.explicit_at_rc` improvement 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.
- `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.tidy` iter to land four drift fixes: `[high-1]`, `[high-2]`,
  `[medium-1]`, `[medium-2]`. Routes through `planner` →
  `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`.
