<!-- task=m-workspace-port-env-standardization/01_compose_env_defaults plan=0 tag=REFACTOR -->

# Plan - REFACTOR

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

`CODE_REVIEW-local-G04.md`의 구현 에이전트 소유 섹션을 채우기 전까지 구현은 완료가 아니다. 검증을 실행하고 실제 출력, 계획 대비 변경, 설계 결정을 기록한 뒤 active 파일을 유지하고 리뷰 준비 상태로 보고한다. 최종 판정, log rename, `complete.log`, archive 이동은 code-review 전용이다. 사용자 결정, 사용자 소유 외부 환경, 범위 충돌이 없이는 진행할 수 없으면 직접 질문하지 말고 review stub의 `사용자 리뷰 요청`에 증거를 남긴다.

## 배경

마일스톤 inventory에서 `.env.example`과 `docker-compose.yml`의 host publish 기본값이 서로 어긋났다. compose 자체는 web `13000`, CP wire `19080/19081`, Edge-Node `19090`을 사용하지만 `.env.example`은 web `3000`, DB/cache `5432/6379`를 그대로 노출한다. 이 subtask는 runtime 코드 변경 없이 compose/env source of truth만 정렬한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 채팅에서 직접 질문하거나 선택지를 제시하지 않으며, code-review가 사용자 리뷰 요청의 정당성을 판단한다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/automation-runtime-bridge/PHASE.md`
- `agent-roadmap/phase/automation-runtime-bridge/milestones/workspace-port-env-standardization.md`
- `docker-compose.yml`
- `.env.example`
- `configs/control-plane.yaml`
- `configs/edge-compose.yaml.tmpl`
- `configs/edge.yaml`
- `configs/node.yaml`
- `configs/worker.yaml`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `agent-test/local/platform-common-smoke.md`
- `agent-test/local/client-smoke.md`
- `agent-test/local/control-plane-smoke.md`

### 테스트 환경 규칙

- 선택 test_env: `local`
- `agent-test/local/rules.md`를 읽었다.
- 적용 profile: `testing-smoke`, `platform-common-smoke`, `client-smoke`, `control-plane-smoke`
- Docker compose 검증은 현재 컨테이너에서 Docker-in-Docker를 쓰지 않고 원격 runner/code-server에서 수행한다.
- tracked 파일에는 host, token, credential 원문을 기록하지 않는다.
- compose 변경은 `docker compose --env-file .env.example config`와 원격 compose smoke로 확인한다.

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

- `.env.example` 기본값 drift는 기존 Go/Flutter unit test로 검출되지 않는다.
- `docker-compose.yml` host publish 변경은 compose config/render 검증과 원격 compose 기동 evidence가 필요하다.
- 이 subtask는 runtime 코드 변경을 하지 않으므로 앱 package unit test는 직접 커버하지 않는다.

### 심볼 참조

- none

### 분할 판단

- split decision policy를 먼저 평가했다.
- shared task group: `m-workspace-port-env-standardization`
- `01_compose_env_defaults`: `.env.example`와 `docker-compose.yml`의 host publish/env source of truth 정렬. 의존성 없음.
- `02+01_runtime_config_docs`: runtime/config/docs default 정렬. `01`의 source of truth 완료가 선행되어야 한다.
- `03+01,02_remote_smoke_evidence`: 원격 compose/smoke evidence 수집. `01`, `02`의 `complete.log`가 선행되어야 한다.

### 범위 결정 근거

- 이 subtask는 `.env.example`와 `docker-compose.yml`만 수정한다.
- `packages/go/config`, app defaults, README, `agent-test/local` 문서 갱신은 `02+01_runtime_config_docs`로 분리한다.
- 원격 compose 기동, Flutter runtime evidence, full-cycle smoke 수집은 `03+01,02_remote_smoke_evidence`로 분리한다.

### 빌드 등급

- `local-G04`: 변경 범위가 compose/env 두 파일로 제한되고, drift가 line-level로 명확하며, 검증은 deterministic render와 후속 원격 smoke로 분리된다.

## 구현 체크리스트

- [ ] `.env.example`의 host publish 기본값을 slot plan에 맞춰 갱신하고 필요한 compose env key를 보강한다.
- [ ] `docker-compose.yml`의 host publish 기본값과 web build args를 `.env.example` 기준과 맞춘다.
- [ ] `docker compose --env-file .env.example config` 또는 원격 runner에서 동등한 render 검증을 실행해 출력에 새 host publish 값이 반영되는지 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REFACTOR-1] Align `.env.example`

문제: `.env.example:3-12`는 CP HTTP `9080`, web `3000`, DB/cache `5432/6379`를 노출하지만 마일스톤 slot plan은 backend `18000`, frontend `13000`, DB/cache `15400/16300`을 후보로 둔다. `docker-compose.yml:87`은 이미 web host default `13000`을 사용해 `.env.example:5`와 충돌한다.

해결 방법:

```dotenv
# before: .env.example:3-12
IOP_CONTROL_PLANE_HTTP_PORT=9080
IOP_CONTROL_PLANE_WIRE_PORT=19080
IOP_WEB_PORT=3000
IOP_POSTGRES_PORT=5432
IOP_REDIS_PORT=6379
```

```dotenv
# after
IOP_CONTROL_PLANE_HTTP_PORT=18000
IOP_CONTROL_PLANE_WIRE_PORT=19080
IOP_CONTROL_PLANE_EDGE_WIRE_PORT=19081
IOP_EDGE_NODE_PORT=19090
IOP_WEB_PORT=13000
IOP_POSTGRES_PORT=15400
IOP_REDIS_PORT=16300
```

수정 파일 및 체크리스트:

- [ ] `.env.example`: port values update
- [ ] `.env.example`: missing compose port keys add
- [ ] `.env.example`: no token/secret raw value add

테스트 작성: 새 테스트 파일은 작성하지 않는다. `.env.example`은 runtime code path가 아니며 compose render 검증으로 확인한다.

중간 검증:

```bash
rg --sort path 'IOP_WEB_PORT=3000|IOP_POSTGRES_PORT=5432|IOP_REDIS_PORT=6379|IOP_CONTROL_PLANE_HTTP_PORT=9080' .env.example
```

기대 결과: 출력 없음.

### [REFACTOR-2] Align compose host publish defaults

문제: `docker-compose.yml:12`와 `docker-compose.yml:26`은 DB/cache host publish 기본값을 `5432/6379`로 둔다. `docker-compose.yml:45`와 `docker-compose.yml:83`은 CP HTTP host/API URL 기본값을 `9080`으로 둔다. 표준 후보는 DB/cache `15400/16300`, backend `18000`이다.

해결 방법:

```yaml
# before: docker-compose.yml:12,26,45,83
- "${IOP_POSTGRES_BIND:-127.0.0.1}:${IOP_POSTGRES_PORT:-5432}:5432"
- "${IOP_REDIS_BIND:-127.0.0.1}:${IOP_REDIS_PORT:-6379}:6379"
- "${IOP_CONTROL_PLANE_HTTP_BIND:-127.0.0.1}:${IOP_CONTROL_PLANE_HTTP_PORT:-9080}:9080"
IOP_CONTROL_PLANE_HTTP_URL: "${IOP_CONTROL_PLANE_HTTP_URL:-http://localhost:9080}"
```

```yaml
# after
- "${IOP_POSTGRES_BIND:-127.0.0.1}:${IOP_POSTGRES_PORT:-15400}:5432"
- "${IOP_REDIS_BIND:-127.0.0.1}:${IOP_REDIS_PORT:-16300}:6379"
- "${IOP_CONTROL_PLANE_HTTP_BIND:-127.0.0.1}:${IOP_CONTROL_PLANE_HTTP_PORT:-18000}:9080"
IOP_CONTROL_PLANE_HTTP_URL: "${IOP_CONTROL_PLANE_HTTP_URL:-http://localhost:18000}"
```

수정 파일 및 체크리스트:

- [ ] `docker-compose.yml`: Postgres host publish default update
- [ ] `docker-compose.yml`: Redis host publish default update
- [ ] `docker-compose.yml`: Control Plane HTTP host publish default update
- [ ] `docker-compose.yml`: web build arg HTTP URL default update
- [ ] `docker-compose.yml`: internal service addresses and healthcheck stay on container ports

테스트 작성: 새 테스트 파일은 작성하지 않는다. compose render와 후속 remote smoke로 검증한다.

중간 검증:

```bash
rg --sort path ':-5432|:-6379|:-9080|localhost:9080' docker-compose.yml .env.example
```

기대 결과: 내부 DB URL/Redis URL 또는 healthcheck처럼 container-internal 용도만 남아야 하며, host publish/default URL drift는 없어야 한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `.env.example` | REFACTOR-1 |
| `docker-compose.yml` | REFACTOR-2 |

## 최종 검증

```bash
command -v docker
docker compose --env-file .env.example config >/tmp/iop-compose-config.out
rg --sort path '18000|13000|15400|16300|19080|19081|19090' /tmp/iop-compose-config.out
```

기대 결과: `docker`가 사용 가능한 원격 runner/code-server에서 compose config가 성공하고 새 host publish 값이 출력에 포함된다. 현재 컨테이너에서 Docker 사용이 불가하면 `CODE_REVIEW-local-G04.md`에 차단 사유와 원격 미실행 사유를 기록한다.

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
