# 통합 아키텍처 결함 분석

> 작성: 2026-04-17 (v0.5.3, branch `shot-more`)
> 입력: Claude 초안 + Codex 검토 + 코드 재검증
> 분석 범위: backend 전체 + frontend 주요 페이지

---

## 0. Executive Summary

TheRoad Scene Lab은 10개월간 기능 단위 확장을 통해 **13개 구조적 결함**을 누적했다. 초기 프로토타입의 "체크포인트 파일 중심" 설계 위에 Postgres가 얹히고, v3 `AnalysisService` 경로 위에 v4 `StepRunner` 경로가 병행되었으며, 결과적으로 **진실원·제어·실행 경로 모두가 이중화된 상태**에 있다.

Claude 초안은 **체크포인트 ↔ DB 이중화, `_sync` 거대화, 하드코딩 invalidation, ImageService 비대화**를 잘 포착했으나, 다음 네 가지를 놓쳤다 (Codex 검토가 지적):

1. `AnalysisService` vs `StepRunner` **이중 실행 경로** (1,137줄 + 1,518줄 공존)
2. `image_steps.py`가 API private 함수(`_sync_checkpoints_to_db`)에 **역의존**
3. `ref_image_gen`이 `composite_image_gen`의 상태를 완료 처리하는 **step 경계 붕괴**
4. `test_step_manifest_v3.py`가 "26단계"를 가정하지만 실제는 48단계 — **테스트 baseline 붕괴**

이 4건을 포함한 **13개 결함**을 (1) 데이터 / (2) 제어 / (3) 책임 / (4) 실행경로 네 축으로 재분류하며, 각 결함에 대해 현상·증거·근본 원인·연쇄 영향·수정 복잡도를 기록한다.

---

## 1. 배경 재정리

### 1.1 진화 타임라인

| 시기 | 단계 | 아키텍처 특성 | 잔재 |
|---|---|---|---|
| 초기 프로토타입 | Phase 1~3 | 체크포인트 JSON 파일 중심 | `analysis_steps_legacy.py` |
| v3 리팩토링 | 26단계 | Postgres 본격 도입. `_sync_checkpoints_to_db` 탄생 | `AnalysisService` (1,137줄) |
| v4 beat→shot | shot 단위 | shot 기반. 일부 API가 DB 직접 조작 | `_sync` 722줄로 확장 |
| v0.5.x shot-more | 현재 | `scene_camera_flow`, `scene_consistency`, `character_state_variant` 등 신규 | 이중 실행 경로 잔존 |

### 1.2 현재 상태 지표 (재측정)

| 지표 | 값 | 근거 |
|---|---|---|
| `image_service.py` | 5,281줄 | `wc -l` |
| `api/v1/steps.py` | 1,518줄 | `wc -l` |
| `services/analysis_service.py` | **1,137줄** (신규 확인) | `wc -l` |
| `api/v1/episodes.py` | 382줄 | `wc -l` |
| `core/steps/detail_steps.py` | 1,021줄 | `wc -l` |
| `core/steps/image_steps.py` | 746줄 | `wc -l` |
| `_sync_checkpoints_to_db` | **722줄 한 함수** | `steps.py:768-1487` |
| `STEP_MANIFEST` 총 step 수 | **48** (초안의 33 아님) | `step_manifest.py` 전수 |
| Active step (disabled/on_demand 제외) | ~40 | 상동 |
| `test_step_manifest_v3.py` 가정 | 26단계 | `test_step_manifest_v3.py:11` |
| `EpisodeDetail.tsx` | 1,358줄 | `wc -l` |
| `EpisodeDetail.tsx` useState | 13~34 (측정 방식 의존) | `grep -c useState` |
| `except Exception: pass` 패턴 | **37건** | Agent 전수 조사 |
| DELETE→INSERT 위반 | **12건 이상** | Agent 전수 조사 |
| 하드코딩 downstream 리스트 위치 | **4곳** | Agent 전수 조사 |

---

