- src: phase_z2 composition/mapper/pipeline/placement_planner/retry, ai_fallback(prompts/schema/validate), mdx_text_atoms 신규 - Front: PipelineTracePanel 신규, FramePanel/SlideCanvas/Home/designAgentApi 등 갱신 + 테스트 4종 추가 - templates/phase_z2: catalog(component_expansion_registry, node_slot_mapping 신규), frames, families, slide_base 갱신 - tests/matching: phase2~26 매칭 실험 스크립트·리포트·온톨로지 전체 (미커밋 진행분) - tests: b4_v4 evidence, task5~28.5 시리즈, regression(imp95 baseline) 등 신규 테스트 대량 추가 - docs/reference: MDX 구조 인벤토리, MDX→Frame 구조 계약 문서 - scripts: mdx 계약/parity/coverage/viewport 체크, gitea comment, run sync 유틸 - .gitignore: tmp*.json, chromedriver, .orchestrator, *.pkl, Front_test* 등 임시/스냅샷 제외 미완성 작업의 보존용 스냅샷 커밋 (2026-07-02) Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
191 lines
7.2 KiB
Python
191 lines
7.2 KiB
Python
"""IMP-95 u8 — capture final.html SHA baseline via the FULL Phase Z pipeline
|
|
under PHASE_Z_B4_V4_EVIDENCE=OFF (default) for mdx 01/02/04/05.
|
|
|
|
Runs ``src.phase_z2_pipeline.run_phase_z2_mvp1`` end-to-end for each mdx file
|
|
in the Stage 2 u8 scope (01/02/04/05; mdx 03 is excluded per the ``mdx 03
|
|
정비`` lock). Each run writes a real ``final.html`` to disk at the production
|
|
write site ``src/phase_z2_pipeline.py:5994-5996``; bytes are IMP-94-normalized
|
|
and SHA-256 hashed, then frozen to
|
|
``tests/regression/fixtures/imp95_pre_baseline_sha.json``.
|
|
|
|
The accompanying regression test
|
|
``tests/regression/test_b4_v4_evidence_off_sha_parity.py`` re-runs the same
|
|
pipeline shape under flag OFF and asserts SHA equality with the frozen
|
|
values. Under flag OFF, IMP-95 (u1~u7) is a strict no-op for ``final.html``
|
|
bytes — see the trace-only docstring at ``src/phase_z2_pipeline.py:86`` —
|
|
so the captured baseline IS the immediate pre-IMP-95 reference even when
|
|
captured with IMP-95 code already in tree.
|
|
|
|
Reason for a separate baseline (vs. reusing 89a_pre_baseline_sha.json)
|
|
======================================================================
|
|
|
|
The 89a baseline was captured at HEAD ``6e9e3ee`` before subsequent
|
|
working-tree changes accumulated (e.g. the Emergency P3/P4/P4b verbatim
|
|
slot_payload builders), so the 89a SHAs no longer match the current
|
|
flag-OFF pipeline output for mdx 02/04/05 — a pre-existing baseline
|
|
drift orthogonal to IMP-95. A fresh baseline keyed to the current
|
|
flag-OFF state isolates IMP-95's u8 regression axis from that upstream
|
|
drift; the 89a baseline remains the load-bearing guard for the 89-a
|
|
axis.
|
|
|
|
IMP-94 marker normalization
|
|
===========================
|
|
|
|
IMP-94 ``data-region-id`` / ``data-content-unit-id`` tokens (stamped at
|
|
``src/region_marker_stamper.py:131-135``) are stripped before hashing.
|
|
The strip is anchored on ``leading space + attr token`` shape and is
|
|
disjoint from the IMP-96 ``data-frame-slot-id`` axis by attribute name.
|
|
|
|
Run from repo root::
|
|
|
|
python tests/regression/scripts/capture_imp95_pre_baseline.py
|
|
|
|
Idempotent. Re-run only when an upstream mapper/render/template delta is
|
|
reviewed and accepted as the new pre-IMP-95 reference. Refuses to run
|
|
with PHASE_Z_B4_V4_EVIDENCE enabled (the flag-ON state is not the
|
|
baseline axis).
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
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", "04.mdx", "05.mdx") # Stage 2 u8 scope
|
|
_OUT_PATH = (
|
|
_REPO_ROOT
|
|
/ "tests"
|
|
/ "regression"
|
|
/ "fixtures"
|
|
/ "imp95_pre_baseline_sha.json"
|
|
)
|
|
|
|
# IMP-94 additive marker strip patterns — must stay in sync with
|
|
# tests/regression/test_b4_v4_evidence_off_sha_parity.py.
|
|
_STRIP_REGION_ID_RE = re.compile(rb' data-region-id="[^"]*"')
|
|
_STRIP_CONTENT_UNIT_ID_RE = re.compile(rb' data-content-unit-id="[^"]*"')
|
|
|
|
|
|
def _strip_imp94_markers(raw_bytes: bytes) -> bytes:
|
|
stripped = _STRIP_REGION_ID_RE.sub(b"", raw_bytes)
|
|
stripped = _STRIP_CONTENT_UNIT_ID_RE.sub(b"", stripped)
|
|
return stripped
|
|
|
|
|
|
def _capture_one(mdx_file: str, runs_root: Path) -> dict:
|
|
mdx_path = _SAMPLES_DIR / mdx_file
|
|
assert mdx_path.exists(), f"sample missing: {mdx_path}"
|
|
|
|
run_id = f"imp95_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}"
|
|
normalized_bytes = _strip_imp94_markers(raw_bytes)
|
|
|
|
return {
|
|
"mdx_file": mdx_file,
|
|
"run_id": run_id,
|
|
"final_html_size_bytes": len(normalized_bytes),
|
|
"sha256": hashlib.sha256(normalized_bytes).hexdigest(),
|
|
"pipeline_exit_code": pipeline_exit_code,
|
|
}
|
|
|
|
|
|
def capture() -> dict:
|
|
assert os.environ.get("PHASE_Z_B4_V4_EVIDENCE", "") == "", (
|
|
"PHASE_Z_B4_V4_EVIDENCE must be unset when capturing baseline "
|
|
"(default-OFF state is the production-equivalent axis for u8). "
|
|
"Refusing to run with the flag enabled."
|
|
)
|
|
assert os.environ.get("PHASE_Z_B4_MAPPER_SOURCE", "") == "", (
|
|
"PHASE_Z_B4_MAPPER_SOURCE must also be unset — the u8 baseline "
|
|
"is captured under both flags OFF (full pre-IMP-89/95 path)."
|
|
)
|
|
|
|
_OUT_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
with tempfile.TemporaryDirectory(prefix="imp95_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": 1,
|
|
"axis": (
|
|
"IMP-95 u8 — final.html SHA baseline captured via FULL "
|
|
"run_phase_z2_mvp1 pipeline under PHASE_Z_B4_V4_EVIDENCE=OFF "
|
|
"and PHASE_Z_B4_MAPPER_SOURCE=OFF (defaults)"
|
|
),
|
|
"description": (
|
|
"Frozen SHA-256 of `final.html` bytes (production write site "
|
|
"src/phase_z2_pipeline.py:5994-5996) for mdx 01/02/04/05 "
|
|
"under PHASE_Z_B4_V4_EVIDENCE OFF. Under flag OFF, IMP-95 "
|
|
"(u1~u7) is strictly no-op for final.html (planner branch "
|
|
"falls through to legacy _select_frame at u3; u4/u5/u6 "
|
|
"additive telemetry confined to placement_trace per the "
|
|
"trace-only docstring at src/phase_z2_pipeline.py:86). The "
|
|
"u8 regression test asserts SHA equality with these frozen "
|
|
"values, so any future code change that drifts the flag-OFF "
|
|
"render output produces a mismatch and breaks the test. "
|
|
"mdx 03 is excluded per Stage 2 u8 scope (mdx 03 정비 LOCK). "
|
|
"Regenerate only when an upstream delta is reviewed and "
|
|
"accepted as the new pre-IMP-95 reference."
|
|
),
|
|
"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()
|