feat(IMP-15): 실행-1 — Step 14 image_aspect_mismatch detection
Issue: #45 (IMP-15 실행-1, image axis only).
Adds Selenium-based <img> aspect ratio measurement to Step 14
run_overflow_check + numeric tolerance gate. Tolerance lives as
module-scope constant so tests can import it.
src/phase_z2_pipeline.py (+73/-2):
- L131-L135 IMAGE_ASPECT_DELTA_TOL = 0.05 (module scope, importable)
- L2216-L2261 JS payload extension: image_events[] per <img>
(src, zone_position via closest('.zone') with 'unknown' fallback,
zone_template_id, natural/rendered w+h+ratio, delta, slide-rel bbox)
- L2262 run_overflow_check return extended with image_events
- L2302-L2320 Python aggregation: abs(delta) > TOL ⇒ fail_reasons
append 'image aspect mismatch in zone--<pos>: natural=<n> rendered=<r>
delta=<+d> (template=<tid>, tol=0.05, src=<src>)'.
Null-delta entries (image not loaded) are skipped — no false positive.
Branch placed AFTER existing non-image branches; ordering & strings
for slide/slide-body/zone/clipped_inner unchanged.
- L4425-L4429 Step 14 note: image half closed, table half deferred
to 실행-2.
tests/phase_z2/test_phase_z2_step14_image_check.py (+196, new):
- 3-tier chromedriver resolver mirroring pipeline (PROJECT_ROOT/
chromedriver{,.exe} → PATH → Selenium Manager probe).
- pytestmark: skip when chromedriver unresolvable AND
PHASE_Z_REQUIRE_SELENIUM != '1'; xfail(strict=True) opt-in when =='1'.
- Fixture A: 200×100 img rendered 200×100 → aspect_delta < 0.05, passed.
- Fixture B: 200×100 intrinsic forced to 200×200 → delta > 0.30,
fail_reason present.
- Fixture C: <img> with no .zone ancestor → zone_position == 'unknown'.
Verification (Stage 4 PASS, Claude + Codex independent):
- pytest -q tests/phase_z2/test_phase_z2_step14_image_check.py → 3 passed
- PHASE_Z_REQUIRE_SELENIUM=1 same suite → 3 passed (strict opt-in)
- pytest -q tests/phase_z2 → 90 passed (no regression)
- pytest -q --ignore=tests/matching → 174 passed
Scope-locked: no slide_base.html / catalog / classifier / debug.json /
spec-doc changes. table_events (실행-2), visual_check_passed flip
(실행-3), debug.json image_events surfacing + PHASE-Z spec doc row
(실행-4) remain queued as separate IMP-15 child execution issues.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>