## 2. 데이터 축 결함

### 2.1 [Critical] 진실원 이중화 (초안 #2.1 + Codex 원칙 보정)

#### 2.1.1 현상

체크포인트 JSON 파일과 Postgres가 **동등한 위치의 진실원**으로 혼재. Codex 지적대로 현재 실제 구조는 "DB Primary"가 아니라 **"checkpoint canonical + DB projection"**에 가깝다.

#### 2.1.2 실제 진실원 분류 (코드 검증)

| 데이터 | 저장소 | 실제 진실원 | 쓰기 순서 | 위반 리스크 |
|---|---|---|---|---|
| `scene_still.is_selected` | 파일 + DB | **DB primary** (v0.5.3) | DB → 파일 atomic | `steps.py:334-401` 참조 |
| `step_run.status` | DB only | **DB** | StepRunner 자동 | 휘발성 아님 |
| `t2i_appearance_count` | DB only (휘발성) | **DB, 재계산 가능** | `_sync` 후 | 스냅샷 복원 시 소실 |
| `entity_canon.short_id` | 파일 + DB | **파일 canonical → DB projection** | 파일 → `_sync` UPSERT | 정상 |
| `relations` | 파일 + DB | **파일 canonical → DB DELETE+INSERT** | 파일 → DB | UPSERT 규칙 위반 (`steps.py:905-967`) |
| `outlook_phase3` | 파일 + DB | **파일 canonical → DB DELETE+INSERT** | 파일 → DB | UPSERT 규칙 위반 (`steps.py:1344-1457`) |
| `scene_detail t2i_prompt` | 파일 + DB | **파일 canonical → DB UPSERT** | 파일 → `_sync` | 정상 |
| `visible_entities` | 파일 + DB + T2I regex | **3단 fallback chain** | `steps.py:1152-1166` | silent degradation |
| 이미지 자산 | DB 메타 + FS 파일 | **둘 다 동시 소유** | Service 내부 | 정상 |

#### 2.1.3 원칙 보정 (Codex 제안 수용)

초안의 "DB Primary, 체크포인트 Backup"은 **현재 실행 엔진이 checkpoint-first이므로 이르다**. 

→ 데이터 종류별 **3-tier 분류**로 재정의 (`01-principles-revised.md` §2 참조).

---

### 2.2 [Critical] `_sync_checkpoints_to_db` 722줄 (초안 #2.2 + Codex 강화)

#### 2.2.1 구조 (코드 검증)

`steps.py:768-1487` 총 722줄, 7개 블록, 3개 호출 경로.

| 블록 | 라인 | 대상 | 패턴 | CLAUDE.md 준수 |
|---|---|---|---|---|
| 1) entity_t2i | 776-881 | `entity_canon`, `entity_episode_link` | UPSERT ✓ | ✓ (v0.5.3 수정) |
| 1.5) entity_relation | 905-967 | `relation_fact`, `relation_participant` | **DELETE + INSERT** | ✗ 위반 |
| 2) scene_detail | 970-1299 | `scene_still` | 조건부 UPSERT | ✓ |
| 2c) scene_summary | 1302-1312 | `scene_still.scene_summary` | UPDATE | ✓ |
| 2d) shot_cinematography | 1314-1342 | `scene_still.shot_type_*` | UPDATE | ✓ |
| 3) outlook_phase3 | 1344-1457 | `entity_canon(outlook)`, `character_outlook` | **DELETE + INSERT** | ✗ 위반 |
| 3b) orphan outlook cleanup | 1459-1474 | `entity_canon(outlook)` | DELETE sweep | - |
| 3c) `_sync_t2i_appearance_counts` | 1476-1477 | `entity_episode_link.t2i_appearance_count` | 런타임 계산 | - |

#### 2.2.2 호출 경로 3개의 의미 차이

