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

# Code Review Reference - REVIEW_TEST

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves.
> Do not ask the user directly, present choices in chat, or call `request_user_input` during implementation; record the needed decision in `사용자 리뷰 요청` and stop for code-review.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-06-07
task=m-workspace-port-env-standardization/03+01,02_remote_smoke_evidence, plan=1, tag=REVIEW_TEST

## 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

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G07.md` -> `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` -> `plan_cloud_G07_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-workspace-port-env-standardization/03+01,02_remote_smoke_evidence/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다.
4. PASS이고 task group이 `m-workspace-port-env-standardization`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_TEST-1] Remove ignored sibling lock dependency from client Docker image build | [x] |
| [REVIEW_TEST-2] Recollect standard-slot compose health evidence after the repo-owned fix | [ ] blocked |

## 구현 체크리스트

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

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] PASS이면 active task 디렉터리 `agent-task/m-workspace-port-env-standardization/03+01,02_remote_smoke_evidence/`를 `agent-task/archive/YYYY/MM/m-workspace-port-env-standardization/03+01,02_remote_smoke_evidence/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-workspace-port-env-standardization`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-workspace-port-env-standardization/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [x] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [x] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

## 계획 대비 변경 사항

- `apps/client/Dockerfile`에서 `proto-socket/dart/pubspec.lock`뿐 아니라 clean Docker context에 없는 sibling package lock 의존성도 제거했다. `iop_console/pubspec.lock`은 local generated file로 존재할 수 있지만 git 추적 대상이 아니어서 Dockerfile 필수 `COPY` 대상으로 둘 수 없다.
- `flutter pub get` 단계가 app의 path dependencies를 해석할 수 있도록 `proto-socket`, `agent-shell`, `nexo_messaging`, `iop_console` manifest를 먼저 복사하고, `flutter build web` 전에 해당 source를 복사하도록 Dockerfile을 보강했다. 첫 remote build rerun에서 `iop_console` path dependency 누락이 추가로 드러나 repo-owned build contract 범위로 처리했다.
- 원격 runner의 `nexo` sibling은 외부 symlink였고 Docker build context가 context 밖 symlink target을 따라가지 않아, 검증용 isolated context에는 symlink target의 실제 디렉터리 복사본을 넣었다. 이는 검증 환경 준비이며 tracked source 변경은 아니다.
- `docker compose build web`을 추가로 실행해 repo-owned Dockerfile build contract가 해소됐는지 `up` 전에 분리 확인했다. 이후 계획의 standard-slot `docker compose up --build ...` 명령을 실행했다.
- repo-owned Dockerfile issue 해소 후에도 원격 standard host slot `19080`과 `13000`은 사용자 소유 `code-server` container가 점유 중이었다. 해당 container를 중지하지 않고 blocker evidence와 재개 조건을 남겼다.

## 주요 설계 결정

- Source 수정은 `apps/client/Dockerfile`에 한정했다.
- sibling package lock file은 추적 여부가 repo마다 다를 수 있으므로 Docker cache layer는 manifest-only로 구성하고, app-level `apps/client/pubspec.lock`을 유지했다.
- Docker build stage의 path dependency resolution은 `/workspace` 아래 실제 경로 구조가 app `pubspec.yaml`의 relative path와 맞도록 구성했다.
- 원격 standard-slot collision은 사용자 소유 Docker 상태로 판단해 stop/restart하지 않았다.
- 출력에는 token 원문을 기록하지 않고 `<sanitized-smoke-token>`으로 표기했다.

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._

