<!-- task=03_stable_edge_node_id plan=0 tag=REFACTOR -->

# Code Review Reference - REFACTOR

> **[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.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-05-10
task=03_stable_edge_node_id, plan=0, tag=REFACTOR

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

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

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

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [REFACTOR-1] Edge identity config | [x] |
| [REFACTOR-2] Prefer stable node ID in config | [x] |
| [REFACTOR-3] Duplicate ID validation | [x] |
| [REFACTOR-4] Registration path stays stable | [x] |

## 계획 대비 변경 사항

- 계획에서는 `EdgeInfo` 또는 `IdentityConf` 중 하나를 추가하라고 명시했고, `EdgeInfo`로 신설했다. 기존 `NodeInfo`(packages/config/config.go:38)는 미사용이지만 별도 책임이라 그대로 두고 edge 식별용 별도 타입을 추가했다.
- bootstrap/console에서 edge id/name을 적극적으로 로깅하지는 않았다. 계획이 "if helpful"이라 명시했고, 현재 콘솔 출력 흐름을 변경할 필요성이 없어 config 적재만 보장했다.
- REFACTOR-4 통합 테스트는 명시 ID(`node-test-01`)를 사용하도록 갱신하면서, store에 보관된 ID가 명시 config 값과 동일한지 추가로 단언했다(stable identity 검증 강화).

## 주요 설계 결정

- `EdgeInfo`는 Control Plane 등록 용도가 아닌, "loading and logging only" 용도임을 doc comment에 명시했다(packages/config/config.go).
- `NodeDefinition.ID` 코멘트를 "optional; UUID v4 auto-assigned if empty"에서 "stable node identity; if empty, a UUID v4 is auto-assigned (dev fallback only)"로 변경해 명시 ID를 권장하도록 톤을 바꾸었다.
- `LoadFromConfig`의 중복 ID 검사는 명시 ID(`d.ID != ""`)에 한해 수행한다. UUID fallback은 `uuid.NewString()`이 충돌하지 않는다고 가정한다(기존 `TestLoadFromConfig_AutoIDUnique`로 보호됨).
- `configs/edge.yaml`은 sample edge id/name(`edge-dgx-group` / `DGX Group`)과 명시 node id(`node-dgx-01`)를 포함하도록 갱신했다. UUID fallback 안내는 sample 위 주석에서만 유지했다.

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

- Edge id/name is config-loadable without adding Control Plane behavior.
- Node id is stable from `nodes[].id`; UUID fallback remains dev-only.
- Duplicate explicit node IDs are rejected.
- Token-based registration still works.
- No auth, DB, inventory UI, or federation design was added.

## 검증 결과

### REFACTOR-1 중간 검증
```bash
$ go test ./packages/config
ok  	iop/packages/config	0.007s
```

### REFACTOR-2 중간 검증
```bash
$ go test ./apps/edge/internal/node/...
ok  	iop/apps/edge/internal/node	0.004s
```

### REFACTOR-3 중간 검증
```bash
$ go test ./apps/edge/internal/node/...
ok  	iop/apps/edge/internal/node	0.004s
```

### REFACTOR-4 중간 검증
```bash
$ go test ./apps/edge/internal/transport/... ./apps/node/internal/bootstrap/...
ok  	iop/apps/edge/internal/transport	0.006s
ok  	iop/apps/node/internal/bootstrap	0.160s
```

### 최종 검증
```bash
$ gofmt -w packages/config/config.go packages/config/config_test.go apps/edge/internal/node/store.go apps/edge/internal/node/store_test.go apps/edge/internal/transport/integration_test.go
(no output)

$ go test ./packages/config ./apps/edge/internal/node/... ./apps/edge/internal/transport/... ./apps/node/internal/bootstrap/...
ok  	iop/packages/config	(cached)
ok  	iop/apps/edge/internal/node	(cached)
ok  	iop/apps/edge/internal/transport	(cached)
ok  	iop/apps/node/internal/bootstrap	(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	0.004s
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/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	(cached)
?   	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]

$ rg -n "uuid.NewString|UUID v4|auto-assigned|NodeDefinition|EdgeConfig|NodeConfig" apps packages configs proto/iop --glob '!**/README.md'
proto/iop/runtime.proto:122:  NodeConfigPayload config   = 5;
proto/iop/runtime.proto:125:// NodeConfigPayload carries all configuration edge pushes to the node.
proto/iop/runtime.proto:126:message NodeConfigPayload {
packages/config/config.go:7:type NodeConfig struct {
packages/config/config.go:13:type EdgeConfig struct {
packages/config/config.go:20:	Nodes   []NodeDefinition `mapstructure:"nodes"   yaml:"nodes"`
packages/config/config.go:30:// NodeDefinition is the edge-side record for a pre-registered node.
packages/config/config.go:31:type NodeDefinition struct {
packages/config/config.go:32:	ID       string       `mapstructure:"id"       yaml:"id"` // stable node identity; if empty, a UUID v4 is auto-assigned (dev fallback only)
packages/config/config.go:138:func Load(cfgFile string) (*NodeConfig, error) {
packages/config/config.go:145:	var cfg NodeConfig
packages/config/config.go:152:func LoadEdge(cfgFile string) (*EdgeConfig, error) {
packages/config/config.go:159:	var cfg EdgeConfig
apps/edge/internal/node/store.go:65:// LoadFromConfig seeds the store from EdgeConfig.Nodes.
apps/edge/internal/node/store.go:66:func LoadFromConfig(defs []config.NodeDefinition) (*NodeStore, error) {
apps/edge/internal/node/store.go:88:			nodeID = uuid.NewString()
apps/edge/internal/node/mapper.go:14:func BuildConfigPayload(rec *NodeRecord) (*iop.NodeConfigPayload, error) {
apps/edge/internal/node/mapper.go:15:	payload := &iop.NodeConfigPayload{
apps/edge/internal/bootstrap/module.go:16:func Module(cfg *config.EdgeConfig) fx.Option {
... (NodeDefinition / EdgeConfig / NodeConfig / NodeConfigPayload references in test and bootstrap files; no stale "auto-assigned" text outside the updated comment)
```

---

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