| 호출 경로 | 위치 | 의미 | 필요한 블록 |
|---|---|---|---|
| run-all 완료 후 | `steps.py:247-253` | 전체 projection | 모두 |
| scene_director 사전 | `steps.py:217-223` | short_id 확보 | 블록 1만 |
| 개별 step 완료 후 | `steps.py:657-667` | 부분 projection | step별 다름 |
| 이미지 step 사전 | `image_steps.py:58` | 게이트용 DB 채우기 | 1, 2, 3 |
| snapshot restore | `steps.py:577-589` | 복원 후 재동기화 | 복원된 step에 해당하는 블록만 |

#### 2.2.3 Codex 보강 제안 수용

초안의 "CheckpointSyncService 하나로 통합"은 부족하다. **도메인별로 분해**해야 한다 (Codex 제안):
- `EntitySyncService`
- `RelationSyncService`
- `SceneStillSyncService`
- `OutlookSyncService`
- `EpisodeProjectionService`

이유는 호출 목적이 5가지로 다르기 때문 (§2.2.2 참조).

---

### 2.3 [Medium] 체크포인트 원자성 부분 적용 (초안 #2.3)

초안 분석 유효. 이번 세션 `toggle_shot_selection`에 `_atomic_write_json` 로컬 함수 추가(`steps.py:373-378`)했으나, 외부 재사용 불가. `StepRunner.save_checkpoint`의 `os.replace` 로직(`step_runner.py:141-145`)과 중복.

→ `app/core/checkpoint_io.py` 공통 유틸로 통합 (Phase 1).

---

## 3. 제어 축 결함

### 3.1 [Critical] 하드코딩 downstream (초안 #3.1 + Agent 신규 발견)

#### 3.1.1 위치 전수 (4곳)

| 위치 | 라인 | 패턴 | 신규 여부 |
|---|---|---|---|
| `api/v1/steps.py::toggle_shot_selection` | 356-358 | `downstream = [...]` (10개) | 초안 인지 |
| `api/v1/steps.py::toggle_shot_selection` | 383 | `_resume_sensitive = [...]` (3개) | 초안 인지 |
| **`api/v1/steps.py::run_step`** | **658** | `if sid in ("scene_director", ..., "scene_detail", "scene_verify")` (6개) | **Agent 신규 발견** |
| `api/v1/steps.py` 기타 | 분산 | manifest 조회 없이 step_id 리터럴 | 부분 |

`step_manifest.py:579-588`의 `get_all_downstream_recursive()`와 `StepRunner.invalidate_downstream`(`step_runner.py:161`)이 이미 존재하지만 API 레이어가 활용하지 않는다.

#### 3.1.2 Codex 보강 제안 수용

초안의 "recursive downstream으로 통일"은 부족. `_resume_sensitive`는 단순 downstream이 아니라 **재개 시 재실행 필요** 의미이므로 별도 메타가 필요.

→ manifest에 **`resume_sensitive: true`** 플래그 추가 + `invalidates_on_upstream_change` 플래그 (Phase 1).

---

### 3.2 [High] Applicability 런타임 미검증 (초안 #3.2 + Codex 범위 확장)

#### 3.2.1 상황

`step_runner.py:82-92`의 기본 `check_applicability()`는 `if_*` 규칙 전부 `True` 반환. manifest에 선언된 `if_planning_doc`, `if_has_outlooks`가 실효성 없음.

#### 3.2.2 `set_design` drift 사례

| 경로 | 선언 | 실제 |
|---|---|---|
| `step_manifest.py:454` | `"applicability": "always"` | - |
| `set_design_step.py:42-49` | - | `if not settings.set_design_enabled: return no-op` |

manifest "항상 실행", runtime no-op. UI엔 "completed"로 표시 → **false positive completion**.

#### 3.2.3 Codex 범위 확장 지적

applicability 해석은 4곳에서 각기 다르게 수행:
- `StepRunner.check_applicability()` (`step_runner.py:82-92`)
- `GET /steps` (`steps.py:79-126`)
- `POST /steps/run-all` (`steps.py:191-204`)
- UI `PipelineStepsPanel.tsx:173-185`

