This commit is contained in:
@@ -0,0 +1,285 @@
|
||||
[Claude #2] Stage 2 Round #1 — Plan
|
||||
|
||||
**Stage 2 entry**
|
||||
- commit_sha = `7a52cebfaa8b356c42a5de45c7241a3002615cab`
|
||||
- Issue scope = IMP-15 실행-1 image axis only. Table is issue #46 (실행-2). Classifier consumer + `visual_check_passed` flip = 실행-3. debug.json surfacing + spec doc row = 실행-4.
|
||||
- Stage 1 exit_report scope_lock binding.
|
||||
|
||||
**Stage 1 unresolved_questions resolved here**
|
||||
1. **IMAGE_ASPECT_DELTA_TOL anchor — Codex was right, no spec anchor for 0.05.** Verified `docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` §3.1 table (lines 86–95) does NOT contain `image_aspect_mismatch` row; `image_aspect_mismatch` appears as a *category name* in `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:312,316` but the `0.05` numeric value is not anchored in any spec doc (grep negative across `docs/`). **Decision:** lock constant at module scope with `# TODO(IMP-15 실행-4): mirror in PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md §3.1 / §3.4` comment. Spec doc row addition is out-of-scope per issue body (실행-4).
|
||||
2. **image_events[] schema — minimum fields locked.** Per-event dict = `{src, zone_position, zone_template_id, frame_slot_id, natural_w, natural_h, rendered_w, rendered_h, aspect_natural, aspect_rendered, aspect_delta, aspect_mismatch}`. Bbox px (x/y/w/h) deferred to 실행-4 (debug.json surfacing — current issue does not need bbox).
|
||||
3. **fail_reasons string format locked** as: `f"image aspect mismatch in zone--{zone_position}: natural {nW}x{nH} rendered {rW}x{rH} delta {delta:.3f}"`. Matches issue body prefix verbatim; tail is measurable / grep-friendly.
|
||||
4. **Test fixture — synthesize minimal HTML in pytest tmp_path** (not reuse Phase Z rendered artifacts). PNG generated on-the-fly via Pillow (200×100 single colour). HTML attaches `width=/height=` to force rendered geometry. Keeps test independent of frame DB / composition state; Fixture C can omit `.zone` wrapper without breaking the slide_base contract.
|
||||
|
||||
**Stage 1 commit_sha re-verified** = `7a52cebfaa8b356c42a5de45c7241a3002615cab` (via `git rev-parse HEAD`).
|
||||
|
||||
---
|
||||
|
||||
### Issue-body axes — exhaustive coverage checklist
|
||||
|
||||
| # | axis (issue body verbatim) | covered by | how |
|
||||
|---|---|---|---|
|
||||
| A1 | "JS payload (image_events) + Python aggregation" — `<img>` traversal | u1 | `slide.querySelectorAll('img')` → measure `naturalWidth/Height` + `clientWidth/Height`; result included in returned dict |
|
||||
| A2 | "emit `image_aspect_mismatch` fail_reason when |delta| > 0.05" | u1 | `IMAGE_ASPECT_DELTA_TOL = 0.05` gates appendage to `fail_reasons` |
|
||||
| A3 | "`result["image_events"]` populated for every `<img>` in slide" | u1 | return literal extended from 5 keys to 6 keys (`image_events` appended last; legacy 5 byte-identical) |
|
||||
| A4 | "`IMAGE_ASPECT_DELTA_TOL = 0.05` constant in pipeline" | u1 | module scope in `src/phase_z2_pipeline.py` near existing SLIDE_W/SLIDE_H constants (line 118–129 region); importable as `from src.phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOL` |
|
||||
| A5 | "`fail_reasons` includes `image aspect mismatch in zone--<pos>: ...`" | u1 | Python-side aggregator iterates `result["image_events"]` and appends fail_reason for every event where `aspect_mismatch=True` |
|
||||
| A6 | "Selenium test suite added with skip-guard" | u2 | new file `tests/phase_z2/test_phase_z2_step14_image_check.py` |
|
||||
| A7 | "Fixture A — no distortion (200×100 / 200×100): aspect_delta < 0.05, passed=True" | u2 | `_make_fixture_A()` writes 200×100 PNG via Pillow + HTML with `<img width=200 height=100>` inside a `.zone`; asserts `image_events[0]["aspect_delta"] < 0.05`, `passed == True`, no `image aspect mismatch` in fail_reasons |
|
||||
| A8 | "Fixture B — forced distortion (200×100 intrinsic, 200×200 rendered): aspect_delta > 0.30, fail_reason present, passed=False" | u2 | `_make_fixture_B()` same PNG, `<img width=200 height=200>` inside `.zone[data-zone-position="top"]`; asserts `aspect_delta > 0.30`, `passed == False`, fail_reasons contains literal substring `"image aspect mismatch in zone--top"` |
|
||||
| A9 | "Fixture C — image with no .zone ancestor: zone_position == 'unknown' (attachment fallback)" | u2 | `_make_fixture_C()` `<img>` placed directly under `.slide` (no `.zone` wrapper), forced distortion to trigger fail_reason; asserts `image_events[0]["zone_position"] == "unknown"`, fail_reasons contains `"image aspect mismatch in zone--unknown"` |
|
||||
| A10a | "Chromedriver skip-guard: skip when not resolvable" | u2 | helper `_resolve_chromedriver()` mirrors `pipeline.py:2082-2099` resolver order (`PROJECT_ROOT/chromedriver`, `PROJECT_ROOT/chromedriver.exe`, then `shutil.which('chromedriver')`); fixture returns `None` → `pytest.skip("chromedriver not resolvable")` when env var unset |
|
||||
| A10b | "xfail strict when PHASE_Z_REQUIRE_SELENIUM=1" | u2 | `if os.environ.get("PHASE_Z_REQUIRE_SELENIUM") == "1" and resolver returns None: pytest.xfail(strict=True, reason="...")`; expressed via `pytest.mark.xfail(strict=True, condition=...)` decorator wired through a fixture |
|
||||
| B1 | Step note update — "image / table 검사 부재 — Step 14 ⚠ partial." → image half resolved | u1 | `pipeline.py:4358` note string rewritten to: `"Selenium 실측 — clientHeight / scrollHeight / excess_y / frame_slot_metrics / image_events. image 검사 추가됨 (IMP-15 실행-1). table 검사 부재 — Step 14 ⚠ partial."` |
|
||||
| C1 | Guardrail — no existing fail_reasons strings reordered | u1 | new image branch appended *after* existing zone clipped_inner branch in the same `for z in result["zones"]: ...` block sibling loop; existing string order preserved byte-identical |
|
||||
| C2 | Guardrail — no AI calls in Step 14 path | u1 + u2 | aggregator is deterministic; tests check threshold logic only |
|
||||
| C3 | Guardrail — no edits to `templates/phase_z2/slide_base.html` | n/a | fixtures synthesize minimal HTML directly; production template untouched |
|
||||
| C4 | Guardrail — `IMAGE_ASPECT_DELTA_TOL >= 0.05` (Stage 1 guardrail wording) | u1 | constant value locked at 0.05; comment cross-refs Stage 1 exit_report |
|
||||
|
||||
---
|
||||
|
||||
### Per-unit plan
|
||||
|
||||
Atomicity: estimate_lines ≤ 50 AND files ≤ 3 per unit; counted as total diff in unit's commit (production + tests).
|
||||
|
||||
#### u1 — pipeline.py: module constant + JS payload `image_events[]` + Python aggregator branch + Step 14 note
|
||||
|
||||
**Files (1):**
|
||||
- `src/phase_z2_pipeline.py`
|
||||
|
||||
**Before:**
|
||||
- `run_overflow_check` JS at `src/phase_z2_pipeline.py:2105-2212` returns `{slide, slide_body, zones, frame_slot_metrics, zone_geometries_px}` (line 2211). No `<img>` traversal.
|
||||
- `fail_reasons` aggregator at `pipeline.py:2226-2253` covers slide size / slide overflow / slide-body overflow / zone overflow / clipped_inner only. No image branch.
|
||||
- No `IMAGE_ASPECT_DELTA_TOL` symbol (grep negative).
|
||||
- Step 14 note at `pipeline.py:4358` states `"image / table 검사 부재 — Step 14 ⚠ partial."`.
|
||||
|
||||
**After (per-edit list — all in one file, single commit):**
|
||||
|
||||
1. **Module constant** — insert after existing constants block (after line 129 `DEFAULT_ZONE_MIN_HEIGHT_PX = 100`):
|
||||
```python
|
||||
# Step 14 image aspect mismatch tolerance (IMP-15 실행-1).
|
||||
# Locally anchored — spec doc row pending in IMP-15 실행-4
|
||||
# (PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md §3.1 / §3.4).
|
||||
# Category name appears in docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:312,316.
|
||||
IMAGE_ASPECT_DELTA_TOL = 0.05
|
||||
```
|
||||
|
||||
2. **JS payload extension** — insert before `return { slide: slideM, ... }` at line 2211 (inside `driver.execute_script(...)`):
|
||||
```javascript
|
||||
// IMP-15 실행-1 — <img> intrinsic vs rendered aspect mismatch detection.
|
||||
const image_events = [];
|
||||
slide.querySelectorAll('img').forEach((img) => {
|
||||
const parentZone = img.closest('.zone');
|
||||
const zonePos = parentZone
|
||||
? (parentZone.getAttribute('data-zone-position') || 'unknown')
|
||||
: 'unknown';
|
||||
const zoneTid = parentZone
|
||||
? (parentZone.getAttribute('data-template-id') || '?')
|
||||
: '?';
|
||||
const slotEl = img.closest('[data-frame-slot-id]');
|
||||
const slotId = slotEl ? slotEl.getAttribute('data-frame-slot-id') : null;
|
||||
image_events.push({
|
||||
src: (img.getAttribute('src') || '').slice(0, 200),
|
||||
zone_position: zonePos,
|
||||
zone_template_id: zoneTid,
|
||||
frame_slot_id: slotId,
|
||||
natural_w: img.naturalWidth,
|
||||
natural_h: img.naturalHeight,
|
||||
rendered_w: img.clientWidth,
|
||||
rendered_h: img.clientHeight,
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
3. **Return literal** — change line 2211 from
|
||||
```javascript
|
||||
return { slide: slideM, slide_body: bodyM, zones, frame_slot_metrics, zone_geometries_px };
|
||||
```
|
||||
to
|
||||
```javascript
|
||||
return { slide: slideM, slide_body: bodyM, zones, frame_slot_metrics, zone_geometries_px, image_events };
|
||||
```
|
||||
|
||||
4. **Python aggregator branch** — insert after existing zone loop at line 2249 (after the `clipped_inner` inner loop, before `result["passed"] = ...`):
|
||||
```python
|
||||
# IMP-15 실행-1 — image aspect mismatch detection (post-JS aggregation).
|
||||
for ev in result.get("image_events", []) or []:
|
||||
nW, nH = ev.get("natural_w") or 0, ev.get("natural_h") or 0
|
||||
rW, rH = ev.get("rendered_w") or 0, ev.get("rendered_h") or 0
|
||||
if nW <= 0 or nH <= 0 or rW <= 0 or rH <= 0:
|
||||
ev["aspect_natural"] = None
|
||||
ev["aspect_rendered"] = None
|
||||
ev["aspect_delta"] = None
|
||||
ev["aspect_mismatch"] = False
|
||||
continue
|
||||
an = nW / nH
|
||||
ar = rW / rH
|
||||
delta = abs(an - ar) / an
|
||||
ev["aspect_natural"] = an
|
||||
ev["aspect_rendered"] = ar
|
||||
ev["aspect_delta"] = delta
|
||||
ev["aspect_mismatch"] = delta > IMAGE_ASPECT_DELTA_TOL
|
||||
if ev["aspect_mismatch"]:
|
||||
fail_reasons.append(
|
||||
f"image aspect mismatch in zone--{ev['zone_position']}: "
|
||||
f"natural {nW}x{nH} rendered {rW}x{rH} delta {delta:.3f}"
|
||||
)
|
||||
```
|
||||
|
||||
5. **Step 14 note rewrite** — `pipeline.py:4355-4359` note string:
|
||||
```python
|
||||
note=(
|
||||
"Selenium 실측 — clientHeight / scrollHeight / excess_y / frame_slot_metrics / "
|
||||
"image_events. image 검사 추가됨 (IMP-15 실행-1, IMAGE_ASPECT_DELTA_TOL=0.05). "
|
||||
"table 검사 부재 — Step 14 ⚠ partial."
|
||||
),
|
||||
```
|
||||
`step_status="partial"` retained (table still partial).
|
||||
|
||||
**Tests in u1:** none. Tests live in u2 (Selenium-driven end-to-end fixtures). Smoke-import test is unnecessary because u2's fixture A exercises the constant import path implicitly (`from src.phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOL, run_overflow_check`).
|
||||
|
||||
**estimate_lines:** ~35 (constant 5 + JS block 18 + return literal 1 + aggregator 18 + note 3 ≈ 45; trimmed via inline expression density). Single file. Under threshold.
|
||||
|
||||
**Rollback:** revert all 5 edits in `src/phase_z2_pipeline.py`. No data migration. Trace JSON loses `image_events` and image fail_reasons; legacy 5 keys + existing fail_reasons preserved.
|
||||
|
||||
---
|
||||
|
||||
#### u2 — Selenium test file with 3 fixtures + chromedriver skip-guard
|
||||
|
||||
**Files (1):**
|
||||
- `tests/phase_z2/test_phase_z2_step14_image_check.py` (NEW)
|
||||
|
||||
**Skip-guard helper** (mirrors `pipeline.py:2082-2099` resolver order):
|
||||
```python
|
||||
def _resolve_chromedriver() -> Path | None:
|
||||
from src.phase_z2_pipeline import PROJECT_ROOT
|
||||
for p in (PROJECT_ROOT / "chromedriver", PROJECT_ROOT / "chromedriver.exe"):
|
||||
if p.is_file():
|
||||
return p
|
||||
which = shutil.which("chromedriver")
|
||||
return Path(which) if which else None
|
||||
|
||||
@pytest.fixture
|
||||
def chromedriver_or_skip():
|
||||
p = _resolve_chromedriver()
|
||||
if p is None:
|
||||
if os.environ.get("PHASE_Z_REQUIRE_SELENIUM") == "1":
|
||||
pytest.xfail("chromedriver unresolvable but PHASE_Z_REQUIRE_SELENIUM=1 (strict)")
|
||||
pytest.skip("chromedriver not resolvable on PROJECT_ROOT or PATH")
|
||||
return p
|
||||
```
|
||||
Note: `pytest.xfail(...)` raises `Failed` with `strict` semantics handled at session level. To match Stage 1 contract ("xfail strict — not skip — when PHASE_Z_REQUIRE_SELENIUM=1"), the helper raises via the `pytest.xfail` API directly inside the fixture. This causes the test to be reported as XFAIL (strict-friendly). If a future maintainer needs `xpassed`-detection semantics, the test signature can be decorated with `@pytest.mark.xfail(strict=True, condition=...)` — equivalent surface for CI.
|
||||
|
||||
**Fixture builders:**
|
||||
```python
|
||||
def _write_png(path: Path, w: int, h: int) -> None:
|
||||
from PIL import Image
|
||||
Image.new("RGB", (w, h), (200, 100, 50)).save(path, "PNG")
|
||||
|
||||
def _write_html(html_path: Path, png_uri: str, *, in_zone: bool, render_w: int, render_h: int, zone_position: str = "top"):
|
||||
inner = (
|
||||
f'<div class="zone" data-zone-position="{zone_position}" data-template-id="t1">'
|
||||
f'<img src="{png_uri}" width="{render_w}" height="{render_h}">'
|
||||
f'</div>'
|
||||
if in_zone
|
||||
else f'<img src="{png_uri}" width="{render_w}" height="{render_h}">'
|
||||
)
|
||||
html = (
|
||||
'<!doctype html><html><head><meta charset="utf-8">'
|
||||
'<style>html,body{margin:0;padding:0;}'
|
||||
'.slide{width:1280px;height:720px;position:relative;}'
|
||||
'.slide-body{width:1180px;height:585px;position:absolute;left:50px;top:76px;}'
|
||||
'.zone{position:relative;}</style></head>'
|
||||
f'<body><div class="slide"><div class="slide-body">{inner}</div></div></body></html>'
|
||||
)
|
||||
html_path.write_text(html, encoding="utf-8")
|
||||
```
|
||||
|
||||
**Test cases (3, aligned with issue body Fixtures A/B/C):**
|
||||
|
||||
1. `test_fixture_A_no_distortion_passes(tmp_path, chromedriver_or_skip)` —
|
||||
- PNG 200×100, rendered 200×100, inside `.zone[data-zone-position="top"]`.
|
||||
- Expect: `result["image_events"][0]["aspect_delta"] < IMAGE_ASPECT_DELTA_TOL`, `result["passed"] is True`, no fail_reason containing `"image aspect mismatch"`.
|
||||
|
||||
2. `test_fixture_B_forced_distortion_fails(tmp_path, chromedriver_or_skip)` —
|
||||
- PNG 200×100, rendered 200×200, inside `.zone[data-zone-position="top"]`.
|
||||
- Expect: `aspect_delta > 0.30`, `aspect_mismatch is True`, `result["passed"] is False`, fail_reasons contains substring `"image aspect mismatch in zone--top"`.
|
||||
|
||||
3. `test_fixture_C_no_zone_ancestor_unknown(tmp_path, chromedriver_or_skip)` —
|
||||
- PNG 200×100, rendered 200×200, `<img>` directly under `.slide-body` (no `.zone` wrapper).
|
||||
- Expect: `image_events[0]["zone_position"] == "unknown"`, `image_events[0]["zone_template_id"] == "?"`, fail_reasons contains `"image aspect mismatch in zone--unknown"`.
|
||||
|
||||
**estimate_lines:** ~50 (imports 5 + skip-guard 12 + helpers 20 + 3 tests at ~5 lines each = ~52; trimmed via shared `_run_and_get` helper).
|
||||
|
||||
**Tests under PHASE_Z_REQUIRE_SELENIUM=0:** skip cleanly when chromedriver absent.
|
||||
**Tests under PHASE_Z_REQUIRE_SELENIUM=1:** xfail strict when chromedriver absent; pass when present.
|
||||
|
||||
**Rollback:** delete file. No production impact.
|
||||
|
||||
---
|
||||
|
||||
### Rollback plan (combined)
|
||||
|
||||
- u2 rollback: delete test file.
|
||||
- u1 rollback: revert 5 edits in `src/phase_z2_pipeline.py` (constant + JS block + return literal + aggregator + note). All inverse-applicable from the unit's commit diff.
|
||||
- Combined order: u2 → u1. u1's production reverts must follow u2 deletion to avoid leaving orphaned tests that import a removed constant.
|
||||
|
||||
### Side effects / follow-up issues
|
||||
|
||||
- **(deferred — IMP-15 실행-2, issue #46)** `table_events` standalone-table detection. Same JS extension pattern; independent commit.
|
||||
- **(deferred — IMP-15 실행-3)** Classifier consumer extension (`classify_visual_runtime_check`) emits category `image_aspect_mismatch` from `image_events[i].aspect_mismatch == True`. `visual_check_passed` flip — already correctly driven by `overflow["passed"]` which u1 sets to False on mismatch (no flip needed in 실행-3 if 실행-1 lands first; verify in 실행-3 evidence).
|
||||
- **(deferred — IMP-15 실행-4)** debug.json surfacing of `image_events` (with optional bbox px) + spec doc row update in `PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` §3.1 / §3.4 (anchor sync for `IMAGE_ASPECT_DELTA_TOL = 0.05`).
|
||||
- **(verify in Stage 3)** Step 21 (PDF export) reads `slide_status.fail_reasons` and surfaces them in the PDF; no Step 21 code edit needed (verify by reading Step 21 site during Stage 3 u1 evidence capture).
|
||||
- **(IMP-01 / IMP-13 soft-link)** `image_events[]` is additive top-level alongside `frame_slot_metrics` and `zone_geometries_px` (IMP-01 trace layer). Same shape convention; no IMP-01/IMP-13 fields renamed/removed.
|
||||
|
||||
---
|
||||
|
||||
### IMPLEMENTATION_UNITS
|
||||
|
||||
=== IMPLEMENTATION_UNITS ===
|
||||
- id: u1
|
||||
summary: src/phase_z2_pipeline.py — add IMAGE_ASPECT_DELTA_TOL=0.05 module constant; extend run_overflow_check JS payload with <img> traversal emitting image_events[] (naturalWidth/Height + clientWidth/Height + src + zone_position + zone_template_id + frame_slot_id); add Python aggregator branch computing aspect_natural/rendered/delta/mismatch and appending fail_reasons string "image aspect mismatch in zone--<pos>: natural NxN rendered NxN delta D.DDD" when delta > tol; rewrite Step 14 step-note to mark image half resolved (table half remains partial).
|
||||
files: [src/phase_z2_pipeline.py]
|
||||
tests: []
|
||||
estimate_lines: 45
|
||||
- id: u2
|
||||
summary: tests/phase_z2/test_phase_z2_step14_image_check.py (NEW) — Selenium test file with chromedriver skip-guard (mirroring pipeline.py:2082-2099 resolver order; xfail strict under PHASE_Z_REQUIRE_SELENIUM=1) and 3 fixtures matching issue body Fixtures A/B/C: A=no distortion (200x100/200x100, passed=True), B=forced distortion (200x100 intrinsic, 200x200 rendered, fail_reason in zone--top, passed=False), C=no .zone ancestor (zone_position=='unknown', fail_reason in zone--unknown). PNG generated via Pillow into pytest tmp_path; HTML synthesized inline (no production template touched).
|
||||
files: [tests/phase_z2/test_phase_z2_step14_image_check.py]
|
||||
tests: [tests/phase_z2/test_phase_z2_step14_image_check.py]
|
||||
estimate_lines: 50
|
||||
|
||||
=== EVIDENCE ===
|
||||
- commit_sha at Stage 2 entry: `7a52cebfaa8b356c42a5de45c7241a3002615cab` (via `git rev-parse HEAD`).
|
||||
- Step 14 JS surface return literal verified at `src/phase_z2_pipeline.py:2211`: `return { slide: slideM, slide_body: bodyM, zones, frame_slot_metrics, zone_geometries_px };` — confirms u1 must extend return literal additively (6th key).
|
||||
- fail_reasons aggregator verified at `src/phase_z2_pipeline.py:2226-2252`: 5 existing branches (slide size / slide overflow / slide-body overflow / zone overflow / clipped_inner). u1 inserts image branch as 6th, after zone loop, preserving order.
|
||||
- Module constants block verified at `src/phase_z2_pipeline.py:118-129` (SLIDE_W=1280 … DEFAULT_ZONE_MIN_HEIGHT_PX=100). u1 inserts IMAGE_ASPECT_DELTA_TOL after line 129 — same module-scope band, importable.
|
||||
- Chromedriver resolver verified at `src/phase_z2_pipeline.py:2082-2099`: PROJECT_ROOT/chromedriver → PROJECT_ROOT/chromedriver.exe → fallback `webdriver.Chrome(options=options)` (PATH). u2 skip-guard mirrors first two entries + `shutil.which` for the PATH fallback.
|
||||
- Step 14 note verified at `src/phase_z2_pipeline.py:4358`: `"image / table 검사 부재 — Step 14 ⚠ partial."` — u1 rewrites *image* half to resolved while preserving table half.
|
||||
- Spec anchor claim audit:
|
||||
- `docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` §3.1 table (lines 86-95) — grep negative for `image_aspect_mismatch` and `0.05`. Issue-body anchor claim (§3.1) is incorrect.
|
||||
- `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:312,316` — `image_aspect_mismatch` listed as category name; no `0.05` value.
|
||||
- Conclusion: lock as locally-anchored with TODO comment; spec doc row addition deferred to 실행-4 (per issue body out-of-scope).
|
||||
- IMAGE_ASPECT_DELTA_TOL symbol grep verified absent in repo (Stage 1 evidence reproduced).
|
||||
- Test file path verified absent: `tests/phase_z2/test_phase_z2_step14_image_check.py` does not exist (Stage 1 evidence reproduced).
|
||||
- Selenium fixture strategy verified self-contained: `templates/phase_z2/slide_base.html:267` uses `data-zone-position` attribute on `.zone` only — u2 fixtures synthesize a `.zone` wrapper directly, no slide_base template dependency. No CSS Grid required because fixtures use explicit `width=/height=` on `<img>`.
|
||||
- Pillow availability verified: project uses Pillow already (CLAUDE.md "이미지 크기 읽기: Pillow `Image.open().size`"). No new dependency.
|
||||
- chromedriver.exe presence verified at `D:\ad-hoc\kei\design_agent\chromedriver/win64/147.0.7727.117/chromedriver.exe`. Stage 3 may need to surface this path (PROJECT_ROOT/chromedriver{,.exe} resolver currently does NOT pick this up — Stage 1 chromedriver_candidates is shallow). This affects PHASE_Z_REQUIRE_SELENIUM=1 CI behaviour but does not change u1/u2 scope; flagged here for awareness.
|
||||
- Stage 1 scope_lock items 1-7 mapped:
|
||||
- #1 (JS <img> measurement) → u1
|
||||
- #2 (image_events[] emit) → u1
|
||||
- #3 (fail_reasons image branch with locked string) → u1
|
||||
- #4 (IMAGE_ASPECT_DELTA_TOL=0.05 module constant) → u1
|
||||
- #5 (Selenium test with skip-guard + xfail strict) → u2
|
||||
- #6 (zone_position='unknown' fallback) → u1 (JS `closest('.zone') ? attr : 'unknown'`)
|
||||
- #7 (Step note image half closed; table half partial) → u1
|
||||
- Stage 1 guardrails honoured:
|
||||
- No reordering of non-image fail_reasons strings (u1 appends after zone loop).
|
||||
- No AI calls in Step 14 path (deterministic threshold).
|
||||
- IMAGE_ASPECT_DELTA_TOL locked at 0.05 (not lowered).
|
||||
- Skip vs xfail behaviour matches Stage 1 contract.
|
||||
- zone_position fallback string is literal "unknown".
|
||||
- No edits to templates/phase_z2/slide_base.html.
|
||||
- Stage 1 out_of_scope honoured: table_events untouched (실행-2), classifier consumer untouched (실행-3), debug.json untouched (실행-4), spec doc row untouched (실행-4), all Step ≠ 14 untouched.
|
||||
|
||||
FINAL_CONSENSUS: YES
|
||||
|
||||
Reference in New Issue
Block a user