<!-- task=step_events/02+01_execution_result_output plan=0 tag=STEP_EVENT_OUTPUT -->

# STEP_EVENT_OUTPUT - executionResult에 step event 노출

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

구현 시작 전 같은 task group의 `01_core_event_contract`가 PASS되어 `complete.log`를 가진 상태인지 확인한다. 구현 완료 전 `CODE_REVIEW-cloud-G07.md`의 구현 에이전트 소유 섹션을 반드시 채우고, active 파일은 제자리에 둔 채 리뷰 준비 상태를 보고한다. 판정과 archive 이동은 code-review-skill 전용이다.

## 배경

선행 작업은 step event를 context에 수집한다. 이 작업은 그 event를 외부 자동화가 소비할 수 있도록 `BuildResult.toJson()`과 `oto exe --json`의 최종 JSON envelope에 노출한다. live streaming은 별도 설계가 필요한 범위라 이번 계획에서는 최종 실행 결과 envelope에 event history를 포함하는 데 집중한다.

## 의존 관계 및 구현 순서

- 이 디렉터리 이름 `02+01_execution_result_output`의 `+01`이 런타임 의존성의 source of truth다.
- `agent-task/step_events/01_core_event_contract/complete.log`가 생긴 뒤 구현을 시작한다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/core/rules.md`
- `agent-ops/rules/project/domain/cli/rules.md`
- `agent-ops/rules/project/domain/pipeline/rules.md`
- `agent-ops/roadmap/current.md`
- `agent-ops/roadmap/milestones/structured-automation-surface.md`
- `pubspec.yaml`
- `lib/oto/core/execution_context.dart`
- `lib/oto/core/build_result.dart`
- `lib/oto/application.dart`
- `lib/cli/commands/command_exe.dart`
- `lib/oto/pipeline/pipeline.dart`
- `lib/oto/pipeline/pipeline_exe.dart`
- `lib/oto/pipeline/pipeline_exe_handle.dart`
- `test/oto_application_test.dart`
- `test/oto_core_test.dart`
- `test/oto_context_test.dart`

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

- `BuildResult.toJson()`의 `stepEvents` 필드: 기존 테스트는 `schemaVersion`, `type`, `success`, `exitCode`, `message`, `error`만 검증한다.
- `Application.build()`가 context event를 result에 전달하는지: 기존 성공/실패 테스트는 result event를 보지 않는다.
- `CommandExe --json` actual bin 출력이 step event를 포함하는지: 기존 테스트는 최종 JSON이 parseable하고 사람이 읽는 로그가 없는지만 확인한다.

### 심볼 참조

- renamed/removed symbol 없음. `BuildResult` 생성 호출은 `lib/oto/application.dart`, `lib/cli/commands/command_exe.dart`, `test/oto_application_test.dart`에 있다.

### 분할 판단

- split decision policy를 먼저 평가했다.
- 공유 작업 그룹: `step_events`
- 선행 `01_core_event_contract`: `StepEvent` 모델과 context event 수집.
- 현재 `02+01_execution_result_output`: 수집된 event를 result/CLI JSON에 노출하고 로드맵 체크를 최신화.
- 분할 이유: 이 작업은 선행 event 수집 API 없이는 구현할 수 없고, 검증은 bin-level JSON 출력과 execution envelope에 집중된다.

### 범위 결정 근거

- `--json`의 단일 최종 JSON 출력 방식은 유지한다. 이 작업은 `stepEvents` 배열을 추가할 뿐 `--jsonl`이나 streaming event 출력은 만들지 않는다.
- scheduler 로그 파일, Edge agent 메시지 프로토콜, command별 결과 payload는 제외한다.
- 선행 작업에서 만든 `StepEvent` 필드명을 변경해야 한다면 `계획 대비 변경 사항`에 이유를 남긴다.

### 빌드 등급

- build lane/grade: `cloud-G07`
- review lane/grade: `cloud-G07`
- 근거: 실행 결과 JSON schema와 CLI stdout 계약을 바꾸는 terminal-agent/API 성격 작업이다.

## 구현 체크리스트

- [ ] 선행 `01_core_event_contract`의 `complete.log` 존재를 확인한다.
- [ ] `BuildResult`가 `stepEvents`를 받아 `toJson()`에 포함하도록 확장한다.
- [ ] `Application.build()` 성공/실패 반환 시 `context.stepEvents`를 전달한다.
- [ ] `CommandExe --json` actual bin 테스트가 `stepEvents` 배열을 검증하도록 갱신한다.
- [ ] 구조화된 자동화 표면 Milestone 문서의 완료 체크를 코드 근거에 맞게 갱신한다.
- [ ] 중간 검증과 최종 검증 명령을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [STEP_EVENT_OUTPUT-1] BuildResult JSON envelope 확장

### 문제

[build_result.dart](/config/workspace/oto/lib/oto/core/build_result.dart:1)의 `BuildResult`는 성공 여부와 error만 JSON으로 낸다. 선행 작업의 `context.stepEvents`가 있어도 [Application.build](/config/workspace/oto/lib/oto/application.dart:159)와 [Application.build](/config/workspace/oto/lib/oto/application.dart:165)가 result에 전달할 경로가 없다.

```dart
// lib/oto/core/build_result.dart:20
Map<String, dynamic> toJson() => {
      'schemaVersion': 1,
      'type': 'executionResult',
      'success': success,
      'exitCode': exitCode,
      'message': message,
      'error': success
          ? null
          : {
              'type': error.runtimeType.toString(),
              'message': message,
            },
    };
