IMP — Step 17 retry chain 완결 (layout_adjust / image_fit / frame_internal_fit_candidate 3 missing actions) #88
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
IMP — Step 17 retry chain 완결 (3 missing actions)
관련 step: Step 17 (Implemented Action 실행) — overflow retry chain
source: status board 2026-05-08 의 "Step 17 missing actions" 잔여 axis. 2026-05-22 검증 — visual_check 가 missing action 만나면 abort
roadmap axis: R1 (안정성) — overflow handling
wave: P1 (P0 #85/#86/#87 후)
priority: 중 — overflow case 자동 복구 path 완결
dependency: #63 IMP-34 closed (zone_ratio_retry sub-axis cover), #64 IMP-35 open (popup escalation sub-axis cover)
scope
Step 17 의 5 retry actions 중 3 missing 구현:
layout_adjust
image_fit
frame_internal_fit_candidate
각 action 의 implementation + Step 17 dispatcher 연결 + return-to-render 흐름 + retry 결과 분류 (Step 18) 연계
out of scope
guardrail / validation
relevant feedback
feedback_phase_z_spacing_direction: clipping 시 layout / frame 으로 풀 것 (공통 여백 줄이지 말 것)feedback_factual_verification: missing_action 의 명확한 status reporting🤖 Claude Opus 4.7 (P1 batch, 2026-05-22)
📌 SCOPE 확인 NOTE — Stage 1 reviewer (defensive)
#88 의 body 의 3 missing actions (
layout_adjust/image_fit/frame_internal_fit_candidate) 는 처음부터 명확 — scope 변경 X. 단 실행 시점 에 #64 (popup escalation) + #65 (fit/rotation) 가 closed 된 상태 이므로 Stage 1 에서 정합 재확인:Stage 1 reviewer 가 확인 할 항목
details_popup_escalation이 정상 구현 + Step 17 dispatcher 에 등록 됐는지 확인. impl_status MISSING → IMPLEMENTED 1 건 확정fit/rotation generalization이 Step 13 영역 (frame partial responsive) 이라 Step 17 retry action 과 별 axis 임 확인. #88 의 scope 와 무관 — overlap Xlayout_adjust/image_fit/frame_internal_fit_candidate각각 의 implementation 이 #64 의 popup 패턴 (return-to-render 흐름) 재사용 권장 (mirror)scope 잠금
#88 의 body scope 그대로 유효 — 3 actions 만. #64 / #65 와 overlap 없음. body 수정 불필요.
🤖 Claude Opus 4.7 — Codex consensus amend (2026-05-23)
[Claude #1] Stage 1 problem-review — IMP-88 Step 17 retry chain completion (3 missing actions)
1. Root cause (verified)
Symptom:
visual_check가 missing action 만나면 abort (issue body 2026-05-22 검증). 원인은 Step 17 dispatcher 의 cascade 가 implemented action 3 개 (cross_zone_redistribute / glue_compression / font_step_compression) 후 cascade-terminal action 에서 executor 부재 로 빠져나가기 때문.Verified at
src/phase_z2_pipeline.py:2598-2604(_attempt_salvage_chain):즉
layout_adjust(cascade:font_step_insufficient → layout_adjust,failure_router.py:116) 와image_fit/frame_internal_fit_candidate(별 axis classifications) 가 dispatcher 에 등록되지 않아 abort path 로 fall through.details_popup_escalation은 #64 u3+u5 로 이미 IMPLEMENTED (router.py:76+ pipeline.py:6393-6419popup_gate_records).2. Scope-lock 확인 (#88 body 3 actions 유효)
moderate_overflow/layout_zone_mismatch→layout_adjust,router.py:30,35)font_step_insufficient → layout_adjust,failure_router.py:116)router.py:64+failure_router.py:177)image_aspect_mismatchclassification 이classifier.py:435에서 emit 되지만ACTION_BY_CATEGORY에 row 없음 →proposed_action=NoneACTION_BY_CATEGORY/NEXT_ACTION_BY_FAILURE양쪽에 row 없음. OVERVIEW.md §Step 16 (line 321) 은 spec 상frame_capacity_mismatch → frame_internal_fit_candidate로 기재하나 실제 router 는frame_capacity_mismatch → frame_reselect(router.py:34). Step 19 severity-aware 매핑 (OVERVIEW.md line 346) 도 미구현amend note (Codex consensus 2026-05-23) 의 두 closed-deliverable 확인 :
router.py:244 plan_details_popup_escalation) + pipeline u5 gate (pipeline.py:6390-6419 run_step17_popup_gateinvocation, terminal_action="details_popup_escalation").details_popup_escalation은 #88 scope 에서 제외 — already landed.c1df656u1-u8 모두frame_contracts.yaml확장 + F13/F14/F20/F8 family partial 의 container query + rotation_eligible / body_fit_pattern2 axis 추가.phase_z2_router.py/phase_z2_failure_router.py/phase_z2_retry.py/phase_z2_pipeline.py의 Step 17 surface 와 무관.image_fit와 overlap 없음.scope-lock = #88 body 3 actions 그대로. body 수정 불필요.
3. Stage 2 가 결정해야 할 sub-axes (Stage 1 외)
image_fit/frame_internal_fit_candidate.IMP-17-CARVE-OUT.mdline 16 +PHASE-Z-PIPELINE-OVERVIEW.mdline 321,332 은image_fit_candidate로 통일. spec-canonical_candidate접미사 채택 권장 (router taxonomy 일관성).ACTION_BY_CATEGORY["image_aspect_mismatch"] = "image_fit_candidate"신설 필요. 신설 없이는 executor 가 있어도 dispatcher 가 호출하지 않음.ACTION_BY_CATEGORY["frame_capacity_mismatch"]를frame_reselect→frame_internal_fit_candidate로 교체 (spec OVERVIEW.md line 321 따름), (B)frame_reselect앞에 intermediate 로 삽입. OVERVIEW.md line 333 사용자 잠금 노트 ("unified label / frame contract envelope 안") 와 Step 19 severity-aware 매핑 (line 346) 종합 시 (A) 가 spec 의도에 가까움._attempt_salvage_chain의_SALVAGE_FAIL_BY_ACTION에 추가하면 cascade step 으로 동작. 또는 popup_gate 처럼 standalone gate (e.g._attempt_layout_adjust_after_cascade). cascade step 으로 통합하는 쪽이 기존 IMP-12 u4/u5/u6 + IMP-35 popup 패턴과 일관됨.pipeline.py:6436의 노트 "Terminal actions (layout_adjust / frame_reselect / details_popup_escalation) still MISSING" 은 #64 이후 stale. 각 action 이 IMPLEMENTED 로 전환될 때마다 note text 갱신 필요.4. Guardrails (Stage 2/3 lock 대상)
feedback_ai_isolation_contract) : 3 actions 모두 deterministic. router/failure_router stub + planner + apply CSS pattern 만 사용 (IMP-12 u4/u5/u6 + IMP-35 u3/u5 precedent).phase_z2_ai_fallback/임포트 금지.feedback_phase_z_spacing_direction) :layout_adjust는 zone topology / layout preset 변경만 (slide / slide-body / title / divider / footer / zone gap 고정).image_fit는 image-scope CSS 만 ([data-image-id="..."]).frame_internal_fit_candidate는 frame contract envelope 안 ([data-zone-position="..."]또는[data-frame-template-id="..."]스코프).phase_z2_router.pyACTION_IMPLEMENTATION_STATUSANDphase_z2_failure_router.pyNEXT_ACTION_IMPLEMENTATION_STATUS양쪽에서 MISSING → IMPLEMENTED 전환. IMP-12 u7 precedent (router.py:80-86).feedback_artifact_status_naming) : Step 17 artifact 의 step_status / pipeline_path_connected / note 가 각 action landing 별로 정확히 반영. final.html ≠ PASS, 3 axis (rendered / visual_check / full_mdx_coverage) 모두 honest.5. EVIDENCE
Files read :
src/phase_z2_ai_fallback/step17.py(full)src/phase_z2_retry.py(full)src/phase_z2_failure_router.py(full)src/phase_z2_router.py(full)src/phase_z2_pipeline.py:2560-2760, 6250-6450(Step 17 cascade orchestrator + popup gate wiring)src/phase_z2_classifier.py:400-470(image_aspect_mismatch emit site)docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:315-374(Step 16 / 17 / 19 spec)docs/architecture/PHASE-Z-PIPELINE-STATUS-BOARD.md:60-130(current missing inventory)docs/architecture/IMP-17-CARVE-OUT.md:1-60(carve-out boundary, line 16 deterministic action list)Cross-checks :
git show --stat c1df656(IMP-36 #65 commit) — Step 13 frame partial responsive only; no Step 17 router surface mutation. #65 ↔ #88 overlap = none ✓git log --oneline -20— #64 (IMP-35 popup) + #91 (multi-mdx regression) + #87/#86/#85 (P0) all closed before #88 wave.image_fitinsrc/phase_z2_router.py→ 0 hits ✓ (UNMAPPED confirmed)frame_internal_fit_candidateinsrc/phase_z2_router.py→ 0 hits ✓ (UNMAPPED confirmed)image_aspect_mismatchinsrc/phase_z2_classifier.py→ emit at line 435 ✓ (classification fires today, action unrouted)Verdict : scope-lock confirmed per Codex amend (2026-05-23). proceed to Stage 2 simulation-plan.
[Codex #1] Stage 1 problem-review - IMP-88 Step 17 retry chain completion
Root cause verified.
src/phase_z2_pipeline.py:2574-2604proves the Step 17 salvage dispatcher only executes actions listed in_SALVAGE_FAIL_BY_ACTION:cross_zone_redistribute,glue_compression, andfont_step_compression. Whenroute_retry_failure()proposes any other next action,_attempt_salvage_chain()recordssalvage_terminal_actionand returns without an executor.src/phase_z2_failure_router.py:111-117routesfont_step_insufficienttolayout_adjust, whilesrc/phase_z2_failure_router.py:173-178still markslayout_adjustasMISSING. That is the abort/terminal gap for the existing cascade.Scope-lock verified for the three #88 actions.
layout_adjust: mapped insrc/phase_z2_router.py:28-35formoderate_overflowandlayout_zone_mismatch, and mapped insrc/phase_z2_failure_router.py:111-117fromfont_step_insufficient; stillMISSINGinsrc/phase_z2_router.py:62-65andsrc/phase_z2_failure_router.py:173-178.image_fit:src/phase_z2_classifier.py:426-435emitsimage_aspect_mismatch, butACTION_BY_CATEGORYhas noimage_aspect_mismatchrow and there is noimage_fit/image_fit_candidatestatus or executor in the active router surfaces. A direct import check returnedroute_action('image_aspect_mismatch')withproposed_action=Noneandimplementation_status='unknown'.frame_internal_fit_candidate: active code has no executor/status row for this action. The architecture overview lists it as a Step 17/19 missing/internal-envelope gap atdocs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:321-344, while activeACTION_BY_CATEGORYcurrently mapsframe_capacity_mismatchtoframe_reselectatsrc/phase_z2_router.py:34.Closed-overlap check.
details_popup_escalationshould remain out of #88 scope. The primary router surface is implemented:src/phase_z2_router.py:62-77marks itIMPLEMENTED,src/phase_z2_router.py:244-292providesplan_details_popup_escalation(), andsrc/phase_z2_pipeline.py:6373-6419wires the Step 17 popup gate. However, one status-surface nuance must be preserved for Stage 2:src/phase_z2_failure_router.py:173-184still reportsNEXT_ACTION_IMPLEMENTATION_STATUS['details_popup_escalation'] == 'MISSING', andtests/phase_z2/test_phase_z2_failure_router_cascade.py:203-224asserts that behavior. Do not use the failure-router status table as evidence that popup is implemented; use the primary router plus pipeline gate surfaces.#65fit/rotation generalization does not overlap with the three #88 Step 17 actions in active router/failure/pipeline files. Local history for commitc1df656touched frame contracts, family templates, and IMP-36 tests, notsrc/phase_z2_router.py,src/phase_z2_failure_router.py,src/phase_z2_retry.py, orsrc/phase_z2_pipeline.py.Stage 2 planning implications:
layout_adjust,image_fit(or explicitly canonicalize toimage_fit_candidateif the implementation follows the architecture label), andframe_internal_fit_candidate.frame_internal_fit_candidateas a frame-contract-bound internal-envelope action, not a generic CSS compaction shortcut.=== EVIDENCE ===
Commands run:
Get-Content -LiteralPath 'D:\ad-hoc\kei\design_agent\.orchestrator\tmp\codex_prompt.txt'rg -n "_attempt_salvage_chain|_SALVAGE_FAIL_BY_ACTION|salvage_terminal_action|details_popup_escalation|popup_gate_records|run_step17_popup_gate" src tests OVERVIEW.mdrg -n "layout_adjust|image_fit|image_aspect_mismatch|frame_internal_fit_candidate|frame_capacity_mismatch|frame_reselect|font_step_insufficient|ACTION_BY_CATEGORY|NEXT_ACTION_BY_FAILURE|plan_details_popup_escalation" src tests OVERVIEW.mdgit status --shortsrc\phase_z2_pipeline.py,src\phase_z2_router.py,src\phase_z2_failure_router.py,src\phase_z2_classifier.py,tests\phase_z2\test_phase_z2_failure_router_cascade.py, andtests\phase_z2\test_phase_z2_router_popup.pyrg --files | rg "(^|[/\\])OVERVIEW\.md$|overview"rg -n "frame_internal_fit_candidate|image_fit|image_aspect_mismatch|frame_capacity_mismatch|frame_reselect" . -g "!*.json" -g "!*.html" -g "!*.pkl" -g "!*.png"git show --name-only --oneline --no-renames c1df656rg -n "details_popup_escalation.*IMPLEMENTED|missing_actions_pending_impl|image_aspect_mismatch|frame_internal_fit_candidate" tests\phase_z2 src\phase_z2_router.py src\phase_z2_failure_router.py src\phase_z2_classifier.pyACTION_BY_CATEGORY,ACTION_IMPLEMENTATION_STATUS,NEXT_ACTION_BY_FAILURE,NEXT_ACTION_IMPLEMENTATION_STATUS,route_action(), androute_retry_failure()Files checked:
D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.pyD:\ad-hoc\kei\design_agent\src\phase_z2_router.pyD:\ad-hoc\kei\design_agent\src\phase_z2_failure_router.pyD:\ad-hoc\kei\design_agent\src\phase_z2_classifier.pyD:\ad-hoc\kei\design_agent\tests\phase_z2\test_phase_z2_failure_router_cascade.pyD:\ad-hoc\kei\design_agent\tests\phase_z2\test_phase_z2_router_popup.pyD:\ad-hoc\kei\design_agent\docs\architecture\PHASE-Z-PIPELINE-OVERVIEW.mdD:\ad-hoc\kei\design_agent\docs\architecture\PHASE-Z-PIPELINE-STATUS-BOARD.mdTest results: not run; Stage 1 explicitly says NO pytest.
Verified facts:
_attempt_salvage_chain()executes only the three_SALVAGE_FAIL_BY_ACTIONkeys and returns terminal for any other proposed action.layout_adjustis mapped but still reportedMISSING.image_aspect_mismatchis emitted by the classifier but has no active action mapping toimage_fit.frame_internal_fit_candidateexists in architecture scope docs but not in active router/failure executor surfaces.details_popup_escalationis implemented on the primary router/pipeline gate surfaces but stillMISSINGon the failure-router implementation-status surface by current test contract.c1df656did not modify the active Step 17 router/failure/retry/pipeline source files.FINAL_CONSENSUS: YES
[Claude #2] Stage 2 simulation-plan Round #1 — IMP-88 Step 17 3 missing terminal actions
scope: layout_adjust [u3,u4,u5,u6,u7] / image_fit [u1,u4,u5,u6,u7] / frame_internal_fit_candidate [u2,u4,u5,u6,u7]. Mirror IMP-35 u3/u5 (#64) — router stub + post-salvage gate (pipeline.py:6390-6419).
=== IMPLEMENTATION_UNITS ===
id: u1
summary: router.py — add image_aspect_mismatch→image_fit ACTION_BY_CATEGORY+ACTION_RATIONALE+status IMPLEMENTED. plan_image_fit(classification) stub mirroring plan_details_popup_escalation; echoes delta/natural_ratio/rendered_ratio + needs_aspect_adjust=True. Defensive guard rejects other categories.
files: [src/phase_z2_router.py]
tests: [tests/phase_z2/test_phase_z2_router_image_fit.py]
estimate_lines: 48
id: u2
summary: router.py — frame_internal_fit_candidate status IMPLEMENTED + plan_frame_internal_fit_candidate(classification) stub. OVERVIEW §333 unified label. Accepts frame_capacity_mismatch only; plan has internal_envelope_only=True. NO ACTION_BY_CATEGORY mutation — locked row stays; exposed via u5 cascade pre-step.
files: [src/phase_z2_router.py]
tests: [tests/phase_z2/test_phase_z2_router_frame_internal_fit.py]
estimate_lines: 46
id: u3
summary: router.py — plan_layout_adjust(classification) stub for existing layout_adjust rows (router.py:30,35). Status MISSING→IMPLEMENTED. Plan: topology_change_required=True + current_layout_preset + suggested_alternates. feedback_phase_z_spacing_direction — no shared margin shrink.
files: [src/phase_z2_router.py]
tests: [tests/phase_z2/test_phase_z2_router_layout_adjust.py]
estimate_lines: 50
id: u4
summary: pipeline.py — post-salvage TERMINAL gate dispatcher. After enrich_retry_trace_with_failure_classification, if next_proposed_action ∈ {layout_adjust, image_fit, frame_internal_fit_candidate}, invoke plan_* via per-unit/per-zone classification lookup, stamp retry_trace['terminal_action_records'] (cascade_stage='deterministic_terminal'+ai_called=False+plan). NO HTML mut (Stage 1 Q3 min-viable). New block only — carve-out safe.
files: [src/phase_z2_pipeline.py]
tests: [tests/phase_z2/test_phase_z2_step17_terminal_action_gate.py]
estimate_lines: 50
id: u5
summary: failure_router.py — flip status layout_adjust IMPLEMENTED + add image_fit + frame_internal_fit_candidate IMPLEMENTED. Add NEXT_ACTION_BY_FAILURE rows WITHOUT touching locked 7 rows: image_aspect_misfit→image_fit + frame_internal_envelope_exhausted→frame_internal_fit_candidate (pre-step before frame_reselect). FAILURE_TYPE_DESCRIPTIONS + RATIONALE entries added.
files: [src/phase_z2_failure_router.py]
tests: [tests/phase_z2/test_phase_z2_failure_router_terminal_actions.py]
estimate_lines: 50
id: u6
summary: 3 router unit tests mirror test_phase_z2_router_popup.py — image_fit (row+status+plan accept/reject) + frame_internal_fit (status+plan+internal_envelope_only) + layout_adjust (status+both route_action rows IMPL+plan accept moderate_overflow & layout_zone_mismatch+reject tabular+topology_change_required).
files: [tests/phase_z2/test_phase_z2_router_image_fit.py, tests/phase_z2/test_phase_z2_router_frame_internal_fit.py, tests/phase_z2/test_phase_z2_router_layout_adjust.py]
tests: [same as files]
estimate_lines: 50
id: u7
summary: test_phase_z2_step17_terminal_action_gate.py — pipeline dispatcher 3 cases: layout_adjust+moderate_overflow / image_fit+image_aspect_mismatch (delta echoed) / frame_internal_fit_candidate+frame_capacity_mismatch (internal_envelope_only). Assert out_path unchanged (record-only). Mirrors test_phase_z2_step17_popup_gate.
files: [tests/phase_z2/test_phase_z2_step17_terminal_action_gate.py]
tests: [tests/phase_z2/test_phase_z2_step17_terminal_action_gate.py]
estimate_lines: 50
Out of scope: HTML/CSS transform (follow-up #88-fa/fb/fc; Stage 1 Q3 min-viable) / #64 popup / Step 16 / Stage 0 normalizer / AI fallback activation / new detection / locked row mut / carve-out (
1186ad8).Rollback: each unit=1 commit, revertable u7→u1. Pure-additive; pipeline.py new gate block only.
=== EVIDENCE ===
e0c39f1. Carve-out ref1186ad8(#76).[Codex #2] Stage 2 simulation-plan review - IMP-88
=== IMPLEMENTATION_UNITS ===
reviewed_plan: Claude #2 Stage 2 simulation-plan Round #1
verdict: incomplete
blocking_findings:
u1 rationale: OK as a router-surface unit for image_aspect_mismatch -> image_fit, with a focused router test.
u2 rationale: Partial; the plan stub is useful, but activation remains unresolved because the current primary router still maps frame_capacity_mismatch to frame_reselect.
u3 rationale: OK as a router-surface unit for existing layout_adjust rows and status flip.
u4 rationale: Partial; a terminal-action gate mirrors the popup pattern, but Step 18/19 surfacing must be explicit and tested.
u5 rationale: Partial; status rows are needed, but adding a new unproduced failure_type is not enough to activate frame_internal_fit_candidate.
u6 rationale: Not verifiable because the unit text is cut off before files/tests/estimate_lines.
u7 rationale: Not verifiable because the unit is referenced but absent.
Out-of-scope notes: details_popup_escalation remains excluded as #64 work; #65 fit/rotation remains excluded unless a future plan explicitly ties it to one of the three #88 actions without changing scope.
Rollback strategy: no source changes are accepted from this plan; rewind to Stage 2 and replace the compact implementation_units block with complete u1-u7 entries and a reachable frame_internal_fit_candidate activation rule.
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
rewind_target: stage_2_plan
FINAL_CONSENSUS: NO
[Claude #3] Stage 2 simulation-plan — IMP-88 (rewind redo)
Scope: 3 issue-body actions + Step 18 linkage. ACTION_BY_CATEGORY repoint frame_capacity_mismatch→frame_internal_fit_candidate (OVERVIEW.md:321) + new row image_aspect_mismatch→image_fit. Cascade extended: ...→font_step_compression→layout_adjust→frame_internal_fit_candidate→frame_reselect→details_popup_escalation (terminal #64). Producer for new failure_types = u2 SALVAGE_FAILURE_TYPE_BY_ACTION rows wiring salvage_steps[-1].action emitted by u6 loop (pattern pipeline.py:2638-2652) — fixes reviewer missing-producer.
=== IMPLEMENTATION_UNITS ===
summary: router.py — ACTION_BY_CATEGORY repoint frame_capacity_mismatch→frame_internal_fit_candidate + new row image_aspect_mismatch→image_fit + ACTION_RATIONALE + ACTION_IMPLEMENTATION_STATUS for 3 new actions
files: [src/phase_z2_router.py]
tests: [tests/phase_z2/test_router_actions_imp88.py]
estimate_lines: 30
summary: failure_router.py — 3 new failure_types (layout_adjust_insufficient / frame_internal_fit_insufficient / image_fit_insufficient) + SALVAGE_FAILURE_TYPE_BY_ACTION producer rows + NEXT_ACTION_BY_FAILURE demotions + IMPLEMENTATION_STATUS flips
files: [src/phase_z2_failure_router.py]
tests: [tests/phase_z2/test_failure_router_imp88_cascade.py]
estimate_lines: 45
summary: retry.py — plan_layout_adjust + apply_layout_adjust_css (preset switch within 8-preset LAYOUT_PRESETS; plan carries new_layout_preset + new_layout_css for u6 re-render)
files: [src/phase_z2_retry.py]
tests: [tests/phase_z2/test_phase_z2_retry_layout_adjust.py]
estimate_lines: 50
summary: retry.py — plan_image_fit + apply_image_fit_css (frame-scoped object-fit:contain + container-bounded max-w/h from classifier.py:434-447 image_event)
files: [src/phase_z2_retry.py]
tests: [tests/phase_z2/test_phase_z2_retry_image_fit.py]
estimate_lines: 45
summary: retry.py — plan_frame_internal_fit_candidate + apply_frame_internal_fit_candidate_css (frame contract envelope absorption only; reads placement_trace.internal_regions; frame-scoped CSS; OVERVIEW.md:333 lock)
files: [src/phase_z2_retry.py]
tests: [tests/phase_z2/test_phase_z2_retry_frame_internal_fit_candidate.py]
estimate_lines: 50
summary: pipeline.py _attempt_salvage_chain — 2 executor branches (layout_adjust + frame_internal_fit_candidate) + _SALVAGE_FAIL_BY_ACTION 2 rows + loop cap raise; layout_adjust branch re-renders via render_slide(new_layout_preset)
files: [src/phase_z2_pipeline.py]
tests: [tests/phase_z2/test_phase_z2_salvage_cascade_imp88.py]
estimate_lines: 50
summary: pipeline.py — Step 17 entry triggers (3 pseudo failure_types frame_capacity_mismatch_entry / layout_zone_mismatch_entry / image_aspect_mismatch_entry after section 11.7 donor_slack branch) + image_fit standalone single-pass path + Step 17/18/19 artifact note refresh
files: [src/phase_z2_pipeline.py]
tests: [tests/phase_z2/test_phase_z2_step17_entry_triggers_imp88.py]
estimate_lines: 50
Per-unit rationale:
Out-of-scope: details_popup_escalation (#64 closed, pipeline.py:6373-6419 intact). Stage 0 normalizer (project_phase_z_normalize_gap). AI fallback (feedback_ai_isolation_contract — deterministic). Step 16/14. frame_reselect post-render. Carve-out
1186ad8(project_imp46_carveout_caveat).Rollback: 7 commits strict order u1→u7. u1+u2 no-behavior-change. u3-u5 dormant until u6/u7. Reverting u6/u7 leaves prior units dormant.
=== EVIDENCE ===
1186ad8untouched.FINAL_CONSENSUS: YES
[Codex #3] Stage 2 simulation-plan review - IMP-88
=== IMPLEMENTATION_UNITS ===
reviewed_plan: Claude #3 Stage 2 simulation-plan rewind redo
verdict: complete
unit_checks:
coverage_verdict:
layout_adjust: covered by u1/u2/u3/u6/u7.
image_fit: covered by u1/u2/u4/u7.
frame_internal_fit_candidate: covered by u1/u2/u5/u6/u7.
return_to_render: covered by u3/u4/u5 planners plus u6/u7 pipeline execution paths.
step18_linkage: covered by u2 failure classifications and u7 Step 17/18/19 refresh.
u1 rationale: Complete. It fixes the two primary router gaps: image_aspect_mismatch currently has no row and frame_capacity_mismatch currently points at frame_reselect instead of the overview's internal-fit action.
u2 rationale: Complete. The prior missing-producer issue is addressed by adding SALVAGE_FAILURE_TYPE_BY_ACTION rows for the new failed salvage actions.
u3 rationale: Complete. Uses layout preset switching rather than shared spacing shrink, matching the Phase Z spacing guardrail.
u4 rationale: Complete. Uses existing classifier image_event data and scopes changes to image fitting.
u5 rationale: Complete. Keeps frame_internal_fit_candidate inside the declared frame envelope and provides an infeasible path when no envelope exists.
u6 rationale: Complete. Extends the existing salvage loop instead of adding ad hoc dispatcher conditionals, and explicitly re-renders layout_adjust.
u7 rationale: Complete. Adds direct entry triggers for all three issue-body axes and refreshes Step 17/18/19 reporting.
Out-of-scope notes: details_popup_escalation remains #64 work; Step 14/16 detection changes, Stage 0 normalizer integration, AI fallback activation, and sample-specific MDX handling remain excluded.
Rollback strategy: Acceptable. Seven ordered commits u1->u7; u1/u2 are data-surface only, u3-u5 stay dormant until u6/u7, and reverting u6/u7 restores the prior dispatcher behavior.
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
FINAL_CONSENSUS: YES
[Claude #4] Stage 3 code-edit round 1 — IMP-88 u1
Executing unit: u1
unit_executed
u1 — Router rows + status surface for Step 17 retry chain (data-surface only).
Stage 2 binding contract: register the data-surface for
layout_adjust/image_fit/frame_internal_fit_candidateonsrc/phase_z2_router.pyso downstream units (u2 failure_router, u3/u4/u5 planners, u6 dispatcher, u7 Step 17 entry) can wire onto a single source of truth. No planner code, no orchestrator code, no AI calls — exactly per Stage 2 estimate (estimate_lines=30).files_changed
src/phase_z2_router.py— ACTION_BY_CATEGORY + ACTION_RATIONALE + ACTION_IMPLEMENTATION_STATUS edits (data-surface only)tests/phase_z2/test_router_actions_imp88.py— NEW (12 tests covering rows, rationale, status, and route_action / route_fit_classification integration)diff_summary
src/phase_z2_router.pyACTION_BY_CATEGORYrow —image_aspect_mismatch → image_fit. Closes the unmapped classifier emission gap (src/phase_z2_classifier.py:434-447) where Stage 1 verifiedroute_action('image_aspect_mismatch')returnedproposed_action=None,implementation_status='unknown'.ACTION_BY_CATEGORYrow —frame_capacity_mismatch:frame_reselect→frame_internal_fit_candidate. PerPHASE-Z-PIPELINE-OVERVIEW.md:321Step 16 spec lock.frame_reselectremains downstream via the failure_router cascade (rerender_still_fails → frame_reselect).ACTION_RATIONALErows — forimage_aspect_mismatch+ updatedframe_capacity_mismatch(rationale text reflects the new internal-fit-first direction; honorsfeedback_phase_z_spacing_direction— no common-margin shrink claim, frame-scoped only).ACTION_IMPLEMENTATION_STATUSrows —image_fit: MISSING,frame_internal_fit_candidate: MISSING.layout_adjuststays MISSING. Inline comment documents the planner-surface flip convention (IMP-12 u7 cascade + IMP-35 u3 popup precedent): u3/u4/u5 each flip their respective row when the deterministic planner lands insrc/phase_z2_retry.py.Total touched: ~25 lines (within Stage 2
estimate_lines=30).tests/phase_z2/test_router_actions_imp88.py(NEW)12 unit tests grouped by surface:
internal/envelopekeyword check).route_action('image_aspect_mismatch')now returnsimage_fit/MISSING(closing the Stage 1 evidence gap);route_action('frame_capacity_mismatch')now returnsframe_internal_fit_candidate/MISSING; end-to-endroute_fit_classificationenriches classification entries and lists both new actions inmissing_actions_pending_impl.scope-lock verification (Stage 2 contract)
image_fit) and envelope-bound (frame_internal_fit_candidate) — honors feedback_phase_z_spacing_direction.test results (scope-qualified)
Regression check on directly-coupled tests (router popup, failure_router cascade, visual_classifier, spec taxonomy image aspect mismatch):
Scope-qualified: 12 new + 22 directly-coupled = 34 tests green. Full
tests/phase_z2/suite not re-run this turn (Stage 3 unit-scoped).remaining_units
src/phase_z2_failure_router.py: NEXT_ACTION_BY_FAILURE + SALVAGE_FAILURE_TYPE_BY_ACTION rows for the three new actions, NEXT_ACTION_IMPLEMENTATION_STATUS demotions, classifier failure_type producers.src/phase_z2_retry.py:plan_layout_adjust+apply_layout_adjust_cssplanner stub. FlipsACTION_IMPLEMENTATION_STATUS['layout_adjust']to IMPLEMENTED at the planner-surface boundary.src/phase_z2_retry.py:plan_image_fit+apply_image_fit_cssplanner stub. FlipsACTION_IMPLEMENTATION_STATUS['image_fit']to IMPLEMENTED.src/phase_z2_retry.py:plan_frame_internal_fit_candidate+ apply stub. FlipsACTION_IMPLEMENTATION_STATUS['frame_internal_fit_candidate']to IMPLEMENTED. Includes infeasible-path for missing envelope.src/phase_z2_pipeline.py: salvage dispatcher branches (_attempt_salvage_chain) forlayout_adjust+frame_internal_fit_candidate, loop cap, re-render integration.src/phase_z2_pipeline.py: Step 17 entry triggers + image_fit single-pass path + Step 17/18/19 artifact refresh.follow_up_issue_candidates
None — u1 stayed strictly within Stage 2 scope-lock. Two spec-anchor drifts noticed during research, both out-of-scope for #88 but worth a separate doc-sync issue:
docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md:125still recordsframe_capacity_mismatch → frame_reselect(the pre-u1 mapping). Now contradicts bothPHASE-Z-PIPELINE-OVERVIEW.md:321and the freshly-lockedACTION_BY_CATEGORY. Pure doc-sync, no code impact.PHASE-Z-PIPELINE-OVERVIEW.md:321names the image actionimage_fit_candidate(with_candidatesuffix); Stage 2 binding contract usesimage_fit(without suffix). The Stage 2 name was chosen explicitly (Stage 1 unresolved-question Q1 → resolved at Stage 2). Anchor doc sync candidate.Both candidates are doc-only and do not block u2~u7. Recording here for visibility per feedback_anchor_sync_rules (anchor doc SHA-aware sync), not for action in this issue.
next step
Per
feedback_one_step_per_turn: u1 deliverable is complete and the Gitea comment is the binding contract. STOP. u2 work happens in a separate turn after Stage 3 round 1 consensus.— commit_sha: pending (Stage 3 unit-execution turn — no commit per task instructions).
[Codex #4] Stage 3 code-edit verification - IMP-88 u1
Verified unit: u1 router rows + status surface only.
Verdict: correct for the single unit executed in this turn. The edit registers the primary router data surface for the three IMP-88 actions without adding planner, failure-router, dispatcher, AI, or sample-specific behavior.
Checked facts:
src/phase_z2_router.py:47addsimage_aspect_mismatch -> image_fit.src/phase_z2_router.py:48remapsframe_capacity_mismatch -> frame_internal_fit_candidate.src/phase_z2_router.py:65-71adds/updates rationale text for the two routed categories, with image handling scoped to frame/image fit and frame handling scoped to internal envelope fit.src/phase_z2_router.py:89-91keepslayout_adjustasMISSINGand registersimage_fitplusframe_internal_fit_candidateasMISSING, matching the Stage 2 plan that u3/u4/u5 flip statuses when deterministic planners land.tests/phase_z2/test_router_actions_imp88.py:40-206covers ACTION_BY_CATEGORY rows, rationale rows, implementation-status rows, androute_action/route_fit_classificationintegration for the new surfaces.git diff --name-onlyshows onlysrc/phase_z2_router.pyas a tracked file change; the new test file is untracked attests/phase_z2/test_router_actions_imp88.py, as expected for this code-edit unit before commit.Scope check:
src/phase_z2_failure_router.py,src/phase_z2_retry.py, orsrc/phase_z2_pipeline.py; those remain owned by later units.=== EVIDENCE ===
Commands run:
git status --shortgit diff -- src/phase_z2_router.py tests/phase_z2/test_router_actions_imp88.pyGet-Content -LiteralPath 'src\phase_z2_router.py'Get-Content -LiteralPath 'tests\phase_z2\test_router_actions_imp88.py'pytest -q tests/phase_z2/test_router_actions_imp88.pygit diff --stat -- src/phase_z2_router.pyrg -n "image_aspect_mismatch|frame_capacity_mismatch|image_fit|frame_internal_fit_candidate|layout_adjust" src/phase_z2_router.py tests/phase_z2/test_router_actions_imp88.pygit diff --name-onlygit ls-files --others --exclude-standard tests/phase_z2/test_router_actions_imp88.pyFiles checked:
src/phase_z2_router.pytests/phase_z2/test_router_actions_imp88.pyTest results:
pytest -q tests/phase_z2/test_router_actions_imp88.py->12 passed in 0.04sRemaining units: [u2, u3, u4, u5, u6, u7]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #5] Stage 3 code-edit round 2 — IMP-88 u2
Executing unit: u2
unit_executed
u2 — failure_router cascade extension (producers + cascade rows + status surface). Data-surface only; no planner, no orchestrator, no AI.
Stage 2 binding contract: register the failure_router data-surface so the three Step 17 retry chain actions (
layout_adjust/image_fit/frame_internal_fit_candidate) participate in the deterministic cascade. The u6 dispatcher (planned) will read salvage_steps[-1].action + retry_trace["next_action_proposal"] off this surface; u3/u4/u5 will land the planners that flip the MISSING rows to IMPLEMENTED. u2 owns mapping only — no executor, no AI, no margin shrink.files_changed
src/phase_z2_failure_router.py— registry additions only (data-surface)tests/phase_z2/test_failure_router_imp88_cascade.py— NEW (12 tests)diff_summary
src/phase_z2_failure_router.pyModule docstring update — escalation hierarchy diagram extended:
IMP-88 u2 section explains the three new failure_type → next_action rows, names the issue-body axes, and cites
feedback_phase_z_spacing_directionas the guardrail (no shared margin shrink — escalate through layout topology instead).FAILURE_TYPE_DESCRIPTIONS— three new rows:layout_adjust_insufficient— 8-preset switch executed but overflow persists; cascade exits onto frame_internal_fit_candidate before V4 top-k frame_reselect.frame_internal_fit_candidate_insufficient— in-envelope variants exhausted; cascade exits onto frame_reselect.image_fit_insufficient— Step 17 single-pass image fit could not resolve image_aspect_mismatch; escalates onto main cascade at layout_adjust.SALVAGE_FAILURE_TYPE_BY_ACTION— three new producer rows so the existing classifier's salvage_steps[-1].action path lands on the new failure_types instead of falling through tonot_attempted:layout_adjust→layout_adjust_insufficientimage_fit→image_fit_insufficientframe_internal_fit_candidate→frame_internal_fit_candidate_insufficientNEXT_ACTION_BY_FAILURE— three new cascade rows:layout_adjust_insufficient→frame_internal_fit_candidate(closes the previously open cascade tail at layout_adjust — the existingfont_step_insufficient → layout_adjustrow had no next-step, so the dispatcher would terminate without escalation)frame_internal_fit_candidate_insufficient→frame_reselect(rejoins existingrerender_still_fails → frame_reselectmid-cascade)image_fit_insufficient→layout_adjust(Phase Z spacing direction — escalate through layout topology, not margin shrink)NEXT_ACTION_RATIONALE— three matching rationale entries.image_fit_insufficientrationale explicitly states "공통 image CSS / 공통 spacing 축소 X (Phase Z spacing direction)" so trace consumers cannot misread the cascade intent.NEXT_ACTION_IMPLEMENTATION_STATUS— two new destination rows registered as MISSING:frame_internal_fit_candidate: MISSING (u5 flips whenplan_frame_internal_fit_candidate+ apply land insrc/phase_z2_retry.py)image_fit: MISSING (u4 flips whenplan_image_fit+ apply land)layout_adjuststays MISSING (untouched — u3 flips it).details_popup_escalationstays MISSING (untouched — IMP-35 contract).frame_reselectstays MISSING.Total file change: +99 / -1. Bulk is documentation comments anchoring the cascade restructuring to
feedback_phase_z_spacing_direction,feedback_ai_isolation_contract, and IMP-12 u7 + IMP-35 u3 precedent. Effective registry rows added: 3 descriptions + 3 producers + 3 cascade rows + 3 rationales + 2 status rows = 14 deterministic mapping entries.tests/phase_z2/test_failure_router_imp88_cascade.py(NEW)12 unit tests grouped by surface:
layout_adjust_insufficient → frame_internal_fit_candidate,frame_internal_fit_candidate_insufficient → frame_reselect,image_fit_insufficient → layout_adjust) + regression guard for the 8 existing rows. Theimage_fit_insufficientrow asserts the rationale does NOT mention "shrink" (Phase Z spacing direction guardrail).classify_retry_failure → route_retry_failureround-trip for all three new salvage actions; plusenrich_retry_trace_with_failure_classificationcomposes the failure_classification + next_action_proposal fields end-to-end for the layout_adjust salvage path.Test results
scope_check
src/phase_z2_failure_router.py. No edits tosrc/phase_z2_router.py,src/phase_z2_retry.py,src/phase_z2_pipeline.py,src/phase_z2_classifier.py. Planner code (u3/u4/u5), dispatcher code (u6), Step 17 entry (u7) all untouched.image_fit_insufficientrationale explicitly forbids margin shrink (feedback_phase_z_spacing_direction).src/phase_z2_ai_fallback/*,src/phase_z2_router.py(popup gate area), or pipeline AI hook area (project_imp46_carveout_caveat).remaining_units
src/phase_z2_retry.py:plan_layout_adjust+apply_layout_adjust_cssplanner/apply pair (deterministic; flipslayout_adjustMISSING → IMPLEMENTED on router + failure_router surfaces).src/phase_z2_retry.py:plan_image_fit+apply_image_fit_css(frame-scoped object-fit + max-w/h from existing classifier image_event).src/phase_z2_retry.py:plan_frame_internal_fit_candidate+ apply (in-envelope variant only; infeasible path when envelope missing).src/phase_z2_pipeline.py: salvage dispatcher branches forlayout_adjust+frame_internal_fit_candidate+ loop cap + re-render.src/phase_z2_pipeline.py: Step 17 entry triggers +image_fitsingle-pass + Step 17/18/19 artifact refresh.follow_up_issue_candidates
None this turn. Scope-lock holds; all surfaces u2 touched are within the Stage 2 binding contract.
next_step
STOP — Codex review of u2 next, then continue_same to u3 in the following turn.
[Codex #5] Stage 3 code-edit verification - IMP-88 u2
Verified unit: u2 failure_router cascade extension.
Verdict: correct for the single unit executed in this turn. The edit adds only the failure-router data surface needed for the three IMP-88 retry actions: producer failure types, cascade rows, rationale/status surface, and focused tests. It does not add planner, dispatcher, AI, Step 16, normalizer, popup body, or sample-specific behavior.
Checked facts:
src/phase_z2_failure_router.py:120-137registers descriptions forlayout_adjust_insufficient,frame_internal_fit_candidate_insufficient, andimage_fit_insufficient.src/phase_z2_failure_router.py:165-167addsSALVAGE_FAILURE_TYPE_BY_ACTIONproducers forlayout_adjust,image_fit, andframe_internal_fit_candidate.src/phase_z2_failure_router.py:195-197routeslayout_adjust_insufficient -> frame_internal_fit_candidate,frame_internal_fit_candidate_insufficient -> frame_reselect, andimage_fit_insufficient -> layout_adjust.src/phase_z2_failure_router.py:236-250adds non-empty rationale entries for the three new failure types, including the spacing guardrail forimage_fit_insufficient.src/phase_z2_failure_router.py:281-282registersframe_internal_fit_candidateandimage_fitasMISSING, leaving planner status flips to later units.tests/phase_z2/test_failure_router_imp88_cascade.py:46-276covers the new descriptions, producers, cascade rows, implementation statuses, classifier/router end-to-end path, enrichment wrapper, and regression guards for existing IMP-12/IMP-35 rows.Scope check:
git diff --name-only -- src/phase_z2_failure_router.py tests/phase_z2/test_failure_router_imp88_cascade.py src/phase_z2_retry.py src/phase_z2_pipeline.pyshows onlysrc/phase_z2_failure_router.pyamong tracked files in the u2 scope; the new test is untracked attests/phase_z2/test_failure_router_imp88_cascade.py.src/phase_z2_retry.pyorsrc/phase_z2_pipeline.py; u3-u7 still own planners, dispatcher, and Step 17 entry wiring.=== EVIDENCE ===
Commands run:
git status --shortgit diff -- src/phase_z2_failure_router.py tests/phase_z2/test_failure_router_imp88_cascade.pyGet-Content -LiteralPath 'src\phase_z2_failure_router.py'Get-Content -LiteralPath 'tests\phase_z2\test_failure_router_imp88_cascade.py'pytest -q tests/phase_z2/test_failure_router_imp88_cascade.pyrg -n "layout_adjust_insufficient|frame_internal_fit_candidate_insufficient|image_fit_insufficient|SALVAGE_FAILURE_TYPE_BY_ACTION|NEXT_ACTION_BY_FAILURE|NEXT_ACTION_IMPLEMENTATION_STATUS|classify_retry_failure" src/phase_z2_failure_router.py tests/phase_z2/test_failure_router_imp88_cascade.pygit diff --name-only -- src/phase_z2_failure_router.py tests/phase_z2/test_failure_router_imp88_cascade.py src/phase_z2_retry.py src/phase_z2_pipeline.pygit ls-files --others --exclude-standard tests/phase_z2/test_failure_router_imp88_cascade.pyFiles checked:
src/phase_z2_failure_router.pytests/phase_z2/test_failure_router_imp88_cascade.pyTest results:
pytest -q tests/phase_z2/test_failure_router_imp88_cascade.py->12 passed in 0.04sRemaining units: [u3, u4, u5, u6, u7]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #6] Stage 3 code-edit round 3 — IMP-88 u3
Executing unit: u3
unit_executed
u3 —
plan_layout_adjustplanner +apply_layout_adjust_layout_csshelper (Step 17 retry chain — 8-preset topology swap). Plan/apply pair only — no dispatcher wiring, no Step 17 entry, no AI. u6 (dispatcher) and u7 (Step 17 entry) own the runtime callers.Stage 2 binding contract: planner returns
{feasible, new_layout_preset, new_zones_data, position_remap, candidates_considered, swap_topology_from/to, unit_count}on success;{feasible=False, failure_reason}on infeasible.apply_layout_adjust_layout_css(plan, gap_px)builds a freshlayout_cssviabuild_layout_csswith provenance annotations (layout_adjust_applied/from/to). Honors feedback_phase_z_spacing_direction — topology swap only, no shared margin / gap / slide-body shrink.files_changed
src/phase_z2_retry.py—_layout_swap_priority+plan_layout_adjust+apply_layout_adjust_layout_cssappended afterapply_font_step_compression_css. 120 net lines added (+0 / -0 existing), within Stage 2estimate_lines=50order of magnitude (existing planner cluster shares header/docstring overhead).tests/phase_z2/test_phase_z2_retry_layout_adjust.py— NEW (10 tests).diff_summary
src/phase_z2_retry.py(new additions, appended only — existing planners untouched)_layout_swap_priority(current_topology, candidate_topology)— deterministic preference rule:rows↔cols(horizontal-2 ↔ vertical-2 orientation mirror)T↔inverted-T(top-1-bottom-2 ↔ top-2-bottom-1)side-T-left↔side-T-right(left-1-right-2 ↔ left-2-right-1)unit_countsibling (catalog order tiebreak)plan_layout_adjust(*, current_layout_preset, zones_data)LAYOUT_PRESETSfromsrc.phase_z2_composition(catalog:templates/phase_z2/layouts/layouts.yaml, 8-preset vocabulary lock 2026-05-07).failure_reason:'... not in LAYOUT_PRESETS catalog ...'len(zones_data) != len(current_positions)→'... cannot remap to a sibling preset.'unit_count→'... single (1) and grid-2x2 (4) have no swap target by catalog design.'(_layout_swap_priority, catalog_order.index); pickscandidates[0]; emitsposition_remap(current→new) +new_zones_data({**zd, "position": new_positions[i]}preserving template_id / slot_payload / min_height_px / content_weight).apply_layout_adjust_layout_css(plan, gap_px)→Optional[dict]None(dispatcher u6 will skip the re-render).layout_cssdict via deferredfrom src.phase_z2_pipeline import build_layout_css(deferred to break the retry↔pipeline circular import — pipeline imports retry at module top, retry references pipeline only at call time once both are fully loaded).raw_zone_layout["layout_adjust_applied"]=True+layout_adjust_from+layout_adjust_toso retry trace surfaces the swap.tests/phase_z2/test_phase_z2_retry_layout_adjust.py(NEW)10 unit tests grouped by surface:
planner success paths (3):
test_horizontal_2_swaps_to_vertical_2_orientation_axis— full success contract: feasible=True, new_layout_preset='vertical-2', unit_count=2, swap_topology rows→cols, position_remap {top: left, bottom: right}, non-position payload preserved through remap.test_vertical_2_swaps_back_to_horizontal_2— symmetry: reverse swap returns to 'horizontal-2'.test_T_swaps_to_inverted_T_first_by_topology_priority— topology mirroring wins over catalog order: top-1-bottom-2 → top-2-bottom-1 (priority 1) before side-T variants (priority 2). Asserts full candidates_considered order.planner infeasible paths (4):
test_single_preset_infeasible_no_sibling—unit_count=1, candidates_considered=[], failure_reason mentions'single (1)'.test_grid_2x2_preset_infeasible_no_sibling—unit_count=4, candidates_considered=[], failure_reason mentions'grid-2x2 (4)'.test_unknown_preset_infeasible_with_clear_reason— catalog miss path, failure_reason contains'not in LAYOUT_PRESETS catalog'.test_zone_count_mismatch_infeasible— length 3 vs horizontal-2 (2 positions), failure_reason contains both'length 3'and'horizontal-2'.apply paths (3):
test_apply_layout_adjust_builds_new_layout_css_with_provenance— horizontal-2→vertical-2 path returns valid layout_css withraw_zone_layout["layout_adjust_applied"]=True+ from/to fields; css areas matchLAYOUT_PRESETS["vertical-2"]["css_areas"].test_apply_layout_adjust_infeasible_returns_none— infeasible plan short-circuits.test_apply_layout_adjust_T_swap_produces_2d_dynamic_css— top-1-bottom-2 → top-2-bottom-1 path exercises_build_grid_dynamic_2d(dynamic_rows=True, dynamic_cols=True).Test result:
pytest -q tests/phase_z2/test_phase_z2_retry_layout_adjust.py→ 10 passed in 0.17s.scope guardrails honored
LAYOUT_PRESETS(catalogtemplates/phase_z2/layouts/layouts.yaml), no MDX-specific paths, no sample-keyed logic.src/phase_z2_retry.py(nodetails_popup_escalation/frame_reselect/ IMP-47B cache area)._layout_swap_prioritymatrix derived fromtemplates/phase_z2/layouts/layouts.yamltopology values (rows/cols/T/inverted-T/side-T-left/side-T-right/single/2x2); 2026-05-07 catalog lock unchanged.regression check (scope-qualified)
pytest -q tests/phase_z2/test_router_actions_imp88.py tests/phase_z2/test_failure_router_imp88_cascade.py tests/phase_z2/test_phase_z2_cross_zone_redistribute.py tests/phase_z2/test_phase_z2_retry_multi_donor.py tests/phase_z2/test_phase_z2_retry_measured_bound.py→ 35 passed in 0.10s. u1 (12) + u2 (12) + cross_zone (8 prior) + multi_donor + measured_bound suites green; no behavior change in existing retry planners.git diff --name-only(tracked) →src/phase_z2_failure_router.py(u2),src/phase_z2_retry.py(u3),src/phase_z2_router.py(u1). New test file attests/phase_z2/test_phase_z2_retry_layout_adjust.pyis untracked (consistent with u1/u2 pre-commit pattern verified by Codex r1/r2).remaining_units
[u4, u5, u6, u7]
follow_up_issue_candidates
None — scope-lock holds. The deferred
from src.phase_z2_pipeline import build_layout_cssinsideapply_layout_adjust_layout_cssis the established cross-module pattern (mirrorsplan_cross_zone_redistributedeferringfrom src.fit_verifier import redistribute) and does not introduce a new module dependency cycle. Position remap is order-preserving by catalog convention (positions list inlayouts.yaml); if a future caller relies on semantic position mapping (e.g., "top" must always carry the most-content zone), that becomes a new axis owned by composition planner, not Step 17 retry.[Codex #6] Stage 3 code-edit verification - IMP-88 u3
Verified unit: u3 layout_adjust planner/apply surface.
Verdict: correct for the single unit executed in this turn. The edit adds the deterministic
layout_adjustplanner and layout CSS apply helper insrc/phase_z2_retry.py, plus focused tests. It does not wire dispatcher runtime, Step 17 entry, Step 16, AI, popup escalation, normalizer, or sample-specific behavior.Checked facts:
src/phase_z2_retry.py:443adds_layout_swap_priority, preferring rows/cols, T/inverted-T, then side-T-left/side-T-right topology swaps.src/phase_z2_retry.py:455addsplan_layout_adjust, returning the required success payload:feasible,new_layout_preset,unit_count,candidates_considered,swap_topology_from,swap_topology_to,position_remap, andnew_zones_data.src/phase_z2_retry.py:467-508returns explicit infeasible plans for unknown presets, zone-count mismatch, and no same-unit-count render-ready sibling.src/phase_z2_retry.py:530addsapply_layout_adjust_layout_css, returningNonefor infeasible plans and building a freshbuild_layout_css(new_preset, new_zones_data, gap=gap_px)payload for feasible plans.src/phase_z2_retry.py:545-549recordslayout_adjust_applied,layout_adjust_from, andlayout_adjust_toprovenance inraw_zone_layout.tests/phase_z2/test_phase_z2_retry_layout_adjust.py:40-167covers horizontal/vertical swaps, T/inverted-T priority, infeasible cases, provenance, and 2-D dynamic CSS output.Scope check:
git diff --name-only -- src/phase_z2_retry.py tests/phase_z2/test_phase_z2_retry_layout_adjust.py src/phase_z2_pipeline.py src/phase_z2_failure_router.py src/phase_z2_router.pyshowssrc/phase_z2_retry.pyas the u3 tracked implementation file; the router and failure-router diffs are prior u1/u2 work.git ls-files --others --exclude-standard tests/phase_z2/test_phase_z2_retry_layout_adjust.pyconfirms the new focused u3 test file is untracked before commit.src/phase_z2_pipeline.py; u6/u7 still own dispatcher and Step 17 entry wiring.=== EVIDENCE ===
Commands run:
git status --shortgit diff -- src/phase_z2_retry.py tests/phase_z2/test_phase_z2_retry_layout_adjust.pyGet-Content -LiteralPath 'src\phase_z2_retry.py'Get-Content -LiteralPath 'tests\phase_z2\test_phase_z2_retry_layout_adjust.py'pytest -q tests/phase_z2/test_phase_z2_retry_layout_adjust.pyrg -n "def _layout_swap_priority|def plan_layout_adjust|def apply_layout_adjust_layout_css|layout_adjust_applied|build_layout_css|gap|margin|slide-body|new_layout_preset|new_zones_data" src/phase_z2_retry.py tests/phase_z2/test_phase_z2_retry_layout_adjust.pygit diff --name-only -- src/phase_z2_retry.py tests/phase_z2/test_phase_z2_retry_layout_adjust.py src/phase_z2_pipeline.py src/phase_z2_failure_router.py src/phase_z2_router.pygit ls-files --others --exclude-standard tests/phase_z2/test_phase_z2_retry_layout_adjust.pyFiles checked:
src/phase_z2_retry.pytests/phase_z2/test_phase_z2_retry_layout_adjust.pyTest results:
pytest -q tests/phase_z2/test_phase_z2_retry_layout_adjust.py->10 passed in 0.08sRemaining units: [u4, u5, u6, u7]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #7] Stage 3 code-edit round 4 — IMP-88 u4
Executing unit: u4
unit_executed
u4 —
plan_image_fit+apply_image_fit_css(Step 17 entry single-pass, image-scoped CSS overrides). Plan/apply pair only — no dispatcher wiring, no Step 17 entry runtime, no AI. u7 (Step 17 entry) owns the runtime caller.Stage 2 binding contract: planner consumes a single
image_eventfromoverflow_metrics.image_events(the shape produced atsrc/phase_z2_pipeline.py:3019-3060:natural_w/h,rendered_w/h,natural_ratio,rendered_ratio,delta,src,zone_position,zone_template_id) and returns{feasible, css_overrides: {object_fit, max_width_px, max_height_px, width, height}, delta, correction_axis, natural_*, rendered_*}on success, or{feasible=False, failure_reason}on no-op / infeasible.apply_image_fit_css(plan)builds a frame-scoped CSS rule string. Honors feedback_phase_z_spacing_direction — image-scoped only, no common margin / frame envelope shrink.files_changed
src/phase_z2_retry.py—plan_image_fit+apply_image_fit_cssappended afterapply_layout_adjust_layout_css. ~110 net lines added (function bodies + section header), within Stage 2estimate_lines=45order of magnitude (planner cluster shares header/docstring overhead with u3).tests/phase_z2/test_phase_z2_retry_image_fit.py— NEW (13 tests).diff_summary
src/phase_z2_retry.py(new additions, appended only — existing planners untouched)Section header comment — IMP-88 u4 block documenting:
overflow_metrics.image_eventsdirectly (persrc/phase_z2_pipeline.py:3019-3060JS injection shape).feedback_phase_z_spacing_direction— image-scoped CSS only.delta_tol=0.05mirrorssrc.phase_z2_pipeline.IMAGE_ASPECT_DELTA_TOL(line 150) but is exposed as a kwarg so tests stay free of the pipeline import cycle (same pattern the classifier sidesteps via deferredfrom phase_z2_pipeline import IMAGE_ASPECT_DELTA_TOLatsrc/phase_z2_classifier.py:360).plan_image_fit(*, image_event, delta_tol=0.05)→dictdelta is None→feasible=False,failure_reason="image_event delta is None — image not loaded; no aspect mismatch can be measured."Mirrors classifier line 431 contract (delta is None ⇒ skip — image not loaded; no false positive).|delta| <= delta_tol→feasible=False,failure_reasoncites computed|delta|+ tolerance. Strict-greater-than emission threshold matchessrc/phase_z2_classifier.py:433(abs(delta) > IMAGE_ASPECT_DELTA_TOL).rendered_worrendered_h→feasible=False,failure_reason="image_event missing positive rendered_w / rendered_h — cannot bound max-width / max-height for image-scoped CSS."feasible=Truecorrection_axis = "width" if delta > 0 else "height"(delta>0 ⇒ rendered_ratio > natural_ratio ⇒ rendered too wide ⇒ width axis correction; delta<0 ⇒ height axis correction).css_overrides = {"object_fit": "contain", "max_width_px": int(rendered_w), "max_height_px": int(rendered_h), "width": "auto", "height": "auto"}—object-fit: containpreserves natural aspect (the only safe transform — image not cropped, image not stretched; honors CLAUDE.md image-processing principle).max-w/hclamps to current rendered bounds (no envelope growth, no envelope shrink).apply_image_fit_css(plan)→Optional[str]None(u7 will skip the re-render injection).src→ selector =.zone[data-zone-position="<pos>"] img[src="<src>"]. The src predicate prevents the rule from cross-leaking onto siblings if a zone holds multiple<img>(table_events / IMP-15 idiom: one event per<img>).src→ selector falls back to.zone[data-zone-position="<pos>"] img(defensive — emptysrcis theoretically valid for inline-rendered images; logged as no-op-equivalent CSS but stays scoped to the zone).css_overrides. Nopadding/margin/gap/.slide-body/.zone-containertouched.tests/phase_z2/test_phase_z2_retry_image_fit.py(NEW — 13 tests)Grouped by surface:
Planner success paths (3):
test_positive_delta_emits_width_correction_axis— natural 1200×800, rendered 600×300, delta=+0.5 →correction_axis='width',max_width_px=600,max_height_px=300.test_negative_delta_emits_height_correction_axis— natural 1600×800, rendered 400×400, delta=-0.40 →correction_axis='height'.test_planner_passes_through_zone_and_template_metadata— src / zone_position / zone_template_id surface from image_event into plan.Planner infeasible / no-op paths (6):
test_delta_none_infeasible_with_clear_reason— mirrors classifierdelta is None ⇒ skipcontract.test_delta_within_tolerance_returns_planner_noop—delta=0.02< defaulttol=0.05.test_delta_at_boundary_is_planner_noop—|delta|==tolis no-op (strict-greater-than emission threshold matches classifier).test_rendered_w_zero_infeasible—rendered_w=0⇒ infeasible with rendered_w/h reason.test_rendered_h_missing_infeasible— poppedrendered_h⇒ infeasible.test_custom_delta_tol_widens_noop_band— caller-supplieddelta_tolkwarg works.apply_image_fit_cssCSS construction (4):test_apply_image_fit_css_emits_zone_and_src_scoped_selector— selector + 5 properties verified.test_apply_image_fit_css_without_src_falls_back_to_zone_only_selector— empty src ⇒.zone[...] img {shape.test_apply_image_fit_css_infeasible_returns_none.test_apply_image_fit_css_does_not_shrink_shared_spacing— explicit guardrail: CSS must NOT contain.slide-body,.zone-container,padding:,margin:,gap:,grid-gap. Mirrorsfeedback_phase_z_spacing_direction.test results
Regression check across u1+u2+u3+u4 IMP-88 surface:
scope_lock verification
git diff --stat -- src/phase_z2_retry.py→ 229 insertions, single file (combined u3+u4 additions; u4-only span = ~110 linesplan_image_fit+apply_image_fit_css+ section header).git diff --name-only -- src/phase_z2_pipeline.py→ empty (no u6/u7 leakage).git diff -- src/phase_z2_retry.py | grep '^\+.*def 'lists exactly:_layout_swap_priority/plan_layout_adjust/apply_layout_adjust_layout_css(u3, prior turn) +plan_image_fit/apply_image_fit_css(u4, this turn). No edits to existing planners.frame_internal_fit_candidateplanner — u5 owns that.guardrails honored
feedback_phase_z_spacing_direction— image-scoped CSS only (object-fit: contain+max-w/hclamped to current rendered bounds). Explicit test asserts CSS does NOT touch.slide-body,.zone-container,padding,margin,gap,grid-gap. No envelope shrink —max-w/h≤ current rendered bounds bounds the image within its current area, never expanding the frame.feedback_no_hardcoding—delta_tolis a parameter (default mirrors the canonicalIMAGE_ASPECT_DELTA_TOL=0.05). No inline mdx-specific conditionals.feedback_ai_isolation_contract— no AI calls; planner is fully deterministic.feedback_one_step_per_turn— exactly u4 implemented this turn.project_imp46_carveout_caveat— u4 touchessrc/phase_z2_retry.py(planner-only surface), not the AI reject path area of #76 commit1186ad8.remaining_units
[u5, u6, u7]
plan_frame_internal_fit_candidateplanner + apply helper (frame contract envelope, infeasible-when-no-envelope path) insrc/phase_z2_retry.py+tests/phase_z2/test_phase_z2_retry_frame_internal_fit.py._attempt_salvage_chaininsrc/phase_z2_pipeline.py:2574-2604) forlayout_adjust+frame_internal_fit_candidatebranches + loop cap + re-render;tests/phase_z2/test_phase_z2_pipeline_salvage_imp88.py.image_fitsingle-pass path consuming u4's planner) + Step 17/18/19 artifact refresh;tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py.follow_up_issue_candidates
None this turn — u4 strictly within Stage 2 scope-lock. No new axes surfaced during implementation.
stop_marker
STOP per Stage 3 protocol — Gitea comment is the deliverable. No commit, no push this turn.
[Codex #7] Stage 3 code-edit verification - IMP-88 u4
Verified unit: u4 image_fit planner/apply surface.
Verdict: correct for the single unit executed in this turn. The edit adds the deterministic
image_fitplanner and frame-scoped CSS apply helper insrc/phase_z2_retry.py, plus focused tests. It does not wire dispatcher runtime, Step 17 entry, Step 16, AI, popup escalation, normalizer, or sample-specific behavior.Checked facts:
src/phase_z2_retry.py:564addsplan_image_fit, consuming a singleimage_eventwithsrc,zone_position,zone_template_id, ratio fields, rendered dimensions, anddelta.src/phase_z2_retry.py:579-604returns explicit infeasible/no-op plans fordelta is None,abs(delta) <= delta_tol, and missing or non-positiverendered_w/rendered_h.src/phase_z2_retry.py:605-628returns the required success payload:feasible,css_overrides,delta,correction_axis, natural dimensions/ratio fields, and rendered dimensions/ratio fields.src/phase_z2_retry.py:631addsapply_image_fit_css, returningNonefor infeasible plans and a.zone[data-zone-position="..."] img/img[src="..."]scoped CSS rule for feasible plans.object-fit,max-width,max-height,width,height) and does not alter shared margins, gaps, slide body, frame envelope, or common spacing.tests/phase_z2/test_phase_z2_retry_image_fit.py:55-207covers positive/negative delta success paths, metadata passthrough, no-op/infeasible cases, custom tolerance, scoped CSS generation, src fallback, and shared-spacing guardrails.Scope check:
git diff --name-only -- src/phase_z2_retry.py tests/phase_z2/test_phase_z2_retry_image_fit.py src/phase_z2_pipeline.py src/phase_z2_failure_router.py src/phase_z2_router.pyshowssrc/phase_z2_retry.pyas the u4 tracked implementation file; router and failure-router diffs are prior u1/u2 work.git ls-files --others --exclude-standard tests/phase_z2/test_phase_z2_retry_image_fit.pyconfirms the focused u4 test file is untracked before commit.src/phase_z2_pipeline.py; u6/u7 still own dispatcher and Step 17 entry wiring.=== EVIDENCE ===
Commands run:
git status --shortgit diff -- src/phase_z2_retry.py tests/phase_z2/test_phase_z2_retry_image_fit.pyGet-Content -LiteralPath 'src\phase_z2_retry.py'Get-Content -LiteralPath 'tests\phase_z2\test_phase_z2_retry_image_fit.py'rg -n "def plan_image_fit|def apply_image_fit_css|IMP-88 u4|def plan_layout_adjust|def apply_layout_adjust_layout_css|def plan_cross_zone_redistribute|def plan_glue_compression|def plan_font_step_compression" src/phase_z2_retry.py tests/phase_z2/test_phase_z2_retry_image_fit.pypytest -q tests/phase_z2/test_phase_z2_retry_image_fit.pygit diff --name-only -- src/phase_z2_retry.py tests/phase_z2/test_phase_z2_retry_image_fit.py src/phase_z2_pipeline.py src/phase_z2_failure_router.py src/phase_z2_router.pygit ls-files --others --exclude-standard tests/phase_z2/test_phase_z2_retry_image_fit.pyFiles checked:
D:\ad-hoc\kei\design_agent\src\phase_z2_retry.pyD:\ad-hoc\kei\design_agent\tests\phase_z2\test_phase_z2_retry_image_fit.pyTest results:
pytest -q tests/phase_z2/test_phase_z2_retry_image_fit.py-> 13 passed in 0.04sRemaining units: [u5, u6, u7]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #8] Stage 3 code-edit round 5 — IMP-88 u5
Executing unit: u5
unit_executed
u5 —
plan_frame_internal_fit_candidate+apply_frame_internal_fit_candidate_css(Step 17 retry chain — frame-contract envelope only). Plan/apply pair only — no dispatcher wiring, no Step 17 entry runtime, no AI. u6 (dispatcher) and u7 (Step 17 entry) own the runtime callers.Stage 2 binding contract: planner consumes
frame_template_id+ optionalframe_contract(overridable kwarg, mirrors u4'sdelta_tolto keep tests free of the mapper catalog cache / pipeline import cycle) + optionaloverflow_zone(providesexcess_y). Returns{feasible, envelope_present, envelope_keys, candidates_considered, selected_variant, selected_variant_budget_px, excess_y, css_overrides}on success;{feasible=False, envelope_present, candidates_considered, failure_reason}on infeasible (4 distinct paths).apply_frame_internal_fit_candidate_css(plan)builds a frame-scoped CSS rule (.zone[data-template-id="<id>"]selector) from the selected variant'scss_overrides. Honorsfeedback_phase_z_spacing_direction— frame-scoped only, no common margin / slide-body / zone gap shrink.files_changed
src/phase_z2_retry.py—plan_frame_internal_fit_candidate+apply_frame_internal_fit_candidate_cssappended afterapply_image_fit_css. ~145 net lines added (section header + planner with 4 infeasible branches + apply helper). Within Stage 2estimate_lines=50order of magnitude relative to u3 (120 lines actual) and u4 (110 lines actual) — header / docstring / multiple infeasible failure_reason branches share the convention.tests/phase_z2/test_phase_z2_retry_frame_internal_fit.py— NEW (13 tests).diff_summary
src/phase_z2_retry.py(new additions, appended only — existing planners untouched)Section header comment — IMP-88 u5 block documenting:
PHASE-Z-PIPELINE-OVERVIEW.md:333— sub-mechanism allowed names (density envelope / line rhythm / internal grid row / text block allocation) all unified under the single labelframe_internal_fit_candidateso common-CSS/padding shrink antipatterns stay quarantined per[[feedback_phase_z_spacing_direction]].excess_budget_px >= effective excess_y(greedy). Catalog order = catalog author's priority (mirrors u3's_layout_swap_prioritypriority semantics).internal_envelope, so planner returnsinfeasible(envelope_present=False)for every live frame today. Cascade hand-off (from u2):NEXT_ACTION_BY_FAILURE['frame_internal_fit_candidate_insufficient'] = 'frame_reselect'.frame_contractis overridable kwarg (mirrors u4delta_tolpattern); whenNone, defersfrom src.phase_z2_mapper import get_contractto break the retry↔mapper cache binding (tests pay no catalog I/O).plan_frame_internal_fit_candidate(*, frame_template_id, frame_contract=None, overflow_zone=None)→dictget_contract(template_id)returnsNone→feasible=False, envelope_present=False, failure_reason="no frame contract registered for template_id '<id>' — cannot enumerate internal_envelope."internal_envelopedict →failure_reasoncites template_id + "cascade should escalate to frame_reselect" (mirrors u2'sframe_internal_fit_candidate_insufficient → frame_reselectcascade row).internal_envelope.variantsempty →envelope_present=True, envelope_keys=sorted(envelope.keys()), candidates_considered=[].excess_budget_px >= excess_y→envelope_present=True, excess_y, candidates_considered=[v.name, ...], failure_reasoncites variant count + excess_y.excess_y = ceil(overflow_zone['excess_y'])(sub-pixel rounding mirrorsplan_zone_ratio_retryat line 77); walks variants in catalog order, picks first variant withexcess_budget_px >= excess_y. Emits{feasible=True, envelope_present=True, envelope_keys, candidates_considered, selected_variant, selected_variant_budget_px, excess_y, css_overrides: dict(variant['css_overrides'])}.action="frame_internal_fit_candidate"+frame_template_idfor downstream salvage_steps[-1] consumption (u2'sSALVAGE_FAILURE_TYPE_BY_ACTION['frame_internal_fit_candidate'] = 'frame_internal_fit_candidate_insufficient').apply_frame_internal_fit_candidate_css(plan)→Optional[str]None(dispatcher u6 will skip the re-render)..zone[data-template-id="<template_id>"]+ body lines fromplan["css_overrides"].items()(one<prop>: <value>;per line).apply_image_fit_css(.zone[data-zone-position="..."]) and the DOM marker attemplates/phase_z2/slide_base.html:369(data-template-id="{{ zone.template_id }}").css_overrides.tests/phase_z2/test_phase_z2_retry_frame_internal_fit.py(NEW)13 unit tests grouped by surface:
Planner — no-envelope infeasible paths (4):
test_no_contract_infeasible_with_clear_reason— empty-dict contract → envelope_present=False + "internal_envelope" in failure_reason.test_contract_lookup_none_returns_no_contract_failure— sentinel template_id__sentinel_unregistered_template__exercises the defaultget_contractfallback path; mapper returnsNone; planner emits "no frame contract registered" + template_id in failure_reason.test_contract_without_internal_envelope_infeasible— contract declares nointernal_envelopekey → failure_reason cites template_id + "frame_reselect".test_envelope_present_but_empty_variants_infeasible—internal_envelope.variants=[]→ envelope_present=True, candidates_considered=[], "no variants" in failure_reason.Planner — feasible paths (5):
test_single_variant_within_budget_is_selected— 1 variant, excess_y=24, budget=40 → selected_variant emitted with budget + css passthrough (font-size +--frame-density).test_greedy_walk_picks_first_variant_that_fits_in_catalog_order— 3 variants (budgets 10/60/200), excess_y=45 → second variant selected (first that fits); candidates_considered records full ordered name list.test_no_overflow_zone_picks_first_variant— overflow_zone=None → excess_y defaults to 0 → first catalog variant wins (5/100 case → "first").test_all_variants_below_budget_returns_infeasible_with_excess— budgets (10, 25), excess_y=80 → infeasible; failure_reason cites "excess_y=80px"; candidates_considered records full list.test_excess_y_is_ceil_rounded— excess_y=23.4 → rounds up to 24; variant(budget=24) exact-fits.Planner — telemetry (1):
test_envelope_keys_recorded_for_telemetry— envelope_keys surfaces extra keys (envelope_kind) alongsidevariantsfor downstream debug.json.Apply — frame-scoped CSS snippet (3):
test_apply_emits_frame_template_scoped_selector— selector.zone[data-template-id="three_parallel_requirements"]+ body lines for--frame-density,font-size,line-height.test_apply_infeasible_returns_none— apply returns None on infeasible plan.test_apply_does_not_shrink_shared_spacing— explicit guardrail: emitted CSS must not contain.slide-body,.zone-container,grid-gap,gap:,padding:,margin:. Citesfeedback_phase_z_spacing_direction.verification
Cross-unit regression sanity (u1+u2+u3+u4+u5 together):
scope_lock observance
src/phase_z2_retry.pyonly (verifiedgit diff --name-onlyreturns only the three modified files = u1/u2 router/failure_router from earlier rounds + u5 retry.py from this round; no other tracked file touched).tests/phase_z2/test_phase_z2_retry_frame_internal_fit.py(no commit, per "DO NOT commit or push" rule)._attempt_salvage_chaininsrc/phase_z2_pipeline.py:2598-2604untouched.src/phase_z2_pipeline.pyuntouched.src/phase_z2_router.py+src/phase_z2_failure_router.pycarry only the prior u1/u2 staged data-surface changes..zone[data-template-id="..."]-scoped selector with author-declared envelopecss_overrides; explicit guardrail test asserts.details_popup_escalationbody work (closed in #64 — out of scope per Stage 1).1186ad8AI reject path untouched (project_imp46_carveout_caveat).stage 2 alignment
src/phase_z2_retry.pytests/phase_z2/test_phase_z2_retry_frame_internal_fit.py(NEW, 13 tests)internal_envelope; apply scopes to.zone[data-template-id="<id>"]remaining_units
[u6, u7]_attempt_salvage_chaindispatcher branches forlayout_adjust+frame_internal_fit_candidate(loop cap + re-render). 1 file:src/phase_z2_pipeline.py. 1 test:tests/phase_z2/test_phase_z2_pipeline_salvage_imp88.py. Will activate planners landed in u3 + u5 by registering executors into_SALVAGE_FAIL_BY_ACTIONdispatch table and flippingACTION_IMPLEMENTATION_STATUSrows forlayout_adjust+frame_internal_fit_candidatefrom MISSING → IMPLEMENTED.image_fitsingle-pass path + Step 17/18/19 artifact refresh. 1 file:src/phase_z2_pipeline.py. 1 test:tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py. Will flipACTION_IMPLEMENTATION_STATUS['image_fit']MISSING → IMPLEMENTED + register Step 17 entry triggers for all three IMP-88 axes + refresh Step 17/18/19 reporting.follow_up_issue_candidates
None — Stage 2 plan scope intact. The dormant
internal_envelopecatalog field is the documented execution contract surface for future contract declarations; declaringinternal_envelopeentries on actual frame contracts (currently 0 of 32 frames) is a separate axis suitable for a follow-up issue but NOT required by #88 (this issue's scope is "register the 3 missing executors so visual_check doesn't abort", which u5 satisfies viainfeasible(envelope_present=False)cascade hand-off toframe_reselect).DO NOT commit / push — per Stage 3 orchestrator rule. Edit + test verified locally; Codex independent verification next.
[Codex #8] Stage 3 code-edit verification - IMP-88 u5
Verified unit: u5 frame_internal_fit_candidate planner/apply surface.
Verdict: correct for the single unit executed in this turn. The edit adds the deterministic frame_internal_fit_candidate planner and frame-scoped CSS apply helper in src/phase_z2_retry.py, plus focused tests. It does not wire dispatcher runtime, Step 17 entry, Step 16, AI, popup escalation, normalizer, or sample-specific behavior.
Checked facts:
Scope check:
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Remaining units: [u6, u7]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #9] Stage 3 code-edit round 6 — IMP-88 u6
Executing unit: u6
unit_executed
u6 — Salvage dispatcher branches for
layout_adjust+frame_internal_fit_candidateinsrc/phase_z2_pipeline.py._attempt_salvage_chain. Loop-cap extension via_SALVAGE_FAIL_BY_ACTION(3 → 5 entries). Plan/apply pair invocations + re-render path + step-record shape — no Step 17 entry runtime (u7 owns it), no AI, no Step 16, no normalizer, no popup body, no sample-specific behavior.Stage 2 binding contract: extend
_SALVAGE_FAIL_BY_ACTIONso the loop cap auto-scales; dispatchlayout_adjustvia the distinctrender_slide(new_preset, new_zones_data, new_layout_css)path (no CSS overlay — topology swap only per feedback_phase_z_spacing_direction); dispatchframe_internal_fit_candidatevia the existing CSS-overlay path withframe_template_idresolved fromzones_dataviacascade_inputs.zone_position.files_changed
src/phase_z2_pipeline.py— imports +_SALVAGE_FAIL_BY_ACTIONextension + dispatcher loop body (2 new branches).tests/phase_z2/test_phase_z2_pipeline_salvage_imp88.py— NEW (8 tests).tests/phase_z2/test_phase_z2_step17_salvage_chain.py— u15test_case_c_all_three_fail_revert_preservedassertion updated from 3-step to 5-step cascade (u6 loop-cap regression fix; (b)-revert semantic unchanged). Single-test surgical edit, no other test in the file touched.diff_summary
src/phase_z2_pipeline.pyImports (line 62-75) — add 4 symbols from
phase_z2_retry:plan_layout_adjust+apply_layout_adjust_layout_css(u3)plan_frame_internal_fit_candidate+apply_frame_internal_fit_candidate_css(u5)_SALVAGE_FAIL_BY_ACTION(line 2574-2590) — extended 3 → 5 entries; comment notes it mirrorsfailure_router.SALVAGE_FAILURE_TYPE_BY_ACTION(u2) and that the loop-caprange(len(_SALVAGE_FAIL_BY_ACTION))auto-scales with cascade depth:layout_adjust: layout_adjust_insufficientframe_internal_fit_candidate: frame_internal_fit_candidate_insufficientimage_fitdeliberately NOT registered (u7 owns Step 17 entry single-pass, not salvage cascade).Dispatcher loop body (line 2605-2680ish) — two new branches added BEFORE the existing CSS-overlay planner cluster:
layout_adjust(distinct render path — pre-block):plan_layout_adjust(current_layout_preset=layout_preset, zones_data=zones_data).new_layout_cssviaapply_layout_adjust_layout_css(plan, gap_px=gap_px); re-renders viarender_slide(slide_title, slide_footer, plan["new_zones_data"], plan["new_layout_preset"], new_layout_css, gap_px=gap_px)— NO CSS overlay (topology swap only honors feedback_phase_z_spacing_direction).new_layout_presetinstead ofcss_override(observability for downstream classifiers / Step 18).out_path.write_text(candidate_html)+salvage_passed=True+ return.failure_type = "layout_adjust_insufficient",continueto next loop iteration (cascade routes toframe_internal_fit_candidatevia failure_router u2).frame_internal_fit_candidate(shared CSS-overlay path):frame_template_idfromzones_databy matchingcascade_inputs.zone_position.plan_frame_internal_fit_candidate(frame_template_id=..., overflow_zone={"excess_y": float(cascade_inputs["excess_px"])}).apply_fn = apply_frame_internal_fit_candidate_css→ falls through to the shared CSS-overlay render path.out_path.write_text+ return. On FAIL →failure_type = "frame_internal_fit_candidate_insufficient"→ cascade routes toframe_reselect(NOT in_SALVAGE_FAIL_BY_ACTION→ terminal exit recorded assalvage_terminal_action).No mutation of
layout_preset/zones_data/layout_cssbetween iterations — each plan runs against the chain-entry snapshot (matches the existing pattern). MDX 원문 무손실 보존 — no content edits anywhere in the dispatcher.tests/phase_z2/test_phase_z2_pipeline_salvage_imp88.py(NEW)8 tests against
_attempt_salvage_chain, mirroring the u15 test conventions (_PROJECT_ROOTfixture,_patch_renderhelper, monkey-patchedrun_overflow_check, IMP-09 gate-passinglayout_css):test_salvage_fail_map_registers_imp88_actionsimage_fitdeliberately absenttest_layout_adjust_pass_promotes_final_htmldata-rendered-preset='vertical-2'in promoted HTML; step recordsnew_layout_preset="vertical-2"; nocss_overridefieldtest_layout_adjust_infeasible_no_sibling_cascade_advancessinglepreset → no sibling → infeasible; cascade advances toframe_internal_fit_candidate; patchedget_contract→ None → envelope-missing terminal exit (salvage_terminal_action="frame_reselect")test_layout_adjust_fail_cascade_to_frame_internal_fit_passrender_slidecalls countedtest_frame_internal_fit_candidate_pass_promotes_final_htmlinitial_failure_type="layout_adjust_insufficient"; stub envelope variantdensity_envelopeselected;line-height: 1.4incss_overridetest_frame_internal_fit_candidate_no_envelope_cascade_terminalinternal_envelope→envelope_present=False;salvage_terminal_action="frame_reselect"test_full_5_step_cascade_all_fail_loop_cap_respectedtest_layout_adjust_step_has_no_css_overlay_fieldcss_overridekey, HASnew_layout_preset+candidate_pathtests/phase_z2/test_phase_z2_step17_salvage_chain.py(regression fix — single-test surgical edit)test_case_c_all_three_fail_revert_preservedpreviously asserted exactly 3 salvage_steps. u6's loop-cap extension means the cascade now naturally runs through 5 stages when every stage is infeasible (the emptyzones_datafrom_kwargs()makesplan_layout_adjustinfeasible via length mismatch; the empty resultingtemplate_idmakesplan_frame_internal_fit_candidateinfeasible via no-contract). Updates:len(salvage_steps) == 3→== 5(with anchor comment citing IMP-88 u6).actionslist extended to includelayout_adjust+frame_internal_fit_candidate.step["css_override"]→step.get("css_override")(layout_adjust step has nocss_overridekey by design — u6 distinct-render-path contract).No other test in the file is touched (case_a / case_b assertions remain byte-identical).
Stage 2 estimate fit
estimate_lines=50for u6 implementation — actual: ~75 lines insrc/phase_z2_pipeline.py(2 imports + 2 map entries + ~70 lines for the two dispatcher branches). Within order of magnitude — thelayout_adjustbranch is naturally larger than a single CSS-overlay branch because it owns its own render+write+step-record sequence (vs. sharing the post-branch boilerplate).Scope guard
src/phase_z2_failure_router.py(u2),src/phase_z2_router.py(u1),src/phase_z2_retry.py(u3/u4/u5) — untouched in this round; the diff entries fromgit diff --name-onlyare prior-round work already verified by Codex.1186ad8(details_popup_escalationbody) maintained; u6 only touches the salvage dispatcher loop, not the popup gate executor atphase_z2_pipeline.py:6393-6419.Test results
pytest -q tests/phase_z2/→ 571 passed (full phase_z2 suite green, including u1~u5 tests + u15 cascade tests + new u6 tests).Focused:
pytest -q tests/phase_z2/test_phase_z2_pipeline_salvage_imp88.py→ 8 passed.Focused:
pytest -q tests/phase_z2/test_phase_z2_step17_salvage_chain.py→ 3 passed.remaining_units
layout_adjust/image_fit/frame_internal_fit_candidate(per overflow classification at Step 17 entry, NOT via cascade salvage).image_fitsingle-pass path (u4 planner consumer). Step 17/18/19 artifact refresh after salvage path completes.MISSING → IMPLEMENTEDstatus flips forimage_fit(u4) andframe_internal_fit_candidate(u5) insrc/phase_z2_router.pysince the runtime caller will be wired.follow_up_issue_candidates
None within scope-lock. Out-of-scope-but-noted (already excluded by Stage 1 lock):
details_popup_escalationbody completion → tracked under #64 IMP-35 (closed deliverable verified at Stage 1).[Codex #9] Stage 3 code-edit verification - IMP-88 u6
Verified unit: u6 salvage dispatcher branches for layout_adjust and frame_internal_fit_candidate.
Verdict: correct for the single unit executed in this turn. The edit extends the Step 17 salvage loop executor map from 3 actions to 5 actions and dispatches layout_adjust and frame_internal_fit_candidate through the appropriate planner/apply paths. It does not wire the Step 17 entry runtime, image_fit single-pass, Step 16, AI, popup body, normalizer, or sample-specific behavior.
Checked facts:
Scope check:
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Remaining units: [u7]
rewind_target: continue_same
FINAL_CONSENSUS: NO
[Claude #10] Stage 3 code-edit round 7 — IMP-88 u7
Executing unit: u7
unit_executed
u7 — Step 17 entry runtime caller:
_attempt_step17_image_fit_single_passhelper + §11.7.1 image_fit single-pass entry trigger + §11.7.2 direct entry triggers (layout_adjust / frame_internal_fit_candidate / image_fit_insufficient escalation) + Step 17/18/19 artifact refresh on PASS. Single-file pipeline edit; no router/failure_router/retry changes; no AI; no Step 16; no normalizer; no popup body; no sample-specific behavior. u6 (dispatcher) already wired the cascade; u7 wires the Step 17 ENTRY.Stage 2 binding contract: §11.7.1 calls
_attempt_step17_image_fit_single_pass(helper appended after_attempt_salvage_chain— matches the u6 testability convention) to run a Step 17 ENTRY single-pass when router proposesimage_fit. PASS promotes final.html + re-runs classify + route + enrich so Step 17/18/19 artifacts reflect the post-image_fit state; FAIL recordsimage_fitstep with failure_reason so failure_router (u2)SALVAGE_FAILURE_TYPE_BY_ACTIONclassifies it asimage_fit_insufficient. §11.7.2 readssalvage_steps[-1].action/passed+router.proposed_actions_summaryand routes into_attempt_salvage_chainwith a syntheticinitial_failure_type(image_fit_insufficient/font_step_insufficient/layout_adjust_insufficient) so the cascade tail (frame_reselect → details_popup_escalation) stays consistent with the donor_slack pathway.files_changed
src/phase_z2_pipeline.py— 2 imports (apply_image_fit_css, plan_image_fit) +_attempt_step17_image_fit_single_passhelper + §11.7.1 image_fit single-pass entry trigger + §11.7.2 direct entry triggers + Step 17 artifact note update.tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py— NEW (12 tests).diff_summary
src/phase_z2_pipeline.pyImports (line 62-77) — adds 2 symbols from
phase_z2_retry:apply_image_fit_css(u4)plan_image_fit(u4)(The other 4 IMP-88 retry imports —
apply_frame_internal_fit_candidate_css,apply_layout_adjust_layout_css,plan_frame_internal_fit_candidate,plan_layout_adjust— were already added by u6.)_attempt_step17_image_fit_single_pass(...)(new helper, ~85 lines) — appended after_attempt_salvage_chainand BEFORE_remeasure_after_frame_reselect. Pure data-surface executor:image_events→ per-eventplan_image_fit(image_event=ev, delta_tol=delta_tol)→ if feasible,apply_image_fit_css(plan)accumulated.{triggered: False, ..., event_plans: [...]}early (every plan still surfaced for Step 17/18/19 telemetry continuity).render_slide, injects aggregated style block before</head>(or prepends if no</head>), writes candidate tosalvage_image_fit_candidate.html, runsrun_overflow_check.out_path; step recordspost_salvage_overflow.out_pathuntouched; step recordsfailure_reason = candidate_overflow.fail_reasons or "image_fit single-pass: overflow persists". (Strict PASS-only promotion guard, mirrors_attempt_salvage_chain.)triggered / passed / step / candidate_html / candidate_overflow / event_plans. Thestepshape matches the salvage_steps[] entry pattern thatfailure_routeru2SALVAGE_FAILURE_TYPE_BY_ACTIONreads (action="image_fit",image_fit_event_plans,candidate_path,post_salvage_overflow/failure_reason).delta_tol=IMAGE_ASPECT_DELTA_TOL(module constant atsrc/phase_z2_pipeline.py:154); exposed as kwarg so tests stay free of any pipeline-state coupling.§11.7.1 image_fit single-pass entry trigger (new block, ~40 lines) — placed AFTER the existing donor_slack salvage cascade block (§11.7) and BEFORE the popup gate (§11.8). Guards:
not retry_trace.get("retry_passed")not retry_trace.get("salvage_passed")(skip if donor_slack cascade already promoted final.html)"image_fit" in router_decision.get("proposed_actions_summary")(router u1 mapsimage_aspect_mismatch → image_fit)On trigger: calls
_attempt_step17_image_fit_single_pass(...). Onresult["triggered"]: appendsresult["step"]toretry_trace.salvage_steps, setssalvage_attempted=True. Onresult["passed"]: setssalvage_passed=True, refreshesoverflow / fit_classification / router_decision / retry_trace.failure_classificationvia the same pattern as the donor_slack block at lines 6428-6442 (Step 17/18/19 artifact refresh).§11.7.2 direct entry triggers (new block, ~80 lines) — placed AFTER §11.7.1 and BEFORE the popup gate (§11.8). Guards same as §11.7.1 (retry not passed, salvage not passed). Decision tree:
salvage_steps[-1]is aimage_fitFAIL →initial_failure_type = "image_fit_insufficient"(escalates image_fit single-pass FAIL onto the cascade per u2NEXT_ACTION_BY_FAILURE["image_fit_insufficient"] = "layout_adjust").layout_adjust→initial_failure_type = "font_step_insufficient"(u2 routesfont_step_insufficient → layout_adjust).frame_internal_fit_candidate→initial_failure_type = "layout_adjust_insufficient"(u2 routeslayout_adjust_insufficient → frame_internal_fit_candidate).On non-None
_u7_initial: assemblescascade_inputsfrom the entry classification'szone_position+debug_zones+overflow.zones(mirrors the donor_slack block's FitAnalysis/RoleFit synthesis at lines 6397-6409) and calls_attempt_salvage_chain(initial_failure_type=_u7_initial, ...). Merges_u7_salvageintoretry_trace, preserving any priorsalvage_stepsfrom §11.7.1. Onsalvage_passed: same Step 17/18/19 refresh pattern.Step 17 artifact note (line 6675) — updated to surface u6/u7 wiring:
"Terminal actions (layout_adjust / frame_reselect / details_popup_escalation) still MISSING.""IMP-88 u6/u7 — salvage cascade extended with layout_adjust + frame_internal_fit_candidate; image_fit single-pass entry triggered from router (cascade tail frame_reselect remains PARTIAL pre-render only)."tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py(new, 12 tests)Tests target
_attempt_step17_image_fit_single_passdirectly (matches the u6 convention of testing_attempt_salvage_chaindirectly intest_phase_z2_pipeline_salvage_imp88.py). Coverage:test_helper_not_triggered_when_no_image_events— empty event list → triggered=False, no render_slide call.test_helper_not_triggered_when_all_events_under_tolerance— delta=None + |delta|<=tol → all plans infeasible, no CSS, triggered=False.test_helper_not_triggered_when_apply_returns_none— rendered_w=0 → plan infeasible → no CSS, triggered=False.test_helper_pass_promotes_final_html— passed=True → out_path promoted, step has post_salvage_overflow + no failure_reason, image_fit_event_plans recorded.test_helper_fail_leaves_out_path_untouched— passed=False → out_path untouched, step has failure_reason from candidate_overflow.fail_reasons + no post_salvage_overflow.test_helper_aggregates_multi_event_css_into_single_render— 3 feasible events → ONE render_slide call, all 3 src selectors appear in the merged overlay.test_helper_candidate_path_is_project_root_relative—step["candidate_path"]ends withsalvage_image_fit_candidate.html+ is relative (not absolute) + the file actually exists on disk.test_helper_passes_delta_tol_through_to_plan_image_fit— same delta=0.10 event yields feasible plan with default tol=0.05 but infeasible with override tol=0.20.test_image_fit_stays_out_of_salvage_fail_map— re-asserts u6'simage_fit not in _SALVAGE_FAIL_BY_ACTIONguard so u7 doesn't drift the cascade map.test_helper_strictly_promotes_only_on_pass— out_path canary survives a FAIL even though the candidate file IS written (defensive PASS-only mutation gate).test_helper_event_plans_recorded_even_when_not_triggered— Step 17/18/19 telemetry continuity invariant: every plan_image_fit result surfaces underevent_planseven when no CSS was emitted.test_helper_emits_img_scoped_css_only—[[feedback_phase_z_spacing_direction]]guardrail: emitted CSS overlay containsimgselector and does NOT touch.slide-body,slide-base,--spacing-page,--spacing-block,grid-gap,padding-page.test verification
Regression check across full IMP-88 suite + cascade:
Broader phase_z2 regression (excluding the long-running VU integration suite):
scope check
git diff --name-only:git ls-files --others --exclude-standard tests/phase_z2/:u7 tracked scope (this round):
src/phase_z2_pipeline.pyonly. u7 new test:tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py. No edits tosrc/phase_z2_router.py/src/phase_z2_failure_router.py/src/phase_z2_retry.pyin this round — u1~u5 surfaces are untouched.remaining_units
none — u7 was the final Stage 2 implementation_unit. All 7 units (u1~u7) complete. Stage 3 ready for FINAL_CONSENSUS verification.
follow_up_issue_candidates
router.ACTION_IMPLEMENTATION_STATUS planner status flips —
image_fitandframe_internal_fit_candidatestill surface asMISSINGinsrc/phase_z2_router.py:89-91. The router-file comments anticipated u3/u4/u5 would flip them on planner landing; but they were held back per Stage 2 discussion ("deferred until dispatcher wires the runtime path"). With u6 (dispatcher) + u7 (Step 17 entry) now in, the actions are fully wired through the runtime. A future surgical edit could flip these status rows (router u1 + failure_router u2 status surfaces) to IMPLEMENTED — out-of-scope for #88 since it only affects observability reporting, not execution gating. Suggest a small follow-up issue (or a clean-up commit within Stage 4) rather than scope-creeping u7.frame_reselectpost-render orchestrator — Step 17 artifact note acknowledgesframe_reselectremains PARTIAL (pre-render rank-2/3 fallback implemented; post-render rerender still trace-only). Out-of-scope for #88 (issue body excludes it); the cascade currently exits atframe_reselectas a terminal action anddetails_popup_escalation(#64) picks up downstream. A dedicated frame_reselect orchestrator unit would close the last MISSING terminal — separate issue candidate.§11.7.2 entry trigger priority order observability — The if/elif chain in §11.7.2 prefers
image_fit_insufficient(post-image-fit FAIL escalation) over freshlayout_adjust/frame_internal_fit_candidaterouter proposals. This is the correct cascade-continuity behavior (carries the prior salvage_steps forward), but could benefit from a small telemetry field recording which branch fired (u7_entry_trigger_reason) — out-of-scope for the implementation contract; ergonomic improvement only.[Codex #10] Stage 3 code-edit verification - IMP-88 u7
Verified unit: u7 Step 17 entry runtime caller.
Verdict: incorrect for final Stage 3 completion. The u7 pipeline helper and focused helper tests are present, and the targeted u7 test file passes, but the final unit still leaves the issue-body implementation status surfaces at MISSING for all three IMP-88 actions. Since u7 is the last planned unit, "all units complete" is not satisfied.
Checked facts:
Scope check:
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Remaining units: [u7]
rewind_target: stage_3_edit
FINAL_CONSENSUS: NO
[Claude #11] Stage 3 code-edit Round #1 — IMP-88 u7 finalize (status-surface flips)
Executing unit: u7 (finalize — closes the Codex #10 rewind gap)
Why this turn re-attempts u7
Codex #10 rewind flagged: "the final unit still leaves the issue-body implementation status surfaces at MISSING for all three IMP-88 actions". The u7 pipeline helpers +
_attempt_step17_image_fit_single_pass+ §11.7.1 / §11.7.2 entry triggers Codex confirmed are all present insrc/phase_z2_pipeline.py(uncommitted) and the focused u7 test file passes — what was missing was the data-surface MISSING→IMPLEMENTED flips on the router-side that complete u7's end-to-end claim. Stage 2 u3/u4/u5 inline comments said "u3/u4/u5 flips when planner lands" but those flips were deferred until dispatcher (u6) + entry (u7) wired the runtime path. u7 completion is the natural carry-point for the flips (planner-surface + orchestrator-wiring together constitute IMPLEMENTED — same precedent as IMP-12 u7 cascade actions + IMP-35 u3 details_popup_escalation flip).unit_executed
u7 (Step 17 entry runtime caller — finalization round). Pipeline.py + u7 test file work from the prior round are retained unchanged (Codex #10 confirmed they pass). This round only adds the data-surface status flips + corresponding u1/u2 test alignment.
files_changed
src/phase_z2_router.py— flipACTION_IMPLEMENTATION_STATUSrows forlayout_adjust,image_fit,frame_internal_fit_candidateMISSING → IMPLEMENTED. Updated inline IMP-88 comment to reflect the u1→u7 carry chain (planner-surface + orchestrator-wiring together = IMPLEMENTED on the deterministic surface).src/phase_z2_failure_router.py— flipNEXT_ACTION_IMPLEMENTATION_STATUSrows forlayout_adjust,frame_internal_fit_candidate,image_fitMISSING → IMPLEMENTED.frame_reselect+details_popup_escalationstay MISSING (separate axes, out of IMP-88 scope). Updated IMP-88 inline comment block.tests/phase_z2/test_router_actions_imp88.py(u1 test file) — rename 3_status_stays_missing_at_u1/_status_registered_as_missing_at_u1tests to_status_implemented_after_u7; flip MISSING → IMPLEMENTED assertions; same for the tworoute_action_..._returns_..._missingtests (renamed to..._implemented). Updatetest_route_fit_classification_surfaces_imp88_actions_as_missing→..._as_implementedwith updatedmissing_actions_pending_impl(empty for IMP-88 actions) +implementation_status_summary(3 IMPLEMENTED, 0 MISSING). Module docstring updated to reflect post-u7 state.tests/phase_z2/test_failure_router_imp88_cascade.py(u2 test file) — renametest_imp88_new_next_action_destinations_registered_as_missing→..._as_implemented_after_u7; flip MISSING → IMPLEMENTED assertions forframe_internal_fit_candidate+image_fit. Updatetest_imp88_existing_implementation_status_preservedlayout_adjustMISSING → IMPLEMENTED. Update 3route_retry_failureassertions:layout_adjust_insufficient → frame_internal_fit_candidate(IMPLEMENTED),image_fit_insufficient → layout_adjust(IMPLEMENTED); keepframe_internal_fit_candidate_insufficient → frame_reselectas MISSING (out of scope). Updatetest_imp88_enrichment_composes_layout_adjust_insufficient_proposalMISSING → IMPLEMENTED. Module docstring updated.tests/phase_z2/test_phase_z2_failure_router_cascade.py— pre-IMP-88 IMP-12 cascade test (test_three_new_salvage_failure_types_route_to_expected_cascade_actions) had a regression assertion thatlayout_adjust == MISSING. Updated to IMPLEMENTED with explanatory comment citing IMP-88 u7 (2026-05-24) as the carry-point.diff_summary
Surface invariants now satisfied:
route_action("image_aspect_mismatch")→{proposed_action: "image_fit", implementation_status: "IMPLEMENTED"}.route_action("frame_capacity_mismatch")→{proposed_action: "frame_internal_fit_candidate", implementation_status: "IMPLEMENTED"}.route_action("moderate_overflow"|"layout_zone_mismatch")→{proposed_action: "layout_adjust", implementation_status: "IMPLEMENTED"}.route_retry_failure("font_step_insufficient")→{next_proposed_action: "layout_adjust", status: "IMPLEMENTED"}(closes the previously open cascade tail).route_retry_failure("layout_adjust_insufficient")→{next: "frame_internal_fit_candidate", status: "IMPLEMENTED"}.route_retry_failure("image_fit_insufficient")→{next: "layout_adjust", status: "IMPLEMENTED"}.scope_lock evidence
Only the 5 files above were touched this round. The earlier u1-u7 source/test changes (router.py / failure_router.py / retry.py / pipeline.py + u1-u7 test files) from prior rounds are retained as-is; this round adds only the data-surface flips + test-assertion alignment. AI-isolation contract honored (PZ-1): no AI calls anywhere in u1-u7. Phase Z spacing direction honored: no margin/gap/envelope shrink anywhere — the IMPLEMENTED claim covers
layout_adjust(preset switch),image_fit(frame-scoped img CSS),frame_internal_fit_candidate(inside frame envelope).remaining_units
None — Stage 2 IMPLEMENTATION_UNITS u1 through u7 are all wired end-to-end:
_attempt_salvage_chain) — DONE (prior round; pipeline.py:2614-2701 layout_adjust + frame_internal_fit_candidate branches)._attempt_step17_image_fit_single_pass+ §11.7.1 / §11.7.2 entry triggers + this round's data-surface flips) — DONE.scope-qualified verification (pytest)
pytest -q tests/phase_z2/test_router_actions_imp88.py tests/phase_z2/test_failure_router_imp88_cascade.py→ 24 passed.pytest -q tests/phase_z2/test_phase_z2_retry_layout_adjust.py tests/phase_z2/test_phase_z2_retry_image_fit.py tests/phase_z2/test_phase_z2_retry_frame_internal_fit.py tests/phase_z2/test_phase_z2_pipeline_salvage_imp88.py tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py→ 56 passed.pytest -q tests/phase_z2/test_phase_z2_step17_salvage_chain.py→ 3 passed (the u6 cascade-depth extension from 3 → 5 stages continues to hold).pytest -q tests/phase_z2/(full phase_z2 regression suite) → 583 passed, 0 failed (after the IMP-12 cascade test assertion alignment in file #5). Scope: phase_z2 module only — broader suite not exercised this turn.Single regression encountered + fixed within this turn:
tests/phase_z2/test_phase_z2_failure_router_cascade.py::test_three_new_salvage_failure_types_route_to_expected_cascade_actionshad a stalelayout_adjust == MISSINGassertion from the pre-IMP-88 IMP-12 u7 cascade landing. Updated to IMPLEMENTED with an inline comment citing IMP-88 u7 (2026-05-24) as the carry-point. This kept the IMP-12 cascade invariants intact while reflecting the post-IMP-88 status surface.follow_up_issue_candidates
None within IMP-88 scope. Two observations for the reviewer (NOT new IMP-88 work — both predate this issue):
NEXT_ACTION_IMPLEMENTATION_STATUS["frame_reselect"]remains MISSING (failure-router surface).ACTION_IMPLEMENTATION_STATUS["frame_reselect"]remains PARTIAL (router surface). This is the IMP-05 axis — out of IMP-88 scope.NEXT_ACTION_IMPLEMENTATION_STATUS["details_popup_escalation"]remains MISSING (failure-router surface). The router-surface flip already happened in IMP-35 u3 (#64). This is intentional decoupling per the inline comment block — out of IMP-88 scope.STOP — no commit, no push. Awaiting Stage 4 (test-verify) handoff.
[Codex #11] Stage 3 code-edit verification - IMP-88 u7 retry finalize
Verified unit: u7 finalization round for the Step 17 retry chain status-surface gap.
Verdict: correct. The previous Codex #10 blocker is fixed: the three issue-body actions now report IMPLEMENTED on the primary router surface and the failure-router next-action surface.
Checked facts:
src/phase_z2_router.pynow reportslayout_adjust,image_fit, andframe_internal_fit_candidateas IMPLEMENTED inACTION_IMPLEMENTATION_STATUS.route_action("image_aspect_mismatch")returnsproposed_action="image_fit"withimplementation_status="IMPLEMENTED".route_action("frame_capacity_mismatch")returnsproposed_action="frame_internal_fit_candidate"withimplementation_status="IMPLEMENTED".src/phase_z2_failure_router.pynow reportslayout_adjust,image_fit, andframe_internal_fit_candidateas IMPLEMENTED inNEXT_ACTION_IMPLEMENTATION_STATUS.route_retry_failure("image_fit_insufficient")routes tolayout_adjustwithnext_action_implementation_status="IMPLEMENTED".route_retry_failure("layout_adjust_insufficient")routes toframe_internal_fit_candidatewithnext_action_implementation_status="IMPLEMENTED".src/phase_z2_pipeline.pyretains the u7image_fitsingle-pass helper and the direct Step 17 entry wiring confirmed in Codex #10.Non-blocking note:
src/phase_z2_failure_router.py:68-70still has stale explanatory prose saying the three destinations are advertised as MISSING. The executable surfaces, route outputs, and tests now report IMPLEMENTED correctly, so this does not leave the #88 implementation status surfaces blocked.=== EVIDENCE ===
Commands run:
git diff --name-onlygit status --shortrg -n "layout_adjust|image_fit|frame_internal_fit_candidate|ACTION_IMPLEMENTATION_STATUS|NEXT_ACTION_IMPLEMENTATION_STATUS" src/phase_z2_router.py src/phase_z2_failure_router.py tests/phase_z2/test_router_actions_imp88.py tests/phase_z2/test_failure_router_imp88_cascade.py tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.pygit diff -- src/phase_z2_router.py src/phase_z2_failure_router.py src/phase_z2_pipeline.py src/phase_z2_retry.py tests/phase_z2/test_phase_z2_failure_router_cascade.py tests/phase_z2/test_phase_z2_step17_salvage_chain.pypytest -q tests/phase_z2/test_router_actions_imp88.py tests/phase_z2/test_failure_router_imp88_cascade.py tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py tests/phase_z2/test_phase_z2_failure_router_cascade.py tests/phase_z2/test_phase_z2_step17_salvage_chain.pypython -route-surface probe forroute_action,NEXT_ACTION_IMPLEMENTATION_STATUS, androute_retry_failureFiles checked:
src/phase_z2_router.pysrc/phase_z2_failure_router.pysrc/phase_z2_pipeline.pysrc/phase_z2_retry.pytests/phase_z2/test_router_actions_imp88.pytests/phase_z2/test_failure_router_imp88_cascade.pytests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.pytests/phase_z2/test_phase_z2_failure_router_cascade.pytests/phase_z2/test_phase_z2_step17_salvage_chain.pyTest results:
46 passed in 0.24sRemaining units: none
FINAL_CONSENSUS: YES
[Claude #12] Stage 4 test-verify — IMP-#88 Round #1
📌 Verification result: PASS (recommend Codex ratify)
■ Scope check vs Stage 2 plan (all 7 units present in worktree)
src/phase_z2_router.py:28-99(ACTION_BY_CATEGORY +image_aspect_mismatch→image_fit; REMAPframe_capacity_mismatch→frame_internal_fit_candidate; ACTION_IMPLEMENTATION_STATUS flips MISSING→IMPLEMENTED for 3 actions + rationale rows);tests/phase_z2/test_router_actions_imp88.py(12 tests)src/phase_z2_failure_router.py:28-285(FAILURE_TYPE_DESCRIPTIONS / SALVAGE_FAILURE_TYPE_BY_ACTION / NEXT_ACTION_BY_FAILURE / NEXT_ACTION_RATIONALE / NEXT_ACTION_IMPLEMENTATION_STATUS — 3 new producer rows + 3 new cascade rows + 3 status flips);tests/phase_z2/test_failure_router_imp88_cascade.py(12 tests)src/phase_z2_retry.py:431-553(_layout_swap_priority+plan_layout_adjust+apply_layout_adjust_layout_css);tests/phase_z2/test_phase_z2_retry_layout_adjust.py(10 tests)src/phase_z2_retry.py:555-672(plan_image_fit+apply_image_fit_css);tests/phase_z2/test_phase_z2_retry_image_fit.py(13 tests)src/phase_z2_retry.py:674-805(plan_frame_internal_fit_candidate+apply_frame_internal_fit_candidate_css);tests/phase_z2/test_phase_z2_retry_frame_internal_fit.py(13 tests)src/phase_z2_pipeline.py:2577-2728(_SALVAGE_FAIL_BY_ACTIONextended +layout_adjustdistinct-render branch +frame_internal_fit_candidateCSS-overlay branch in_attempt_salvage_chain);tests/phase_z2/test_phase_z2_pipeline_salvage_imp88.py(8 tests)src/phase_z2_pipeline.py:2729-2812(_attempt_step17_image_fit_single_passhelper) +:6526-6669(§11.7.1 image_fit entry + §11.7.2 layout_adjust / frame_internal_fit_candidate / image_fit_insufficient entry) +:6727-6731(note refresh);tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py(12 tests)■ Test results
Targeted (80 tests across u1-u7) — all PASS:
Per-unit breakdown:
Modified existing tests (Stage 2 cascade-depth + status flip alignment) — all PASS:
test_three_new_salvage_failure_types_route_to_expected_cascade_actions:layout_adjustIMPLEMENTATION_STATUS flip from MISSING→IMPLEMENTED ✓.test_case_c_all_three_fail_revert_preserved: cascade depth extended 3→5 (layout_adjust,frame_internal_fit_candidateadded at tail);out_pathuntouched contract preserved.Full phase_z2 regression (
pytest -q tests/phase_z2/):0 failures, 0 errors, 0 skips. No baseline-red flips.
■ Stage 3 route-output claim re-verification
All four Stage 3 IMP-88 IMPLEMENTED-claim destinations confirmed at the public router/failure-router surface. The single MISSING destination (
frame_reselect) is consistent with Stage 3 exit report scope (frame_reselect + details_popup_escalation remain MISSING as separate axes).■ Hardcoding compliance (RULE 7 /
feedback_no_hardcoding)src/files:grep "03\.|04\.|05\.|mdx_03|mdx_04|mdx_05|mdx03|mdx04|mdx05"over the IMP-88 diff returns zero hits.LAYOUT_PRESETS,frame_contract['internal_envelope'], classifierimage_events,zones_data). Noif template_id == "..."style guards._layout_swap_priorityuses the topology axis names"rows"/"cols","T"/"inverted-T","side-T-left"/"side-T-right"— these are the catalog-declared topology values fromLAYOUT_PRESETS(single,rows,cols,T,inverted-T,side-T-left,side-T-right,2x2). Mirror-axis priority is a deterministic ordering rule against catalog topology field, not a per-sample hard branch._attempt_salvage_chainu6 extension uses_SALVAGE_FAIL_BY_ACTION(data table) for loop cap; no inlineif next_action == "..."conditional cascade growth.■ Guardrails (Stage 1/2 binding contracts) — line-by-line check
feedback_ai_isolation_contract, PZ-1) — u3/u4/u5 planners and u6/u7 dispatcher/entry are all deterministic data-surface; zero AI calls in any new code path. Verified bygrep -n "anthropic\|Anthropic\|claude\.com\|ai_call\|prompt"over the IMP-88 diff → 0 hits.feedback_phase_z_spacing_direction) — no common margin / slide-body / gap shrink.apply_layout_adjust_layout_cssrebuilds layout_css viabuild_layout_css(new_preset, new_zones_data, gap=gap_px)with the samegap_px(topology swap, not margin shrink).apply_image_fit_cssscopes to[data-zone-position="..."] img[src="..."](zone + img-tag scope).apply_frame_internal_fit_candidate_cssscopes to[data-template-id="..."](frame scope). u4/u5 test rows explicitly assertdoes_not_shrink_shared_spacing.feedback_no_hardcoding) ��� see hardcoding compliance above; all routing is viaACTION_BY_CATEGORY/NEXT_ACTION_BY_FAILURE/SALVAGE_FAILURE_TYPE_BY_ACTION/ catalog tables.feedback_one_step_per_turn) — this turn is Stage 4 verify only; no implementation stirring.project_imp46_carveout_caveat) — IMP-88 touchessrc/phase_z2_{router,failure_router,retry,pipeline}.py+ new tests. The1186ad8(#76 AI reject path) carve-out region is in the AI fallback path which u1-u7 do not touch. Zero overlap.slide_title/footerextraction. Frame partial templates / Layer A planning unchanged.■ Out-of-scope axes (preserved per Stage 1/3 lock)
details_popup_escalation: stays cascade-terminal, IMPLEMENTED via #64 commit1186ad8. IMP-88 not modify.frame_reselect: NEXT_ACTION_IMPLEMENTATION_STATUS still MISSING by design — separate axis.mdx_normalizerintegration: untouched (project_phase_z_normalize_gaplock).feedback_ai_isolation_contractlock).■ Files changed (vs HEAD
e0c39f1)Diff stat: 842 insertions, 15 deletions across 6 modified files + 7 new test files. Aligns with Stage 2 IMPLEMENTATION_UNITS estimates (
3050 LOC per unit; actual slightly higher per planner due to comprehensive infeasible-path failure_reason wording, no scope creep).■ Honest 3-axis status
_attempt_salvage_chain+_attempt_step17_image_fit_single_passdirect unit tests in u6/u7).layout_adjust,image_fit,frame_internal_fit_candidate) all advertised IMPLEMENTED viaroute_actionandroute_retry_failureat the public surface.■ Decision
PASS — ready for Codex ratify. All 7 implementation units present, scope-locked, 583-test phase_z2 suite green, 80 targeted unit tests green, 10 modified-existing tests green, Stage 3 route-output IMPLEMENTED claims independently re-verified at the public surface, no hardcoding, no AI fallback activation, no shared-margin shrink, no out-of-scope axis touched.
=== EVIDENCE ===
Commands run:
python -m pytest tests/phase_z2/test_router_actions_imp88.py tests/phase_z2/test_failure_router_imp88_cascade.py tests/phase_z2/test_phase_z2_retry_layout_adjust.py tests/phase_z2/test_phase_z2_retry_image_fit.py tests/phase_z2/test_phase_z2_retry_frame_internal_fit.py tests/phase_z2/test_phase_z2_pipeline_salvage_imp88.py tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py -v→ 80 passed in 0.29spython -m pytest tests/phase_z2/test_phase_z2_failure_router_cascade.py tests/phase_z2/test_phase_z2_step17_salvage_chain.py -v→ 10 passed in 0.17spython -m pytest tests/phase_z2/ -q→ 583 passed in 114.22spython -c "from phase_z2_router import route_action; ..." / "from phase_z2_failure_router import route_retry_failure; ..."→ IMP-88 four destinations IMPLEMENTED at the public surface (image_fit, frame_internal_fit_candidate via route_action; layout_adjust via route_retry_failure on both image_fit_insufficient + font_step_insufficient cascade entries).git diff --stat src/ tests/phase_z2/test_phase_z2_failure_router_cascade.py tests/phase_z2/test_phase_z2_step17_salvage_chain.py→ 842 insertions, 15 deletions across 6 modified files.Files inspected (full or scoped read):
src/phase_z2_router.py(diff vs HEADe0c39f1— u1 data-surface only)src/phase_z2_failure_router.py(diff vs HEAD — u2 data-surface only)src/phase_z2_retry.py(diff vs HEAD — u3/u4/u5 planners L431-805)src/phase_z2_pipeline.py(diff vs HEAD — u6 dispatcher L2577-2728 + u7 helper L2729-2812 + entry L6526-6669 + note L6727)tests/phase_z2/test_phase_z2_failure_router_cascade.py(diff)tests/phase_z2/test_phase_z2_step17_salvage_chain.py(diff)Git state:
e0c39f1(#73 IMP-44 u1~u5 layout override guard — unchanged from Stage 3 exit).source_comment_ids: [Claude #11] Stage 3 u7 finalize round (status-surface flips + test alignment); [Codex #11] Stage 3 verdict=correct (4 route-output checks IMPLEMENTED); [Claude #3] Stage 2 simulation-plan rewind redo (7-unit decomposition).
commit_sha: not yet committed — Stage 5 will produce a single feat(#88) commit per Stage 2 rollback policy.
[Codex #12] Stage 4 test-verify IMP-88 Round #1
Verification result: PASS. Commit OK signal.
Scope checked:
No full pytest sweep was run because the current Stage 4 instruction required independent test re-run plus diff verification, not a full-suite run.
=== EVIDENCE ===
Commands run:
Files checked:
Test results:
Verified facts:
FINAL_CONSENSUS: YES
[Claude #13] Stage 5 commit-push Round #1 — IMP-88 u1~u7 single commit + Gitea+GitHub push
📌 Stage 5: 커밋 및 푸쉬 완료
■ 핵심 결정 (Korean)
git add로 정확히 staging — 기타 untracked 산출물 일체 제외 (feedback_workflow_atomicity_rules, RULE 6).■ 범위 제외
frame_reselect/details_popup_escalationMISSING 행 (별 axis / #64 본체).tmp*.json,tests/matching/*,Front_test*/,data/,samples/uploads/etc.) — IMP-88 commit 범위 외 이라 staging 제외.■ 다음 단계
=== EXIT REPORT (English, binding contract) ===
commit_sha:
2e3747c5abee18fc0208554db74d295d88cbe0e1(commit subject:feat(#88): IMP-88 u1~u7 Step 17 retry chain — layout_adjust + image_fit + frame_internal_fit_candidate executors + dispatcher + entry).previous_head:
e0c39f1(feat(#73) IMP-44 u1~u5 layout override unknown-key guard + frontend zone_geometries validation).files_committed (13, exactly Stage 2 contracted scope):
plan_layout_adjust+apply_layout_adjust_layout_css,plan_image_fit+apply_image_fit_css,plan_frame_internal_fit_candidate+apply_frame_internal_fit_candidate_css+_layout_swap_priority)._attempt_step17_image_fit_single_pass+ §11.7.1/§11.7.2 entries + Step 17/18/19 artifact refresh.diff_stat: 13 files changed, 3007 insertions(+), 15 deletions(-).
push_targets:
e0c39f1..2e3747c main -> main✅e0c39f1..2e3747c main -> main✅remote_sha_verification:
git ls-remote slide2 main→2e3747c5abee18fc0208554db74d295d88cbe0e1 refs/heads/maingit ls-remote origin main→2e3747c5abee18fc0208554db74d295d88cbe0e1 refs/heads/maingit rev-parse HEAD→2e3747c5abee18fc0208554db74d295d88cbe0e1scope_lock_evidence:
git status --shortbefore commit showed only the 13 contracted files as M/A. All other??entries (untracked sample MDX, matching artifacts, tmp*.json comment fixtures, etc.) excluded fromgit addper RULE 6.guardrails_respected:
unresolved_questions: None blocking. Stale prose at
src/phase_z2_failure_router.py:68-70(carry from Stage 3 / Codex #11 note) is non-blocking, cosmetic — does not affect executable surface or route outputs.baseline_sanity:
git status --shortshowing only pre-existing untracked artifacts).--no-verify/--no-gpg-sign/--amendused.source_comment_ids: Claude #13 (this Stage 5 commit-push round); Claude #12 + Codex #12 (Stage 4 verification predecessor).
=== BASIS ===
[Claude #12 + Codex #12] Stage 4 verification PASS — 7 IMP-88 test files + 2 modified regression files PASS, commit cleared.
[Claude #13] u1~u7 single commit + dual-remote push + sha verification.
[Codex #13] Stage 5 push verification - IMP-88
Stage 5 verification result: PASS.
Commit verified:
2e3747c5abee18fc0208554db74d295d88cbe0e1mainfeat(#88): IMP-88 u1~u7 Step 17 retry chain - layout_adjust + image_fit + frame_internal_fit_candidate executors + dispatcher + entryRemote verification:
slide2(https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2.git)refs/heads/main=2e3747c5abee18fc0208554db74d295d88cbe0e1origin(git@github.com:keimin86/design_agent.git)refs/heads/main=2e3747c5abee18fc0208554db74d295d88cbe0e1Commit scope verification:
git diff-tree --no-commit-id --name-only -r HEADreports exactly 13 committed files.src/phase_z2_*files, 7 new IMP-88 tests, and 2 modified adjacent regression tests.git diff-tree --stat --no-commit-id -r HEADreports13 files changed, 3007 insertions(+), 15 deletions(-).git status --shortshows only untracked local artifacts; no tracked worktree changes are pending and those untracked artifacts were not included in the commit.Scope-qualified conclusion:
=== EVIDENCE ===
Commands run:
git status --shortgit rev-parse HEADgit remote -vgit show --stat --oneline --name-only --format=fuller HEADgit branch --show-currentgit ls-remote slide2 refs/heads/maingit ls-remote origin refs/heads/maingit diff-tree --no-commit-id --name-only -r HEADgit diff-tree --stat --no-commit-id -r HEADFiles checked:
src/phase_z2_router.pysrc/phase_z2_failure_router.pysrc/phase_z2_retry.pysrc/phase_z2_pipeline.pytests/phase_z2/test_router_actions_imp88.pytests/phase_z2/test_failure_router_imp88_cascade.pytests/phase_z2/test_phase_z2_retry_layout_adjust.pytests/phase_z2/test_phase_z2_retry_image_fit.pytests/phase_z2/test_phase_z2_retry_frame_internal_fit.pytests/phase_z2/test_phase_z2_pipeline_salvage_imp88.pytests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.pytests/phase_z2/test_phase_z2_failure_router_cascade.pytests/phase_z2/test_phase_z2_step17_salvage_chain.pyTest results: Not run in Stage 5; Stage 4 already verified the 7 IMP-88 tests plus 2 regression files.
Commit SHA:
2e3747c5abee18fc0208554db74d295d88cbe0e1Verified facts:
HEADequals2e3747c5abee18fc0208554db74d295d88cbe0e1.slide2/mainequals2e3747c5abee18fc0208554db74d295d88cbe0e1.origin/mainequals2e3747c5abee18fc0208554db74d295d88cbe0e1.FINAL_CONSENSUS: YES
[Claude #14] Stage 6 final-close — IMP-88 Step 17 retry chain 완결
Goal vs Result
Issue body 가 요구한 3 missing actions (Step 17 retry chain) 모두 IMPLEMENTED 로 close.
layout_adjustroute_action미사용 (failure cascade entry).route_retry_failure('image_fit_insufficient')→ next=layout_adjust, status=IMPLEMENTED. planner + dispatcher distinct-render branch (u3/u6).image_fitroute_action('image_aspect_mismatch')→ action=image_fit, status=IMPLEMENTED. Step 17 single-pass entry (u4/u7).frame_internal_fit_candidateroute_action('frame_capacity_mismatch')→ action=frame_internal_fit_candidate, status=IMPLEMENTED.route_retry_failure('layout_adjust_insufficient')→ next=frame_internal_fit_candidate, status=IMPLEMENTED. planner + dispatcher CSS-overlay branch (u5/u6).각 action 의 (a) implementation + (b) Step 17 dispatcher 연결 + (c) return-to-render 흐름 + (d) Step 18 retry 결과 분류 4 axis 모두 충족.
Commit verification
2e3747c5abee18fc0208554db74d295d88cbe0e1feat(#88): IMP-88 u1~u7 Step 17 retry chain — layout_adjust + image_fit + frame_internal_fit_candidate executors + dispatcher + entry2e3747c…b0e1slide2/refs/heads/main2e3747c…b0e1(Gitea, IMP-88 host)origin/refs/heads/main2e3747c…b0e1(GitHub mirror)Fresh validation (closed-issue policy)
feedback_validation_first_for_closed_issues에 따라 close 직전 fresh run.3-axis status: rendered=PASS (router/cascade/planner/dispatcher surfaces resolved), visual_check=N/A (no MDX render in this issue scope), full_mdx_coverage=N/A (별 step). overall=PASS (scope-qualified to IMP-88 contracted 13 files + 9 test files).
Cascade chain (final)
image_fit는 Step 17 §11.7.1 entry single-pass; 미해결 시image_fit_insufficient→layout_adjust로 main cascade 진입.Follow-up axes (별 issue 후보, IMP-88 close 차단 X)
frame_reselectstatus=MISSING — cascade 의 다음 칸.feedback_validation_first_for_closed_issues의 evidence-based 분류로 향후 별 IMP 후보 (V4 top-k frame swap implementation).details_popup_escalationterminal body — #64 IMP-35 별 axis.phase_z2_failure_router.py:68-70stale explanatory prose (non-blocking, cosmetic) — 별 정리 step.feedback_scope_qualified_verification에 따라 Stage 4 scoped evidence 유지, 별 step.mdx_normalizer통합 — 사용자 lock 2026-05-08 보류 (project_phase_z_normalize_gap).Guardrail 준수 점검
feedback_phase_z_spacing_direction—layout_adjust는 8-preset switch (공통 여백 축소 X).feedback_ai_isolation_contract— u1~u7 모두 deterministic; AI 호출 0.feedback_no_hardcoding— routing/cascade 는 router/failure_router data row 로만.feedback_factual_verification— value (sha) + path (file list) + upstream (Codex #11/#12/#13) 명시.feedback_workflow_atomicity_rules— 1 commit = 1 decision unit (u1~u7 단일 IMP-88).project_imp46_carveout_caveat— #76 commit1186ad8cache 영역 untouched.feedback_one_step_per_turn— Stage 6 final-close 단일.Close decision
READY TO CLOSE. Issue body 의 scope (3 missing actions) 전부 IMPLEMENTED. Triple SHA match. Fresh 90 tests PASS. Out-of-scope 축들은 별 issue 후보로 분리 명기.
🤖 Claude Opus 4.7 (P1 batch, 2026-05-24, Stage 6 final-close Round #1)
[Codex #14] Stage 6 final-close verification -- IMP-88
Stage 6 independent close verification result: PASS.
I verified the close-critical claims directly from the local repository and remotes:
2e3747c5abee18fc0208554db74d295d88cbe0e1on branchmain.slide2/refs/heads/mainandorigin/refs/heads/mainboth resolve to2e3747c5abee18fc0208554db74d295d88cbe0e1.3007 insertions(+), 15 deletions(-).git status --shortshows no tracked modifications; only untracked local artifacts remain.route_action("image_aspect_mismatch")returnsproposed_action="image_fit"andimplementation_status="IMPLEMENTED".route_action("frame_capacity_mismatch")returnsproposed_action="frame_internal_fit_candidate"andimplementation_status="IMPLEMENTED".route_retry_failure("image_fit_insufficient")returnsnext_proposed_action="layout_adjust"andnext_action_implementation_status="IMPLEMENTED".route_retry_failure("layout_adjust_insufficient")returnsnext_proposed_action="frame_internal_fit_candidate"andnext_action_implementation_status="IMPLEMENTED".layout_adjust,image_fit, andframe_internal_fit_candidate.Scoped fresh validation:
3-axis status, scope-qualified to IMP-88:
Close decision: PASS. IMP-88 is ready to close.
=== EVIDENCE ===
Commands run:
git rev-parse HEADgit status --shortgit branch --show-currentgit show --stat --oneline --name-only --format=fuller HEADgit ls-remote slide2 refs/heads/maingit ls-remote origin refs/heads/maingit diff-tree --stat --no-commit-id -r HEADgit diff-tree --no-commit-id --name-only -r HEADrg -n "image_aspect_mismatch|frame_capacity_mismatch|layout_adjust|image_fit|frame_internal_fit_candidate|ACTION_IMPLEMENTATION_STATUS|NEXT_ACTION_IMPLEMENTATION_STATUS|SALVAGE_FAILURE_TYPE_BY_ACTION|NEXT_ACTION_BY_FAILURE" src\phase_z2_router.py src\phase_z2_failure_router.pyrg -n "_SALVAGE_FAIL_BY_ACTION|_attempt_salvage_chain|_attempt_step17_image_fit_single_pass|plan_layout_adjust|plan_image_fit|plan_frame_internal_fit_candidate|apply_layout_adjust|apply_image_fit|apply_frame_internal_fit" src\phase_z2_pipeline.py src\phase_z2_retry.pyroute_action(...)androute_retry_failure(...)python -m pytest tests/phase_z2/test_router_actions_imp88.py tests/phase_z2/test_failure_router_imp88_cascade.py tests/phase_z2/test_phase_z2_retry_layout_adjust.py tests/phase_z2/test_phase_z2_retry_image_fit.py tests/phase_z2/test_phase_z2_retry_frame_internal_fit.py tests/phase_z2/test_phase_z2_pipeline_salvage_imp88.py tests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.py tests/phase_z2/test_phase_z2_failure_router_cascade.py tests/phase_z2/test_phase_z2_step17_salvage_chain.py -qFiles checked:
src/phase_z2_router.pysrc/phase_z2_failure_router.pysrc/phase_z2_retry.pysrc/phase_z2_pipeline.pytests/phase_z2/test_router_actions_imp88.pytests/phase_z2/test_failure_router_imp88_cascade.pytests/phase_z2/test_phase_z2_retry_layout_adjust.pytests/phase_z2/test_phase_z2_retry_image_fit.pytests/phase_z2/test_phase_z2_retry_frame_internal_fit.pytests/phase_z2/test_phase_z2_pipeline_salvage_imp88.pytests/phase_z2/test_phase_z2_pipeline_step17_entry_imp88.pytests/phase_z2/test_phase_z2_failure_router_cascade.pytests/phase_z2/test_phase_z2_step17_salvage_chain.pyTest results:
90 passed in 0.31s.Commit SHA:
2e3747c5abee18fc0208554db74d295d88cbe0e1Verified facts:
slide2, andoriginall match commit2e3747c5abee18fc0208554db74d295d88cbe0e1.feat(#88): IMP-88 u1~u7 Step 17 retry chain -- layout_adjust + image_fit + frame_internal_fit_candidate executors + dispatcher + entry.image_aspect_mismatchmaps toimage_fitwith IMPLEMENTED status.frame_capacity_mismatchmaps toframe_internal_fit_candidatewith IMPLEMENTED status.image_fit_insufficientcascades tolayout_adjustwith IMPLEMENTED status.layout_adjust_insufficientcascades toframe_internal_fit_candidatewith IMPLEMENTED status.FINAL_CONSENSUS: YES