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

# Plan - REVIEW_API2

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

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

## 배경

두 번째 리뷰에서 server next-claim, shutdown, parse failure report는 대체로 충족됐지만, remote variables merge가 ad hoc YAML 문자열 삽입으로 구현되어 일부 유효한 YAML property 형태를 깨뜨릴 수 있고, 테스트도 실제 `<!property.FLAVOR>` 치환 결과를 assertion으로 고정하지 못했다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 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

## 이전 루프

- Archived plan: `agent-task/m-remote-run-lifecycle/02+01_step_log_stream/plan_cloud_G07_1.log`
- Archived review: `agent-task/m-remote-run-lifecycle/02+01_step_log_stream/code_review_cloud_G07_1.log`
- Verdict: FAIL

## 범위 결정 근거

- Go server next-job claim과 runner shutdown/report 구현은 이번 follow-up 범위에서 다시 넓히지 않는다.
- remote variables merge와 그 검증만 수정한다.
- YAML 입력은 structured data이므로, 문자열 삽입보다 YAML parse 결과 map을 기준으로 병합하는 방식을 우선한다.

## 빌드 등급

- build: `cloud-G07`. 반복된 verification trust 이슈가 있으므로 같은 lane에서 증거를 회복한다.
- review: `cloud-G07`. 실제 YAML 입력 다양성과 테스트 assertion 신뢰성을 다시 본다.

## 구현 체크리스트

- [ ] remote variables merge가 YAML 문자열 삽입이 아니라 parsed build map/property map 기준으로 동작하도록 고친다.
- [ ] inline property map 같은 유효한 YAML property 형태에서도 variables override가 성공하는 테스트를 추가한다.
- [ ] variables merge smoke가 `<!property.FLAVOR>`의 실제 결과값이 `release`임을 assertion으로 검증한다.
- [ ] Dart 검증과 CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 상세 구현 항목

### [REVIEW_API2-1] Structured Variables Merge

문제: `apps/runner/lib/oto/agent/remote_run_executor.dart:195`가 `property:` 라인을 문자열로 찾고 `remote_run_executor.dart:211`에서 변수 라인을 삽입한다. `property: {workspace: ., FLAVOR: original}` 같은 유효한 inline map YAML은 삽입 후 잘못된 YAML이 된다.

해결 방법: `Application.getMapFromYamlA()` 또는 `yaml` parser 결과를 사용해 root map과 `property` map을 얻고, `variables`를 `property`에 overlay한다. 다시 build 입력으로 넘길 때는 기존 `Application.build` 경로가 읽을 수 있는 YAML 문자열 또는 `DataBuild`/override 경로를 사용한다. 이때 기존 block-style property, property 없음, inline property map을 모두 처리한다.

체크리스트:
- [ ] block-style property에서 remote variables가 override한다.
- [ ] property가 없으면 property map을 만든다.
- [ ] inline property map에서도 remote variables가 override하고 build가 성공한다.

중간 검증:

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

### [REVIEW_API2-2] Assert Actual Variable Substitution

문제: `apps/runner/test/oto_server_connection_smoke_test.dart:784`의 smoke는 성공 여부와 step events만 검증한다. `FLAVOR`가 기존 YAML 값 `original`로 남아도 테스트가 통과할 수 있다.

해결 방법: variables merge 테스트에서 실제 관찰 가능한 값을 assertion한다. 선택지는 `Application.instance.property['FLAVOR'] == 'release'`, stdout capture에 `release`가 있고 `original`이 없음을 확인, 또는 테스트용 command 결과를 property에 써서 검증하는 방식이다. 단순 성공/stepEvents/log success만으로 대체하지 않는다.

체크리스트:
- [ ] `release` 값이 실제 pipeline property/tag 치환 결과로 사용됐음을 assertion한다.
- [ ] `original`이 그대로 사용되는 회귀가 테스트를 실패시킨다.
- [ ] 검증 결과에 실제 stdout/stderr를 기록한다.

중간 검증:

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

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/runner/lib/oto/agent/remote_run_executor.dart` | REVIEW_API2-1 |
| `apps/runner/test/oto_agent_registration_test.dart` | REVIEW_API2-1 |
| `apps/runner/test/oto_server_connection_smoke_test.dart` | REVIEW_API2-1, REVIEW_API2-2 |

## 최종 검증

```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으로 종료한다. Go server 코드를 건드리지 않았다면 Go 테스트 재실행은 선택 사항이지만, 건드렸다면 `cd services/core && go test ./internal/httpserver`를 추가하고 실제 출력도 review stub에 기록한다.
