<!-- task=m-agent-message-protocol/01_proto_contract plan=0 tag=API -->

# Plan - API

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 필수 단계다. 구현 후 검증을 실행하고 실제 내용과 출력을 리뷰 파일에 남긴 뒤 active 파일을 그대로 둔 채 리뷰 준비를 보고한다. 사용자만 결정할 수 있는 범위 변경, 사용자 소유 외부 환경/secret, 또는 scope conflict가 있으면 리뷰 파일의 `사용자 리뷰 요청`에 근거를 채우고 멈춘다. 구현 중 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

현재 `proto/oto/runner.proto`는 register, heartbeat, job claim, execution report, log append, artifact report를 담고 있지만 run request의 YAML 연결, cancel/status/self-update, 공통 실패 응답 계약이 없다. 마일스톤의 `message-fields` Task는 OTO Server와 runner가 같은 proto 계약으로 병렬 구현을 시작할 수 있어야 하므로 먼저 schema와 생성 코드를 닫는다. 이 작업은 후속 Core 호환성 검증 계획의 선행 작업이다.

## 사용자 리뷰 요청 흐름

구현 중 사용자 결정이 필요한 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 기록한다. 직접 사용자 프롬프트는 금지이며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/message-based-build-agent/milestones/agent-message-protocol.md`
- Task ids:
  - `message-fields`: 각 메시지의 최소 필드와 실패 응답 형식이 정의되어 있다.
- 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-ops/skills/common/update-roadmap/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/message-based-build-agent/PHASE.md`
- `agent-roadmap/phase/message-based-build-agent/milestones/agent-message-protocol.md`
- `agent-test/local/rules.md`
- `agent-test/local/agent-smoke.md`
- `agent-test/local/core-smoke.md`
- `agent-ops/rules/project/domain/agent/rules.md`
- `Makefile`
- `proto/oto/runner.proto`
- `apps/runner/lib/oto/agent/edge_registration_client.dart`
- `apps/runner/lib/oto/agent/oto_server_job_client.dart`
- `apps/runner/lib/oto/agent/agent_config.dart`
- `apps/runner/lib/oto/agent/agent_runner.dart`
- `apps/runner/test/oto_agent_registration_test.dart`
- `apps/runner/test/oto_agent_migration_plan_test.dart`
- `apps/runner/test/oto_server_connection_smoke_test.dart`
- `apps/runner/pubspec.yaml`
- `services/core/go.mod`
- `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/internal/cicdstate/store.go`
- `services/core/internal/cicdstate/store_test.go`

### 테스트 환경 규칙

- 선택한 test_env: `local`.
- `agent-test/local/rules.md`는 존재하고 읽었다.
- matching profile: agent 경로와 runner protocol 변경이므로 `agent-test/local/agent-smoke.md`를 읽었다. Core smoke는 Go Core fallback 판단을 위해 읽었지만 현재 local rules에는 `services/core/**` 라우팅이 없다.
- 적용 명령: agent profile의 `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`.
- fallback 검증: `Makefile`의 `make proto`, `services/core/go.mod` 기준 `cd services/core && go test ./...`.
- 공백: `proto/oto/**`와 `services/core/**`에 대한 별도 agent-test profile이 없다. 이 plan은 fallback 명령을 명시하고, 후속 `create-test/update-test`가 core/proto smoke profile을 보강할 수 있다.

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

- 새 proto 메시지와 generated code: 기존 테스트는 현재 등록/job report proto만 간접 검증한다. `oto_agent_registration_test.dart`에 새 메시지 생성, JSON helper, 기존 request payload 보존 assertion을 추가한다.
- run request YAML 연결 필드: 기존 테스트 없음. 새 assertion으로 `pipeline_yaml_path` 또는 inline YAML 중 하나가 표현되고 command catalog 경계가 남는지 확인한다.
- cancel/status/self-update 메시지: 기존 테스트 없음. proto class 생성과 기본 필드 round-trip assertion을 추가한다.
- 공통 실패 응답: 기존 response들은 `error_message` 문자열만 본다. 새 `ProtocolError` 필드를 추가하되 기존 문자열 필드를 유지하는 회귀 assertion이 필요하다.

