[{"id": 18944, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/12#issuecomment-18944", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/12", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #1] Stage 1 problem-review Round #1\n\n## Root cause\n\n**Problem statement (per issue body)**\n- Step 17 (Implemented Action 실행) — only `zone_ratio_retry` is implemented. Other Step 16 router action labels are MISSING.\n- Backlog §2 `IMP-12` + INSIGHT-MAP §3 axis registry propose three Salvage Candidates: `redistribute` + glue + font compression (sourced from PHASE-Q-AUDIT §2.4 / §2.5).\n\n**Current code paths (verified)**\n- Router: `src/phase_z2_router.py:28-37` `ACTION_BY_CATEGORY` — 8-category mapping. `zone_ratio_retry` is the only IMPLEMENTED entry; `layout_adjust` / `details_popup_escalation` MISSING, `frame_reselect` / `adapter_needed` PARTIAL, `abort` IMPLEMENTED.\n- Retry plan: `src/phase_z2_retry.py:43-195` `plan_zone_ratio_retry` — **single primary donor only**. Lines 165-180: `if primary_donor[\"slack\"] < target_added_px → feasible=False`, with inline comment \"A3 minimal : single primary donor (multi-donor 는 future)\".\n- Retry orchestrator: `src/phase_z2_pipeline.py:1765-1900` `_attempt_zone_ratio_retry` — retry_budget=1, donor rule strict (sibling / visual ok / capacity ok / slack>0 / min_height preserved, line 1813).\n- Failure router: `src/phase_z2_failure_router.py` — `donor_slack_insufficient` enum exists, consumed downstream.\n- Salvage sources: `src/fit_verifier.py:488-587` `ROLE_ZONE_MAP` + `redistribute(analysis, containers, min_margin_px)` (role/zone schema, Phase Q product). `src/space_allocator.py:897-1011` `GlueSpec / SPACING_GLUE / FONT_SIZE_STEPS / compute_glue_css_overrides / find_fitting_font_size`.\n\n**Upstream locks that constrain Step 16/17 scope**\n\n1. **PHASE-Z-PIPELINE-OVERVIEW.md:333 Step 17 user lock (current)** : \"frame_internal_fit_candidate 가 *허용할 수 있는 내부 sub-mechanism* (density envelope / line rhythm / internal grid row / text block allocation 등) 은 *frame contract 가 declare 한 envelope 안* 에서만 동작 ... **별도 action label 로 등재하지 않음** — `density_adjust_candidate` 같은 이름은 *공통 CSS/padding 축소 antipattern* 을 초대할 위험\". Verbatim Korean quote from doc.\n2. **memory `feedback_phase_z_spacing_direction`** : forbids \"shared text-line gap\", \"transform-block padding\", \"section-title margin-bottom\" shrinking. Direction is preserve / increase breathing room, not shrink to fit.\n3. **PHASE-Z-PIPELINE-OVERVIEW.md:352 Step 19** : `frame_internal_fit_candidate` execution contract / internal envelope **undefined** (the frame-contract envelope format itself is yet to be specified).\n4. **PHASE-Q-AUDIT.md:1300** — the source already classifies the three Salvage Candidates as `§3 review subjects`, not as automatic adoption. AI-isolation invariant + \"audit verdict ≠ adoption decision\" reaffirmed in the 2026-05-11 reclassification pass.\n\n**Root cause + conflict**\n- Primary root cause = `plan_zone_ratio_retry` is single-donor only. When the primary donor's slack is insufficient, sibling donors could supply additional slack but the planner returns feasible=False.\n- Secondary observation = the source §2.5 glue + font compression are exactly the antipattern that lock #1 and memory rule #2 forbid: `compute_glue_css_overrides` shrinks the shared CSS vars `--spacing-block` / `--spacing-inner` / `--container-padding` (`space_allocator.py:944-983`), and `find_fitting_font_size` shrinks the visible font size globally (`space_allocator.py:986-1011`). Lock #3 means the frame-contract envelope that could legitimately host them does not exist yet.\n\n**Key conclusion** : Of the three Salvage Candidates, only zone-level multi-donor `redistribute` is compatible with the active locks. Glue + font compression conflict directly with the locks → explicit reject from IMP-12 scope.\n\n## Scope-lock (2 axes, refine-only)\n\n### IN scope\n\n1. **axis_1 — multi-donor aggregation** : Extend `src/phase_z2_retry.py:165-180` `plan_zone_ratio_retry` from the single-donor branch to aggregated multi-donor.\n - When the primary donor is insufficient, walk the already-sorted `donor_candidates` (slack desc) and accumulate reductions.\n - Each donor's contribution = `min(remaining_target, donor.slack)` — each donor's `min_height_px` floor is preserved (existing rule 4-d kept intact).\n - Sum ≥ `target_added_px` → `feasible=True`. Sum < `target_added_px` → `feasible=False` with combined-slack trace.\n - `zones_after` reflects every donor's reduction.\n\n2. **axis_2 — trace fidelity** : Add `donors_applied: [{position, reduced_px}]` list to the retry plan output.\n - Existing scalar fields `donor_zone_position` / `donor_reduced_px` retained for back-compat (populated from the single-donor case or from the primary entry in the list). Existing pipeline / failure_router consumers see no regression.\n - For `feasible=False`, `failure_reason` cites both *combined slack* and *target_added_px*, and records the candidate-list length.\n - Downstream `_attempt_zone_ratio_retry` (`src/phase_z2_pipeline.py:1886`) log line assumes a single donor. Plumbing change is optional (back-compat allows skip); Stage 2 decides.\n\n### OUT of scope (explicit reject)\n\n- **`glue_compression` router action label** — shrinks shared CSS variables. Violates Step 17 user lock + `feedback_phase_z_spacing_direction`. Reject.\n- **`font_size_retry` router action label** — shrinks shared font size. Same lock + memory-rule violations. Reject.\n- **`ACTION_BY_CATEGORY` mapping extension** — router table unchanged. Only `zone_ratio_retry` internals are refined; no new categories or actions.\n- **AI / Kei retry loop (H5) revival** — guardrailed by issue body.\n- **Cross-layout / cross-zone-position transfer** — donor rule 4-a (same-layout siblings) unchanged.\n- **Retry budget increase** — stays 1 (rule 1).\n- **`slide / slide-body / title / divider / footer / zone gap`** — fixed (rule 2).\n- **Catalog (`frame_contracts.yaml`) modification** — read-only reference, no writes.\n\n### Future-axis recording (separate issues, outside IMP-12)\n\nIf glue / font compression are ever to land, they belong inside `frame_internal_fit_candidate` (Step 19, currently undefined envelope). That would require: (1) a new frame-contract `density_envelope` field (Step 10 contract extension), (2) per-frame opt-in declarations, (3) execution scope strictly inside the declaring frame's DOM subtree — never shared CSS vars. None of that is in IMP-12.\n\n## Guardrails\n\n- AI fallback X (`feedback_ai_isolation_contract`).\n- Kei retry loop (H5) X (issue body guardrail).\n- Status semantics consistent — when retry is infeasible, `RENDERED_WITH_VISUAL_REGRESSION` is preserved (issue body).\n- No shared CSS / font / padding shrink (Step 17 user lock + `feedback_phase_z_spacing_direction`).\n- No new router action labels (Step 17 user lock).\n- Each donor's `min_height_px` floor preserved (existing rule 4-d).\n- Retry budget = 1 (existing rule 1).\n- Catalog SHA unchanged.\n- Zero collision with `IMP-05` — IMP-05 touches the router `ACTION_BY_CATEGORY` / V4 fallback action surface; IMP-12 touches `plan_zone_ratio_retry` internals + trace plumbing only.\n\n## Validation (3-axis status)\n\n- **rendered** : `pytest -q tests/phase_z2/test_retry_gate.py` regression 0 (single-donor early-exit paths unchanged). New unit-test file decided in Stage 2.\n- **visual_check** : MDX 03 / 04 / 05 single-donor cases unchanged. Identify at least one case where multi-donor newly becomes feasible (Stage 2 names the sample).\n- **full_mdx_coverage** : This axis refines the retry path. Coverage is orthogonal.\n\n### Tests (Stage 2 will finalize)\n\n- New unit test (e.g. `tests/phase_z2/test_zone_ratio_retry_plan.py`):\n - Direct calls to `plan_zone_ratio_retry`.\n - Case A: single donor sufficient → legacy behavior (output identical).\n - Case B: primary < target, primary + secondary ≥ target → `feasible=True`, `donors_applied` length 2, sum(reductions) == `target_added_px`, each donor's `min_height_px` floor verified.\n - Case C: all donors combined < target → `feasible=False`, `failure_reason` cites combined slack + target.\n - Case D: zero donors → existing `no donor candidates` failure (unchanged).\n- Existing `tests/phase_z2/test_retry_gate.py` PASS preserved.\n\n## Dependency\n\n- `soft link: IMP-05` (Step 16 router scope shared, parallel allowed) — IMP-05 extends `ACTION_BY_CATEGORY` / V4 fallback action; IMP-12 refines `plan_zone_ratio_retry` internals + trace. **No table collision.** Parallel OK.\n\n## Open questions for next reviewer (Codex #1)\n\n**Q1** — Do you concur with rejecting glue + font compression from IMP-12 scope?\n- Basis = Step 17 user lock (line 333) forbidding `density_adjust_candidate`-style labels + `feedback_phase_z_spacing_direction` forbidding shared spacing shrink + Step 19 `frame_internal_fit_candidate` envelope undefined.\n- If concur → scope-lock stands as drafted (axis_1 + axis_2). If not → user-lock-lift request + scope expansion goes to a separate stage.\n\n**Q2** — `donors_applied` list addition: keep legacy scalar `donor_zone_position` / `donor_reduced_px` for back-compat, or drop them in favor of list only?\n- This draft = back-compat preserved (downstream log + failure_router enum reference unchanged). Drop is feasible (only one call site: `phase_z2_pipeline.py:1886`).\n\n## Cross-ref (factual, file:line)\n\n- Router table: `src/phase_z2_router.py:28-37` (ACTION_BY_CATEGORY) + `:61-68` (ACTION_IMPLEMENTATION_STATUS).\n- Retry plan: `src/phase_z2_retry.py:43-195` (single-donor branch 165-180).\n- Retry orchestrator: `src/phase_z2_pipeline.py:1765-1900` (log line 1886).\n- Failure router: `src/phase_z2_failure_router.py` (`donor_slack_insufficient` reference).\n- Step 17 user lock: `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:333`.\n- Step 19 envelope undefined: `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:352`.\n- Memory rule: `memory/feedback_phase_z_spacing_direction.md` (entire body).\n- Salvage source 1: `src/fit_verifier.py:488-587` (redistribute pattern, role/zone schema).\n- Salvage source 2: `src/space_allocator.py:897-1011` (GlueSpec / FONT_SIZE_STEPS / compute_glue_css_overrides / find_fitting_font_size).\n- Audit classification: `docs/architecture/PHASE-Q-AUDIT.md:454,468,476,489-493,548-549,565-569,576` (Salvage Candidate categorization + dual-write review criteria).\n- Backlog definition: `docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md:63` (IMP-12 row).\n- INSIGHT-MAP §3 registry: `docs/architecture/PHASE-Q-INSIGHT-TO-22STEP-MAP.md:118` (Step 16/17 retry refinement axis).\n\n## Stage 2 plan input\n\n- 2 changed files:\n 1. `src/phase_z2_retry.py` (lines 142-195 region — base_plan construction + multi-donor aggregation branch + zones_after computation).\n 2. `src/phase_z2_pipeline.py:1886` (optional — donor log line plumbing; can be skipped if back-compat retains scalar fields).\n- 1 new test file:\n - `tests/phase_z2/test_zone_ratio_retry_plan.py` (4 unit test cases).\n- Stage 4 verify scope: existing `tests/phase_z2/test_retry_gate.py` + new unit tests PASS, and MDX 03 / 04 / 05 baseline regression-free.\n\n=== EVIDENCE ===\n\nCommands run :\n- `Read src/phase_z2_router.py` (entire file, 1-181) — confirmed router action mapping + ACTION_IMPLEMENTATION_STATUS.\n- `Read src/phase_z2_retry.py` (entire file, 1-215) — confirmed single-donor branch location + retry rules.\n- `Grep _attempt_zone_ratio_retry|donor src/phase_z2_pipeline.py` → orchestrator at lines 1765-1900, log line at 1886.\n- `Read docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md` (Step 14-22, lines 298-403) — confirmed Step 17 user lock + Step 19 envelope-undefined notes.\n- `Read docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md` (§4 + §5 + §7 + §8) — action mapping table + current implementation gap.\n- `Read docs/architecture/PHASE-Q-AUDIT.md:410-579` — §2.4 V-3 redistribute + §2.5 glue + font compression classification (all Salvage Candidates, §3 review subjects).\n- `Read docs/architecture/PHASE-Q-INSIGHT-TO-22STEP-MAP.md:100-150` — §3 axis registry line 118 (Step 16/17 retry refinement sourced from §2.4 + §2.5).\n- `Grep IMP-12 docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md` → backlog entry at line 63.\n- `Read src/fit_verifier.py:488-587` — confirmed `ROLE_ZONE_MAP` + `redistribute` zone-internal role-to-role px transfer pattern.\n- `Read src/space_allocator.py:897-1011` — confirmed `GlueSpec / SPACING_GLUE / FONT_SIZE_STEPS / compute_glue_css_overrides / find_fitting_font_size`; the glue overrides target shared CSS vars `--spacing-block` / `--spacing-inner` / `--container-padding`.\n- `Read memory/feedback_phase_z_spacing_direction.md` (entire) — confirmed forbidden expressions (\"shared text-line gap / transform-block padding / section-title margin-bottom 줄이자\") + allowed-layer 5-step ladder.\n- `Grep plan_zone_ratio_retry|zone_ratio_retry tests/` → `tests/phase_z2/test_retry_gate.py` covers only single-donor + early-exit paths; multi-donor unit tests absent.\n\nFacts established :\n1. `plan_zone_ratio_retry` is single-donor only — multi-donor aggregation is explicitly flagged as \"future\" (`phase_z2_retry.py:178-179`).\n2. `compute_glue_css_overrides` shrinks shared CSS variables — same pattern as the `density_adjust_candidate` antipattern forbidden by Step 17 user lock.\n3. `find_fitting_font_size` shrinks shared font size — not mappable to any of the 5 allowed layers in `feedback_phase_z_spacing_direction`.\n4. Step 19 `frame_internal_fit_candidate` envelope is undefined — no contract container exists for glue / font compression to live inside.\n5. Router `ACTION_BY_CATEGORY` is a closed mapping — registering a new action label would violate Step 17 user lock.\n6. `tests/phase_z2/test_retry_gate.py` does not cover the multi-donor case — new test required.\n7. `IMP-05` works on the router table / V4 fallback surface; `IMP-12` works inside the retry plan — no table collision, parallel execution OK.\n\nUnknown surface (Stage 2 plan will resolve) :\n- `_attempt_zone_ratio_retry` (`phase_z2_pipeline.py:1886`) donor log line plumbing: skip vs. extend to list — back-compat allows skip; Stage 2 to decide.\n- Exact fixture numerics for the new tests (zone min_heights / observed excess_y) — Stage 2 plan finalizes.\n- Multi-donor aggregation reduction policy: greedy fill (slack-desc order) vs. proportional (per-donor slack share). This scope-lock = greedy fill (simpler, deterministic). Stage 2 confirms.\n\nFINAL_CONSENSUS: YES\n", "assets": [], "created_at": "2026-05-17T22:46:12+09:00", "updated_at": "2026-05-17T22:46:12+09:00"}, {"id": 18948, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/12#issuecomment-18948", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/12", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Codex #1] Stage 1 problem-review Round #1\n\nVerdict: root cause is verified, with one scope-lock caveat.\n\nVerified root cause:\n- `src/phase_z2_retry.py:163-178` explicitly uses one `primary_donor` and returns `feasible=False` when that donor slack is below `target_added_px`; the failure reason says multi-donor aggregation is a future axis.\n- `src/phase_z2_pipeline.py:1780-1815` locks retry budget to 1 and strict donor rules; `src/phase_z2_pipeline.py:1845-1869` calls `plan_zone_ratio_retry` and stops before rerender when the plan is infeasible.\n- `src/phase_z2_failure_router.py:121-136` classifies the resulting failure text as `donor_slack_insufficient`, and `src/phase_z2_failure_router.py:61-65` maps that to `layout_adjust` only as a next proposed action.\n\nVerified current action surface:\n- `src/phase_z2_router.py:27-36` maps 8 overflow categories to actions.\n- `src/phase_z2_router.py:58-64` marks `zone_ratio_retry` implemented, `layout_adjust` and `details_popup_escalation` missing, `frame_reselect` and `adapter_needed` partial, and `abort` implemented.\n- `src/phase_z2_pipeline.py:4329-4342` writes Step 17 as `zone_ratio_retry` only and documents other actions as missing/partial.\n\nVerified salvage sources:\n- `src/fit_verifier.py:488-587` contains `ROLE_ZONE_MAP` and deterministic `redistribute(...)` for same-zone deficit/surplus transfer.\n- `src/space_allocator.py:897-919` contains `GlueSpec`, `SPACING_GLUE`, and `FONT_SIZE_STEPS`.\n- `src/space_allocator.py:944-1011` contains `compute_glue_css_overrides(...)` and `find_fitting_font_size(...)`.\n- `docs/architecture/PHASE-Q-AUDIT.md:1231-1237`, `docs/architecture/PHASE-Q-INSIGHT-TO-22STEP-MAP.md:102-103`, and `docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md:62` all identify the Step 16/17 retry axis as deterministic `redistribute` + glue + font compression, with AI/Kei retry guardrails.\n\nScope-lock caveat:\n- I could not verify a standalone memory file containing the exact quoted `feedback_phase_z_spacing_direction` wording (`shared text-line gap`, `section-title margin-bottom`, etc.). Repository evidence does support the broader lock: `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:110-114` says common CSS/padding/tolerance shrinking is not a graph change and is forbidden; `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md:420-428` cross-references `feedback_phase_z_spacing_direction` for Step 17 common spacing changes.\n- Therefore Stage 2 should not assume glue/font compression can be adopted blindly. It must define deterministic action labels and status semantics while respecting the no-silent-shrink / no-common-padding-shrink lock, or explicitly exclude unsafe submechanisms.\n\nMissing files / wrong assumptions flagged:\n- No required source/doc file from the issue references was missing.\n- Wrong assumption to avoid: treating Phase Q glue/font helpers as automatic Step 17 fixes. The docs classify them as Salvage candidates/axis inputs, not pre-approved runtime behavior.\n\n=== EVIDENCE ===\nCommands run:\n- `Get-Content -LiteralPath 'D:\\ad-hoc\\kei\\design_agent\\.orchestrator\\tmp\\codex_prompt.txt'`\n- `rg -n \"ACTION_BY_CATEGORY|zone_ratio_retry|layout_adjust|details_popup_escalation|frame_reselect|adapter_needed|abort\" src/phase_z2_router.py src/phase_z2_retry.py src/phase_z2_pipeline.py src/phase_z2_failure_router.py`\n- `rg -n \"def plan_zone_ratio_retry|single primary donor|primary_donor|donor_slack_insufficient|retry_budget|slack|feasible\" src/phase_z2_retry.py src/phase_z2_pipeline.py src/phase_z2_failure_router.py`\n- `rg -n \"ROLE_ZONE_MAP|def redistribute|GlueSpec|SPACING_GLUE|FONT_SIZE_STEPS|compute_glue_css_overrides|find_fitting_font_size\" src/fit_verifier.py src/space_allocator.py`\n- `rg -n \"Step 17|frame_internal_fit_candidate|density_adjust_candidate|Step 19|internal envelope|Salvage|IMP-12|redistribute|glue|font\" docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md docs/architecture/PHASE-Q-AUDIT.md docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md docs/architecture/PHASE-Q-INSIGHT-TO-22STEP-MAP.md`\n- `Get-Content` line-range reads for `src/phase_z2_retry.py`, `src/phase_z2_router.py`, `src/phase_z2_pipeline.py`, `src/phase_z2_failure_router.py`, `src/fit_verifier.py`, `src/space_allocator.py`, `docs/architecture/PHASE-Z-PIPELINE-OVERVIEW.md`, `docs/architecture/PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md`, `docs/architecture/PHASE-Q-AUDIT.md`, `docs/architecture/PHASE-Q-INSIGHT-TO-22STEP-MAP.md`, and `docs/architecture/PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md`\n- `rg -n \"feedback_phase_z_spacing_direction|shared text-line gap|transform-block padding|section-title margin-bottom|breathing room|shrink\" .`\nFiles checked:\n- `D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_router.py`\n- `D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_retry.py`\n- `D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_pipeline.py`\n- `D:\\ad-hoc\\kei\\design_agent\\src\\phase_z2_failure_router.py`\n- `D:\\ad-hoc\\kei\\design_agent\\src\\fit_verifier.py`\n- `D:\\ad-hoc\\kei\\design_agent\\src\\space_allocator.py`\n- `D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\PHASE-Z-PIPELINE-OVERVIEW.md`\n- `D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\PHASE-Z-FIT-CLASSIFIER-ROUTER-SPEC.md`\n- `D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\PHASE-Q-AUDIT.md`\n- `D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\PHASE-Q-INSIGHT-TO-22STEP-MAP.md`\n- `D:\\ad-hoc\\kei\\design_agent\\docs\\architecture\\PHASE-Z-IMPLEMENTATION-ISSUE-BACKLOG.md`\nTest results: not run; Stage 1 explicitly says NO pytest.\n\nFINAL_CONSENSUS: YES", "assets": [], "created_at": "2026-05-17T22:48:25+09:00", "updated_at": "2026-05-17T22:48:25+09:00"}, {"id": 18950, "html_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/12#issuecomment-18950", "pull_request_url": "", "issue_url": "https://gitea.hmac.kr/Kyeongmin/C.E.L_Slide_test2/issues/12", "user": {"id": 28, "login": "Kyeongmin", "login_name": "", "source_id": 0, "full_name": "이경민", "email": "kyeongmin@noreply.localhost", "avatar_url": "https://gitea.hmac.kr/avatars/d883a35f9197b080b2a0c535c3f9357dcd5c7d40b3c42bd8f40218cb94609a8e", "html_url": "https://gitea.hmac.kr/Kyeongmin", "language": "", "is_admin": false, "last_login": "0001-01-01T00:00:00Z", "created": "2025-12-30T10:51:04+09:00", "restricted": false, "active": false, "prohibit_login": false, "location": "", "website": "", "description": "", "visibility": "public", "followers_count": 0, "following_count": 0, "starred_repos_count": 1, "username": "Kyeongmin"}, "original_author": "", "original_author_id": 0, "body": "[Claude #2] Stage 2 simulation-plan Round #1\n\n## Scope axis enumeration (issue body coverage check)\n\n| # | Axis from issue body | Stage 2 unit IDs |\n|---|-----------------------------------------------------------------|--------------------|\n| A1 | multi-donor `zone_ratio_retry` (root cause from Stage 1) | u1, u10 |\n| A2 | `redistribute` salvage (deterministic, `fit_verifier.redistribute`) | u4, u11 |\n| A3 | `glue` salvage (`compute_glue_css_overrides`, frame-scoped) | u5, u12 |\n| A4 | font compression salvage (`find_fitting_font_size`, scoped) | u6, u13 |\n| A5 | Step 16 router action registration (impl-status surface) | u7 |\n| A6 | Step 17 action execution (cascade orchestrator) | u8, u9, u15 |\n| A7 | failure_router cascade rewire (donor_slack → salvage chain) | u2, u3, u14 |\n\nEvery scoped axis maps to ≥1 implementation unit. None deferred.\n\n## Stage 1 unresolved-question resolution (Stage 2 lock)\n\n- **Q1 — multi-donor as label or internal extension?**\n → **Internal extension of `plan_zone_ratio_retry`**. `ACTION_BY_CATEGORY` stays at 8 entries.\n Rationale: donor selection is a planner concern, not a policy concern. No new label preserves\n `feedback_no_hardcoding` + Stage-1 lock against new density-style labels.\n\n- **Q2 — salvage chain: composite vs sequential actions?**\n → **Three sequential router-cascade actions** (`cross_zone_redistribute` → `glue_compression`\n → `font_step_compression`), each with `retry_budget = 1` per action. Cascade is driven by\n `phase_z2_failure_router.NEXT_ACTION_BY_FAILURE` chain, NOT by composite call.\n Rationale: independent feasibility + revert + diagnostic artifact per step; matches existing\n `(b) revert` policy; observable trace per action.\n\n- **Q3 — glue / font output target: CSS-time vs adapter?**\n → **Frame-scoped CSS override at rerender time** (NEW helper emits\n `.slide [data-zone-position=\"\"] { --spacing-inner: ...; --container-padding: ...; font-size: ...; }`).\n NEVER touches global `--spacing-block` / `--spacing-page`.\n Rationale: honors `feedback_phase_z_spacing_direction` (\"resolve within frame envelope, not by\n common shrink\") + Stage-1 upstream lock `PHASE-Z-PIPELINE-OVERVIEW.md:333`.\n\n## Per-axis: expected behavior, files, changes, tests, rollback, side effects\n\n### A1 — multi-donor `zone_ratio_retry`\n\n- **Before**: `src/phase_z2_retry.py:163-180` — only `donor_candidates[0]` evaluated;\n if `primary_donor.slack < target_added_px` → `feasible=False` + reason\n `\"primary donor ... slack ... < target_added_px ...\"`.\n- **After**: greedy aggregation across `donor_candidates` (already sorted by slack desc).\n Each donor contributes up to its slack; loop stops when remaining_needed ≤ 0 OR donor list\n exhausted. `feasible=True` iff aggregate slack ≥ target_added_px. On failure, new reason\n `\"aggregate donor slack px across N donors < target_added_px px\"` →\n classifier maps to existing `donor_slack_insufficient`.\n- **Files**: `src/phase_z2_retry.py` (function body), plus the `zones_after` math (loop\n reducing each donor by its share).\n- **Per-file change**: extend lines 165-195 — replace single-donor block with multi-donor\n loop; preserve all existing return fields; add new fields `donors_used: list[dict]`,\n `aggregate_slack_used: int`. `apply_retry_to_layout_css` already consumes\n `plan[\"zones_after\"]` (dict) so it handles multi-donor with no change.\n- **Tests**: `tests/phase_z2/test_phase_z2_retry_multi_donor.py` — single-donor sufficient\n (regression), single-donor insufficient + 2nd donor sufficient (multi-donor PASS),\n all donors aggregate still < target (multi-donor FAIL with new reason).\n- **Rollback**: revert single commit. `zones_after` semantics unchanged → downstream\n `apply_retry_to_layout_css` keeps working.\n- **Side effects**: `retry_trace.plan` grows by 2 fields. Step 17 JSON artifact grows\n ~50 bytes. No effect on slide_status.\n\n### A2 — `cross_zone_redistribute` (new salvage action #1)\n\n- **Before**: `fit_verifier.redistribute(analysis, containers)` already redistributes\n within-zone roles (e.g., `body` zone has roles 본심+배경; surplus role donates to\n deficit role until `min_margin_px`). Used in `pipeline.py` legacy path only; NOT wired\n into Step 17.\n- **After**: NEW `plan_cross_zone_redistribute(...)` in `src/phase_z2_retry.py` —\n wraps `fit_verifier.redistribute` with a Step-17 plan-style signature. Input:\n target frame's role-level analysis from `composition` artifact; output: per-role\n `allocated_px` override dict, feasible flag, failure reason. On feasible, the apply\n function emits frame-scoped CSS:\n `.slide [data-zone-position=\"\"] [data-role=\"본심\"] { height: ; } [data-role=\"배경\"] { height: ; }`\n (or equivalent grid-row-span override, whichever the existing frame template uses).\n- **Files**: `src/phase_z2_retry.py` (new function `plan_cross_zone_redistribute` +\n `apply_cross_zone_redistribute_css`).\n- **Per-file change**: ~50 lines: invoke `fit_verifier.redistribute(analysis, containers)`,\n inspect `analysis.can_redistribute`, build plan dict (feasible, role_heights_after,\n failure_reason). NO new logic — pure wiring.\n- **Tests**: `tests/phase_z2/test_phase_z2_cross_zone_redistribute.py` — body-zone with\n surplus 배경 + deficit 본심 → feasible=True with new heights; single-role zone →\n feasible=False reason `\"single-role zone — no intra-zone redistribution possible\"`.\n- **Rollback**: revert single commit. `fit_verifier.redistribute` untouched.\n- **Side effects**: `retry_trace.salvage_steps[]` grows by 1 entry per attempt.\n\n### A3 — `glue_compression` (new salvage action #2, frame-scoped)\n\n- **Before**: `space_allocator.compute_glue_css_overrides(excess_px, block_count)` returns\n `{\"--spacing-block\": \"Xpx\", \"--spacing-inner\": \"Ypx\", \"--container-padding\": \"Zpx\"}`.\n Currently unused in Phase Z runtime.\n- **After**: NEW `plan_glue_compression(...)` in `src/phase_z2_retry.py` — calls\n `compute_glue_css_overrides` on excess from `overflow.zones[target_zone].excess_y` and\n the frame's `block_count` (from `debug_zones[target].composition_rationale.frame_block_count`).\n Returns plan with `scoped_css_overrides: dict[str, str]`, `target_zone_position`,\n feasible (True if `excess_after_glue_px ≤ 0`), `failure_reason`. Apply helper emits\n ZONE-SCOPED block: `.slide [data-zone-position=\"\"] { --spacing-block: ...; ... }`.\n GLOBAL `--spacing-block` is NOT modified.\n- **Files**: `src/phase_z2_retry.py` (new function + apply helper).\n- **Per-file change**: ~45 lines.\n- **Tests**: `tests/phase_z2/test_phase_z2_glue_compression.py` — feasible case\n asserts (1) returned `scoped_css_overrides` non-empty (2) emitted CSS string is wrapped\n in `[data-zone-position=\"\"]` selector (3) NO global rule emitted (assert\n `body {` / `:root {` not in emitted CSS). Insufficient absorption case →\n feasible=False reason `\"glue absorption px < excess px (frame envelope)\"`.\n- **Rollback**: revert single commit. `compute_glue_css_overrides` untouched.\n- **Side effects**: rerender HTML grows by ~200 bytes when this action fires (a `` block placed inside `` (or prepended if `` absent). The candidate is written to `salvage__candidate.html` next to the slide, then `run_overflow_check` runs.\n - On PASS: `out_path.write_text(candidate_html)` promotes the candidate to `final.html`; step recorded with `post_salvage_overflow`; loop exits with `salvage_passed=True`.\n - On FAIL (infeasible plan, no CSS, or visual_check fail): step recorded with `failure_reason` (planner reason first, then visual_check fail_reasons, then a generic fallback). `failure_type` updated via `_SALVAGE_FAIL_BY_ACTION[next_action]` so the next loop iteration's `route_retry_failure` advances the cascade.\n - **No `final.html` mutation outside `passed=True`** — (b) revert preserved on every fail path (skip, terminal, infeasible, no-CSS, visual_check fail). Verified via probes 3, 5, 6.\n4. **Backward-compat / surface invariants preserved**:\n - `_attempt_zone_ratio_retry` body unchanged.\n - `enrich_retry_trace_with_failure_classification` import preserved (u2/u3 surface intact).\n - No changes to `phase_z2_router.ACTION_BY_CATEGORY`, `_attempt_zone_ratio_retry`, or pipeline-Step-17 wiring (u9 territory).\n - No `ACTION_IMPLEMENTATION_STATUS` / `NEXT_ACTION_IMPLEMENTATION_STATUS` mutations (u7 already registered IMPLEMENTED for the 3 cascade actions).\n\n### evidence\n- `python -c \"from src.phase_z2_pipeline import _attempt_salvage_chain, _SALVAGE_FAIL_BY_ACTION; ...\"` → `OK: ['cross_zone_redistribute', 'glue_compression', 'font_step_compression']` (import + table sanity).\n- `python -m pytest -q tests/phase_z2` → **62 passed in 0.12s** (regression-free; u15 will add cascade-orchestrator fixture tests).\n- `git diff --numstat src/phase_z2_pipeline.py` → `95\t1\tsrc/phase_z2_pipeline.py` (only file touched by u8).\n- Direct behavior probes (all PASS):\n - **Probe 3 (IMP-09 gate, `dynamic_cols=True`)**: `salvage_attempted=False`, `salvage_skipped_reason` contains \"IMP-09\", `final.html` unchanged → (b) revert preserved on gate skip.\n - **Probe 4 (IMP-09 gate, `dynamic_rows=False`)**: same as probe 3 → gate covers fr_default layouts.\n - **Probe 5 (terminal-route from non-cascade failure)**: `initial_failure_type='rerender_still_fails'` → `salvage_attempted=True` but `salvage_steps=[]`, `salvage_terminal_action='frame_reselect'`, `final.html` unchanged → terminal route preserves (b) revert without consuming any cascade slot.\n - **Probe 6 (cascade walk from `donor_slack_insufficient`, all 3 stages infeasible)**: `actions == ['cross_zone_redistribute', 'glue_compression', 'font_step_compression']`, all `passed=False`, all `css_override=None`, all `candidate_path=None`, `final.html` unchanged → 3-stage walk works, (b) revert preserved, no spurious render.\n - **Probe 6.5 (per-stage failure_reason surfaces)**: `cross_zone_redistribute` step → `\"fit_analysis missing\"`; `glue_compression` step → `\"excess_px <= 0\"`; `font_step_compression` step → `\"excess_after_glue_px <= 0\"` or `\"text_metrics missing\"` → upstream planner reasons propagate cleanly through the orchestrator.\n - **Probe 7 (u3 routing aligns with u8 lookup table)**: `route_retry_failure('donor_slack_insufficient') → cross_zone_redistribute`; `route_retry_failure('cross_zone_redistribute_insufficient') → glue_compression`; `route_retry_failure('glue_absorption_insufficient') → font_step_compression`; `route_retry_failure('font_step_insufficient') → layout_adjust` → 4-transition cascade verified end-to-end against the failure router surface.\n - **Probe 8 (feasible + render + PASS)**: SKIPPED — needs Selenium-driven `run_overflow_check`. Will be covered by u15 fixture tests in `tests/phase_z2/test_phase_z2_step17_salvage_chain.py`.\n- No new tests added in this unit (u15 is the test unit for the cascade orchestrator).\n\n### scope_lock\n- **No changes** to: `phase_z2_router.py`, `phase_z2_failure_router.py`, `phase_z2_retry.py` (all u1-u7 surfaces intact).\n- **No changes** to `_attempt_zone_ratio_retry`, Step 17 telemetry block (lines 4329-4342), or `enrich_retry_trace_with_failure_classification` call site — u9 territory.\n- **No AI/Kei call** on normal path (`feedback_ai_isolation_contract`).\n- **No `:root` / `body` / `.slide` / `.zone` CSS** — the `