<!-- task=07_node_command_ops_surface plan=0 tag=NCO -->

# Code Review Reference - NCO

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**

## 개요

date=2026-05-17
task=07_node_command_ops_surface, plan=0, tag=NCO

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

구현과 검증 출력을 대조하고 PASS 시 plan/review를 log로 아카이브하세요.

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|---|---|
| [NCO-1] NodeCommand schema 확장 | [x] |
| [NCO-2] Node runtime command mapping | [x] |
| [NCO-3] Edge service와 opsconsole command 추가 | [x] |

## 계획 대비 변경 사항

- 계획에서 "unspecified command explicit error" 테스트를 추가하라 했고, 기존 `TestOnCommandRequest_AdapterError`가 UNSPECIFIED 타입을 사용해 adapter까지 흘려보내고 있었다. 새 정책(UNSPECIFIED는 dispatch 단계에서 거부)과 충돌하므로 다음과 같이 정리했다.
  - 새 테스트 `TestOnCommandRequest_UnspecifiedRejected`: UNSPECIFIED가 adapter를 호출하지 않고 `unsupported command type` 에러를 돌려주는 것을 검증.
  - 기존 `TestOnCommandRequest_AdapterError`: 유효한 타입(`SESSION_LIST`)으로 adapter를 호출하되 adapter가 명시적으로 unsupported 에러를 반환하도록 시나리오를 변경.
  - 새 테스트 `TestOnCommandRequest_Capabilities`: CAPABILITIES 명령이 success 응답에 `result` 맵을 실어 돌려주는 것을 검증.
- 응답 페이로드 형태는 계획대로 "metadata map 우선" 노선을 채택하여 `NodeCommandResponse.result map<string,string>`을 추가했다. usage_status는 기존 `AgentUsageStatus` 페이로드를 유지한다.
- `BuildUsageStatusRequest`는 내부적으로 `buildNodeCommandRequest` 공유 빌더에 위임하도록 리팩토링했고 외부 시그니처는 그대로 유지했다. 새 타입은 `BuildNodeCommandRequest(cmdType, idPrefix, …)`로 노출한다.

## 주요 설계 결정

- **메타데이터 맵 vs 전용 메시지**: `result map<string,string>`을 우선 채택. 새 명령들은 아직 구체 필드가 안정화되지 않았고, opsconsole은 정렬된 키-값 출력으로 즉시 사용 가능하다. 향후 stable해진 시점에 dedicated message로 이전하기 쉽다.
- **Unspecified는 dispatch 단계에서 거부**: 기존 구현은 알 수 없는 proto 타입을 빈 `runtime.CommandType`으로 떨어뜨려 adapter에 위임했고, adapter가 무관심한 경우 조용한 success가 될 수 있었다. `protoCommandTypeToDomain`이 (type, ok)를 돌려주고 false면 `OnCommandRequest`가 즉시 `unsupported command type` 응답을 반환한다.
- **opsconsole 출력 안정성**: `FormatNodeCommandView`에서 result 맵 키를 정렬해 출력. 사람이 다시 읽기 쉽고 테스트도 결정적이다.
- **service layer**: 세 가지 새 명령에 대해 typed 메서드(`Capabilities`, `SessionList`, `TransportStatus`)와 공통 `sendNodeCommand` helper로 정리. 호출자(opsconsole)가 함수 포인터(`nodeCommandFunc`)로 분기해 중복 코드를 줄였다.
- **README**: 새 console 명령 3개와 unsupported / empty payload 출력 정책을 명시.

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

- 새 command가 조용한 success/no-op이 되지 않는가.
  - `TestOnCommandRequest_UnspecifiedRejected`가 명시적 error 보장.
  - service `sendNodeCommand`는 `resp.GetError() != ""`이면 즉시 error로 변환.
  - opsconsole `FormatNodeCommandView`는 빈 result에 대해 `no <label> payload returned`를 출력.
- Proto/generated code가 일치하는가.
  - `make proto` 재실행 가능 (protoc + protoc-gen-go 사용 환경 확인 완료). `proto/gen/iop/runtime.pb.go`는 `NodeCommandType` 5개 enum과 `Result map[string]string`을 포함.
- Edge/Node parser tests가 새 command를 커버하는가.
  - `TestNodeParserMap_NodeCommandRequest_NewTypes` — node 측이 CAPABILITIES/SESSION_LIST/TRANSPORT_STATUS request를 파싱.
  - `TestEdgeParserMap_NodeCommandResponse_NewTypes` — edge 측이 동일 3타입 response + result map을 파싱.

## 검증 결과

### make proto

