Files
C.E.L_Slide_test2/.orchestrator/tmp/u9_rewind_body.md
T

6.4 KiB

Executing unit: u9

[Claude #N] Stage 3 code-edit Round #2 — unit u9 (rewind fix)

unit_executed

u9 — pipeline wiring for _attempt_salvage_chain. Addresses both Codex #10 findings: stale failure_classification leaking into Step 18/19 after salvage_passed=True, and fit_analysis=None / containers={} making cross_zone_redistribute permanently infeasible at the real call site.

files_changed

  • src/phase_z2_pipeline.py

diff_summary

  • u9 block at src/phase_z2_pipeline.py:4423-4490 now synthesizes a FitAnalysis from debug_zones + overflow.zones:
    • Each debug_zones[i].position becomes a RoleFit(role=position, allocated_px=height_px, shortfall_px=scrollHeight-clientHeight). Sign convention matches fit_verifier.redistribute() (positive = deficit, negative = surplus).
    • All positions share conceptual zone "slide_body" via containers[position] = {"zone": "slide_body", "height_px": int(allocated)} so redistribute() trades shortfalls between them in a single bucket. Phase Q's calculate_fit is not used in Phase Z (policy comment at src/phase_z2_pipeline.py:2839-2843), so this synthesis is the deterministic substitute.
    • FitAnalysis(roles=_fa_roles) + _fa_containers replace the previous fit_analysis=None, containers={} placeholders.
  • Import added inline (from src.fit_verifier import FitAnalysis, RoleFit) — matches the existing from src.fit_verifier import ... pattern used across src/space_allocator.py, src/renderer.py, src/phase_z2_retry.py.
  • After if _salvage_trace.get("salvage_passed"): block — enrich_retry_trace_with_failure_classification(retry_trace) is re-invoked. classify_retry_failure short-circuits on salvage_passed=True (src/phase_z2_failure_router.py:167), so this clears both retry_trace["failure_classification"] and retry_trace["next_action_proposal"] to None, matching the contract enforced at src/phase_z2_failure_router.py:302-306. Step 18/19 artifacts now reflect the post-salvage state (no stale donor_slack_insufficient / no_donor_candidates).
  • Net delta on the u9 block: +60 / -2 lines (git diff --numstat src/phase_z2_pipeline.py reports 164/4 cumulative including the u8 hunk from prior round; u9 block alone is ~22 added lines).
  • No new module dependencies, no behavior change outside the cascade trigger guard (_ft in {"donor_slack_insufficient", "no_donor_candidates"}).

verification

  • Smoke test 1 — RoleFit/FitAnalysis construction with synthesized Phase Z roles:
    redistribute(FitAnalysis(roles={top:RoleFit(shortfall=+30), bottom_l:RoleFit(shortfall=-50), bottom_r:RoleFit(shortfall=-10)}), containers, min_margin_px=10) →
      can_redistribute=True; redistribution={top:230, bottom_l:150, bottom_r:180}; needs_escalation=False
    
    Deficit 30px transferred from bottom_l (40px usable surplus after 10px margin) → top. Confirms the synthesis lets plan_cross_zone_redistribute return feasible=True when geometry permits, and feasible=False (with can_redistribute=False) otherwise.
  • Smoke test 2 — post-salvage enrich refresh: Pre: failure_classification={'failure_type': 'donor_slack_insufficient', ...}, next_action_proposal={'next_proposed_action': 'cross_zone_redistribute'}, salvage_passed=True. After enrich_retry_trace_with_failure_classification(rt): both fields = None. Asserted.
  • python -m ast parse OK on src/phase_z2_pipeline.py.
  • pytest -q tests/phase_z262 passed (regression-free baseline maintained; matches prior u1/u2/u3 baseline reported in MID-STAGE COMPACTION).

scope-lock adherence

  • Edits confined to src/phase_z2_pipeline.py (file count = 1, well within u9's ≤3-file budget).
  • No :root / body / .slide / global CSS mutation. The cross_zone CSS still emits [data-role="<role>"] selectors from apply_cross_zone_redistribute_css (u4 surface, untouched); whether Phase Z DOM honors them is a separate axis (see follow-up below). The synthesis correctly lets the plan be feasible; if the rerender visual check still fails, the cascade naturally falls through to glue_compression via _SALVAGE_FAIL_BY_ACTION → cross_zone_redistribute_insufficient → NEXT_ACTION_BY_FAILURE → glue_compression.
  • No AI call added on normal path (feedback_ai_isolation_contract — preserved).
  • No spacing-direction shrink on common envelope (feedback_phase_z_spacing_direction — preserved).
  • IMP-09 dynamic_cols / fr_default gate already enforced inside _attempt_salvage_chain at src/phase_z2_pipeline.py:1958-1960 — unchanged.
  • (b) revert semantics preserved: out_path.write_text(candidate_html) is only called inside _attempt_salvage_chain on passed=True. On all-3-fail, the original final.html written at line 4306 stays intact. Codex case (c) contract holds.

remaining_units

  • u10 — Regression tests for multi-donor plan_zone_ratio_retry (tests/phase_z2/test_phase_z2_retry_multi_donor.py). Deferred from u1.
  • u11 — Tests for plan_cross_zone_redistribute (tests/phase_z2/test_phase_z2_cross_zone_redistribute.py).
  • u12 — Tests for plan_glue_compression (tests/phase_z2/test_phase_z2_glue_compression.py).
  • u13 — Tests for plan_font_step_compression (tests/phase_z2/test_phase_z2_font_step_compression.py).
  • u14 — Tests for failure_router cascade (tests/phase_z2/test_phase_z2_failure_router_cascade.py).
  • u15 — End-to-end Step 17 salvage chain tests (tests/phase_z2/test_phase_z2_step17_salvage_chain.py). Will lock the post-salvage failure_classification=None invariant as a regression guard.

follow_up_issue_candidates

  • Selector mismatch in apply_cross_zone_redistribute_css: emits [data-role="<role>"] rules, but Phase Z's templates/phase_z2/slide_base.html:242 only declares data-zone-position, not data-role. The salvage plan is now feasible, but the rerender CSS will not match any Phase Z DOM and the cascade will fall through to glue_compression. Two non-mutually-exclusive resolutions, both out-of-scope for u9:
    1. Extend apply_cross_zone_redistribute_css to also emit [data-zone-position="<role>"] when synthesized roles equal zone positions (would require role-vs-position discrimination signal).
    2. Add data-role="{{ zone.position }}" to slide_base.html so Phase Z DOM honors the Phase Q-style selector.
  • These are template/CSS-emission concerns, not Step 17 wiring. Filing as a follow-up axis preserves Stage 2's u4 contract (already verified) while keeping the u9 scope on pipeline plumbing.