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:
@@ -56,12 +56,24 @@ ACTION_RATIONALE: dict[str, str] = {
|
||||
"위 매핑 모두 미적용 — 마지막 fallback (현재 코드는 sys.exit 으로 abort)",
|
||||
}
|
||||
|
||||
# 각 action 의 *현재 코드* 구현 상태 (2026-04-29 기준; IMP-12 u7 cascade 2026-05-18)
|
||||
# 각 action 의 *현재 코드* 구현 상태 (2026-04-29 기준; IMP-12 u7 cascade 2026-05-18;
|
||||
# IMP-35 u3 popup-stub 2026-05-23)
|
||||
# A2 단계에서 이 매핑이 *어디까지 자동 처리되고 어디서 막히는지* trace 확보용
|
||||
ACTION_IMPLEMENTATION_STATUS: dict[str, str] = {
|
||||
"zone_ratio_retry": "IMPLEMENTED", # A3 (2026-04-29) phase_z2_retry.plan_zone_ratio_retry + pipeline orchestration
|
||||
"layout_adjust": "MISSING",
|
||||
"details_popup_escalation": "MISSING", # CLAUDE.md 의 <details> 원칙은 있음, runtime 미구현
|
||||
# IMP-35 (#64) u3 — MISSING → IMPLEMENTED on the primary router surface.
|
||||
# `plan_details_popup_escalation` (below) provides the deterministic stub
|
||||
# that downstream units consume: u4 binds the AI split-decision contract
|
||||
# in `src/phase_z2_ai_fallback/step17.py`; u5 wires the Step 17 POPUP
|
||||
# gate executor in `src/phase_z2_pipeline.py`. Router-level mapping is
|
||||
# decoupled from orchestrator wiring (same precedent as the IMP-12 u7
|
||||
# cascade actions below): IMPLEMENTED here reflects deterministic
|
||||
# *surface availability* (importable stub), not whether a given pipeline
|
||||
# run has invoked it. The failure_router companion surface
|
||||
# (NEXT_ACTION_IMPLEMENTATION_STATUS in phase_z2_failure_router.py) keeps
|
||||
# `details_popup_escalation` as MISSING until u5 lands the pipeline gate.
|
||||
"details_popup_escalation": "IMPLEMENTED",
|
||||
"frame_reselect": "PARTIAL", # IMP-05 pre-render rank-2/3 fallback implemented; post-render rerender trace-only
|
||||
"adapter_needed": "PARTIAL", # composition v0.1.1 의 mapper FitError catch
|
||||
"abort": "IMPLEMENTED", # sys.exit(1) — pipeline 의 현재 default
|
||||
@@ -185,3 +197,112 @@ def route_fit_classification(fit_classification: dict) -> dict:
|
||||
"MISSING 이면 그 action 은 실행 X 이고 기존 abort/status 흐름 (sys.exit(1)) 으로 종료."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
# ─── IMP-35 (#64) u3 — details_popup_escalation deterministic stub ─
|
||||
# Surface contract for the cascade-terminal popup escalation. This stub
|
||||
# does NOT mutate HTML / CSS / MDX content; it emits the canonical plan
|
||||
# marker that the Step 17 POPUP gate (u5) and the AI split-decision hook
|
||||
# (u4) consume. Keeping the executor surface here (next to the primary
|
||||
# ACTION_BY_CATEGORY mapping) lets the router report IMPLEMENTED for
|
||||
# `details_popup_escalation` while u4/u5 are still landing.
|
||||
#
|
||||
# Contract (locked in Stage 2 IMPLEMENTATION_UNITS u3):
|
||||
# - Inputs: classification dict (a single fit_classifier output row).
|
||||
# The category MUST be one of the two ACTION_BY_CATEGORY
|
||||
# rows that map onto `details_popup_escalation` —
|
||||
# `structural_major_overflow` or `tabular_overflow`.
|
||||
# Other categories raise the stub's defensive guard (so
|
||||
# callers do not silently popup-escalate the wrong category).
|
||||
# - Output: popup_escalation_plan dict with `feasible=True`,
|
||||
# `stub=True`, the source category, the canonical
|
||||
# ACTION_RATIONALE entry, and `needs_split_decision=True`
|
||||
# to flag that u4 (AI hook) must run before u5 renders.
|
||||
# - No side effects (no AI call, no MDX read, no HTML mutation).
|
||||
#
|
||||
# Guardrails honored:
|
||||
# - feedback_ai_isolation_contract: stub is deterministic-with-data;
|
||||
# no AI call inside the router surface.
|
||||
# - Phase Z spacing 방향: stub does not shrink common margins; it
|
||||
# expands capacity by routing content to popup downstream.
|
||||
# - 자세히보기 원칙 (CLAUDE.md): plan carries the marker that u5 uses
|
||||
# to put MDX 원문 in popup body and a summary/subset in preview.
|
||||
# - 1 turn = 1 unit: this is router-surface only. u4/u5 own the
|
||||
# downstream wiring on their respective files.
|
||||
|
||||
|
||||
# Categories that legitimately escalate onto details_popup_escalation
|
||||
# per the ACTION_BY_CATEGORY mapping above. Kept as a derived constant
|
||||
# so the router cannot drift away from the single source of truth.
|
||||
POPUP_ESCALATION_CATEGORIES: frozenset[str] = frozenset(
|
||||
category
|
||||
for category, action in ACTION_BY_CATEGORY.items()
|
||||
if action == "details_popup_escalation"
|
||||
)
|
||||
|
||||
|
||||
def plan_details_popup_escalation(classification: dict) -> dict:
|
||||
"""Cascade-terminal popup escalation plan stub (IMP-35 u3).
|
||||
|
||||
Returns a deterministic popup_escalation_plan marker. The actual
|
||||
content split (popup_html / preview_text / has_popup payload) is
|
||||
composed downstream: u4 binds the AI split-decision contract on
|
||||
`src/phase_z2_ai_fallback/step17.py`; u5 wires the Step 17 POPUP
|
||||
gate executor on `src/phase_z2_pipeline.py`.
|
||||
|
||||
Args:
|
||||
classification: a single fit_classifier classification dict.
|
||||
Must contain a `category` key. Only the categories that
|
||||
map onto `details_popup_escalation` in ACTION_BY_CATEGORY
|
||||
(currently `structural_major_overflow` and `tabular_overflow`)
|
||||
are accepted; any other category produces an
|
||||
`feasible=False` plan with `failure_reason` so the caller
|
||||
never silently popup-escalates the wrong overflow shape.
|
||||
|
||||
Returns:
|
||||
popup_escalation_plan dict with at least:
|
||||
action : "details_popup_escalation"
|
||||
feasible : True/False (True for accepted categories)
|
||||
stub : True (marks u3 surface; u4/u5 fill in)
|
||||
category : echoed from input
|
||||
rationale : canonical ACTION_RATIONALE entry
|
||||
needs_split_decision : True (u4 AI hook must run before u5 renders)
|
||||
mapping_source : "IMP-35 u3 plan_details_popup_escalation stub"
|
||||
note : downstream-wiring pointer text
|
||||
"""
|
||||
category = (classification or {}).get("category")
|
||||
base = {
|
||||
"action": "details_popup_escalation",
|
||||
"stub": True,
|
||||
"category": category,
|
||||
"mapping_source": "IMP-35 u3 plan_details_popup_escalation stub",
|
||||
}
|
||||
if category not in POPUP_ESCALATION_CATEGORIES:
|
||||
return {
|
||||
**base,
|
||||
"feasible": False,
|
||||
"needs_split_decision": False,
|
||||
"rationale": "",
|
||||
"failure_reason": (
|
||||
f"category {category!r} does not map onto details_popup_escalation "
|
||||
f"in ACTION_BY_CATEGORY. Accepted categories: "
|
||||
f"{sorted(POPUP_ESCALATION_CATEGORIES)}. Defensive guard — "
|
||||
f"router must not silently popup-escalate the wrong overflow shape."
|
||||
),
|
||||
"note": (
|
||||
"u3 stub — caller passed a category that should not popup-escalate. "
|
||||
"Honour the ACTION_BY_CATEGORY mapping at the router entry point."
|
||||
),
|
||||
}
|
||||
return {
|
||||
**base,
|
||||
"feasible": True,
|
||||
"needs_split_decision": True,
|
||||
"rationale": ACTION_RATIONALE.get(category, ""),
|
||||
"note": (
|
||||
"u3 stub — actual content split planning lands in u4 "
|
||||
"(AI split-decision contract on src/phase_z2_ai_fallback/step17.py) "
|
||||
"and u5 (Step 17 POPUP gate executor on src/phase_z2_pipeline.py). "
|
||||
"popup body = MDX 원문, preview = summary/subset (자세히보기 원칙)."
|
||||
),
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user