Files
C.E.L_Slide_test2/tests/test_mdx04_vp_routing.py
T
KyeongminandClaude Fable 5 4d7c401779
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 9m49s
test: 통합 스냅샷 재생성 스크립트 추가 + 회귀 테스트 갱신 (#29 후속 작업분)
- tests/integration/scripts/regenerate_snapshots.py: 스냅샷 일괄 재생성 도구
- 통합 스냅샷 9종 갱신, 회귀/유닛 테스트 7건 보강
- ISSUE_DRAFTS_2026-07-02.md: 7/2 전수 검토 이슈 초안 보존

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-12 11:13:44 +09:00

140 lines
5.9 KiB
Python

"""IMP-#85 u6 — mdx04 VP routing regression against the real V4 evidence.
Scope (Stage 2 lock):
- Use the production ``tests/matching/v4_full32_result.yaml`` + the production
``templates/phase_z2/catalog/frame_contracts.yaml`` (no fixtures, no mocks).
- Prove that ``sw_dependency_four_problems`` (VP rank-1 on ``04-2.1``, VP
rank-2 on ``04-2.2``) is excluded from ``lookup_v4_candidates`` after u4,
while ``lookup_v4_all_judgments`` retains it as Step 7-A raw telemetry.
- Guard mdx03 dynamically — the actual rank-1 winners on ``03-1`` / ``03-2``
must be non-VP per catalog AND must survive into live candidates.
- VP gating is asserted data-driven (catalog ``visual_pending: true`` flag),
never hard-coded — matches Stage 1/2 ``feedback_no_hardcoding`` guardrail.
Out of scope:
- Implementing the 17 missing VP builders (별 P0 backlog, IMP-04b / #42).
- VP semantics redefinition or VP frame removal from V4 evidence.
- Adapter pipeline redesign.
"""
from __future__ import annotations
from src.phase_z2_mapper import get_contract
from src.phase_z2_pipeline import (
load_v4_result,
lookup_v4_all_judgments,
lookup_v4_candidates,
)
CRASH_TEMPLATE_ID = "sw_dependency_four_problems"
def _rank1_template_id(v4: dict, section_id: str) -> str:
judgments = v4["mdx_sections"][section_id]["judgments_full32"]
return judgments[0]["template_id"]
# ─── Dynamic catalog proof — VP flag is data-driven ─────────────
def test_crash_template_is_visual_pending_in_catalog():
"""Catalog declares ``sw_dependency_four_problems.visual_pending: true``.
Locks the data-driven contract — the entire u4 / u6 chain rests on this
YAML flag, not a hard-coded frame allow-list. If the catalog ever drops
the flag without registering the ``cards_4_grid`` builder, this assertion
surfaces the regression before mdx04 crashes the mapper.
"""
contract = get_contract(CRASH_TEMPLATE_ID)
assert isinstance(contract, dict), CRASH_TEMPLATE_ID
assert contract.get("visual_pending") is True
# ─── mdx04-2.1 — VP frame at rank 1 ─────────────────────────────
def test_mdx04_2_1_vp_rank_1_marked_blocked_runtime():
"""``04-2.1`` rank-1 VP crash frame — Emergency P2 (2026-05-26) 이후
drop 이 아니라 candidate_status='blocked_runtime' 으로 보존·표시된다.
crash 방지는 status 기반 라우팅(자동 렌더 제외)이 담당. 구 drop 계약은
include_blocked_runtime=False 로 존속."""
v4 = load_v4_result()
assert _rank1_template_id(v4, "04-2.1") == CRASH_TEMPLATE_ID
candidates = lookup_v4_candidates(v4, "04-2.1", max_n=6)
by_tid = {c.template_id: c for c in candidates}
assert CRASH_TEMPLATE_ID in by_tid
assert by_tid[CRASH_TEMPLATE_ID].candidate_status == "blocked_runtime"
# VP 후보는 auto_renderable 로 분류되면 안 된다 (crash-path 차단 핵심)
for c in candidates:
contract = get_contract(c.template_id) or {}
if contract.get("visual_pending") is True:
assert c.candidate_status == "blocked_runtime"
legacy = lookup_v4_candidates(
v4, "04-2.1", max_n=6, include_blocked_runtime=False,
)
assert CRASH_TEMPLATE_ID not in [c.template_id for c in legacy]
def test_mdx04_2_1_retains_vp_frame_in_raw_judgments():
"""Step 7-A axis preservation — raw 32-entry telemetry still carries VP."""
v4 = load_v4_result()
all_tids = [j.template_id for j in lookup_v4_all_judgments(v4, "04-2.1")]
assert CRASH_TEMPLATE_ID in all_tids
# ─── mdx04-2.2 — VP frame at rank 2 ─────────────────────────────
def test_mdx04_2_2_vp_rank_2_marked_blocked_runtime():
"""``04-2.2`` rank-2 VP crash frame — Emergency P2: 보존 + blocked_runtime.
비-VP rank-1 이 선두 유지, VP 는 status 로 자동 렌더에서 제외."""
v4 = load_v4_result()
rank_1 = _rank1_template_id(v4, "04-2.2")
rank_1_contract = get_contract(rank_1) or {}
# Pre-condition for this regression: rank-1 on 04-2.2 is non-VP.
assert rank_1_contract.get("visual_pending") is not True
candidates = lookup_v4_candidates(v4, "04-2.2", max_n=6)
tids = [c.template_id for c in candidates]
assert tids[0] == rank_1
by_tid = {c.template_id: c for c in candidates}
if CRASH_TEMPLATE_ID in by_tid:
assert by_tid[CRASH_TEMPLATE_ID].candidate_status == "blocked_runtime"
legacy = lookup_v4_candidates(
v4, "04-2.2", max_n=6, include_blocked_runtime=False,
)
assert CRASH_TEMPLATE_ID not in [c.template_id for c in legacy]
def test_mdx04_2_2_retains_vp_frame_in_raw_judgments():
"""Raw judgments path preserves VP frame regardless of its rank."""
v4 = load_v4_result()
all_tids = [j.template_id for j in lookup_v4_all_judgments(v4, "04-2.2")]
assert CRASH_TEMPLATE_ID in all_tids
# ─── mdx03 dynamic guard — non-VP rank-1 survives ───────────────
def test_mdx03_rank_1_non_vp_survives_live_candidates():
"""Non-VP rank-1 winners on mdx03 sections must still win after u4.
Dynamic check — pulls rank-1 from the V4 yaml + catalog VP flag at runtime.
No hard-coded template_id list; only the regression contract is asserted.
"""
v4 = load_v4_result()
for section_id in ("03-1", "03-2"):
rank_1 = _rank1_template_id(v4, section_id)
contract = get_contract(rank_1) or {}
assert contract.get("visual_pending") is not True, (
f"{section_id} rank-1 ({rank_1}) unexpectedly VP — guard precondition broken"
)
candidates = lookup_v4_candidates(v4, section_id, max_n=6)
tids = [c.template_id for c in candidates]
assert tids and tids[0] == rank_1, (
f"{section_id}: expected rank-1 ({rank_1}) live, got {tids}"
)