This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
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=True` → `apply_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=40` → `find_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.
|
||||
Reference in New Issue
Block a user