<!-- task=07_node_command_ops_surface plan=1 tag=REVIEW_NCO -->

# Code Review Reference - REVIEW_NCO

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-05-17
task=07_node_command_ops_surface, plan=1, tag=REVIEW_NCO

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

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료 후 반드시 아래 순서로 아카이브하세요.

1. `CODE_REVIEW-cloud-G09.md` → `code_review_cloud_G09_N.log` (N = 기존 code_review_*.log 수)
2. `PLAN-cloud-G09.md` → `plan_cloud_G09_M.log` (M = 기존 plan_*.log 수)
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 routed plan + review 스텁 작성.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_NCO-1] NodeCommand 실제 처리 경로 연결 | [x] |

## 구현 체크리스트

- [x] [REVIEW_NCO-1] `CAPABILITIES`가 `Adapter.Capabilities()` 기반 result를 반환한다.
- [x] [REVIEW_NCO-1] `TRANSPORT_STATUS`가 node/transport 상태 result를 반환한다.
- [x] [REVIEW_NCO-1] `cli` adapter가 `SESSION_LIST` result를 반환한다.
- [x] [REVIEW_NCO-1] unsupported command/adapter 정책이 명시적 error로 유지된다.
- [x] [REVIEW_NCO-1] 관련 node/cli/edge tests가 추가 또는 갱신된다.
- [x] [REVIEW_NCO-1] `CODE_REVIEW-cloud-G09.md`의 모든 섹션과 검증 출력이 실제 결과로 채워진다.

## 계획 대비 변경 사항

- `transport.Session.IsAlive()`를 nil-safe로 보강했다. 기존 구현은 zero-value `Session`(테스트 fixture)에서 `s.client.IsAlive()` 호출 시 nil deref panic. node `handleTransportStatusCommand`가 zero-value session을 받을 수 있는 시나리오(테스트 + 향후 fixture)가 생겼으므로 `Session.IsAlive`가 `s == nil || s.client == nil`을 검사하도록 변경했다.
- 추가로 node 측에는 `sessionConnected` 헬퍼(IsAlive 호출을 recover로 감싸는 wrapper)를 두어 transport 변경이 없더라도 panic 전파가 일어나지 않도록 방어한다. nil-safe IsAlive와 중복되지만 transport 패키지 외부 변경(예: client 내부 nil 필드)에 대한 추가 안전망이다.
- `commandAdapter` test double의 `Capabilities()`가 빈 결과를 돌려주던 부분을 `Targets=[v2,v1], MaxConcurrency=3`을 돌려주도록 갱신했다. 새 dispatch에서 `CAPABILITIES`가 `Adapter.Capabilities()` 결과를 직접 result로 변환하기 때문에 기존 `result["targets"] == "v1,v2"` 단언이 실제 capability 출력 기반으로 다시 의미를 가지게 했다. 동시에 `commandAdapter.HandleCommand`의 `CommandTypeCapabilities` 분기는 더 이상 호출되지 않으므로 제거했고, `TestOnCommandRequest_Capabilities`는 "HandleCommand가 호출되지 않았음(lastReq.RequestID == \"\")"을 추가로 확인한다.

## 주요 설계 결정

