<!-- task=m-control-plane-separation-migration/01_wire_contract plan=0 tag=API -->

# Plan - API

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

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

## 배경

`server-runner-contract` 에픽은 Go server와 Dart runner가 같은 Server-Runner 계약을 공유해야 한다. 현재 runner는 iop runtime protobuf를 직접 import하고, core service는 health/readiness HTTP handler만 가진다. 먼저 원본 계약과 생성물 경로를 확정해야 등록, heartbeat, bootstrap 작업이 같은 메시지 이름과 필드를 기준으로 이어진다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 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:
  - `wire-contract`: runner-server 장기 연결에 필요한 protobuf/proto-socket 또는 대체 wire 계약의 원본 위치와 생성물 경로가 확정된다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/independent-control-plane/PHASE.md`
- `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md`
- `agent-test/local/rules.md`
- `agent-test/local/agent-smoke.md`
- `agent-test/local/framework-smoke.md`
- `services/core/go.mod`
- `services/core/internal/httpserver/server.go`
- `services/core/internal/httpserver/server_test.go`
- `apps/runner/pubspec.yaml`
- `apps/runner/lib/oto/agent/edge_registration_client.dart`
- `apps/runner/test/oto_agent_registration_test.dart`
- `apps/runner/test/oto_agent_migration_plan_test.dart`
- `Makefile`

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`를 읽었고, agent/framework smoke profile을 적용한다.
- 적용 명령: runner `dart pub get`, `dart analyze`, agent test subset, framework 영향 시 `dart test`; core service는 agent-test profile이 없어 repository fallback `cd services/core && go test ./...`를 쓴다.
- 도구 확인: `protoc`, `protoc-gen-dart`, `protoc-gen-go`, `dart`, `go`가 PATH에 있다.
- `proto/oto/**`와 `services/core`용 agent-test profile은 아직 없으므로, 이번 계획은 fallback 검증을 명시한다.

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

- shared proto 원본과 생성물 경로: 기존 테스트 없음. 새 Makefile target 또는 script dry-run 검증이 필요하다.
- Dart runner가 새 OTO proto import로 빌드 가능한지: 기존 iop registration tests만 있음. 새 generated import compile 검증이 필요하다.
- Go server가 새 generated package를 import 가능한지: 현재 health/readiness tests만 있음. 새 generated package compile 검증이 필요하다.

### 심볼 참조

- 제거/rename 없음. 후속 plan에서 `EdgeRegistrationClient`, `EdgeEndpoint`, iop generated imports를 교체한다.

### 분할 판단

- split decision policy를 먼저 평가했다.
- 공유 task group: `m-control-plane-separation-migration`.
- `01_wire_contract`: 독립 선행 작업. shared proto 원본과 generation target만 확정한다.
- `02+01_runner_registration`: `01`의 generated 계약 완료 후 runner/server 등록 구현.
- `03+02_online_state`: `02`의 registry와 registration endpoint 완료 후 heartbeat/timeout 구현.
- `04+02_bootstrap_command`: `02`의 token/runner registration model 완료 후 bootstrap command 구현.
- 이 plan은 선행 의존성이 없다.

### 범위 결정 근거

- OTO job dispatch, log streaming, artifact reporting은 후속 Milestone 범위라 제외한다.
- iop generated protobuf 파일은 migration evidence라 직접 수정하지 않는다.
- `apps/client`는 생성된 계약 소비가 아직 없으므로 이번 작업에서 제외한다.
- proto-socket 장기 연결 runtime 구현은 등록/heartbeat HTTP fallback이 안정화된 뒤 별도 작업으로 둔다.

### 빌드 등급

- `cloud-G06`: cross-language proto/schema와 생성 toolchain 경계가 있어 cloud lane이 적합하다.

## 구현 체크리스트

- [ ] [API-1] `proto/oto/runner.proto`를 만들고 Register/Heartbeat/Bootstrap 최소 메시지와 생성물 경로를 확정한다.
- [ ] [API-2] root `Makefile`에 proto generation target을 추가해 Go/Dart 생성물을 같은 원본에서 갱신한다.
- [ ] [API-3] 생성된 Go/Dart protobuf 파일을 추가하고 각 runtime이 import 가능한지 빌드 검증한다. 검증: `make proto`, `cd services/core && go test ./...`, `cd apps/runner && dart analyze` 통과.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Proto 원본 계약 추가

문제: [apps/runner/lib/oto/agent/edge_registration_client.dart](/config/workspace/oto/apps/runner/lib/oto/agent/edge_registration_client.dart:6)가 `iop/runtime.pb.dart`를 import하고, [services/core/internal/httpserver/server.go](/config/workspace/oto/services/core/internal/httpserver/server.go:15)는 OTO runner 계약을 전혀 등록하지 않는다.