### 심볼 참조

- renamed/removed symbols: none. 기존 proto field number와 Dart/Go generated getter 이름은 보존한다.
- 관련 참조: `RegisterRunnerRequest`는 `proto/oto/runner.proto:16`, `apps/runner/lib/oto/agent/edge_registration_client.dart:242`, `services/core/internal/runnerregistry/registry.go:56`, `services/core/internal/httpserver/server.go:91`에서 사용된다. `JobClaimRequest/Response`는 `proto/oto/runner.proto:57`, `apps/runner/lib/oto/agent/oto_server_job_client.dart:80`, `services/core/internal/httpserver/server.go:720` 이후 runner claim handler에서 사용된다.

### 분할 판단

- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `agent-task/m-agent-message-protocol/`.
- `01_proto_contract`: proto schema, generated Go/Dart code, Dart JSON helper/test에서 최소 메시지 필드를 정의한다. 의존성 없음.
- `02+01_core_compatibility`: registry/Core 호환성 검증과 version/capability 협상 기준을 적용한다. `01_proto_contract`의 `complete.log`가 필요하다.
- API/foundation과 broad call-site rollout이 분리되어 있어 split gate가 적용된다. 이 plan은 foundation only다.

### 범위 결정 근거

- 포함: `proto/oto/runner.proto`, generated `services/core/oto/runner.pb.go`, generated `apps/runner/lib/oto/agent/oto/runner.pb.dart|pbenum.dart|pbjson.dart`, Dart request/response JSON helper와 agent registration test.
- 제외: HTTP endpoint behavior, registry validation policy, job cancellation state transition 구현은 `02+01_core_compatibility`에서 다룬다.
- 제외: 웹 UI, artifact storage 운영 정책, scheduler policy, generic remote shell 기능.

### 빌드 등급

- build lane/grade: `cloud-G05`. 공용 protocol/schema 변경이며 생성 코드와 양쪽 언어 compile을 동시에 확인해야 하지만, 구현 범위는 proto foundation과 bounded tests로 제한된다.

## 구현 체크리스트

- [ ] `proto/oto/runner.proto`에 공통 실패 응답, run request, cancel, status, self-update 최소 메시지를 추가하고 기존 field number를 보존한다.
- [ ] `make proto`로 Go/Dart generated protobuf 파일을 재생성한다.
- [ ] Dart JSON helper와 `oto_agent_registration_test.dart`를 새 schema에 맞춰 갱신하되 기존 registration/job report payload를 보존한다.
- [ ] `message-fields` 검증을 위해 YAML 파이프라인/Command catalog가 run request 위에서 어떻게 참조되는지 test assertion 또는 proto comment로 남긴다.
- [ ] 최종 검증 명령을 실행하고 실제 stdout/stderr를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Proto message fields

#### 문제

`proto/oto/runner.proto:25` 이후 response들은 `error_message` 또는 `reject_reason` 문자열만 갖고 있어 실패 코드와 세부 정보를 일관되게 표현하지 못한다. `proto/oto/runner.proto:57`의 `JobClaimRequest`는 `job_id`/`execution_id`만 claim하고, Server-owned run request가 YAML 파이프라인과 command catalog를 어떻게 연결하는지 표현하지 못한다. cancel/status/self-update 메시지도 없다.

#### 해결 방법

기존 field number를 바꾸지 않고 새 field를 append한다. 문자열 오류 필드는 backward compatibility로 유지하고, 새 `ProtocolError`를 추가한다.

```proto
// proto/oto/runner.proto:25 before
message JobClaimResponse {
  bool accepted = 1;
  string error_message = 2;
  string job_id = 3;
  string execution_id = 4;
  string state = 5;
  string runner_id = 6;
}
```