→ **Step Catalog 레이어에서 단일 resolver로 통합** (Phase 1, `01-principles-revised.md` §4).

---

### 3.3 [High] run-all 실패 복구 계약 부재 (초안 #3.3)

초안 분석 유효. `_run_all_bg`(`steps.py:209-266`)는 sequential 실행, 한 step 실패 시 break. `partial` 상태의 재실행 정책 미명시.

→ Phase 2에서 `RetryPolicy`, partial 복구 semantics 명문화.

---

### 3.4 [Medium] Disabled/Legacy 관리 체계 (초안 #3.4)

#### 3.4.1 현황

`STEP_MANIFEST` 48개 중:
- `applicability: "disabled"`: `shot_cinematography`, `scene_dependency`, `scene_verify` (3개)
- `applicability: "on_demand"`: `scene_split`, `scene_cinematography`, `outlook_extraction`, `outlook_dedup`, `project_summary` (5개)

`analysis_steps_legacy.py` 파일 존재. `STEP_CLASSES` 미등록 (`core/steps/__init__.py`).

`step_manifest.py:1`의 주석 "33단계"는 낡음 (실제 48개).

#### 3.4.2 Codex 보강 지적

파일 이동(legacy 디렉토리로)은 Phase 0/1이 아니라 뒤로 미뤄도 됨. 더 급한 건 **실행 경로 수렴** (§5.1 결함 #5).

→ Phase 1은 manifest `lifecycle` 필드 추가만, 실파일 이동은 Phase 4.

---

## 4. 책임 축 결함

### 4.1 [Critical] ImageService 5,281줄 (초안 #4.1 + Codex 강화)

#### 4.1.1 책임 목록 (11개)

| # | 책임 | 대표 메서드 | 라인 |
|---|---|---|---|
| 1 | 참조 이미지 생성 | `generate_reference_images_only` | 558+ |
| 2 | 단일 엔티티 이미지 | `generate_single_entity_image` | 분산 |
| 3 | 씬 이미지 batch | `generate_scene_with_variations` | 3000+ |
| 4 | 단일 씬 이미지 | `generate_single_scene_image` | 3500+ |
| 5 | 최종 프롬프트 빌드 | `_build_final_scene_prompt` | 62-272 (388줄 단일 함수) |
| 6 | 프롬프트 작곡/번역 | `compose_prompts` | 200-270 |
| 7 | Ref 해소 | `_rewrite_t2i_with_image_refs` | 150-200 |
| 8 | 안전필터 fallback | 분산 | - |
| 9 | 이미지 검증 (GPT Vision) | `_validate_reference` | 분산 |
| 10 | fal.ai 앵글 | `_apply_fal_angle` | 분산 |
| 11 | 체크포인트 읽기 | `_load_*` | 분산 |

#### 4.1.2 Codex 보강 지적

초안의 "3-way 분할"은 맞지만, **step 경계 자체가 붕괴**된 상태이므로 Service 분할 전에 step 경계부터 재정의해야 한다 (결함 #10).

---

### 4.2 [High] `detail_steps::_execute` Closure 공유 (초안 #4.2)

`SceneDetailStep._execute`가 13개 dict를 로드하고 nested `_analyze_one`이 closure로 참조. 타입 안전성 없음, 단위 테스트 불가능.

→ `SceneAnalysisContext` dataclass + `SceneContextLoader` (Phase 3).

---

### 4.3 [High] API 책임 오염 (초안 #4.3)

`steps.py` 1,518줄에 라우팅 + DB 직접 조작 + 파일 I/O + `_sync` 722줄 + snapshot + toggle 비즈니스 로직 공존. Service 레이어 실질적 부재.

→ Phase 2에서 `CheckpointSyncService`, `ShotSelectionService`, `SnapshotService`로 분리.

---

### 4.4 [Medium] 설정 분산 (초안 #4.4)

`settings.fal_ai_enabled`, `settings.set_design_enabled` 등 여러 파일에서 직접 조회:
- `set_design_step.py`, `image_service.py`, `entities.py`에서 5+ 위치
- `project_config` 파싱이 API, step, service에서 중복

→ `SettingsRegistry` (Phase 2).

---

### 4.5 [Medium] Frontend 상태 관리 (초안 #4.5)

| 파일 | useState | useEffect | 3-tuple 패턴 |
|---|---|---|---|
| `EpisodeDetail.tsx` | 13+ | 10+ | 8+ |
| `ProjectDetail.tsx` | 12 | 5 | 5+ |
| `Episodes.tsx` | 6 | 4 | 3+ |
| `Dashboard.tsx` | 7 | 1 | 1 |
| **합계** | **38+** | **20+** | **17+** |

초안의 "useState 51개"는 틀림. 실제는 파일별 분산. 그러나 패턴 반복 문제는 유효.

→ Phase 5에서 React Query 도입.

---

### 4.6 [Medium] 에러 처리 일관성 (초안 #4.6)

`except Exception: pass` **37건**(초안의 12건보다 3배):
1. `api/v1/steps.py` — 7건 (line 60, 183, 184, 556, 621-622, 661)
2. `core/steps/image_steps.py` — 4건
3. `modules/pipeline/outlook_dedup.py` — 3건

→ Phase 5에서 `@api_endpoint` 데코레이터 + 에러 정책 문서.

---

## 5. 실행 경로 축 결함 (Codex 전수 신규)

### 5.1 [Critical 🆕] AnalysisService vs StepRunner 이중 실행

#### 5.1.1 사실 관계

| 경로 | 진입점 | 구현 | 라인 |
|---|---|---|---|
| A: AnalysisService | `POST /episodes/{id}/analyze`, `/reanalyze-scenes` | `episodes.py:134`, `episodes.py:237` → `AnalysisService.run_analysis()` | `analysis_service.py` 1,137줄 |
| B: StepRunner | `POST /steps/run-all`, `POST /steps/{step_id}` | `steps.py:161-266`, `steps.py:602-688` → `StepRunner.run()` | `step_runner.py` 343줄 |

#### 5.1.2 책임 차이

| 항목 | AnalysisService | StepRunner |
|---|---|---|
| 오케스트레이션 | 자체 (Phase1 엔티티 + Phase2 씬) | DAG 기반, manifest 참조 |
| DB 쓰기 | Phase마다 즉시 commit (destructive cleanup 포함 — `analysis_service.py:322`) | 체크포인트 canonical + `_sync` projection |
| 체크포인트 | `segments.json`, `scene_dependencies.json`, `outlook_extraction.json` 3개만 | 40+ step별 manifest.json |
| step_run 기록 | ✗ 없음 | ✓ 모든 step 기록 |
| 스냅샷 | ✗ 미지원 | ✓ 지원 |
| fan-out 병렬 | 제한적 (scene_detail만) | 모든 `fan_out: True` step |
| 재개 | ✗ 전체 재실행 | ✓ `mode=resume` |
| 부분 실행 | ✗ | ✓ 개별 step |

#### 5.1.3 Frontend 호출 현실

`EpisodeDetail.tsx:821`:
```tsx
await api(`/api/v1/projects/${id}/episodes/${episodeId}/steps/run-all?category=analysis`, { method: 'POST' })
```

`PipelineStepsPanel.tsx:125-128`:
```tsx
await api(`/api/v1/projects/${projectId}/episodes/${episodeId}/steps/run-all?category=${category}`, { method: 'POST' })
```

→ **UI는 StepRunner만 호출한다**. AnalysisService 엔드포인트는 UI 버튼 없이 API 직접 호출만 가능 — 사실상 dead endpoint.

#### 5.1.4 수렴 판단

공식 경로는 **StepRunner**로 확정한다. 이유:
1. Frontend가 이미 StepRunner만 사용
2. 기능 우위 (스냅샷, 재개, fan-out 병렬, 부분 실행)
3. v4 beat→shot 아키텍처 네이티브 지원
4. AnalysisService는 v3(26단계) 시절 설계로, 현재 48단계 manifest와 어긋남

→ Phase 4에서 `AnalysisService` 제거, `/episodes/{id}/analyze`를 `run_all_steps(category="analysis")` 래퍼로 교체.

---

### 5.2 [High 🆕] Image Step 경계 붕괴

#### 5.2.1 현상

`RefImageGenStep._execute`(`image_steps.py:311-356`)가 자기 책임 외에 `composite_image_gen` step의 완료까지 기록한다:

```python
# image_steps.py:345-349
if ol_gen + ol_skip > 0:
    self._mark_composite_done(ol_gen + ol_skip)

# image_steps.py:358-375 (_mark_composite_done)
self.db.execute(text("""
    INSERT INTO step_run (..., step_id, status, ...)
    VALUES (..., 'composite_image_gen', 'completed', ...)
    ON CONFLICT ... DO UPDATE SET status = 'completed', ...
"""))
```

`CompositeImageGenStep._execute`(`image_steps.py:380-458`)는 자기 step에서 다시 `generate_reference_images_only(mode="resume")`을 호출한다 (`image_steps.py:436`). → 이미 `ref_image_gen`에서 실행한 것과 중복.

#### 5.2.2 근본 원인

`ImageService.generate_reference_images_only`가 reference + composite 이미지를 모두 생성하도록 설계됨. 두 step 모두 이 하나의 함수를 호출. step 분리의 의미가 없음.

#### 5.2.3 연쇄 영향

- step_run 상태와 실제 작업의 괴리 (`ref_image_gen` 실행 중에 `composite_image_gen`가 completed로 표시)
- snapshot 복원 시 체크포인트와 DB 상태 불일치
- invalidation 의미 혼란 (`composite_image_gen` stale 처리해도 실제 파일 유지)

→ Phase 3에서 step 경계 재정의 + `ImageService` 분할.

---

### 5.3 [Medium 🆕] 테스트 baseline 붕괴

#### 5.3.1 현황

`backend/tests/test_step_manifest_v3.py`:
- `test_total_step_count`: `assert len(STEP_MANIFEST) == 26` (line 11) → 실제 48
- `test_analysis_steps_count`: `assert == 20` (line 15) → 실제 41
- `test_image_steps_count`: `assert == 4` (line 19) → 실제 5
- `test_scene_detail_depends_on_all_prior` (line 70-76): `scene_dependency`, `outlook_extraction`, `scene_cinematography` 의존성 기대 → 현재 `shot_dependency`, `outlook_phase3`, `shot_staging`, `scene_consistency`, `set_design` 의존
- `test_vah_step_is_scene_director` (line 118-120): `label == "씬 감독 (V/A/H)"` → 현재 `"씬 감독 (물리적 존재)"`

→ 테스트가 수 버전 낡음. 리팩토링 전 안전망 역할 불가능.

→ Phase 0에서 baseline 복구 (테스트 작성이 리팩토링 안전망이므로 반드시 선행).

---

## 6. 결함 간 상호작용 / 연쇄 지도

```
 ┌─────────────────────────────────────┐
 │ #5.1 AnalysisService/StepRunner     │ ← Codex 신규
 │ 이중 실행 경로                      │
 └───────────────┬─────────────────────┘
                 │ 유발
                 ↓
 ┌─────────────────────────────────────┐
 │ #1 진실원 이중화                    │
 │ (DB vs checkpoint semantics)        │
 └───────────────┬─────────────────────┘
                 ↓ 유발                  ↓ 유발                   ↓ 유발
 ┌──────────────┐  ┌──────────────┐  ┌──────────────┐
 │ #2 _sync 722줄│  │ t2i_count    │  │ VE 3-fallback│
 │               │  │ 휘발성       │  │              │
 └───────┬──────┘  └──────────────┘  └──────────────┘
         ↓
 ┌──────────────┐
 │ #9 API 책임  │
 │ 오염         │
 └──────────────┘

 ┌──────────────────────────────┐
 │ #3 하드코딩 downstream       │
 └──────────────┬───────────────┘
                ↓
 ┌──────────────┐
 │ 신규 step    │
 │ invalidation │
 │ 누락         │
 └──────────────┘

 ┌──────────────────────────────┐
 │ #6 Applicability 미검증       │
 └──────┬──────┬────────────────┘
        ↓      ↓
  ┌────────┐ ┌──────────────┐
  │ 조건부 │ │ set_design   │
  │ step   │ │ false        │
  │ 항상   │ │ positive     │
  │ 실행   │ │ completion   │
  └────────┘ └──────────────┘

 ┌──────────────────────────────┐
 │ #4 ImageService 5,281줄       │
 └──────────────┬───────────────┘
                ↓
 ┌──────────────────────────────┐
 │ #10 Image step 경계 붕괴     │ ← Codex 신규
 │ (ref→composite 완료 처리)    │
 └──────────────────────────────┘

 ┌──────────────────────────────┐
 │ #13 테스트 baseline 붕괴     │ ← Codex 신규
 └──────────────────────────────┘
           ↓
    모든 리팩토링의
    안전망 부재
```

**핵심 관찰**:
- **#5.1(이중 실행)**이 **#1(진실원)**과 **#2(_sync 거대화)**의 근본 원인 중 하나
- **#10(image step 경계)**는 **#4(ImageService 비대화)**와 분리된 별개 결함
- **#13(테스트 붕괴)**가 모든 리팩토링의 선행 차단 요인 → Phase 0에서 선결

---

## 7. 결함별 수정 우선순위 재정리

| # | 결함 | 초안 심각도 | 최종 심각도 | Phase |
|---|---|---|---|---|
| 1 | 진실원 이중화 | Critical | Critical | 전 Phase 영향, 원칙 통합 (1) |
| 2 | `_sync` 722줄 | Critical | Critical | Phase 2 |
| 3 | 하드코딩 downstream | Critical | Critical | Phase 1 |
| 4 | ImageService 비대 | Critical | Critical | Phase 3 |
| 5 | AnalysisService/StepRunner 이중 (신규) | — | **Critical** | **Phase 4** |
| 6 | Applicability 미검증 | High | High | Phase 1 |
| 7 | run-all 재시도 | High | High | Phase 2 |
| 8 | detail_steps closure | High | High | Phase 3 |
| 9 | API 책임 오염 | High | High | Phase 2 |
| 10 | Image step 경계 (신규) | — | **High** | **Phase 3** |
| 11 | 체크포인트 원자성 | Medium | Medium | Phase 1 |
| 12 | Legacy 관리 | Medium | Medium | Phase 1 (필드만) + Phase 4 (파일 이동) |
| 13 | 테스트 baseline (신규) | — | **Medium** | **Phase 0** (선행 필수) |

---

## 8. 결론

이 코드베이스의 근본 문제는 이제 **두 가지**다:
1. 초기 "체크포인트 파일 중심 → DB 점진 도입" 과정에서 **진실원 계약이 정의되지 않음** (초안 발견)
2. v3 → v4 전환 과정에서 **AnalysisService와 StepRunner가 동시 운영됨** (Codex 발견)

두 문제는 거의 독립이지만 영향 범위가 겹친다 (예: 둘 다 `_sync_checkpoints_to_db`를 거쳐감). 따라서 수정 순서는 중요:

1. **Phase 0**에서 공식 경로(StepRunner)를 선언하고 baseline(테스트)을 복구
2. **Phase 1**에서 catalog 계약을 정립
3. **Phase 2**에서 sync 분해 (이중 경로가 아직 존재한 채로)
4. **Phase 3**에서 image 도메인 분해
5. **Phase 4**에서 이중 경로 수렴
6. **Phase 5**에서 frontend 정비

자세한 설계는 `01-principles-revised.md`, 실행은 `02-final-roadmap.md`에서 다룬다.
