<!-- task=m-scheduled-market-data-refresh/04+03_refresh_status_cli plan=2 tag=REVIEW_REVIEW_API -->

# Plan - Review Follow-up: Real S04 Refresh Status Evidence

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

이 plan은 `code_review_local_G06_1.log`의 FAIL 판정을 닫기 위한 좁은 후속 작업이다. 사용자에게 직접 질문하지 말고, Milestone lock 결정이 필요한 경우에만 `CODE_REVIEW-local-G06.md`의 `사용자 리뷰 요청` 섹션을 채운 뒤 멈춘다. 현재 발견 사항은 repo 안에서 구현과 검증으로 해결 가능한 문제이므로 기본 경로는 자동 follow-up이다.

## Review Route

- route: `local-G06`
- 근거: scheduler tick result/status aggregation, worker unit tests, operator JSONL fixture 보강으로 범위가 명확하고 deterministic local Go/lint 명령으로 검증 가능하다.

## Archive Evidence Snapshot

- archived plan: `agent-task/m-scheduled-market-data-refresh/04+03_refresh_status_cli/plan_local_G06_1.log`
- archived review: `agent-task/m-scheduled-market-data-refresh/04+03_refresh_status_cli/code_review_local_G06_1.log`
- verdict: FAIL
- Required summary:
  - `RefreshStatusStore.RecordTick()` treats skipped items as success and fabricates `imported_bar_count` from succeeded item count while S04 metrics are otherwise zero.
  - scheduler loop tests pass a status store but do not assert tick recording, skipped/failed mapping, or config/next_run wiring.
  - operator refresh status exact JSONL evidence still covers only success; stale entry and error entry evidence are not exact-matched.
- affected files:
  - `services/worker/internal/scheduler/runner.go`
  - `services/worker/internal/scheduler/status_store.go`
  - `services/worker/internal/scheduler/backfill.go`
  - `services/worker/cmd/alt-worker/main.go`
  - `services/worker/cmd/alt-worker/main_test.go`
  - `services/worker/internal/scheduler/backfill_test.go`
  - `services/worker/internal/socket/scheduler_test.go`
  - `apps/cli/internal/operator/runner.go`
  - `apps/cli/internal/operator/client_test.go`
  - `apps/cli/internal/operator/runner_market_test.go`
  - `apps/cli/testdata/operator/scheduler_refresh_status.yaml`
  - `apps/cli/testdata/operator/expected/scheduler_refresh_status.jsonl`
- verification evidence from failed loop:
  - `bin/contracts-check` PASS, no output
  - `go test ./packages/contracts/gen/go/...` PASS
  - `go test ./services/api/...` PASS
  - `go test ./services/worker/...` PASS
  - `go test -count=1 ./services/worker/cmd/alt-worker ./services/worker/internal/scheduler ./services/worker/internal/socket -run 'Test(StoreBackedDeps|StartScheduler|SchedulerLoop|RefreshStatus|MarketHandlers|HandleSchedulerRefreshStatus|WorkerHelloCapabilities)'` PASS
  - `go test -count=1 ./apps/cli/internal/operator -run 'Test(RunScenarioRefreshStatus|HeadlessValidationHandoff|ExpectedOutputFixtures|HandoffExpectedFixtures)'` PASS
  - `bin/lint` exit code 0 with existing Flutter analyzer `avoid_print` info in Mattermost integration
- narrow archive reread allowed if needed:
  - `agent-task/m-scheduled-market-data-refresh/04+03_refresh_status_cli/code_review_local_G06_1.log`
  - `agent-task/m-scheduled-market-data-refresh/04+03_refresh_status_cli/plan_local_G06_1.log`

## Roadmap Targets

- `agent-roadmap/phase/backtest-loop/milestones/scheduled-market-data-refresh.md`
  - Handoff epic: `[refresh-status] success/stale/error status`

## 범위 결정 근거

이 follow-up은 새 public protobuf contract를 추가하지 않는다. 이미 추가된 refresh status surface가 SDD S04의 실제 evidence 의미를 갖도록 내부 tick result aggregation과 operator fixture coverage만 바로잡는다. 구현 에이전트는 roadmap target을 직접 체크하지 않는다.

## 구현 항목

### [REVIEW_REVIEW_API-1] Runtime refresh status must carry real S04 evidence

- 문제: `RecordTick()`이 `"failed"`만 실패로 보고 `"skipped"`를 성공처럼 남기며, `ItemResult`가 실제 imported bar count나 missing/gap/duplicate/provider_delay를 담지 않기 때문에 S04 필드가 성공 item 개수와 0값으로 위장된다.
- 해결:
  - scheduler tick result 또는 scheduler 전용 execution path에 실제 import result bar count와 freshness/backfill counters를 운반할 수 있는 내부 필드를 추가한다.
  - 성공 entry는 실제 imported bar count와 S04 counters를 기록할 수 있을 때만 `success`로 남긴다.
  - skipped/duplicate-window 결과는 `success`로 기록하지 않고 stale 또는 별도 error/stale mapping으로 기록한다. `last_error` 또는 stable reason을 남겨 operator가 성공과 구분할 수 있게 한다.
  - failed item은 `error`로 기록하고 기존 last_success는 보존하되 last_error를 새 실패 원인으로 갱신한다.
  - unknown/unsupported item status는 success evidence를 만들지 않게 방어한다.
