docs(#1): IMP-01 A-6 u1~u5 zone_geometries_px runtime verification log (driver chain + 4-topology runs + schema lock + no-drift guardrail + pytest baseline gate; production source untouched, impl at 1dc81e0)
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 20s
Some checks failed
Multi-MDX Regression (IMP-91) / multi-mdx-regression (push) Failing after 20s
This commit is contained in:
499
tests/verification/imp01_a6_runtime_verification.md
Normal file
499
tests/verification/imp01_a6_runtime_verification.md
Normal file
@@ -0,0 +1,499 @@
|
||||
# IMP-01 A-6 — `zone_geometries_px` Runtime Verification Log
|
||||
|
||||
**Issue:** [#1 IMP-01 A-6 Zone DOM coordinate export](https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/1)
|
||||
**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/chromedriver` — **skipped** (directory, not file).
|
||||
2. `PROJECT_ROOT/chromedriver.exe` — **skipped** (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 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.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 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 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') || 'unknown'` — always string) |
|
||||
| `template_id` type | string | `src/phase_z2_pipeline.py:3225` (`z.getAttribute('data-template-id') || '?'` — always string) |
|
||||
| `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`.
|
||||
|
||||
```python
|
||||
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.
|
||||
Reference in New Issue
Block a user