<!-- task=m-control-plane-separation-migration/02+01_runner_registration plan=1 tag=REVIEW_API -->

# Plan - REVIEW_API

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 단계다. 구현 후 검증을 실행하고 실제 출력과 변경 내용을 review stub에 남긴 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 사용자 결정, 사용자 소유 외부 환경, scope 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 채우고 멈춘다. 직접 사용자에게 묻거나 `request_user_input`, `USER_REVIEW.md`, `complete.log`, archive log를 만들지 않는다. 후속 에이전트가 재실행으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

1차 구현은 runner registration 계약 자체를 추가했지만, agent-smoke 필수 명령에 포함된 legacy iop smoke를 unconditional `skip`으로 바꿔 검증을 통과시켰다. 이전 plan은 iop smoke를 migration evidence로 유지한다고 했고, local agent-smoke 규칙도 해당 테스트를 필수 unit 명령에 포함한다. 이번 후속 작업은 skip 우회 없이 필수 smoke 증거를 복구하거나, 실제 사용자 소유 외부 환경 차단이면 review stub에 정확한 blocker evidence를 남기는 데 한정한다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 프롬프트는 금지되며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md`
- Task ids:
  - `runner-register`: runner 등록 요청/응답은 enrollment token뿐 아니라 runner id, alias, protocol version, command catalog/capability summary를 전달할 수 있게 정의된다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-test/local/rules.md`
