<!-- task=06_run_event_node_label plan=0 tag=RNL -->

# Code Review Reference - RNL

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

## 개요

date=2026-05-17
task=06_run_event_node_label, plan=0, tag=RNL

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

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

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|---|---|
| [RNL-1] RunEvent alias/display field 추가 | [x] |
| [RNL-2] Edge fanout 전 alias enrich | [x] |

## 계획 대비 변경 사항

- 계획에서 언급된 `apps/edge/cmd/edge/console_events.go` / `console_test.go`는 이미 `apps/edge/internal/opsconsole/events.go` / `events_test.go`로 이전된 상태였다. 실제 수정은 opsconsole 경로에서만 이뤄졌다.
- Node sink 코드는 변경하지 않았다 (계획대로 alias는 Edge에서만 enrich).

## 주요 설계 결정

- `RunEvent.node_alias = 12` 필드를 신설했다. `node_id`(11)는 그대로 두어 호환을 유지한다.
- Edge `Server.enrichRunEvent`가 fanout 핸들러 호출 직전에 registry에서 alias를 찾아 빈 `NodeAlias`만 채운다. 이미 값이 있으면(예: 미래의 다른 enricher) 덮어쓰지 않는다.
- Registry 조회 실패 / 빈 nodeID는 무시한다 — renderer가 기존 fallback(`registry → node_id → "unknown"`) 으로 계속 동작한다.
- Renderer `NodeLabel`은 `event.NodeAlias`를 가장 먼저 본다. registry/node_id fallback 순서는 유지.

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

- Node가 alias를 알도록 결합하지 않았는가. → `apps/node/internal/node/node.go` 미변경, `RunEvent` 생성 시 `NodeAlias` 미설정.
- Generated proto가 `make proto` 결과인가. → `make proto` 한 번만 실행, diff는 `node_alias` 필드 추가에 한정.
- Renderer fallback이 유지되는가. → `TestEventRouterPrintsNodeScopedAsyncRun`(registry alias), `TestEventRouterFallsBackToNodeID`(node_id) 모두 통과.

## 검증 결과

```
$ command -v protoc
/config/.local/bin/protoc

$ command -v protoc-gen-go
/config/.local/bin/protoc-gen-go

$ 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/edge/internal/transport ./apps/node/internal/transport ./apps/node/internal/node ./apps/edge/internal/opsconsole ./apps/edge/cmd/edge
ok      iop/apps/edge/internal/transport        0.006s
ok      iop/apps/node/internal/transport        0.006s
ok      iop/apps/node/internal/node             0.008s
ok      iop/apps/edge/internal/opsconsole       0.004s
?       iop/apps/edge/cmd/edge  [no test files]

$ 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.007s
ok      iop/apps/edge/internal/events           0.005s
ok      iop/apps/edge/internal/node             0.007s
ok      iop/apps/edge/internal/opsconsole       0.009s
ok      iop/apps/edge/internal/service          0.007s
ok      iop/apps/edge/internal/transport        0.010s
?       iop/apps/node/cmd/node  [no test files]
ok      iop/apps/node/internal/adapters         0.007s
ok      iop/apps/node/internal/adapters/cli     87.685s
?       iop/apps/node/internal/adapters/cli/internal/testutil   [no test files]
ok      iop/apps/node/internal/adapters/cli/status      30.922s
?       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.161s
ok      iop/apps/node/internal/node             0.008s
ok      iop/apps/node/internal/router           0.004s
?       iop/apps/node/internal/runtime  [no test files]
ok      iop/apps/node/internal/store            0.027s
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]
```

## 코드리뷰 결과

### 종합 판정

PASS

### 차원별 평가

| 차원 | 평가 | 근거 |
|---|---|---|
| correctness | Pass | `Server.enrichRunEvent`가 handler fanout 전에 빈 `NodeAlias`만 채우고, node ID 없음/registry miss/preset alias를 보존한다. |
| completeness | Pass | RNL-1/RNL-2 계획 항목이 모두 구현되었고, opsconsole 이전 경로 차이는 리뷰 파일에 설명되어 있다. |
| test coverage | Pass | `TestServerEnrichesRunEventNodeAlias`, `TestEventRouterPrefersRunEventAlias` 및 기존 fallback 테스트가 의미 있는 assertion을 포함한다. |
| API contract | Pass | `node_id = 11` 유지, `node_alias = 12` 추가로 protobuf 호환성을 유지하며 generated code는 `make proto` 재실행 결과와 일치한다. |
| code quality | Pass | 관련 경로 `git diff --check` 통과, debug print/TODO/dead code 없음. |
| plan deviation | Pass | `cmd/edge`에서 `internal/opsconsole`로 이전된 기존 구조를 따른 변경이며, deviation 사유가 기록되어 있다. |
| verification trust | Pass | `make proto`, 대상 패키지 테스트, `go test -count=1 ./...`를 리뷰 중 재실행해 기록된 결과와 일치함을 확인했다. |

### 발견된 문제

없음

### 다음 단계

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