<!-- task=m-architecture-refactor-foundation/09_proto_legacy_contract plan=1 tag=REVIEW_API -->

# Plan - REVIEW_API

## 이 파일을 읽는 구현 에이전트에게

이 계획을 구현한 뒤에는 반드시 같은 디렉터리의 `CODE_REVIEW-*-G??.md`에서 구현 에이전트 소유 섹션을 실제 내용과 검증 출력으로 채운다. 검증 명령을 실행하고 실제 stdout/stderr를 붙이며, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 최종 판정, log archive, `complete.log` 작성, task archive 이동은 code-review-skill 전용이다.

구현 중 사용자만 결정할 수 있는 범위 변경, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 충돌이 없이는 진행할 수 없으면 `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 근거와 재개 조건을 기록하고 중단한다. 구현 에이전트는 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 증거 수집으로 해소할 수 있는 검증 공백은 사용자 리뷰 요청이 아니다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/automation-runtime-bridge/milestones/architecture-refactor-foundation.md`
- Task ids:
  - `proto-legacy`: legacy scheduling/node registration proto placeholder를 deprecated/reserved/remove 중 하나로 정리하고 생성 코드와 call-site를 맞춘다.
- Completion mode: check-on-pass

## 배경

첫 리뷰(`code_review_cloud_G06_0.log`)에서 `NodeStatus` retired value names가 source-level `reserved`로 고정되지 않은 점이 Required로 판정됐다. 현재 구현은 value number `1 to 3`만 reserved하므로 `NODE_STATUS_ONLINE` 같은 retired 이름을 새 번호로 다시 추가할 수 있다. 이 follow-up은 proto source 자체가 legacy enum 이름 재활성화를 막도록 보강하고, 검증 검색은 reserved metadata와 active generated constants를 구분하도록 조정한다.

## 리뷰 지적 사항

- Required: `proto/iop/node.proto`의 `NodeStatus`에 `reserved "NODE_STATUS_ONLINE", "NODE_STATUS_OFFLINE", "NODE_STATUS_DRAINING";`가 없다.

## 범위 결정 근거

- `ScheduleRequest`/`ScheduleResponse`, `NodeInfo`, `NodeRegisterRequest`, `NodeRegisterResponse`의 message field tombstone은 이미 계획대로 적용되어 있어 수정 범위가 아니다.
- `runtime.proto`의 active `RegisterRequest`/`RegisterResponse`와 Control Plane-Edge active wire 계약은 변경하지 않는다.
- 기존 final `rg`가 bare retired enum names를 찾으면 reserved-name metadata까지 실패로 잡는다. 이번 follow-up에서는 active generated enum constants/accessors만 찾는 패턴으로 바꾼다.

## 빌드 등급

- `cloud-G06`: protobuf schema, Go/Dart generated binding, descriptor regression을 함께 갱신한다.

## 구현 체크리스트

- [ ] `proto/iop/node.proto`에서 retired `NodeStatus` value names를 source-level reserved name으로 추가하고 관련 주석을 갱신한다.
- [ ] `make proto`와 `make proto-dart`로 Go/Dart 생성물을 갱신하고, generated files를 사람이 직접 편집하지 않는다.
- [ ] `apps/control-plane/internal/wire/edge_test.go`의 descriptor guard가 `NodeStatus` active value, reserved range, reserved names를 함께 검증하도록 보강한다.
- [ ] deterministic stale-symbol 검색을 active generated constants/accessors만 탐지하도록 실행하고 결과를 기록한다.
- [ ] 최종 검증 명령을 실행하고 결과를 `CODE_REVIEW-*-G??.md`에 붙인다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

1. `proto/iop/node.proto`의 `NodeStatus` reserved names와 주석을 먼저 수정한다.
2. `make proto`, `make proto-dart`로 생성물을 갱신한다.
3. Go descriptor guard를 reserved names/range까지 확인하도록 보강한다.
4. 검증 명령과 deterministic search를 실행한다.

### [REVIEW_API-1] NodeStatus Reserved Name Tombstone

#### 문제

현재 `NodeStatus`는 retired value numbers만 reserved한다.

```proto
enum NodeStatus {
  NODE_STATUS_UNSPECIFIED = 0;
  reserved 1 to 3;
}
```

이 상태에서는 `NODE_STATUS_ONLINE = 4` 같은 source 변경이 proto compiler에서 막히지 않는다. legacy enum 이름 자체를 tombstone으로 고정하려면 retired value names도 reserved해야 한다.

#### 해결 방법

`NodeStatus`에 retired value name reservation을 추가한다.

```proto
enum NodeStatus {
  NODE_STATUS_UNSPECIFIED = 0;
  reserved 1 to 3;
  reserved "NODE_STATUS_ONLINE", "NODE_STATUS_OFFLINE", "NODE_STATUS_DRAINING";
}
```

Go guard는 다음을 확인한다.

- active enum value는 `NODE_STATUS_UNSPECIFIED=0` 하나뿐이다.
- reserved range가 `1 to 3`을 포함한다.
- reserved names에 `NODE_STATUS_ONLINE`, `NODE_STATUS_OFFLINE`, `NODE_STATUS_DRAINING`이 모두 있다.

#### 수정 파일 및 체크리스트

- [ ] `proto/iop/node.proto`: retired enum value names를 reserved 처리한다.
- [ ] `proto/gen/iop/node.pb.go`: `make proto` 산출물로 갱신한다.
- [ ] `apps/client/lib/gen/proto/iop/node.pb.dart`: `make proto-dart` 산출물로 갱신한다.
- [ ] `apps/client/lib/gen/proto/iop/node.pbenum.dart`: `make proto-dart` 산출물로 갱신한다.
- [ ] `apps/client/lib/gen/proto/iop/node.pbjson.dart`: `make proto-dart` 산출물로 갱신한다.
- [ ] `apps/control-plane/internal/wire/edge_test.go`: reserved range/name descriptor guard를 추가한다.

#### 중간 검증

```bash
make proto
make proto-dart
go test -count=1 ./apps/control-plane/internal/wire ./proto/gen/...
```

## 최종 검증

```bash
make proto
make proto-dart
go test -count=1 ./apps/control-plane/... ./packages/go/... ./proto/gen/...
cd apps/client && flutter test test/iop_wire/generated_proto_import_test.dart test/iop_wire/parser_map_test.dart
rg --sort path -n "NodeStatus_NODE_STATUS_ONLINE|NodeStatus_NODE_STATUS_OFFLINE|NodeStatus_NODE_STATUS_DRAINING|static const NodeStatus NODE_STATUS_ONLINE|static const NodeStatus NODE_STATUS_OFFLINE|static const NodeStatus NODE_STATUS_DRAINING|GetAddress|GetToken|hasAddress|hasToken|set address|set token" proto/gen/iop/control.pb.go proto/gen/iop/node.pb.go apps/client/lib/gen/proto/iop/control.pb.dart apps/client/lib/gen/proto/iop/control.pbjson.dart apps/client/lib/gen/proto/iop/node.pb.dart apps/client/lib/gen/proto/iop/node.pbenum.dart apps/client/lib/gen/proto/iop/node.pbjson.dart
```

기대 결과: codegen 명령은 성공하고 추가 codegen drift가 없다. Go/Flutter focused tests는 통과한다. 마지막 `rg --sort path`는 출력이 없어야 한다. reserved-name metadata에 bare `NODE_STATUS_ONLINE` 문자열이 남는 것은 의도된 tombstone이므로 실패로 보지 않는다. 출력이 active generated enum constant/accessor이면 실패다.
