<!-- task=m-remote-run-lifecycle/02+01_step_log_stream plan=0 tag=API -->

# Plan - API

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

구현 완료의 마지막 단계는 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것이다. 검증 명령을 실행하고 실제 출력, 계획 대비 변경, 주요 설계 결정을 기록한 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 구현 중 사용자 전용 결정, 사용자 소유 환경/secret, 범위 충돌이 생기면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 근거를 남기고 중단한다. 구현 에이전트는 사용자에게 직접 묻거나 선택지를 제시하거나 `request_user_input`을 호출하거나, `USER_REVIEW.md`/`complete.log`를 만들거나, 로그 아카이브를 수행하지 않는다.

## 배경

서버와 Dart client에는 claim/report/log API 조각이 있지만, `DefaultAgentRunner`의 기본 실행 경로는 등록 후 job loop를 실행하지 않는다. `Application.build(BuildType.file, yamlContent: ...)`는 기존 YAML 파이프라인 실행과 step event 결과를 이미 제공한다. 이 계획은 claim된 `RunRequest`를 YAML 실행 입력으로 변환하고 결과와 step events를 OTO Server에 보고하는 runner loop를 만든다.

## 사용자 리뷰 요청 흐름

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

## Roadmap Targets

- Milestone: `agent-roadmap/phase/message-based-build-agent/milestones/remote-run-lifecycle.md`
- Task ids:
  - `run-input`: run request가 pipeline 실행 입력으로 변환되는 기준이 정의되어 있다.
  - `step-log`: step event와 log stream이 원격 소비자에게 전달되는 흐름이 정의되어 있다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-task/m-remote-run-lifecycle/01_run_request_pipeline/PLAN-cloud-G07.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/rules/project/domain/agent/rules.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `agent-ops/rules/project/domain/pipeline/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/agent-smoke.md`
- `agent-test/local/core-smoke.md`
- `agent-test/local/pipeline-smoke.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/message-based-build-agent/PHASE.md`
- `agent-roadmap/phase/message-based-build-agent/milestones/remote-run-lifecycle.md`
- `apps/runner/pubspec.yaml`
- `apps/runner/lib/oto/agent/agent_runner.dart`
- `apps/runner/lib/oto/agent/edge_registration_client.dart`
- `apps/runner/lib/oto/agent/oto_server_job_client.dart`
- `apps/runner/lib/oto/application.dart`
- `apps/runner/lib/oto/core/build_result.dart`
- `apps/runner/lib/oto/core/data_composer.dart`
- `apps/runner/lib/oto/core/execution_context.dart`
- `apps/runner/lib/oto/pipeline/pipeline.dart`
- `apps/runner/lib/cli/commands/command_exe.dart`
- `apps/runner/test/oto_agent_registration_test.dart`
- `apps/runner/test/oto_server_connection_smoke_test.dart`
- `apps/runner/test/oto_application_test.dart`
- `apps/runner/test/oto_context_test.dart`
- `apps/runner/test/oto_core_test.dart`

### 테스트 환경 규칙

- test_env: `local`.
- 읽은 프로파일: `agent-smoke`, `core-smoke`, `pipeline-smoke`.
- 필수 baseline: `dart analyze`, agent 관련 `dart test`, core 관련 `dart test`, pipeline 영향 시 `dart test` 전체 또는 관련 회귀.
- Go service는 이 plan의 직접 변경 대상이 아니며, `01` 완료 결과를 전제로 한다.
- 사전 검증으로 `dart test test/oto_agent_registration_test.dart test/oto_application_test.dart test/oto_context_test.dart test/oto_core_test.dart`와 `dart test test/oto_server_connection_smoke_test.dart`가 통과했다.

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

- `AgentRunner`는 주입된 `runJobs` 훅 실행만 테스트하고 기본 remote job loop는 없다.
- `OtoServerJobClient`는 claim/report/log/artifact 호출만 테스트하며, claim result의 `RunRequest`를 실행하지 않는다.
- `Application.build`의 step events는 테스트되지만 OTO Server report payload로 자동 전송되는 end-to-end 테스트가 없다.
- log stream은 현재 명시 `appendLog` 호출만 검증하고, runner 실행 시작/완료 로그와 BuildResult message 보고 흐름은 자동화되어 있지 않다.

### 심볼 참조