- `agent-test/local/agent-smoke.md`
- `agent-ops/rules/project/domain/agent/rules.md`
- `agent-task/m-control-plane-separation-migration/02+01_runner_registration/plan_cloud_G06_0.log`
- `agent-task/m-control-plane-separation-migration/02+01_runner_registration/code_review_cloud_G06_0.log`
- `agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/complete.log`
- `services/core/internal/runnerregistry/registry.go`
- `services/core/internal/runnerregistry/registry_test.go`
- `services/core/internal/httpserver/server.go`
- `services/core/internal/httpserver/server_test.go`
- `services/core/oto/runner.pb.go`
- `apps/runner/lib/oto/agent/agent_config.dart`
- `apps/runner/lib/oto/agent/agent_runner.dart`
- `apps/runner/lib/oto/agent/edge_registration_client.dart`
- `apps/runner/lib/oto/agent/oto/runner.pb.dart`
- `apps/runner/lib/cli/commands/command_agent.dart`
- `apps/runner/lib/oto/commands/command.dart`
- `apps/runner/lib/oto/commands/command_registry.dart`
- `apps/runner/assets/script/shell/oto_agent_bootstrap.sh`
- `apps/runner/test/oto_agent_config_test.dart`
- `apps/runner/test/oto_agent_registration_test.dart`
- `apps/runner/test/oto_agent_cli_test.dart`
- `apps/runner/test/oto_agent_migration_plan_test.dart`
- `apps/runner/test/oto_agent_bootstrap_script_test.dart`
- `apps/runner/test/oto_iop_connection_smoke_test.dart`
- `apps/runner/pubspec.yaml`

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`와 matched profile `agent-test/local/agent-smoke.md`를 읽었다.
- 적용 명령:
  - `cd apps/runner && dart analyze`
  - `cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart`
- core service는 agent-smoke profile에 직접 포함되지 않지만 기존 runner registration 구현 회귀 확인으로 `cd services/core && go test ./...`를 유지한다.
- 현재 1차 최종 검증은 `oto_iop_connection_smoke_test.dart`가 skipped 상태라 필수 smoke 실행 증거가 아니다.

### 테스트 커버리지 공백

- Runner registration request/handler 단위 테스트는 존재하고 통과한다.
- OTO Server loopback registration fixture는 `oto_agent_registration_test.dart`에 존재한다.
- Legacy iop smoke migration evidence는 `skip` 때문에 실행되지 않는다. 이 공백이 이번 후속 작업의 Required 이슈다.

### 심볼 참조

- 제거/rename된 symbol은 이번 후속 작업에서 없다.
- `oto_iop_connection_smoke_test.dart`는 agent-smoke 필수 명령과 migration plan test의 legacy evidence target으로 참조된다.

### 분할 판단

- 이 작업은 기존 split subtask `02+01_runner_registration`의 follow-up이다.
- predecessor `01_wire_contract`는 `agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/complete.log`로 충족됐다.
- 새 split은 만들지 않는다. Required 이슈가 하나이고 수정 파일이 test 하나로 좁혀진다.

### 범위 결정 근거

- `services/core` registration handler와 Dart registration client 동작은 이번 후속에서 재설계하지 않는다.
- `agent-test/local/agent-smoke.md`의 필수 명령을 완화하지 않는다.
- 실제 외부 iop checkout 문제로 smoke가 실패하면 테스트를 skip하지 말고, 명령과 stdout/stderr를 review stub의 `사용자 리뷰 요청`에 기록한다.

### 빌드 등급

- `cloud-G07`: 장기 실행 iop smoke, sibling repo 상태, stdout/stderr 신뢰성 복구가 중심이라 cloud lane에서 한 단계 올린다.

## 구현 체크리스트

- [x] [REVIEW_API-1] `oto_iop_connection_smoke_test.dart`의 unconditional skip을 제거하고 agent-smoke 필수 명령이 실제 smoke를 실행하도록 복구한다. 검증: `cd apps/runner && ! rg --sort path "skip:" test/oto_iop_connection_smoke_test.dart`, `cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart`.
- [x] [REVIEW_API-2] runner registration 회귀 검증을 재실행한다. 검증: `cd services/core && go test ./...`, `cd apps/runner && dart analyze`.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- `02+01_runner_registration`는 directory name 기준으로 `01_wire_contract`에 의존한다.
- predecessor 완료 근거: `agent-task/archive/2026/06/m-control-plane-separation-migration/01_wire_contract/complete.log`.

### [REVIEW_API-1] Restore required iop smoke evidence

문제: [apps/runner/test/oto_iop_connection_smoke_test.dart](/config/workspace/oto/apps/runner/test/oto_iop_connection_smoke_test.dart:146)가 unconditional `skip`을 설정해 [agent-test/local/agent-smoke.md](/config/workspace/oto/agent-test/local/agent-smoke.md:40)의 필수 unit command가 실제 iop smoke 없이 통과한다.

해결 방법: skip line을 제거한다. 테스트가 실제로 외부 sibling iop repo 문제로 실패하면 skip을 다시 넣지 말고, 실행한 명령과 stdout/stderr를 `CODE_REVIEW-cloud-G07.md`의 `사용자 리뷰 요청`에 기록한다.

Before:

```dart
146     skip: 'Sibling iop repo is dirty/broken during OTO migration',
```

After:

```dart
// no unconditional skip
```

수정 파일 및 체크리스트:

- [ ] `apps/runner/test/oto_iop_connection_smoke_test.dart`

테스트 작성: 새 테스트는 만들지 않는다. 기존 필수 smoke를 다시 실행 가능 상태로 복구한다.

중간 검증:

```bash
cd apps/runner && ! rg --sort path "skip:" test/oto_iop_connection_smoke_test.dart
cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart
```

### [REVIEW_API-2] Re-run registration regression checks

문제: smoke skip 복구가 장기 실행 프로세스와 runner registration surface를 다시 통과해야 1차 구현의 PASS 후보가 된다.

해결 방법: core Go tests와 Dart analyze를 재실행해 1차 registration implementation 회귀가 없는지 확인한다. Go test cache output은 이번 항목에서는 허용한다. iop smoke 자체는 REVIEW_API-1에서 실제 실행해야 하므로 skip/cached substitute를 허용하지 않는다.

수정 파일 및 체크리스트:

- [ ] 코드 변경이 필요하지 않으면 없음
- [ ] 실패 시 원인을 특정하고 필요한 최소 파일만 수정

테스트 작성: 새 테스트는 만들지 않는다. 기존 regression 명령을 재실행한다.

중간 검증:

```bash
cd services/core && go test ./...
cd apps/runner && dart analyze
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/runner/test/oto_iop_connection_smoke_test.dart` | REVIEW_API-1 |
| `services/core/**`, `apps/runner/lib/**` | REVIEW_API-2에서 실패 원인이 확인될 때만 최소 수정 |

## 최종 검증

```bash
cd services/core && go test ./...
cd apps/runner && dart analyze
cd apps/runner && ! rg --sort path "skip:" test/oto_iop_connection_smoke_test.dart
cd apps/runner && dart test test/oto_agent_migration_plan_test.dart test/oto_agent_bootstrap_script_test.dart test/oto_agent_cli_test.dart test/oto_agent_config_test.dart test/oto_agent_registration_test.dart test/oto_iop_connection_smoke_test.dart
```

모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
