<!-- task=edge_multi_point_routing plan=0 tag=API -->

# Code Review Reference - API

## 개요

date=2026-05-04
task=edge_multi_point_routing, plan=0, tag=API

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

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

1. `CODE_REVIEW.md` → `code_review_N.log` (N = 기존 code_review_*.log 수)
2. `PLAN.md` → `plan_M.log` (M = 기존 plan_*.log 수)
3. PASS인 경우 `complete.log` 작성 후 종료. WARN/FAIL인 경우 새 `PLAN.md` + `CODE_REVIEW.md` 스텁 작성.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [API-1] Registry를 임의 Pick에서 명시적 조회 API 중심으로 전환 | [x] |
| [API-2] Console 실행/세션 종료를 target node 명시 모델로 변경 | [x] |
| [API-3] RunEvent에 node 출처를 추가해 foreground/background 출력 모두 node-aware 하게 만들기 | [x] |

## 계획 대비 변경 사항

- `apps/node/internal/node/node.go`의 `sessionSink`를 테스트하기 위해 `protoSender` 인터페이스를 도입했습니다. 이를 통해 `toki` 의존성 없이 `iop.RunEvent` 필드(특히 `node_id`)가 올바르게 채워지는지 단위 테스트할 수 있게 되었습니다.

## 주요 설계 결정

- **Implicit Resolve Logic**: `Resolve("")` 호출 시 연결된 node가 정확히 1개인 경우에만 성공하도록 하여, 하위 호환성을 유지하면서도 멀티 노드 환경에서의 모호성을 원천 차단했습니다.
- **Unified Node Labeling**: `consoleEventRouter.nodeLabel` helper를 통해 `node_id`를 `alias`로 변환하는 로직을 일원화하고, registry에 없는 노드라도 `node_id`를 그대로 출력하여 추적성을 보장했습니다.
- **State Consolidation**: Console의 상태를 `consoleTarget` 구조체로 묶어 관리함으로써, `/node`, `/session`, `/background` 등 여러 명령이 상태를 안전하게 변경하고 전파할 수 있게 했습니다.

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

- `apps/edge/internal/node/registry.go: Resolve`가 빈 ref에서 단일 node만 자동 선택하고, multi-node에서는 반드시 명시적 선택 에러를 반환하는지 확인
- `apps/edge/cmd/edge/console.go` 내의 `sendConsoleRun`, `sendTerminateSession`, `sendConsoleStatus`가 모두 `resolveConsoleNode`를 거쳐 `Registry.Pick()` 직접 호출을 제거했는지 확인
- `RunEvent.node_id`가 `apps/node/internal/node/node.go`에서 실제로 채워지고, `apps/edge/cmd/edge/console_events.go`에서 `[node-{alias|id}-...]` 형식으로 출력되는지 확인

## 검증 결과

### API-1 중간 검증
```bash
$ go test ./apps/edge/internal/node/... ./apps/edge/internal/transport/...
ok      iop/apps/edge/internal/node     0.003s
ok      iop/apps/edge/internal/transport        0.006s
```

### API-2 중간 검증
```bash
$ go test ./apps/edge/cmd/edge/... ./apps/edge/internal/node/...
ok      iop/apps/edge/cmd/edge  0.004s
ok      iop/apps/edge/internal/node     (cached)
```

### API-3 중간 검증
```bash
$ 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 ./apps/node/internal/node/... ./apps/edge/cmd/edge/...
ok      iop/apps/node/internal/node     0.007s
ok      iop/apps/edge/cmd/edge  (cached)
```

### 최종 검증
```bash
$ make proto
(already up to date)
$ go test ./apps/edge/... ./apps/node/internal/node/...
ok      iop/apps/edge/cmd/edge  (cached)
ok      iop/apps/edge/internal/node     (cached)
ok      iop/apps/edge/internal/transport        (cached)
ok      iop/apps/node/internal/node     (cached)
$ go test ./...
?       iop/apps/control-plane/cmd/control-plane        [no test files]
ok      iop/apps/edge/cmd/edge  (cached)
?       iop/apps/edge/internal/bootstrap        [no test files]
ok      iop/apps/edge/internal/node     (cached)
ok      iop/apps/edge/internal/transport        (cached)
?       iop/apps/node/cmd/node  [no test files]
ok      iop/apps/node/internal/adapters (cached)
ok      iop/apps/node/internal/adapters/cli     (cached)
?       iop/apps/node/internal/adapters/cli/internal/testutil   [no test files]
ok      iop/apps/node/internal/adapters/cli/lifecycle   (cached)
ok      iop/apps/node/internal/adapters/cli/oneshot     (cached)
ok      iop/apps/node/internal/adapters/cli/persistent  (cached)
ok      iop/apps/node/internal/adapters/cli/status      (cached)
?       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        (cached)
ok      iop/apps/node/internal/node     (cached)
ok      iop/apps/node/internal/router   (cached)
?       iop/apps/node/internal/runtime  [no test files]
ok      iop/apps/node/internal/store    (cached)
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     (cached)
?       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]
```

## 코드리뷰 결과

- 종합 판정: FAIL

- 차원별 평가
  - correctness: Pass
  - completeness: Fail
  - test coverage: Fail
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Fail
  - verification trust: Pass

- 발견된 문제
  - Required — [apps/edge/cmd/edge/console.go](/config/workspace/go-iop/apps/edge/cmd/edge/console.go:126): `/terminate-session` 성공 메시지가 여전히 `terminated session <id>`만 출력해, 계획서의 "startup banner와 `/terminate-session` 결과도 `node=<alias>`를 포함" 요구를 충족하지 못합니다. 멀티 노드 환경에서 어떤 node의 세션을 종료했는지 즉시 확인할 수 없고, 이에 대한 회귀 테스트도 없습니다. 성공 경로에서 `resolveConsoleNode(...)`로 얻은 node label을 포함해 `terminated session <id> node=<alias|id>` 형태로 출력하고, console 테스트에 해당 문자열을 검증하는 케이스를 추가하세요.

- 다음 단계
  - FAIL: Required 이슈를 반영하는 새 `PLAN.md`와 `CODE_REVIEW.md` 스텁을 작성하고 리뷰 루프를 계속한다.
