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

7.1 KiB

Executing unit: u15

[Claude #N] Stage 3 code-edit Round #N -- unit u15

unit_executed

u15 -- pytest coverage for _attempt_salvage_chain (u8 surface) end-to-end. Three Stage 2 cases per contract: (a) cross_zone passes, (b) cross_zone fails + glue passes, (c) all three fail with (b) revert.

files_changed

  • tests/phase_z2/test_phase_z2_step17_salvage_chain.py (new file, untracked -- single file scope)

diff_summary

  • New test module exercises three Stage 2 cases against src.phase_z2_pipeline._attempt_salvage_chain:

    1. test_case_a_cross_zone_passes_final_html_promoted -- two-role same-zone FitAnalysis (top +30 deficit, bottom_l -50 surplus, both in slide_body). plan_cross_zone_redistribute returns feasible=Trueapply_cross_zone_redistribute_css emits [data-role=...] rules → monkey-patched run_overflow_check returns passed=True on the first candidate. Asserts salvage_attempted=True, salvage_passed=True, len(salvage_steps)==1, salvage_steps[0]={action: "cross_zone_redistribute", passed: True, plan.feasible: True}, css_override contains [data-role=, and out_path content was overwritten (no longer "ORIGINAL_BEFORE_SALVAGE", contains the patched render fingerprint u15-test). This is the "final.html promoted" branch at src/phase_z2_pipeline.py:2007-2012.

    2. test_case_b_cross_zone_fails_glue_passes_second_promoted -- single-role FitAnalysis (top only) → fit_verifier.redistribute short-circuits at len(roles_in_zone) < 2 (per src/fit_verifier.py:528-535) → plan_cross_zone_redistribute returns feasible=False with failure_reason containing "single-role zone". No CSS emitted → no rerender / no overflow call on iter 1. Loop sets failure_type="cross_zone_redistribute_insufficient", route_retry_failure returns glue_compression. plan_glue_compression(excess_px=40, block_count=3, zone_position="bottom_l") is feasible (envelope=76 >= 40) → apply_glue_compression_css emits [data-zone-position="bottom_l"] rule → patched run_overflow_check returns passed=True. Asserts len(salvage_steps)==2, salvage_steps[0].passed=False with cross_zone failure_reason, salvage_steps[1].action=="glue_compression" with passed=True and CSS containing [data-zone-position="bottom_l"], render_slide call counter == 1 (only the feasible glue branch rendered), out_path overwritten. This is the "2nd promoted, (b) revert NOT triggered" branch.

    3. test_case_c_all_three_fail_revert_preserved -- fit_analysis=None in cascade_inputs → _attempt_salvage_chain body at src/phase_z2_pipeline.py:1972-1974 emits a synthetic feasible=False plan with reason cascade_inputs.fit_analysis missing (no CSS). excess_px=200, block_count=1 → glue envelope = 28 < 200 → failure_reason contains "glue envelope insufficient". excess_after_glue_px=200, current_font_px=15.2, available_lines=10, chars_per_line=40find_fitting_font_size returns None (8px floor saves only 115.2 < 200) → failure_reason contains "FONT_SIZE_STEPS exhausted". Asserts salvage_passed=False, len(salvage_steps)==3, actions==[cross_zone_redistribute, glue_compression, font_step_compression], all passed=False and css_override is None. run_overflow_check is monkey-patched to a sentinel that fails the test if invoked (defensive: confirms no rerender when no CSS). render_slide call counter == 0. out_path.read_text() == "ORIGINAL_BEFORE_SALVAGE" (original final.html intact -- (b) revert preserved, per Stage 2 contract feedback_phase_z_spacing_direction lock).

  • Module-level fixtures:

    • _LAYOUT_CSS_GATE_PASS -- minimal layout_css with dynamic_rows=True, dynamic_cols=False so the IMP-09 gate at src/phase_z2_pipeline.py:1958-1960 does not early-skip. Other layout_css fields unused by cascade body.
    • project_tmp fixture -- creates a temp dir under PROJECT_ROOT/.orchestrator/tmp/ (not the default pytest tmp_path which sits under %LOCALAPPDATA% on Windows -- a different drive from this D:\ project, which would break candidate_path.relative_to(PROJECT_ROOT) at src/phase_z2_pipeline.py:2006).
    • _patch_render(monkeypatch) -- stubs render_slide to a deterministic 1-line HTML envelope (no Jinja2 / template files required), returns counter to assert rerender count.
    • _kwargs(...) -- builds the 10-arg call dict for _attempt_salvage_chain.
  • No source edits -- test-only unit; observes existing u8 + u4/u5/u6 + u2/u3 surfaces.

verification

  • python -m pytest -q tests/phase_z2/test_phase_z2_step17_salvage_chain.py -> 3 passed in 0.07s.
  • Full cascade suite re-run: python -m pytest -q tests/phase_z2/test_phase_z2_retry_multi_donor.py tests/phase_z2/test_phase_z2_cross_zone_redistribute.py tests/phase_z2/test_phase_z2_glue_compression.py tests/phase_z2/test_phase_z2_font_step_compression.py tests/phase_z2/test_phase_z2_failure_router_cascade.py tests/phase_z2/test_phase_z2_step17_salvage_chain.py -> 19 passed in 0.08s (3+3+3+3+4+3).
  • Cross-checked _attempt_salvage_chain semantics at src/phase_z2_pipeline.py:1947-2019: (i) IMP-09 gate at 1958-1960, (ii) per-iter plan → apply CSS → rerender → run_overflow_check at 1971-2003, (iii) out_path.write_text(candidate_html, ...) only on passed=True at 2008, (iv) for-loop bound len(_SALVAGE_FAIL_BY_ACTION)==3 at 1964, (v) _SALVAGE_FAIL_BY_ACTION re-feeds route_retry_failure for the deterministic cascade order.
  • git status --short tests/phase_z2/test_phase_z2_step17_salvage_chain.py confirms single-file untracked addition.

remaining_units

[] -- u15 is the final unit in the Stage 2 plan. All 15 implementation_units (u1-u15) now have artifacts in the tree.

follow_up_issue_candidates

  • None new this round. The two open items already named in the mid-stage compaction remain:
    1. frame_reselect body implementation (terminal cascade action; out-of-scope per Stage 1).
    2. layout_adjust body implementation (font_step_insufficient routes here; out-of-scope per Stage 1). Both are Stage 2 follow_up_candidates and not blocking IMP-12 close.

=== EVIDENCE === Commands run:

  • git status --short
  • wc -l tests/phase_z2/test_phase_z2_step17_salvage_chain.py
  • python -m pytest -q tests/phase_z2/test_phase_z2_step17_salvage_chain.py
  • python -m pytest -q tests/phase_z2/test_phase_z2_retry_multi_donor.py tests/phase_z2/test_phase_z2_cross_zone_redistribute.py tests/phase_z2/test_phase_z2_glue_compression.py tests/phase_z2/test_phase_z2_font_step_compression.py tests/phase_z2/test_phase_z2_failure_router_cascade.py tests/phase_z2/test_phase_z2_step17_salvage_chain.py
  • grep -n "_attempt_salvage_chain\|_SALVAGE_FAIL_BY_ACTION\|candidate_path.relative_to" src/phase_z2_pipeline.py

Files checked:

  • src/phase_z2_pipeline.py (lines 1937-2019, 4423-4467)
  • src/phase_z2_retry.py (plan_cross_zone_redistribute / plan_glue_compression / plan_font_step_compression, lines 247-409)
  • src/phase_z2_failure_router.py (NEXT_ACTION_BY_FAILURE / route_retry_failure)
  • src/fit_verifier.py (redistribute single-role short-circuit, lines 528-535)
  • tests/phase_z2/test_retry_gate.py (precedent for _attempt_zone_ratio_retry unit testing pattern)

DO NOT commit. Gitea comment IS the deliverable.