Frame-aware AI fallback module scaffolded under src/phase_z2_ai_fallback/ with master flag ai_fallback_enabled=False; normal-path AI call count remains 0. AI output constrained to builder_options_patch / partial_overrides / slot_mapping_proposal; MDX / frame_id / raw HTML / raw CSS mutations rejected at schema layer. IMP-46 cache gate (cache.py) raises AiFallbackCacheGateError unless visual_check_passed AND user_approved. Step 12 wires AI repair after IMP-30 provisional payload only; Step 17 stays blocked behind IMP-34 / IMP-35 prerequisites. AST isolation guard forbids fallback package from importing Phase Q / Kei / pipeline runtime symbols. Docs IMP-17 / IMP-31 bound to runtime module surface via 11-row structural test pin (test_docs_sync.py) so drift fails CI. Tests: 116 fallback / 161 phase_z2 regression / 526 scoped full sweep all passing. Existing pre-IMP-33 fixture issue in scripts/test_phase_t_* remains untouched (out of scope). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
112 lines
4.6 KiB
Python
112 lines
4.6 KiB
Python
"""IMP-33 u9 — Step 17 AI repair wiring (BLOCKED until IMP-34 + IMP-35 land).
|
|
|
|
Phase Z Step 17 = retry / salvage cascade (see ``src.phase_z2_pipeline``
|
|
section 11.7 ``_attempt_salvage_chain`` and the existing IMP-12 u8/u9
|
|
deterministic chain at ``src/phase_z2_pipeline.py:1994`` and
|
|
``src/phase_z2_pipeline.py:4948``).
|
|
|
|
Per IMP-17 carve-out (``docs/architecture/IMP-17-CARVE-OUT.md`` lines 16,
|
|
40-44), AI repair at Step 17 is permitted ONLY after the full deterministic
|
|
chain is exhausted AND popup escalation is exhausted AND a user-approved
|
|
fallback budget remains. IMP-34 (zone resize + compact retry) and IMP-35
|
|
(``details_popup_escalation``) are explicit prerequisites under the IMP-33
|
|
out-of-scope contract — neither has landed yet. Therefore Step 17 AI repair
|
|
is STRUCTURALLY BLOCKED at u9.
|
|
|
|
This module:
|
|
|
|
1. **SPECIFIES** the canonical overflow cascade order via
|
|
:data:`OVERFLOW_CASCADE_ORDER` — ``deterministic`` → ``popup`` →
|
|
``ai_repair`` → ``user_override``. Downstream Step 17 consumers can rely
|
|
on this single source of truth.
|
|
2. **KEEPS** Step 17 AI repair structurally blocked. The entry point
|
|
:func:`gather_step17_ai_repair_proposals` does NOT import
|
|
``route_ai_fallback`` (u7), does NOT instantiate ``AiFallbackClient`` (u4),
|
|
and does NOT call any Anthropic API. Every unit is recorded with
|
|
``skip_reason="step17_ai_blocked_imp_34_35_prerequisites_missing"`` so
|
|
the caller can distinguish "blocked by carve-out gate" from any other
|
|
skip path (e.g., u8 ``not_provisional`` / ``design_reference_only_no_ai``).
|
|
|
|
Once IMP-34 + IMP-35 land AND a user-approved fallback budget is granted,
|
|
this module will gain the actual ``route_ai_fallback`` wiring guarded by
|
|
the cascade-stage conjunction. Today the gate is closed.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from enum import Enum
|
|
from typing import Any, Callable, Iterable
|
|
|
|
|
|
class OverflowCascadeStage(str, Enum):
|
|
"""Step 17 overflow cascade stages — canonical order (u9 single source of truth).
|
|
|
|
Members are ordered to match the AI isolation contract:
|
|
|
|
* ``DETERMINISTIC`` — IMP-12 u4/u5/u6 (``cross_zone_redistribute`` /
|
|
``glue_compression`` / ``font_step_compression``) + IMP-12 terminal
|
|
actions (``layout_adjust`` / ``frame_reselect``) + IMP-34
|
|
(``zone resize + compact retry``, pending). No AI in any sub-stage.
|
|
* ``POPUP`` — IMP-35 (``details_popup_escalation``, pending). Content
|
|
popup escalation as the final deterministic resort before any AI.
|
|
* ``AI_REPAIR`` — IMP-33 (this carve-out) + IMP-46 cache. Only reachable
|
|
after DETERMINISTIC and POPUP are both exhausted AND user-approved
|
|
fallback budget remains.
|
|
* ``USER_OVERRIDE`` — explicit user override after all auto stages.
|
|
"""
|
|
|
|
DETERMINISTIC = "deterministic"
|
|
POPUP = "popup"
|
|
AI_REPAIR = "ai_repair"
|
|
USER_OVERRIDE = "user_override"
|
|
|
|
|
|
OVERFLOW_CASCADE_ORDER: tuple[OverflowCascadeStage, ...] = (
|
|
OverflowCascadeStage.DETERMINISTIC,
|
|
OverflowCascadeStage.POPUP,
|
|
OverflowCascadeStage.AI_REPAIR,
|
|
OverflowCascadeStage.USER_OVERRIDE,
|
|
)
|
|
|
|
|
|
STEP17_AI_REPAIR_BLOCKED_REASON = (
|
|
"step17_ai_blocked_imp_34_35_prerequisites_missing"
|
|
)
|
|
|
|
|
|
def gather_step17_ai_repair_proposals(
|
|
units: Iterable[Any],
|
|
*,
|
|
route_for_label: Callable[[str | None], str | None],
|
|
) -> list[dict]:
|
|
"""Return one BLOCKED record per unit. No AI call is performed at u9.
|
|
|
|
The record schema mirrors :func:`src.phase_z2_ai_fallback.step12
|
|
.gather_step12_ai_repair_proposals` so the Step 17 artifact consumer can
|
|
reuse the same shape, with one addition: ``cascade_stage`` pins the
|
|
stage this record belongs to (always ``ai_repair`` here).
|
|
|
|
Per Stage 2 contract (IMP-33 u9): Step 17 AI repair is blocked behind
|
|
IMP-34 + IMP-35. Every unit returns with
|
|
``skip_reason=STEP17_AI_REPAIR_BLOCKED_REASON`` and ``ai_called=False``.
|
|
"""
|
|
records: list[dict] = []
|
|
for index, unit in enumerate(units):
|
|
label = getattr(unit, "label", None)
|
|
record: dict = {
|
|
"unit_index": index,
|
|
"source_section_ids": list(
|
|
getattr(unit, "source_section_ids", []) or []
|
|
),
|
|
"frame_template_id": getattr(unit, "frame_template_id", None),
|
|
"label": label,
|
|
"route_hint": route_for_label(label),
|
|
"provisional": bool(getattr(unit, "provisional", False)),
|
|
"cascade_stage": OverflowCascadeStage.AI_REPAIR.value,
|
|
"ai_called": False,
|
|
"skip_reason": STEP17_AI_REPAIR_BLOCKED_REASON,
|
|
"proposal": None,
|
|
"error": None,
|
|
}
|
|
records.append(record)
|
|
return records
|