해결 방법: `proto/oto/runner.proto`를 추가한다. package는 `oto.runner.v1`, Go package는 `github.com/toki/oto/services/core/oto;otopb`로 둔다. 메시지는 `RunnerCapability`, `CommandCatalogSummary`, `RegisterRunnerRequest`, `RegisterRunnerResponse`, `HeartbeatRequest`, `HeartbeatResponse`, `BootstrapCommandRequest`, `BootstrapCommandResponse`만 둔다.

Before:

```text
1  module github.com/toki/oto/services/core
3  go 1.26
```

After:

```proto
syntax = "proto3";

package oto.runner.v1;

option go_package = "github.com/toki/oto/services/core/oto;otopb";

message RunnerCapability {
  string name = 1;
  string version = 2;
}
```

수정 파일 및 체크리스트:

- [ ] `proto/oto/runner.proto` 생성
- [ ] message/field 이름은 runner/server 용어만 사용하고 Edge/Node/iop 용어를 쓰지 않는다.
- [ ] enrollment token, runner id, alias, protocol version, command catalog summary, heartbeat status, bootstrap command 필드를 포함한다.

테스트 작성: proto 자체 단위 테스트는 작성하지 않는다. 생성물 compile 검증과 후속 등록/heartbeat tests가 사용 검증을 맡는다.

중간 검증:

```bash
protoc -I proto --go_out=services/core --go_opt=paths=source_relative --dart_out=apps/runner/lib/oto/agent oto/runner.proto
```

### [API-2] Generation target 추가

문제: [Makefile](/config/workspace/oto/Makefile:1)는 runner/client/core test target만 있고 proto generation target이 없다.

해결 방법: `PROTOC_GEN_DART` 탐색과 `proto`, `proto-go`, `proto-dart` phony target을 추가한다. 출력 위치는 Go `services/core/oto/runner.pb.go`, Dart `apps/runner/lib/oto/agent/oto/runner.pb.dart` 계열로 고정한다.

Before:

```makefile
1 .PHONY: all setup test runner-get runner-analyze runner-test client-get client-analyze client-test core-test
25 core-test:
26 	cd services/core && go test ./...
```

After:

```makefile
.PHONY: proto proto-go proto-dart

PROTOC_GEN_DART := $(shell command -v protoc-gen-dart 2>/dev/null)

proto: proto-go proto-dart
```

수정 파일 및 체크리스트:

- [ ] `Makefile` phony 목록에 proto targets 추가
- [ ] `protoc-gen-dart`가 없으면 명확한 error를 출력한다.
- [ ] 생성 명령은 `proto/oto/runner.proto` 하나만 대상으로 둔다.

테스트 작성: Makefile target은 별도 test file 없이 `make proto` 실행으로 검증한다.

중간 검증:

```bash
make proto
```

### [API-3] 생성물 compile 검증

문제: [apps/runner/pubspec.yaml](/config/workspace/oto/apps/runner/pubspec.yaml:23)는 `proto_socket`, [apps/runner/pubspec.yaml](/config/workspace/oto/apps/runner/pubspec.yaml:30)는 `protobuf`를 이미 갖고 있지만 OTO 계약 생성물이 없다. [services/core/go.mod](/config/workspace/oto/services/core/go.mod:1)는 generated Go protobuf dependency가 아직 없다.

해결 방법: `make proto`로 생성물을 추가하고 Go module이 필요한 `google.golang.org/protobuf` dependency를 `go mod tidy`로 반영한다.

수정 파일 및 체크리스트:

- [ ] `services/core/oto/runner.pb.go` 추가
- [ ] `apps/runner/lib/oto/agent/oto/runner.pb.dart`, `.pbenum.dart`, `.pbjson.dart` 추가
- [ ] `services/core/go.mod`와 `go.sum` 변경이 생기면 함께 반영
- [ ] generated files는 수동 편집하지 않는다.

테스트 작성: 생성물은 수동 테스트를 작성하지 않는다. Go/Dart compile 검증으로 충분하다.

중간 검증:

```bash
cd services/core && go test ./...
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `proto/oto/runner.proto` | API-1 |
| `Makefile` | API-2 |
| `services/core/oto/runner.pb.go` | API-3 |
| `apps/runner/lib/oto/agent/oto/runner.pb.dart` | API-3 |
| `apps/runner/lib/oto/agent/oto/runner.pbenum.dart` | API-3 |
| `apps/runner/lib/oto/agent/oto/runner.pbjson.dart` | API-3 |
| `services/core/go.mod` | API-3 |
| `services/core/go.sum` | API-3 |

## 최종 검증

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

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