<!-- task=m-backtest-multi-timeframe-coverage/02_freshness_readiness plan=1 tag=REVIEW_FRESHNESS -->

# Plan - REVIEW_FRESHNESS

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

이 task는 구현 후 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채워야 완료된다. 검증을 실행하고 active 파일은 그대로 둔 채 리뷰 준비 상태를 보고한다. 종결, archive, `complete.log` 작성은 code-review 전용이다. 선택된 SDD 결정 또는 Milestone `구현 잠금 > 결정 필요` 항목이 막을 때만 review stub의 `사용자 리뷰 요청`을 채우고, 사용자에게 직접 질문하지 않는다.

## 배경

첫 리뷰에서 multi-timeframe freshness/readiness 구현은 큰 방향은 맞았지만 두 Required 이슈가 남았다. 새 `expect.latest_bucket` schema가 runner mismatch 판정에 연결되지 않았고, bar가 전혀 없는 freshness 경로에서 provider delay가 bucket index가 아니라 calendar day 차이로 계산된다.

## 사용자 리뷰 요청 흐름

구현 중 직접 사용자 프롬프트를 띄우지 않는다. 선택된 SDD 결정 또는 Milestone lock 결정만 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식으로 active review stub의 `사용자 리뷰 요청`에 기록하고, code-review가 `USER_REVIEW.md` 작성 여부를 판단한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/backtest-loop/milestones/backtest-multi-timeframe-coverage.md`
- Task ids:
  - `freshness-readiness`: collection freshness와 readiness output이 월/일/분 timeframe별 latest/missing/gap/duplicate 상태를 구분한다.
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- Acceptance scenarios:
  - `S08`: task=`freshness-readiness`; evidence=`freshness/readiness fixture text/JSONL for monthly/daily/minute latest/missing/gap/duplicate`
- Completion mode: spec-check-on-pass

## Archive Evidence Snapshot

- Archived plan: `agent-task/m-backtest-multi-timeframe-coverage/02_freshness_readiness/plan_local_G07_0.log`
- Archived review: `agent-task/m-backtest-multi-timeframe-coverage/02_freshness_readiness/code_review_local_G07_0.log`
- Verdict: FAIL
- Required findings:
  - R1: `apps/cli/internal/operator/runner.go:1525`에서 `Expect.LatestBucket`이 실제 `ev.LatestBucket`과 비교되지 않아 잘못된 `expect.latest_bucket`이 scenario success로 통과할 수 있다.
  - R2: `apps/cli/internal/operator/runner.go:1445`의 `latestBucket == ""` branch가 `tLast.Sub(tFirst)/(24*time.Hour)`를 사용해 minute/monthly no-bar provider delay를 bucket index 기준으로 계산하지 못한다.
- Affected files:
  - `apps/cli/internal/operator/runner.go`
  - `apps/cli/internal/operator/runner_market_test.go`
- Verification evidence from reviewed loop:
  - `go test -count=1 ./apps/cli/internal/operator` first rerun failed once in `TestRunGetPaperTradingStateOutputsFields` with a request timeout.
  - Targeted reruns passed: `go test -count=1 ./apps/cli/internal/operator -run 'TestValidateExpected(Buckets|YYYYMMDD)'`, `go test -count=1 ./apps/cli/internal/operator -run 'TestRunCollectionFreshness(BucketsByTimeframe|ZeroBarRegression)'`, `go test -count=1 ./apps/cli/internal/operator -run 'Test(MultiTimeframeFreshnessReadinessFixture|ExpectedOutputFixturesAreValidJSONLines)'`, and `go test -count=1 ./apps/cli/internal/operator -run '^TestRunGetPaperTradingStateOutputsFields$'`.
  - Final rerun passed: `go test -count=1 ./apps/cli/internal/operator`.
- Roadmap carryover: same `freshness-readiness` task remains check-on-pass.
- Spec carryover: same SDD S08 remains spec-check-on-pass.
- Narrow reread allowed: the two archived log paths above may be read if exact previous-loop wording is needed. Do not search `agent-task/archive/**`.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/domain/operations/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/operations-smoke.md`
- `agent-roadmap/sdd/backtest-loop/backtest-multi-timeframe-coverage/SDD.md`
- `apps/cli/internal/operator/runner.go`
- `apps/cli/internal/operator/runner_market_test.go`

### 범위 결정 근거

- proto/API contract는 변경하지 않는다.
- follow-up은 첫 리뷰의 Required 이슈 두 개만 수정한다.
- readiness fixture schema와 output field 이름은 유지한다.
- 사용자 결정이나 SDD 변경은 필요하지 않다.

### 빌드 등급

- `local-G07`: runner expectation/check logic과 targeted tests만 수정하며 local Go package test로 deterministic 검증 가능하다.

## 구현 체크리스트

- [ ] `apps/cli/internal/operator/runner.go`에서 `expect.latest_bucket` mismatch를 실제 `status=mismatch`/non-zero exit로 처리한다.
- [ ] `apps/cli/internal/operator/runner_market_test.go`에 잘못된 `latest_bucket` expectation이 실패하는 테스트를 추가한다.
- [ ] `apps/cli/internal/operator/runner.go`에서 `latestBucket == ""` provider delay 계산을 bucket index 기준으로 바꾼다.
- [ ] `apps/cli/internal/operator/runner_market_test.go`에 no-bar monthly/minute expected bucket provider delay 테스트를 추가한다.
- [ ] 최종 검증 명령을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_FRESHNESS-1] Enforce Latest Bucket Expectations

#### 문제

`Expect.LatestBucket` 필드는 scenario schema와 fixtures에 추가되었지만 `runCollectionFreshness`는 `LatestYYYYMMDD`만 비교한다. 그래서 잘못된 `expect.latest_bucket`이 있는 scenario가 성공할 수 있다.

#### 해결 방법

`runCollectionFreshness`에서 `step.Expect.LatestBucket`이 비어 있지 않을 때 `ev.LatestBucket`과 비교하고, mismatch이면 기존 freshness expectation mismatch와 같은 방식으로 `statusMismatch`, `codeAppError`, 명확한 error message를 반환한다.

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

- `apps/cli/internal/operator/runner.go`: `LatestBucket` expectation comparison 추가.
- `apps/cli/internal/operator/runner_market_test.go`: wrong latest bucket expectation이 실패하는 테스트 추가.

#### 테스트 작성

- 작성: `TestRunCollectionFreshnessLatestBucketExpectationMismatch`.

#### 중간 검증

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

기대: exit 0.

### [REVIEW_FRESHNESS-2] Bucket-Index Delay When No Latest Bucket Exists

#### 문제

`latestBucket == ""` branch는 bucket-aware 구현 뒤에도 calendar day 차이로 delay를 계산한다. minute bucket 여러 개가 모두 비어 있으면 bucket index 기준 delay가 아니라 0일로 축소되어 FRESHNESS-2 계획과 S08 evidence 의미를 깨뜨린다.

#### 해결 방법

최신 bucket이 없을 때도 expected bucket list의 index 거리 기준으로 delay를 계산한다. 최소 동작은 expected bucket이 1개 이상이면 마지막 index인 `len(expectedBuckets)-1`을 delay로 쓰는 것이다. daily 기존 의미가 바뀌는지 테스트로 확인하고, monthly/minute no-bar case를 추가한다.

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

- `apps/cli/internal/operator/runner.go`: no-latest provider delay branch를 bucket index 기준으로 변경.
- `apps/cli/internal/operator/runner_market_test.go`: monthly/minute no-bar expected bucket delay 테스트 추가.

#### 테스트 작성

- 작성: `TestRunCollectionFreshnessNoLatestBucketDelayByTimeframe`.

#### 중간 검증

```bash
go test -count=1 ./apps/cli/internal/operator -run 'TestRunCollectionFreshness(NoLatestBucketDelayByTimeframe|ZeroBarRegression|BucketsByTimeframe)'
```

기대: exit 0.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `apps/cli/internal/operator/runner.go` | REVIEW_FRESHNESS-1, REVIEW_FRESHNESS-2 |
| `apps/cli/internal/operator/runner_market_test.go` | REVIEW_FRESHNESS-1, REVIEW_FRESHNESS-2 |

## 최종 검증

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

기대: exit 0.

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