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

# Plan - API

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

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

## 배경

OTO Server와 Dart job client에는 artifact report API가 이미 있다. 하지만 `Application.build` 결과에는 artifact 목록이 없고, remote job loop가 빌드 결과에서 artifact event를 자동 보고하는 기준도 없다. 이 계획은 기존 서버 API를 유지하면서 runner 결과 모델과 remote loop에 artifact event 전달 흐름을 붙인다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 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:
  - `artifact-event`: artifact event와 최종 artifact metadata가 원격 소비자에게 전달되는 흐름이 정의되어 있다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-task/m-remote-run-lifecycle/01_run_request_pipeline/PLAN-cloud-G07.md`
- `agent-task/m-remote-run-lifecycle/02+01_step_log_stream/PLAN-local-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`
- `services/core/internal/httpserver/server.go`
- `apps/runner/lib/oto/agent/agent_runner.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/pipeline/pipeline.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_core_test.dart`

### 테스트 환경 규칙

- test_env: `local`.
- 읽은 프로파일: `agent-smoke`, `core-smoke`, `pipeline-smoke`.
- 직접 변경 대상은 Dart runner/core/pipeline이다.
- 서버 artifact endpoint는 이미 존재하므로 Go 변경은 기본 범위가 아니다. 서버 응답 계약이 부족한 것이 발견되면 계획 대비 변경에 기록하고 `cd services/core && go test ./...`를 최종 검증에 추가한다.
- 사전 검증으로 Dart agent/core/pipeline/server smoke 테스트가 통과했다.

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

- `BuildResult`는 success/exitCode/error/stepEvents만 직렬화하고 artifact metadata를 담지 않는다.
- `OtoServerJobClient.reportArtifact`는 단건 보고만 제공하며 build result의 artifacts 목록을 일괄 보고하지 않는다.
- server smoke는 수동 artifact report를 검증하지만, remote job loop가 build 결과 artifact를 보고하는 end-to-end 테스트가 없다.

### 심볼 참조

- renamed/removed symbol: none.
- 변경 후보 call site: `BuildResult.success`, `BuildResult.failure`, `BuildResult.toJson`, `OtoServerJobClient.reportArtifact`, 후속 `02`의 remote loop executor.

### 분할 판단

- split decision policy를 먼저 평가했다.
- 이 subtask는 `03+01,02_artifact_event`이므로 predecessor `01`, `02`가 필요하다.
- 현재 active `agent-task/m-remote-run-lifecycle/01_run_request_pipeline/complete.log`와 `agent-task/m-remote-run-lifecycle/02+01_step_log_stream/complete.log`가 없고 archive predecessor도 확인하지 않았다. predecessor `01`: missing, predecessor `02`: missing.
- `01`은 claim/run request 계약을 제공하고, `02`는 remote loop의 claim/execution/report 흐름을 제공한다. artifact event는 그 흐름에 붙어야 하므로 추가 숨은 의존성을 두지 않는다.

### 범위 결정 근거

- 이 계획은 artifact metadata 모델, artifact 선언 파싱, remote loop의 reportArtifact 호출까지 다룬다.
- artifact 파일 업로드/보관소 구현은 현재 server API가 path metadata만 받으므로 범위 밖이다.
- cancel/status/self-update는 `04+01,02_cancel_status_update`로 분리한다.

### 빌드 등급

- build: `local-G06`. Dart 모델과 runner loop 보강이며 기존 server artifact API를 재사용한다.
- review: `local-G06`. predecessor 산출물과 server smoke 결과를 확인해야 한다.

## 구현 체크리스트

- [ ] `01_run_request_pipeline`과 `02+01_step_log_stream`의 `complete.log`가 존재하는지 확인하고 없으면 구현을 시작하지 않는다.
- [ ] artifact metadata는 name/path 필수, path는 빈 값 금지로 검증한다.
- [ ] `BuildResult`가 artifact 목록을 보존하고 `toJson()`에 포함한다.
- [ ] remote loop가 build 성공/실패와 별개로 산출된 artifact 목록을 서버에 보고한다.
- [ ] artifact report 실패는 execution report와 구분되는 명확한 실패 결과 또는 로그로 남긴다.
- [ ] agent/core/pipeline/server smoke 테스트를 갱신한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

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

### [API-1] BuildResult Artifact Model

문제: [apps/runner/lib/oto/core/build_result.dart](/config/workspace/oto/apps/runner/lib/oto/core/build_result.dart:3)의 `BuildResult`는 artifact metadata를 보존하지 않는다.

Before:

```dart
class BuildResult {
  final bool success;
  final int exitCode;
  final Object? error;
  final StackTrace? stackTrace;
  final List<StepEvent> stepEvents;
```

해결 방법: `BuildArtifact` value class를 추가하고 `BuildResult.success`/`failure`에 `artifacts = const []` optional parameter를 둔다. `toJson()`에는 `artifacts` 배열을 포함한다. 기존 생성자 호출은 모두 source compatible하게 유지한다.

수정 파일 및 체크리스트:

- [ ] `apps/runner/lib/oto/core/build_result.dart`: `BuildArtifact`와 `BuildResult.artifacts` 추가.
- [ ] `apps/runner/test/oto_core_test.dart` 또는 `apps/runner/test/oto_application_test.dart`: `toJson()` artifact serialization 테스트 추가.

중간 검증:

```bash
dart test test/oto_core_test.dart test/oto_application_test.dart
```

### [API-2] Artifact Declaration Extraction

문제: YAML pipeline 실행 결과에서 어떤 파일을 artifact로 보고할지에 대한 runner-side 기준이 없다.

해결 방법: remote executor가 build YAML의 `property.artifacts`를 artifact 선언으로 인정한다. 형식은 list of map이며 각 entry는 `name`, `path`를 가진다. `path`는 `Application.defaultWorkspace` 기준 상대 경로 또는 workspace-confined 절대 경로만 허용한다. 유효하지 않은 artifact 선언은 build 실패로 처리해 조용히 누락하지 않는다.

예시:

```yaml
property:
  artifacts:
    - name: smoke-report
      path: dist/report.json
```

수정 파일 및 체크리스트:

- [ ] `apps/runner/lib/oto/agent/remote_run_executor.dart`: artifact declaration parser 추가.
- [ ] `apps/runner/lib/oto/application.dart`: 필요 시 build result 생성 시 artifact 목록을 받을 수 있는 내부 hook 추가.
- [ ] `apps/runner/test/oto_agent_registration_test.dart`: artifact parser 정상/오류/path confinement 테스트 추가.

중간 검증:

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

### [API-3] Remote Artifact Reporting

문제: [apps/runner/lib/oto/agent/oto_server_job_client.dart](/config/workspace/oto/apps/runner/lib/oto/agent/oto_server_job_client.dart:151)는 단건 `reportArtifact`만 제공하고 remote loop가 자동 호출하지 않는다.

해결 방법: `OtoServerJobClient.reportArtifacts` helper를 추가하고 remote loop가 `BuildResult.artifacts`를 순서대로 보고한다. 보고 순서는 execution report 이후로 고정한다. artifact report 실패는 log append 후 execution failure로 재보고할 수 없으므로, 실패를 throw하고 caller test에서 HTTP 오류가 드러나게 한다.

수정 파일 및 체크리스트:

- [ ] `apps/runner/lib/oto/agent/oto_server_job_client.dart`: `reportArtifacts` 추가.
- [ ] `apps/runner/lib/oto/agent/remote_run_executor.dart`: build result artifacts 보고 연결.
- [ ] `apps/runner/test/oto_server_connection_smoke_test.dart`: inline YAML job이 artifact metadata를 server artifacts endpoint에 남기는 smoke 추가.

중간 검증:

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

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/runner/lib/oto/core/build_result.dart` | API-1 |
| `apps/runner/lib/oto/agent/remote_run_executor.dart` | API-2, API-3 |
| `apps/runner/lib/oto/application.dart` | API-2 |
| `apps/runner/lib/oto/agent/oto_server_job_client.dart` | API-3 |
| `apps/runner/test/oto_core_test.dart` | API-1 |
| `apps/runner/test/oto_application_test.dart` | API-1, API-2 |
| `apps/runner/test/oto_agent_registration_test.dart` | API-2 |
| `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으로 종료한다.

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