feat(IMP-15): 실행-4 — debug.json event surfacing + spec taxonomy row
Issue: #48 (IMP-15 실행-4, axis 4: debug.json + spec doc trace). Parent: #15. Depends on 실행-1/2/3 (events + classifier outputs). Surfaces the image/table event streams that 실행-1/2/3 already produced and consumed, mirroring the existing `zone_geometries_px` top-level precedent (no new pattern introduced). Adds the matching taxonomy row to the Phase Z fit-classifier/router spec. src/phase_z2_pipeline.py (+3): - write_debug_json now lifts `image_events` and `table_events` to top-level of `debug.json` via `(visual_runtime_check or {}).get(<k>, [])`, exactly mirroring the immediately preceding `zone_geometries_px` surfacing line. Defaults to `[]` when `visual_runtime_check` is None — additive, no consumer-visible breakage. docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md (+1): - §3.1 taxonomy adds `image_aspect_mismatch` row. Row text explicitly marks the signal as post-render `fail_reasons` from Step 14 visual_runtime_check (rendered vs declared aspect ratio mismatch), NOT a router-routed fit_classifier output, and notes the separate `image_events` stream surface. Prevents future readers from wiring this taxonomy into §3.2 priority list or §4 router action map. tests/phase_z2/test_debug_json_event_surfacing.py (new, 2 tests): - `test_write_debug_json_surfaces_image_and_table_events` invokes write_debug_json with synthetic visual_runtime_check containing both event lists; reads back the on-disk debug.json and asserts both keys are present at top level with the exact payloads. - `test_write_debug_json_defaults_when_visual_runtime_check_none` asserts both new keys default to `[]` when visual_runtime_check is None — guards the defensive `(… or {})` pattern. tests/phase_z2/test_spec_taxonomy_image_aspect_mismatch.py (new, 2 tests): - `test_spec_has_image_aspect_mismatch_row` opens the spec file and asserts exactly one `^\| image_aspect_mismatch \|` row exists inside the §3.1 table block (no markdown-parser dependency). - `test_spec_row_marks_post_render_fail_reasons_semantic` asserts the row text carries both "Post-render" and "fail_reasons" tokens — enforces the Stage 1 guardrail wording. Verification (Stage 4 PASS, Claude + Codex independent): - pytest -q tests/phase_z2/test_debug_json_event_surfacing.py \ tests/phase_z2/test_spec_taxonomy_image_aspect_mismatch.py → 4 passed in 0.07s. - git diff scope: 4 files, +148 insertions / 0 deletions. Scope-locked: no edits to classifier (실행-3), event generation (실행-1/2), Step 21 viewer, §3.2 priority list, §4 router action mapping, or `table_self_overflow` taxonomy row. Pre-existing dirty/untracked working-tree files left untouched. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -93,6 +93,7 @@ action :
|
|||||||
| `moderate_overflow` | content_type ∈ {`text_flow`, `frame_label`} AND `line_equivalent` ∈ (1.5, 4] |
|
| `moderate_overflow` | content_type ∈ {`text_flow`, `frame_label`} AND `line_equivalent` ∈ (1.5, 4] |
|
||||||
| `minor_overflow` | content_type ∈ {`text_flow`, `frame_label`} AND `line_equivalent` ≤ 1.5 |
|
| `minor_overflow` | content_type ∈ {`text_flow`, `frame_label`} AND `line_equivalent` ≤ 1.5 |
|
||||||
| `hard_visual_fail` | 위 어디에도 매핑 안 됨 OR retry budget 소진 |
|
| `hard_visual_fail` | 위 어디에도 매핑 안 됨 OR retry budget 소진 |
|
||||||
|
| `image_aspect_mismatch` | Post-render `fail_reasons` signal — Step 14 visual_runtime_check 가 이미지 frame slot 의 rendered aspect ratio 와 declared aspect ratio 불일치를 감지 (router-routed fit_classifier 출력 아님; 별도 image_events stream 으로 표면화) |
|
||||||
|
|
||||||
### 3.2 분류 우선순위 (위에서 아래로)
|
### 3.2 분류 우선순위 (위에서 아래로)
|
||||||
|
|
||||||
|
|||||||
@@ -2737,6 +2737,9 @@ def write_debug_json(run_dir: Path, layout_preset: str,
|
|||||||
"visual_runtime_check": visual_runtime_check,
|
"visual_runtime_check": visual_runtime_check,
|
||||||
# A-6 (IMP-01 #1) — additive top-level zone bbox trace (slide-relative px)
|
# A-6 (IMP-01 #1) — additive top-level zone bbox trace (slide-relative px)
|
||||||
"zone_geometries_px": (visual_runtime_check or {}).get("zone_geometries_px", []),
|
"zone_geometries_px": (visual_runtime_check or {}).get("zone_geometries_px", []),
|
||||||
|
# IMP-15 실행-4 (issue #48) — additive top-level Step 14 event streams
|
||||||
|
"image_events": (visual_runtime_check or {}).get("image_events", []),
|
||||||
|
"table_events": (visual_runtime_check or {}).get("table_events", []),
|
||||||
}
|
}
|
||||||
debug_path = run_dir / "debug.json"
|
debug_path = run_dir / "debug.json"
|
||||||
debug_path.write_text(json.dumps(debug, ensure_ascii=False, indent=2), encoding="utf-8")
|
debug_path.write_text(json.dumps(debug, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||||
|
|||||||
81
tests/phase_z2/test_debug_json_event_surfacing.py
Normal file
81
tests/phase_z2/test_debug_json_event_surfacing.py
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
"""IMP-15 실행-4 (Gitea issue #48) — debug.json top-level event surfacing.
|
||||||
|
|
||||||
|
Verifies ``write_debug_json`` lifts ``image_events`` + ``table_events`` out of
|
||||||
|
``visual_runtime_check`` and exposes them as top-level keys, mirroring the
|
||||||
|
existing ``zone_geometries_px`` precedent (src/phase_z2_pipeline.py:2739).
|
||||||
|
|
||||||
|
Two scenarios:
|
||||||
|
|
||||||
|
* Populated — ``visual_runtime_check`` carries non-empty event lists; the
|
||||||
|
written debug dict surfaces both at the top level with identical payloads.
|
||||||
|
* None — ``visual_runtime_check is None``; both top-level keys default to ``[]``
|
||||||
|
(no KeyError, no propagated None).
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import json
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
from src.phase_z2_pipeline import write_debug_json
|
||||||
|
|
||||||
|
|
||||||
|
def _read_debug(run_dir: Path) -> dict:
|
||||||
|
return json.loads((run_dir / "debug.json").read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_debug_json_surfaces_image_and_table_events(tmp_path: Path) -> None:
|
||||||
|
image_events = [
|
||||||
|
{
|
||||||
|
"src": "img/a.png",
|
||||||
|
"zone_position": "primary",
|
||||||
|
"zone_template_id": "tid-1",
|
||||||
|
"natural_w": 200,
|
||||||
|
"natural_h": 100,
|
||||||
|
"rendered_w": 200,
|
||||||
|
"rendered_h": 200,
|
||||||
|
"delta": 1.0,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
table_events = [
|
||||||
|
{
|
||||||
|
"zone_position": "secondary",
|
||||||
|
"zone_template_id": "tid-2",
|
||||||
|
"clientWidth": 300,
|
||||||
|
"scrollWidth": 360,
|
||||||
|
"excess_x": 60,
|
||||||
|
"wrapper_clipped_index": 0,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
visual_runtime_check = {
|
||||||
|
"image_events": image_events,
|
||||||
|
"table_events": table_events,
|
||||||
|
"zone_geometries_px": [],
|
||||||
|
}
|
||||||
|
|
||||||
|
write_debug_json(
|
||||||
|
run_dir=tmp_path,
|
||||||
|
layout_preset="single",
|
||||||
|
debug_zones=[],
|
||||||
|
layout_css={},
|
||||||
|
visual_runtime_check=visual_runtime_check,
|
||||||
|
)
|
||||||
|
|
||||||
|
debug = _read_debug(tmp_path)
|
||||||
|
assert "image_events" in debug, "image_events must be a top-level key"
|
||||||
|
assert "table_events" in debug, "table_events must be a top-level key"
|
||||||
|
assert debug["image_events"] == image_events
|
||||||
|
assert debug["table_events"] == table_events
|
||||||
|
|
||||||
|
|
||||||
|
def test_write_debug_json_defaults_when_visual_runtime_check_none(tmp_path: Path) -> None:
|
||||||
|
write_debug_json(
|
||||||
|
run_dir=tmp_path,
|
||||||
|
layout_preset="single",
|
||||||
|
debug_zones=[],
|
||||||
|
layout_css={},
|
||||||
|
visual_runtime_check=None,
|
||||||
|
)
|
||||||
|
|
||||||
|
debug = _read_debug(tmp_path)
|
||||||
|
assert debug["image_events"] == []
|
||||||
|
assert debug["table_events"] == []
|
||||||
63
tests/phase_z2/test_spec_taxonomy_image_aspect_mismatch.py
Normal file
63
tests/phase_z2/test_spec_taxonomy_image_aspect_mismatch.py
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
"""Spec lint: PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md §3.1 taxonomy must declare
|
||||||
|
the `image_aspect_mismatch` row (IMP-15 실행-4, issue #48 u2).
|
||||||
|
|
||||||
|
The row encodes a post-render `fail_reasons` signal surfaced by Step 14
|
||||||
|
visual_runtime_check, not a router-routed fit_classifier output. It is
|
||||||
|
intentionally placed inside §3.1 to keep the taxonomy vocabulary aligned
|
||||||
|
with the event streams now exposed at debug.json top level (u1).
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
SPEC_PATH = (
|
||||||
|
Path(__file__).resolve().parents[2]
|
||||||
|
/ "docs"
|
||||||
|
/ "architecture"
|
||||||
|
/ "PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def _read_spec_text() -> str:
|
||||||
|
return SPEC_PATH.read_text(encoding="utf-8")
|
||||||
|
|
||||||
|
|
||||||
|
def _extract_section_3_1(text: str) -> str:
|
||||||
|
start_match = re.search(r"^###\s+3\.1\b", text, flags=re.MULTILINE)
|
||||||
|
assert start_match, "§3.1 heading missing from spec"
|
||||||
|
after_3_1 = text[start_match.end():]
|
||||||
|
end_match = re.search(r"^###\s+3\.2\b", after_3_1, flags=re.MULTILINE)
|
||||||
|
assert end_match, "§3.2 heading missing from spec"
|
||||||
|
return after_3_1[: end_match.start()]
|
||||||
|
|
||||||
|
|
||||||
|
def test_spec_section_3_1_contains_image_aspect_mismatch_row():
|
||||||
|
section = _extract_section_3_1(_read_spec_text())
|
||||||
|
row_pattern = re.compile(r"^\|\s*`image_aspect_mismatch`\s*\|", re.MULTILINE)
|
||||||
|
matches = row_pattern.findall(section)
|
||||||
|
assert len(matches) == 1, (
|
||||||
|
"Expected exactly 1 `image_aspect_mismatch` row inside §3.1 taxonomy, "
|
||||||
|
f"found {len(matches)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_image_aspect_mismatch_row_reflects_post_render_semantic():
|
||||||
|
section = _extract_section_3_1(_read_spec_text())
|
||||||
|
row_line = next(
|
||||||
|
(
|
||||||
|
line
|
||||||
|
for line in section.splitlines()
|
||||||
|
if line.lstrip().startswith("| `image_aspect_mismatch`")
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
assert row_line is not None, "image_aspect_mismatch row not found"
|
||||||
|
assert "Post-render" in row_line or "post-render" in row_line, (
|
||||||
|
"Row must mark the signal as post-render (Stage 1 guardrail)"
|
||||||
|
)
|
||||||
|
assert "fail_reasons" in row_line, (
|
||||||
|
"Row must reference `fail_reasons` so the vocabulary mirrors the "
|
||||||
|
"visual_runtime_check output"
|
||||||
|
)
|
||||||
Reference in New Issue
Block a user