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

16 KiB
Raw Blame History

[Claude #4] Stage 3: 수정 계획 — round 4

Codex #3 round 3 의 2 remaining blocker 모두 실측 검증 + 수용. 본 round 에서 lock. 모든 line 번호 main HEAD 실측. 코드 미수정.

0. Codex #3 round 3 정정 검증

Codex #3 finding 검증 결과 처분
Block 1 — PR 1 의 unsupported override 동작이 plan 안에서 자기모순. round 3 §3 가 "strict raise" 와 "warn-and-fallthrough 그대로" 동시 명시 확인. D:\ad-hoc\kei\design_agent\src\phase_z2_pipeline.py:897-902 = print(f" [override-warning] ... 미지원 ...", file=sys.stderr) + 그 뒤 :918-927 fr_default_from_preset 으로 fallthrough. 현재 코드 = warn + fallthrough, raise 아님. round 3 §3 의 "strict raise" 표현이 실측과 불일치 수용 — Option A-1 lock (§1)
Block 2 — span-sum negative test 가 잘못된 axis 사용. top-1-bottom-2top 은 col 을 span, row 는 non-span 확인. D:\ad-hoc\kei\design_agent\templates\phase_z2\layouts\layouts.yaml:72 = css_areas: '"top top" "bottom-left bottom-right"'. row 0 = top top (top 은 row 0 only, span=1 on row), row 1 = bottom-left bottom-right. col 0 = top + bottom-left, col 1 = top + bottom-right (top 은 cols 0+1, span=2 on col). row 축의 top.h=0.4 + bl.h=0.3 + br.h=0.3 케이스는 R-3 (span sum) 이 아니라 R-5 (total sum 0.7) 로 raise. R-3 검증 안 됨 수용 — col-axis 로 재작성 (§2)
Non-block — compute_zone_layout_2d 의 정확한 scoring 식이 round 3 에 미명시 acknowledge. Stage 4 implementation 시 Stage 2 §4 Q-r2-2 / Q-r2-3 lock (sum aggregation, span zone counted once) 준수. content_weight + min_height_px + no min_width_px 입력 invariant 유지 non-blocker / 유지
Non-block — 신규 test/fixture 파일이 아직 미존재 acknowledge. plan obligation, Stage 4 land non-blocker / 유지

2 blocker 모두 실제 코드 + catalog 데이터 기준 검증. round 3 §3 / §2 의 두 surface 본 round 에서 lock.

1. PR 1 unsupported override 동작 — Option A-1 lock (Block 1 fix)

round 3 §3 의 모순 해소. 실측 = print + fallthrough. PR 1 = 실측 그대로 보존 (warn-and-fallthrough legacy). PR 2 가 unified helper 도입 시점에 raise 로 promote.

1-A. PR 1 의 override branch 동작 lock

PR 1 override branch (build_layout_css :856-902 의 분기 유지):
  - layout_preset == "horizontal-2"  → 기존 h-only inline 분기 (line 858-877)  [legacy preserve]
  - layout_preset == "vertical-2"    → 기존 w-only inline 분기 (line 878-895)  [legacy preserve]
  - else                              → print warning (:898-901) + return None
                                          → caller 가 dynamic branch 로 fallthrough
                                          → dynamic branch 가 fr_default sink 로 처리
                                            (PR 1 에서 fr_default = _build_fr_default unified
                                             — widths_px / heights_px 채움)

PR 1 의 unsupported override (T / 2x2 / single / 4 side-T) = warn + fr_default with populated geometry. raise 아님. 사용자가 T preset 에 override 던지면 trace = "[override-warning] ... default layout_css 사용" + fr_default_from_preset computation + 평등 분배 widths_px / heights_px. 기존 dirty run artifacts (data/runs/imp08_stage5_r2_backwardcompat/phase_z2/debug.json 등) 호환.

1-B. PR 2 의 override branch 동작 lock

PR 2 override branch (unified _override_to_grid_tracks 도입):
  - 8 preset 전수: _override_to_grid_tracks(css_areas, override, "row")
                    + _override_to_grid_tracks(css_areas, override, "col")
                    → 5 룰 strict (R-1..R-5) raise
                    → 정상 시 heights_px / widths_px 계산 + dynamic_rows=True, dynamic_cols=True
  - print warning (line 898-901) 폐기. raise 가 대체.

PR 1 의 4 inline 분기 (horizontal-2 h-only / vertical-2 w-only + 2 warn) 모두 PR 2 에서 삭제. PR 1 → PR 2 의 행동 변경 :

override input PR 1 결과 PR 2 결과
horizontal-2 + {top:{h:0.6}, bottom:{h:0.4}} legacy inline (heights_px=[351,234], dynamic_rows=True, dynamic_cols=False) unified (heights_px=[351,234], widths_px=[1180], dynamic_rows=True, dynamic_cols=True) — widths_px 채워짐
vertical-2 + {left:{w:0.4}, right:{w:0.6}} legacy inline (widths_px=[471,695], dynamic_cols=True, dynamic_rows=False) unified (heights_px=[585], widths_px=[471,695], dynamic_rows=True, dynamic_cols=True)
top-1-bottom-2 + valid override warn + fr_default (dynamic_rows=False, dynamic_cols=False) unified (heights_px, widths_px, dynamic_rows=True, dynamic_cols=True)
top-1-bottom-2 + 누락 zone override warn + fr_default (legacy 와 동일) R-1 raise
horizontal-2 + extra zone override legacy 가 unused key 무시 (실측 :858-877 = geom = override.get(pos["id"], ...), extra key silent ignore) R-2 raise

행동 변경 은 PR 2 의 의도된 promotion. fixture lock (PR 2 가 추가하는 tests/phase_z2/fixtures/build_layout_css/<preset>_override.yaml ×8) 가 새 expected behavior 의 source-of-truth.

1-C. PR 1 의 verifiable scope (RULE 4)

PR 1 commit-scope 검증 = horizontal-2 / vertical-2 의 override 입력 시 :

  • horizontal-2 byte-identity : in-memory dict 의 heights_px / rows / ratios / dynamic_rows 변경 0. 신규 key (widths_px=[1180], width_ratios=[1.0], dynamic_cols=False) 추가만.
  • vertical-2 신규 dynamic : widths_px / cols / width_ratios / dynamic_cols=True 채워짐. heights_px=[585], dynamic_rows=False (PR 2 가 dynamic_rows=True 로 promote 하지만 PR 1 은 False).

PR 1 의 legacy override path 가 PR 2 에서 폐기되는 것은 의도된 PR 간 행동 변경. PR 1 만 ship 한 상태에서는 unsupported override = warn + fr_default 보장.

2. Span-sum negative test 재작성 — col-axis (Block 2 fix)

round 3 §2 의 test_override_span_sum_mismatch :

top-1-bottom-2 의 top.h=0.4 + bottom-left.h=0.3 + bottom-right.h=0.3 → span row sum 0.6 ≠ top 0.4 → R-3 raise

이 케이스는 row 축이지만 top 은 row 0 only (css_areas="top top" "bottom-left bottom-right" → row 0 = [top, top], row 1 = [bottom-left, bottom-right]). row 축에서 top.h=0.4 는 non-spanning (row 0 한 칸). 따라서 :

  • pass 1 (non-span): row[0] = 0.4 (top), row[1] = 0.3 (bottom-left), row[1] = 0.3 (bottom-right, same value → R-4 무위)
  • track_values = [0.4, 0.3], sum = 0.7
  • R-3 미발동, R-5 가 0.7 != ~1.0 으로 raise

→ R-3 검증 실패. R-5 redundant 검증.

2-A. 정정된 R-3 negative test (col-axis)

top 은 col 축에서 span=2. col-axis 로 negative case 재작성 :

def test_override_span_sum_mismatch_col_axis():
    """R-3: spanning zone ratio MUST equal sum of spanned-track ratios.

    top-1-bottom-2 col axis:
      - top  spans cols 0+1 (span=2)
      - bottom-left  in col 0 only (non-span, span=1)
      - bottom-right in col 1 only (non-span, span=1)

    Setup: bottom-left.w=0.3 fixes col 0, bottom-right.w=0.3 fixes col 1.
    Fixed col sum = 0.6. Spanning top.w=0.4 != 0.6 → R-3 raise.
    """
    css_areas = '"top top" "bottom-left bottom-right"'
    override = {
        "top":          {"x": 0, "y": 0, "w": 0.4, "h": 0.3},  # span violation
        "bottom-left":  {"x": 0, "y": 0, "w": 0.3, "h": 0.7},
        "bottom-right": {"x": 0, "y": 0, "w": 0.3, "h": 0.7},
    }
    with pytest.raises(ValueError, match="ratio=0.4.*sum.*0.60"):
        _override_to_grid_tracks(css_areas, override, "col")

기대 raise 메시지 (round 3 §2 snippet line 2423-2427) :

spanning zone top ratio=0.4 != sum of fixed col tracks [(0, 0.3), (1, 0.3)] = 0.600

이 메시지 패턴 = _override_to_grid_tracksif abs(fixed_sum - ratio) > 0.01: raise ValueError(...) 분기. R-3 정확히 trigger.

2-B. 정정된 R-5 redundant 제거

round 3 의 test_override_sum_out_of_range (h 합 = 1.2 → R-5 raise) 는 별 케이스이므로 유지. R-3 test 가 R-5 도 같이 trigger 하던 redundancy 만 해소.

2-C. 5 negative test 최종 lock (test_override_to_grid_tracks.py)

test name axis input
test_override_extra_position both R-2 extra {foo: {h:0.3, w:1.0}}
test_override_missing_position both R-1 top 누락
test_override_span_sum_mismatch_col_axis col R-3 top.w=0.4, bl.w=0.3, br.w=0.3 → fixed=0.6 ≠ 0.4
test_override_non_span_conflict row R-4 bl.h=0.4, br.h=0.6 (둘 다 row 1, 0.01 초과 차)
test_override_sum_out_of_range row R-5 top.h=0.3, bl.h=0.4, br.h=0.5 → sum 1.2

5 negative + 5 positive (h-2/v-2/top-1-bottom-2/grid-2x2/single 의 valid override) = 10 case. PR 2 land.

3. round 3 plan delta (요약)

§1 (PR 1 override) — round 3 §3 의 "strict raise" / "warn-and-fallthrough 그대로" 동시 명시 폐기 → PR 1 = warn-and-fallthrough lock / PR 2 = strict raise (unified helper) promote 명시.

§2 (negative test axis) — round 3 의 R-3 row-axis 케이스 (실제로는 R-5 trigger) → col-axis 로 정정. R-3 / R-5 각각 별 case 로 분리.

§3 ~ §5 (그 외 round 3 lock) — 변경 X. 즉 :

  • §1 unified _compute_per_zone_geometry (round 3 §1) — 유지
  • §2 2-pass _override_to_grid_tracks (round 3 §2) — 유지 (snippet 자체 변경 X)
  • §3 Option A PR scope (round 3 §3) — Option A-1 으로 명시화 (PR 1 = warn-and-fallthrough legacy)
  • §4 rollback trigger paths (round 3 §4) — 유지 (step14_visual_check.json source-of-truth)

4. Final PR scope (round 4 lock)

4-A. PR 1

component scope line / file
_parse_css_areas (strict validation, ValueError 4 case) 신설 src/phase_z2_pipeline.py (834 직후)
_parse_fr_string (1fr / 1fr 1fr parse) 신설 src/phase_z2_pipeline.py
_compute_per_zone_geometry (unified, css_areas span-aware) 신설 + 활성 src/phase_z2_pipeline.py
compute_zone_layout_cols (1-D cols solver, weight-only, zero-weight guard) 신설 src/phase_z2_pipeline.py (834 직후 / compute_zone_layout 뒤)
_build_fr_default (widths_px / heights_px 채움) 신설 (replace :918-927) src/phase_z2_pipeline.py
_build_rows_dynamic (h-2 path, widths_px=[1180]) 신설 (extract :904-916) src/phase_z2_pipeline.py
_build_cols_dynamic (v-2 path, heights_px=[585]) 신설 src/phase_z2_pipeline.py
build_layout_css override branch warn-and-fallthrough 보존 (:897-902 그대로) — horizontal-2 h-only inline 유지, vertical-2 w-only inline 신설 :856-902
build_layout_css dynamic branch rows/cols topology dispatch (T/2x2/inv-T/side-T = fr_default sink 유지, PR 2 가 enable) :904-927
layout_css 신규 key (widths_px, width_ratios, dynamic_cols) 모든 return path 일관 src/phase_z2_pipeline.py
Step 8 col-axis 4 신규 field (per-zone + top-level) 신설 :3228-3260
Retry gate (dynamic_cols=True skip / dynamic_rows=False skip) 신설 (line 1322 직후, base_trace["retry_attempted"] = True 직전) :1322
zone assembly (debug_zones["width_px"] / ["width_ratio"] 주입) 추가 (call _compute_per_zone_geometry) :3067-3073
fixtures horizontal-2 ×3 + vertical-2 ×3 + retry_gate ×3 = 9 YAML tests/phase_z2/fixtures/build_layout_css/, tests/phase_z2/fixtures/retry_gate/
unit tests test_compute_zone_layout_cols.py, test_build_layout_css.py (h-2 + v-2), test_retry_gate.py, test_compute_per_zone_geometry.py (h-2/v-2 path), test_parse_css_areas_validation.py (4 negative case), test_parse_fr_string.py new

4-B. PR 2

component scope line / file
compute_zone_layout_2d (T / inv-T / side-T-L/R / 2x2 solver) 신설 (Q-r2-2 row sum, Q-r2-3 2x2 row/col sum) src/phase_z2_pipeline.py
_override_to_grid_tracks (5 룰 2-pass) 신설 src/phase_z2_pipeline.py
build_layout_css override branch PR 1 의 4 inline 분기 + warn 폐기 → _override_to_grid_tracks unified call (8 preset 전수, strict raise) :856-902 재구조화
build_layout_css dynamic branch T/inv-T/side-T-L/R/2x2 dispatcher 활성 :904-927
_compute_per_zone_geometry 2-D 활성 (rows_grid 의 span aggregation) 활성 (PR 1 이 helper 신설, PR 2 가 2-D 사용) (no code change)
fixtures top-1-bottom-2 / top-2-bottom-1 / left-1-right-2 / left-2-right-1 / grid-2x2 ×3 = 15 YAML tests/phase_z2/fixtures/build_layout_css/
unit tests test_compute_zone_layout_2d.py, test_build_layout_css.py 8-preset extension, test_override_to_grid_tracks.py (5 룰 positive + 5 룰 negative — R-3 = col-axis, §2-C) new

4-C. PR 3

component scope line / file
_build_single (heights_px=[585], widths_px=[1180]) 신설 (dispatcher 의 single 분기) src/phase_z2_pipeline.py
Step 8 nested status (zone_geometry_status="done", region_geometry_status="partial") 추가 :3252-3274
note 갱신 (:3265-3273) "zone-level dynamic = done (IMP-09), region-level 균등 분배 = partial" :3265-3273
fixtures single ×3 tests/phase_z2/fixtures/build_layout_css/
invariant tests test_all_8_presets_dynamic.py (8 preset 모두 fr_default sink 아님), test_v4_full32_layout_coverage.py (select_layout_preset(units: list[CompositionUnit]) 도달 4 default preset 정합) new

4-D. Out-of-scope (변경 X)

select_layout_preset (hardcoded 별 axis), select_layout_candidates, apply_retry_to_layout_css mutation (row-axis only 유지), slide_base.html (cols/rows/areas/gap_px contract 유지), CLI parser (X,Y,W,H + x/y advisory 유지), Kei build_containers_type_b (Phase X-B 격리), IMP-19 _group_blocks_by_area (reference only / import 금지), frame_contracts.yaml min_width_px 도입 (별 issue), Step 8 step_status="complete" 승급 (별 issue), gap row/col 분리 (shorthand 유지).

5. 검증 + rollback (round 3 §4 그대로 lock)

각 PR commit-scope (RULE 4) :

  • PR 1 — python -m pytest -q tests, horizontal-2 render output (cols/rows/areas 문자열) byte-diff = 0, vertical-2 step14_visual_check.json (src/phase_z2_pipeline.py:3694 _write_step_artifact(..., 14, "visual_check", ...), source = run_overflow_check :3690) passes, retry_gate fixture × 3 통과.
  • PR 2 — fixture × 15 (5 preset × 3 scenario) deep-equal, test_override_to_grid_tracks.py 의 5 룰 negative case 모두 expected ValueError raise, 8 preset step14_visual_check.json zone clipping = 0.
  • PR 3 — test_all_8_presets_dynamic.py 8 preset 모두 computation != "fr_default_from_preset", test_v4_full32_layout_coverage.py 4 default preset 도달 정합.

공통 revert : python -m py_compile fail, slide_base.html template contract 변경, apply_retry_to_layout_css mutation 추가.

6. 잔여 axis (본 plan 외 / 별 issue)

  • select_layout_preset catalog-driven 전환
  • frame_contracts.yaml min_width_px 도입 (col-axis solver 가 weight-only 한계 가지면 별 issue)
  • IMP-19 renderer _group_blocks_by_area 직접 통합
  • Step 8 step_status="complete" 승급 (region-level partial 별 axis)
  • render gap row/col 분리
  • 혼합 px/fr ("200px 1fr") catalog 지원

7. Round 4 결론

  • Codex #3 round 3 의 2 blocker (PR 1 override 모순 / span-sum test axis 오류) 모두 실측 검증 + lock.
  • §1 PR 1 override = warn-and-fallthrough legacy 보존 (실측 :897-902 동작 그대로), PR 2 가 unified helper + strict raise 로 promote. PR 간 의도된 행동 변경 fixture lock.
  • §2 R-3 negative test = col-axis 재작성. top-1-bottom-2top 이 cols 0+1 span (row 는 non-span) 인 catalog 실측 반영. R-5 와 분리.
  • §4 PR scope = round 3 §3 의 Option A 를 A-1 으로 명시 (PR 1 legacy override inline + PR 2 unified helper migrate, 4 inline + 2 warn 폐기).
  • 본 round 변경 = 0 (plan only).
  • Stage 2 의 7 결정 + Stage 3 round 1 Q-r2-15 + round 2 Gap 14 + round 3 Block 14 + round 3 Codex #3 Block 12 모두 코드 변경 단위 lock.
  • 잔여 open 결정 0. Stage 4 implementation handoff 준비 완료.

FINAL_CONSENSUS: YES