<!-- task=m-backtest-scenario-automation/03+01,02_result_handoff plan=1 tag=REVIEW_TEST -->

# Plan - REVIEW_TEST Result Handoff Contract Repair

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

이 plan의 구현이 끝나면 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 검증 출력, 계획 대비 변경 사항으로 채운다. 구현 중 사용자 결정, 사용자 소유 외부 환경, 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정 필요 내용과 증거를 남기고 멈춘다. 사용자에게 직접 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 명령 재실행이나 산출물 수집으로 해소 가능한 evidence 공백은 사용자 리뷰 요청이 아니다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md`
- Task ids:
  - `result-handoff`: 성공, 실패, timeout, expectation mismatch 결과를 fixture와 `headless_validation.md`에 남겨 후속 운영 표면이 소비할 수 있게 한다.
- Completion mode: check-on-pass

## 배경

첫 리뷰에서 `backtest_matrix_mismatch` expected JSONL이 현재 matrix runner가 실제로 낼 수 없는 `status=mismatch`/`exit_code=1`을 약속하는 문제가 발견됐다. 또한 timeout expected JSONL은 `run_status=running`을 담지만 현재 `pollMatrixRun`은 timeout 반환 시 마지막 non-terminal run status를 보존하지 않는다. 기존 handoff tests는 promised key 존재만 확인해서 이 계약 불일치를 잡지 못했다.

## 범위 결정 근거

- 범위는 operator CLI matrix output contract, handoff fixtures, handoff tests로 제한한다.
- API/worker/contracts/domain 경계와 Flutter 화면 구현은 제외한다.
- matrix terminal failed가 lifecycle evidence로 `exit_code=0`인 기존 계약은 유지한다. mismatch는 명시적인 expected run status가 있는 경우에만 발생하게 한다.

## 빌드 등급

- build: `local-G06`, review: `local-G06`.
- 이유: deterministic Go unit tests와 fixture/doc checks로 검증 가능한 operations-domain 보정이다.

## 구현 체크리스트

- [ ] Matrix expectation mismatch와 timeout expected JSONL을 실제 runner output 계약과 일치시킨다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`
- [ ] `handoff_test.go`가 matrix row의 expected exit code/status와 expected JSONL summary를 검증하도록 확장한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`
- [ ] `headless_validation.md`와 네 matrix fixtures/expected JSONL이 갱신된 계약을 같은 이름, key, exit-code로 참조하는지 확인한다. 검증: `rg --sort path -n "backtest_matrix_(success|failed|timeout|mismatch)|Expected exit code|exit_code" apps/cli/testdata/operator/headless_validation.md apps/cli/testdata/operator apps/cli/internal/operator/handoff_test.go`
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [REVIEW_TEST-1] Matrix Fixture Runtime Contract

### 문제

`apps/cli/testdata/operator/expected/backtest_matrix_mismatch.jsonl:2`는 `status=mismatch`와 `exit_code=1`을 약속하지만 `apps/cli/internal/operator/runner.go:1609`는 terminal `failed`를 `status=ok`, `codeOK`로 반환한다. `apps/cli/testdata/operator/backtest_matrix_mismatch.yaml:10`에도 expected run status가 없어서 mismatch를 재현할 입력 계약이 없다. `apps/cli/testdata/operator/expected/backtest_matrix_timeout.jsonl:2`의 `run_status=running`도 현재 timeout path에서는 보존되지 않는다.

### 해결 방법

- matrix batch에 명시적인 expected run status를 둘 수 있는 최소 YAML 계약을 추가한다. 기존 step expectation 이름과 맞춰 `matrix.expect.run_status` 형태를 우선 사용한다.
- `runMatrixBatch`/`pollMatrixRun`은 expected run status가 없으면 기존처럼 terminal `succeeded/failed/canceled`를 lifecycle evidence로 처리한다.
- expected run status가 있고 terminal run status가 다르면 `status=mismatch`, `exit_code=1`, concrete error message를 출력한다.
- timeout path는 마지막으로 관측한 non-terminal run status를 `run_status`에 보존하거나, 보존하지 않는 방향을 선택한다면 timeout expected JSONL과 promised keys를 그 계약에 맞춰 수정한다. 사용자-facing 상태 표시에는 `running` 보존이 더 유용하므로 보존을 우선한다.
- 네 matrix YAML fixture와 expected JSONL을 실제 runner behavior와 맞춘다.

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

- [ ] `apps/cli/internal/operator/scenario.go`: matrix expectation schema와 validation 추가.
- [ ] `apps/cli/internal/operator/runner.go`: matrix expected run status mismatch와 timeout last status 처리.
- [ ] `apps/cli/internal/operator/runner_backtest_test.go`: success, terminal failed, timeout, mismatch JSONL output regression 추가 또는 기존 tests 확장.
- [ ] `apps/cli/testdata/operator/backtest_matrix_*.yaml`: 새 expectation 계약 반영.
- [ ] `apps/cli/testdata/operator/expected/backtest_matrix_*.jsonl`: 실제 output 계약에 맞춰 갱신.

### 중간 검증

```bash
go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli
```

예상: 두 package 모두 `ok`.

## [REVIEW_TEST-2] Handoff Exit-Code/Status Guard

### 문제

`apps/cli/internal/operator/handoff_test.go:357`의 matrix-driven test는 expected output keys 존재만 확인한다. 그래서 handoff matrix의 Expected exit code cell, expected JSONL summary의 `exit_code`/`status`, 실제 runner behavior가 서로 달라도 통과한다.

### 해결 방법

- handoff matrix parser가 `Expected exit code` column도 읽게 한다.
- 각 required scenario의 expected JSONL summary line을 찾아 `exit_code`와 `status`를 확인한다.
- matrix row가 단일 expected exit code를 문서화한 경우 그 값과 summary `exit_code`가 일치해야 한다. 여러 code가 문서화된 legacy row는 허용 목록으로 처리한다.
- 네 matrix row는 success=`0`, failed=`0`, timeout=`3`, mismatch=`1`을 강제한다.
- fixture existence/key tests와 중복되더라도 exit code/status mismatch를 독립적으로 실패시키는 test name을 둔다.

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

- [ ] `apps/cli/internal/operator/handoff_test.go`: expected exit code column parsing 추가.
- [ ] `apps/cli/internal/operator/handoff_test.go`: expected JSONL summary `exit_code`/`status` 검증 추가.
- [ ] `apps/cli/internal/operator/handoff_test.go`: 네 matrix row의 exit code 계약을 명시적으로 검증.
- [ ] 필요하면 markdown table parsing helper의 column bounds guard를 추가한다.

### 중간 검증

```bash
go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli
```

예상: 두 package 모두 `ok`.

## 최종 검증

```bash
go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli
git diff --check
rg --sort path -n "backtest_matrix_(success|failed|timeout|mismatch)|Expected exit code|exit_code" apps/cli/testdata/operator/headless_validation.md apps/cli/testdata/operator apps/cli/internal/operator/handoff_test.go
```

## 리뷰어를 위한 체크포인트

- `backtest_matrix_mismatch` expected JSONL이 실제 matrix runner path에서 재현 가능한지 확인한다.
- terminal failed가 기존 lifecycle evidence 계약대로 `exit_code=0`을 유지하는지 확인한다.
- timeout expected JSONL의 `run_status`가 runner가 실제로 출력하는 필드와 일치하는지 확인한다.
- handoff tests가 keys뿐 아니라 expected exit code/status drift도 실패시키는지 확인한다.

