<!-- task=m-workspace-port-env-standardization/03+01,02_remote_smoke_evidence plan=1 tag=REVIEW_TEST -->

# Plan - REVIEW_TEST

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

이 follow-up은 `code_review_cloud_G07_0.log`의 FAIL 판정을 해소한다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하지 않는다. repo-owned Dockerfile/build 계약을 먼저 고친 뒤에도 표준 host slot 검증이 사용자 소유 원격 Docker/port 상태 때문에 불가능하면, `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청` 섹션에 실제 명령과 stdout/stderr, 재개 조건을 기록하고 멈춘다.

## 배경

`03+01,02_remote_smoke_evidence`는 `port-default-rollout`의 최종 evidence split이다. 첫 리뷰 루프에서는 predecessor completion, Go/client/wire/e2e smoke는 확인됐지만, Docker compose full service health가 `apps/client/Dockerfile`의 ignored sibling lock 의존성과 원격 `19080`/`13000` port 점유로 막혔다. 보조 smoke는 통과했으나 standard-slot compose health가 증명되지 않았으므로 roadmap task completion evidence로 볼 수 없다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 채팅에서 직접 질문하거나 선택지를 제시하지 않으며, code-review가 사용자 리뷰 요청의 정당성을 판단한다. 단순한 증거 공백이나 repo-owned build 계약 문제는 사용자 리뷰 요청이 아니라 후속 구현/검증으로 해소한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/workspace-port-env-standardization.md`
- Task ids:
  - `port-default-rollout`: `.env.example`, `docker-compose.yml`, config/default, app README, `agent-test/local`의 포트 기본값과 검증 명령을 표준 후보에 맞춰 정렬한다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-task/m-workspace-port-env-standardization/03+01,02_remote_smoke_evidence/plan_cloud_G07_0.log`
- `agent-task/m-workspace-port-env-standardization/03+01,02_remote_smoke_evidence/code_review_cloud_G07_0.log`
- `agent-task/archive/2026/06/m-workspace-port-env-standardization/01_compose_env_defaults/complete.log`
- `agent-task/archive/2026/06/m-workspace-port-env-standardization/02+01_runtime_config_docs/complete.log`
- `.gitignore`
- `.env.example`
- `docker-compose.yml`
- `apps/client/Dockerfile`
- `apps/client/pubspec.yaml`
- `apps/client/pubspec.lock`
- `../proto-socket/dart/pubspec.yaml`
- `../proto-socket/.gitignore`
- `Makefile`
- `scripts/e2e-smoke.sh`
- `scripts/e2e-openai-ollama.sh`
- `scripts/e2e-control-plane-edge-wire.sh`
- `scripts/dev/web.sh`
- `agent-test/local/rules.md`
- `agent-test/local/testing-smoke.md`
- `agent-test/local/client-smoke.md`
- `agent-test/local/control-plane-smoke.md`
- `agent-test/local/edge-smoke.md`
- `agent-test/local/node-smoke.md`
- `agent-test/local/platform-common-smoke.md`

### 실패 원인

- `apps/client/Dockerfile`은 `proto-socket/dart/pubspec.lock`을 필수 `COPY`하지만 sibling `../proto-socket` repo는 `dart/pubspec.lock`을 `.gitignore`로 무시하고 추적하지 않는다. clean remote checkout에서는 web image build가 재현 가능하게 실패할 수 있다.
- 첫 리뷰 루프의 compose render는 표준 slot을 확인했지만, full compose service health와 `/healthz`/`/readyz`는 통과하지 못했다.
- 원격 `19080`/`13000` port 점유는 사용자 소유 Docker 상태일 수 있으므로, repo-owned Dockerfile issue를 해소한 뒤에도 표준 slot이 막히면 정확한 port owner evidence로 사용자 리뷰 stop을 남겨야 한다.

### 범위 결정 근거

- 이 follow-up은 `apps/client/Dockerfile`과 active review evidence만 수정한다. `.env.example`, `docker-compose.yml`, runtime defaults, docs/test-rule defaults는 predecessor PASS 범위이므로 새 결함이 확인되지 않으면 수정하지 않는다.
- sibling `../proto-socket` repo를 수정하거나 ignored lock file을 추적 대상으로 바꾸는 선택은 이 repo의 기본 수정 범위를 벗어난다. 기본 해법은 IOP Dockerfile이 ignored sibling lock에 의존하지 않게 하는 것이다.
- 원격 runner의 기존 Docker project/container를 임의로 중지하지 않는다. 현재 smoke project가 만든 container만 정리한다.

### 빌드 등급

- `cloud-G07`: remote Docker compose, Flutter web image build, standard host slot service health evidence가 중심이며, 포트 충돌과 외부 runner 상태 판정이 필요하다.

## 구현 체크리스트

- [ ] `apps/client/Dockerfile`이 ignored sibling `proto-socket/dart/pubspec.lock`에 의존하지 않도록 수정한다.
- [ ] Dockerfile 변경 후 client/package quick regression과 web build 검증을 실행하고 실제 stdout/stderr를 기록한다.
- [ ] 원격 runner/code-server의 isolated worktree에 local patch를 동기화한 뒤 표준 host slot compose render/build/service health를 재검증한다.
- [ ] 표준 `13000`/`19080` slot이 repo-owned fix 후에도 사용자 소유 Docker 상태로 막히면 직접 중지하지 말고 `사용자 리뷰 요청`에 port owner evidence와 재개 조건을 기록한다.
- [ ] 검증 결과에 host/token/credential 원문이 남지 않도록 출력 sanitization 여부를 확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_TEST-1] Remove ignored sibling lock dependency from client Docker image build

