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

13 KiB
Raw Blame History

Executing unit: u9

[Claude #9b] Stage 3 code-edit Round #N — unit u9

unit_executed

  • id: u9
  • summary: Pipeline wiring after _attempt_zone_ratio_retry — on donor_slack_insufficient / no_donor_candidates, call _attempt_salvage_chain (u8 orchestrator), merge salvage trace into retry_trace, refresh overflow / fit_classification / router_decision on salvage_passed=True (mirrors retry_passed branch refresh), update Step 17 telemetry step_status + note to surface u8/u9 cascade. debug_zones / layout_css intentionally NOT refreshed (glue/font are non-geometric, scoped CSS only).

files_changed

  • src/phase_z2_pipeline.py (u9-only delta = +43 / 3, net +40 lines; combined file diff = +138 / 4 incl. u8's +95/1 from prior unit. Slight overshoot of u9 estimate_lines: 30 is structural — cascade_inputs has 10 contract-required fields and state refresh mirrors retry_passed branch's 4 router_decision fields. No drop in scope.)

diff_summary

  1. Salvage cascade dispatcher (lines 4423-4456, +37 lines) — placed between enrich_retry_trace_with_failure_classification(retry_trace) (4421) and the Step 17 artifact write (4459):
    • Trigger guard — reads retry_trace["failure_classification"]["failure_type"]; runs cascade only when failure_type ∈ {"donor_slack_insufficient", "no_donor_candidates"}. Both routes via u3 NEXT_ACTION_BY_FAILUREcross_zone_redistribute, so the cascade is exactly the surface that u3 routing points at. Any other failure_type (rerender_still_failsframe_reselect, not_attemptednone, etc.) is left to the existing escalation surface (Step 18/19), so this wiring does NOT short-circuit non-cascade paths.
    • Target zone lookup_tpos = retry_trace["plan"]["target_zone_position"] (same target the failed retry tried); _tdz = matching debug_zones entry by position. _excess = float(retry_trace["plan"]["target_excess_y"]) — reuses the planner's own excess measurement so cascade excess_px agrees with what the retry planner computed.
    • cascade_inputs dict literal — exactly mirrors u8's 10-field contract (fit_analysis / containers / min_margin_px / excess_px / block_count / zone_position / current_font_px / excess_after_glue_px / available_lines / chars_per_line). Defensive defaults:
      • fit_analysis=None / containers={} / min_margin_px=None — the pipeline does NOT yet construct a FitAnalysis object here, so cross_zone_redistribute will hit u8's fit_analysis is None guard and emit feasible=False with the contractual failure_reason. Cascade then routes onward to glue_compression. This is the intended fallback — see u8's diff_summary "graceful degrade when fit_analysis missing". Implementing pipeline-side FitAnalysis construction is out-of-scope for u9 (would expand line budget by 50+; would block on a separate axis: when/where to instantiate FitAnalysis from existing fit_classification+debug_zones+containers).
      • block_count = len(placement_trace.internal_regions) or 1 — drawn from the trace-only B1→B4 chain already populated on debug_zones[i].placement_trace (line 76 comment). Fallback 1 ensures calculate_glue_absorption(1) returns a finite envelope.
      • current_font_px / available_lines / chars_per_line — read from _tdz (debug_zone metadata). Pipeline does NOT currently populate these (space_allocator.find_fitting_font_size does, but at a different point), so they default to 0 → font_step_compression's u6 guard not available_lines or available_lines <= 0 fires → feasible=False with "text_metrics missing". Cascade then routes to layout_adjust (terminal — currently MISSING per u7 NEXT_ACTION_IMPLEMENTATION_STATUS). This is the honest current state: glue is the only cascade stage that will realistically execute work until pipeline-side text_metrics propagation lands (separate axis, follow-up).
    • _attempt_salvage_chain call — passes initial_failure_type=_ft so the chain's first route_retry_failure call routes from the SAME failure_type that triggered the dispatcher (avoids any double-routing). gap_px=GRID_GAP matches _attempt_zone_ratio_retry's convention.
    • Trace mergeretry_trace.update(_salvage_trace) adds salvage_attempted / salvage_passed / salvage_steps (+ optional salvage_skipped_reason / salvage_terminal_action / salvage_terminal_rationale) to the existing retry_trace. The Step 17 artifact thus carries BOTH retry and salvage traces in a single JSON, preserving (b)-revert telemetry for either failure mode.
  2. salvage_passed=True state refresh (lines 4448-4456, mirrors retry_passed branch at 4406-4417):
    • overflow = _salvage_trace["salvage_steps"][-1].get("post_salvage_overflow") or overflow — the u8 orchestrator records post_salvage_overflow on the WINNING step only (per if passed: step["post_salvage_overflow"] = candidate_overflow). Defensive or overflow preserves pre-salvage overflow if telemetry is absent (e.g., race).
    • fit_classification = classify_visual_runtime_check(overflow, debug_zones) + router_decision = route_fit_classification(fit_classification) — re-classify and re-route on the post-salvage state, so downstream Step 18/19/20 see the cascade-PASS state.
    • 3 router_decision enrichment fields (v4_fallback_summary, v4_fallback_selections, frame_reselect_fallback_status) copied verbatim from the retry_passed branch — keeps Step 20 status reporting consistent across both PASS paths.
    • debug_zones / layout_css intentionally NOT refreshed — glue_compression and font_step_compression emit scoped CSS overrides under [data-zone-position="<pos>"] and do not alter row geometry. Refreshing layout_css.heights_px would falsely report a redistribution that didn't happen. cross_zone_redistribute WOULD geometrically refresh, but it requires fit_analysis (currently None → infeasible), so this branch is unreachable in practice. When a future axis populates fit_analysis, u9 will need a follow-up to add cross_zone-specific geometry refresh (tracked as follow_up_issue_candidates below).
  3. Step 17 artifact step_status + note updated (lines 4459-4471, ±5 lines):
    • step_status: "done" now fires on retry_passed OR salvage_passed; "failed" only when retry_attempted AND NOT retry_passed (semantically: "retry attempted and didn't pass even via salvage"); "skipped" otherwise. This honors the retry+salvage as one logical step model from Stage 2.
    • note rewritten to enumerate u8/u9 cascade actions and call out which terminal actions remain MISSING (layout_adjust / frame_reselect / details_popup_escalation). Removes the now-stale "Step 17 ⚠ partial" claim.
  4. Importsapply_cross_zone_redistribute_css / apply_font_step_compression_css / apply_glue_compression_css / plan_cross_zone_redistribute / plan_font_step_compression / plan_glue_compression from phase_z2_retry, route_retry_failure from phase_z2_failure_router — all added in u8 prior. No new imports for u9.
  5. No _attempt_zone_ratio_retry mutation — pipeline retry call site (4389-4402) is unchanged; salvage runs AFTER retry trace is enriched, never as a replacement.

evidence

  • python -m py_compile src/phase_z2_pipeline.py → exit 0.
  • python -m pytest -q tests/phase_z262 passed in 0.12s (regression-free).
  • python -m pytest -q tests (full suite, not just phase_z2) → 109 passed in 6.90s (regression-free across the whole project).
  • git diff --numstat src/phase_z2_pipeline.py138 4 src/phase_z2_pipeline.py. Subtracting u8 baseline (+95/-1) leaves u9-only = +43/-3, net +40. Estimate was 30 — slight overshoot, structural (cascade_inputs has 10 contract fields + 4-field router_decision refresh mirroring retry_passed branch).
  • Marker probe — confirmed 7 markers present: 11.7 IMP-12 u9, _attempt_salvage_chain(, donor_slack_insufficient, no_donor_candidates, salvage_passed, retry_trace.update(_salvage_trace), IMP-12 u8/u9 salvage cascade.
  • Trace shape verified via reading u8's _attempt_salvage_chain source:
    • On dynamic_cols/no dynamic_rows: {salvage_attempted: False, salvage_passed: False, salvage_steps: [], salvage_skipped_reason: ...} — u9 surfaces salvage_skipped_reason via retry_trace.update.
    • On terminal action (layout_adjust/frame_reselect/none): {salvage_attempted: True, salvage_passed: False, salvage_terminal_action, salvage_terminal_rationale} — u9 surfaces both fields via retry_trace.update.
    • On PASS at stage N: {salvage_attempted: True, salvage_passed: True, salvage_steps: [...,{passed: True, post_salvage_overflow}]} — u9 reads salvage_steps[-1].post_salvage_overflow for state refresh.
    • On all-fail: {salvage_attempted: True, salvage_passed: False, salvage_steps: [3 failures]} — u9 leaves overflow / fit_classification / router_decision unchanged, preserving (b)-revert.

remaining_units

  • u10 — Tests for multi-donor plan_zone_ratio_retry (u1 deferred)
  • u11 — Tests for plan_cross_zone_redistribute (u4 deferred)
  • u12 — Tests for plan_glue_compression (u5 deferred)
  • u13 — Tests for plan_font_step_compression (u6 deferred)
  • u14 — Tests for failure_router cascade routing (u2/u3 deferred)
  • u15 — End-to-end test of _attempt_salvage_chain orchestrator (u8 deferred)

follow_up_issue_candidates

  • Pipeline-side FitAnalysis construction — currently cascade_inputs["fit_analysis"] = None so cross_zone_redistribute cascade stage always returns infeasible. A follow-up axis should instantiate FitAnalysis from existing fit_classification + containers + debug_zones BEFORE the cascade, so cross_zone has real input. Until then, glue is the only stage that gets a real chance to fire from this pipeline. Scope-lock 외 axis — not part of IMP-12.
  • Pipeline-side text_metrics propagation onto debug_zonescurrent_font_px / available_lines / chars_per_line are computed inside space_allocator but not surfaced onto debug_zones[i]. Without these, font_step_compression always returns "text_metrics missing" → cascade exits to terminal layout_adjust (MISSING). A follow-up should add a text_metrics surfacing step alongside placement_trace. Scope-lock 외 axis — not part of IMP-12.
  • Post-cross_zone geometry refresh — when cross_zone_redistribute starts passing (after FitAnalysis is wired), debug_zones/layout_css would also need geometric refresh (mirrors the retry_passed branch's height_px/ratio refresh logic at 4406-4417). u9's state refresh deliberately omits this because cross_zone is currently unreachable. Track as part of the same FitAnalysis follow-up.

guardrails_honored

  • feedback_ai_isolation_contract — no AI/Kei call on the cascade path; all three salvage actions delegate to deterministic plan/apply functions (u4/u5/u6).
  • feedback_phase_z_spacing_direction — glue_compression CSS is scoped to [data-zone-position] (u5); cascade does NOT shrink common margin / :root / body / .slide tokens.
  • feedback_no_hardcoding — cascade triggers off existing failure_router classifier output (donor_slack_insufficient / no_donor_candidates); no hard-coded action selection.
  • feedback_one_step_per_turn — this comment ends u9; remaining_units list explicitly lists u10u15 without claiming progress on any of them. Pipeline wiring (orchestrator) lands here; tests land in u10u15.
  • IMP-09 dynamic_cols / fr_default gate — u8 enforces gate inside _attempt_salvage_chain (lines 1958-1960); u9 dispatcher does not bypass — gate-skip flows through retry_trace.update and surfaces salvage_skipped_reason on Step 17 artifact.
  • PZ-2 (1turn=1step) — single unit edited; no scope creep into tests or pipeline-FitAnalysis construction.

scope_audit

  • Files modified: 1 (src/phase_z2_pipeline.py).
  • Lines added (u9 only): 43; removed: 3.
  • ACTION_BY_CATEGORY changes: 0. NEXT_ACTION_BY_FAILURE changes: 0. NEXT_ACTION_IMPLEMENTATION_STATUS changes: 0. SALVAGE_FAILURE_TYPE_BY_ACTION changes: 0. classifier body changes: 0. AI/Kei call additions: 0. Global CSS token changes: 0.
  • Test files added: 0 (deferred to u10u15).
  • Pipeline call-site for _attempt_zone_ratio_retry: unchanged (4389-4402).
  • Step 18/19/20 artifact writes: unchanged (4470-4500). They will naturally pick up the merged failure_classification / next_action_proposal from retry_trace regardless of whether salvage ran — the cascade-PASS path simply means failure_classification reflects the salvage cascade's terminal route (recorded on the cascade trace itself, not re-enriched on retry_trace). If a future axis wants Step 18/19 to also surface the cascade's terminal_action, that's a follow-up.

evidence_commands

git status --short
git diff --numstat src/phase_z2_pipeline.py
python -m py_compile src/phase_z2_pipeline.py
python -m pytest -q tests/phase_z2
python -m pytest -q tests
python -c "import sys; sys.path.insert(0,'src'); from phase_z2_pipeline import _attempt_salvage_chain, _SALVAGE_FAIL_BY_ACTION; ..."