<!-- task=m-scheduled-market-data-refresh/01_scheduled_runner plan=1 tag=REVIEW_SCHED -->

# Plan - REVIEW_SCHED

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

`CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 채우는 것이 구현의 마지막 단계다. 검증 명령을 실행하고 실제 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 두고 review ready 상태로 보고한다. 선택된 SDD 결정이나 선택된 Milestone `구현 잠금 > 결정 필요` 항목이 막으면 직접 질문하지 말고 review stub의 `사용자 리뷰 요청` 섹션에 근거를 기록한다. 환경/secret/service 준비, 일반 범위 조정, 검증 증거 공백은 사용자 리뷰 요청이 아니라 검증 결과나 후속 plan 대상이다. 구현 에이전트는 `USER_REVIEW.md`, `complete.log`, archive 로그를 만들지 않는다.

## 배경

첫 리뷰는 동작 코드와 기본 테스트가 통과했지만, SDD `S02`와 plan의 필수 증거가 완전히 닫히지 않아 `FAIL` 판정을 받았다. 이번 follow-up은 scheduler 구현을 넓히는 작업이 아니라, 기존 구현이 주장한 `scheduled-runner` 완료 근거를 실제 scheduler tick smoke와 runtime loop test로 증명하는 작업이다.

## 사용자 리뷰 요청 흐름

사용자 리뷰는 선택된 SDD decision 또는 Milestone lock decision만 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 구현 중 직접 채팅 질문, 선택지 제시, `request_user_input` 호출은 금지한다. code-review가 요청 타당성을 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/backtest-loop/milestones/scheduled-market-data-refresh.md`
- Task ids:
  - `scheduled-runner`: 원격 worker runtime에서 내장 scheduler tick이 market data import job을 실행하고, 병렬 수집과 재시작 또는 tick 중복에도 같은 bar key를 중복 저장하지 않는다.
- Completion mode: check-on-pass

## Spec Targets

- SDD: `agent-roadmap/sdd/backtest-loop/scheduled-market-data-refresh/SDD.md`
- Acceptance scenarios:
  - `S02`: task=`scheduled-runner`; evidence=`worker 내장 scheduler tick smoke, bounded parallel dispatch output, bars key 중복 없음, freshness/readiness output`
- Completion mode: spec-check-on-pass

## Archive Evidence Snapshot

- Archived plan: `agent-task/m-scheduled-market-data-refresh/01_scheduled_runner/plan_local_G06_0.log`
- Archived review: `agent-task/m-scheduled-market-data-refresh/01_scheduled_runner/code_review_local_G06_0.log`
- Verdict: FAIL
- Required summary:
  - `services/worker/internal/scheduler/runner_test.go:166`의 sequential 2회 tick 검증이 `succeeded`만 확인하고 `bars` key 중복 없음과 freshness/readiness output을 실제 import/store evidence에 연결하지 못했다.
  - `services/worker/cmd/alt-worker/main_test.go:93`의 valid config path 검증은 `startScheduler`가 nil을 반환하는지만 확인하며, 첫 ticker dispatch와 context cancellation shutdown을 검증하지 못했다.
- Affected files:
  - `services/worker/internal/scheduler/runner.go`
  - `services/worker/internal/scheduler/runner_test.go`
  - `services/worker/cmd/alt-worker/main.go`
  - `services/worker/cmd/alt-worker/main_test.go`
  - 필요 시 `services/worker/internal/jobs/marketdata_jobs.go`
- Verification evidence from failed loop:
  - `go test -count=1 ./services/worker/internal/scheduler ./services/worker/internal/config ./services/worker/cmd/alt-worker` passed.
  - `go test ./services/worker/...` passed.
  - Passing tests are insufficient because the S02 duplicate `bars` key and runtime tick dispatch evidence were not asserted.