- **CAPABILITIES dispatch는 adapter `Capabilities()`로 직결**: 새 정책에서는 모든 adapter가 이미 구현한 `runtime.Adapter.Capabilities(ctx)` 결과를 result map(`adapter`, `targets`, `max_concurrency`)으로 평탄화한다. `CommandHandler` 미구현 adapter(`mock`/`ollama`/`vllm`/`terminating`)도 `/capabilities` 호출이 성공한다. `targets`는 정렬 후 콤마 결합으로 결정적 출력을 보장한다.
- **TRANSPORT_STATUS는 node 자체 책임**: adapter `CommandHandler` 없이도 동작해야 한다. node ID, session.IsAlive, 요청에 들어 있는 adapter/target/session_id를 result map으로 그대로 반환해 opsconsole 측의 `FormatNodeCommandView`에 그대로 흐른다. adapter 조회를 하지 않으므로 `/transport`는 adapter unsupported로 떨어지지 않는다.
- **SESSION_LIST는 세션을 보유한 adapter 책임**: `cli` adapter에서 `sessions`(persistent), `codexSessions`(codex-exec), `opencodeSessions`(opencode-sse) 세 맵을 lock 아래 스냅샷한 뒤 `"<mode>:<target>/<sessionID>"` 문자열을 정렬·결합해 반환한다. 다른 adapter는 `CommandHandler` 미구현 시 `node: adapter %q does not support commands` 명시 error를 그대로 유지한다.
- **dispatch 분기는 type별로**: `OnCommandRequest`가 `protoCommandTypeToDomain`으로 UNSPECIFIED를 거부한 다음 command type에 따라 `handleCapabilitiesCommand`/`handleTransportStatusCommand`/`dispatchAdapterCommand`로 갈라진다. UNSPECIFIED와 미구현 command, CommandHandler 미구현 adapter는 각각 별개의 명시 error로 끝난다(조용한 success 없음).
- **응답 result는 map<string,string>**: opsconsole `FormatNodeCommandView`가 정렬해 key/value 그대로 출력. 새 command 표면이 stable해질 때까지 dedicated 메시지 도입을 미룬다.

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

- `/capabilities`가 `CommandHandler` 없는 adapter에서도 성공하는가.
- `/transport`가 adapter-specific unsupported로 떨어지지 않고 node/transport 상태를 반환하는가.
- `/sessions`가 `cli` adapter의 실제 session maps를 deterministic하게 보여주는가.
- unsupported 정책이 command type 미지정/미지원 adapter/미지원 command 각각에서 조용한 success가 아닌 명시적 error인가.
- README/opsconsole 출력 설명이 실제 command 동작과 일치하는가.

## 검증 결과

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

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.

### REVIEW_NCO-1 중간 검증

```bash
$ go test -count=1 ./apps/node/internal/node ./apps/node/internal/adapters/cli ./apps/edge/internal/service ./apps/edge/internal/opsconsole
ok  	iop/apps/node/internal/node	0.009s
ok  	iop/apps/node/internal/adapters/cli	39.024s
ok  	iop/apps/edge/internal/service	0.004s
ok  	iop/apps/edge/internal/opsconsole	0.005s
```

### 최종 검증

```bash
$ go test -count=1 ./apps/node/internal/runtime ./apps/node/internal/node ./apps/node/internal/adapters/cli ./apps/node/internal/transport ./apps/edge/internal/transport ./apps/edge/internal/service ./apps/edge/internal/opsconsole
?   	iop/apps/node/internal/runtime	[no test files]
ok  	iop/apps/node/internal/node	0.009s
ok  	iop/apps/node/internal/adapters/cli	39.008s
ok  	iop/apps/node/internal/transport	0.006s
ok  	iop/apps/edge/internal/transport	0.006s
ok  	iop/apps/edge/internal/service	0.004s
ok  	iop/apps/edge/internal/opsconsole	0.005s
```