- renamed/removed symbol: none.
- 변경 후보 call site: `DefaultAgentRunner` 생성은 agent CLI와 테스트에서 간접 사용된다. `OtoServerJobSession.jobs`는 `oto_agent_registration_test.dart`와 `oto_server_connection_smoke_test.dart`에서 확인했다.

### 분할 판단

- split decision policy를 먼저 평가했다.
- 이 subtask는 `02+01_step_log_stream`이므로 predecessor `01`이 필요하다.
- 현재 active `agent-task/m-remote-run-lifecycle/01_run_request_pipeline/complete.log`가 없고 archive predecessor도 확인하지 않았다. predecessor `01`: missing.
- `01`이 claim response에 `RunRequest`를 보장해야 이 plan을 구현할 수 있다.

### 범위 결정 근거

- 이 계획은 runner가 claim한 run request를 실행하고 logs/step events/result를 보고하는 데 집중한다.
- artifact 자동 추출/보고는 `03+01_artifact_event`로 분리한다.
- cancel/status/self-update는 `04+01_cancel_status_update`로 분리한다.
- server claim 계약 변경은 `01_run_request_pipeline`의 완료 결과를 사용한다.

### 빌드 등급

- build: `local-G07`. Dart runner/core/pipeline 경계가 넓지만 기존 `Application.build`와 deterministic tests로 검증 가능하다.
- review: `cloud-G06`. 서버 계약 선행 여부와 runner loop 증거를 함께 확인해야 한다.

## 구현 체크리스트

- [ ] `01_run_request_pipeline`의 `complete.log`가 존재하는지 확인하고 없으면 구현을 시작하지 않는다.
- [ ] `RunRequest`를 inline YAML 또는 workspace-confined file path 중 하나로 해석하고 variables를 pipeline property 입력으로 병합한다.
- [ ] runner job loop가 claim -> 실행 -> log append -> execution report 순서를 보장한다.
- [ ] `BuildResult.stepEvents`가 `ExecutionReportRequest.step_events`와 `execution_result.stepEvents`에 보존된다. 검증: OTO Server에서 요청한 빌드/배포 작업의 시작, 진행, 완료, 실패, 취소 상태를 추적할 수 있다.
- [ ] agent/core/pipeline 테스트와 server connection smoke를 갱신한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- Runtime dependency: sibling `01_run_request_pipeline` must produce `complete.log`.
- 이 plan은 `agent-task/m-remote-run-lifecycle/02+01_step_log_stream` 이름의 `+01` 의존성만 가진다. 추가 의존성을 plan 본문에 숨기지 않는다.

### [API-1] RunRequest To DataBuild Input

문제: [apps/runner/lib/oto/application.dart](/config/workspace/oto/apps/runner/lib/oto/application.dart:76)는 `BuildType.file`에서 YAML 문자열 실행은 지원하지만 remote variables 병합과 workspace-confined path 해석 기준은 없다.

Before:

```dart
// apps/runner/lib/oto/application.dart:76
Future<BuildResult> build(BuildType buildType,
    {String? yamlContent,
    DataBuild? buildData,
    bool logEnable = true,
    String? logPath}) async {
```

해결 방법: 새 helper `RemoteRunInput` 또는 `OtoRemoteRunExecutor`를 만들어 `oto.RunRequest`를 검증한다. `pipelineYaml`이 있으면 inline을 우선하고, `pipelineYamlPath`는 `AgentRuntimeConfig.workspaceRoot` 아래의 정규화된 파일만 허용한다. `variables`는 parsed YAML map의 `property`에 병합한 뒤 기존 `DataBuild.fromJson`/`Application.build` 경로를 재사용한다. 필요하면 `BuildType.remote`를 추가해 `DataBuild`를 log file 없이 실행한다.

수정 파일 및 체크리스트:

- [ ] `apps/runner/lib/oto/agent/remote_run_executor.dart` 추가 또는 기존 agent 파일에 작게 포함한다.
- [ ] `apps/runner/lib/oto/application.dart`: `BuildType.remote`가 필요하면 최소 분기 추가.
- [ ] `apps/runner/test/oto_agent_registration_test.dart`: inline YAML, path confinement, variables merge 테스트 추가.

테스트 작성: 정상 inline YAML, both inline/path rejection, path traversal rejection, variables property merge 테스트를 추가한다.