- Allowed narrow reread:
  - 이 snapshot으로 부족한 경우에만 위 archived plan/review log 두 파일을 좁게 읽는다. `agent-task/archive/**`는 읽지 않는다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/domain/worker/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/worker-smoke.md`
- `agent-roadmap/sdd/backtest-loop/scheduled-market-data-refresh/SDD.md`
- `agent-roadmap/phase/backtest-loop/milestones/scheduled-market-data-refresh.md`
- `services/worker/internal/scheduler/runner.go`
- `services/worker/internal/scheduler/runner_test.go`
- `services/worker/internal/scheduler/config.go`
- `services/worker/internal/scheduler/config_test.go`
- `services/worker/cmd/alt-worker/main.go`
- `services/worker/cmd/alt-worker/main_test.go`
- `services/worker/internal/config/config.go`
- `services/worker/internal/config/config_test.go`
- `services/worker/internal/jobs/job.go`
- `services/worker/internal/jobs/runner.go`
- `services/worker/internal/jobs/marketdata_jobs.go`
- `services/worker/internal/jobs/marketdata_jobs_test.go`
- `services/worker/internal/marketdata/importer/importer.go`
- `services/worker/internal/storage/postgres/queries/queries.sql`

### 범위 결정 근거

이번 follow-up은 `scheduled-runner`의 필수 evidence gap만 닫는다. retry/backfill, refresh status surface, remote live provider smoke, API/contracts schema, Flutter UI, external cron/systemd timer 구현은 제외한다. SDD나 Milestone decision은 이미 해제되어 있으므로 `USER_REVIEW.md` 대상이 아니다.

### 빌드 등급

local-G06. 범위는 worker scheduler/runtime test 보강과 작은 loop helper refactor로 제한되고, local Go tests로 deterministic하게 검증 가능하다.

## 구현 체크리스트

- [ ] `services/worker/internal/scheduler/runner_test.go`에 `jobs.Runner` + fake daily bar importer/store 기반 2회 tick smoke를 추가/수정하고, `(instrument_id,timeframe,timestamp)` unique key count가 중복 증가하지 않음을 assert한다.
- [ ] `TickResult` text/JSONL 검증을 2회 tick smoke와 연결해 item별 status/readiness/freshness output이 duplicate key evidence와 함께 남도록 assert한다.
- [ ] `services/worker/cmd/alt-worker/main.go`의 scheduler loop를 테스트 가능한 helper로 분리하거나 ticker/runner를 주입하고, `services/worker/cmd/alt-worker/main_test.go`에서 최소 1회 tick dispatch와 context cancellation shutdown을 검증한다.
- [ ] 최종 검증으로 `go test -count=1 ./services/worker/internal/scheduler ./services/worker/internal/config ./services/worker/cmd/alt-worker`와 `go test ./services/worker/...`를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_SCHED-1] S02 Tick Evidence

문제: `services/worker/internal/scheduler/runner_test.go:166`의 sequential 2회 tick 검증은 두 번째 실행이 `succeeded`인지까지만 확인한다. `services/worker/internal/scheduler/runner.go:190`은 executor 성공을 바로 `ready/fresh`로 표시하므로, SDD `S02`의 "`bars` key 중복 없음"과 freshness/readiness output이 실제 import/store evidence에 연결되지 않는다.

해결 방법: scheduler package test에서 fake daily bar importer/store를 만들고 `jobs.RegisterDailyBarImportHandler`를 연결한 `jobs.Runner`를 `RunTick` executor로 사용한다. fake store는 `UpsertBar` 호출 시 `(instrument_id,timeframe,timestamp)` key set과 per-key write count를 기록한다. 같은 schedule/window를 2회 sequential tick으로 실행한 뒤 unique key count가 증가하지 않고 per-key count만 갱신되는지 assert한다. 같은 test 또는 인접 test에서 `WriteTickText`/`WriteTickJSONL` output이 item별 `status`, `readiness`, `freshness`를 포함하는지 확인한다.

수정 파일 및 체크리스트:

- [ ] `services/worker/internal/scheduler/runner_test.go`: fake importer/store, 2회 tick, unique key assertion, output assertion 추가.
- [ ] 필요 시 `services/worker/internal/scheduler/runner.go`: test가 요구하는 result evidence를 만들기 위한 최소 필드 보강. 단, 실제 duplicate 판정 없이 성공을 무조건 ready/fresh로 두는 구조를 유지할 경우 테스트가 그 한계를 명확히 보완해야 한다.

테스트 작성:

- `TestRunTickSequentialTicksDoNotDuplicateBarKeys`
- `TestRunTickOutputCarriesS02Evidence` 또는 기존 JSONL/text test 확장

중간 검증:

```bash
go test -count=1 ./services/worker/internal/scheduler
```

기대 결과: exit code 0. 2회 tick 후 unique bar key count와 readiness/freshness output assertion이 통과한다.

### [REVIEW_SCHED-2] Runtime Loop Dispatch Evidence

문제: `services/worker/cmd/alt-worker/main_test.go:93`은 valid config에서 `startScheduler`가 nil을 반환하는지만 확인한다. loop는 1분 ticker를 기다리므로 테스트 중 dispatch가 일어나지 않고, runner에도 import handler가 없어 실제 tick path를 검증하지 못한다.

해결 방법: `startScheduler`는 process wiring entrypoint로 유지하되, 내부 loop를 테스트 가능한 helper로 분리한다. 예를 들어 `runSchedulerLoop(ctx, schedCfg, opts, ticks <-chan time.Time, logger)` 또는 작은 `schedulerLoop` helper를 두고 test에서 tick channel을 즉시 보낸다. fake executor 또는 `jobs.Runner` with fake importer를 연결해 최소 1회 `KindImportDailyBars` dispatch를 assert하고, context cancellation 후 helper가 종료되는지 확인한다. production path는 기존 1분 ticker를 사용한다.

수정 파일 및 체크리스트:

- [ ] `services/worker/cmd/alt-worker/main.go`: scheduler loop body를 주입 가능한 helper로 분리하고 production behavior는 유지.
- [ ] `services/worker/cmd/alt-worker/main_test.go`: valid config path test를 실제 tick dispatch와 cancellation shutdown test로 확장.

테스트 작성:

- `TestStartSchedulerDispatchesTick` 또는 `TestSchedulerLoopDispatchesTickAndStops`

중간 검증:

```bash
go test -count=1 ./services/worker/cmd/alt-worker
```

기대 결과: exit code 0. test가 tick channel/fake executor를 통해 실제 dispatch와 shutdown을 검증한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/worker/internal/scheduler/runner_test.go` | REVIEW_SCHED-1 |
| `services/worker/internal/scheduler/runner.go` | REVIEW_SCHED-1, 필요 시 |
| `services/worker/cmd/alt-worker/main.go` | REVIEW_SCHED-2 |
| `services/worker/cmd/alt-worker/main_test.go` | REVIEW_SCHED-2 |

## 최종 검증

```bash
go test -count=1 ./services/worker/internal/scheduler ./services/worker/internal/config ./services/worker/cmd/alt-worker
go test ./services/worker/...
```

Go test cache output is acceptable only for the broad second command; the focused first command must use `-count=1`.

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