feat(#64): IMP-35 details_popup_escalation u1~u10 + Stage 3 R7 anchor re-pin
Land the production + test surface for the Step 17 cascade POPUP terminal (DETERMINISTIC -> POPUP -> AI_REPAIR -> USER_OVERRIDE) per Stage 2 plan R2. u11 (baseline-red invariance gate) was already landed in7c93031ahead of this commit; this commit completes u1~u10 plus the Stage 3 R7 follow-up anchor re-pin for test_imp17_comment_anchor.py. Implementation units (Stage 2 R2 contract): u1 frame_reselect_insufficient failure_type + post-frame remeasure (q4) - src/phase_z2_failure_router.py, src/phase_z2_pipeline.py u2 NEXT_ACTION_BY_FAILURE row + impl_status flip - src/phase_z2_failure_router.py u3 Router details_popup_escalation MISSING->IMPLEMENTED + executor stub - src/phase_z2_router.py u4 step17.py AI split-decision contract (POPUP cascade_stage + route_for_label + skip_reason); API gated - src/phase_z2_ai_fallback/step17.py u5 Step 17 POPUP gate executor; popup_escalation_plan + has_popup marker - src/phase_z2_pipeline.py, src/phase_z2_ai_fallback/step17.py u6 Composition popup binding -- yaml strategy -> zone payload - src/phase_z2_composition.py u7 Pipeline composer -> render_slide wiring (popup_html / preview_text / has_popup) - src/phase_z2_pipeline.py u8 slide_base.html <details>/<summary> popup wrapper - templates/phase_z2/slide_base.html u9 display_strategies.yaml inline_preview + popup metadata - templates/phase_z2/regions/display_strategies.yaml u10 MDX preservation invariant: popup=full source / body=summary or subset (asserted by tests/phase_z2/test_popup_mdx_preservation.py) u11 (already in7c93031) -- baseline-red invariance gate Stage 3 R7 follow-up (anchor re-pin, test-only): - tests/orchestrator_unit/test_imp17_comment_anchor.py Pre-anchor additions in src/phase_z2_pipeline.py (u1 / u5 / u7) shifted the restructure/reject route-hint comments 578/579 -> 586/587. Re-pinned the two guard tests (and docstring re-pin lineage 564 -> 570 -> 578 -> 586). Production code untouched. Verification (Stage 4 R1): pytest -q tests/orchestrator_unit/test_imp17_comment_anchor.py -> 2 passed / 0.02s pytest -q <10 IMP-35 unit files in tests/phase_z2 + tests/phase_z2_ai_fallback> -> 136 passed / 15.94s Baseline-red invariance gate (tests/test_imp47b_step12_ai_wiring.py + tests/test_phase_z2_ai_fallback_config.py) -> 4 failed / 6 passed; FAILED set === IMP35_BASELINE_RED_NODE_IDS (frozen registry from7c93031). Contract holds. Codex Stage 4 R1 = YES (independent verify). Guardrails honored: - MDX content preservation: popup carries full source, body holds summary or subset only (CLAUDE.md 자세히보기 원칙; feedback_phase_z_spacing_direction -- capacity expanded, no margin shrink). - AI isolation contract: Step 17 POPUP gate is deterministic; AI hook surface is split-decision contract only, API call gated. - No hardcoding: escalation thresholds derived from existing overflow detector outputs; preview_chars deterministic from container px. - 1 commit = 1 decision unit: u1~u10 land together as the planned production surface; u11 was deliberately split into7c93031as Stage 3 R7 carve-out, and the R7 anchor re-pin rides with this commit because it is the direct shift consequence of the u1/u5/u7 pre-anchor additions. - Scope-locked: .claude/settings.json explicitly excluded (Stage 4 exit report contract). Out of scope (per Stage 1 + Stage 2): - AI_REPAIR API activation (post IMP-35 axis). - IMP-34 zone resize, IMP-36 responsive fit (chain partners, separate issues). - Print-time auto-expand JavaScript for <details>. - Popup escalation in stages other than Step 17. - Baseline-red body repair (4 frozen failures) -- separate follow-up issue; u11 only guards the count. - frame_reselect algorithm changes (entry point only). - templates/phase_z2/slide_base.html path rename. source_comment_ids: Stage 1: claude_stage1_problem_review_imp35, codex_stage1_verification_imp35_yes Stage 2: Claude #4 R2 plan, Codex #5 R2 YES Stage 3: Claude #86 (R7 anchor re-pin), Codex #87 YES Stage 4: Claude #88 R1, Codex #89 R1 YES Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -34,8 +34,12 @@ frame_reselect (V4 top-k 의 다른 frame)
|
||||
details_popup_escalation (가장 invasive — content popup, 마지막 resort)
|
||||
```
|
||||
|
||||
`details_popup_escalation` 은 본 매핑에 *없음* — tabular_overflow / structural_major_overflow /
|
||||
frame_reselect 실패 이후 단계에서 다룸 (별 step).
|
||||
IMP-35 (#64) u2 — cascade terminal landed. `frame_reselect_insufficient`
|
||||
(post-frame remeasure failure, classifier path locked in u1) now routes onto
|
||||
`details_popup_escalation`. The status table records the popup action as
|
||||
MISSING here; the actual executor stub + MISSING→IMPLEMENTED flip lives in
|
||||
`src/phase_z2_router.py` (u3 surface), so this module advertises the cascade
|
||||
terminal without claiming an implementation it does not own.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
@@ -74,6 +78,13 @@ FAILURE_TYPE_DESCRIPTIONS: dict[str, str] = {
|
||||
"font_step_compression salvage step failed — FONT_SIZE_STEPS exhausted "
|
||||
"down to the floor without resolving overflow (or text_metrics missing)"
|
||||
),
|
||||
"frame_reselect_insufficient": (
|
||||
"frame_reselect salvage step failed — V4 top-k alternate frame swap "
|
||||
"re-rendered + post-frame remeasure (run_overflow_check) still fails. "
|
||||
"IMP-35 (#64) u1 contract: emitted from salvage_steps[-1].action == "
|
||||
"'frame_reselect' AND passed=False AND post_salvage_overflow present. "
|
||||
"Routes to details_popup_escalation in u2 (cascade terminal)."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +97,12 @@ SALVAGE_FAILURE_TYPE_BY_ACTION: dict[str, str] = {
|
||||
"cross_zone_redistribute": "cross_zone_redistribute_insufficient",
|
||||
"glue_compression": "glue_absorption_insufficient",
|
||||
"font_step_compression": "font_step_insufficient",
|
||||
# IMP-35 (#64) u1: post-frame remeasure failure. frame_reselect salvage step
|
||||
# writes a salvage_steps entry with action='frame_reselect', passed=False,
|
||||
# and post_salvage_overflow populated by run_overflow_check on the swapped
|
||||
# frame's HTML. classifier reads that entry; u2 adds the NEXT_ACTION row
|
||||
# that routes this onto details_popup_escalation.
|
||||
"frame_reselect": "frame_reselect_insufficient",
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +115,14 @@ NEXT_ACTION_BY_FAILURE: dict[str, str] = {
|
||||
"glue_absorption_insufficient": "font_step_compression",
|
||||
"font_step_insufficient": "layout_adjust",
|
||||
"rerender_still_fails": "frame_reselect",
|
||||
# IMP-35 (#64) u2 — cascade terminal. frame_reselect salvage exhausted
|
||||
# (post-frame remeasure failed; classifier path gated on
|
||||
# post_salvage_overflow per u1/q4) escalates onto details_popup_escalation.
|
||||
# Popup body holds full MDX source; preview shows summary/subset
|
||||
# (CLAUDE.md 자세히보기 원칙). Executor + MISSING→IMPLEMENTED flip lands
|
||||
# in u3 (src/phase_z2_router.py); this module owns the cascade mapping
|
||||
# only.
|
||||
"frame_reselect_insufficient": "details_popup_escalation",
|
||||
"not_attempted": "none",
|
||||
}
|
||||
|
||||
@@ -127,6 +152,12 @@ NEXT_ACTION_RATIONALE: dict[str, str] = {
|
||||
"frame/zone 조합 자체 부적합, V4 top-k 의 다른 frame 평가 (frame_reselect). "
|
||||
"popup 직행은 아직 빠름 (tabular / structural_major 가 아닌 한)"
|
||||
),
|
||||
"frame_reselect_insufficient": (
|
||||
"V4 top-k frame swap + 명시적 post-frame remeasure 까지 했는데도 overflow "
|
||||
"잔존 → cascade terminal 인 details_popup_escalation 으로 escalate. "
|
||||
"본문 = summary/subset, popup = MDX 원문 (자세히보기 원칙). "
|
||||
"AI repair 진입 전 deterministic 마지막 단계."
|
||||
),
|
||||
"not_attempted": (
|
||||
"retry 시도 자체가 없었음 (visual ok 등) — escalation 불필요"
|
||||
),
|
||||
@@ -145,6 +176,12 @@ NEXT_ACTION_IMPLEMENTATION_STATUS: dict[str, str] = {
|
||||
"font_step_compression": "IMPLEMENTED", # u6 plan_font_step_compression + apply_font_step_compression_css
|
||||
"layout_adjust": "MISSING",
|
||||
"frame_reselect": "MISSING",
|
||||
# IMP-35 (#64) u2 — cascade terminal advertised as MISSING here. The
|
||||
# router executor stub + MISSING→IMPLEMENTED flip lives in
|
||||
# src/phase_z2_router.py (u3). Keeping this entry as MISSING until u3
|
||||
# lands prevents premature "popup ready" claims from the failure-router
|
||||
# surface.
|
||||
"details_popup_escalation": "MISSING",
|
||||
"none": "n/a",
|
||||
}
|
||||
|
||||
@@ -170,21 +207,40 @@ def classify_retry_failure(retry_trace: dict) -> Optional[dict]:
|
||||
# case 0.7 : salvage chain attempted and ended in a salvage-level failure.
|
||||
# zone_ratio_retry 가 먼저 실패한 뒤 _attempt_salvage_chain 이 가동된 path —
|
||||
# 마지막 salvage step 의 action 으로 failure_type 을 분류한다. u3 가 routing.
|
||||
#
|
||||
# IMP-35 (#64) u1 — q4 explicit remeasure contract: the frame_reselect
|
||||
# branch is gated on post_salvage_overflow being present on the salvage
|
||||
# step. A bare passed=False flag with no remeasure payload is *not*
|
||||
# sufficient to emit frame_reselect_insufficient (which routes to
|
||||
# details_popup_escalation in u2). When the gate fails, the classifier
|
||||
# falls through to lower-priority cases so the salvage trace surfaces as
|
||||
# an unmatched defensive fallback instead of a spurious popup escalation.
|
||||
salvage_steps = retry_trace.get("salvage_steps") or []
|
||||
if salvage_steps:
|
||||
last = salvage_steps[-1] or {}
|
||||
if not last.get("passed"):
|
||||
action = (last.get("action") or "").lower()
|
||||
ftype = SALVAGE_FAILURE_TYPE_BY_ACTION.get(action)
|
||||
if ftype is not None:
|
||||
reason = last.get("failure_reason") or ""
|
||||
return {
|
||||
"failure_type": ftype,
|
||||
"classification_rule": (
|
||||
f"salvage_steps[-1].action == {action!r} "
|
||||
f"AND passed=False. raw failure_reason: {reason!r}"
|
||||
),
|
||||
}
|
||||
frame_reselect_blocked = (
|
||||
action == "frame_reselect"
|
||||
and not last.get("post_salvage_overflow")
|
||||
)
|
||||
if not frame_reselect_blocked:
|
||||
ftype = SALVAGE_FAILURE_TYPE_BY_ACTION.get(action)
|
||||
if ftype is not None:
|
||||
reason = last.get("failure_reason") or ""
|
||||
rule_suffix = (
|
||||
" AND post_salvage_overflow present"
|
||||
if action == "frame_reselect"
|
||||
else ""
|
||||
)
|
||||
return {
|
||||
"failure_type": ftype,
|
||||
"classification_rule": (
|
||||
f"salvage_steps[-1].action == {action!r} "
|
||||
f"AND passed=False{rule_suffix}. "
|
||||
f"raw failure_reason: {reason!r}"
|
||||
),
|
||||
}
|
||||
|
||||
# case 1 : retry 시도 자체 안 됨 (router_active=False 또는 다른 action)
|
||||
if not retry_trace.get("retry_attempted"):
|
||||
|
||||
Reference in New Issue
Block a user