- 상태: 요청
- 사유 유형: 사용자 소유 외부 환경 전제
- 결정 필요: 원격 runner에서 standard host slot `19080`과 `13000`을 점유 중인 `code-server` container를 중지/재배치하거나, 이 validation split에서 temporary override port로 compose health를 인정할지 code-review/user 판단이 필요하다.
- 차단 근거:
  - repo-owned Dockerfile issue 해소 후 `docker compose --project-name iop-g07-port-smoke --env-file .env.example up --build -d postgres redis control-plane edge web`는 `control-plane`, `edge`, `web` image build까지 통과했으나 Control Plane start 단계에서 `Bind for 0.0.0.0:19080 failed: port is already allocated`로 실패했다.
  - 사전 port owner 확인에서 `code-server` container가 `0.0.0.0:19080->19080/tcp`와 `0.0.0.0:13000->3000/tcp`를 점유 중이었다.
  - 실패 후 `curl -fsS http://127.0.0.1:18000/healthz`와 `/readyz`는 Control Plane이 뜨지 못해 exit 7로 실패했다.
- 실행한 검증/명령:
  - `rg -n --sort path 'proto-socket/dart/pubspec\.lock' apps/client/Dockerfile`: PASS, no matches, exit=1
  - `go test ./...`: PASS
  - `cd apps/client && flutter test`: PASS, 27 tests
  - `make client-build-web`: PASS, `✓ Built build/web`
  - remote isolated `docker compose build web`: PASS, `Service web Built`
  - remote standard-slot compose config: PASS, standard slots present
  - remote standard-slot `docker compose up --build -d postgres redis control-plane edge web`: BLOCKED by user-owned `19080` collision after all relevant images built
  - `make test-control-plane-edge-wire`: PASS
  - `./scripts/e2e-smoke.sh`: PASS
  - `./scripts/e2e-openai-ollama.sh`: PASS
- 자동 후속 불가 이유: port owner is an existing user-owned `code-server` container, and the plan explicitly forbids stopping/restarting user-owned remote Docker state.
- 재개 조건: `19080` and `13000` are freed/reassigned on the remote runner, or code-review/user approves temporary override ports for compose health evidence.

## 리뷰어를 위한 체크포인트

- `apps/client/Dockerfile`이 ignored sibling `proto-socket/dart/pubspec.lock`에 더 이상 의존하지 않는지 확인한다.
- client/web build 검증이 Dockerfile 변경과 같은 patch 상태에서 수행되었는지 확인한다.
- remote compose evidence가 `.env.example` 표준 host slot `18000`, `13000`, `15400`, `16300`, `19080`, `19081`, `19090`을 기준으로 수집되었는지 확인한다.
- remote port collision이 남으면 repo-owned fix 후에도 사용자 소유 blocker인지, 아니면 follow-up agent가 정리 가능한 smoke project 잔여물인지 구분한다.
- token/host/credential 원문이 검증 결과에 남지 않았는지 확인한다.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.

### REVIEW_TEST-1 중간 검증

```bash
$ rg -n --sort path 'proto-socket/dart/pubspec\.lock' apps/client/Dockerfile
exit=1

$ cd apps/client && flutter test
00:03 +27: All tests passed!

$ make client-build-web
cd apps/client && flutter build web \
	--dart-define=IOP_CONTROL_PLANE_HTTP_URL=http://localhost:18000 \
	--dart-define=IOP_CONTROL_PLANE_WIRE_URL=ws://localhost:19080/client
Compiling lib/main.dart for the Web...                             33.4s
✓ Built build/web
```

### REVIEW_TEST-2 중간 검증

