<!-- task=m-workspace-port-env-standardization/02+01_local_infra plan=0 tag=PORT_INFRA -->

# Plan - PORT_INFRA

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

`CODE_REVIEW-local-G05.md`의 구현 에이전트 소유 섹션 작성까지가 구현입니다. 이 task는 `01_service_defaults`의 `complete.log`가 생긴 뒤 시작하세요. 검증 출력은 실제 stdout/stderr를 붙이고, finalization은 code-review가 처리합니다.

## 배경

service defaults가 새 슬롯으로 이동하면 local compose host publish와 workspace helper docs도 같은 포트 정책을 말해야 한다. 이 subtask는 local infra와 operator entrypoint 문서를 정렬하되, CLI/client code references는 후속 task에 둔다. Docker가 없는 환경에서는 compose runtime 확인이 차단될 수 있으므로 syntax와 config-level 검증을 분리한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청`에 기록한다. 구현 에이전트는 직접 사용자에게 묻지 않는다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/trading-expansion/milestones/workspace-port-env-standardization.md`
- `agent-ops/rules/project/domain/operations/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/operations-smoke.md`
- `deployments/local/docker-compose.yml`
- `bin/infra-check`
- `bin/dev`
- `README.md`

### 테스트 환경 규칙

- test_env: `local`.
- `agent-test/local/rules.md`와 `agent-test/local/operations-smoke.md`를 읽었다.
- operations 변경 baseline: helper scripts 변경 시 해당 command 직접 실행 또는 dry-run에 준하는 검증, local infra 변경 시 Docker 가능 여부 기록.
- Docker CLI는 현재 PATH에서 발견되지 않았다. plan은 `command -v docker` 결과와 conditional `docker compose config` 검증을 요구한다.
- `.gitignore`의 Agent-Ops block은 이미 task artifact를 unignore한다.

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

- `deployments/local/docker-compose.yml` host publish 변경은 unit test가 없으므로 `docker compose config` 또는 Docker 부재 blocker 기록이 필요하다.
- `bin/infra-check` fallback 문자열 변경은 shell syntax와 `rg` drift 검증으로 확인한다. 실제 health check는 Docker daemon/services가 필요하다.
- README/bin/dev 문서 변경은 command output test가 없으므로 `bash -n`과 `rg`로 확인한다.

### 심볼 참조

- renamed/removed symbols: none.

### 분할 판단

- split policy를 평가했다. 이 subtask는 operations surface만 다루며, service defaults가 선행되어야 한다.
- predecessor `01`: missing. `agent-task/m-workspace-port-env-standardization/01_service_defaults/complete.log`가 아직 없다.
- task directory dependency `02+01_local_infra`가 runtime source of truth다. 구현은 predecessor complete 후 시작한다.

### 범위 결정 근거

- 수정 범위는 `deployments/local/docker-compose.yml`, `bin/infra-check`, `bin/dev`, `README.md`.
- `services/**` defaults는 `01_service_defaults`, `apps/cli/**`와 `apps/client/**` references는 `03+01,02_operator_client_refs`에서 처리한다.
- 원격 서버 배포 포트 즉시 변경과 private host/secret 기록은 범위 밖이다.

### 빌드 등급

- build/review: `local-G05`. shell/compose/docs 변경이지만 코드 path가 짧고 검증은 syntax/config/rg 중심이다.

## 의존 관계 및 구현 순서

`01_service_defaults`가 `complete.log`를 남긴 뒤 시작한다. 만족 경로는 `agent-task/m-workspace-port-env-standardization/01_service_defaults/complete.log` 또는 matching archive `agent-task/archive/*/*/m-workspace-port-env-standardization/01_service_defaults/complete.log`다.

## 구현 체크리스트

- [ ] predecessor `01_service_defaults`의 `complete.log` 존재를 확인한다.
- [ ] compose host publish를 PostgreSQL `15430:5432`, Redis `16330:6379`로 바꾼다.
- [ ] `bin/infra-check` default URL과 default-branch comparison 문자열을 `15430/16330`으로 맞춘다.
- [ ] `bin/dev`와 `README.md`의 실행 안내를 API `18030`, Flutter preview `13030`, DB/cache `15430/16330` 기준으로 갱신한다.
- [ ] `bash -n`, conditional Docker config, `rg` drift 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [PORT_INFRA-1] Compose host publish

문제: `deployments/local/docker-compose.yml:9`와 `deployments/local/docker-compose.yml:21`이 host publish를 `5432:5432`, `6379:6379`로 둔다.

Before:

