10 KiB
10 KiB
Executing unit: u8
[Claude #9] Stage 3 code-edit Round #N — unit u8
unit_executed
- id: u8
- summary: New
_attempt_salvage_chaininphase_z2_pipeline.py— deterministic Step 17 salvage cascade orchestrator. Iteratesphase_z2_failure_router.route_retry_failureacross the triad (cross_zone_redistribute → glue_compression → font_step_compression); per stage runs plan → apply CSS → render candidate →run_overflow_check. PASS promotesfinal.html; cascade-exit routes (layout_adjust/frame_reselect/none) terminate; all-fail preserves (b) revert. Honors IMP-09dynamic_cols/fr_defaultgate. u9 pipeline wiring deferred.
files_changed
src/phase_z2_pipeline.py(u8-only delta = +95 / −1 — includes +13 lines of new imports, +6 lines of header comment + lookup table, +76 lines of orchestrator function body. Slightly above the u8 line estimate of 50 due to 3-action plan/apply dispatch + (b)-revert telemetry needing explicit per-stage step dicts; the cascade body itself is loop-driven and table-based to avoid duplication.)
diff_summary
- New imports (lines 58-76,
+13):- From
phase_z2_retry:plan_cross_zone_redistribute/apply_cross_zone_redistribute_css(u4),plan_glue_compression/apply_glue_compression_css(u5),plan_font_step_compression/apply_font_step_compression_css(u6). No mutation of existingplan_zone_ratio_retry/apply_retry_to_layout_css(u1) import. - From
phase_z2_failure_router:route_retry_failure(u3) added alongside existingenrich_retry_trace_with_failure_classification.
- From
_SALVAGE_FAIL_BY_ACTIONconstant (lines 1937-1944) — single-point mapping from cascade action label to the failure-type label the failure router uses for the next routing step. Mirrorsphase_z2_failure_router.SALVAGE_FAILURE_TYPE_BY_ACTION(u2) at the orchestrator layer so the route-walk does not need to reach back into the failure-router module for cascade state._attempt_salvage_chainorchestrator (lines 1947-2024):- Signature:
run_dir,out_path,slide_title,slide_footer,zones_data,layout_preset,layout_css,cascade_inputs: dict,initial_failure_type: str,gap_px: int. Mirrors_attempt_zone_ratio_retryarg shape so u9 can plumb identically.cascade_inputsis a flat dict that u9 will populate with{fit_analysis, containers, min_margin_px, excess_px, block_count, zone_position, current_font_px, excess_after_glue_px, available_lines, chars_per_line}— keeps the orchestrator agnostic to where the values come from. - IMP-09 gate (lines 1956-1959): exits with
salvage_attempted=False+salvage_skipped_reasonwhendynamic_cols=Trueordynamic_rows=False. Same posture as_attempt_zone_ratio_retry(line 1833-1843) — row-axis CSS overrides on 2-D or fr_default layouts would either misapply or no-op. - Cascade loop (lines 1963-2023, bounded to
len(_SALVAGE_FAIL_BY_ACTION) == 3iterations to guard against router cycles):route_retry_failure(failure_type)resolves the next cascade action. Ifnext_actionis not one of the 3 salvage labels (i.e.layout_adjust/frame_reselect/noneterminal), the orchestrator recordssalvage_terminal_action+salvage_terminal_rationaleand returns — preserving (b) revert.- Per stage: dispatch to the matching plan/apply pair.
cross_zone_redistributehas an explicitfit_analysis is Nonedefensive guard (synthesises afeasible=Falseplan with a clearfailure_reasonrather than crashingplan_cross_zone_redistributeon.rolesdereference).glue_compression/font_step_compressionplanners already self-guard on zero / missing inputs (u5/u6 case-0 short-circuits). - When
plan.feasibleis True:apply_fn(plan)emits scoped CSS ([data-role=…]for cross_zone,[data-zone-position=…]for glue/font — guardrail respect baked into u4/u5/u6). The CSS is injected into the slide HTML via a<style>…</style>block placed inside</head>(or prepended if</head>absent). The candidate is written tosalvage_<action>_candidate.htmlnext to the slide, thenrun_overflow_checkruns. - On PASS:
out_path.write_text(candidate_html)promotes the candidate tofinal.html; step recorded withpost_salvage_overflow; loop exits withsalvage_passed=True. - On FAIL (infeasible plan, no CSS, or visual_check fail): step recorded with
failure_reason(planner reason first, then visual_check fail_reasons, then a generic fallback).failure_typeupdated via_SALVAGE_FAIL_BY_ACTION[next_action]so the next loop iteration'sroute_retry_failureadvances the cascade.
- No
final.htmlmutation outsidepassed=True— (b) revert preserved on every fail path (skip, terminal, infeasible, no-CSS, visual_check fail). Verified via probes 3, 5, 6.
- Signature:
- Backward-compat / surface invariants preserved:
_attempt_zone_ratio_retrybody unchanged.enrich_retry_trace_with_failure_classificationimport preserved (u2/u3 surface intact).- No changes to
phase_z2_router.ACTION_BY_CATEGORY,_attempt_zone_ratio_retry, or pipeline-Step-17 wiring (u9 territory). - No
ACTION_IMPLEMENTATION_STATUS/NEXT_ACTION_IMPLEMENTATION_STATUSmutations (u7 already registered IMPLEMENTED for the 3 cascade actions).
evidence
python -c "from src.phase_z2_pipeline import _attempt_salvage_chain, _SALVAGE_FAIL_BY_ACTION; ..."→OK: ['cross_zone_redistribute', 'glue_compression', 'font_step_compression'](import + table sanity).python -m pytest -q tests/phase_z2→ 62 passed in 0.12s (regression-free; u15 will add cascade-orchestrator fixture tests).git diff --numstat src/phase_z2_pipeline.py→95 1 src/phase_z2_pipeline.py(only file touched by u8).- Direct behavior probes (all PASS):
- Probe 3 (IMP-09 gate,
dynamic_cols=True):salvage_attempted=False,salvage_skipped_reasoncontains "IMP-09",final.htmlunchanged → (b) revert preserved on gate skip. - Probe 4 (IMP-09 gate,
dynamic_rows=False): same as probe 3 → gate covers fr_default layouts. - Probe 5 (terminal-route from non-cascade failure):
initial_failure_type='rerender_still_fails'→salvage_attempted=Truebutsalvage_steps=[],salvage_terminal_action='frame_reselect',final.htmlunchanged → terminal route preserves (b) revert without consuming any cascade slot. - Probe 6 (cascade walk from
donor_slack_insufficient, all 3 stages infeasible):actions == ['cross_zone_redistribute', 'glue_compression', 'font_step_compression'], allpassed=False, allcss_override=None, allcandidate_path=None,final.htmlunchanged → 3-stage walk works, (b) revert preserved, no spurious render. - Probe 6.5 (per-stage failure_reason surfaces):
cross_zone_redistributestep →"fit_analysis missing";glue_compressionstep →"excess_px <= 0";font_step_compressionstep →"excess_after_glue_px <= 0"or"text_metrics missing"→ upstream planner reasons propagate cleanly through the orchestrator. - Probe 7 (u3 routing aligns with u8 lookup table):
route_retry_failure('donor_slack_insufficient') → cross_zone_redistribute;route_retry_failure('cross_zone_redistribute_insufficient') → glue_compression;route_retry_failure('glue_absorption_insufficient') → font_step_compression;route_retry_failure('font_step_insufficient') → layout_adjust→ 4-transition cascade verified end-to-end against the failure router surface. - Probe 8 (feasible + render + PASS): SKIPPED — needs Selenium-driven
run_overflow_check. Will be covered by u15 fixture tests intests/phase_z2/test_phase_z2_step17_salvage_chain.py.
- Probe 3 (IMP-09 gate,
- No new tests added in this unit (u15 is the test unit for the cascade orchestrator).
scope_lock
- No changes to:
phase_z2_router.py,phase_z2_failure_router.py,phase_z2_retry.py(all u1-u7 surfaces intact). - No changes to
_attempt_zone_ratio_retry, Step 17 telemetry block (lines 4329-4342), orenrich_retry_trace_with_failure_classificationcall site — u9 territory. - No AI/Kei call on normal path (
feedback_ai_isolation_contract). - No
:root/body/.slide/.zoneCSS — the<style>injection only contains output from the u4/u5/u6apply_*_cssfunctions, all of which emit[data-role=…]or[data-zone-position=…]scoped rules (feedback_phase_z_spacing_direction). - No
ACTION_BY_CATEGORYchange, noNEXT_ACTION_BY_FAILUREchange, noACTION_IMPLEMENTATION_STATUS/NEXT_ACTION_IMPLEMENTATION_STATUSchange — cascade actions stayIMPLEMENTED(u7) and surface-less at Step 16 by design.
remaining_units
- u9 — Pipeline wiring after
_attempt_zone_ratio_retry: assemblecascade_inputsfromfit_analysis/containers/ overflow /debug_zones/text_metrics, call_attempt_salvage_chainwhenfailure_type in ('donor_slack_insufficient', 'no_donor_candidates'), merge result intoretry_trace(salvage_steps,salvage_passed), update Step 17 telemetry, mirror post-retry state refresh from lines 4311-4323 whensalvage_passed=True. - u10 — Tests for multi-donor
plan_zone_ratio_retry(u1). - u11 — Tests for
plan_cross_zone_redistribute(u4). - u12 — Tests for
plan_glue_compression(u5) — scope-CSS assertion + envelope assertion. - u13 — Tests for
plan_font_step_compression(u6) — 4-guard ladder assertions. - u14 — Tests for failure router cascade (u2/u3/u7).
- u15 — End-to-end fixture test for
_attempt_salvage_chain(u8) — cases (a) cross_zone pass / (b) glue pass after cross_zone fail / (c) all 3 fail → (b) revert.
follow_up_issue_candidates
None inside u8's scope-lock. One observation for the implementer of u9 (not a follow-up issue): when cascade_inputs.fit_analysis is missing the orchestrator records a clean infeasible step rather than crashing — this lets u9 plumb only what is available without needing to short-circuit at the call site for partial-context zones. If we later want stricter "fit_analysis required" semantics, that is a u9-time decision, not a u8 axis.
one_step_per_turn_compliance
This turn implemented exactly u8 from the Stage 2 implementation_units list. No work began on u9 / u10 / u11 / u12 / u13 / u14 / u15. No git add, no git commit, no push — per Stage 3 prompt.