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

# Plan - REVIEW_API Matrix Period Required Validation

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

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

## 배경

이 follow-up은 `code_review_local_G06_0.log`의 Required 이슈만 보완한다. Matrix period가 `from_unix_ms` 또는 `to_unix_ms`를 누락해도 Go zero value `0`으로 들어와 현재 `Validate`를 통과한다. 기존 `start_backtest` request validation은 두 timestamp를 모두 필수로 취급하므로, matrix dry-run validation도 같은 기준을 적용해야 한다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자 prompt는 금지이며, code-review가 해당 요청을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-scenario-automation.md`
- Task ids:
  - `scenario-matrix`: universe, 기간, frequency, strategy 조합을 YAML scenario에서 표현하고 검증할 수 있게 한다.
- Completion mode: check-on-pass

## 범위 결정 근거

- 수정 범위는 `apps/cli/internal/operator/scenario.go`와 `apps/cli/internal/operator/scenario_test.go`로 제한한다.
- CLI validate는 `operator.LoadScenario`를 통해 같은 validation을 사용하므로 별도 CLI behavior 변경은 하지 않는다.
- Batch runner, output contract, fixture handoff는 후속 subtask 범위이므로 건드리지 않는다.

## 구현 체크리스트

- [ ] Matrix period validation이 `from_unix_ms` 또는 `to_unix_ms` 누락을 거부하도록 보강한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`
- [ ] invalid matrix tests에 missing `from_unix_ms`와 missing `to_unix_ms` 케이스를 추가한다. 검증: `go test -count=1 ./apps/cli/internal/operator ./apps/cli/internal/cli`
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [REVIEW_API-1] Matrix Period Required Timestamps

### 문제

[scenario.go](/config/workspace/alt/apps/cli/internal/operator/scenario.go:528)는 matrix period마다 `ID`와 inverted range만 검사한다. YAML에서 `from_unix_ms` 또는 `to_unix_ms`가 빠지면 값이 `0`이 되지만, `0 > 200`은 false이고 `100 > 0`만 일부 케이스를 잡는다. 이 때문에 dry-run validation을 통과한 matrix run이 나중에 invalid `start_backtest` request가 될 수 있다.

### 해결 방법

`MatrixPeriod` validation을 기존 `start_backtest` request validation과 맞춘다.

Before:

```go
if p.FromUnixMs > p.ToUnixMs {
	return fmt.Errorf("matrix period %q: from_unix_ms %d cannot be after to_unix_ms %d", p.ID, p.FromUnixMs, p.ToUnixMs)
}
```

After:

```go
if p.FromUnixMs == 0 || p.ToUnixMs == 0 {
	return fmt.Errorf("matrix period %q: from_unix_ms and to_unix_ms are required", p.ID)
}
if p.FromUnixMs > p.ToUnixMs {
	return fmt.Errorf("matrix period %q: from_unix_ms %d cannot be after to_unix_ms %d", p.ID, p.FromUnixMs, p.ToUnixMs)
}
```

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

- [ ] `apps/cli/internal/operator/scenario.go`: matrix period missing timestamp validation 추가.
- [ ] `apps/cli/internal/operator/scenario_test.go`: missing `from_unix_ms` case 추가.
- [ ] `apps/cli/internal/operator/scenario_test.go`: missing `to_unix_ms` case 추가.

### 테스트 작성

- `TestValidateRejectsInvalidBacktestScenarioMatrix` table에 두 케이스를 추가한다.
- 기대 에러는 필수 timestamp 누락을 명확히 드러내는 문자열이어야 한다.
- 기존 reversed period, duplicate generated ID tests는 유지한다.

### 중간 검증

```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
```

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