문제: `apps/client/Dockerfile`의 early cache layer가 ignored sibling `proto-socket/dart/pubspec.lock`을 필수로 요구해 clean remote Docker build가 실패한다.

해결 방법:

- `apps/client/Dockerfile`에서 `proto-socket/dart/pubspec.lock` 필수 `COPY`를 제거한다.
- `proto-socket/dart/pubspec.yaml`과 `apps/client/pubspec.yaml`/`apps/client/pubspec.lock` cache layer는 유지하되, ignored sibling lock file이 없어도 `flutter pub get`과 `flutter build web`이 진행되도록 한다.
- final `COPY proto-socket/dart/ ...`는 Flutter build 전에 유지해 path dependency source가 이미지에 들어가게 한다.

수정 파일 및 체크리스트:

- [ ] `apps/client/Dockerfile`: ignored sibling lock 필수 COPY 제거
- [ ] `CODE_REVIEW-cloud-G07.md`: 변경 근거와 검증 출력 기록

테스트 작성: Dockerfile build 계약 보정이므로 새 테스트 파일은 작성하지 않는다. 대신 clean remote compose/web image build evidence를 수집한다.

중간 검증:

```bash
rg -n --sort path 'proto-socket/dart/pubspec\.lock' apps/client/Dockerfile
cd apps/client && flutter test
make client-build-web
```

기대 결과: 첫 명령은 출력 없음(exit=1)으로 ignored sibling lock 의존이 제거되었음을 보인다. Flutter test와 web build는 통과한다.

### [REVIEW_TEST-2] Recollect standard-slot compose health evidence after the repo-owned fix

문제: `port-default-rollout` completion은 `.env.example` 표준 host slot의 compose service health evidence가 필요하지만 첫 루프는 web image build와 `19080` collision으로 막혔다.

해결 방법:

- 원격 runner/code-server에서 local patch와 같은 commit/patch 상태의 isolated worktree를 준비한다.
- `IOP_EDGE_NODE_TOKEN`은 원격 shell 또는 명령 prefix로 sanitized smoke token을 주입하고, 출력에는 원문을 남기지 않는다.
- smoke project name을 분리해 compose render, build, service health, Control Plane health/readiness를 확인한다.
- `19080`/`13000`이 여전히 기존 user-owned Docker process에 점유되어 있으면, 중지하지 말고 `docker ps`/port owner evidence와 재개 조건을 active review stub의 `사용자 리뷰 요청`에 기록한다.

수정 파일 및 체크리스트:

- [ ] source 수정 없음
- [ ] `CODE_REVIEW-cloud-G07.md`: remote path/sync 상태, compose output, cleanup output 기록

테스트 작성: 검증-only 항목이므로 새 테스트 파일은 작성하지 않는다.

중간 검증:

```bash
docker ps --format '{{.Names}} {{.Ports}}' | grep -E '13000|19080' || true
IOP_EDGE_NODE_TOKEN=<sanitized-smoke-token> docker compose --project-name iop-g07-port-smoke --env-file .env.example config >/tmp/iop-compose-config.out
grep -E '18000|13000|15400|16300|19080|19081|19090' /tmp/iop-compose-config.out
IOP_EDGE_NODE_TOKEN=<sanitized-smoke-token> docker compose --project-name iop-g07-port-smoke --env-file .env.example up --build -d postgres redis control-plane edge web
curl -fsS http://127.0.0.1:18000/healthz
curl -fsS http://127.0.0.1:18000/readyz
docker compose --project-name iop-g07-port-smoke ps
docker compose --project-name iop-g07-port-smoke --env-file .env.example down
```

기대 결과: compose render, full build/start, Control Plane health/readiness, cleanup이 통과한다. 실패 시 web build regression, port collision, token missing, Docker unavailable을 분리해 기록한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/client/Dockerfile` | REVIEW_TEST-1 |
| `CODE_REVIEW-cloud-G07.md` | REVIEW_TEST-1, REVIEW_TEST-2 |

## 최종 검증

```bash
rg -n --sort path 'proto-socket/dart/pubspec\.lock' apps/client/Dockerfile
go test ./...
cd apps/client && flutter test
make client-build-web
docker ps --format '{{.Names}} {{.Ports}}' | grep -E '13000|19080' || true
IOP_EDGE_NODE_TOKEN=<sanitized-smoke-token> docker compose --project-name iop-g07-port-smoke --env-file .env.example config >/tmp/iop-compose-config.out
grep -E '18000|13000|15400|16300|19080|19081|19090' /tmp/iop-compose-config.out
IOP_EDGE_NODE_TOKEN=<sanitized-smoke-token> docker compose --project-name iop-g07-port-smoke --env-file .env.example up --build -d postgres redis control-plane edge web
curl -fsS http://127.0.0.1:18000/healthz
curl -fsS http://127.0.0.1:18000/readyz
docker compose --project-name iop-g07-port-smoke ps
docker compose --project-name iop-g07-port-smoke --env-file .env.example down
make test-control-plane-edge-wire
./scripts/e2e-smoke.sh
./scripts/e2e-openai-ollama.sh
```

기대 결과: ignored sibling lock 의존 제거, Go/client/web build 회귀, standard-slot compose health, wire/e2e smoke가 모두 통과한다. `rg -n ... pubspec.lock` 명령은 출력 없음(exit=1)이 기대 결과이므로 실패로 보지 말고 그대로 기록한다. 원격 사용자 소유 port collision이 남으면 `사용자 리뷰 요청`으로 멈춘다.

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