```bash
$ docker ps --format '{{.Names}} {{.Ports}}' | grep -E '13000|19080' || true
code-server 0.0.0.0:9080->9080/tcp, 0.0.0.0:18080->18080/tcp, 8443/tcp, 0.0.0.0:19080->19080/tcp, 0.0.0.0:13000->3000/tcp, ... 0.0.0.0:13099->3099/tcp, 0.0.0.0:18081->8080/tcp, 0.0.0.0:19090->9090/tcp, 0.0.0.0:19092->9092/tcp, 0.0.0.0:19190->9190/tcp

$ 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
(remote isolated worktree with Dockerfile patch) exit=0

$ grep -E '18000|13000|15400|16300|19080|19081|19090' /tmp/iop-compose-config.out
        - http://127.0.0.1:18000/healthz
        target: 18000
        published: "18000"
        target: 19080
        published: "19080"
        target: 19081
        published: "19081"
      IOP_CONTROL_PLANE_EDGE_WIRE_ADDR: control-plane:19081
        published: "19090"
        published: "15400"
        published: "16300"
        IOP_CONTROL_PLANE_HTTP_URL: http://localhost:18000
        IOP_CONTROL_PLANE_WIRE_URL: ws://localhost:19080/client
        published: "13000"

$ IOP_EDGE_NODE_TOKEN=<sanitized-smoke-token> docker compose --project-name iop-g07-port-smoke --env-file .env.example build web
Service web  Building
...
#16 [web builder  8/14] RUN flutter pub get
#16 DONE 2.3s
...
#22 30.48 Compiling lib/main.dart for the Web...                             26.7s
#22 30.49 ✓ Built build/web
...
Service web  Built

$ 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
Service control-plane  Built
Service edge  Built
Service web  Built
Container iop-g07-port-smoke-postgres-1  Healthy
Container iop-g07-port-smoke-redis-1  Healthy
Container iop-g07-port-smoke-control-plane-1  Starting
Error response from daemon: driver failed programming external connectivity on endpoint iop-g07-port-smoke-control-plane-1 (...): Bind for 0.0.0.0:19080 failed: port is already allocated

$ curl -fsS http://127.0.0.1:18000/healthz
curl: (7) Failed to connect to 127.0.0.1 port 18000 after 0 ms: Couldn't connect to server

$ curl -fsS http://127.0.0.1:18000/readyz
curl: (7) Failed to connect to 127.0.0.1 port 18000 after 0 ms: Couldn't connect to server

$ docker compose --project-name iop-g07-port-smoke ps
NAME                            IMAGE                COMMAND                  SERVICE    CREATED          STATUS                    PORTS
iop-g07-port-smoke-postgres-1   postgres:17-alpine   "docker-entrypoint.s..." postgres   11 seconds ago   Up 10 seconds (healthy)   127.0.0.1:15400->5432/tcp
iop-g07-port-smoke-redis-1      redis:7-alpine       "docker-entrypoint.s..." redis      11 seconds ago   Up 10 seconds (healthy)   127.0.0.1:16300->6379/tcp

$ docker compose --project-name iop-g07-port-smoke --env-file .env.example down
Container iop-g07-port-smoke-web-1  Removed
Container iop-g07-port-smoke-edge-1  Removed
Container iop-g07-port-smoke-control-plane-1  Removed
Container iop-g07-port-smoke-postgres-1  Removed
Container iop-g07-port-smoke-redis-1  Removed
Network iop-g07-port-smoke_default  Removed
up_rc=1 health_rc=7 ready_rc=7
```

### 최종 검증

