<!-- task=m-scheduled-market-data-refresh/04+03_refresh_status_cli plan=0 tag=cloud-G08 -->

# Code Review Reference - API

## 리뷰어에게

이 파일은 `PLAN_cloud_G08.md` 구현 결과를 검토하기 위한 기준이다. 이 task가 PASS하면 roadmap target `[refresh-status]`를 완료할 수 있다.

## Archive Evidence Snapshot

- `agent-task/archive/2026/06/m-scheduled-market-data-refresh/01_scheduled_runner/complete.log`
- `agent-task/archive/2026/06/m-scheduled-market-data-refresh/02_retry_backfill/complete.log`
- `agent-task/m-scheduled-market-data-refresh/03+01,02_refresh_status_model/complete.log`

## Roadmap Targets

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

## 구현 에이전트 소유 섹션

### 구현 요약

**[API-1] Proto contract 추가**
- `market.proto`에 `SchedulerRefreshStatusRequest/Entry/Response` 3개 메시지 추가 (S04 evidence 필드 10개 포함)
- `bin/contracts-gen` 재생성 후 `bin/contracts-check` PASS
- services/api, services/worker, apps/cli 3개 parser_map에 factory 추가

**[API-2] Worker scheduler status store + socket handler**
- `services/worker/internal/scheduler/status_store.go` 신규: `RefreshStatusStore` (sync.RWMutex, Record/All/Get)
- `services/worker/internal/socket/backtest.go`: `RefreshStatusQuerier` 인터페이스 + `Deps.RefreshStatus` 필드
- `services/worker/internal/socket/market.go`: `handleSchedulerRefreshStatus()` + guard handler 등록

**[API-3] WorkerClient + API forwarding**
- `services/api/internal/workerclient/client.go`: 인터페이스 + socketClient 구현
- `services/api/internal/socket/market.go`: `handleSchedulerRefreshStatus()` + handler 등록
- `services/api/internal/socket/backtest_test.go`: fakeWorkerClient stub 추가

**[API-4] Operator scenario/output/runner + tests**
- `scenario.go`: ActionSchedulerRefreshStatus, validSchedulerStatuses, Request.ScheduleName, Expect.SchedulerStatus, validateExpect/validateRequest case
- `output.go`: StepEvent 11개 필드 + JSONL/text 렌더링
- `runner.go`: runStep case + evaluateSchedulerRefreshStatus()
- `client.go`: APIClient.SchedulerRefreshStatus()
- `client_test.go`: fakeAPI 확장 (resp/req 필드 + helper + handler 등록)
- `runner_market_test.go`: TestRunScenarioRefreshStatus{Success,ExpectedStatusMismatch,TypedError,Fixture} 4개 추가
- `testdata/operator/scheduler_refresh_status.yaml` 신규
- `testdata/operator/expected/scheduler_refresh_status.jsonl` 신규

**[Handoff]**
- `handoff_test.go`: requiredScenarios + requiredScenarioKeys 에 scheduler_refresh_status 추가
- `testdata/operator/headless_validation.md`: scheduler_refresh_status 행 추가

### 변경 파일