- 테스트 결정:
  - `RecordTick` 또는 새 aggregation helper가 success, stale/skipped, failed, mixed result를 각각 검증한다.
  - actual bar count와 missing/gap/duplicate/provider_delay가 response까지 보존되는지 확인한다.
- 중간 검증:
  - `go test -count=1 ./services/worker/internal/scheduler ./services/worker/internal/socket -run 'Test(RefreshStatus|RecordTick|SchedulerRefreshStatus|SchedulerHandlers)'`

### [REVIEW_REVIEW_API-2] Worker loop/status wiring tests must assert recorded state

- 문제: `TestSchedulerLoopDispatchesTickAndStops`는 `statusStore`를 넘기지만 tick 이후 store entry를 확인하지 않는다. `startScheduler()`도 config가 status store에 들어가 next_run 계산에 쓰이는지 검증하지 않는다.
- 해결:
  - scheduler loop test가 tick 후 `statusStore.Get(schedule)` 또는 handler response를 조회해 status, last_success, imported_bar_count, last_error, next_run/config 기반 계산을 확인하게 한다.
  - skipped/failed tick 결과를 직접 만들거나 mock executor로 유도해 stale/error mapping을 검증한다.
  - `startScheduler()` valid config case가 `statusStore.ScheduleConfig()`에 schedule이 저장됐는지 확인한다.
  - `storeBackedDeps()`가 refresh store를 직접 만들지 않는 구조를 유지한다면 main-level wiring은 testable helper로 분리하거나, `startScheduler`/`runSchedulerLoop` tests가 실제 공유 store contract를 충분히 보장하게 한다.
- 테스트 결정:
  - worker cmd tests가 implementation checklist의 "tick 이후 store entry 확인"을 실제 assertion으로 닫는다.
- 중간 검증:
  - `go test -count=1 ./services/worker/cmd/alt-worker -run 'Test(StartScheduler|SchedulerLoop)'`

### [REVIEW_REVIEW_API-3] Operator S04 exact JSONL must cover success, stale, and error entries

- 문제: current fixture exact-match는 success 한 줄뿐이고, stale은 mismatch용 테스트, error는 typed `ErrorInfo` 테스트라 `SchedulerRefreshStatusEntry.Status == "stale"`와 `"error"`의 S04 field set이 stable JSONL로 검증되지 않는다.
- 해결:
  - `scheduler_refresh_status.yaml`을 success, stale, error entry를 모두 조회하는 scenario로 확장하거나, 별도 fixture/table exact-match 테스트를 추가한다.
  - fake API가 schedule_name별로 success/stale/error `SchedulerRefreshStatusEntry`를 반환하게 해 scenario runner의 실제 output path를 검증한다.
  - expected JSONL은 각 status line마다 `last_success_unix_ms`, `last_error`, `next_run_unix_ms`, `imported_bar_count`, `missing_count`, `gap_count`, `duplicate_count`, `provider_delay_days`를 모두 포함해야 한다.
  - typed `ErrorInfo` test는 유지하되 S04 error entry evidence와 이름/목적이 섞이지 않게 한다.
- 테스트 결정:
  - exact-match fixture test가 success/stale/error 세 줄과 summary를 비교한다.
  - handoff required key test가 확장 fixture에도 계속 통과해야 한다.
- 중간 검증:
  - `go test -count=1 ./apps/cli/internal/operator -run 'Test(RunScenarioRefreshStatus|HeadlessValidationHandoff|ExpectedOutputFixtures|HandoffExpectedFixtures)'`

### [TEST-1] Final verification and review evidence

- 문제: FAIL 후속은 실제 stdout/stderr evidence를 다시 남겨야 한다.
- 해결:
  - 아래 최종 검증을 실행하고 `CODE_REVIEW-local-G06.md`에 실제 출력 또는 차단 사유를 붙인다.
- 최종 검증:
  - `bin/contracts-check`
  - `go test ./packages/contracts/gen/go/...`
  - `go test ./services/api/...`
  - `go test ./services/worker/...`
  - `go test -count=1 ./apps/cli/internal/operator -run 'Test(RunScenarioRefreshStatus|HeadlessValidationHandoff|ExpectedOutputFixtures|HandoffExpectedFixtures)'`
  - `bin/lint`

## 구현 체크리스트

- [ ] [REVIEW_REVIEW_API-1] Runtime refresh status aggregation no longer fabricates success or S04 counts from scheduler item count, and preserves actual success/stale/error evidence.
- [ ] [REVIEW_REVIEW_API-2] Worker loop/startScheduler tests assert refresh status store recording, skipped/failed mapping, and config/next_run wiring.
- [ ] [REVIEW_REVIEW_API-3] Operator scheduler refresh status JSONL exact-match evidence covers success, stale, and error entries with all S04 fields.
- [ ] [TEST-1] Final verification commands are run and actual outputs are recorded in `CODE_REVIEW-local-G06.md`.
- [ ] `CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
