16 KiB
[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-2 의 top 은 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_tracks 의 if 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.jsonsource-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-2step14_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 presetstep14_visual_check.jsonzone clipping = 0. - PR 3 —
test_all_8_presets_dynamic.py8 preset 모두computation != "fr_default_from_preset",test_v4_full32_layout_coverage.py4 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_presetcatalog-driven 전환frame_contracts.yamlmin_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-2의top이 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-1
5 + round 2 Gap 14 + round 3 Block 14 + round 3 Codex #3 Block 12 모두 코드 변경 단위 lock. - 잔여 open 결정 0. Stage 4 implementation handoff 준비 완료.
FINAL_CONSENSUS: YES