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:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user