```

### 해결 방법

`BuildResult`에 `List<StepEvent> stepEvents`를 추가하고 기본값은 빈 리스트로 둔다. `toJson()`에 `stepEvents: stepEvents.map((e) => e.toJson()).toList()`를 추가한다. `Application.build()`의 성공/실패 반환은 `stepEvents: context.stepEvents`를 넘긴다.

```dart
return BuildResult.failure(e, stacktrace, stepEvents: context.stepEvents);
...
return BuildResult.success(stepEvents: context.stepEvents);
```

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

- [ ] `lib/oto/core/build_result.dart`: `StepEvent` import, field, constructor parameter, JSON field 추가.
- [ ] `lib/oto/application.dart`: success/failure result 생성 시 `context.stepEvents` 전달.

### 테스트 작성

- 작성: `test/oto_application_test.dart`
  - `BuildResult.toJson exposes empty step events by default`
  - `file build returns step events in execution result`

### 중간 검증

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

예상 결과: 모든 테스트 통과.

## [STEP_EVENT_OUTPUT-2] exe --json 출력 계약 검증

### 문제

[command_exe.dart](/config/workspace/oto/lib/cli/commands/command_exe.dart:110)는 isolate가 보낸 result JSON을 그대로 출력한다. 기존 [oto_application_test.dart](/config/workspace/oto/test/oto_application_test.dart:585)의 actual bin 테스트는 parseable JSON과 로그 억제만 검증하며 `stepEvents`를 확인하지 않는다.

```dart
// lib/cli/commands/command_exe.dart:110
if (isJson && _resultJson != null) {
  await _printString(const JsonEncoder.withIndent('  ').convert(_resultJson));
}
```

### 해결 방법

`CommandExe` 자체는 선행/이전 구조를 유지한다. `IsolateExe`가 `result.toJson()`을 보내므로, `BuildResult.toJson()` 확장만으로 CLI JSON에 `stepEvents`가 포함된다. 테스트에서 `stepEvents` 배열과 첫 step의 `started/completed` event를 검증한다.

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

- [ ] `test/oto_application_test.dart`: actual bin `exe --json` 테스트에 `stepEvents` shape 검증 추가.
- [ ] `test/oto_application_test.dart`: missing file `--json`은 `stepEvents`가 빈 배열임을 검증.

### 테스트 작성

- 작성: 기존 `CommandExe JSON Mode` group 안에 assertion 추가. 성공 YAML의 `stepEvents`는 2개 이상이고, `stepEvents[0].type == 'stepEvent'`, `event == 'started'`, `commandId == 'hello'`, 마지막 event가 `completed`인지 검증한다.

### 중간 검증

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

예상 결과: 모든 테스트 통과.

## [STEP_EVENT_OUTPUT-3] Milestone 완료 상태 갱신

### 문제

[structured-automation-surface.md](/config/workspace/oto/agent-ops/roadmap/milestones/structured-automation-surface.md:27)는 실행 결과 envelope를 아직 미완료로 표시하지만 현재 코드에는 `executionResult` envelope가 구현되어 있다. step event까지 노출되면 이 Milestone의 남은 체크박스를 코드 근거에 맞게 갱신해야 한다.

### 해결 방법

구현과 테스트가 끝난 뒤 `실행 결과 envelope`, `step event`, 대응 완료 기준을 `[x]`로 바꾸고 괄호 안에 `BuildResult.toJson`, `oto exe --json`, `stepEvents` 근거를 짧게 남긴다.

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

- [ ] `agent-ops/roadmap/milestones/structured-automation-surface.md`: 완료된 필수 기능과 완료 기준 체크 갱신.

### 테스트 작성

- 문서 변경이므로 별도 테스트는 작성하지 않는다. 대신 최종 검증의 `rg --sort path`로 문서와 코드의 `stepEvents` 근거를 확인한다.

### 중간 검증

```bash
rg --sort path -n "stepEvents|executionResult|step event" lib test agent-ops/roadmap/milestones/structured-automation-surface.md
```

예상 결과: 코드, 테스트, Milestone 문서에 step event/output 근거가 함께 나타난다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `lib/oto/core/build_result.dart` | STEP_EVENT_OUTPUT-1 |
| `lib/oto/application.dart` | STEP_EVENT_OUTPUT-1 |
| `test/oto_application_test.dart` | STEP_EVENT_OUTPUT-1, STEP_EVENT_OUTPUT-2 |
| `agent-ops/roadmap/milestones/structured-automation-surface.md` | STEP_EVENT_OUTPUT-3 |

## 최종 검증

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

예상 결과: analyze issue 없음, 모든 테스트 통과. Dart test cache 출력은 허용한다.

```bash
rg --sort path -n "stepEvents|executionResult|step event" lib test agent-ops/roadmap/milestones/structured-automation-surface.md
```

예상 결과: execution result JSON, step event tests, Milestone 근거가 일관되게 검색된다.

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