Files
C.E.L_Slide_test2/tests/regression/scripts/capture_89a_pre_baseline.py
kyeongmin b1bbe27c38 feat(#89): IMP-89 89-a u1~u5 Layer A render path activation (B4→mapper source-of-truth switch, default-OFF flag)
PHASE_Z_B4_MAPPER_SOURCE env flag (default OFF) switches slot_payload
source-of-truth from legacy mapper-only / V4 rank-1 to B4 PlacementPlan
.selected_template_id at the single switch site in the runtime loop.
OFF preserves final.html SHA byte-equivalence (u4 parity guard, mdx 01-05).
ON requires Layer A render-active path; BLOCKED exits on B4 no-cover
and on B4-selected FitError (IMP-87 honesty gate pattern — NO silent
fallback). Distinct from PHASE_Z_B4_GATEKEEPER (mismatch render-skip).

Units (1 commit = 1 axis per Stage 1 scope_lock):
  u1 — _b4_mapper_source_enabled() flag reader (default OFF)
  u2 — _select_mapper_template_id() selector wired at the switch site
  u3 — _b4_mapper_source_blocked_exit() for b4_no_cover / b4_selected_fit_error
  u4 — render SHA parity regression (tests/regression/ baseline mdx 01-05)
  u5 — slot_payload byte-equivalence (matches_mapper=True axis, mdx 01-05)

Targeted 89-a suite 63 PASS; Phase Z regression 323 PASS; IMP-87 mirror
20 PASS. Demo activation via .env only (no vite.config hardcoding).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-24 00:33:28 +09:00

169 lines
6.6 KiB
Python

"""IMP-89 89-a u4 — capture final.html SHA baseline via the FULL Phase Z pipeline.
Runs ``src.phase_z2_pipeline.run_phase_z2_mvp1`` end-to-end for every mdx file
in ``samples/mdx_batch/`` (01-05) under PHASE_Z_B4_MAPPER_SOURCE=OFF (default).
Each run writes a real ``final.html`` to disk at
``<RUNS_DIR>/<run_id>/phase_z2/final.html`` — exactly the production write
site at ``src/phase_z2_pipeline.py:5994-5996``. The bytes of that on-disk
artifact are SHA-256 hashed and stored in
``tests/regression/fixtures/89a_pre_baseline_sha.json``.
The u4 regression test in ``tests/regression/test_b4_mapper_source_sha_parity.py``
runs the same pipeline shape under flag OFF, reads the on-disk ``final.html``,
hashes its bytes, and asserts SHA equality with each frozen value. The
mathematical chain that makes this a genuine "pre-89-a baseline" guard:
* Under flag OFF, ``_select_mapper_template_id(plan, T) == T`` for every
``(plan, T)`` pair (locked by u2 + u4 algebraic precondition tests).
* Therefore the mapper input is byte-identical to the legacy pre-89-a call
shape ``map_mdx_to_slots(section, unit.frame_template_id)``.
* Therefore the rendered HTML is byte-identical to pre-89-a output.
* Therefore the on-disk ``final.html`` is byte-identical → SHA matches.
Any future drift — in the selector, mapper, render_slide, slide_base.html,
or any upstream code path — produces a divergent SHA and breaks the test.
Run from repo root::
python tests/regression/scripts/capture_89a_pre_baseline.py
The capture script is idempotent and meant to be re-run only when an
upstream mapper/render/template delta is reviewed and accepted. It refuses
to run with PHASE_Z_B4_MAPPER_SOURCE enabled (the post-89-a flag-ON state
is NOT the baseline axis).
"""
from __future__ import annotations
import hashlib
import json
import os
import sys
import tempfile
from datetime import datetime, timezone
from pathlib import Path
_REPO_ROOT = Path(__file__).resolve().parents[3]
sys.path.insert(0, str(_REPO_ROOT))
sys.path.insert(0, str(_REPO_ROOT / "src"))
import src.phase_z2_pipeline as pz2 # noqa: E402
_SAMPLES_DIR = _REPO_ROOT / "samples" / "mdx_batch"
_MDX_BATCH = ("01.mdx", "02.mdx", "03.mdx", "04.mdx", "05.mdx")
_OUT_PATH = (
_REPO_ROOT / "tests" / "regression" / "fixtures" / "89a_pre_baseline_sha.json"
)
def _capture_one(mdx_file: str, runs_root: Path) -> dict:
"""Run the full pipeline once and hash the on-disk final.html.
``pz2.RUNS_DIR`` MUST be pinned to ``runs_root`` by the caller before
invocation; ``run_phase_z2_mvp1`` writes final.html to
``<pz2.RUNS_DIR>/<run_id>/phase_z2/final.html``.
``SystemExit`` from the pipeline (e.g. IMP-87 EMPTY_SHELL_NO_CONTENT
BLOCKED exit on mdx 05) is caught: the BLOCKED exit fires AFTER the
final.html write at ``src/phase_z2_pipeline.py:5994-5996``, so the
artifact still exists on disk and the SHA is captured. The exit code
is recorded on the entry so the test can assert the same terminal
state under flag OFF. If final.html is missing post-exit, that is a
genuine pipeline failure and the script aborts.
"""
mdx_path = _SAMPLES_DIR / mdx_file
assert mdx_path.exists(), f"sample missing: {mdx_path}"
run_id = f"89a_baseline_{mdx_path.stem}"
pipeline_exit_code: int | None = None
try:
pz2.run_phase_z2_mvp1(mdx_path, run_id=run_id)
except SystemExit as exc:
pipeline_exit_code = (
int(exc.code) if isinstance(exc.code, int) else 1
)
final_html_path = runs_root / run_id / "phase_z2" / "final.html"
assert final_html_path.exists(), (
f"final.html not written by pipeline: {final_html_path} "
f"(pipeline_exit_code={pipeline_exit_code})"
)
raw_bytes = final_html_path.read_bytes()
assert len(raw_bytes) > 0, f"final.html is empty: {final_html_path}"
return {
"mdx_file": mdx_file,
"run_id": run_id,
"final_html_size_bytes": len(raw_bytes),
"sha256": hashlib.sha256(raw_bytes).hexdigest(),
"pipeline_exit_code": pipeline_exit_code,
}
def capture() -> dict:
assert os.environ.get("PHASE_Z_B4_MAPPER_SOURCE", "") == "", (
"PHASE_Z_B4_MAPPER_SOURCE must be unset when capturing baseline "
"(default-OFF state is the production-equivalent axis for u4). "
"Refusing to run with the flag enabled."
)
_OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
with tempfile.TemporaryDirectory(prefix="89a_baseline_") as tmp:
runs_root = Path(tmp)
original_runs_dir = pz2.RUNS_DIR
pz2.RUNS_DIR = runs_root
try:
entries = [_capture_one(mf, runs_root) for mf in _MDX_BATCH]
finally:
pz2.RUNS_DIR = original_runs_dir
return {
"schema_version": 2,
"axis": (
"IMP-89 89-a u4 — final.html SHA baseline captured via FULL "
"run_phase_z2_mvp1 pipeline (flag OFF / default)"
),
"description": (
"Frozen SHA-256 of `final.html` bytes (the artifact written to "
"disk at src/phase_z2_pipeline.py:5994-5996) captured by running "
"the full Phase Z pipeline end-to-end for each mdx 01-05 under "
"PHASE_Z_B4_MAPPER_SOURCE=OFF. Under flag OFF the 89-a selector "
"`_select_mapper_template_id(plan, T)` returns `T` verbatim, so "
"the mapper input is byte-identical to the pre-89-a legacy call "
"shape `map_mdx_to_slots(section, unit.frame_template_id)` — "
"the rendered HTML and therefore the final.html SHA match the "
"pre-89-a baseline. The u4 regression test runs the same "
"pipeline shape under flag OFF and asserts SHA equality. "
"Regenerate only when an upstream mapper/render/template delta "
"is deliberately reviewed and accepted."
),
"captured_at_utc": (
datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
),
"renderer": {
"entrypoint": "src.phase_z2_pipeline.run_phase_z2_mvp1",
"write_site": "src/phase_z2_pipeline.py:5994-5996",
"artifact_relpath": "<RUNS_DIR>/<run_id>/phase_z2/final.html",
},
"mdx_batch": list(_MDX_BATCH),
"mdx_files": {entry["mdx_file"]: entry for entry in entries},
"total_files": len(entries),
}
def main() -> None:
data = capture()
_OUT_PATH.write_text(
json.dumps(data, indent=2, ensure_ascii=False) + "\n",
encoding="utf-8",
)
print(
f"wrote {_OUT_PATH} ({data['total_files']} files: "
f"{', '.join(data['mdx_files'].keys())})"
)
if __name__ == "__main__":
main()