test: 통합 스냅샷 재생성 스크립트 추가 + 회귀 테스트 갱신 (#29 후속 작업분)
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 9m49s

- 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>
This commit is contained in:
2026-08-12 11:13:44 +09:00
co-authored by Claude Fable 5
parent 9b79bf7538
commit 4d7c401779
19 changed files with 2274 additions and 851 deletions
+53 -62
View File
@@ -107,8 +107,13 @@ def test_rank_1_direct_eligible_is_retained(patch_selector_deps):
# ─── Case 2 : rank-1 non-direct → rank-2/3 direct selected (fallback used) ───
def test_rank_1_non_direct_promotes_rank_2(patch_selector_deps):
"""Codex #10 E4 case 2 — rank-1 reject + rank-2 use_as_is → promote rank-2."""
def test_rank_1_non_direct_promotes_direct_candidate(patch_selector_deps):
"""Codex #10 E4 case 2 — IMP-39(#68) label-priority sort 반영 갱신.
구 계약: rank 순 평가 → reject 스킵 후 rank_2_fallback 승격.
현 계약: 평가 전 label-priority 정렬 → use_as_is 가 정렬 1위로 즉시
선택 (fallback 아님). 선택 결과물(direct_a)은 동일 — 표기 의미만 변경.
"""
v4 = _make_v4([
_j(1, "MOCK_template_reject_a", "MOCK_frame_001", "reject"),
_j(2, "MOCK_template_direct_a", "MOCK_frame_002", "use_as_is"),
@@ -120,39 +125,31 @@ def test_rank_1_non_direct_promotes_rank_2(patch_selector_deps):
assert match is not None
assert match.template_id == "MOCK_template_direct_a"
assert match.v4_rank == 2
assert match.selection_path == "rank_2_fallback"
assert trace["fallback_used"] is True
assert trace["selected_rank"] == 2
assert "phase_z_status_not_allowed" in trace["fallback_reason"]
assert match.selection_path == "rank_1"
assert trace["fallback_used"] is False
assert trace["selected_rank"] == 1
# ─── Case 3 : duplicate template_id is skipped / deduped ────────────────────
def test_duplicate_template_id_is_skipped_rank_3_wins(patch_selector_deps):
"""Codex #14 dedup precision lock — first occurrence reserves template_id
for the chain regardless of decision. Later rank with same template_id MUST
be skipped as duplicate, regardless of its V4 label.
def test_duplicate_template_id_skipped_under_label_priority_sort(patch_selector_deps):
"""Codex #14 dedup — IMP-39(#68) label-priority sort 반영 갱신.
Fixture simulates V4 anomaly : rank-1 + rank-2 share same template_id (and
same frame_id per Codex #6 1:1 catalog terminology — real catalog 정합).
rank-1 label = reject (non-direct, first occurrence), rank-2 label =
use_as_is (would be executable but MUST be skipped as duplicate per
Codex #14 intended rule). rank-3 = distinct executable template, wins.
dedup 규칙 자체("첫 occurrence 가 template_id 를 claim, 이후 중복은
decision 무관 skip")는 존속하나, '첫 occurrence' 의 기준이 원 rank 순
→ 정렬 순으로 바뀌었다. 구 예시(reject 가 claim 해 use_as_is 중복이
져야 함)는 정렬 하에서 성립 불가 — use_as_is 가 정렬 선두로 오므로
같은 라벨의 중복 쌍으로 dedup 을 검증한다.
Per Codex #14 example :
rank 1: A reject → skipped (non-direct), template A claimed
rank 2: A use_as_is → skipped as duplicate_template_id (must NOT win)
rank 3: B use_as_is → selected (distinct template, eligible)
Fixture: A(미등록) use_as_is ×2 + B(등록) use_as_is.
정렬 1: A use_as_is → skipped_no_contract (첫 occurrence, A claim)
정렬 2: A use_as_is duplicate_template_id (audit 필드 보존)
정렬 3: B use_as_is → selected
"""
v4 = _make_v4([
# rank-1 : non-direct (reject), reserves template_id for chain
_j(1, "MOCK_template_dup_a", "MOCK_frame_dup_001", "reject"),
# rank-2 : same template_id + same frame_id (1:1 catalog), would be
# executable but MUST be skipped as duplicate (Codex #14 intended rule)
_j(2, "MOCK_template_dup_a", "MOCK_frame_dup_001", "use_as_is"),
# rank-3 : distinct executable template, wins
_j(1, "MOCK_template_dup_unreg", "MOCK_frame_dup_001", "use_as_is"),
_j(2, "MOCK_template_dup_unreg", "MOCK_frame_dup_001", "use_as_is"),
_j(3, "MOCK_template_direct_a", "MOCK_frame_003", "use_as_is"),
])
@@ -160,34 +157,26 @@ def test_duplicate_template_id_is_skipped_rank_3_wins(patch_selector_deps):
v4, "S1", raw_content="- a\n- b\n- c\n"
)
# rank-3 must be selected (distinct executable, after rank-1+2 duplicates)
assert match is not None
assert match.template_id == "MOCK_template_direct_a"
assert match.v4_rank == 3
assert match.selection_path == "rank_3_fallback"
assert trace["fallback_used"] is True
assert trace["selected_rank"] == 3
# Trace must preserve all 3 candidate entries with precise reasons
candidates = trace["candidates"]
by_rank = {c["rank"]: c for c in candidates}
assert set(by_rank.keys()) == {1, 2, 3}
# rank-1 : non-direct first occurrence (status_not_allowed reason preserved)
# 정렬 1위: 첫 occurrence — no_contract skip 이면서 template claim
assert by_rank[1]["decision"] == "skipped"
assert by_rank[1]["reason"] == "phase_z_status_not_allowed:fallback_candidate"
assert by_rank[1]["template_id"] == "MOCK_template_dup_a"
assert by_rank[1]["v4_label"] == "reject"
assert by_rank[1]["reason"] == "skipped_no_contract"
assert by_rank[1]["template_id"] == "MOCK_template_dup_unreg"
# rank-2 : duplicate of rank-1 template (MUST be skipped as duplicate, NOT selected)
# 정렬 2위: 중복 — decision 무관 skip + audit 필드 보존 (Codex #14 존속부)
assert by_rank[2]["decision"] == "skipped"
assert by_rank[2]["reason"] == "duplicate_template_id"
assert by_rank[2]["template_id"] == "MOCK_template_dup_a"
# audit fields preserved even though duplicate
assert by_rank[2]["v4_label"] == "use_as_is"
assert by_rank[2]["template_id"] == "MOCK_template_dup_unreg"
assert by_rank[2]["frame_id"] == "MOCK_frame_dup_001"
# rank-3 : distinct executable, selected
# 정렬 3위: distinct 등록 template — selected
assert by_rank[3]["decision"] == "selected"
assert by_rank[3]["template_id"] == "MOCK_template_direct_a"
@@ -215,10 +204,15 @@ def test_missing_contract_yields_chain_exhausted_trace(patch_selector_deps):
def test_restructure_reject_preserved_as_non_direct_evidence(patch_selector_deps):
"""Codex #10 E4 case 5 + Codex #2 conceptual + Claude #11 L5 — restructure / reject
candidates must remain visible in candidate_evidence with route hints,
not silently discarded.
"""Codex #10 E4 case 5 + Claude #11 L5 — IMP-39(#68) sort 반영 갱신.
가시성 계약("restructure/reject 를 침묵 폐기하지 않는다")의 surface 가
이동했다: IMP-39 정렬로 use_as_is 가 즉시 선택되면 selector trace 는
선택 시점까지의 후보만 기록하므로, 비-direct 후보의 보존은 Emergency
P2 의 lookup_v4_candidates (전원 보존 + candidate_status) 가 담당한다.
"""
from src.phase_z2_pipeline import lookup_v4_candidates
v4 = _make_v4([
_j(1, "MOCK_template_reject_a", "MOCK_frame_001", "reject"),
_j(2, "MOCK_template_restructure_a", "MOCK_frame_002", "restructure"),
@@ -231,26 +225,23 @@ def test_restructure_reject_preserved_as_non_direct_evidence(patch_selector_deps
assert match is not None
assert match.template_id == "MOCK_template_direct_a"
# 정렬 1위 use_as_is 즉시 선택 — trace 는 선택 entry 를 포함
assert any(c["decision"] == "selected" for c in trace["candidates"])
candidates = trace["candidates"]
# All 3 must appear with informative schema (L2 fields)
by_rank = {c["rank"]: c for c in candidates}
assert set(by_rank.keys()) == {1, 2, 3}
# rank-1 reject — non-direct, ai_adaptation_required (IMP-47B u1 policy correction)
assert by_rank[1]["v4_label"] == "reject"
assert by_rank[1]["filtered_for_direct_execution"] is True
assert by_rank[1]["route_hint"] == "ai_adaptation_required"
# rank-2 restructure — non-direct, ai_adaptation_required
assert by_rank[2]["v4_label"] == "restructure"
assert by_rank[2]["filtered_for_direct_execution"] is True
assert by_rank[2]["route_hint"] == "ai_adaptation_required"
# rank-3 use_as_is — direct, direct_render
assert by_rank[3]["v4_label"] == "use_as_is"
assert by_rank[3]["filtered_for_direct_execution"] is False
assert by_rank[3]["route_hint"] == "direct_render"
# 보존 surface: lookup_v4_candidates 가 3 후보 전원 + status 를 노출
candidates = lookup_v4_candidates(v4, "S1", max_n=6)
by_tid = {c.template_id: c for c in candidates}
assert set(by_tid) == {
"MOCK_template_reject_a",
"MOCK_template_restructure_a",
"MOCK_template_direct_a",
}
assert by_tid["MOCK_template_reject_a"].candidate_status == "ai_adaptation_required"
assert by_tid["MOCK_template_direct_a"].candidate_status == "auto_renderable"
# restructure 는 capacity 판정에 따라 auto/ai — 어느 쪽이든 보존이 계약
assert by_tid["MOCK_template_restructure_a"].candidate_status in {
"auto_renderable", "ai_adaptation_required",
}
# ─── Case 6 : additive fields do not regress existing trace shape ───────────