<!-- task=flutter_first_portal_migration/03+01_web_delete_flutter_deploy plan=1 tag=REVIEW_WEB_DEPLOY -->

# Plan - REVIEW_WEB_DEPLOY

## 이 파일을 읽는 구현 에이전트에게

구현 마지막 단계에서 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 반드시 실제 변경 내용과 검증 출력으로 채운다. 검증 명령을 실행하고 실제 stdout/stderr를 기록한 뒤 active 파일은 그대로 두고 리뷰 준비 상태만 보고한다. `complete.log` 작성, `.md` 파일 로그화, archive 이동은 code-review-skill 전용이다.

## 배경

이 follow-up은 `WEB_DEPLOY` 리뷰에서 발견된 Docker build 경로 문제만 고친다. `apps/portal`은 `proto_socket`을 sibling path dependency로 쓰는데, Dockerfile은 해당 sibling package를 이미지에 복사하지 않은 채 `flutter pub get`을 실행한다. compose `web` 서비스가 Flutter Web static serving으로 실제 빌드 가능하려면 Docker build context 안의 `proto-socket/dart`를 이미지의 path dependency 위치에 먼저 제공해야 한다.

## 분석 결과

### 읽은 파일

- `agent-task/flutter_first_portal_migration/03+01_web_delete_flutter_deploy/plan_cloud_G07_0.log`
- `agent-task/flutter_first_portal_migration/03+01_web_delete_flutter_deploy/code_review_cloud_G07_0.log`
- `apps/portal/Dockerfile`
- `apps/portal/pubspec.yaml`
- `../proto-socket/dart/pubspec.yaml`
- `docker-compose.yml`
- `Makefile`
- `bin/web.sh`

### 테스트 커버리지 공백

- Docker image 내부 path dependency 존재 여부는 `make portal-build-web`로는 검증되지 않는다. Docker CLI가 있으면 `docker compose build web`로 확인하고, 없으면 Dockerfile에서 `proto-socket/dart` copy가 `RUN flutter pub get`보다 앞서는지 deterministic static check로 확인한다.

### 심볼 참조

- renamed/removed symbol 없음.

### 분할 판단

분할 정책을 평가했다. Required issue가 `apps/portal/Dockerfile`의 path dependency copy 순서 하나에 국한되고, compose/bin/Flutter 앱 코드를 함께 바꿀 필요가 없으므로 기존 subtask 안 follow-up 단일 plan으로 유지한다.

### 범위 결정 근거

- `apps/portal` UI, wire client, generated Dart protobuf, Control Plane endpoint, `docker-compose.yml`의 service 구조는 변경하지 않는다.
- Docker가 없는 환경에서 Docker CLI를 설치하거나 외부 환경을 수정하지 않는다. unavailable 여부는 명령 출력으로 기록한다.

### 빌드 등급

Build lane `cloud-G07`, review lane `cloud-G07`. Docker/compose 검증과 terminal command 출력 신뢰도 회복이 포함된 follow-up이다.

## 구현 체크리스트

- [ ] [REVIEW_WEB_DEPLOY-1] `apps/portal/Dockerfile`이 `proto-socket/dart` path dependency를 `flutter pub get` 전에 이미지에 제공하도록 수정한다.
- [ ] 중간 검증과 최종 검증 명령을 실행하고 실제 출력을 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_WEB_DEPLOY-1] Docker path dependency 복사

문제: `apps/portal/pubspec.yaml:39`는 `proto_socket`을 `../../../proto-socket/dart` path dependency로 선언한다. `apps/portal/Dockerfile:13`은 `iop/apps/portal/pubspec.yaml`과 lockfile만 복사하고, `apps/portal/Dockerfile:14`에서 바로 `flutter pub get`을 실행하므로 Docker image 안에는 `/workspace/proto-socket/dart`가 없다.

해결 방법: compose의 build context `..`를 유지하고, context 안의 `proto-socket/dart`를 image의 `/workspace/proto-socket/dart`에 복사한다. cache 효율을 위해 dependency manifest를 `flutter pub get` 전에 먼저 복사하고, build 전에 source 전체를 복사한다.

Before:

```dockerfile
apps/portal/Dockerfile:8
WORKDIR /workspace/iop/apps/portal

COPY iop/apps/portal/pubspec.yaml iop/apps/portal/pubspec.lock ./
RUN flutter pub get

COPY iop/apps/portal/ ./
RUN flutter build web \
```

After:

```dockerfile
WORKDIR /workspace/iop/apps/portal

COPY proto-socket/dart/pubspec.yaml proto-socket/dart/pubspec.lock /workspace/proto-socket/dart/
COPY iop/apps/portal/pubspec.yaml iop/apps/portal/pubspec.lock ./
RUN flutter pub get

COPY proto-socket/dart/ /workspace/proto-socket/dart/
COPY iop/apps/portal/ ./
RUN flutter build web \
```

수정 파일 및 체크리스트:

- [ ] `apps/portal/Dockerfile`: `/workspace/proto-socket/dart` 복사를 `RUN flutter pub get`보다 앞에 둔다.
- [ ] `apps/portal/Dockerfile`: build 단계에서 `proto-socket/dart` source 전체를 복사해 `flutter build web` 때 dependency source가 존재하게 한다.
- [ ] `docker-compose.yml`의 `web.build.context: ..`와 `dockerfile: iop/apps/portal/Dockerfile` 계약이 그대로 맞는지 확인한다.

테스트 작성: 별도 테스트 파일은 작성하지 않는다. Dockerfile path/order 검증과 Flutter Web build, Docker 가능 환경의 compose build가 회귀 검증이다.

중간 검증:

```bash
awk '/proto-socket\/dart/ { seen=1 } /RUN flutter pub get/ { exit seen ? 0 : 1 } END { if (!seen) exit 1 }' apps/portal/Dockerfile
```

기대 결과: 출력 없이 exit 0. `proto-socket/dart` copy가 `RUN flutter pub get`보다 뒤에 있으면 실패해야 한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/portal/Dockerfile` | REVIEW_WEB_DEPLOY-1 |

## 최종 검증

```bash
make portal-build-web
awk '/proto-socket\/dart/ { seen=1 } /RUN flutter pub get/ { exit seen ? 0 : 1 } END { if (!seen) exit 1 }' apps/portal/Dockerfile
command -v docker || echo "docker missing"
if command -v docker >/dev/null 2>&1; then docker compose config && docker compose build web; else echo "docker compose verification skipped: docker missing"; fi
git diff --check
```

기대 결과: Flutter Web build 통과, Dockerfile static order check exit 0, Docker가 있으면 compose config/build web 통과. Docker가 없으면 `docker missing`과 skip 메시지를 실제 출력으로 기록한다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
