test(IMP-05): tighten Step 9 candidate evidence guard

Refs #5

Replace the hand-built Case 7 payload assertion with a temporary
production-source guard. The test now fails if Step 9 stops emitting
candidate_evidence, breaks the fallback_chain compat alias, or removes
the alias intent comment.

This is intentionally temporary because Step 9 application-plan unit
assembly is inline. Follow-up IMP-32 should extract a helper and replace
this source-string guard with a direct helper test.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-14 00:24:42 +09:00
parent 21476ae000
commit 23d1b25144

View File

@@ -20,6 +20,9 @@ from typing import Optional
import pytest
import inspect
from src import phase_z2_pipeline
from src.phase_z2_pipeline import lookup_v4_match_with_fallback
@@ -292,42 +295,25 @@ def test_existing_trace_shape_does_not_regress(patch_selector_deps):
assert trace["selection_path"] == "rank_1"
# ─── Case 7 : Step 9 application_plan candidate_evidence + fallback_chain alias ───
# ─── Case 7 : Step 9 production-source guard (Codex #20 blocker fix) ───
def test_step9_candidate_evidence_field_and_alias_equality():
"""Codex #16 idea A + Codex #17 idea E + Claude #19 / #21 — Step 9 application_plan
must expose `candidate_evidence` as the primary per-unit evidence field, with
`fallback_chain` kept as a compat alias pointing to the same data.
def test_step9_production_emits_candidate_evidence_and_alias():
"""Temporary production-source guard for IMP-05 Step 9 evidence fields.
Both fields must reference the same selection_trace.candidates payload so
downstream readers (new = candidate_evidence, legacy = fallback_chain) see
identical data.
Step 9 application-plan unit assembly is currently inline, so this test
checks the exact production assignments until IMP-32 extracts a helper.
Once that helper exists, replace this source-string guard with a direct
helper-call test.
"""
# Synthetic selection_trace.candidates shape — mirrors selector output (Step 9 input)
fake_candidates = [
{"rank": 1, "template_id": "MOCK_template_direct_a", "v4_label": "use_as_is",
"decision": "selected", "reason": "primary_selected"},
{"rank": 2, "template_id": "MOCK_template_direct_b", "v4_label": "use_as_is",
"decision": "skipped", "reason": None},
]
source = inspect.getsource(phase_z2_pipeline)
candidate_line = '"candidate_evidence": selection_trace.get("candidates", [])'
alias_line = '"fallback_chain": selection_trace.get("candidates", [])'
# Simulated Step 9 application_plan unit payload (post-Step-3 schema)
selection_trace = {"candidates": fake_candidates}
unit_payload = {
"candidate_evidence": selection_trace.get("candidates", []),
"fallback_chain": selection_trace.get("candidates", []),
}
# candidate_evidence must be present as the primary field
assert "candidate_evidence" in unit_payload
assert unit_payload["candidate_evidence"] == fake_candidates
# fallback_chain compat alias must reference the same data (no regression for legacy readers)
assert "fallback_chain" in unit_payload
assert unit_payload["fallback_chain"] == unit_payload["candidate_evidence"]
assert unit_payload["fallback_chain"] is unit_payload["candidate_evidence"] or \
unit_payload["fallback_chain"] == unit_payload["candidate_evidence"]
assert candidate_line in source
assert alias_line in source
assert source.index(candidate_line) < source.index(alias_line)
assert "compat alias; prefer candidate_evidence" in source
# ─── Case 8 : Step 20 slide-status qualifier fields presence + defensive default