중간 검증:

```bash
dart test test/oto_agent_registration_test.dart test/oto_application_test.dart
```

### [API-2] Remote Job Loop

문제: [apps/runner/lib/oto/agent/agent_runner.dart](/config/workspace/oto/apps/runner/lib/oto/agent/agent_runner.dart:63)는 `_runJobs`가 주입된 경우만 실행한다. 기본 runner는 등록 후 remote job을 claim하지 않는다.

Before:

```dart
// apps/runner/lib/oto/agent/agent_runner.dart:63
if (_runJobs != null) {
  await _runJobs(config, session);
}
```

해결 방법: `OtoRemoteRunLoop`를 기본 job loop로 연결한다. loop는 accepted `OtoServerJobSession`에서만 동작하고, `claimNextJob()` 또는 `claimJob()` 결과가 empty/no job이면 interval 후 재시도한다. 테스트 가능한 `runOnce`를 제공해 infinite loop 없이 검증한다.

수정 파일 및 체크리스트:

- [ ] `apps/runner/lib/oto/agent/agent_runner.dart`: default run loop 주입 또는 생성.
- [ ] `apps/runner/lib/oto/agent/oto_server_job_client.dart`: `claimNextJob()`와 no-job response 파싱 추가.
- [ ] `apps/runner/test/oto_agent_registration_test.dart`: default runner가 run loop를 호출하고 session close를 유지하는 테스트 수정.

테스트 작성: fake client/server로 `claim -> execute -> report` 순서와 no-job no-op을 검증한다.

중간 검증:

```bash
dart test test/oto_agent_registration_test.dart
```

### [API-3] Step Events And Logs Report

문제: [apps/runner/lib/oto/pipeline/pipeline.dart](/config/workspace/oto/apps/runner/lib/oto/pipeline/pipeline.dart:77)는 started/completed/failed step events를 기록하지만, [apps/runner/lib/oto/agent/oto_server_job_client.dart](/config/workspace/oto/apps/runner/lib/oto/agent/oto_server_job_client.dart:107)의 report는 호출자가 직접 불러야 한다.

Before:

```dart
// apps/runner/lib/oto/agent/oto_server_job_client.dart:107
Future<ExecutionReportResult> reportExecution({
  required String jobId,
  required String executionId,
  required BuildResult result,
}) async {
```

해결 방법: remote loop가 실행 시작 로그를 `appendLog`로 보내고, `Application.build`가 반환한 `BuildResult`를 항상 `reportExecution`으로 보낸다. 실패 결과도 success=false, exitCode=10, stepEvents 포함으로 보고한다. report message는 server가 기존처럼 execution log에 append한다.

수정 파일 및 체크리스트:

- [ ] `apps/runner/lib/oto/agent/remote_run_executor.dart`: 시작/완료/실패 로그와 report 순서 구현.
- [ ] `apps/runner/test/oto_server_connection_smoke_test.dart`: 실제 Go server와 Dart runner loop가 inline YAML job을 실행하고 logs/stepEvents/result를 확인하는 smoke 추가.

테스트 작성: smoke에서 `POST /api/v1/jobs`에 inline YAML run_request를 넣고 runner `runOnce`가 job state succeeded, logs, step_events를 남기는지 확인한다.

중간 검증:

```bash
dart test test/oto_server_connection_smoke_test.dart
```

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/runner/lib/oto/agent/remote_run_executor.dart` | API-1, API-2, API-3 |
| `apps/runner/lib/oto/agent/agent_runner.dart` | API-2 |
| `apps/runner/lib/oto/agent/oto_server_job_client.dart` | API-2, API-3 |
| `apps/runner/lib/oto/application.dart` | API-1 |
| `apps/runner/test/oto_agent_registration_test.dart` | API-1, API-2 |
| `apps/runner/test/oto_application_test.dart` | API-1 |
| `apps/runner/test/oto_server_connection_smoke_test.dart` | API-3 |

## 최종 검증

```bash
cd apps/runner && dart analyze
cd apps/runner && dart test test/oto_agent_registration_test.dart test/oto_application_test.dart test/oto_context_test.dart test/oto_core_test.dart test/oto_server_connection_smoke_test.dart
```

예상 결과: 모든 명령이 exit code 0으로 종료한다. Dart test cache 개념은 적용하지 않는다.

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