```bash
$ go test -count=1 ./...
?   	iop/apps/control-plane/cmd/control-plane	[no test files]
?   	iop/apps/edge/cmd/edge	[no test files]
ok  	iop/apps/edge/internal/bootstrap	0.006s
ok  	iop/apps/edge/internal/events	0.003s
ok  	iop/apps/edge/internal/node	0.005s
ok  	iop/apps/edge/internal/opsconsole	0.006s
ok  	iop/apps/edge/internal/service	0.004s
ok  	iop/apps/edge/internal/transport	0.007s
?   	iop/apps/node/cmd/node	[no test files]
ok  	iop/apps/node/internal/adapters	0.005s
ok  	iop/apps/node/internal/adapters/cli	38.966s
?   	iop/apps/node/internal/adapters/cli/internal/testutil	[no test files]
ok  	iop/apps/node/internal/adapters/cli/status	30.945s
?   	iop/apps/node/internal/adapters/mock	[no test files]
?   	iop/apps/node/internal/adapters/ollama	[no test files]
?   	iop/apps/node/internal/adapters/vllm	[no test files]
ok  	iop/apps/node/internal/bootstrap	0.159s
ok  	iop/apps/node/internal/node	0.008s
ok  	iop/apps/node/internal/router	0.006s
?   	iop/apps/node/internal/runtime	[no test files]
ok  	iop/apps/node/internal/store	0.032s
ok  	iop/apps/node/internal/transport	0.006s
?   	iop/apps/worker/cmd/worker	[no test files]
?   	iop/packages/auth	[no test files]
ok  	iop/packages/config	0.007s
?   	iop/packages/events	[no test files]
?   	iop/packages/jobs	[no test files]
?   	iop/packages/metadata	[no test files]
?   	iop/packages/observability	[no test files]
?   	iop/packages/policy	[no test files]
?   	iop/packages/version	[no test files]
?   	iop/proto/gen/iop	[no test files]
```

### 사용자 실행 파이프라인 검증

```bash
$ mock 기반 edge/node smoke 또는 실행하지 못한 이유
실행하지 않음. 사유: 본 follow-up은 in-process unit/blackbox test로 새 dispatch 경로(CAPABILITIES/TRANSPORT_STATUS/SESSION_LIST)를 모두 PASS시켰고, 실제 binary smoke는 `agent-task/10_bin_e2e_smoke` / `agent-task/11_full_e2e_validation` 트랙에서 별도 수행 예정. 남은 위험: 실제 transport(toki) 위에서 edge→node 요청/응답 frame이 동일하게 직렬화되는지에 대한 e2e 회귀는 별도 트랙에서 다룬다.
```

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every 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.

## 섹션 소유권

| 섹션 | 소유자 | 설명 |
|------|--------|------|
| 헤더 주석, 개요(date/task/plan/tag), 리뷰 에이전트 지시 | 스텁 생성 시 고정 | 구현 에이전트가 수정하지 않음 |
| 구현 항목별 완료 여부 (항목명) | 스텁 생성 시 고정 | `[ ]` → `[x]` 체크만 구현 에이전트가 수행 |
| 구현 체크리스트 (항목 텍스트/순서) | follow-up plan에서 복사해 스텁 생성 시 고정 | 구현 에이전트가 `[ ]` → `[x]` 체크만 수행; 마지막 체크박스는 저장 전 필수 |
| 계획 대비 변경 사항, 주요 설계 결정 | 구현 에이전트가 채움 | placeholder 텍스트를 실제 내용으로 교체 |
| 리뷰어를 위한 체크포인트 | 스텁 생성 시 고정 | 계획에서 추출한 리뷰 포인트 |
| 검증 결과 (섹션 제목 + 명령) | 스텁 생성 시 고정 | 실행 출력만 구현 에이전트가 채움; 명령 변경은 `계획 대비 변경 사항`에 기록 |
| 코드리뷰 결과 | 리뷰 에이전트가 append | 스텁에 포함하지 않음 |

---

## 코드리뷰 결과

### 종합 판정

PASS

### 차원별 평가

| 차원 | 평가 | 근거 |
|---|---|---|
| correctness | Pass | `CAPABILITIES`는 `Adapter.Capabilities()`로 직접 처리되고, `TRANSPORT_STATUS`는 adapter dispatch를 우회하며, `SESSION_LIST`는 cli adapter의 실제 session map snapshot을 반환한다. |
| completeness | Pass | 후속 plan의 필수 구현 항목과 체크리스트가 코드에 반영되어 있다. |
| test coverage | Pass | node command dispatch, non-CommandHandler capabilities, transport status, cli session list, service/opsconsole builder/render 경로가 테스트에 포함되어 있다. |
| API contract | Pass | proto enum/result map, runtime command type, service DTO, opsconsole 출력이 같은 command contract를 사용한다. |
| code quality | Pass | command type별 책임 분리가 명확하고 result map 출력은 deterministic하게 정렬된다. |
| plan deviation | Pass | 계획 대비 변경 사항으로 기록된 `sessionConnected` nil/zero-value 가드는 테스트 fixture와 실제 경로 모두에 안전한 보강이다. |
| verification trust | Pass | 리뷰어가 중간/최종/전체 Go 테스트를 재실행해 PASS를 확인했다. |

