This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
[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 재작성 :
|
||||
|
||||
```python
|
||||
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.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-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 1~4 + round 3 Block 1~4 + round 3 Codex #3 Block 1~2 모두 코드 변경 단위 lock.
|
||||
- 잔여 open 결정 0. Stage 4 implementation handoff 준비 완료.
|
||||
|
||||
FINAL_CONSENSUS: YES
|
||||
Reference in New Issue
Block a user