13 KiB
13 KiB
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— ondonor_slack_insufficient/no_donor_candidates, call_attempt_salvage_chain(u8 orchestrator), merge salvage trace intoretry_trace, refreshoverflow / fit_classification / router_decisiononsalvage_passed=True(mirrors retry_passed branch refresh), update Step 17 telemetrystep_status+noteto surface u8/u9 cascade.debug_zones/layout_cssintentionally 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 u9estimate_lines: 30is structural — cascade_inputs has 10 contract-required fields and state refresh mirrors retry_passed branch's 4router_decisionfields. No drop in scope.)
diff_summary
- 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 whenfailure_type ∈ {"donor_slack_insufficient", "no_donor_candidates"}. Both routes via u3NEXT_ACTION_BY_FAILURE→cross_zone_redistribute, so the cascade is exactly the surface that u3 routing points at. Any other failure_type (rerender_still_fails→frame_reselect,not_attempted→none, 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= matchingdebug_zonesentry byposition._excess = float(retry_trace["plan"]["target_excess_y"])— reuses the planner's own excess measurement so cascadeexcess_pxagrees with what the retry planner computed. cascade_inputsdict 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 aFitAnalysisobject here, so cross_zone_redistribute will hit u8'sfit_analysis is Noneguard and emitfeasible=Falsewith 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 existingfit_classification+debug_zones+containers).block_count = len(placement_trace.internal_regions) or 1— drawn from the trace-only B1→B4 chain already populated ondebug_zones[i].placement_trace(line 76 comment). Fallback1ensurescalculate_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_sizedoes, but at a different point), so they default to0→ font_step_compression's u6 guardnot available_lines or available_lines <= 0fires →feasible=Falsewith"text_metrics missing". Cascade then routes tolayout_adjust(terminal — currently MISSING per u7NEXT_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_chaincall — passesinitial_failure_type=_ftso the chain's firstroute_retry_failurecall routes from the SAME failure_type that triggered the dispatcher (avoids any double-routing).gap_px=GRID_GAPmatches_attempt_zone_ratio_retry's convention.- Trace merge —
retry_trace.update(_salvage_trace)addssalvage_attempted / salvage_passed / salvage_steps(+ optionalsalvage_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.
- Trigger guard — reads
salvage_passed=Truestate 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 recordspost_salvage_overflowon the WINNING step only (perif passed: step["post_salvage_overflow"] = candidate_overflow). Defensiveor overflowpreserves 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_decisionenrichment 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_cssintentionally 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 requiresfit_analysis(currentlyNone→ infeasible), so this branch is unreachable in practice. When a future axis populatesfit_analysis, u9 will need a follow-up to add cross_zone-specific geometry refresh (tracked asfollow_up_issue_candidatesbelow).
- Step 17 artifact
step_status+noteupdated (lines 4459-4471, ±5 lines):step_status:"done"now fires onretry_passed OR salvage_passed;"failed"only whenretry_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.noterewritten 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.
- Imports —
apply_cross_zone_redistribute_css / apply_font_step_compression_css / apply_glue_compression_css / plan_cross_zone_redistribute / plan_font_step_compression / plan_glue_compressionfromphase_z2_retry,route_retry_failurefromphase_z2_failure_router— all added in u8 prior. No new imports for u9. - No
_attempt_zone_ratio_retrymutation — 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_z2→ 62 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.py→138 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_chainsource:- On
dynamic_cols/nodynamic_rows:{salvage_attempted: False, salvage_passed: False, salvage_steps: [], salvage_skipped_reason: ...}— u9 surfacessalvage_skipped_reasonviaretry_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 viaretry_trace.update. - On PASS at stage N:
{salvage_attempted: True, salvage_passed: True, salvage_steps: [...,{passed: True, post_salvage_overflow}]}— u9 readssalvage_steps[-1].post_salvage_overflowfor 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.
- On
remaining_units
u10— Tests for multi-donorplan_zone_ratio_retry(u1 deferred)u11— Tests forplan_cross_zone_redistribute(u4 deferred)u12— Tests forplan_glue_compression(u5 deferred)u13— Tests forplan_font_step_compression(u6 deferred)u14— Tests forfailure_routercascade routing (u2/u3 deferred)u15— End-to-end test of_attempt_salvage_chainorchestrator (u8 deferred)
follow_up_issue_candidates
- Pipeline-side FitAnalysis construction — currently
cascade_inputs["fit_analysis"] = Nonesocross_zone_redistributecascade stage always returns infeasible. A follow-up axis should instantiateFitAnalysisfrom existingfit_classification+containers+debug_zonesBEFORE 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_zones—current_font_px / available_lines / chars_per_lineare computed insidespace_allocatorbut not surfaced ontodebug_zones[i]. Without these, font_step_compression always returns"text_metrics missing"→ cascade exits to terminallayout_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_csswould 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/.slidetokens.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 u10–u15 without claiming progress on any of them. Pipeline wiring (orchestrator) lands here; tests land in u10–u15.- 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 throughretry_trace.updateand surfacessalvage_skipped_reasonon 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 u10–u15).
- 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_proposalfromretry_traceregardless of whether salvage ran — the cascade-PASS path simply meansfailure_classificationreflects 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; ..."