<!-- task=m-control-plane-separation-migration/06+05_runner_dispatch_contract plan=0 tag=CICD_RUNNER -->

# Plan - CICD_RUNNER

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

구현 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 변경 사유, 검증 출력으로 채운다. 구현 중 사용자만 결정할 수 있는 범위 변경이나 외부 환경 준비가 필요하면 리뷰 stub의 `사용자 리뷰 요청` 섹션에 근거를 적고 멈춘다. 직접 사용자에게 질문하거나 `USER_REVIEW.md`, `complete.log`, archive 로그를 만들지 않는다.

## 배경

서버 상태/API가 생겨도 runner가 job을 가져오고 execution 결과를 보고하지 않으면 OTO Server가 CI/CD 실행 상태를 소유한다고 볼 수 없다. 이 계획은 `05+04_core_cicd_state` 완료 후 runner/server 계약을 `proto/oto`와 Dart client에 연결한다.

## 사용자 리뷰 요청 흐름

구현 중 차단 사유는 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 에이전트는 사용자에게 직접 묻지 않고, code-review가 요청 타당성을 판단한다.

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/independent-control-plane/milestones/control-plane-separation-migration.md`
- `agent-test/local/rules.md`
- `agent-test/local/agent-smoke.md`
- `services/core/go.mod`
- `proto/oto/runner.proto`
- `Makefile`
- `apps/runner/pubspec.yaml`
- `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/core/build_result.dart`
- `apps/runner/lib/oto/core/execution_context.dart`
- `apps/runner/lib/oto/application.dart`
- `apps/runner/test/oto_agent_config_test.dart`
- `apps/runner/test/oto_agent_registration_test.dart`
- `apps/runner/test/oto_server_connection_smoke_test.dart`
- `apps/runner/test/oto_core_test.dart`
- `apps/runner/test/oto_application_test.dart`
- `services/core/internal/httpserver/server.go`
- `services/core/internal/httpserver/server_test.go`

### 테스트 환경 규칙

`test_env=local`이다. `agent-test/local/rules.md`와 `agent-test/local/agent-smoke.md`를 읽었다. agent-smoke는 `dart analyze`와 agent 관련 `dart test` 명령을 필수로 둔다. 이 계획은 proto generation과 Go server도 건드리므로 `make proto`, `cd services/core && go test -count=1 ./...`, `cd apps/runner && dart analyze`, agent-smoke Dart 테스트를 최종 검증으로 둔다.

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

- runner job polling/claim/reporting: 기존 테스트 없음. `oto_agent_registration_test.dart` 또는 새 agent test에 fake HTTP server 기반 테스트가 필요하다.
- proto job/execution message: 기존 `runner.proto`에는 register/heartbeat/bootstrap만 있다. 생성물 갱신 후 Go/Dart compile 테스트가 필요하다.
- BuildResult to execution report mapping: `oto_application_test.dart`는 `BuildResult.toJson`과 stepEvents를 검증한다. server report payload mapping 테스트가 추가로 필요하다.

### 심볼 참조

기존 symbol rename/remove는 요구하지 않는다. 새 proto message와 Dart helper를 추가한다. `RegisterRunnerRequest`, `HeartbeatRequest`, `BootstrapCommandRequest` call site는 유지한다.

### 분할 판단

split 정책을 평가했다. 이 계획은 `06+05_runner_dispatch_contract`이며 predecessor `05`는 같은 task group의 `05+04_core_cicd_state/complete.log`가 필요하다. 현재 작성 시점에는 05가 active plan 상태라 predecessor는 아직 미충족이다. 구현 에이전트는 `agent-task/m-control-plane-separation-migration/05+04_core_cicd_state/complete.log` 또는 archive의 matching complete.log를 확인한 뒤 시작한다.

### 범위 결정 근거

이 계획은 runner가 OTO Server job 계약을 소비하고 BuildResult/stepEvents를 report payload로 변환하는 최소 흐름만 다룬다. 장기 streaming transport, durable artifact upload, scheduling policy, self-update는 제외한다.

### 빌드 등급

`cloud-G07`. proto/schema, Go/Dart 생성물, runner background HTTP workflow가 함께 움직여 검증 신뢰와 cross-runtime 판단이 필요하다.

## 의존 관계 및 구현 순서

`06+05_runner_dispatch_contract`는 directory name 기준으로 `05+04_core_cicd_state` 완료에 의존한다. 구현 시작 전 predecessor complete.log를 확인한다.

## 구현 체크리스트

- [ ] `proto/oto/runner.proto`에 runner job claim/reporting 최소 메시지를 추가하고 `make proto`로 Go/Dart 생성물을 갱신한다.
- [ ] Dart runner client에 job claim, execution report, log/artifact report helper를 추가한다.
- [ ] `DefaultAgentRunner` 또는 OTO Server session 흐름에 polling/reporting을 주입 가능한 형태로 연결한다.
- [ ] fake HTTP server 테스트로 runner가 job을 받고 BuildResult/stepEvents를 OTO Server report payload로 보내는지 검증한다.
- [ ] `make proto`, `cd services/core && go test -count=1 ./...`, `cd apps/runner && dart analyze`, agent-smoke Dart 테스트를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [CICD_RUNNER-1] runner job proto 계약

### 문제

[runner.proto](/config/workspace/oto/proto/oto/runner.proto:16)는 registration, heartbeat, bootstrap만 정의한다. runner가 OTO Server job을 claim하거나 execution result를 보고할 계약이 없다.

Before:

```proto
16 message RegisterRunnerRequest {
17   string enrollment_token = 1;
18   string runner_id = 2;
...
53 message BootstrapCommandResponse {
54   string bootstrap_command = 1;
55 }
```

### 해결 방법

`JobClaimRequest/Response`, `ExecutionReportRequest/Response`, `LogAppendRequest`, `ArtifactReportRequest`를 추가한다. HTTP/JSON helpers가 우선이더라도 proto 원본은 `proto/oto`에 둔다. 생성물은 `make proto`로 갱신한다.

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

- [ ] `proto/oto/runner.proto`
- [ ] `services/core/oto/runner.pb.go`
- [ ] `apps/runner/lib/oto/agent/oto/runner.pb.dart`
- [ ] `apps/runner/lib/oto/agent/oto/runner.pbjson.dart`
- [ ] `apps/runner/lib/oto/agent/oto/runner.pbenum.dart` 필요 시

### 테스트 작성

생성물 자체의 별도 테스트는 작성하지 않는다. compile/analyze와 runner/server behavior 테스트로 검증한다.

### 중간 검증

```bash
make proto
```

기대 결과: Go/Dart generated files 갱신 성공.

## [CICD_RUNNER-2] Dart runner reporting client

### 문제

[edge_registration_client.dart](/config/workspace/oto/apps/runner/lib/oto/agent/edge_registration_client.dart:198)는 등록 후 heartbeat/disconnect만 수행한다. job claim/reporting URI와 payload helper가 없다.

Before:

```dart
198 @override
199 Future<EdgeAgentSession> openSession(AgentConfig config) async {
...
341 Future<void> _sendHeartbeat() async {
342   if (_closed) return;
```

### 해결 방법

기존 session을 유지하되 job 관련 helper를 별도 class나 주입 가능한 collaborator로 분리한다. `BuildResult.toJson()`과 `StepEvent.toJson()`을 payload source로 사용한다. runner loop는 테스트에서 interval/client를 주입할 수 있어야 한다.

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

- [ ] `apps/runner/lib/oto/agent/edge_registration_client.dart`
- [ ] `apps/runner/lib/oto/agent/agent_runner.dart`
- [ ] 필요 시 `apps/runner/lib/oto/agent/oto_server_job_client.dart`
- [ ] `apps/runner/test/oto_agent_registration_test.dart`

### 테스트 작성

작성한다. fake `HttpServer`로 claim response, report request body, close 후 polling 중단을 검증한다.

### 중간 검증

```bash
cd apps/runner && dart test test/oto_agent_registration_test.dart
```

기대 결과: All tests passed.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `proto/oto/runner.proto` | CICD_RUNNER-1 |
| `services/core/oto/runner.pb.go` | CICD_RUNNER-1 |
| `apps/runner/lib/oto/agent/oto/runner.pb.dart` | CICD_RUNNER-1 |
| `apps/runner/lib/oto/agent/oto/runner.pbjson.dart` | CICD_RUNNER-1 |
| `apps/runner/lib/oto/agent/edge_registration_client.dart` | CICD_RUNNER-2 |
| `apps/runner/lib/oto/agent/agent_runner.dart` | CICD_RUNNER-2 |
| `apps/runner/test/oto_agent_registration_test.dart` | CICD_RUNNER-2 |

## 최종 검증

```bash
make proto
cd services/core && go test -count=1 ./...
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_server_connection_smoke_test.dart
```

기대 결과: 모든 명령 PASS. Go test cache output은 허용하지 않으므로 `-count=1`을 유지한다.

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