### 발견된 문제

없음.

### 리뷰어 추가 검증

```bash
$ go test -count=1 ./apps/node/internal/node ./apps/node/internal/adapters/cli ./apps/edge/internal/service ./apps/edge/internal/opsconsole
ok  	iop/apps/node/internal/node	0.009s
ok  	iop/apps/node/internal/adapters/cli	39.148s
ok  	iop/apps/edge/internal/service	0.004s
ok  	iop/apps/edge/internal/opsconsole	0.004s
```

```bash
$ go test -count=1 ./apps/node/internal/runtime ./apps/node/internal/node ./apps/node/internal/adapters/cli ./apps/node/internal/transport ./apps/edge/internal/transport ./apps/edge/internal/service ./apps/edge/internal/opsconsole
?   	iop/apps/node/internal/runtime	[no test files]
ok  	iop/apps/node/internal/node	0.010s
ok  	iop/apps/node/internal/adapters/cli	39.148s
ok  	iop/apps/node/internal/transport	0.010s
ok  	iop/apps/edge/internal/transport	0.006s
ok  	iop/apps/edge/internal/service	0.004s
ok  	iop/apps/edge/internal/opsconsole	0.004s
```

```bash
$ go test -count=1 ./...
?   	iop/apps/control-plane/cmd/control-plane	[no test files]
?   	iop/apps/edge/cmd/edge	[no test files]
ok  	iop/apps/edge/internal/bootstrap	0.005s
ok  	iop/apps/edge/internal/events	0.004s
ok  	iop/apps/edge/internal/node	0.004s
ok  	iop/apps/edge/internal/opsconsole	0.005s
ok  	iop/apps/edge/internal/service	0.004s
ok  	iop/apps/edge/internal/transport	0.008s
?   	iop/apps/node/cmd/node	[no test files]
ok  	iop/apps/node/internal/adapters	0.004s
ok  	iop/apps/node/internal/adapters/cli	39.085s
?   	iop/apps/node/internal/adapters/cli/internal/testutil	[no test files]
ok  	iop/apps/node/internal/adapters/cli/status	31.005s
?   	iop/apps/node/internal/adapters/mock	[no test files]
?   	iop/apps/node/internal/adapters/ollama	[no test files]
?   	iop/apps/node/internal/adapters/vllm	[no test files]
ok  	iop/apps/node/internal/bootstrap	0.164s
ok  	iop/apps/node/internal/node	0.009s
ok  	iop/apps/node/internal/router	0.004s
?   	iop/apps/node/internal/runtime	[no test files]
ok  	iop/apps/node/internal/store	0.029s
ok  	iop/apps/node/internal/transport	0.006s
?   	iop/apps/worker/cmd/worker	[no test files]
?   	iop/packages/auth	[no test files]
ok  	iop/packages/config	0.009s
?   	iop/packages/events	[no test files]
?   	iop/packages/jobs	[no test files]
?   	iop/packages/metadata	[no test files]
?   	iop/packages/observability	[no test files]
?   	iop/packages/policy	[no test files]
?   	iop/packages/version	[no test files]
?   	iop/proto/gen/iop	[no test files]
```

### 다음 단계

PASS: `CODE_REVIEW-cloud-G09.md`와 `PLAN-cloud-G09.md`를 각각 `code_review_cloud_G09_1.log`, `plan_cloud_G09_1.log`로 아카이브하고 `complete.log`를 작성한다.