```proto
// proto/oto/runner.proto after
message ProtocolError {
  string code = 1;
  string message = 2;
  map<string, string> details = 3;
}

message RunRequest {
  string runner_id = 1;
  string job_id = 2;
  string execution_id = 3;
  string pipeline_yaml_path = 4;
  string pipeline_yaml = 5;
  map<string, string> variables = 6;
  repeated string command_types = 7;
}

message JobClaimResponse {
  bool accepted = 1;
  string error_message = 2;
  string job_id = 3;
  string execution_id = 4;
  string state = 5;
  string runner_id = 6;
  RunRequest run_request = 7;
  ProtocolError error = 8;
}
```

같은 방식으로 `ExecutionReportResponse`, `LogAppendResponse`, `ArtifactReportResponse`, `HeartbeatResponse`, `RegisterRunnerResponse`에 새 `ProtocolError error`를 append한다. `CancelRunRequest/Response`, `RunnerStatusRequest/Response`, `SelfUpdateRequest/Response`는 최소 identity, state/action, error 필드만 정의하고 동작 구현은 후속 plan에 둔다.

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

- [ ] `proto/oto/runner.proto`: 새 message와 response error field 추가.
- [ ] `services/core/oto/runner.pb.go`: `make proto`로 재생성.
- [ ] `apps/runner/lib/oto/agent/oto/runner.pb.dart`: `make proto`로 재생성.
- [ ] `apps/runner/lib/oto/agent/oto/runner.pbenum.dart`: `make proto`로 재생성.
- [ ] `apps/runner/lib/oto/agent/oto/runner.pbjson.dart`: `make proto`로 재생성.

#### 테스트 작성

`apps/runner/test/oto_agent_registration_test.dart`에 proto class 생성/JSON round-trip assertion을 추가한다. 새 test name: `OTO runner protocol exposes run, cancel, status, self-update message fields`. 기존 generated code import를 사용하고, `RunRequest`가 `jobId`, `executionId`, `pipelineYamlPath` 또는 `pipelineYaml`, `commandTypes`를 표현하는지 확인한다.

#### 중간 검증

```bash
make proto
```

### [API-2] Dart request/response compatibility

#### 문제

`apps/runner/lib/oto/agent/edge_registration_client.dart:303`의 registration JSON helper는 기존 protocol/capability/catalog payload만 보낸다. `apps/runner/lib/oto/agent/oto_server_job_client.dart:219` 이후 helper들은 job/report/log/artifact request를 기존 필드만으로 만들며 새 run request/error field와의 backward compatibility를 검증하지 않는다.

#### 해결 방법

기존 payload key를 유지한다. 새 proto 필드가 response에 들어와도 기존 result parser가 깨지지 않게 `error_message` fallback을 유지하고, 필요하면 `ProtocolError` helper를 추가한다.

```dart
// apps/runner/lib/oto/agent/oto_server_job_client.dart:24 before
errorMessage: _emptyToNull(
  (json['error_message'] ?? json['errorMessage'] ?? '').toString(),
),
```

```dart
// after
errorMessage: _emptyToNull(
  (json['error_message'] ??
          json['errorMessage'] ??
          (json['error'] is Map ? json['error']['message'] : '') ??
          '')
      .toString(),
),
```

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

- [ ] `apps/runner/lib/oto/agent/oto_server_job_client.dart`: 새 error object가 와도 기존 `errorMessage`가 채워지게 한다.
- [ ] `apps/runner/lib/oto/agent/edge_registration_client.dart`: registration payload 기존 key와 capability/catalog 출력이 변하지 않는지 유지한다.
- [ ] `apps/runner/test/oto_agent_registration_test.dart`: 기존 registration/job report payload assertion과 새 response error object fallback assertion을 함께 둔다.

#### 테스트 작성

`apps/runner/test/oto_agent_registration_test.dart`의 `job client claims jobs and reports build results` group에 error object fallback 케이스를 추가한다. 정상 payload assertion은 유지한다.

#### 중간 검증

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

## 수정 파일 요약

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

## 최종 검증

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

기대 결과: proto generation이 완료되고 Dart analyzer issue가 없으며 지정 Dart test와 Go test가 통과한다. 모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.