```
$ make proto
protoc \
	--go_out=. \
	--go_opt=module=iop \
	--proto_path=. \
	proto/iop/runtime.proto \
	proto/iop/node.proto \
	proto/iop/control.proto \
	proto/iop/job.proto
```

### 대상 패키지 테스트

```
$ go test -count=1 ./apps/node/internal/runtime ./apps/node/internal/node ./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.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.006s
```

### 전체 테스트

```
$ go test -count=1 ./...
ok  	iop/apps/edge/internal/bootstrap	0.006s
ok  	iop/apps/edge/internal/events	0.006s
ok  	iop/apps/edge/internal/node	0.004s
ok  	iop/apps/edge/internal/opsconsole	0.005s
ok  	iop/apps/edge/internal/service	0.005s
ok  	iop/apps/edge/internal/transport	0.008s
ok  	iop/apps/node/internal/adapters	0.005s
ok  	iop/apps/node/internal/adapters/cli	87.540s
ok  	iop/apps/node/internal/adapters/cli/status	30.895s
ok  	iop/apps/node/internal/bootstrap	0.165s
ok  	iop/apps/node/internal/node	0.009s
ok  	iop/apps/node/internal/router	0.005s
ok  	iop/apps/node/internal/store	0.029s
ok  	iop/apps/node/internal/transport	0.006s
ok  	iop/packages/config	0.008s
```

전체 PASS. 새 명령에 대한 unit-level 커버리지는 추가되었지만, 실제 transport를 통한 end-to-end 흐름(edge→node→edge response)에 대한 검증은 본 task 범위 밖이라 별도 e2e smoke 작업에서 다룬다.

---

## 코드리뷰 결과

### 종합 판정

FAIL

### 차원별 평가

| 차원 | 평가 | 근거 |
|---|---|---|
| correctness | Fail | 새 opsconsole command가 실제 기본 adapter 경로에서 대부분 unsupported로 종료된다. |
| completeness | Fail | schema/service/console 표면은 추가됐지만 새 command의 실제 node/adapter 구현 연결이 빠져 있다. |
| test coverage | Warn | parser/service/node mock 테스트는 있으나 실제 `cli` adapter와 non-CommandHandler adapter 경로 회귀 테스트가 없다. |
| API contract | Fail | `apps/edge/README.md`가 `/capabilities`, `/sessions`, `/transport` 조회를 문서화하지만 실제 응답 계약과 맞지 않는다. |
| code quality | Pass | 확인 범위에서 formatting/debug print 문제는 발견하지 못했다. |
| plan deviation | Warn | 계획의 "capabilities/session/transport command" surface가 edge와 proto 중심으로만 완료되어 실제 adapter 지원 범위가 좁다. |
| verification trust | Pass | 리뷰어가 대상 패키지 테스트를 재실행해 PASS를 확인했다. |

### 발견된 문제

- Required — `apps/node/internal/node/node.go:207`, `apps/node/internal/adapters/cli/cli.go:187`: `OnCommandRequest`는 `CAPABILITIES`, `SESSION_LIST`, `TRANSPORT_STATUS`까지 모두 `runtime.CommandHandler`로만 위임한다. 하지만 실제 `cli` adapter는 `usage_status` 외 command를 `unsupported command`로 반환하고, `mock`/`ollama`/`vllm` adapter는 `CommandHandler`를 구현하지 않는다. 따라서 `apps/edge/internal/opsconsole/status.go:48`에서 추가한 `/capabilities`, `/sessions`, `/transport` 명령은 README에 문서화된 조회성 command surface와 달리 정상 사용 시 대부분 `node reported error`로 끝난다. `CAPABILITIES`는 기존 `Adapter.Capabilities()`를 node command 응답으로 직접 변환하고, `SESSION_LIST`/`TRANSPORT_STATUS`는 실제 node 또는 adapter 상태에 연결해 지원하거나 명시적 unsupported 정책과 테스트를 command별로 분리해야 한다.

### 리뷰어 추가 검증

```bash
$ go test -count=1 ./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
ok  	iop/apps/node/internal/node	0.009s
ok  	iop/apps/node/internal/adapters/cli	87.780s
ok  	iop/apps/node/internal/transport	0.006s
ok  	iop/apps/edge/internal/transport	0.007s
ok  	iop/apps/edge/internal/service	0.005s
ok  	iop/apps/edge/internal/opsconsole	0.004s
```

### 다음 단계

FAIL: 새 node command가 실제 node/adapter 경로에서 동작하도록 `PLAN-cloud-G09.md` / `CODE_REVIEW-cloud-G09.md` 후속 루프를 진행한다.