```bash
$ rg -n --sort path 'proto-socket/dart/pubspec\.lock' apps/client/Dockerfile
exit=1

$ go test ./...
ok  	iop/apps/control-plane/cmd/control-plane	(cached)
ok  	iop/apps/control-plane/internal/wire	(cached)
ok  	iop/apps/edge/...	(cached)
ok  	iop/apps/node/...	(cached)
ok  	iop/packages/go/...	(cached)
?   	iop/proto/gen/iop	[no test files]

$ cd apps/client && flutter test
00:03 +27: All tests passed!

$ make client-build-web
Compiling lib/main.dart for the Web...                             33.4s
✓ Built build/web

$ docker ps --format '{{.Names}} {{.Ports}}' | grep -E '13000|19080' || true
code-server 0.0.0.0:9080->9080/tcp, 0.0.0.0:18080->18080/tcp, 8443/tcp, 0.0.0.0:19080->19080/tcp, 0.0.0.0:13000->3000/tcp, ... 0.0.0.0:13099->3099/tcp

$ 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
PASS; config rendered in remote isolated worktree with sanitized token.

$ grep -E '18000|13000|15400|16300|19080|19081|19090' /tmp/iop-compose-config.out
PASS; output includes published slots `18000`, `13000`, `15400`, `16300`, `19080`, `19081`, `19090`.

$ 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
BLOCKED; images built, Postgres/Redis healthy, Control Plane failed to publish `0.0.0.0:19080` because the port is already allocated by `code-server`.

$ curl -fsS http://127.0.0.1:18000/healthz
BLOCKED; Control Plane did not start. curl exit=7.

$ curl -fsS http://127.0.0.1:18000/readyz
BLOCKED; Control Plane did not start. curl exit=7.

$ docker compose --project-name iop-g07-port-smoke ps
postgres and redis were healthy before cleanup; control-plane failed before health endpoints were available.

$ docker compose --project-name iop-g07-port-smoke --env-file .env.example down
PASS cleanup; `iop-g07-port-smoke` containers/network removed.

$ make test-control-plane-edge-wire
[cp-edge-wire] CP: hello accepted
[cp-edge-wire] Edge: connected to control plane
[cp-edge-wire] CP: edge disconnected
[cp-edge-wire] Control Plane-Edge wire smoke PASSED.

$ ./scripts/e2e-smoke.sh
[e2e] Auxiliary smoke test PASSED.
[e2e] Completion still requires scripts/dev/edge.sh + scripts/dev/node.sh user-flow verification.

$ ./scripts/e2e-openai-ollama.sh
[openai-ollama] OpenAI-compatible Ollama serving test PASSED.
```

Overall final verification status: BLOCKED on user-owned remote standard-slot collision. Repo-owned Dockerfile build blockers found in the first review loop and follow-up rerun are fixed; `docker compose build web` now passes in an isolated clean context with the local patch.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

Sections and their ownership:

| 섹션 | 소유자 | 설명 |
|------|--------|------|
| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하거나 실행하지 않음 |
| Roadmap Targets | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음; PASS 시 code-review가 `complete.log`의 `Roadmap Completion`으로 복사 |
| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` -> `[x]` 체크만 구현 에이전트가 수행 |
| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` -> `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 |
| 코드리뷰 전용 체크리스트 | Review agent only | Implementing agent must not modify or check this section |
| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 |
| 사용자 리뷰 요청 | 구현 에이전트가 채움 | 진행에 사용자 입력이 필요하지 않으면 `상태: 없음` 유지 |
| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 |
| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움; 명령 변경은 `계획 대비 변경 사항`에 기록 |
| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 |

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - correctness: Pass
  - completeness: Fail
  - test coverage: Fail
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Pass
  - verification trust: Pass
- 발견된 문제:
  - Required: `agent-task/m-workspace-port-env-standardization/03+01,02_remote_smoke_evidence/CODE_REVIEW-cloud-G07.md:46`의 standard-slot compose health 검증이 아직 완료되지 않았다. Dockerfile build blocker는 해소됐고 `docker compose build web`까지 통과했지만, 원격 runner의 사용자 소유 `code-server` container가 `19080`과 `13000`을 점유해 `docker compose --project-name iop-g07-port-smoke --env-file .env.example up --build -d postgres redis control-plane edge web`가 `Bind for 0.0.0.0:19080 failed: port is already allocated`로 막혔다(`agent-task/m-workspace-port-env-standardization/03+01,02_remote_smoke_evidence/CODE_REVIEW-cloud-G07.md:248`). 이 task는 `port-default-rollout`의 completion evidence split이므로, 사용자가 `code-server`의 standard slot 점유를 해제/재배치하거나 temporary override port를 completion evidence로 인정해야 재개할 수 있다.
- 다음 단계: USER_REVIEW stop state를 작성한다.