- `packages/contracts/proto/alt/v1/market.proto`
- `packages/contracts/gen/go/alt/v1/market.pb.go` (generated)
- `services/api/internal/contracts/parser_map.go`
- `services/api/internal/socket/market.go`
- `services/api/internal/socket/backtest_test.go`
- `services/api/internal/workerclient/client.go`
- `services/worker/internal/contracts/parser_map.go`
- `services/worker/internal/scheduler/status_store.go` (신규)
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/market.go`
- `apps/cli/internal/operator/client.go`
- `apps/cli/internal/operator/client_test.go`
- `apps/cli/internal/operator/handoff_test.go`
- `apps/cli/internal/operator/output.go`
- `apps/cli/internal/operator/parser_map.go`
- `apps/cli/internal/operator/runner.go`
- `apps/cli/internal/operator/runner_market_test.go`
- `apps/cli/internal/operator/scenario.go`
- `apps/cli/testdata/operator/scheduler_refresh_status.yaml` (신규)
- `apps/cli/testdata/operator/expected/scheduler_refresh_status.jsonl` (신규)
- `apps/cli/testdata/operator/headless_validation.md`
- `agent-roadmap/phase/backtest-loop/milestones/scheduled-market-data-refresh.md`

### 검증 결과

- [x] `bin/contracts-check` — PASS (no output)
- [x] `go test ./packages/contracts/gen/go/...` — no test files (OK)
- [x] `go test ./services/api/...` — PASS (socket, contracts, workerclient)
- [x] `go test ./services/worker/...` — PASS (scheduler, socket, 나머지 패키지)
- [x] `go test -count=1 ./apps/cli/internal/operator -run 'Test(RunScenarioRefreshStatus|HeadlessValidationHandoff|ExpectedOutputFixtures|HandoffExpectedFixtures)'` — 9개 테스트 모두 PASS
- remote runner: 이 task는 local headless gate 단계. remote provider 연결은 backtest-readiness 선행 조건이며 별도 task.

### Evidence

- JSONL fixture: `apps/cli/testdata/operator/expected/scheduler_refresh_status.jsonl`
  ```jsonl
  {"action":"scheduler_refresh_status","count":1,"duplicate_count":0,"gap_count":0,"imported_bar_count":120,"last_success_unix_ms":1700000000000,"missing_count":0,"next_run_unix_ms":1700086400000,"provider_delay_days":1,"refresh_count":1,"scenario":"scheduler_refresh_status","schedule":"kr-daily","scheduler_status":"success","status":"ok","step":"status1","type":"step"}
  {"exit_code":0,"passed":1,"scenario":"scheduler_refresh_status","status":"ok","steps":1,"type":"summary"}
  ```
- roadmap `[refresh-status]` diff: `[ ]` → `[x]` in `agent-roadmap/phase/backtest-loop/milestones/scheduled-market-data-refresh.md` line 60

### 남은 위험

- `RefreshStatusStore`는 worker가 재시작되면 초기화된다. 영속 상태는 이후 별도 subtask 범위.
- `RecordTick()` helper는 scheduler runner에서 명시적으로 호출해야 한다. 현재 worker cmd는 이 연결을 하지 않는다 (backtest-readiness task에서 연결 예정).
- `imported_bar_count`는 tick 성공 시 누적값이 아닌 마지막 tick 값으로 초기화된다. 누적 semantics는 요구사항 명확화 후 수정 필요.

## 리뷰 에이전트 소유 섹션

### 리뷰 체크리스트

- [ ] contract가 additive이며 기존 market/backtest message를 깨지 않는다.
- [ ] worker socket이 success/stale/error와 S04 필수 필드를 모두 반환한다.
- [ ] API forwarding이 worker unavailable/error를 기존 패턴대로 전달한다.
- [ ] operator JSONL expected output에 `last_success`, `last_error`, `next_run`, imported bar count, missing/gap/duplicate/provider_delay가 모두 있다.
- [ ] handoff matrix가 refresh status scenario를 참조한다.
- [ ] roadmap target은 evidence PASS 이후에만 체크됐다.

### 리뷰 결과

- [ ] PASS
- [x] CHANGES_REQUESTED

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - correctness: Fail
  - completeness: Fail
  - test coverage: Fail
  - API contract: Pass
  - code quality: Warn
  - implementation deviation: Fail
  - verification trust: Fail
  - spec conformance: Fail
- 발견된 문제:
  - Required: `services/worker/cmd/alt-worker/main.go:112`에서 `socket.Deps`에 `RefreshStatus`를 주입하지 않고, `runSchedulerLoop()`도 tick 결과를 status store에 기록하지 않습니다(`services/worker/cmd/alt-worker/main.go:176`). 그런데 worker socket handler는 `deps.RefreshStatus != nil`일 때만 `SchedulerRefreshStatusRequest` handler를 등록합니다(`services/worker/internal/socket/market.go:89`). 현재 실제 worker process에서는 operator/API가 새 refresh status endpoint를 호출할 수 없고, SDD S04의 runtime refresh status evidence를 만들 수 없습니다. `RefreshStatusStore`를 worker startup에 만들고 socket deps와 scheduler loop에 연결한 뒤, status handler/capability/wiring 테스트를 추가해야 합니다.
  - Required: `apps/cli/internal/operator/output.go:383`와 `apps/cli/internal/operator/output.go:578`이 `last_error`가 빈 값이면 필드를 출력하지 않습니다. SDD S04와 plan/review checklist는 `last_success`, `last_error`, `next_run`, imported/missing/gap/duplicate/provider_delay를 JSONL evidence에 모두 남기도록 요구합니다. 현재 expected JSONL도 `last_error`가 빠져 있고(`apps/cli/testdata/operator/expected/scheduler_refresh_status.jsonl:1`), handoff required key 목록도 `last_error`와 `next_run_unix_ms`를 강제하지 않습니다(`apps/cli/internal/operator/handoff_test.go:257`). 필수 evidence 필드를 빈 값이어도 안정적으로 출력하고 fixture/handoff 테스트가 이를 검증해야 합니다.
  - Required: roadmap target `[refresh-status]`가 이미 `[x]`로 체크되어 있습니다(`agent-roadmap/phase/backtest-loop/milestones/scheduled-market-data-refresh.md:60`). 이 task는 PASS가 아니고 `complete.log`/runtime completion event도 아직 없으므로, 현재 규칙상 milestone 완료 반영은 성립하지 않습니다. 후속 작업에서 premature check를 되돌리고, 최종 PASS 때 `complete.log`의 Roadmap Completion으로 runtime이 처리하게 해야 합니다.
  - Required: SDD S04는 success/stale/error status scenario expected JSONL을 요구하지만 현재 operator fixture는 success 한 줄만 exact-match로 검증하고, stale은 mismatch 테스트, error는 typed `ErrorInfo` 테스트에만 머뭅니다. 실제 `SchedulerRefreshStatusEntry.Status == "stale"`와 `"error"`의 필수 evidence 필드가 JSONL로 구분되는지 확인하는 fixture 또는 exact-match 테스트를 추가해야 합니다.
- 검증:
  - `bin/contracts-check` - PASS; no output
  - `go test ./packages/contracts/gen/go/...` - PASS; `[no test files]`
  - `go test ./services/api/...` - PASS
  - `go test ./services/worker/...` - PASS
  - `go test -count=1 ./apps/cli/internal/operator -run 'Test(RunScenarioRefreshStatus|HeadlessValidationHandoff|ExpectedOutputFixtures|HandoffExpectedFixtures)'` - PASS
  - `bin/lint` - PASS exit code 0; existing Flutter analyzer info remains in `apps/client/lib/src/integrations/mattermost/mattermost_auth_service.dart`
- 다음 단계: FAIL follow-up plan/review를 작성한다. `USER_REVIEW.md` gate는 트리거하지 않는다. 발견 사항은 Milestone lock 결정이 아니라 repo 내 구현/검증으로 해결 가능하다.

## 코드리뷰 전용 체크리스트

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append했다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW_cloud_G08.md`를 `code_review_cloud_G08_0.log`로 아카이브했다.
- [x] active `PLAN_cloud_G08.md`를 `plan_cloud_G08_0.log`로 아카이브했다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하고 `agent-roadmap/current.md`를 ignore하는지 확인했다.
- [x] FAIL이고 user-review gate가 트리거되지 않아 다음 active `PLAN-local-G06.md`와 `CODE_REVIEW-local-G06.md`를 작성했다.
- [x] FAIL 경로이므로 `complete.log`를 작성하지 않았다.

## 사용자 리뷰 요청

다음 중 하나라도 해당하면 구현/리뷰 에이전트는 작업을 멈추고 `USER_REVIEW.md`를 작성한다.

- 요구사항 또는 SDD와 충돌하는 결정을 해야 한다.
- public API/contract 필드명, status 의미, evidence 기준을 바꿔야 한다.
- 테스트로 검증할 수 없는 운영 환경 전제가 필요하다.
- roadmap target 완료 여부가 불명확하다.
