<!-- task=m-backtest-scenario-automation/02+01_batch_runner plan=1 tag=REVIEW_API -->

# Plan - REVIEW_API Matrix Market Validation

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

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

## Roadmap Targets

- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md`
- Task ids:
  - `batch-runner`: 여러 backtest run을 순차 실행하고 각 run의 polling 결과를 stable text/JSONL로 출력한다.
- Completion mode: check-on-pass

## 배경

`code_review_cloud_G07_0.log`의 FAIL 판정에서 matrix가 참조한 universe의 `market`이 비어 있거나 `unspecified`인 경우를 validation이 거르지 않는 문제가 발견됐다. `runMatrixBatch`는 이 값을 `StartBacktestRequest.Spec.Market`으로 변환하므로, `operator scenario validate`가 성공한 matrix scenario가 실제 batch run에서는 `MARKET_UNSPECIFIED` 요청을 보낼 수 있다.

## 범위 결정 근거

- Protobuf schema, API socket handler, worker execution/storage는 변경하지 않는다.
- `apps/cli/internal/operator/scenario.go`의 matrix validation과 해당 테스트를 중심으로 수정한다.
- `apps/cli/internal/operator/runner_backtest_test.go`에는 matrix-generated start request가 market/timeframe/window/symbol을 올바르게 materialize하는 증거만 보강한다.
- 후속 handoff fixture나 문서 갱신은 sibling subtask `03+01,02_result_handoff` 범위에 남긴다.

## 구현 체크리스트

- [ ] matrix가 참조한 universe의 `market`이 비어 있거나 `unspecified`이면 validation error가 나도록 `Scenario.Validate`를 보강한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`
- [ ] matrix batch start request가 market, timeframe, from/to window, selector symbols를 materialize한다는 테스트 증거를 추가한다. 검증: `go test -count=1 ./apps/cli/internal/operator`
- [ ] CODE_REVIEW-local-G06.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [REVIEW_API-1] Matrix Backtest Market Contract

### 문제

[scenario.go](/config/workspace/alt/apps/cli/internal/operator/scenario.go:507)의 matrix validation은 referenced universe의 존재만 확인한다. 하지만 [scenario.go](/config/workspace/alt/apps/cli/internal/operator/scenario.go:135)는 `""`와 `unspecified` market을 일반 universe 값으로 허용하고, [runner.go](/config/workspace/alt/apps/cli/internal/operator/runner.go:1481)는 matrix run의 market을 그대로 `StartBacktestRequest.Spec.Market`으로 매핑한다. 이 조합 때문에 validate 성공 후 batch run에서 worker가 거부할 수 있는 request가 생성된다.

### 해결 방법

`s.Matrix != nil`일 때 matrix가 참조한 universe마다 `market`이 `""` 또는 `unspecified`인지 확인하고, 그렇다면 `start_backtest requires request.market`와 동등하게 사용자에게 고칠 수 있는 validation error를 반환한다. 기존 step `start_backtest` validation과 문구를 완전히 같게 할 필요는 없지만, matrix/universe 이름과 required market 원인을 포함한다.

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

- [ ] `apps/cli/internal/operator/scenario.go`: matrix referenced universe market 필수 validation 추가.
- [ ] `apps/cli/internal/operator/scenario_test.go`: missing/unspecified universe market을 가진 matrix scenario가 실패하는 테스트 추가.
- [ ] `apps/cli/internal/operator/runner_backtest_test.go`: matrix start request의 market/timeframe/from/to/symbols materialization assertion 추가.

### 테스트 작성

- 작성: `TestValidateRejectsInvalidBacktestScenarioMatrix`에 missing market 또는 unspecified market case를 추가하거나 별도 테스트를 만든다.
- 작성: `TestRunBacktestMatrixRunsSequentially` 또는 별도 테스트에서 fake `StartBacktest` handler가 받은 request spec의 `Market`, `Timeframe`, `FromUnixMs`, `ToUnixMs`, `Selector.Symbols`를 검증한다.

### 중간 검증

```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 ./services/api/internal/socket ./services/worker/internal/socket ./services/worker/internal/jobs
```

예상: 모든 package `ok`; Go test cache는 fresh 실행이 필요하므로 `-count=1`을 유지한다.
