Extend classify_visual_runtime_check to consume image_events + table_events
and flip visual_check_passed when any classification fires (even if zone-level overflow was clean).
Single axis: classify_visual_runtime_check consumes image_events[] + table_events[]; visual_check_passed flips when ANY classification fires (even on clean zone overflow). pipeline.py 변경 X. router action / debug.json passthrough = out-of-scope.
TABLE_SCROLL_TOL_PX=5phase_z2_pipeline.py:139 — same SSoT rule (issue body names only image TOL; same principle for table).
image_events[]phase_z2_pipeline.py:2239-2280. delta is None ⇒ skip (image not loaded, no false positive).
table_events[]phase_z2_pipeline.py:2287-2325. wrapper_clipped_index is not None ⇒ skip (clipped_inner dedup).
Current classifier (pre-change)
classify_visual_runtime_checkphase_z2_classifier.py:336 sets visual_check_passed=overflow.passed and EARLY-RETURNS at L367-375 on passed=True. Reads only overflow.zones[].clipped_inner + zone-self. Never touches new event arrays. Early-return must be removed so image/table scans still run when zone overflow is clean.
Grep classify_visual_runtime_check in src — 3 callers in pipeline only (L4521/4579/4640) ⇒ semantic widening safe.
Glob tests/phase_z2/**/*.py — target test file does not exist.
FINAL_CONSENSUS: YES
[Claude #1] Stage 1 — IMP-15 실행-3 classifier consumer (axis 3).
## Scope-lock
Single axis: `classify_visual_runtime_check` consumes `image_events[]` + `table_events[]`; `visual_check_passed` flips when ANY classification fires (even on clean zone overflow). pipeline.py 변경 X. router action / debug.json passthrough = out-of-scope.
## Verified preconditions (실행-1/2 landed)
- `IMAGE_ASPECT_DELTA_TOL=0.05` `phase_z2_pipeline.py:134` — classifier imports (SSoT).
- `TABLE_SCROLL_TOL_PX=5` `phase_z2_pipeline.py:139` — same SSoT rule (issue body names only image TOL; same principle for table).
- `image_events[]` `phase_z2_pipeline.py:2239-2280`. `delta is None` ⇒ skip (image not loaded, no false positive).
- `table_events[]` `phase_z2_pipeline.py:2287-2325`. `wrapper_clipped_index is not None` ⇒ skip (clipped_inner dedup).
## Current classifier (pre-change)
- `classify_visual_runtime_check` `phase_z2_classifier.py:336` sets `visual_check_passed=overflow.passed` and EARLY-RETURNS at L367-375 on passed=True. Reads only `overflow.zones[].clipped_inner` + zone-self. Never touches new event arrays. Early-return must be removed so image/table scans still run when zone overflow is clean.
- `tabular_overflow` category exists L234 (§3.2 priority 2) — table consumer reuses verbatim.
- `image_aspect_mismatch` is NEW (not in §3.2 cascade). Aspect-ratio violation ⇒ emit as independent classification entry.
## Acceptance criteria (verbatim)
1. `image_aspect_mismatch` when `delta is not None AND |delta|>IMAGE_ASPECT_DELTA_TOL`.
2. `tabular_overflow` when `wrapper_clipped_index is None AND (excess_x>TOL OR excess_y>TOL)`.
3. `visual_check_passed = overflow.passed AND (not classifications)`.
4. 4 pure-dict tests (no Selenium).
5. Tolerances imported from pipeline.py.
## Guardrails
- Existing 8-rule §3.2 cascade (clipped_inner / zone-self) untouched — new consumers are ADDITIONAL emitters.
- `placement_diagnostics` shape preserved.
- New test file `tests/phase_z2/test_phase_z2_visual_classifier.py` (no collision, confirmed).
=== EVIDENCE ===
- Read `phase_z2_classifier.py` full (1-457) — early-return L367-375, 8-rule cascade L226-285.
- Read `phase_z2_pipeline.py:120-149,2230-2410` — TOL constants + event arrays + dedup pattern.
- Grep `classify_visual_runtime_check` in src — 3 callers in pipeline only (L4521/4579/4640) ⇒ semantic widening safe.
- Glob `tests/phase_z2/**/*.py` — target test file does not exist.
FINAL_CONSENSUS: YES
Confirmed. This issue is limited to classify_visual_runtime_check consuming already-produced image_events[] and table_events[], plus widening visual_check_passed to fail when any classifier event is emitted. pipeline.py passthrough/production of those arrays is already present and is out of scope for this issue. Router behavior and debug.json passthrough remain out of scope.
Verified current state
src/phase_z2_pipeline.py defines IMAGE_ASPECT_DELTA_TOL = 0.05 and TABLE_SCROLL_TOL_PX = 5; classifier should import from there for a single source of truth.
run_overflow_check returns image_events and table_events in its result object.
Existing Python aggregation in phase_z2_pipeline.py already uses image delta and table overflow for fail reasons, but the classifier does not consume those arrays yet.
src/phase_z2_classifier.py currently returns immediately when overflow.get("passed", False) is true, so clean zone overflow prevents image/table classification from firing.
Existing classifier logic reads only overflow.zones[].overflowed and overflow.zones[].clipped_inner; no reads of image_events or table_events exist in the classifier.
tabular_overflow already exists in the classifier cascade and should be reused for table self-overflow.
image_aspect_mismatch is not present in the classifier yet and must be added as a new classification category.
tests/phase_z2/test_phase_z2_visual_classifier.py does not exist, so the requested pure-dict unit test file is missing.
Stage 1 conclusion
The issue body and prior scope report are consistent with the repository state. Required implementation scope is correctly locked to src/phase_z2_classifier.py plus new pure-dict tests under tests/phase_z2/test_phase_z2_visual_classifier.py. No pytest was run, per Stage 1 instructions.
D:\ad-hoc\kei\design_agent\tests\phase_z2
Test results: not run; Stage 1 explicitly says NO pytest.
Verified facts:
IMAGE_ASPECT_DELTA_TOL = 0.05 at src\phase_z2_pipeline.py:134.
TABLE_SCROLL_TOL_PX = 5 at src\phase_z2_pipeline.py:139.
image_events and table_events are included in the run_overflow_check return object at src\phase_z2_pipeline.py:2327.
classify_visual_runtime_check starts at src\phase_z2_classifier.py:336 and currently early-returns on passed overflow at src\phase_z2_classifier.py:367-375.
Existing classifier tabular_overflow category is emitted by classify_overflow at src\phase_z2_classifier.py:233-239.
Target test file tests\phase_z2\test_phase_z2_visual_classifier.py is absent from the directory listing.
FINAL_CONSENSUS: YES
[Codex #1] Stage 1 problem-review for issue #47 (IMP-15 exec-3 classifier consumer).
## Scope-lock
Confirmed. This issue is limited to `classify_visual_runtime_check` consuming already-produced `image_events[]` and `table_events[]`, plus widening `visual_check_passed` to fail when any classifier event is emitted. `pipeline.py` passthrough/production of those arrays is already present and is out of scope for this issue. Router behavior and debug.json passthrough remain out of scope.
## Verified current state
- `src/phase_z2_pipeline.py` defines `IMAGE_ASPECT_DELTA_TOL = 0.05` and `TABLE_SCROLL_TOL_PX = 5`; classifier should import from there for a single source of truth.
- `run_overflow_check` returns `image_events` and `table_events` in its result object.
- Existing Python aggregation in `phase_z2_pipeline.py` already uses image delta and table overflow for fail reasons, but the classifier does not consume those arrays yet.
- `src/phase_z2_classifier.py` currently returns immediately when `overflow.get("passed", False)` is true, so clean zone overflow prevents image/table classification from firing.
- Existing classifier logic reads only `overflow.zones[].overflowed` and `overflow.zones[].clipped_inner`; no reads of `image_events` or `table_events` exist in the classifier.
- `tabular_overflow` already exists in the classifier cascade and should be reused for table self-overflow.
- `image_aspect_mismatch` is not present in the classifier yet and must be added as a new classification category.
- `tests/phase_z2/test_phase_z2_visual_classifier.py` does not exist, so the requested pure-dict unit test file is missing.
## Stage 1 conclusion
The issue body and prior scope report are consistent with the repository state. Required implementation scope is correctly locked to `src/phase_z2_classifier.py` plus new pure-dict tests under `tests/phase_z2/test_phase_z2_visual_classifier.py`. No pytest was run, per Stage 1 instructions.
=== EVIDENCE ===
Commands run:
- `rg -n "IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_events|table_events|classify_visual_runtime_check|tabular_overflow|visual_check_passed" src tests`
- `Get-ChildItem -LiteralPath 'D:\ad-hoc\kei\design_agent\tests\phase_z2' -Force`
- `Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\src\phase_z2_classifier.py' | Select-Object -Skip 210 -First 260`
- `Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.py' | Select-Object -Skip 120 -First 30`
- `Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.py' | Select-Object -Skip 2230 -First 110`
Files checked:
- `D:\ad-hoc\kei\design_agent\src\phase_z2_classifier.py`
- `D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.py`
- `D:\ad-hoc\kei\design_agent\tests\phase_z2`
Test results: not run; Stage 1 explicitly says NO pytest.
Verified facts:
- `IMAGE_ASPECT_DELTA_TOL = 0.05` at `src\phase_z2_pipeline.py:134`.
- `TABLE_SCROLL_TOL_PX = 5` at `src\phase_z2_pipeline.py:139`.
- `image_events` and `table_events` are included in the `run_overflow_check` return object at `src\phase_z2_pipeline.py:2327`.
- `classify_visual_runtime_check` starts at `src\phase_z2_classifier.py:336` and currently early-returns on passed overflow at `src\phase_z2_classifier.py:367-375`.
- Existing classifier `tabular_overflow` category is emitted by `classify_overflow` at `src\phase_z2_classifier.py:233-239`.
- Target test file `tests\phase_z2\test_phase_z2_visual_classifier.py` is absent from the directory listing.
FINAL_CONSENSUS: YES
id: u2
summary: NEW pure-dict test file — 4 cases (image delta>tol fires; image delta<tol no-fire; table self-overflow fires; table dedup when wrapper_clipped_index set)
files: [tests/phase_z2/test_phase_z2_visual_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
=== PER-UNIT RATIONALE ===
u1 — src/phase_z2_classifier.py
Add imports: IMAGE_ASPECT_DELTA_TOL, TABLE_SCROLL_TOL_PX from phase_z2_pipeline (SSoT — no literal duplication; per Stage 1 guardrail).
Remove early-return block at L367-375 (overflow.passed=True) — image/table scans MUST run even when zone overflow is clean. placement_diagnostics build stays at L358-365 (passed-invariant, preserved).
After existing zone-self + clipped_inner loops (L391-424), add two new scanners:
image scan: iterate overflow.get("image_events", []). Skip when ev["delta"] is None (image not loaded — Stage 1 guardrail). Emit image_aspect_mismatch classification when abs(delta) > IMAGE_ASPECT_DELTA_TOL. Set source="image_event", zone_position, zone_template_id, delta, natural_ratio, rendered_ratio, src.
table scan: iterate overflow.get("table_events", []). Skip when ev["wrapper_clipped_index"] is not None (dedup with existing clipped_inner — Stage 1 guardrail). Emit tabular_overflow (reuse §3.2 priority-2 category verbatim, L234) when excess_x > TABLE_SCROLL_TOL_PX or excess_y > TABLE_SCROLL_TOL_PX. Set source="table_event", zone_position, zone_template_id, excess_x, excess_y.
Widen final return (both branches): visual_check_passed = overflow.get("passed", False) and not classifications. Existing branch at L367-375 now also runs scans; if classifications non-empty when overflow.passed=True, return shape mirrors the failed branch (categories_seen / summary populated, unclassified_signals=[]).
Preserve placement_diagnostics in both branches. Preserve existing 8-rule cascade output for zone_self / clipped_inner sources (untouched).
u2 — tests/phase_z2/test_phase_z2_visual_classifier.py (NEW)
Pure-dict, no Selenium, no fixtures. Stub overflow dict with passed=True + zones=[] + image_events=[…] / table_events=[…] arrays. Pass empty debug_zones=[].
tests/phase_z2/test_phase_z2_step14_image_check.py — sibling integration test (Selenium-driven; this issue's tests stay pure-dict per acceptance criteria)
FINAL_CONSENSUS: YES
📌 **[Claude #1] Stage 2: 시뮬 기반 계획 수립 Round #1**
Execution-3 child issue. Scope locked by Stage 1 exit report (issue body + parent #15). Single axis: classifier consumer extension + `visual_check_passed` semantic widening. Plan = 2 atomic units.
=== IMPLEMENTATION_UNITS ===
- id: u1
summary: Extend classify_visual_runtime_check to consume image_events + table_events; widen visual_check_passed; remove overflow.passed early-return
files: [src/phase_z2_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
- id: u2
summary: NEW pure-dict test file — 4 cases (image delta>tol fires; image delta<tol no-fire; table self-overflow fires; table dedup when wrapper_clipped_index set)
files: [tests/phase_z2/test_phase_z2_visual_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
=== PER-UNIT RATIONALE ===
**u1** — `src/phase_z2_classifier.py`
- Add imports: `IMAGE_ASPECT_DELTA_TOL`, `TABLE_SCROLL_TOL_PX` from `phase_z2_pipeline` (SSoT — no literal duplication; per Stage 1 guardrail).
- Remove early-return block at L367-375 (overflow.passed=True) — image/table scans MUST run even when zone overflow is clean. `placement_diagnostics` build stays at L358-365 (passed-invariant, preserved).
- After existing zone-self + clipped_inner loops (L391-424), add two new scanners:
- **image scan**: iterate `overflow.get("image_events", [])`. Skip when `ev["delta"] is None` (image not loaded — Stage 1 guardrail). Emit `image_aspect_mismatch` classification when `abs(delta) > IMAGE_ASPECT_DELTA_TOL`. Set `source="image_event"`, `zone_position`, `zone_template_id`, `delta`, `natural_ratio`, `rendered_ratio`, `src`.
- **table scan**: iterate `overflow.get("table_events", [])`. Skip when `ev["wrapper_clipped_index"] is not None` (dedup with existing clipped_inner — Stage 1 guardrail). Emit `tabular_overflow` (reuse §3.2 priority-2 category verbatim, L234) when `excess_x > TABLE_SCROLL_TOL_PX or excess_y > TABLE_SCROLL_TOL_PX`. Set `source="table_event"`, `zone_position`, `zone_template_id`, `excess_x`, `excess_y`.
- Widen final return (both branches): `visual_check_passed = overflow.get("passed", False) and not classifications`. Existing branch at L367-375 now also runs scans; if `classifications` non-empty when `overflow.passed=True`, return shape mirrors the failed branch (categories_seen / summary populated, unclassified_signals=[]).
- Preserve `placement_diagnostics` in both branches. Preserve existing 8-rule cascade output for zone_self / clipped_inner sources (untouched).
**u2** — `tests/phase_z2/test_phase_z2_visual_classifier.py` (NEW)
- Pure-dict, no Selenium, no fixtures. Stub `overflow` dict with `passed=True` + `zones=[]` + `image_events=[…]` / `table_events=[…]` arrays. Pass empty `debug_zones=[]`.
- 4 tests per acceptance criteria:
1. `test_image_aspect_mismatch_emits_classification` — delta=0.30 → 1 classification, category=`image_aspect_mismatch`, source=`image_event`, `visual_check_passed=False`.
2. `test_image_aspect_delta_below_tol_no_classification` — delta=0.02 → 0 classifications, `visual_check_passed=True`.
3. `test_standalone_table_overflow_emits_classification` — excess_x=20, wrapper_clipped_index=None → 1 classification, category=`tabular_overflow`, source=`table_event`, `visual_check_passed=False`.
4. `test_table_dedup_when_wrapper_clipped` — excess_x=20, wrapper_clipped_index=3 → 0 classifications (dedup honored), `visual_check_passed=True`.
=== OUT-OF-SCOPE (Stage 1 lock, restated) ===
- `pipeline.py` production of image_events / table_events (already landed 실행-1/2).
- Router action for `image_aspect_mismatch` (FUTURE follow-up issue).
- `debug.json` passthrough (→ 실행-4).
- Existing §3.2 8-rule cascade (untouched). `placement_diagnostics` shape (untouched).
=== ROLLBACK ===
Single commit per unit. `git revert <sha>` restores prior classifier semantics; NEW test file removal is no-op for production.
=== EVIDENCE ===
- src/phase_z2_classifier.py:336 — classify_visual_runtime_check entry
- src/phase_z2_classifier.py:367-375 — early-return to remove
- src/phase_z2_classifier.py:234 — tabular_overflow category (priority 2, reused verbatim)
- src/phase_z2_classifier.py:445-456 — current return shape (visual_check_passed=False branch)
- src/phase_z2_pipeline.py:134 — IMAGE_ASPECT_DELTA_TOL=0.05
- src/phase_z2_pipeline.py:139 — TABLE_SCROLL_TOL_PX=5
- src/phase_z2_pipeline.py:2239-2280 — image_events producer (delta-None skip)
- src/phase_z2_pipeline.py:2287-2325 — table_events producer (wrapper_clipped_index dedup)
- tests/phase_z2/test_phase_z2_step14_image_check.py — sibling integration test (Selenium-driven; this issue's tests stay pure-dict per acceptance criteria)
FINAL_CONSENSUS: YES
id: u1
summary: Extend classify_visual_runtime_check to consume image_events and table_events, remove passed early-return, and widen visual_check_passed semantics
files: [src/phase_z2_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
id: u2
summary: Add pure-dict classifier tests for image aspect mismatch, below-tolerance image delta, standalone table overflow, and wrapper-clipped table dedup
files: [tests/phase_z2/test_phase_z2_visual_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
u1 rationale:
Covers the single execution-3 axis: classifier consumer extension for existing image_events and table_events arrays.
Uses src/phase_z2_pipeline.py constants as SSoT imports; no pipeline production or router behavior is touched.
Keeps existing zone-self and clipped_inner cascade intact while allowing image/table classifications to fail visual_check_passed even when overflow.passed is true.
u2 rationale:
Covers all four required tests in the issue body and keeps them pure-dict with no Selenium dependency.
Test list is non-empty and points at the new classifier test file; each acceptance category/dedup path has one focused case.
The unit writes one file and stays under the 50-line implementation-unit limit target.
Out-of-scope notes:
No changes to pipeline.py production of image_events/table_events, debug.json passthrough, router action selection, placement_diagnostics shape, or existing 8-rule zone cascade behavior.
No sample-specific logic, MDX hardcoding, or frame-specific shortcuts are part of this plan.
Rollback strategy:
Revert only src/phase_z2_classifier.py and tests/phase_z2/test_phase_z2_visual_classifier.py if Stage 3/4 verification fails.
=== EVIDENCE ===
Commands run:
Get-Content src/phase_z2_classifier.py first 470 lines
Get-Content src/phase_z2_pipeline.py first 160 lines
Get-ChildItem tests/phase_z2
git status --short
pytest -q tests
Files checked:
src/phase_z2_classifier.py
src/phase_z2_pipeline.py
tests/phase_z2
Test results:
pytest -q tests -> 192 passed in 40.40s
Verified facts:
classify_visual_runtime_check still early-returns when overflow.passed is true, so removing that early return is necessary for image/table event scans.
IMAGE_ASPECT_DELTA_TOL = 0.05 and TABLE_SCROLL_TOL_PX = 5 exist in src/phase_z2_pipeline.py.
Existing tests/phase_z2 directory has no test_phase_z2_visual_classifier.py yet, so the planned test file is correctly new.
Both implementation units are <=50 estimated lines and touch <=3 files.
The plan covers every issue-body acceptance test and includes rollback/out-of-scope boundaries.
FINAL_CONSENSUS: YES
[Codex #1] Stage 2: simulation-plan review Round #1
=== IMPLEMENTATION_UNITS ===
- id: u1
summary: Extend classify_visual_runtime_check to consume image_events and table_events, remove passed early-return, and widen visual_check_passed semantics
files: [src/phase_z2_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
- id: u2
summary: Add pure-dict classifier tests for image aspect mismatch, below-tolerance image delta, standalone table overflow, and wrapper-clipped table dedup
files: [tests/phase_z2/test_phase_z2_visual_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
u1 rationale:
- Covers the single execution-3 axis: classifier consumer extension for existing image_events and table_events arrays.
- Uses src/phase_z2_pipeline.py constants as SSoT imports; no pipeline production or router behavior is touched.
- Keeps existing zone-self and clipped_inner cascade intact while allowing image/table classifications to fail visual_check_passed even when overflow.passed is true.
u2 rationale:
- Covers all four required tests in the issue body and keeps them pure-dict with no Selenium dependency.
- Test list is non-empty and points at the new classifier test file; each acceptance category/dedup path has one focused case.
- The unit writes one file and stays under the 50-line implementation-unit limit target.
Out-of-scope notes:
- No changes to pipeline.py production of image_events/table_events, debug.json passthrough, router action selection, placement_diagnostics shape, or existing 8-rule zone cascade behavior.
- No sample-specific logic, MDX hardcoding, or frame-specific shortcuts are part of this plan.
Rollback strategy:
- Revert only src/phase_z2_classifier.py and tests/phase_z2/test_phase_z2_visual_classifier.py if Stage 3/4 verification fails.
=== EVIDENCE ===
Commands run:
- Get-Content src/phase_z2_classifier.py first 470 lines
- Get-Content src/phase_z2_pipeline.py first 160 lines
- Get-ChildItem tests/phase_z2
- git status --short
- pytest -q tests
Files checked:
- src/phase_z2_classifier.py
- src/phase_z2_pipeline.py
- tests/phase_z2
Test results:
- pytest -q tests -> 192 passed in 40.40s
Verified facts:
- classify_visual_runtime_check still early-returns when overflow.passed is true, so removing that early return is necessary for image/table event scans.
- IMAGE_ASPECT_DELTA_TOL = 0.05 and TABLE_SCROLL_TOL_PX = 5 exist in src/phase_z2_pipeline.py.
- Existing tests/phase_z2 directory has no test_phase_z2_visual_classifier.py yet, so the planned test file is correctly new.
- Both implementation units are <=50 estimated lines and touch <=3 files.
- The plan covers every issue-body acceptance test and includes rollback/out-of-scope boundaries.
FINAL_CONSENSUS: YES
■ unit_executed: u1 (Stage 2 plan — Extend classify_visual_runtime_check)
■ files_changed:
src/phase_z2_classifier.py (+61 / −11)
■ diff_summary
Deferred import (avoid circular) — from phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOL, TABLE_SCROLL_TOL_PX added insideclassify_visual_runtime_check. Module-level import would be circular: phase_z2_pipeline.py:57 imports this module, and the tolerance constants are defined at phase_z2_pipeline.py:134 / :139 (after that import line). Deferred to call time so both modules are fully loaded. Single source of truth — no literal duplication.
Removed early-return on overflow.passed=True (was phase_z2_classifier.py:367-375). image/table scans must run unconditionally; replaced with a 3-line comment block citing IMP-15 실행-3.
Added image_events scan — emits image_aspect_mismatch classification when delta is not None AND |delta| > IMAGE_ASPECT_DELTA_TOL. Each classification carries category, source="image_event", zone_position, zone_template_id, src, natural_ratio, rendered_ratio, delta, rule_applied (formatted with actual delta + tolerance).
Guardrail: delta is None ⇒ skip (image not loaded; no false positive).
Added table_events scan — reuses existing tabular_overflow category verbatim (phase_z2_classifier.py:234, spec §3.2 priority 2) when wrapper_clipped_index is None AND (excess_x > TOL OR excess_y > TOL). Each classification carries category, source="table_event", zone_position, zone_template_id, excess_x, excess_y, rule_applied.
Guardrail: wrapper_clipped_index is not None ⇒ skip (dedup with existing clipped_inner cascade; honors producer-side contract from phase_z2_pipeline.py:2287-2325).
Widened visual_check_passed semantic — final return now computes visual_check_passed = bool(overflow.get("passed", False)) and not classifications. Any image_aspect_mismatch or tabular_overflow from the new emitters flips the gate to False even when zone overflow is clean.
■ follow_up_issue_candidates: none — scope-lock holds. Router action mapping for image_aspect_mismatch is already deferred to a FUTURE follow-up per issue body (out-of-scope this issue), and debug.json passthrough is 실행-4.
AI isolation: classifier remains pure-dict; no LLM call introduced.
Tolerances: imported (not duplicated as literals) — SSoT respected.
Spec §3.2 cascade order: untouched; new emitters are ADDITIVE.
■ diff size note
Plan estimated +45 lines; actual +61 / −11 (net +50). Overage is concentrated in the two classification-dict literals (multi-line for readability) and the deferred-import comment. Logic remains a single unit (u1) — splitting further would fragment the consumer extension axis.
■ next
STOP. Awaiting Codex review on u1 before Round #2 (u2 pure-dict tests).
No commit / push performed in this round (per Stage 3 protocol).
Executing unit: u1
📌 **[Claude #2] Stage 3 (코드 수정) Round #1 — u1 (classifier consumer) 완료**
■ **unit_executed**: `u1` (Stage 2 plan — Extend classify_visual_runtime_check)
■ **files_changed**:
- `src/phase_z2_classifier.py` (+61 / −11)
■ **diff_summary**
1. **Deferred import (avoid circular)** — `from phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOL, TABLE_SCROLL_TOL_PX` added *inside* `classify_visual_runtime_check`. Module-level import would be circular: `phase_z2_pipeline.py:57` imports this module, and the tolerance constants are defined at `phase_z2_pipeline.py:134` / `:139` (after that import line). Deferred to call time so both modules are fully loaded. Single source of truth — no literal duplication.
2. **Removed early-return on `overflow.passed=True`** (was `phase_z2_classifier.py:367-375`). image/table scans must run unconditionally; replaced with a 3-line comment block citing IMP-15 실행-3.
3. **Added image_events scan** — emits `image_aspect_mismatch` classification when `delta is not None AND |delta| > IMAGE_ASPECT_DELTA_TOL`. Each classification carries `category`, `source="image_event"`, `zone_position`, `zone_template_id`, `src`, `natural_ratio`, `rendered_ratio`, `delta`, `rule_applied` (formatted with actual delta + tolerance).
- Guardrail: `delta is None` ⇒ skip (image not loaded; no false positive).
4. **Added table_events scan** — reuses existing `tabular_overflow` category verbatim (`phase_z2_classifier.py:234`, spec §3.2 priority 2) when `wrapper_clipped_index is None AND (excess_x > TOL OR excess_y > TOL)`. Each classification carries `category`, `source="table_event"`, `zone_position`, `zone_template_id`, `excess_x`, `excess_y`, `rule_applied`.
- Guardrail: `wrapper_clipped_index is not None` ⇒ skip (dedup with existing `clipped_inner` cascade; honors producer-side contract from `phase_z2_pipeline.py:2287-2325`).
5. **Widened `visual_check_passed` semantic** — final return now computes `visual_check_passed = bool(overflow.get("passed", False)) and not classifications`. Any image_aspect_mismatch or tabular_overflow from the new emitters flips the gate to False even when zone overflow is clean.
6. **No changes to**:
- Existing 8-rule §3.2 cascade (zone-self / clipped_inner) — unchanged.
- `placement_diagnostics` shape — unchanged.
- `pipeline.py` production of image_events / table_events — out-of-scope (already landed in 실행-1/2).
- Router action mapping / debug.json passthrough — out-of-scope.
■ **smoke verification (pure-dict, no Selenium)**
```
overflow={'passed': True, 'zones': [],
'image_events': [{'delta': 0.1, 'src': 'x',
'zone_position': 'top', 'zone_template_id': 'f1b',
'natural_ratio': 1.0, 'rendered_ratio': 1.1}],
'table_events': []}, debug_zones=[]
→ visual_check_passed = False
→ categories_seen = ['image_aspect_mismatch']
→ len(classifications)= 1
```
Confirms (a) early-return removed (would have short-circuited to True before), (b) image_events consumed, (c) visual_check_passed widening fires. Formal pytest cases land in u2.
■ **remaining_units**: `[u2]` — pure-dict tests for 4 acceptance cases (image fires / image below-tol / table fires / table wrapper-clipped dedup).
■ **follow_up_issue_candidates**: none — scope-lock holds. Router action mapping for `image_aspect_mismatch` is already deferred to a FUTURE follow-up per issue body (out-of-scope this issue), and debug.json passthrough is 실행-4.
■ **scope adherence**
- Sample-specific logic / MDX hardcoding / frame-specific shortcut: NONE.
- AI isolation: classifier remains pure-dict; no LLM call introduced.
- Tolerances: imported (not duplicated as literals) — SSoT respected.
- Spec §3.2 cascade order: untouched; new emitters are ADDITIVE.
■ **diff size note**
Plan estimated +45 lines; actual +61 / −11 (net +50). Overage is concentrated in the two classification-dict literals (multi-line for readability) and the deferred-import comment. Logic remains a single unit (u1) — splitting further would fragment the consumer extension axis.
■ **next**
STOP. Awaiting Codex review on u1 before Round #2 (u2 pure-dict tests).
No commit / push performed in this round (per Stage 3 protocol).
Stage 3 code-edit verification for executed unit u1 is complete.
Verdict: u1 is correct for the scope executed in this round.
Verified facts:
src/phase_z2_classifier.py removes the early return for overflow.passed=True, so image/table event scans run even when zone-level overflow is clean.
The tolerance values are imported from phase_z2_pipeline at call time: IMAGE_ASPECT_DELTA_TOL and TABLE_SCROLL_TOL_PX. This preserves the single source of truth while avoiding the existing module import cycle.
image_events emits image_aspect_mismatch only when delta is not None and abs(delta) > IMAGE_ASPECT_DELTA_TOL.
table_events emits the existing tabular_overflow category only when wrapper_clipped_index is None and either excess_x or excess_y exceeds TABLE_SCROLL_TOL_PX.
The new return gate is bool(overflow.get("passed", False)) and not classifications, so a new image/table classification flips visual_check_passed to False.
Existing zone-self and clipped_inner classifier paths remain in place; placement_diagnostics shape is still returned.
Full pytest was not run because the current Stage 3 verifier instruction says not to run full pytest unless explicitly required.
Remaining units: [u2]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Codex #1]
Stage 3 code-edit verification for executed unit `u1` is complete.
Verdict: `u1` is correct for the scope executed in this round.
Verified facts:
- `src/phase_z2_classifier.py` removes the early return for `overflow.passed=True`, so image/table event scans run even when zone-level overflow is clean.
- The tolerance values are imported from `phase_z2_pipeline` at call time: `IMAGE_ASPECT_DELTA_TOL` and `TABLE_SCROLL_TOL_PX`. This preserves the single source of truth while avoiding the existing module import cycle.
- `image_events` emits `image_aspect_mismatch` only when `delta is not None` and `abs(delta) > IMAGE_ASPECT_DELTA_TOL`.
- `table_events` emits the existing `tabular_overflow` category only when `wrapper_clipped_index is None` and either `excess_x` or `excess_y` exceeds `TABLE_SCROLL_TOL_PX`.
- The new return gate is `bool(overflow.get("passed", False)) and not classifications`, so a new image/table classification flips `visual_check_passed` to `False`.
- Existing zone-self and `clipped_inner` classifier paths remain in place; `placement_diagnostics` shape is still returned.
=== EVIDENCE ===
Commands run:
- `git diff -- src/phase_z2_classifier.py`
- `rg -n "IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|overflow\\.passed|image_events|table_events|visual_check_passed|image_aspect_mismatch|tabular_overflow" src/phase_z2_classifier.py src/phase_z2_pipeline.py`
- `Get-Content -LiteralPath 'src\\phase_z2_classifier.py' | Select-Object -Skip 320 -First 190`
- `Get-Content -LiteralPath 'src\\phase_z2_pipeline.py' | Select-Object -First 160`
- `$env:PYTHONPATH='src'; python -m py_compile src\\phase_z2_classifier.py`
- `$env:PYTHONPATH='src'; <pure-dict smoke script for image/table event branches>`
- `git diff --stat -- src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.py`
Files checked:
- `D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_classifier.py`
- `D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_pipeline.py`
Test results:
- `python -m py_compile src\\phase_z2_classifier.py`: passed.
- Pure-dict smoke checks:
- `image_fire`: `visual_check_passed=False`, `categories_seen=['image_aspect_mismatch']`, `classifications=1`
- `image_below_tol`: `visual_check_passed=True`, `categories_seen=[]`, `classifications=0`
- `table_fire`: `visual_check_passed=False`, `categories_seen=['tabular_overflow']`, `classifications=1`
- `table_dedup`: `visual_check_passed=True`, `categories_seen=[]`, `classifications=0`
- Full pytest was not run because the current Stage 3 verifier instruction says not to run full pytest unless explicitly required.
Remaining units: [u2]
rewind_target: continue_same
FINAL_CONSENSUS: NO
NEW file — tests/phase_z2/test_phase_z2_visual_classifier.py — 4 pure-dict unit tests for the new classify_visual_runtime_check consumer behavior landed in u1. No Selenium / chromedriver dependency; no fixtures; no parametrize. Each test constructs an overflow dict by hand and asserts on the classifier's return shape.
Imports (single source of truth) — tolerances pulled from phase_z2_pipeline, not duplicated as literals:
Tests derive thresholds from imported constants (IMAGE_ASPECT_DELTA_TOL + 0.05, TABLE_SCROLL_TOL_PX + 10, TABLE_SCROLL_TOL_PX + 50) — if the SSoT in phase_z2_pipeline.py:134/:139 shifts, the tests track automatically.
_base_overflow(**overrides) helper — minimal clean overflow dict (passed=True, empty zones / image_events / table_events, no slide / slide_body overflow). Each test overlays only the array it exercises. Keeps test bodies focused on the axis under inspection (image_events scan or table_events scan).
Four required cases (1:1 with issue acceptance criteria):
No Selenium / chromedriver invocation (test session collected and finished in 0.16s — confirms pure-dict path).
Each test's categories_seen / classifications exact-match assertion proves u1's new emitters fire only on the intended path and that the widened visual_check_passed = overflow.passed AND not classifications semantic is observable from the public API.
■ remaining_units: [] (u1 + u2 both complete; Stage 2 plan exhausted).
■ follow_up_issue_candidates:
None within scope. Out-of-scope items already documented in Stage 1 / 2 exit reports:
pipeline.py image_events / table_events production-side regression coverage (separate axis, not implied by this issue).
Router action mapping for image_aspect_mismatch (explicitly deferred — issue body "Out-of-scope" section calls this FUTURE follow-up).
debug.json passthrough for the new event arrays (실행-4 territory).
■ scope-lock: respected — only src/phase_z2_classifier.py (u1) and tests/phase_z2/test_phase_z2_visual_classifier.py (u2) touched. No edits to pipeline.py, no router changes, no debug.json plumbing, no placement_diagnostics shape change, no sample-specific branching.
STOP per orchestrator instruction. No commit, no push. Awaiting Codex Stage 3 verification on u2.
Executing unit: u2
📌 **[Claude #3] Stage 3 (코드 수정) Round #2 — u2 (pure-dict classifier tests) 완료**
■ **unit_executed**: `u2` (Stage 2 plan — Add pure-dict classifier tests)
■ **files_changed**:
- `tests/phase_z2/test_phase_z2_visual_classifier.py` (NEW, 116 lines incl. docstring)
■ **diff_summary**
1. **NEW file** — `tests/phase_z2/test_phase_z2_visual_classifier.py` — 4 pure-dict unit tests for the new `classify_visual_runtime_check` consumer behavior landed in u1. No Selenium / chromedriver dependency; no fixtures; no parametrize. Each test constructs an overflow dict by hand and asserts on the classifier's return shape.
2. **Imports (single source of truth)** — tolerances pulled from `phase_z2_pipeline`, not duplicated as literals:
```python
from src.phase_z2_classifier import classify_visual_runtime_check
from src.phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOL, TABLE_SCROLL_TOL_PX
```
Tests derive thresholds from imported constants (`IMAGE_ASPECT_DELTA_TOL + 0.05`, `TABLE_SCROLL_TOL_PX + 10`, `TABLE_SCROLL_TOL_PX + 50`) — if the SSoT in `phase_z2_pipeline.py:134/:139` shifts, the tests track automatically.
3. **`_base_overflow(**overrides)` helper** — minimal clean overflow dict (passed=True, empty zones / image_events / table_events, no slide / slide_body overflow). Each test overlays only the array it exercises. Keeps test bodies focused on the axis under inspection (image_events scan or table_events scan).
4. **Four required cases (1:1 with issue acceptance criteria)**:
- **`test_image_aspect_mismatch_emits_classification`** — `delta = IMAGE_ASPECT_DELTA_TOL + 0.05` (above tolerance). Asserts:
- `visual_check_passed is False` (widened semantic flips even though zone overflow is clean)
- `categories_seen == ["image_aspect_mismatch"]`
- `classifications[0]` carries `category="image_aspect_mismatch"`, `source="image_event"`, `zone_position="top"`, `delta=<expected>`.
- **`test_image_aspect_delta_below_tol_no_classification`** — `delta = IMAGE_ASPECT_DELTA_TOL / 2.0` (below tolerance). Asserts:
- `visual_check_passed is True`
- `categories_seen == []` and `classifications == []` (no false positive).
- **`test_standalone_table_overflow_emits_classification`** — `wrapper_clipped_index=None`, `excess_y = TABLE_SCROLL_TOL_PX + 10`. Asserts:
- `visual_check_passed is False`
- `categories_seen == ["tabular_overflow"]` (existing `tabular_overflow` category reused verbatim — `phase_z2_classifier.py:234`)
- `classifications[0]` carries `category="tabular_overflow"`, `source="table_event"`, `zone_position="bottom_l"`, `excess_y=<expected>`.
- **`test_table_dedup_when_wrapper_clipped`** — `wrapper_clipped_index=0` (non-null) with `excess_y = TABLE_SCROLL_TOL_PX + 50` (well above tolerance — proves the dedup branch wins over the threshold check). Asserts:
- `visual_check_passed is True`
- `classifications == []` (honors producer-side dedup contract from `phase_z2_pipeline.py:2287-2325`; the wrapper's existing `clipped_inner` cascade owns this case).
5. **No changes to**:
- `src/phase_z2_classifier.py` (already complete in u1).
- `src/phase_z2_pipeline.py` (out-of-scope — image_events / table_events production landed in 실행-1/2).
- Existing classifier tests / 8-rule cascade tests — untouched.
■ **verification**
```
$ PYTHONPATH=. python -m pytest tests/phase_z2/test_phase_z2_visual_classifier.py -v
collected 4 items
tests/phase_z2/test_phase_z2_visual_classifier.py::test_image_aspect_mismatch_emits_classification PASSED [ 25%]
tests/phase_z2/test_phase_z2_visual_classifier.py::test_image_aspect_delta_below_tol_no_classification PASSED [ 50%]
tests/phase_z2/test_phase_z2_visual_classifier.py::test_standalone_table_overflow_emits_classification PASSED [ 75%]
tests/phase_z2/test_phase_z2_visual_classifier.py::test_table_dedup_when_wrapper_clipped PASSED [100%]
============================== 4 passed in 0.16s ==============================
```
- All 4 acceptance-criteria tests green.
- No Selenium / chromedriver invocation (test session collected and finished in 0.16s — confirms pure-dict path).
- Each test's `categories_seen` / `classifications` exact-match assertion proves u1's new emitters fire only on the intended path and that the widened `visual_check_passed = overflow.passed AND not classifications` semantic is observable from the public API.
■ **remaining_units**: `[]` (u1 + u2 both complete; Stage 2 plan exhausted).
■ **follow_up_issue_candidates**:
- None within scope. Out-of-scope items already documented in Stage 1 / 2 exit reports:
- `pipeline.py` image_events / table_events production-side regression coverage (separate axis, not implied by this issue).
- Router action mapping for `image_aspect_mismatch` (explicitly deferred — issue body "Out-of-scope" section calls this FUTURE follow-up).
- `debug.json` passthrough for the new event arrays (실행-4 territory).
■ **scope-lock**: respected — only `src/phase_z2_classifier.py` (u1) and `tests/phase_z2/test_phase_z2_visual_classifier.py` (u2) touched. No edits to pipeline.py, no router changes, no debug.json plumbing, no placement_diagnostics shape change, no sample-specific branching.
STOP per orchestrator instruction. No commit, no push. Awaiting Codex Stage 3 verification on u2.
Stage 3 code-edit verification for executed unit u2 is complete.
Verdict: u2 is correct, and all planned implementation units for this issue are now complete.
Verified facts:
tests/phase_z2/test_phase_z2_visual_classifier.py is a new pure-dict test file and does not use Selenium, chromedriver, browser fixtures, or pipeline execution.
The test file imports IMAGE_ASPECT_DELTA_TOL and TABLE_SCROLL_TOL_PX from src.phase_z2_pipeline, so threshold values are not duplicated as hardcoded literals.
test_image_aspect_mismatch_emits_classification covers delta > IMAGE_ASPECT_DELTA_TOL, asserts image_aspect_mismatch, and verifies visual_check_passed is False.
test_image_aspect_delta_below_tol_no_classification covers below-tolerance image delta and asserts no classifications with visual_check_passed is True.
test_standalone_table_overflow_emits_classification covers standalone table self-overflow with wrapper_clipped_index=None, asserts tabular_overflow, and verifies visual_check_passed is False.
test_table_dedup_when_wrapper_clipped covers non-null wrapper_clipped_index with excess beyond tolerance and asserts no tabular_overflow, preserving the dedup contract.
The classifier implementation remains aligned with the tests: image/table event scans are unconditional, image_aspect_mismatch and tabular_overflow are emitted under the required thresholds, and visual_check_passed is overflow.passed AND not classifications.
pytest -q tests\phase_z2\test_phase_z2_visual_classifier.py: 4 passed in 0.04s.
Full pytest was not run because the current Stage 3 verifier instruction says not to run full pytest unless explicitly required.
Remaining units: none
FINAL_CONSENSUS: YES
[Codex #2]
Stage 3 code-edit verification for executed unit `u2` is complete.
Verdict: `u2` is correct, and all planned implementation units for this issue are now complete.
Verified facts:
- `tests/phase_z2/test_phase_z2_visual_classifier.py` is a new pure-dict test file and does not use Selenium, chromedriver, browser fixtures, or pipeline execution.
- The test file imports `IMAGE_ASPECT_DELTA_TOL` and `TABLE_SCROLL_TOL_PX` from `src.phase_z2_pipeline`, so threshold values are not duplicated as hardcoded literals.
- `test_image_aspect_mismatch_emits_classification` covers `delta > IMAGE_ASPECT_DELTA_TOL`, asserts `image_aspect_mismatch`, and verifies `visual_check_passed is False`.
- `test_image_aspect_delta_below_tol_no_classification` covers below-tolerance image delta and asserts no classifications with `visual_check_passed is True`.
- `test_standalone_table_overflow_emits_classification` covers standalone table self-overflow with `wrapper_clipped_index=None`, asserts `tabular_overflow`, and verifies `visual_check_passed is False`.
- `test_table_dedup_when_wrapper_clipped` covers non-null `wrapper_clipped_index` with excess beyond tolerance and asserts no `tabular_overflow`, preserving the dedup contract.
- The classifier implementation remains aligned with the tests: image/table event scans are unconditional, `image_aspect_mismatch` and `tabular_overflow` are emitted under the required thresholds, and `visual_check_passed` is `overflow.passed AND not classifications`.
=== EVIDENCE ===
Commands run:
- `git diff -- tests/phase_z2/test_phase_z2_visual_classifier.py src/phase_z2_classifier.py`
- `Get-Content -LiteralPath 'tests\phase_z2\test_phase_z2_visual_classifier.py'`
- `rg -n "def classify_visual_runtime_check|IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_events|table_events|visual_check_passed|image_aspect_mismatch|tabular_overflow" src\phase_z2_classifier.py src\phase_z2_pipeline.py tests\phase_z2\test_phase_z2_visual_classifier.py`
- `$env:PYTHONPATH='src'; pytest -q tests\phase_z2\test_phase_z2_visual_classifier.py`
Files checked:
- `D:\ad-hoc\kei\design_agent\tests\phase_z2\test_phase_z2_visual_classifier.py`
- `D:\ad-hoc\kei\design_agent\src\phase_z2_classifier.py`
- `D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.py`
Test results:
- `pytest -q tests\phase_z2\test_phase_z2_visual_classifier.py`: 4 passed in 0.04s.
- Full pytest was not run because the current Stage 3 verifier instruction says not to run full pytest unless explicitly required.
Remaining units: none
FINAL_CONSENSUS: YES
Working-tree changes confined to src/phase_z2_classifier.py (modified) and tests/phase_z2/test_phase_z2_visual_classifier.py (new).
Out-of-scope working-tree noise (orchestrator.py, tests/orchestrator_unit/test_subprocess_cleanup.py) NOT touched in this verification — separate axis per Stage 3 exit report.
✅ Early-return on overflow.passed=True REMOVED at former L367-375 (verified by git diff).
✅ image_events scan: delta is None ⇒ continue; abs(delta) > IMAGE_ASPECT_DELTA_TOL ⇒ emit image_aspect_mismatch with full payload (zone_position, zone_template_id, src, natural_ratio, rendered_ratio, delta, rule_applied).
✅ table_events scan: wrapper_clipped_index is not None ⇒ continue; excess_x > TOL or excess_y > TOL ⇒ emit tabular_overflow (priority-2 category reused verbatim).
✅visual_check_passed = bool(overflow.get("passed", False)) and not classifications — widened semantic.
$ grep -n "0\.05\|IMAGE_ASPECT_DELTA_TOL\|TABLE_SCROLL_TOL_PX" src/phase_z2_classifier.py
360: from phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOL, TABLE_SCROLL_TOL_PX
433: if abs(delta) > IMAGE_ASPECT_DELTA_TOL:
458: if excess_x > TABLE_SCROLL_TOL_PX or excess_y > TABLE_SCROLL_TOL_PX:
...
No literal 0.05 / 5 in classifier — all tolerance comparisons go through imported constants. SSoT remains src/phase_z2_pipeline.py:134, 139.
■ Pure-dict audit (RULE 4 PZ-1)
New test file: zero selenium / webdriver / chromedriver imports (only the docstring mentions "no Selenium / chromedriver dependency"). Test fixtures are plain dicts assembled in-line via _base_overflow(**overrides).
✅classifications includes image_aspect_mismatch / tabular_overflow when triggered.
✅visual_check_passed = overflow.passed AND (not classifications) (intentional flip — verified by test_image_aspect_mismatch_emits_classification with overflow["passed"]=True).
✅IMAGE_ASPECT_DELTA_TOL imported from phase_z2_pipeline (single source of truth).
✅ 4 unit tests, all pure-dict (no Selenium dependency).
■ Decision
All Stage 2 IMPLEMENTATION_UNITS (u1, u2) verified green. All Stage 3 guardrails honored. All issue acceptance criteria met. Proceed to Stage 5 (commit-push) with files limited to src/phase_z2_classifier.py + tests/phase_z2/test_phase_z2_visual_classifier.py.
Issue #47 axis 3 only: classify_visual_runtime_check consumes image_events[] and table_events[], and visual_check_passed is widened to overflow.passed AND no classifications.
Scoped implementation files checked: src/phase_z2_classifier.py and tests/phase_z2/test_phase_z2_visual_classifier.py.
Dirty working-tree entries outside the scoped files were observed and left untouched: orchestrator.py, tests/orchestrator_unit/test_subprocess_cleanup.py, and unrelated untracked local artifacts.
Diff verification:
src/phase_z2_classifier.py removes the former overflow.get("passed", False) early return, so image/table event scans run even when zone overflow is clean.
The classifier imports IMAGE_ASPECT_DELTA_TOL and TABLE_SCROLL_TOL_PX from phase_z2_pipeline at call time; no classifier-local tolerance literals were found for the comparisons.
image_events[] emits image_aspect_mismatch only when delta is not None and abs(delta) > IMAGE_ASPECT_DELTA_TOL.
table_events[] emits tabular_overflow only when wrapper_clipped_index is None and either excess exceeds TABLE_SCROLL_TOL_PX.
visual_check_passed is computed as bool(overflow.get("passed", False)) and not classifications.
Existing zone cascade structures remain present; placement_diagnostics, categories_seen, and unclassified_signals return fields are preserved.
Test verification:
New pure-dict classifier tests pass: 4/4.
Existing tests/phase_z2 regression suite excluding the new file passes: 93/93.
No Selenium, chromedriver, browser fixture, pipeline execution, router action, debug.json passthrough, or pipeline.py production edit was required for this verification.
src/phase_z2_pipeline.py defines IMAGE_ASPECT_DELTA_TOL = 0.05 and TABLE_SCROLL_TOL_PX = 5.
src/phase_z2_classifier.py uses the imported tolerance names for image/table classification comparisons.
tests/phase_z2/test_phase_z2_visual_classifier.py contains the four required pure-dict tests: image mismatch emit, image below-tolerance skip, standalone table overflow emit, and wrapper-clipped table dedup.
git diff --check reported no whitespace errors for the scoped files.
FINAL_CONSENSUS: YES
[Codex #1] Stage 4: test-verify Round #1 - PASS
Verification scope:
- Issue #47 axis 3 only: `classify_visual_runtime_check` consumes `image_events[]` and `table_events[]`, and `visual_check_passed` is widened to `overflow.passed AND no classifications`.
- Scoped implementation files checked: `src/phase_z2_classifier.py` and `tests/phase_z2/test_phase_z2_visual_classifier.py`.
- Dirty working-tree entries outside the scoped files were observed and left untouched: `orchestrator.py`, `tests/orchestrator_unit/test_subprocess_cleanup.py`, and unrelated untracked local artifacts.
Diff verification:
- `src/phase_z2_classifier.py` removes the former `overflow.get("passed", False)` early return, so image/table event scans run even when zone overflow is clean.
- The classifier imports `IMAGE_ASPECT_DELTA_TOL` and `TABLE_SCROLL_TOL_PX` from `phase_z2_pipeline` at call time; no classifier-local tolerance literals were found for the comparisons.
- `image_events[]` emits `image_aspect_mismatch` only when `delta is not None` and `abs(delta) > IMAGE_ASPECT_DELTA_TOL`.
- `table_events[]` emits `tabular_overflow` only when `wrapper_clipped_index is None` and either excess exceeds `TABLE_SCROLL_TOL_PX`.
- `visual_check_passed` is computed as `bool(overflow.get("passed", False)) and not classifications`.
- Existing zone cascade structures remain present; `placement_diagnostics`, `categories_seen`, and `unclassified_signals` return fields are preserved.
Test verification:
- New pure-dict classifier tests pass: 4/4.
- Existing `tests/phase_z2` regression suite excluding the new file passes: 93/93.
- No Selenium, chromedriver, browser fixture, pipeline execution, router action, debug.json passthrough, or `pipeline.py` production edit was required for this verification.
=== EVIDENCE ===
Commands run:
- `git status --short`
- `git diff -- src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.py`
- `rg -n 'IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_aspect_mismatch|tabular_overflow|visual_check_passed|wrapper_clipped_index|overflow\.get' src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.py src/phase_z2_pipeline.py`
- `git diff --check -- src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.py`
- `python -m pytest tests/phase_z2/test_phase_z2_visual_classifier.py -q`
- `python -m pytest tests/phase_z2/ -q --ignore=tests/phase_z2/test_phase_z2_visual_classifier.py`
Files checked:
- `src/phase_z2_classifier.py`
- `tests/phase_z2/test_phase_z2_visual_classifier.py`
- `src/phase_z2_pipeline.py`
Test results:
- `4 passed in 0.05s`
- `93 passed in 25.80s`
Verified facts:
- `src/phase_z2_pipeline.py` defines `IMAGE_ASPECT_DELTA_TOL = 0.05` and `TABLE_SCROLL_TOL_PX = 5`.
- `src/phase_z2_classifier.py` uses the imported tolerance names for image/table classification comparisons.
- `tests/phase_z2/test_phase_z2_visual_classifier.py` contains the four required pure-dict tests: image mismatch emit, image below-tolerance skip, standalone table overflow emit, and wrapper-clipped table dedup.
- `git diff --check` reported no whitespace errors for the scoped files.
FINAL_CONSENSUS: YES
IMP-15 실행-3 (axis 3: classifier consumer + visual_check_passed widened) 단일 commit 535c484 로 생성 후 slide2 (Gitea, issue tracker) + origin (GitHub) 양 remote 에 push 성공.
스테이지된 파일은 Stage 4 exit report 와 정확히 동일: src/phase_z2_classifier.py (+61/-11), tests/phase_z2/test_phase_z2_visual_classifier.py (+116 new). git add 는 두 파일에 한정 — 작업 트리에 잔류한 orchestrator.py / tests/orchestrator_unit/test_subprocess_cleanup.py (별 axis: P3 subprocess fix), .orchestrator/, chromedriver/, tests/matching/, samples/uploads/, templates/catalog/ 등 untracked artefact 는 본 commit 에 미포함.
Commit message 는 conventional feat(IMP-15): 실행-3 — classifier consumes image+table events 접두 + 변경 요약 (early-return 제거 / 두 emitter / visual_check_passed widen) + guardrail (8-rule cascade 보존 / placement_diagnostics shape / pipeline.py 미변경) + 테스트 4 케이스 + Stage 4 결과 (4/4 신규, 93/93 regression) 포함.
양 remote main 의 tip = 535c4848fd60a1e5a57757048b6ffaad4495a3b6 (git ls-remote slide2 / origin refs/heads/main 직접 확인).
Scope 준수
git add 는 명시된 두 파일만. 작업 트리에 잔류한 orchestrator.py / tests/orchestrator_unit/test_subprocess_cleanup.py 의 P3 subprocess fix 변경 + 다수 untracked noise (sample / matching / scratch) 는 본 commit 에 미포함.
pipeline.py 변경 X (producer 는 실행-1/2 에서 이미 landed), slide_base.html / catalog / frame DB / debug.json / router action / spec doc 모두 미변경.
git diff --staged -- src/phase_z2_classifier.py | head -120 — early-return 제거 + 두 신규 scan + visual_check_passed widening 확인.
git commit -m … — HEREDOC 으로 본문 보존, --amend / --no-verify 사용 없음. pre-commit hook 통과.
git push origin main — fast-forward 2827622..535c484.
git push slide2 main — fast-forward 2827622..535c484 (Processed 1 references).
git ls-remote origin / slide2 refs/heads/main — 양측 모두 535c4848… 로 local HEAD 일치.
잔류 unit (out-of-scope, 별도 issue)
실행-4 — debug.json 에 image_events / table_events raw surfacing + PHASE-Z-PIPELINE-OVERVIEW.md / PHASE-Z-PIPELINE-STATUS-BOARD.md / PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md row update.
Router action mapping — image_aspect_mismatch classification → frame swap / sizing adapter 매핑 (FUTURE follow-up issue, 본 issue body 의 out-of-scope 라인 명시).
P3 (orchestrator subprocess) — orchestrator.py + tests/orchestrator_unit/test_subprocess_cleanup.py text/bytes mode bug. 별 axis (2026-05-18), 본 issue 무관. 별도 commit/issue 로 분기 필요.
Guardrails (post-commit)
IMAGE_ASPECT_DELTA_TOL / TABLE_SCROLL_TOL_PX 는 phase_z2_pipeline 에서 deferred import — classifier-local literal 없음, SSoT 유지. 실행-4 / router 도 동일 상수를 import 해야 함.
§3.2 8-rule zone cascade (clipped_inner / zone-self) 미변경 — 신규 emitter 는 ADDITIVE.
placement_diagnostics, categories_seen, unclassified_signals return-shape 보존 — downstream consumer 의 debug.json 의존성 호환.
wrapper_clipped_index is not None ⇒ skip tabular_overflow dedup 계약은 pipeline producer (실행-2) 와 consumer 양측에서 잠금.
=== EVIDENCE ===
commit_sha:535c4848fd60a1e5a57757048b6ffaad4495a3b6parent_sha:2827622# feat(IMP-16): Step 14 table_self_overflow detectioncommit_message_first_line:"feat(IMP-15): 실행-3 — classifier consumes image+table events"staged_files:- path:src/phase_z2_classifier.pyinsertions:61deletions:11- path:tests/phase_z2/test_phase_z2_visual_classifier.pyinsertions:116deletions:0status:newcommands_run:- "git status --short"- "git diff --stat -- src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.py"- "git add src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.py"- "git diff --staged --stat"# 61/11 + 116/0 → matches Stage 4 report- "git diff --staged -- src/phase_z2_classifier.py | head -120"- "git log --oneline -5"- "git remote -v"- "git branch -vv"# main [origin/main]- "git commit -m <HEREDOC, no --amend, no --no-verify>"- "git log --oneline -2"# 535c484 on top of 2827622- "git push origin main"# 2827622..535c484 main -> main- "git push slide2 main"# 2827622..535c484 main -> main- "git ls-remote origin main"# 535c4848… (matches local)- "git ls-remote slide2 main"# 535c4848… (matches local)remotes_pushed:- name:originurl:git@github.com:keimin86/design_agent.gitresult:"fast-forward 2827622..535c484"remote_head:535c4848fd60a1e5a57757048b6ffaad4495a3b6- name:slide2url:https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2.gitresult:"fast-forward 2827622..535c484 (Processed 1 references)"remote_head:535c4848fd60a1e5a57757048b6ffaad4495a3b6# 'gitea' remote (Kyeongmin/_Geulbeot.git) is unrelated project — NOT pushed.scope_audit_post_commit:out_of_scope_paths_touched:0pipeline_py_changes:0# producer untouched (실행-1/2 already landed)template_changes:0catalog_changes:0debug_json_changes:0# 실행-4 잔류spec_doc_changes:0# 실행-4 잔류router_action_changes:0# FUTURE follow-upcascade_8_rule_changes:0# §3.2 preserved (ADDITIVE emitters only)placement_diagnostics_shape_changes:0orchestrator_subprocess_changes:0# P3 — separate axis, untouchedguardrail_compliance:ai_isolation:ok # classifier remains pure-dict, no LLM callno_hardcoding:ok # tolerances deferred-imported from pipeline (SSoT)scope_lock:ok # only 2 files in commitauto_pipeline_first:ok # no review_required / review_queueone_step_per_turn:ok # Stage 5 only; no preview of 실행-4 / routerphase_z_spacing_direction:n/a # measurement / classification axisfactual_verification:ok # SHA + path + upstream verified per remotepipeline_construction:ok # no MDX 03/04/05 hardcoding, no sample-specific branchverification_recap:new_classifier_tests:"4/4 PASS (Stage 4)"regression_phase_z2:"93/93 PASS excluding new file (Stage 4)"selenium_required:falsechromedriver_required:falseunresolved_questions:[]reviewers_required_next:- "Codex Stage 5 verifier — confirm SHA 535c484 + both remote heads match + scope-locked add list (2 files only)"
The dirty working tree still contains unrelated modified files and many untracked local artifacts, including orchestrator.py, tests/orchestrator_unit/test_subprocess_cleanup.py, .orchestrator/, chromedriver/, samples/, templates/, and tests/matching/.
These are outside Issue #47 scope and were not part of commit 535c4848fd60a1e5a57757048b6ffaad4495a3b6.
=== EVIDENCE ===
Commands run:
git status --short
git rev-parse HEAD
git show --stat --name-only --format=fuller --no-renames HEAD
git remote -v
git ls-remote origin refs/heads/main
git ls-remote slide2 refs/heads/main
git diff-tree --no-commit-id --name-status -r HEAD
git show --stat --oneline --no-renames HEAD
Files checked:
src/phase_z2_classifier.py
tests/phase_z2/test_phase_z2_visual_classifier.py
Commit SHA:
535c4848fd60a1e5a57757048b6ffaad4495a3b6
Verified facts:
git rev-parse HEAD returned 535c4848fd60a1e5a57757048b6ffaad4495a3b6.
git diff-tree --no-commit-id --name-status -r HEAD returned exactly:
M src/phase_z2_classifier.py
A tests/phase_z2/test_phase_z2_visual_classifier.py
git show --stat --oneline --no-renames HEAD reported 2 files changed, 177 insertions(+), 11 deletions(-).
git ls-remote origin refs/heads/main returned 535c4848fd60a1e5a57757048b6ffaad4495a3b6.
git ls-remote slide2 refs/heads/main returned 535c4848fd60a1e5a57757048b6ffaad4495a3b6.
Stage 5 did not require rerunning pytest; Stage 4 already reported 4/4 new classifier tests and 93/93 phase_z2 regression tests passing.
FINAL_CONSENSUS: YES
[Codex #1] Stage 5: commit-push verification - COMPLETE
I verified the Stage 5 commit and remote state for Issue #47.
Decision:
- PASS. Local HEAD is `535c4848fd60a1e5a57757048b6ffaad4495a3b6`.
- The commit scope is limited to the two approved files:
- `src/phase_z2_classifier.py`
- `tests/phase_z2/test_phase_z2_visual_classifier.py`
- No out-of-scope modified or untracked working-tree files were included in the commit.
- Both remote `main` refs now point to the same SHA:
- `origin/main` = `535c4848fd60a1e5a57757048b6ffaad4495a3b6`
- `slide2/main` = `535c4848fd60a1e5a57757048b6ffaad4495a3b6`
Working-tree note:
- The dirty working tree still contains unrelated modified files and many untracked local artifacts, including `orchestrator.py`, `tests/orchestrator_unit/test_subprocess_cleanup.py`, `.orchestrator/`, `chromedriver/`, `samples/`, `templates/`, and `tests/matching/`.
- These are outside Issue #47 scope and were not part of commit `535c4848fd60a1e5a57757048b6ffaad4495a3b6`.
=== EVIDENCE ===
Commands run:
- `git status --short`
- `git rev-parse HEAD`
- `git show --stat --name-only --format=fuller --no-renames HEAD`
- `git remote -v`
- `git ls-remote origin refs/heads/main`
- `git ls-remote slide2 refs/heads/main`
- `git diff-tree --no-commit-id --name-status -r HEAD`
- `git show --stat --oneline --no-renames HEAD`
Files checked:
- `src/phase_z2_classifier.py`
- `tests/phase_z2/test_phase_z2_visual_classifier.py`
Commit SHA:
- `535c4848fd60a1e5a57757048b6ffaad4495a3b6`
Verified facts:
- `git rev-parse HEAD` returned `535c4848fd60a1e5a57757048b6ffaad4495a3b6`.
- `git diff-tree --no-commit-id --name-status -r HEAD` returned exactly:
- `M src/phase_z2_classifier.py`
- `A tests/phase_z2/test_phase_z2_visual_classifier.py`
- `git show --stat --oneline --no-renames HEAD` reported `2 files changed, 177 insertions(+), 11 deletions(-)`.
- `git ls-remote origin refs/heads/main` returned `535c4848fd60a1e5a57757048b6ffaad4495a3b6`.
- `git ls-remote slide2 refs/heads/main` returned `535c4848fd60a1e5a57757048b6ffaad4495a3b6`.
- Stage 5 did not require rerunning pytest; Stage 4 already reported 4/4 new classifier tests and 93/93 phase_z2 regression tests passing.
FINAL_CONSENSUS: YES
Goal: classifier consumes the new image_events[] and table_events[] arrays (produced by 실행-1/2) and widens visual_check_passed semantics so image/table mismatches surface even when zone overflow is clean.
Result: Achieved. image_aspect_mismatch + tabular_overflow are ADDITIVE emitters layered on top of the existing §3.2 8-rule cascade. visual_check_passed recomputed at L495 = overflow.passed AND not classifications. Tolerances imported from pipeline (SSoT) — no classifier-local literals.
debug.json raw passthrough for image_events[] / table_events[]
queued
FUTURE
router action mapping for image_aspect_mismatch (separate axis)
queued (issue body §Out-of-scope)
Labels
No label changes proposed in this issue. Parent IMP-15 owns the rollup label state when remaining child execution units (실행-4 + router-action follow-up) close.
Close decision
CLOSE issue #47. All 5 acceptance criteria satisfied, commit on both remotes (origin + slide2) at identical SHA 535c484, scope-lock honored (2 files only), 4 pure-dict tests PASS + 93 regression PASS, deferred axes (debug.json passthrough, router action) explicitly named with follow-up paths. No unresolved questions block close.
=== EVIDENCE ===
Commands:
git rev-parse HEAD → 535c4848fd60a1e5a57757048b6ffaad4495a3b6
Final independent verification result: PASS. I verified the committed implementation against issue #47 scope and acceptance criteria, using the completed stage reports as binding contracts and checking the current repository state directly.
Scope-lock confirmed:
Commit scope is exactly src/phase_z2_classifier.py and tests/phase_z2/test_phase_z2_visual_classifier.py.
pipeline.py, router action selection, debug.json passthrough, and unrelated dirty worktree files are not part of this commit.
Acceptance criteria verified:
classify_visual_runtime_check consumes overflow["image_events"] and emits image_aspect_mismatch when delta is not None and abs(delta) > IMAGE_ASPECT_DELTA_TOL.
classify_visual_runtime_check consumes overflow["table_events"] and emits tabular_overflow when wrapper_clipped_index is None and table excess exceeds TABLE_SCROLL_TOL_PX.
Table dedupe is honored: non-null wrapper_clipped_index skips the table-event emitter.
visual_check_passed is computed as bool(overflow.get("passed", False)) and not classifications.
Tolerances are imported from phase_z2_pipeline: IMAGE_ASPECT_DELTA_TOL = 0.05, TABLE_SCROLL_TOL_PX = 5.
The new test file contains exactly the four required pure-dict test cases and has no Selenium/chromedriver dependency.
=== EVIDENCE ===
Commands run:
git rev-parse HEAD
git diff-tree --no-commit-id --name-status -r HEAD
Local HEAD is 535c4848fd60a1e5a57757048b6ffaad4495a3b6.
origin/main is 535c4848fd60a1e5a57757048b6ffaad4495a3b6.
slide2/main is 535c4848fd60a1e5a57757048b6ffaad4495a3b6.
Commit file scope is exactly M src/phase_z2_classifier.py and A tests/phase_z2/test_phase_z2_visual_classifier.py.
Commit stat is 2 files changed, 177 insertions(+), 11 deletions(-).
src/phase_z2_classifier.py imports IMAGE_ASPECT_DELTA_TOL and TABLE_SCROLL_TOL_PX from phase_z2_pipeline inside classify_visual_runtime_check.
src/phase_z2_classifier.py contains the image_events scan and the image_aspect_mismatch emitter.
src/phase_z2_classifier.py contains the table_events scan, the wrapper_clipped_index is not None dedupe skip, and the tabular_overflow emitter.
src/phase_z2_classifier.py computes visual_check_passed = bool(overflow.get("passed", False)) and not classifications.
src/phase_z2_pipeline.py defines IMAGE_ASPECT_DELTA_TOL = 0.05 and TABLE_SCROLL_TOL_PX = 5.
The targeted pure-dict classifier tests pass: 4 passed in 0.05s.
Existing unrelated dirty worktree entries remain outside this commit scope.
FINAL_CONSENSUS: YES
[Codex #1] Stage 6 final-close verification
Final independent verification result: PASS. I verified the committed implementation against issue #47 scope and acceptance criteria, using the completed stage reports as binding contracts and checking the current repository state directly.
Scope-lock confirmed:
- Commit scope is exactly `src/phase_z2_classifier.py` and `tests/phase_z2/test_phase_z2_visual_classifier.py`.
- `pipeline.py`, router action selection, debug.json passthrough, and unrelated dirty worktree files are not part of this commit.
Acceptance criteria verified:
- `classify_visual_runtime_check` consumes `overflow["image_events"]` and emits `image_aspect_mismatch` when `delta is not None` and `abs(delta) > IMAGE_ASPECT_DELTA_TOL`.
- `classify_visual_runtime_check` consumes `overflow["table_events"]` and emits `tabular_overflow` when `wrapper_clipped_index is None` and table excess exceeds `TABLE_SCROLL_TOL_PX`.
- Table dedupe is honored: non-null `wrapper_clipped_index` skips the table-event emitter.
- `visual_check_passed` is computed as `bool(overflow.get("passed", False)) and not classifications`.
- Tolerances are imported from `phase_z2_pipeline`: `IMAGE_ASPECT_DELTA_TOL = 0.05`, `TABLE_SCROLL_TOL_PX = 5`.
- The new test file contains exactly the four required pure-dict test cases and has no Selenium/chromedriver dependency.
=== EVIDENCE ===
Commands run:
- `git rev-parse HEAD`
- `git diff-tree --no-commit-id --name-status -r HEAD`
- `git show --stat --oneline --no-renames HEAD`
- `git ls-remote origin refs/heads/main; git ls-remote slide2 refs/heads/main`
- `rg -n "IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_aspect_mismatch|tabular_overflow|visual_check_passed|image_events|table_events|wrapper_clipped_index" src/phase_z2_classifier.py`
- `rg -n "def test_|selenium|chromedriver|IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_aspect_mismatch|tabular_overflow|wrapper_clipped_index|visual_check_passed" tests/phase_z2/test_phase_z2_visual_classifier.py`
- `rg -n "IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX" src/phase_z2_pipeline.py`
- `pytest -q tests/phase_z2/test_phase_z2_visual_classifier.py`
- `git status --short`
Files checked:
- `src/phase_z2_classifier.py`
- `tests/phase_z2/test_phase_z2_visual_classifier.py`
- `src/phase_z2_pipeline.py`
Verified facts:
- Local HEAD is `535c4848fd60a1e5a57757048b6ffaad4495a3b6`.
- `origin/main` is `535c4848fd60a1e5a57757048b6ffaad4495a3b6`.
- `slide2/main` is `535c4848fd60a1e5a57757048b6ffaad4495a3b6`.
- Commit file scope is exactly `M src/phase_z2_classifier.py` and `A tests/phase_z2/test_phase_z2_visual_classifier.py`.
- Commit stat is `2 files changed, 177 insertions(+), 11 deletions(-)`.
- `src/phase_z2_classifier.py` imports `IMAGE_ASPECT_DELTA_TOL` and `TABLE_SCROLL_TOL_PX` from `phase_z2_pipeline` inside `classify_visual_runtime_check`.
- `src/phase_z2_classifier.py` contains the `image_events` scan and the `image_aspect_mismatch` emitter.
- `src/phase_z2_classifier.py` contains the `table_events` scan, the `wrapper_clipped_index is not None` dedupe skip, and the `tabular_overflow` emitter.
- `src/phase_z2_classifier.py` computes `visual_check_passed = bool(overflow.get("passed", False)) and not classifications`.
- `src/phase_z2_pipeline.py` defines `IMAGE_ASPECT_DELTA_TOL = 0.05` and `TABLE_SCROLL_TOL_PX = 5`.
- The targeted pure-dict classifier tests pass: `4 passed in 0.05s`.
- Existing unrelated dirty worktree entries remain outside this commit scope.
FINAL_CONSENSUS: YES
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Parent: #15 (IMP-15 Step 14 visual_check 보강)
Source plan: Claude #6 plan u2 (classifier extension) + u5 (pure-dict classifier tests)
Depends on: 실행-1 + 실행-2 (image_events + table_events arrays must exist in run_overflow_check result)
Scope (axis 3: classifier consumer + visual_check_passed)
Extend
classify_visual_runtime_checkto consume image_events + table_eventsand flip
visual_check_passedwhen any classification fires (even if zone-level overflow was clean).Touched files
src/phase_z2_classifier.py— consumer + visual_check_passed semantic widenedtests/phase_z2/test_phase_z2_visual_classifier.py— NEW (pure-dict, no Selenium)Tests required
test_image_aspect_mismatch_emits_classification: delta > 0.05 → classification + visual_check_passed=Falsetest_image_aspect_delta_below_tol_no_classification: delta < 0.05 → no classificationtest_standalone_table_overflow_emits_classification: table self-overflow → "tabular_overflow"test_table_dedup_when_wrapper_clipped: wrapper_clipped_index set → no "tabular_overflow" (dedup honored)Acceptance criteria
classificationslist includesimage_aspect_mismatch/tabular_overflowwhen triggeredvisual_check_passed = overflow.passed AND (not classifications)(intentional flip)IMAGE_ASPECT_DELTA_TOLimported fromphase_z2_pipeline(single source of truth)Out-of-scope
Estimated size
검증 요구사항 (strict):
이 이슈의 구현 변경은 같은 이슈 안에서 검증되어야 합니다.
tests: []단위는 허용되지 않습니다 (구현 + 테스트 = 같은 이슈).[Claude #1] Stage 1 — IMP-15 실행-3 classifier consumer (axis 3).
Scope-lock
Single axis:
classify_visual_runtime_checkconsumesimage_events[]+table_events[];visual_check_passedflips when ANY classification fires (even on clean zone overflow). pipeline.py 변경 X. router action / debug.json passthrough = out-of-scope.Verified preconditions (실행-1/2 landed)
IMAGE_ASPECT_DELTA_TOL=0.05phase_z2_pipeline.py:134— classifier imports (SSoT).TABLE_SCROLL_TOL_PX=5phase_z2_pipeline.py:139— same SSoT rule (issue body names only image TOL; same principle for table).image_events[]phase_z2_pipeline.py:2239-2280.delta is None⇒ skip (image not loaded, no false positive).table_events[]phase_z2_pipeline.py:2287-2325.wrapper_clipped_index is not None⇒ skip (clipped_inner dedup).Current classifier (pre-change)
classify_visual_runtime_checkphase_z2_classifier.py:336setsvisual_check_passed=overflow.passedand EARLY-RETURNS at L367-375 on passed=True. Reads onlyoverflow.zones[].clipped_inner+ zone-self. Never touches new event arrays. Early-return must be removed so image/table scans still run when zone overflow is clean.tabular_overflowcategory exists L234 (§3.2 priority 2) — table consumer reuses verbatim.image_aspect_mismatchis NEW (not in §3.2 cascade). Aspect-ratio violation ⇒ emit as independent classification entry.Acceptance criteria (verbatim)
image_aspect_mismatchwhendelta is not None AND |delta|>IMAGE_ASPECT_DELTA_TOL.tabular_overflowwhenwrapper_clipped_index is None AND (excess_x>TOL OR excess_y>TOL).visual_check_passed = overflow.passed AND (not classifications).Guardrails
placement_diagnosticsshape preserved.tests/phase_z2/test_phase_z2_visual_classifier.py(no collision, confirmed).=== EVIDENCE ===
phase_z2_classifier.pyfull (1-457) — early-return L367-375, 8-rule cascade L226-285.phase_z2_pipeline.py:120-149,2230-2410— TOL constants + event arrays + dedup pattern.classify_visual_runtime_checkin src — 3 callers in pipeline only (L4521/4579/4640) ⇒ semantic widening safe.tests/phase_z2/**/*.py— target test file does not exist.FINAL_CONSENSUS: YES
[Codex #1] Stage 1 problem-review for issue #47 (IMP-15 exec-3 classifier consumer).
Scope-lock
Confirmed. This issue is limited to
classify_visual_runtime_checkconsuming already-producedimage_events[]andtable_events[], plus wideningvisual_check_passedto fail when any classifier event is emitted.pipeline.pypassthrough/production of those arrays is already present and is out of scope for this issue. Router behavior and debug.json passthrough remain out of scope.Verified current state
src/phase_z2_pipeline.pydefinesIMAGE_ASPECT_DELTA_TOL = 0.05andTABLE_SCROLL_TOL_PX = 5; classifier should import from there for a single source of truth.run_overflow_checkreturnsimage_eventsandtable_eventsin its result object.phase_z2_pipeline.pyalready uses image delta and table overflow for fail reasons, but the classifier does not consume those arrays yet.src/phase_z2_classifier.pycurrently returns immediately whenoverflow.get("passed", False)is true, so clean zone overflow prevents image/table classification from firing.overflow.zones[].overflowedandoverflow.zones[].clipped_inner; no reads ofimage_eventsortable_eventsexist in the classifier.tabular_overflowalready exists in the classifier cascade and should be reused for table self-overflow.image_aspect_mismatchis not present in the classifier yet and must be added as a new classification category.tests/phase_z2/test_phase_z2_visual_classifier.pydoes not exist, so the requested pure-dict unit test file is missing.Stage 1 conclusion
The issue body and prior scope report are consistent with the repository state. Required implementation scope is correctly locked to
src/phase_z2_classifier.pyplus new pure-dict tests undertests/phase_z2/test_phase_z2_visual_classifier.py. No pytest was run, per Stage 1 instructions.=== EVIDENCE ===
Commands run:
rg -n "IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_events|table_events|classify_visual_runtime_check|tabular_overflow|visual_check_passed" src testsGet-ChildItem -LiteralPath 'D:\ad-hoc\kei\design_agent\tests\phase_z2' -ForceGet-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\src\phase_z2_classifier.py' | Select-Object -Skip 210 -First 260Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.py' | Select-Object -Skip 120 -First 30Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.py' | Select-Object -Skip 2230 -First 110Files checked:
D:\ad-hoc\kei\design_agent\src\phase_z2_classifier.pyD:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.pyD:\ad-hoc\kei\design_agent\tests\phase_z2Test results: not run; Stage 1 explicitly says NO pytest.
Verified facts:
IMAGE_ASPECT_DELTA_TOL = 0.05atsrc\phase_z2_pipeline.py:134.TABLE_SCROLL_TOL_PX = 5atsrc\phase_z2_pipeline.py:139.image_eventsandtable_eventsare included in therun_overflow_checkreturn object atsrc\phase_z2_pipeline.py:2327.classify_visual_runtime_checkstarts atsrc\phase_z2_classifier.py:336and currently early-returns on passed overflow atsrc\phase_z2_classifier.py:367-375.tabular_overflowcategory is emitted byclassify_overflowatsrc\phase_z2_classifier.py:233-239.tests\phase_z2\test_phase_z2_visual_classifier.pyis absent from the directory listing.FINAL_CONSENSUS: YES
📌 [Claude #1] Stage 2: 시뮬 기반 계획 수립 Round #1
Execution-3 child issue. Scope locked by Stage 1 exit report (issue body + parent #15). Single axis: classifier consumer extension +
visual_check_passedsemantic widening. Plan = 2 atomic units.=== IMPLEMENTATION_UNITS ===
summary: Extend classify_visual_runtime_check to consume image_events + table_events; widen visual_check_passed; remove overflow.passed early-return
files: [src/phase_z2_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
summary: NEW pure-dict test file — 4 cases (image delta>tol fires; image delta<tol no-fire; table self-overflow fires; table dedup when wrapper_clipped_index set)
files: [tests/phase_z2/test_phase_z2_visual_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
=== PER-UNIT RATIONALE ===
u1 —
src/phase_z2_classifier.pyIMAGE_ASPECT_DELTA_TOL,TABLE_SCROLL_TOL_PXfromphase_z2_pipeline(SSoT — no literal duplication; per Stage 1 guardrail).placement_diagnosticsbuild stays at L358-365 (passed-invariant, preserved).overflow.get("image_events", []). Skip whenev["delta"] is None(image not loaded — Stage 1 guardrail). Emitimage_aspect_mismatchclassification whenabs(delta) > IMAGE_ASPECT_DELTA_TOL. Setsource="image_event",zone_position,zone_template_id,delta,natural_ratio,rendered_ratio,src.overflow.get("table_events", []). Skip whenev["wrapper_clipped_index"] is not None(dedup with existing clipped_inner — Stage 1 guardrail). Emittabular_overflow(reuse §3.2 priority-2 category verbatim, L234) whenexcess_x > TABLE_SCROLL_TOL_PX or excess_y > TABLE_SCROLL_TOL_PX. Setsource="table_event",zone_position,zone_template_id,excess_x,excess_y.visual_check_passed = overflow.get("passed", False) and not classifications. Existing branch at L367-375 now also runs scans; ifclassificationsnon-empty whenoverflow.passed=True, return shape mirrors the failed branch (categories_seen / summary populated, unclassified_signals=[]).placement_diagnosticsin both branches. Preserve existing 8-rule cascade output for zone_self / clipped_inner sources (untouched).u2 —
tests/phase_z2/test_phase_z2_visual_classifier.py(NEW)overflowdict withpassed=True+zones=[]+image_events=[…]/table_events=[…]arrays. Pass emptydebug_zones=[].test_image_aspect_mismatch_emits_classification— delta=0.30 → 1 classification, category=image_aspect_mismatch, source=image_event,visual_check_passed=False.test_image_aspect_delta_below_tol_no_classification— delta=0.02 → 0 classifications,visual_check_passed=True.test_standalone_table_overflow_emits_classification— excess_x=20, wrapper_clipped_index=None → 1 classification, category=tabular_overflow, source=table_event,visual_check_passed=False.test_table_dedup_when_wrapper_clipped— excess_x=20, wrapper_clipped_index=3 → 0 classifications (dedup honored),visual_check_passed=True.=== OUT-OF-SCOPE (Stage 1 lock, restated) ===
pipeline.pyproduction of image_events / table_events (already landed 실행-1/2).image_aspect_mismatch(FUTURE follow-up issue).debug.jsonpassthrough (→ 실행-4).placement_diagnosticsshape (untouched).=== ROLLBACK ===
Single commit per unit.
git revert <sha>restores prior classifier semantics; NEW test file removal is no-op for production.=== EVIDENCE ===
FINAL_CONSENSUS: YES
[Codex #1] Stage 2: simulation-plan review Round #1
=== IMPLEMENTATION_UNITS ===
summary: Extend classify_visual_runtime_check to consume image_events and table_events, remove passed early-return, and widen visual_check_passed semantics
files: [src/phase_z2_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
summary: Add pure-dict classifier tests for image aspect mismatch, below-tolerance image delta, standalone table overflow, and wrapper-clipped table dedup
files: [tests/phase_z2/test_phase_z2_visual_classifier.py]
tests: [tests/phase_z2/test_phase_z2_visual_classifier.py]
estimate_lines: 45
u1 rationale:
u2 rationale:
Out-of-scope notes:
Rollback strategy:
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
FINAL_CONSENSUS: YES
Executing unit: u1
📌 [Claude #2] Stage 3 (코드 수정) Round #1 — u1 (classifier consumer) 완료
■ unit_executed:
u1(Stage 2 plan — Extend classify_visual_runtime_check)■ files_changed:
src/phase_z2_classifier.py(+61 / −11)■ diff_summary
Deferred import (avoid circular) —
from phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOL, TABLE_SCROLL_TOL_PXadded insideclassify_visual_runtime_check. Module-level import would be circular:phase_z2_pipeline.py:57imports this module, and the tolerance constants are defined atphase_z2_pipeline.py:134/:139(after that import line). Deferred to call time so both modules are fully loaded. Single source of truth — no literal duplication.Removed early-return on
overflow.passed=True(wasphase_z2_classifier.py:367-375). image/table scans must run unconditionally; replaced with a 3-line comment block citing IMP-15 실행-3.Added image_events scan — emits
image_aspect_mismatchclassification whendelta is not None AND |delta| > IMAGE_ASPECT_DELTA_TOL. Each classification carriescategory,source="image_event",zone_position,zone_template_id,src,natural_ratio,rendered_ratio,delta,rule_applied(formatted with actual delta + tolerance).delta is None⇒ skip (image not loaded; no false positive).Added table_events scan — reuses existing
tabular_overflowcategory verbatim (phase_z2_classifier.py:234, spec §3.2 priority 2) whenwrapper_clipped_index is None AND (excess_x > TOL OR excess_y > TOL). Each classification carriescategory,source="table_event",zone_position,zone_template_id,excess_x,excess_y,rule_applied.wrapper_clipped_index is not None⇒ skip (dedup with existingclipped_innercascade; honors producer-side contract fromphase_z2_pipeline.py:2287-2325).Widened
visual_check_passedsemantic — final return now computesvisual_check_passed = bool(overflow.get("passed", False)) and not classifications. Any image_aspect_mismatch or tabular_overflow from the new emitters flips the gate to False even when zone overflow is clean.No changes to:
placement_diagnosticsshape — unchanged.pipeline.pyproduction of image_events / table_events — out-of-scope (already landed in 실행-1/2).■ smoke verification (pure-dict, no Selenium)
Confirms (a) early-return removed (would have short-circuited to True before), (b) image_events consumed, (c) visual_check_passed widening fires. Formal pytest cases land in u2.
■ remaining_units:
[u2]— pure-dict tests for 4 acceptance cases (image fires / image below-tol / table fires / table wrapper-clipped dedup).■ follow_up_issue_candidates: none — scope-lock holds. Router action mapping for
image_aspect_mismatchis already deferred to a FUTURE follow-up per issue body (out-of-scope this issue), and debug.json passthrough is 실행-4.■ scope adherence
■ diff size note
Plan estimated +45 lines; actual +61 / −11 (net +50). Overage is concentrated in the two classification-dict literals (multi-line for readability) and the deferred-import comment. Logic remains a single unit (u1) — splitting further would fragment the consumer extension axis.
■ next
STOP. Awaiting Codex review on u1 before Round #2 (u2 pure-dict tests).
No commit / push performed in this round (per Stage 3 protocol).
[Codex #1]
Stage 3 code-edit verification for executed unit
u1is complete.Verdict:
u1is correct for the scope executed in this round.Verified facts:
src/phase_z2_classifier.pyremoves the early return foroverflow.passed=True, so image/table event scans run even when zone-level overflow is clean.phase_z2_pipelineat call time:IMAGE_ASPECT_DELTA_TOLandTABLE_SCROLL_TOL_PX. This preserves the single source of truth while avoiding the existing module import cycle.image_eventsemitsimage_aspect_mismatchonly whendelta is not Noneandabs(delta) > IMAGE_ASPECT_DELTA_TOL.table_eventsemits the existingtabular_overflowcategory only whenwrapper_clipped_index is Noneand eitherexcess_xorexcess_yexceedsTABLE_SCROLL_TOL_PX.bool(overflow.get("passed", False)) and not classifications, so a new image/table classification flipsvisual_check_passedtoFalse.clipped_innerclassifier paths remain in place;placement_diagnosticsshape is still returned.=== EVIDENCE ===
Commands run:
git diff -- src/phase_z2_classifier.pyrg -n "IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|overflow\\.passed|image_events|table_events|visual_check_passed|image_aspect_mismatch|tabular_overflow" src/phase_z2_classifier.py src/phase_z2_pipeline.pyGet-Content -LiteralPath 'src\\phase_z2_classifier.py' | Select-Object -Skip 320 -First 190Get-Content -LiteralPath 'src\\phase_z2_pipeline.py' | Select-Object -First 160$env:PYTHONPATH='src'; python -m py_compile src\\phase_z2_classifier.py$env:PYTHONPATH='src'; <pure-dict smoke script for image/table event branches>git diff --stat -- src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.pyFiles checked:
D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_classifier.pyD:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_pipeline.pyTest results:
python -m py_compile src\\phase_z2_classifier.py: passed.image_fire:visual_check_passed=False,categories_seen=['image_aspect_mismatch'],classifications=1image_below_tol:visual_check_passed=True,categories_seen=[],classifications=0table_fire:visual_check_passed=False,categories_seen=['tabular_overflow'],classifications=1table_dedup:visual_check_passed=True,categories_seen=[],classifications=0Remaining units: [u2]
rewind_target: continue_same
FINAL_CONSENSUS: NO
Executing unit: u2
📌 [Claude #3] Stage 3 (코드 수정) Round #2 — u2 (pure-dict classifier tests) 완료
■ unit_executed:
u2(Stage 2 plan — Add pure-dict classifier tests)■ files_changed:
tests/phase_z2/test_phase_z2_visual_classifier.py(NEW, 116 lines incl. docstring)■ diff_summary
NEW file —
tests/phase_z2/test_phase_z2_visual_classifier.py— 4 pure-dict unit tests for the newclassify_visual_runtime_checkconsumer behavior landed in u1. No Selenium / chromedriver dependency; no fixtures; no parametrize. Each test constructs an overflow dict by hand and asserts on the classifier's return shape.Imports (single source of truth) — tolerances pulled from
phase_z2_pipeline, not duplicated as literals:Tests derive thresholds from imported constants (
IMAGE_ASPECT_DELTA_TOL + 0.05,TABLE_SCROLL_TOL_PX + 10,TABLE_SCROLL_TOL_PX + 50) — if the SSoT inphase_z2_pipeline.py:134/:139shifts, the tests track automatically._base_overflow(**overrides)helper — minimal clean overflow dict (passed=True, empty zones / image_events / table_events, no slide / slide_body overflow). Each test overlays only the array it exercises. Keeps test bodies focused on the axis under inspection (image_events scan or table_events scan).Four required cases (1:1 with issue acceptance criteria):
test_image_aspect_mismatch_emits_classification—delta = IMAGE_ASPECT_DELTA_TOL + 0.05(above tolerance). Asserts:visual_check_passed is False(widened semantic flips even though zone overflow is clean)categories_seen == ["image_aspect_mismatch"]classifications[0]carriescategory="image_aspect_mismatch",source="image_event",zone_position="top",delta=<expected>.test_image_aspect_delta_below_tol_no_classification—delta = IMAGE_ASPECT_DELTA_TOL / 2.0(below tolerance). Asserts:visual_check_passed is Truecategories_seen == []andclassifications == [](no false positive).test_standalone_table_overflow_emits_classification—wrapper_clipped_index=None,excess_y = TABLE_SCROLL_TOL_PX + 10. Asserts:visual_check_passed is Falsecategories_seen == ["tabular_overflow"](existingtabular_overflowcategory reused verbatim —phase_z2_classifier.py:234)classifications[0]carriescategory="tabular_overflow",source="table_event",zone_position="bottom_l",excess_y=<expected>.test_table_dedup_when_wrapper_clipped—wrapper_clipped_index=0(non-null) withexcess_y = TABLE_SCROLL_TOL_PX + 50(well above tolerance — proves the dedup branch wins over the threshold check). Asserts:visual_check_passed is Trueclassifications == [](honors producer-side dedup contract fromphase_z2_pipeline.py:2287-2325; the wrapper's existingclipped_innercascade owns this case).No changes to:
src/phase_z2_classifier.py(already complete in u1).src/phase_z2_pipeline.py(out-of-scope — image_events / table_events production landed in 실행-1/2).■ verification
categories_seen/classificationsexact-match assertion proves u1's new emitters fire only on the intended path and that the widenedvisual_check_passed = overflow.passed AND not classificationssemantic is observable from the public API.■ remaining_units:
[](u1 + u2 both complete; Stage 2 plan exhausted).■ follow_up_issue_candidates:
pipeline.pyimage_events / table_events production-side regression coverage (separate axis, not implied by this issue).image_aspect_mismatch(explicitly deferred — issue body "Out-of-scope" section calls this FUTURE follow-up).debug.jsonpassthrough for the new event arrays (실행-4 territory).■ scope-lock: respected — only
src/phase_z2_classifier.py(u1) andtests/phase_z2/test_phase_z2_visual_classifier.py(u2) touched. No edits to pipeline.py, no router changes, no debug.json plumbing, no placement_diagnostics shape change, no sample-specific branching.STOP per orchestrator instruction. No commit, no push. Awaiting Codex Stage 3 verification on u2.
[Codex #2]
Stage 3 code-edit verification for executed unit
u2is complete.Verdict:
u2is correct, and all planned implementation units for this issue are now complete.Verified facts:
tests/phase_z2/test_phase_z2_visual_classifier.pyis a new pure-dict test file and does not use Selenium, chromedriver, browser fixtures, or pipeline execution.IMAGE_ASPECT_DELTA_TOLandTABLE_SCROLL_TOL_PXfromsrc.phase_z2_pipeline, so threshold values are not duplicated as hardcoded literals.test_image_aspect_mismatch_emits_classificationcoversdelta > IMAGE_ASPECT_DELTA_TOL, assertsimage_aspect_mismatch, and verifiesvisual_check_passed is False.test_image_aspect_delta_below_tol_no_classificationcovers below-tolerance image delta and asserts no classifications withvisual_check_passed is True.test_standalone_table_overflow_emits_classificationcovers standalone table self-overflow withwrapper_clipped_index=None, assertstabular_overflow, and verifiesvisual_check_passed is False.test_table_dedup_when_wrapper_clippedcovers non-nullwrapper_clipped_indexwith excess beyond tolerance and asserts notabular_overflow, preserving the dedup contract.image_aspect_mismatchandtabular_overfloware emitted under the required thresholds, andvisual_check_passedisoverflow.passed AND not classifications.=== EVIDENCE ===
Commands run:
git diff -- tests/phase_z2/test_phase_z2_visual_classifier.py src/phase_z2_classifier.pyGet-Content -LiteralPath 'tests\phase_z2\test_phase_z2_visual_classifier.py'rg -n "def classify_visual_runtime_check|IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_events|table_events|visual_check_passed|image_aspect_mismatch|tabular_overflow" src\phase_z2_classifier.py src\phase_z2_pipeline.py tests\phase_z2\test_phase_z2_visual_classifier.py$env:PYTHONPATH='src'; pytest -q tests\phase_z2\test_phase_z2_visual_classifier.pyFiles checked:
D:\ad-hoc\kei\design_agent\tests\phase_z2\test_phase_z2_visual_classifier.pyD:\ad-hoc\kei\design_agent\src\phase_z2_classifier.pyD:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.pyTest results:
pytest -q tests\phase_z2\test_phase_z2_visual_classifier.py: 4 passed in 0.04s.Remaining units: none
FINAL_CONSENSUS: YES
📌 [Claude #1] Stage 4: 테스트 및 검증 Round #1 — PASS
■ Verification scope
classify_visual_runtime_checkconsumer extension forimage_events[]/table_events[]+ widenedvisual_check_passed.src/phase_z2_classifier.py(modified) andtests/phase_z2/test_phase_z2_visual_classifier.py(new).orchestrator.py,tests/orchestrator_unit/test_subprocess_cleanup.py) NOT touched in this verification — separate axis per Stage 3 exit report.■ Test results
test_image_aspect_mismatch_emits_classification— delta=TOL+0.05 ⇒ classification fires,visual_check_passed=False.test_image_aspect_delta_below_tol_no_classification— delta=TOL/2 ⇒ skip,visual_check_passed=True.test_standalone_table_overflow_emits_classification—wrapper_clipped_index=None,excess_y=TOL+10⇒tabular_overflowfires.test_table_dedup_when_wrapper_clipped—wrapper_clipped_index=0⇒ no emit (clipped_inner dedup honored).■ Diff ↔ plan conformance (Stage 2 IMPLEMENTATION_UNITS)
from phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOL, TABLE_SCROLL_TOL_PX(deferred import, circular-safe; SSoT honored —src/phase_z2_pipeline.py:134, 139).overflow.passed=TrueREMOVED at former L367-375 (verified bygit diff).delta is None ⇒ continue;abs(delta) > IMAGE_ASPECT_DELTA_TOL ⇒ emit image_aspect_mismatchwith full payload (zone_position, zone_template_id, src, natural_ratio, rendered_ratio, delta, rule_applied).wrapper_clipped_index is not None ⇒ continue;excess_x > TOL or excess_y > TOL ⇒ emit tabular_overflow(priority-2 category reused verbatim).visual_check_passed = bool(overflow.get("passed", False)) and not classifications— widened semantic.placement_diagnosticsshape preserved;categories_seen/unclassified_signalsunchanged.■ Hardcoding audit (RULE 7)
0.05/5in classifier — all tolerance comparisons go through imported constants. SSoT remainssrc/phase_z2_pipeline.py:134, 139.■ Pure-dict audit (RULE 4 PZ-1)
selenium/webdriver/chromedriverimports (only the docstring mentions "no Selenium / chromedriver dependency"). Test fixtures are plain dicts assembled in-line via_base_overflow(**overrides).■ Acceptance criteria (issue #47)
classificationsincludesimage_aspect_mismatch/tabular_overflowwhen triggered.visual_check_passed = overflow.passed AND (not classifications)(intentional flip — verified bytest_image_aspect_mismatch_emits_classificationwithoverflow["passed"]=True).IMAGE_ASPECT_DELTA_TOLimported fromphase_z2_pipeline(single source of truth).■ Decision
src/phase_z2_classifier.py+tests/phase_z2/test_phase_z2_visual_classifier.py.=== EVIDENCE ===
git diff src/phase_z2_classifier.py— early-return removed, two scans added,visual_check_passedwidened (3 hunks; +61/-11).python -m pytest tests/phase_z2/test_phase_z2_visual_classifier.py -q→ 4 passed in 0.05s.python -m pytest tests/phase_z2/ -q --ignore=tests/phase_z2/test_phase_z2_visual_classifier.py→ 93 passed in 26.10s.grep -n "0\.05\|IMAGE_ASPECT_DELTA_TOL\|TABLE_SCROLL_TOL_PX" src/phase_z2_classifier.py→ no literal tolerances; only imported-constant references.grep -n "IMAGE_ASPECT_DELTA_TOL\|TABLE_SCROLL_TOL_PX" src/phase_z2_pipeline.py→ SSoT confirmed at L134, L139.grep -n "Selenium\|selenium\|webdriver\|chromedriver" tests/phase_z2/test_phase_z2_visual_classifier.py→ only docstring mention; no imports.files_changed(src/phase_z2_classifier.py+61/-11,tests/phase_z2/test_phase_z2_visual_classifier.py+116 new) matchesgit status -sworking-tree state.FINAL_CONSENSUS: YES
[Codex #1] Stage 4: test-verify Round #1 - PASS
Verification scope:
classify_visual_runtime_checkconsumesimage_events[]andtable_events[], andvisual_check_passedis widened tooverflow.passed AND no classifications.src/phase_z2_classifier.pyandtests/phase_z2/test_phase_z2_visual_classifier.py.orchestrator.py,tests/orchestrator_unit/test_subprocess_cleanup.py, and unrelated untracked local artifacts.Diff verification:
src/phase_z2_classifier.pyremoves the formeroverflow.get("passed", False)early return, so image/table event scans run even when zone overflow is clean.IMAGE_ASPECT_DELTA_TOLandTABLE_SCROLL_TOL_PXfromphase_z2_pipelineat call time; no classifier-local tolerance literals were found for the comparisons.image_events[]emitsimage_aspect_mismatchonly whendelta is not Noneandabs(delta) > IMAGE_ASPECT_DELTA_TOL.table_events[]emitstabular_overflowonly whenwrapper_clipped_index is Noneand either excess exceedsTABLE_SCROLL_TOL_PX.visual_check_passedis computed asbool(overflow.get("passed", False)) and not classifications.placement_diagnostics,categories_seen, andunclassified_signalsreturn fields are preserved.Test verification:
tests/phase_z2regression suite excluding the new file passes: 93/93.pipeline.pyproduction edit was required for this verification.=== EVIDENCE ===
Commands run:
git status --shortgit diff -- src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.pyrg -n 'IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_aspect_mismatch|tabular_overflow|visual_check_passed|wrapper_clipped_index|overflow\.get' src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.py src/phase_z2_pipeline.pygit diff --check -- src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.pypython -m pytest tests/phase_z2/test_phase_z2_visual_classifier.py -qpython -m pytest tests/phase_z2/ -q --ignore=tests/phase_z2/test_phase_z2_visual_classifier.pyFiles checked:
src/phase_z2_classifier.pytests/phase_z2/test_phase_z2_visual_classifier.pysrc/phase_z2_pipeline.pyTest results:
4 passed in 0.05s93 passed in 25.80sVerified facts:
src/phase_z2_pipeline.pydefinesIMAGE_ASPECT_DELTA_TOL = 0.05andTABLE_SCROLL_TOL_PX = 5.src/phase_z2_classifier.pyuses the imported tolerance names for image/table classification comparisons.tests/phase_z2/test_phase_z2_visual_classifier.pycontains the four required pure-dict tests: image mismatch emit, image below-tolerance skip, standalone table overflow emit, and wrapper-clipped table dedup.git diff --checkreported no whitespace errors for the scoped files.FINAL_CONSENSUS: YES
📌 [Claude #1] Stage 5: 커밋 및 푸쉬 Round #1 — COMPLETE
Korean 요약
visual_check_passedwidened) 단일 commit535c484로 생성 후slide2(Gitea, issue tracker) +origin(GitHub) 양 remote 에 push 성공.src/phase_z2_classifier.py(+61/-11),tests/phase_z2/test_phase_z2_visual_classifier.py(+116 new).git add는 두 파일에 한정 — 작업 트리에 잔류한orchestrator.py/tests/orchestrator_unit/test_subprocess_cleanup.py(별 axis: P3 subprocess fix),.orchestrator/,chromedriver/,tests/matching/,samples/uploads/,templates/catalog/등 untracked artefact 는 본 commit 에 미포함.feat(IMP-15): 실행-3 — classifier consumes image+table events접두 + 변경 요약 (early-return 제거 / 두 emitter /visual_check_passedwiden) + guardrail (8-rule cascade 보존 /placement_diagnosticsshape /pipeline.py미변경) + 테스트 4 케이스 + Stage 4 결과 (4/4 신규, 93/93 regression) 포함.535c4848fd60a1e5a57757048b6ffaad4495a3b6(git ls-remote slide2 / origin refs/heads/main직접 확인).Scope 준수
git add는 명시된 두 파일만. 작업 트리에 잔류한orchestrator.py/tests/orchestrator_unit/test_subprocess_cleanup.py의 P3 subprocess fix 변경 + 다수 untracked noise (sample / matching / scratch) 는 본 commit 에 미포함.pipeline.py변경 X (producer 는 실행-1/2 에서 이미 landed),slide_base.html/catalog/frame DB/debug.json/ router action / spec doc 모두 미변경.git diff --staged --stat결과 = Stage 3 / Stage 4 exit report 와 일치 (src/phase_z2_classifier.py72L (+61/-11),tests/phase_z2/test_phase_z2_visual_classifier.py116L new).실행 흐름
git status --short— 스코프 파일 + 다수 untracked artefact + 무관 modified 두 파일 확인.git diff --stat -- src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.py— 72 line 변경량 사전 검증.git add src/phase_z2_classifier.py tests/phase_z2/test_phase_z2_visual_classifier.py— 명시 파일만 (CRLF 경고만, 변환 무해).git diff --staged --stat—src/phase_z2_classifier.py+61/-11,tests/phase_z2/test_phase_z2_visual_classifier.py+116/0 확인.git diff --staged -- src/phase_z2_classifier.py | head -120— early-return 제거 + 두 신규 scan +visual_check_passedwidening 확인.git commit -m …— HEREDOC 으로 본문 보존,--amend/--no-verify사용 없음. pre-commit hook 통과.git push origin main— fast-forward2827622..535c484.git push slide2 main— fast-forward2827622..535c484(Processed 1 references).git ls-remote origin / slide2 refs/heads/main— 양측 모두535c4848…로 local HEAD 일치.잔류 unit (out-of-scope, 별도 issue)
debug.json에image_events/table_eventsraw surfacing +PHASE-Z-PIPELINE-OVERVIEW.md/PHASE-Z-PIPELINE-STATUS-BOARD.md/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.mdrow update.image_aspect_mismatchclassification → frame swap / sizing adapter 매핑 (FUTURE follow-up issue, 본 issue body 의 out-of-scope 라인 명시).orchestrator.py+tests/orchestrator_unit/test_subprocess_cleanup.pytext/bytes mode bug. 별 axis (2026-05-18), 본 issue 무관. 별도 commit/issue 로 분기 필요.Guardrails (post-commit)
IMAGE_ASPECT_DELTA_TOL/TABLE_SCROLL_TOL_PX는phase_z2_pipeline에서 deferred import — classifier-local literal 없음, SSoT 유지. 실행-4 / router 도 동일 상수를 import 해야 함.placement_diagnostics,categories_seen,unclassified_signalsreturn-shape 보존 — downstream consumer 의debug.json의존성 호환.wrapper_clipped_index is not None ⇒ skip tabular_overflowdedup 계약은 pipeline producer (실행-2) 와 consumer 양측에서 잠금.=== EVIDENCE ===
source_comment_ids:
FINAL_CONSENSUS: YES
[Codex #1] Stage 5: commit-push verification - COMPLETE
I verified the Stage 5 commit and remote state for Issue #47.
Decision:
535c4848fd60a1e5a57757048b6ffaad4495a3b6.src/phase_z2_classifier.pytests/phase_z2/test_phase_z2_visual_classifier.pymainrefs now point to the same SHA:origin/main=535c4848fd60a1e5a57757048b6ffaad4495a3b6slide2/main=535c4848fd60a1e5a57757048b6ffaad4495a3b6Working-tree note:
orchestrator.py,tests/orchestrator_unit/test_subprocess_cleanup.py,.orchestrator/,chromedriver/,samples/,templates/, andtests/matching/.535c4848fd60a1e5a57757048b6ffaad4495a3b6.=== EVIDENCE ===
Commands run:
git status --shortgit rev-parse HEADgit show --stat --name-only --format=fuller --no-renames HEADgit remote -vgit ls-remote origin refs/heads/maingit ls-remote slide2 refs/heads/maingit diff-tree --no-commit-id --name-status -r HEADgit show --stat --oneline --no-renames HEADFiles checked:
src/phase_z2_classifier.pytests/phase_z2/test_phase_z2_visual_classifier.pyCommit SHA:
535c4848fd60a1e5a57757048b6ffaad4495a3b6Verified facts:
git rev-parse HEADreturned535c4848fd60a1e5a57757048b6ffaad4495a3b6.git diff-tree --no-commit-id --name-status -r HEADreturned exactly:M src/phase_z2_classifier.pyA tests/phase_z2/test_phase_z2_visual_classifier.pygit show --stat --oneline --no-renames HEADreported2 files changed, 177 insertions(+), 11 deletions(-).git ls-remote origin refs/heads/mainreturned535c4848fd60a1e5a57757048b6ffaad4495a3b6.git ls-remote slide2 refs/heads/mainreturned535c4848fd60a1e5a57757048b6ffaad4495a3b6.FINAL_CONSENSUS: YES
📌 [Claude #1] Stage 6: 최종 확인 / close — Round #1
Issue body re-read (execution-issue mode)
IMP-15 실행-3 (child execution issue). Parent IMP-15 already analyzed scope; this issue's single axis = classifier consumer of
image_events[]+table_events[]withvisual_check_passedsemantic widening.Scope-lock from issue body:
classify_visual_runtime_checkto consumeimage_events+table_events.visual_check_passedwhen any classification fires (even if zone overflow is clean).src/phase_z2_classifier.py+tests/phase_z2/test_phase_z2_visual_classifier.py(NEW).Acceptance criteria — code-anchored verification
classificationslist includesimage_aspect_mismatchwhen image delta exceededsrc/phase_z2_classifier.py:435("category": "image_aspect_mismatch") emitted in image_events scan L426-447classificationslist includestabular_overflowwhen standalone table overflow firessrc/phase_z2_classifier.py:460("category": "tabular_overflow") emitted in table_events scan L449-471visual_check_passed = overflow.passed AND (not classifications)src/phase_z2_classifier.py:495— exact form:bool(overflow.get("passed", False)) and not classificationsIMAGE_ASPECT_DELTA_TOLimported fromphase_z2_pipeline(SSoT)src/phase_z2_classifier.py:360deferred import:from phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOL, TABLE_SCROLL_TOL_PX(circular-safe)tests/phase_z2/test_phase_z2_visual_classifier.py:41/63/83/104— fourdef test_*functions, no chromedriver/Selenium importsTest coverage matches issue body exactly:
test_image_aspect_mismatch_emits_classification— delta > 0.05 → classification +visual_check_passed=False✅ (L41)test_image_aspect_delta_below_tol_no_classification— delta < 0.05 → no classification ✅ (L63)test_standalone_table_overflow_emits_classification— table self-overflow →"tabular_overflow"✅ (L83)test_table_dedup_when_wrapper_clipped—wrapper_clipped_indexset → notabular_overflow(dedup honored) ✅ (L104)Out-of-scope (issue body) — verified untouched:
pipeline.pyproduction (실행-1/2 already landed) — 0 changes in this commit.debug.jsonpassthrough (실행-4) — deferred.image_aspect_mismatch— deferred (FUTURE follow-up).Commit verification (3-way SHA match)
HEAD→535c4848fd60a1e5a57757048b6ffaad4495a3b6git ls-remote origin refs/heads/main→535c4848fd60a1e5a57757048b6ffaad4495a3b6git ls-remote slide2 refs/heads/main→535c4848fd60a1e5a57757048b6ffaad4495a3b6feat(IMP-15): 실행-3 — classifier consumes image+table events2827622(IMP-16 Step 14 table_self_overflow detection)src/phase_z2_classifier.py+61/-11,tests/phase_z2/test_phase_z2_visual_classifier.py+116/-0 (total 2 files, +177/-11)Goal vs result
image_events[]andtable_events[]arrays (produced by 실행-1/2) and widensvisual_check_passedsemantics so image/table mismatches surface even when zone overflow is clean.image_aspect_mismatch+tabular_overfloware ADDITIVE emitters layered on top of the existing §3.2 8-rule cascade.visual_check_passedrecomputed at L495 =overflow.passed AND not classifications. Tolerances imported from pipeline (SSoT) — no classifier-local literals.pipeline.pyproduction code untouched,placement_diagnosticsshape preserved, 8-rule cascade (clipped_inner/ zone-self) untouched (verified by Stage 4 — 93/93 regression PASS).Follow-up — IMP-15 remaining execution issues
e9b3d2e2827622(IMP-16 sibling)visual_check_passedflip535c484debug.jsonraw passthrough forimage_events[]/table_events[]image_aspect_mismatch(separate axis)Labels
No label changes proposed in this issue. Parent IMP-15 owns the rollup label state when remaining child execution units (실행-4 + router-action follow-up) close.
Close decision
CLOSE issue #47. All 5 acceptance criteria satisfied, commit on both remotes (origin + slide2) at identical SHA
535c484, scope-lock honored (2 files only), 4 pure-dict tests PASS + 93 regression PASS, deferred axes (debug.json passthrough, router action) explicitly named with follow-up paths. No unresolved questions block close.=== EVIDENCE ===
Commands:
git rev-parse HEAD→535c4848fd60a1e5a57757048b6ffaad4495a3b6git log -1 --format='%H%n%s'→535c484/feat(IMP-15): 실행-3 — classifier consumes image+table eventsgit ls-remote origin refs/heads/main→535c4848fd60a1e5a57757048b6ffaad4495a3b6git ls-remote slide2 refs/heads/main→535c4848fd60a1e5a57757048b6ffaad4495a3b6git show --stat 535c484→ 2 files changed, 177 insertions(+), 11 deletions(-)rg -n 'IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_aspect_mismatch|tabular_overflow|visual_check_passed' src/phase_z2_classifier.py→ import at L360, image emit L435, table emit L460,visual_check_passedwidened at L495rg -n 'def test_' tests/phase_z2/test_phase_z2_visual_classifier.py→ 4 functions at L41/63/83/104Files verified in HEAD commit:
src/phase_z2_classifier.py— early-return removed, image_events scan added (L426-447), table_events scan added (L449-471),visual_check_passedwidened (L495), SSoT import (L360).tests/phase_z2/test_phase_z2_visual_classifier.py— new, 4 pure-dict cases, tolerances imported fromsrc.phase_z2_pipeline(no hardcoded literals).Stage 4 test record (binding):
pytest tests/phase_z2/excluding new file: 93/93 PASS (regression — 8-rule cascade preserved).Reviewer chain:
FINAL_CONSENSUS: YES
[Codex #1] Stage 6 final-close verification
Final independent verification result: PASS. I verified the committed implementation against issue #47 scope and acceptance criteria, using the completed stage reports as binding contracts and checking the current repository state directly.
Scope-lock confirmed:
src/phase_z2_classifier.pyandtests/phase_z2/test_phase_z2_visual_classifier.py.pipeline.py, router action selection, debug.json passthrough, and unrelated dirty worktree files are not part of this commit.Acceptance criteria verified:
classify_visual_runtime_checkconsumesoverflow["image_events"]and emitsimage_aspect_mismatchwhendelta is not Noneandabs(delta) > IMAGE_ASPECT_DELTA_TOL.classify_visual_runtime_checkconsumesoverflow["table_events"]and emitstabular_overflowwhenwrapper_clipped_index is Noneand table excess exceedsTABLE_SCROLL_TOL_PX.wrapper_clipped_indexskips the table-event emitter.visual_check_passedis computed asbool(overflow.get("passed", False)) and not classifications.phase_z2_pipeline:IMAGE_ASPECT_DELTA_TOL = 0.05,TABLE_SCROLL_TOL_PX = 5.=== EVIDENCE ===
Commands run:
git rev-parse HEADgit diff-tree --no-commit-id --name-status -r HEADgit show --stat --oneline --no-renames HEADgit ls-remote origin refs/heads/main; git ls-remote slide2 refs/heads/mainrg -n "IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_aspect_mismatch|tabular_overflow|visual_check_passed|image_events|table_events|wrapper_clipped_index" src/phase_z2_classifier.pyrg -n "def test_|selenium|chromedriver|IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX|image_aspect_mismatch|tabular_overflow|wrapper_clipped_index|visual_check_passed" tests/phase_z2/test_phase_z2_visual_classifier.pyrg -n "IMAGE_ASPECT_DELTA_TOL|TABLE_SCROLL_TOL_PX" src/phase_z2_pipeline.pypytest -q tests/phase_z2/test_phase_z2_visual_classifier.pygit status --shortFiles checked:
src/phase_z2_classifier.pytests/phase_z2/test_phase_z2_visual_classifier.pysrc/phase_z2_pipeline.pyVerified facts:
535c4848fd60a1e5a57757048b6ffaad4495a3b6.origin/mainis535c4848fd60a1e5a57757048b6ffaad4495a3b6.slide2/mainis535c4848fd60a1e5a57757048b6ffaad4495a3b6.M src/phase_z2_classifier.pyandA tests/phase_z2/test_phase_z2_visual_classifier.py.2 files changed, 177 insertions(+), 11 deletions(-).src/phase_z2_classifier.pyimportsIMAGE_ASPECT_DELTA_TOLandTABLE_SCROLL_TOL_PXfromphase_z2_pipelineinsideclassify_visual_runtime_check.src/phase_z2_classifier.pycontains theimage_eventsscan and theimage_aspect_mismatchemitter.src/phase_z2_classifier.pycontains thetable_eventsscan, thewrapper_clipped_index is not Nonededupe skip, and thetabular_overflowemitter.src/phase_z2_classifier.pycomputesvisual_check_passed = bool(overflow.get("passed", False)) and not classifications.src/phase_z2_pipeline.pydefinesIMAGE_ASPECT_DELTA_TOL = 0.05andTABLE_SCROLL_TOL_PX = 5.4 passed in 0.05s.FINAL_CONSENSUS: YES