```yaml
# deployments/local/docker-compose.yml:8-9
ports:
  - "5432:5432"

# deployments/local/docker-compose.yml:20-21
ports:
  - "6379:6379"
```

해결 방법: host side만 `15430`과 `16330`으로 바꾸고 container 내부 포트는 `5432/6379`로 유지한다.

수정 파일 및 체크리스트:

- [ ] `deployments/local/docker-compose.yml`

테스트 작성: compose YAML 변경이라 별도 unit test는 없다.

중간 검증:

```bash
if command -v docker >/dev/null 2>&1; then docker compose -f deployments/local/docker-compose.yml config; else echo "docker unavailable; compose config not run"; fi
```

기대 결과: Docker가 있으면 exit code 0. Docker가 없으면 echo 출력과 함께 CODE_REVIEW에 infra runtime 미검증 위험을 기록한다.

### [PORT_INFRA-2] infra-check defaults

문제: `bin/infra-check:7-8`, `bin/infra-check:53`, `bin/infra-check:60`이 default DB/cache host URLs를 `5432/6379`로 비교한다.

Before:

```bash
# bin/infra-check:7-8
database_url="${DATABASE_URL:-postgres://alt:alt@localhost:5432/alt?sslmode=disable}"
redis_url="${REDIS_URL:-redis://localhost:6379/0}"
```

해결 방법: default URL과 equality comparison을 `localhost:15430`, `localhost:16330`으로 갱신한다. compose default branch는 계속 Docker compose service health를 검사한다.

수정 파일 및 체크리스트:

- [ ] `bin/infra-check`

테스트 작성: shell script라 unit test 대신 syntax와 string drift 검증을 사용한다.

중간 검증:

```bash
bash -n bin/infra-check
rg --sort path -n "localhost:15430|localhost:16330|localhost:5432|localhost:6379" bin/infra-check
```

기대 결과: `bash -n` exit code 0. `rg` 출력은 새 defaults와 intentional absence/presence를 설명 가능해야 한다.

### [PORT_INFRA-3] dev and README entrypoints

문제: `bin/dev:22`는 API URL `ws://127.0.0.1:8080/socket`, `bin/dev:25`는 unspecified Flutter port를 보여준다. `README.md:38`, `README.md:61`, `README.md:113-126`도 현재 default와 후보를 분리해 적고 있어 migration 후 최신 실행값으로 정리해야 한다.

Before:

```bash
# bin/dev:21-25
Operator headless scenarios (see apps/cli/testdata/operator/headless_validation.md):
  cd apps/cli && go run ./cmd/alt operator scenario validate --file testdata/operator/market_data_status_query.yaml
  cd apps/cli && go run ./cmd/alt operator scenario run --file testdata/operator/market_data_status_query.yaml --api-url ws://127.0.0.1:8080/socket --output jsonl

Client:
  cd apps/client && flutter run -d chrome
```

해결 방법: `bin/dev`의 API URL을 `18030`, Flutter command를 `flutter run -d chrome --web-port 13030`로 바꾼다. README quick start, command table, env defaults, Workspace Port Slots를 migration 완료 상태로 갱신한다.

수정 파일 및 체크리스트:

- [ ] `bin/dev`
- [ ] `README.md`

테스트 작성: docs/script output 변경이라 별도 unit test는 없다.

중간 검증:

```bash
bash -n bin/dev
rg --sort path -n "13030|18030|15430|16330|8080|5432|6379" bin/dev README.md
```

기대 결과: `bash -n` exit code 0. README의 old defaults는 migration note나 intentional historical context 외 남지 않아야 한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `deployments/local/docker-compose.yml` | PORT_INFRA-1 |
| `bin/infra-check` | PORT_INFRA-2 |
| `bin/dev` | PORT_INFRA-3 |
| `README.md` | PORT_INFRA-3 |

## 최종 검증

```bash
bash -n bin/dev bin/infra-check
if command -v docker >/dev/null 2>&1; then docker compose -f deployments/local/docker-compose.yml config; else echo "docker unavailable; compose config not run"; fi
rg --sort path -n "5432:5432|6379:6379|127\\.0\\.0\\.1:8080|localhost:5432|localhost:6379|flutter run -d chrome$" deployments/local bin README.md
```

기대 결과: shell syntax exit code 0. Docker가 없으면 compose runtime/config 검증은 blocker/risk로 기록한다. 마지막 `rg`는 의도치 않은 old operations defaults가 없음을 보여야 하며, 남는 항목은 intentional exception으로 설명한다.

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