39 KiB
IMP-01 A-6 — zone_geometries_px Runtime Verification Log
Issue: #1 IMP-01 A-6 Zone DOM coordinate export
Implementation commit (locked, no-touch): 1dc81e0
Current HEAD at verification start: 4e281a2
Scope: Runtime verification ONLY — production source under src/phase_z2_pipeline.py is zero-touch this stage.
This document captures verification artifacts for the existing additive trace field zone_geometries_px produced by Step 14 inline JS and surfaced by Step 21 write_debug_json. Sections fill incrementally per Stage 3 implementation_unit; §A is the unit u1 deliverable.
§A — Driver Resolution Policy and Environment
A.1 Locked driver chain (matches src/phase_z2_pipeline.py:3162-3185)
run_overflow_check initialises Selenium Chrome in the following deterministic order. The verification harness MUST follow this same order without modification.
- PROJECT_ROOT/chromedriver (no extension). Resolved via
Path.is_file(). Skipped if it is a directory or absent. - PROJECT_ROOT/chromedriver.exe. Resolved via
Path.is_file(). Skipped if absent. - Selenium Manager fallback. Triggered only when neither candidate above resolved. Calls
webdriver.Chrome(options=options)with noService(executable_path=...); Selenium 4 downloads/locates a compatible driver against the installed Chrome binary. - Hard failure path. If all of the above raise,
run_overflow_checkreturns{"passed": False, "error": "selenium init failed: ..."}. No retry, no alternate path.
Verification harness MUST NOT:
- Install or copy any binary into
PROJECT_ROOT/chromedriverorPROJECT_ROOT/chromedriver.exe. - Place a chromedriver on
PATHfor the duration of the run. - Mutate
_MEASURE_SCRIPT,run_overflow_check, orwrite_debug_json. - Patch
debug.jsonafter generation.
A.2 Captured environment state (host: Windows 11 Pro 10.0.22631)
| Probe | Path / Command | Observed value |
|---|---|---|
| Chrome binary | C:\Program Files\Google\Chrome\Application\chrome.exe |
present |
| Chrome version | (Get-Item …\chrome.exe).VersionInfo.ProductVersion |
148.0.7778.168 |
| PROJECT_ROOT chromedriver (no ext) | D:\ad-hoc\kei\design_agent\chromedriver |
directory, not a file → Path.is_file() returns False |
| PROJECT_ROOT chromedriver.exe | D:\ad-hoc\kei\design_agent\chromedriver.exe |
absent (No such file or directory) |
| Nested driver (not on resolved chain) | D:\ad-hoc\kei\design_agent\chromedriver\win64\ |
contains 146.0.7680.165\, 147.0.7727.117\ — both ignored by current resolution logic |
where chromedriver (PATH) |
(PowerShell) | not present |
A.3 Predicted resolution outcome at HEAD 4e281a2
Given §A.1 + §A.2:
PROJECT_ROOT/chromedriver— skipped (directory, not file).PROJECT_ROOT/chromedriver.exe— skipped (absent).- Selenium Manager fallback — invoked. Outcome depends on Selenium Manager's ability to obtain a chromedriver compatible with Chrome
148.0.7778.168from this host. Either:- Path A — match success: Selenium Manager downloads or finds a
148.*driver →run_overflow_checkproceeds →zone_geometries_pxpopulated. - Path B — match failure: Selenium Manager raises (network blocked / cache miss / no compatible driver) →
run_overflow_checkreturns{"passed": False, "error": "selenium init failed: …"}→ Step 21 surfaceszone_geometries_px: [](fallback path).
- Path A — match success: Selenium Manager downloads or finds a
Both paths are valid Step 14 / Step 21 behaviour by design (locked schema permits the empty-list fallback). Neither path requires production code change.
A.4 Classification rule (env-blocker vs. code regression)
The following outcomes are classified as env-blocker (NOT a code regression, NOT a Stage 1 rewind trigger). Verification records the blocker and ends at unit u2:
- Selenium Manager raises
WebDriverExceptionorSessionNotCreatedExceptionciting browser-version mismatch (e.g., driver supports Chrome ≤ N, installed Chrome = N+1) on every layout topology. - Selenium Manager fails to retrieve a driver due to absent network / blocked download.
- Chrome process fails to launch headless (
--headless=new) due to sandbox / OS policy.
The following outcomes are classified as code regression and DO trigger Stage 1 rewind:
zone_geometries_pxmissing from top-leveldebug.jsonwhilevisual_runtime_checkreportspassed=True.- Per-item shape diverges from locked schema (e.g., float instead of integer coords, missing key, additional unexpected key).
- Coordinate space is screen-relative or window-relative rather than slide-relative (verified by checking that some non-edge zone has
x > 0ANDy > 0AND(x + w) <= slide_clientWidth + 1AND(y + h) <= slide_clientHeight + 1).
The following outcomes are classified as resolution-chain divergence and DO trigger Stage 1 rewind:
- Driver resolution observed to skip Selenium Manager fallback when both PROJECT_ROOT candidates fail.
- Driver resolution observed to consume the nested
chromedriver/win64/<ver>/path without an explicit code change to the candidate list.
A.5 Runtime invocation contract for u2
Unit u2 executes Phase Z runtime per layout topology with the harness above. Each invocation MUST:
- Run from
PROJECT_ROOTsoPROJECT_ROOT/chromedriverandPROJECT_ROOT/chromedriver.exeresolution semantics match production behaviour. - Persist the resulting
debug.jsonto.orchestrator/tmp/imp01_a6_runs/<layout>/debug.json. - Record the exact command line, working directory, Python interpreter path, and the resolved driver mechanism (
project_root_no_ext|project_root_exe|selenium_manager|init_failed) in §B. - NOT pre-create the per-layout subdirectory by copying a previous run; each layout produces its own pipeline output.
A.6 No-touch guardrail (driver layer)
For the duration of all units u1–u5, the candidate list at src/phase_z2_pipeline.py:3168-3171 is locked. Any urge to add a third candidate (e.g., the nested chromedriver/win64/147.0.7727.117/chromedriver.exe) is rejected — that is an env-setup task on a separate axis, not IMP-01 verification scope.
§B — Per-layout runtime runs (unit u2)
B.1 Legacy → Phase Z preset mapping (axis B coverage)
The Stage 2 plan called for sidebar-right + two-column + hero-detail + single-column layout topology coverage (CLAUDE.md:134-144). Those four names are the legacy Phase Q preset vocabulary. The production Phase Z pipeline (src/phase_z2_pipeline.py:7904-7906) accepts the active 8-preset vocabulary (single, horizontal-2, vertical-2, top-1-bottom-2, top-2-bottom-1, left-1-right-2, left-2-right-1, grid-2x2). The legacy names are not accepted at the CLI, so unit u2 executes the Phase Z preset whose CSS grid topology matches each contracted topology class. The mapping is grounded in templates/phase_z2/layouts/layouts.yaml (positions / css_areas):
| Legacy preset (Stage 2 plan) | Topology class | Phase Z preset executed | Positions | Justification for mapping |
|---|---|---|---|---|
single-column |
1 zone, full-width | single |
[primary] |
Only 1-zone preset in the Phase Z vocabulary. CLAUDE.md:140 single-column grid "title" "body" "footer" / 1fr matches single's primary-only zone. |
two-column |
2 zones, equal columns | vertical-2 |
[left, right] |
CLAUDE.md:136 two-column grid "title title" "left right" "footer footer" / 1fr 1fr matches vertical-2's [left, right] columns at 1fr 1fr. |
sidebar-right |
3 zones, main left + 2 sidebar items right | left-1-right-2 |
[left, right-top, right-bottom] |
layouts.yaml:97-100 declares css_areas: "left right-top" "left right-bottom" — a single left zone spans both rows while the right column is split into right-top / right-bottom. That is the topological shape of CLAUDE.md:135 sidebar-right grid "title title" "body sidebar" "footer footer" once the right-column "sidebar" carries the conventional ≥2 stacked items. vertical-2 does not satisfy this row because it is symmetric (no row-spanning main + stacked sidebar items). |
hero-detail |
3 zones, hero top + asymmetric detail bottom | top-1-bottom-2 |
[top, bottom-left, bottom-right] |
CLAUDE.md:137 hero-detail grid "title title" "hero hero" "detail detail" "footer footer" carries a single dominant hero row above a multi-cell detail row. top-1-bottom-2 is the only Phase Z preset with a single-cell top spanning above a 2-cell bottom. |
All four contracted topologies are now covered. Each preset above produces a distinct CSS grid (single = single-area, vertical-2 = symmetric columns, left-1-right-2 = row-spanning main + stacked sidebar, top-1-bottom-2 = single-cell hero + 2-cell detail), so the Step 14 slideRect-anchored bbox math is exercised against the four required topology classes — not against a degenerate substitution.
B.2 Sample selection
All four runs use samples/mdx_batch/02.mdx (DX의 시행 목표 및 기대효과). Justification:
- 02.mdx is in the canonical
tests/CLAUDE.mdfixture inventory (test fixture convention F-5). - 02.mdx aligns to 3 sections (
02-1, 02-2-sub-1, 02-2-sub-2), which covers all unit-count requirements from 1 to 3 via--override-section-assignment. - Using a single source MDX isolates the layout topology axis from the content axis (the
zone_geometries_pxschema is content-independent by design).
B.3 Runtime invocations (verbatim commands)
Working directory: D:\ad-hoc\kei\design_agent (PROJECT_ROOT).
Python interpreter: C:\Users\User\AppData\Local\Programs\Python\Python313\python.exe (Python 3.13.1).
Selenium version: 4.34.0 (per python -c "import selenium; print(selenium.__version__)").
| # | Contract topology | Phase Z preset | Command |
|---|---|---|---|
| 1 | single-column |
single |
python -m src.phase_z2_pipeline samples/mdx_batch/02.mdx imp01_a6_single_probe --override-layout single --override-section-assignment primary=02-1 |
| 2 | two-column |
vertical-2 |
python -m src.phase_z2_pipeline samples/mdx_batch/02.mdx imp01_a6_vertical2 --override-layout vertical-2 |
| 3 | sidebar-right |
left-1-right-2 |
python -m src.phase_z2_pipeline samples/mdx_batch/02.mdx imp01_a6_sidebar_right --override-layout left-1-right-2 --override-section-assignment left=02-1 --override-section-assignment right-top=02-2-sub-1 --override-section-assignment right-bottom=02-2-sub-2 |
| 4 | hero-detail |
top-1-bottom-2 |
python -m src.phase_z2_pipeline samples/mdx_batch/02.mdx imp01_a6_probe_sections --override-layout top-1-bottom-2 --override-section-assignment top=02-1 --override-section-assignment bottom-left=02-2-sub-1 --override-section-assignment bottom-right=02-2-sub-2 |
Per §A.5, no per-layout subdirectory was pre-created by copying a previous run. Each run is an independent pipeline execution. The debug.json produced under data/runs/<run_id>/phase_z2/debug.json is copied (not patched) to the canonical .orchestrator/tmp/imp01_a6_runs/<phase_z_preset>/debug.json location for downstream u3 schema assertion.
Prior round artifact under .orchestrator/tmp/imp01_a6_runs/horizontal-2/ was deleted in this round — it was a 2-zone horizontal-2 (top/bottom) substitute for the contracted sidebar-right topology, which Codex Round #1 review classified as a contract violation. The deletion is purely an artifact-tidy operation; no production source is touched.
B.4 Resolved driver mechanism (per §A.4 classification)
For every one of the four runs (single, vertical-2, left-1-right-2, top-1-bottom-2) the resolved driver mechanism is selenium_manager:
PROJECT_ROOT/chromedriverresolves to a directory (Path.is_file()= False, skipped).PROJECT_ROOT/chromedriver.exeis absent (skipped).webdriver.Chrome(options=options)was invoked; Selenium Manager located/downloaded a compatible driver against installed Chrome148.0.7778.168and the WebDriver session started.- All four runs report
visual_runtime_check.passed = True(see B.5), proving the third candidate in the locked driver chain succeeded — Path A in §A.3, no env-blocker classification triggered. - No resolution-chain divergence observed: the nested
chromedriver/win64/146.0.7680.165/or147.0.7727.117/paths were not consumed (the candidate list atsrc/phase_z2_pipeline.py:3168-3171was not mutated, per the §A.6 no-touch guardrail).
B.5 Per-run measurement summary
| # | Contract topology | Phase Z preset | data/runs/<run_id>/phase_z2/debug.json source |
passed |
len(zone_geometries_px) |
Positions reported |
|---|---|---|---|---|---|---|
| 1 | single-column |
single |
data/runs/imp01_a6_single_probe/phase_z2/debug.json |
True |
1 |
primary |
| 2 | two-column |
vertical-2 |
data/runs/imp01_a6_vertical2/phase_z2/debug.json |
True |
2 |
left, right |
| 3 | sidebar-right |
left-1-right-2 |
data/runs/imp01_a6_sidebar_right/phase_z2/debug.json |
True |
3 |
left, right-top, right-bottom |
| 4 | hero-detail |
top-1-bottom-2 |
data/runs/imp01_a6_probe_sections/phase_z2/debug.json |
True |
3 |
top, bottom-left, bottom-right |
Per-item coordinates captured (formatted via python -c "import json,pathlib; ..."):
[single] primary template=construction_goals_three_circle_intersection x= 50 y= 76 w=1180 h= 585
[vertical-2] left template=construction_goals_three_circle_intersection x= 50 y= 76 w=1166 h= 585
[vertical-2] right template=__empty__ x=1230 y= 76 w= 0 h= 585
[left-1-right-2] left template=construction_goals_three_circle_intersection x= 50 y= 76 w=1166 h= 585
[left-1-right-2] right-top template=__empty__ x=1230 y= 76 w= 0 h= 0
[left-1-right-2] right-bottom template=__empty__ x=1230 y= 90 w= 0 h= 571
[top-1-bottom-2] top template=construction_goals_three_circle_intersection x= 50 y= 76 w=1180 h= 571
[top-1-bottom-2] bottom-left template=__empty__ x= 50 y= 661 w= 583 h= 0
[top-1-bottom-2] bottom-right template=__empty__ x= 647 y= 661 w= 583 h= 0
Observed properties (preserved for u3 to assert formally; not asserted in u2):
- Every item shape matches
{position: str, template_id: str, x: int, y: int, w: int, h: int}(9 distinct items across 4 runs). - All coordinates are integers (no float drift).
- Slide-relative anchoring is consistent with the locked
slideRectbase (left/top page padding ≈ 50/76 px is reproduced across runs). - Empty / partial zones (
__empty__template,h=0orw=0) still produce well-formed bbox entries — the empty-zone fallback is the bbox itself reading0, not the[]fallback (the[]fallback is reserved for theinit_failedcase in §A.3 Path B, which did not trigger here). - Coverage status (
PASSfor runs 2-3,PARTIAL_COVERAGEfor runs 1 and 4 due to--override-section-assignmentfiltering subsections) is orthogonal to thezone_geometries_pxaxis: even in the partial-coverage runs, every zone in the active layout emits a geometry entry.
B.6 What u2 does NOT claim
- No schema assertion (that is u3's scope).
- No no-drift evidence at
src/phase_z2_pipeline.pyanchors (that is u4's scope). - No pytest baseline capture (that is u5's scope).
- No
verifiedlabel promotion (only after all u1–u5 complete and Codex confirms). - The
[]no-runtime fallback (§A.3 Path B) was not exercised at HEAD on this host. It remains a code-path claim grounded insrc/phase_z2_pipeline.pyStep 14 / Step 21 source (lock §A.1), not a runtime-observed branch in this round.
§C — Schema assertion (unit u3)
C.1 Locked schema (re-stated for assertion)
Bound by Stage 1 lock + Stage 2 contract schema_locked block. The assertions in §C must hold for every artifact under .orchestrator/tmp/imp01_a6_runs/<phase_z_preset>/debug.json produced in u2. Any divergence → Stage 1 rewind (no artifact patching).
| Axis | Locked value | Source-grounded in |
|---|---|---|
| Surface location | debug.json top level |
src/phase_z2_pipeline.py:4530 (key added to the debug dict that becomes the file content at :4536) |
| Top-level type | list (JSON array) |
src/phase_z2_pipeline.py:4530 returns a list (either the runtime-produced list or the [] default) |
| Per-item type | JSON object with exactly 6 keys | src/phase_z2_pipeline.py:3232-3239 — single push({position, template_id, x, y, w, h}) site |
| Per-item key set | {position, template_id, x, y, w, h} |
same |
position type |
string | src/phase_z2_pipeline.py:3224 (`z.getAttribute('data-zone-position') |
template_id type |
string | src/phase_z2_pipeline.py:3225 (`z.getAttribute('data-template-id') |
x / y / w / h type |
integer (no float, no bool) | src/phase_z2_pipeline.py:3235-3238 — every coord is Math.round(...), which in JSON round-trips as integer when the rounded value is whole |
| Coordinate space | slide-relative (zone bbox − slide bbox) | src/phase_z2_pipeline.py:3208 (slideRect = slide.getBoundingClientRect()) + :3235-3238 (zoneRect.left - slideRect.left etc.) |
| Coordinate units | CSS pixels (rounded) | getBoundingClientRect() returns CSS-px DOM rect; Math.round is rounding mode |
| Envelope (slide = 1280×720) | 0 ≤ x ≤ 1280, 0 ≤ y ≤ 720, 0 ≤ x+w ≤ 1281, 0 ≤ y+h ≤ 721 |
slideM.size_correct check at :3206 guarantees slide is 1280×720 when runtime succeeds; the ≤+1 tolerance follows §A.4 "code regression" classification |
| No-runtime fallback | [] (empty list) |
src/phase_z2_pipeline.py:4530 — (visual_runtime_check or {}).get("zone_geometries_px", []) returns [] when visual_runtime_check is None OR dict lacks the key |
C.2 Assertion harness (deterministic, in-doc; no production import)
The assertion is run via a single inline python -c invocation against each of the four artifacts produced in u2. Working directory D:\ad-hoc\kei\design_agent (PROJECT_ROOT). Interpreter C:\Users\User\AppData\Local\Programs\Python\Python313\python.exe.
import json, pathlib
LOCKED_KEYS = {"position", "template_id", "x", "y", "w", "h"}
INT_KEYS = {"x", "y", "w", "h"}
STR_KEYS = {"position", "template_id"}
SLIDE_W, SLIDE_H = 1280, 720
ENVELOPE_TOL = 1 # ≤ slide + 1 px (per §A.4 / §C.1 envelope row)
for r in ["single", "vertical-2", "left-1-right-2", "top-1-bottom-2"]:
p = pathlib.Path(".orchestrator/tmp/imp01_a6_runs") / r / "debug.json"
d = json.loads(p.read_text(encoding="utf-8"))
# C.2.a — top-level surface
assert "zone_geometries_px" in d, f"{r}: missing top-level zone_geometries_px"
zg = d["zone_geometries_px"]
assert isinstance(zg, list), f"{r}: zone_geometries_px is not list"
# C.2.b — per-item shape + types
for it in zg:
assert set(it.keys()) == LOCKED_KEYS, f"{r}/{it.get('position')}: key drift {set(it.keys())}"
for k in STR_KEYS:
assert isinstance(it[k], str), f"{r}/{it.get('position')}: {k} is not str"
for k in INT_KEYS:
v = it[k]
assert isinstance(v, int) and not isinstance(v, bool), f"{r}/{it.get('position')}: {k} is not int"
# C.2.c — slide-relative envelope (slide = 1280×720, +1px round-tolerance)
x, y, w, h = it["x"], it["y"], it["w"], it["h"]
assert 0 <= x <= SLIDE_W + ENVELOPE_TOL, f"{r}/{it['position']}: x={x} out of [0,{SLIDE_W+ENVELOPE_TOL}]"
assert 0 <= y <= SLIDE_H + ENVELOPE_TOL, f"{r}/{it['position']}: y={y} out of [0,{SLIDE_H+ENVELOPE_TOL}]"
assert w >= 0, f"{r}/{it['position']}: w={w} negative"
assert h >= 0, f"{r}/{it['position']}: h={h} negative"
assert x + w <= SLIDE_W + ENVELOPE_TOL, f"{r}/{it['position']}: x+w={x+w} out of [0,{SLIDE_W+ENVELOPE_TOL}]"
assert y + h <= SLIDE_H + ENVELOPE_TOL, f"{r}/{it['position']}: y+h={y+h} out of [0,{SLIDE_H+ENVELOPE_TOL}]"
print("ALL_SCHEMA_ASSERTIONS_PASS")
C.3 Assertion result (executed against u2 artifacts at HEAD 4e281a2)
Stdout (verbatim, expected single line on success):
ALL_SCHEMA_ASSERTIONS_PASS
Observed: ALL_SCHEMA_ASSERTIONS_PASS (9 items audited across 4 artifacts: single 1 item, vertical-2 2 items, left-1-right-2 3 items, top-1-bottom-2 3 items). Zero AssertionError raised. Zero divergence triggers.
C.4 Per-item assertion outcomes (9 items × 5 axes)
| Run | Position | Key set | position/template_id str |
x,y,w,h int (no bool) |
Envelope (≤ slide+1) | Slide-relative origin |
|---|---|---|---|---|---|---|
single |
primary |
PASS | PASS | PASS | PASS (x+w=1230, y+h=661) |
PASS (x=50, y=76) |
vertical-2 |
left |
PASS | PASS | PASS | PASS (x+w=1216, y+h=661) |
PASS (x=50, y=76) |
vertical-2 |
right |
PASS | PASS | PASS | PASS (x+w=1230, y+h=661) |
PASS (x=1230, y=76) |
left-1-right-2 |
left |
PASS | PASS | PASS | PASS (x+w=1216, y+h=661) |
PASS (x=50, y=76) |
left-1-right-2 |
right-top |
PASS | PASS | PASS | PASS (x+w=1230, y+h=76) |
PASS (x=1230, y=76) |
left-1-right-2 |
right-bottom |
PASS | PASS | PASS | PASS (x+w=1230, y+h=661) |
PASS (x=1230, y=90) |
top-1-bottom-2 |
top |
PASS | PASS | PASS | PASS (x+w=1230, y+h=647) |
PASS (x=50, y=76) |
top-1-bottom-2 |
bottom-left |
PASS | PASS | PASS | PASS (x+w=633, y+h=661) |
PASS (x=50, y=661) |
top-1-bottom-2 |
bottom-right |
PASS | PASS | PASS | PASS (x+w=1230, y+h=661) |
PASS (x=647, y=661) |
All 9 items × 5 schema axes = 45 atomic assertions PASS. No item shape exceeds the locked key set; no coordinate is float, bool, negative, or beyond slide envelope.
C.5 Artifact byte-binding (SHA256, audited at u3 time)
The assertion runs against the exact artifact bytes shown below. If a subsequent re-execution mutates these bytes without re-running u2 + re-asserting u3, the binding is broken and the document is invalid.
| Artifact | Bytes | SHA256 |
|---|---|---|
.orchestrator/tmp/imp01_a6_runs/single/debug.json |
102220 | f8162bdb1c86f04c6b7202b4d71e022456c6278ac1b2a1081eef1cdf13168da6 |
.orchestrator/tmp/imp01_a6_runs/vertical-2/debug.json |
108608 | 66eadcb5cb2cbff663365eddcab2b5eb90bcbe1999c4d52c48eb99a17beaa5e3 |
.orchestrator/tmp/imp01_a6_runs/left-1-right-2/debug.json |
153072 | 37ea4e09c09bc1a4e4ff39a9ac9fabd0c1139733a62f27ec09b04255418424c6 |
.orchestrator/tmp/imp01_a6_runs/top-1-bottom-2/debug.json |
153107 | a12a07fd0bd0cda59c49f8bd1a1bc592e2536a0572b70c44acc683a4c2c99857 |
(Per u2 §B.3, each file is byte-identical to its data/runs/<run_id>/phase_z2/debug.json source — no patching, no schema mutation.)
C.6 [] no-runtime fallback — source-grounded claim (not runtime-observed this round)
The locked schema permits zone_geometries_px = [] as the fallback when Step 14 does not produce a zone_geometries_px field. The fallback path is grounded in src/phase_z2_pipeline.py:4530:
"zone_geometries_px": (visual_runtime_check or {}).get("zone_geometries_px", []),
(visual_runtime_check or {}).get("zone_geometries_px", []) returns [] in two cases:
visual_runtime_check is None— Step 14 did not run (e.g.,run_overflow_checkwas skipped because Step 14 was not invoked on a given fallback path).visual_runtime_check is dictbut the key is absent — Step 14 returned an error envelope (e.g.,{"passed": False, "error": "selenium init failed: ..."}— see §A.4 env-blocker path / §A.1 step 4) where the inline JS in_MEASURE_SCRIPTnever executed, so the JS return value ({slide, slide_body, zones, frame_slot_metrics, zone_geometries_px, image_events, table_events}from:3403) was never produced.
Both paths emit a list ([]) and therefore PASS C.2.a + C.2.b vacuously (no items to iterate). On this host at HEAD 4e281a2, all four u2 runs succeeded with visual_runtime_check.passed = True (per B.4 / B.5), so the [] branch was not triggered and the runtime-observed evidence comes from non-empty lists. The fallback claim therefore remains source-grounded, not runtime-observed, in u3 (and is consistent with B.6 disclosure).
C.7 Divergence → Stage 1 rewind rules (no artifact patching)
If, on any future re-run or on a different host, the assertion harness in C.2 raises any AssertionError, the failure MUST be classified per §A.4 and Stage 2 contract schema_locked:
| Divergence | Classification | Action |
|---|---|---|
Top-level zone_geometries_px missing while visual_runtime_check.passed=True |
code regression | Stage 1 rewind (do NOT patch debug.json) |
Per-item key set ≠ {position, template_id, x, y, w, h} |
code regression | Stage 1 rewind |
Any x/y/w/h is float or bool (not int) |
code regression | Stage 1 rewind |
Any x/y/w/h exceeds slide envelope (+1 px tolerance) |
code regression | Stage 1 rewind |
Top-level zone_geometries_px = [] while visual_runtime_check.passed=True and a non-empty zones array exists |
code regression | Stage 1 rewind |
Top-level zone_geometries_px = [] while visual_runtime_check.passed=False (e.g., selenium init failed) |
env-blocker (PASS for schema axis — fallback is the locked behaviour) | record env-blocker; schema check PASS vacuously |
C.8 What u3 does NOT claim
- No runtime invocation (u3 reuses u2 artifacts; u2 owns the runtime axis).
- No no-drift evidence at
src/phase_z2_pipeline.pyanchors (that is u4's scope). - No pytest baseline capture (that is u5's scope).
- No claim about behaviour at HEADs other than
4e281a2. - No claim about
image_events/table_eventsschemas (IMP-15 axis, out of scope).
§D — No-drift guardrail (unit u4)
D.1 Locked anchor verbatim Read at HEAD 4e281a2
Each locked anchor span is read verbatim from src/phase_z2_pipeline.py and bound to the Stage 1 / Stage 2 contract. Any future divergence at these line numbers (semantic, not whitespace) is a code regression per §A.4 and triggers Stage 1 rewind.
src/phase_z2_pipeline.py:3207-3208
3207: // A-6 (IMP-01 #1) — slide-relative bbox base
3208: const slideRect = slide.getBoundingClientRect();
src/phase_z2_pipeline.py:3214
3214: const zone_geometries_px = [];
src/phase_z2_pipeline.py:3230-3239
3230: // A-6 (IMP-01 #1) — zone bbox in slide-relative px (additive trace, no layout side effect)
3231: const zoneRect = z.getBoundingClientRect();
3232: zone_geometries_px.push({
3233: position: pos,
3234: template_id: tid,
3235: x: Math.round(zoneRect.left - slideRect.left),
3236: y: Math.round(zoneRect.top - slideRect.top),
3237: w: Math.round(zoneRect.width),
3238: h: Math.round(zoneRect.height),
3239: });
src/phase_z2_pipeline.py:3403
3403: return { slide: slideM, slide_body: bodyM, zones, frame_slot_metrics, zone_geometries_px, image_events, table_events };
src/phase_z2_pipeline.py:4529-4530
4529: # A-6 (IMP-01 #1) — additive top-level zone bbox trace (slide-relative px)
4530: "zone_geometries_px": (visual_runtime_check or {}).get("zone_geometries_px", []),
These line numbers match the Stage 1 / Stage 2 contract anchors exactly. No off-by-line drift.
D.2 Semantic grep — zone_geometries_px occurrences in production source
Grep over src/phase_z2_pipeline.py for the identifier zone_geometries_px returns exactly 4 occurrences, all inside the locked anchor spans:
| # | Line | Span name | Role |
|---|---|---|---|
| 1 | :3214 |
D.1 :3214 | Step 14 JS — empty list init |
| 2 | :3232 |
D.1 :3230-3239 | Step 14 JS — per-zone push site |
| 3 | :3403 |
D.1 :3403 | Step 14 JS — return tuple member |
| 4 | :4530 |
D.1 :4529-4530 | Step 21 Python — debug dict surface |
Zero additional occurrences outside the locked anchors. No new export site, no second producer, no second consumer. The Stage 1 lock "additive top-level field on debug.json only" remains intact at HEAD 4e281a2.
D.3 Scoped guardrail grep — kei|frame_selection|V4|ai_redesigner within locked spans
The locked spans (D.1) are searched line-by-line for the regex kei|frame_selection|V4|ai_redesigner (case-insensitive). Expected = 0 hits (the bbox export must be orthogonal to AI / Kei / V4 / frame_selection / ai_redesigner subsystems).
| Span | Lines audited | Hits |
|---|---|---|
:3207-3208 |
2 | 0 (EMPTY) |
:3214 |
1 | 0 (EMPTY) |
:3230-3239 |
10 | 0 (EMPTY) |
:3403 |
1 | 0 (EMPTY) |
:4529-4530 |
2 | 0 (EMPTY) |
| TOTAL | 16 | 0 |
Observed TOTAL_HITS=0 matches the expected value. No subsystem coupling introduced at any locked anchor. The bbox export is mechanically isolated from AI / V4 / frame_selection / ai_redesigner code paths at the syntactic layer.
D.4 Git blame provenance — line-by-line commit attribution
git blame on each locked span confirms the implementation commit boundary:
| Span | Lines | Blame commit | Commit subject | Drift verdict |
|---|---|---|---|---|
:3207-3208 |
2 | 1dc81e0 |
feat(step14+step21): add zone_geometries_px artifact (IMP-01 #1) |
unchanged since IMP-01 #1 |
:3214 |
1 | 1dc81e0 |
same | unchanged since IMP-01 #1 |
:3230-3239 |
10 | 1dc81e0 |
same | unchanged since IMP-01 #1 |
:3403 |
1 | 28276228 |
feat(IMP-16): Step 14 table_self_overflow detection |
additive append, zone_geometries_px member preserved verbatim — see D.4.a |
:4529-4530 |
2 | 1dc81e0 |
feat(step14+step21): add zone_geometries_px artifact (IMP-01 #1) |
unchanged since IMP-01 #1 |
D.4.a :3403 audit — IMP-16 (#46) additive append, no drift on zone_geometries_px
git show 28276228 -- src/phase_z2_pipeline.py for the return-tuple line:
- return { slide: slideM, slide_body: bodyM, zones, frame_slot_metrics, zone_geometries_px, image_events };
+ return { slide: slideM, slide_body: bodyM, zones, frame_slot_metrics, zone_geometries_px, image_events, table_events };
The single semantic change is , table_events appended after image_events. The zone_geometries_px member sits in the same position in the tuple before and after (4th member), in the same syntactic role (object shorthand property), and with the same identifier. This is the IMP-15 (#48) / IMP-16 (#46) additive append pattern already locked at Stage 1 (additive only, never mutating the IMP-01 surface). It is NOT a drift on the zone_geometries_px axis.
Cross-reference: .orchestrator/issues/48_stage_final-close_exit.md:22 records the IMP-15 / IMP-16 surfacing pattern as "mirroring zone_geometries_px :2739 precedent" — the additivity is the intentional contract, not a regression.
D.5 Working-tree no-touch verification (this round)
git diff -- src/phase_z2_pipeline.py at the moment of §D authorship returns 0 bytes of diff output (verified). No staged, no unstaged, no working-tree edit to the production source in this Stage 3 round. All §D evidence is read-only audit; no production-source edit, no anchor mutation, no schema mutation. Stage 2 guardrail "Zero production-source edit" remains intact.
D.6 What u4 does NOT claim
- No runtime invocation (u2 owns the runtime axis; u3 owns the schema-assertion axis).
- No claim about behaviour at commits other than HEAD
4e281a2(the working-tree state at §D authorship). A future commit can rebind the anchors; u4 evidence is HEAD-pinned. - No claim about
image_events/table_eventsschemas (IMP-15 / IMP-16 axes, separate issues #45 / #46 / #48). - No claim about non-production source (
Front_test/,Front_test_v515/, etc.). Those are vendored / archived copies and are out of the locked anchor scope. - No pytest baseline capture (that is u5's scope).
§E — Baseline gate (unit u5)
E.1 Capture invocation
Working directory: D:\ad-hoc\kei\design_agent (PROJECT_ROOT).
Interpreter: C:\Users\User\AppData\Local\Programs\Python\Python313\python.exe (Python 3.13.1).
Command (verbatim): python -m pytest -q tests 2>&1 | tee .orchestrator/tmp/imp01_a6_runs/pytest_baseline.txt.
Captured at: HEAD 4e281a2, Stage 3 Round #3 (after u3/u4 doc-only edits, no production-source mutation).
Artifact: .orchestrator/tmp/imp01_a6_runs/pytest_baseline.txt (287 lines, UTF-8 via tee).
E.2 Summary line (verbatim from pytest_baseline.txt:287)
7 failed, 1622 passed in 360.92s (0:06:00)
E.3 Enumerated failures (verbatim from short test summary info block)
| # | Test ID | Test file |
|---|---|---|
| F1 | test_line_586_references_imp17_not_imp31 |
tests/orchestrator_unit/test_imp17_comment_anchor.py |
| F2 | test_line_587_references_imp47b_supersession |
tests/orchestrator_unit/test_imp17_comment_anchor.py |
| F3 | test_post_89a_flag_off_final_html_sha_matches_frozen_baseline[01.mdx] |
tests/regression/test_b4_mapper_source_sha_parity.py |
| F4 | test_post_89a_flag_off_final_html_sha_holistic_sweep |
tests/regression/test_b4_mapper_source_sha_parity.py |
| F5 | test_rank_1_non_direct_promotes_rank_2 |
tests/test_phase_z2_v4_fallback.py |
| F6 | test_duplicate_template_id_is_skipped_rank_3_wins |
tests/test_phase_z2_v4_fallback.py |
| F7 | test_restructure_reject_preserved_as_non_direct_evidence |
tests/test_phase_z2_v4_fallback.py |
E.4 IMP-01 keyword orthogonality check
Regex zone_geometries|IMP-01|step.?14|step.?21|slideRect|imp01 (case-insensitive) over each failing test file body:
| Test file | Hits |
|---|---|
tests/orchestrator_unit/test_imp17_comment_anchor.py |
0 |
tests/regression/test_b4_mapper_source_sha_parity.py |
0 |
tests/test_phase_z2_v4_fallback.py |
0 |
| TOTAL | 0 |
None of the failing test files reference the IMP-01 zone_geometries_px axis, the locked Step 14 / Step 21 anchors, or slideRect. The keyword orthogonality axis passes (zero hits).
E.5 git log provenance per failure (axis classification)
| # | Failure axis | Owner commit (most recent change to file) | Owner subject | Axis ≠ IMP-01? |
|---|---|---|---|---|
| F1, F2 | IMP-17 / IMP-35 / IMP-47B line-anchor pinning for src/phase_z2_pipeline.py route-hint table (lines 586/587). File header docstring: "Anchor re-pin (2026-05-23, IMP-35 u1/u5/u7 / Gitea #64 Stage 3): IMP-35 added a single-line compose_zone_popup_payload import (u7) plus a 7-line run_step17_popup_gate import block (u5) ahead of the route-hint table, totaling +8 lines". Failure suggests subsequent line drift past 586/587 (post-IMP-35 commit). |
f3ef4d9 feat(#64): IMP-35 details_popup_escalation u1~u10 + Stage 3 R7 anchor re-pin |
IMP-35 anchor re-pin | YES — IMP-17 / IMP-35 / IMP-47B route-hint anchors, not IMP-01 zone_geometries_px. |
| F3, F4 | IMP-89 89-a B4-mapper-source SHA parity guard — asserts final.html SHA == pre-89-a frozen baseline. Failure means a subsequent feature commit changed HTML bytes (likely IMP-45 #74 slide_overrides.css injector or IMP-55 #93 manual section swap detection). |
b1bbe27 feat(#89): IMP-89 89-a u1~u5 Layer A render path activation |
IMP-89 89-a B4-mapper source-of-truth switch | YES — IMP-89 HTML SHA baseline regression, not IMP-01 bbox export. |
| F5, F6, F7 | V4 fallback / dedup / candidate evidence semantics — rank-1 reject promotion to rank-2/3, duplicate template_id skip, restructure/reject preservation in candidate evidence. Origin: IMP-05 V4 candidate bridge; most recent commit: IMP-47B #76 reject-as-AI-adaptation. | 1186ad8 feat(#76): IMP-47B reject-as-AI-adaptation activation (u1~u13 backend + tests) |
IMP-47B V4 fallback semantics | YES — IMP-05 / IMP-47B V4 fallback axis, not IMP-01 Step 14 inline JS. |
All 7 failures map cleanly to non-IMP-01 owner commits (f3ef4d9 / b1bbe27 / 1186ad8). The IMP-01 implementation commit (1dc81e0) does not appear in the owner list, and none of the failing test bodies reference IMP-01 / zone_geometries_px / Step 14 / Step 21 / slideRect anchors.
E.6 Cross-reference against Stage 2 baseline (pytest_stage2_codex_r1.txt)
Stage 2 baseline tail (UTF-16 LE, decoded):
FAILED tests/orchestrator_unit/test_imp17_comment_anchor.py::test_line_586_references_imp17_not_imp31
FAILED tests/orchestrator_unit/test_imp17_comment_anchor.py::test_line_587_references_imp47b_supersession
FAILED tests/regression/test_b4_mapper_source_sha_parity.py::test_post_89a_flag_off_final_html_sha_matches_frozen_baseline[01.mdx]
FAILED tests/regression/test_b4_mapper_source_sha_parity.py::test_post_89a_flag_off_final_html_sha_holistic_sweep
FAILED tests/test_phase_z2_v4_fallback.py::test_rank_1_non_direct_promotes_rank_2
FAILED tests/test_phase_z2_v4_fallback.py::test_duplicate_template_id_is_skipped_rank_3_wins
FAILED tests/test_phase_z2_v4_fallback.py::test_restructure_reject_preserved_as_non_direct_evidence
7 failed, 1622 passed in 364.29s (0:06:04)
The failure SET (7 IDs) and the pass/fail counts (7 failed, 1622 passed) are byte-identical between Stage 2 baseline and Stage 3 u5 capture. Only the elapsed time differs (364.29s vs 360.92s — host noise, not test outcome). No new failure introduced by Stage 3 u1~u4 doc-only edits (consistent with §D.5 working-tree no-touch on src/phase_z2_pipeline.py).
E.7 Stage 2 contract gate evaluation
Per Stage 2 EXIT REPORT baseline_tests: "pytest -q tests (full suite) captured as artifact; non-orthogonal failure rewinds to Stage 1."
- All 7 failures are ORTHOGONAL to IMP-01 axis (§E.4 zero hits + §E.5 owner-commit classification).
- All 7 failures are PREEXISTING at Stage 2 baseline (§E.6 byte-identical failure set).
- Stage 3 u1~u5 made ZERO production-source edits (§D.5 confirmed
git diff -- src/phase_z2_pipeline.py= 0 bytes).
Verdict: BASELINE_GATE_PASS — no Stage 1 rewind triggered by pytest baseline.
E.8 What u5 does NOT claim
- u5 does not certify the 7 preexisting failures as resolved. Each axis (IMP-35 anchor drift, IMP-89 SHA baseline regression, IMP-47B V4 fallback semantics) has its own issue / verification scope and is out of IMP-01 scope.
- u5 does not assert the pytest pass count will remain stable across future commits. The 1622-passed snapshot is HEAD-pinned at
4e281a2. - u5 does not modify any failing test or any production source. The baseline is captured-as-is.
E.9 Follow-up issue candidates (orthogonal axes surfaced)
- F1, F2 — IMP-17 / IMP-35 line-anchor drift may require another anchor re-pin (lines 586/587 may have shifted again past Round #1's pin point). Owner: separate issue under #64 / IMP-35 follow-up. NOT bundled into IMP-01 #1.
- F3, F4 — IMP-89 89-a frozen baseline may need re-capture if a subsequent feature commit (IMP-45 / IMP-55) intentionally changed final.html bytes. Owner: separate issue under #89 follow-up. NOT bundled into IMP-01 #1.
- F5, F6, F7 — V4 fallback rank promotion / dedup semantics changed by IMP-47B #76. Owner: separate issue under #76 / IMP-47B follow-up. NOT bundled into IMP-01 #1.