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>
This commit is contained in:
2026-05-24 00:33:28 +09:00
parent 896f273ffa
commit b1bbe27c38
9 changed files with 1434 additions and 1 deletions

View File

@@ -204,6 +204,82 @@ def to_phase_z_status(match: V4Match) -> str:
return V4_LABEL_TO_PHASE_Z_STATUS.get(match.label, "unknown")
def _b4_mapper_source_enabled() -> bool:
"""IMP-89 89-a u1 — PHASE_Z_B4_MAPPER_SOURCE env flag reader (default OFF).
Switches slot_payload source-of-truth from mapper-only (legacy) to B4
PlacementPlan.selected_template_id. Distinct from PHASE_Z_B4_GATEKEEPER
(mismatch render-skip semantics). u2 wires this into the slot_payload
construction site; u3 adds BLOCKED exits for no-cover and FitError under
flag ON. Truthy values: '1', 'true', 'yes' (case-insensitive, trimmed).
"""
return os.environ.get("PHASE_Z_B4_MAPPER_SOURCE", "").strip().lower() in {
"1",
"true",
"yes",
}
def _select_mapper_template_id(
placement_plan, unit_frame_template_id: str
) -> Optional[str]:
"""IMP-89 89-a u2 — slot_payload source-of-truth selector.
Returns the template_id that drives slot_payload construction at the
single switch site in the runtime loop:
flag ON → placement_plan.selected_template_id (B4 PlacementPlan,
Layer A render-active path)
flag OFF → unit_frame_template_id (legacy mapper-only / V4 rank-1;
byte-equivalent default; final.html SHA parity guarded
by u4)
Under flag ON the returned value may be None when B4 found no covering
frame. u3 adds the BLOCKED exit for None and for FitError on the
B4-selected template — NO silent fallback (IMP-87 honesty gate pattern).
"""
if _b4_mapper_source_enabled():
return placement_plan.selected_template_id
return unit_frame_template_id
def _b4_mapper_source_blocked_exit(
reason: str, *, position: str, context: dict
) -> "NoReturn": # type: ignore[name-defined]
"""IMP-89 89-a u3 — BLOCKED exit (sys.exit(1)) when PHASE_Z_B4_MAPPER_SOURCE
is ON and the Layer A render path cannot resolve a covering frame.
Stage 1 Q2 lock: when the user explicitly opts into the B4-driven render
path, a content-bearing zone MUST NOT silently degrade to adapter_needed
or to the legacy V4 rank-1 mapper input. Mirrors IMP-87 u3 honesty-gate
pattern (`_is_blocked_overall` → `sys.exit(1)`): the BLOCKED signal
preempts the silent adapter_needed fallback so the operator sees the
Layer A failure immediately on stderr instead of inheriting a
pseudo-rendered partial.
Reasons (locked enum):
b4_no_cover — PlacementPlan.selected_template_id is None
(B4 found no covering frame on the unit)
b4_selected_fit_error — map_mdx_to_slots raised FitError against the
B4-selected template (frame contract reject)
Always raises SystemExit(1) via sys.exit. The `NoReturn` annotation makes
the call-site control flow explicit for type-checkers; behavior is the
same as IMP-87 u3's sys.exit(1) at L6387.
"""
print(
f"\n[Phase Z-2 IMP-89 89-a u3] BLOCKED @ {reason} (zone--{position})",
file=sys.stderr,
)
print(
" policy : PHASE_Z_B4_MAPPER_SOURCE=ON requires B4-driven render "
"(NO silent fallback — IMP-87 honesty gate pattern)",
file=sys.stderr,
)
for key, value in context.items():
print(f" {key:9}: {value}", file=sys.stderr)
sys.exit(1)
# ─── MDX parsing ────────────────────────────────────────────────
def parse_mdx(mdx_path: Path) -> tuple[str, list[MdxSection], Optional[str]]:
@@ -4681,11 +4757,64 @@ def run_phase_z2_mvp1(
continue
# ─── end B4 gatekeeper ───
# ─── IMP-89 89-a u2 — slot_payload source-of-truth switch ───
# PHASE_Z_B4_MAPPER_SOURCE (u1 flag, default OFF):
# ON → mapper input = B4 PlacementPlan.selected_template_id
# (Layer A render-active; B4 drives slot_payload)
# OFF → mapper input = unit.frame_template_id (legacy mapper-only /
# V4 rank-1; byte-equivalent default — final.html SHA parity
# guarded by u4)
# u3 layers BLOCKED exits for (selected_template_id is None OR
# FitError on B4-selected template) under flag ON — NO silent
# fallback (IMP-87 honesty gate pattern). Under flag OFF semantics
# preserved verbatim.
mapper_template_id = _select_mapper_template_id(
placement_plan, unit.frame_template_id
)
# IMP-89 89-a u3 — BLOCKED exit on B4 no-cover under flag ON.
# When PHASE_Z_B4_MAPPER_SOURCE=ON and PlacementPlan produced no
# covering frame, refuse to fall back to the legacy V4 rank-1 mapper
# input. NO silent fallback (Stage 1 Q2 lock; IMP-87 honesty gate
# pattern). Under flag OFF this branch is never entered, so the
# default render path remains byte-identical.
if _b4_mapper_source_enabled() and mapper_template_id is None:
_b4_mapper_source_blocked_exit(
"b4_no_cover",
position=position,
context={
"unit": (
f"source_section_ids={list(unit.source_section_ids)} "
f"merge_type={unit.merge_type}"
),
"v4_rank1": unit.frame_template_id,
"b4_pick": placement_plan.selected_template_id,
},
)
# mapper 시도 — 실패 (FitError) 시 zone 을 adapter_needed 로 표시하고 skip
try:
slot_payload = map_mdx_to_slots(synth_section, unit.frame_template_id)
slot_payload = map_mdx_to_slots(synth_section, mapper_template_id)
except FitError as e:
_fit_error_str = str(e)
# IMP-89 89-a u3 — BLOCKED exit on B4-selected FitError under flag
# ON. When PHASE_Z_B4_MAPPER_SOURCE=ON the mapper rejecting the
# B4-selected template is a Layer A honesty failure — adapter_needed
# would mask it (Stage 1 Q2 lock). Under flag OFF the legacy
# adapter_needed silent-fallback path executes unchanged.
if _b4_mapper_source_enabled():
_b4_mapper_source_blocked_exit(
"b4_selected_fit_error",
position=position,
context={
"template": f"{mapper_template_id} (B4 selected)",
"unit": (
f"source_section_ids={list(unit.source_section_ids)}"
),
"v4_rank1": unit.frame_template_id,
"fit_error": _fit_error_str,
},
)
_unit_provisional = bool(getattr(unit, "provisional", False))
adapter_record = {
"position": position,