Files
C.E.L_Slide_test2/tests/verification/imp01_a6_runtime_verification.md

39 KiB
Raw Permalink Blame History

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.

  1. PROJECT_ROOT/chromedriver (no extension). Resolved via Path.is_file(). Skipped if it is a directory or absent.
  2. PROJECT_ROOT/chromedriver.exe. Resolved via Path.is_file(). Skipped if absent.
  3. Selenium Manager fallback. Triggered only when neither candidate above resolved. Calls webdriver.Chrome(options=options) with no Service(executable_path=...); Selenium 4 downloads/locates a compatible driver against the installed Chrome binary.
  4. Hard failure path. If all of the above raise, run_overflow_check returns {"passed": False, "error": "selenium init failed: ..."}. No retry, no alternate path.

Verification harness MUST NOT:

  • Install or copy any binary into PROJECT_ROOT/chromedriver or PROJECT_ROOT/chromedriver.exe.
  • Place a chromedriver on PATH for the duration of the run.
  • Mutate _MEASURE_SCRIPT, run_overflow_check, or write_debug_json.
  • Patch debug.json after 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:

  1. PROJECT_ROOT/chromedriverskipped (directory, not file).
  2. PROJECT_ROOT/chromedriver.exeskipped (absent).
  3. Selenium Manager fallback — invoked. Outcome depends on Selenium Manager's ability to obtain a chromedriver compatible with Chrome 148.0.7778.168 from this host. Either:
    • Path A — match success: Selenium Manager downloads or finds a 148.* driver → run_overflow_check proceeds → zone_geometries_px populated.
    • Path B — match failure: Selenium Manager raises (network blocked / cache miss / no compatible driver) → run_overflow_check returns {"passed": False, "error": "selenium init failed: …"} → Step 21 surfaces zone_geometries_px: [] (fallback path).

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 WebDriverException or SessionNotCreatedException citing 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_px missing from top-level debug.json while visual_runtime_check reports passed=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 > 0 AND y > 0 AND (x + w) <= slide_clientWidth + 1 AND (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_ROOT so PROJECT_ROOT/chromedriver and PROJECT_ROOT/chromedriver.exe resolution semantics match production behaviour.
  • Persist the resulting debug.json to .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 u1u5, 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.md fixture 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_px schema 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/chromedriver resolves to a directory (Path.is_file() = False, skipped).
  • PROJECT_ROOT/chromedriver.exe is absent (skipped).
  • webdriver.Chrome(options=options) was invoked; Selenium Manager located/downloaded a compatible driver against installed Chrome 148.0.7778.168 and 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/ or 147.0.7727.117/ paths were not consumed (the candidate list at src/phase_z2_pipeline.py:3168-3171 was 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 slideRect base (left/top page padding ≈ 50/76 px is reproduced across runs).
  • Empty / partial zones (__empty__ template, h=0 or w=0) still produce well-formed bbox entries — the empty-zone fallback is the bbox itself reading 0, not the [] fallback (the [] fallback is reserved for the init_failed case in §A.3 Path B, which did not trigger here).
  • Coverage status (PASS for runs 2-3, PARTIAL_COVERAGE for runs 1 and 4 due to --override-section-assignment filtering subsections) is orthogonal to the zone_geometries_px axis: 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.py anchors (that is u4's scope).
  • No pytest baseline capture (that is u5's scope).
  • No verified label promotion (only after all u1u5 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 in src/phase_z2_pipeline.py Step 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:

  1. visual_runtime_check is None — Step 14 did not run (e.g., run_overflow_check was skipped because Step 14 was not invoked on a given fallback path).
  2. visual_runtime_check is dict but 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_SCRIPT never 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.py anchors (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_events schemas (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_events schemas (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.