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

# Plan - Review Follow-up: Result Error Propagation Without Re-execution

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

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

## Review Route

- route: `local-G06`
- 근거: `jobs.Runner.ExecuteWithResult`, scheduler `RunTick`, worker/scheduler/jobs tests, 검증 기록에 한정된 deterministic local Go 작업이다. 외부 provider live call이나 새 public protobuf surface 판단은 필요하지 않다.

## Archive Evidence Snapshot

- archived plan: `agent-task/m-scheduled-market-data-refresh/04+03_refresh_status_cli/plan_local_G06_4.log`
- archived review: `agent-task/m-scheduled-market-data-refresh/04+03_refresh_status_cli/code_review_local_G06_4.log`
- verdict: FAIL
- Required summary:
  - `ExecuteWithResult()` collapses handler error and "no result handler" into `(Result{}, false)`, so scheduler `RunTick()` can call the same `import_daily_bars` job again through `Execute()`, losing the first error and risking duplicate provider/import side effects.
  - The core `jobs` result path has no matching failure-path test; the targeted verification regex reports `[no tests to run]` for `services/worker/internal/jobs`.
  - Active review did not run/record the plan's fixed final verification commands.
- resolved from previous loop:
  - Real success result can now carry `importer.Result.Bars` into scheduler `ItemResult.ImportSummary`.
  - No-evidence success is classified as stale instead of success.
- affected files:
  - `services/worker/internal/jobs/runner.go`
  - `services/worker/internal/jobs/runner_test.go`
  - `services/worker/internal/jobs/marketdata_jobs.go`
  - `services/worker/internal/jobs/marketdata_jobs_test.go`
  - `services/worker/internal/scheduler/runner.go`
  - `services/worker/internal/scheduler/runner_test.go`
  - `services/worker/internal/scheduler/status_store.go`
  - `services/worker/cmd/alt-worker/main_test.go`
  - any direct `ExecuteWithResult` implementers/mocks
- verification evidence from failed loop:
  - `gofmt -l services/worker/internal/jobs/runner.go services/worker/internal/jobs/marketdata_jobs.go services/worker/internal/scheduler/runner.go services/worker/internal/scheduler/status_store.go services/worker/cmd/alt-worker/main.go services/worker/cmd/alt-worker/main_test.go services/worker/internal/socket/market.go services/worker/internal/socket/scheduler_test.go` PASS, no output.
  - `go test -count=1 ./services/worker/internal/jobs ./services/worker/internal/scheduler ./services/worker/internal/socket -run 'Test(ImportDailyBars|RunTick|RefreshStatus|RecordTick|SchedulerRefreshStatus)'` PASS for scheduler/socket, but `services/worker/internal/jobs` reported `[no tests to run]`.
- narrow archive reread allowed if needed:
  - `agent-task/m-scheduled-market-data-refresh/04+03_refresh_status_cli/code_review_local_G06_4.log`
  - `agent-task/m-scheduled-market-data-refresh/04+03_refresh_status_cli/plan_local_G06_4.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를 추가하지 않는다. 이미 추가된 internal result-returning job path의 error/no-handler semantics와 tests만 바로잡는다. 구현 에이전트는 roadmap target을 직접 체크하지 않는다.

## 구현 항목

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-1] Result-returning execution must preserve errors and avoid re-execution

- 문제: `ExecuteWithResult()`가 handler error를 `(Result{}, false)`로 반환하여 scheduler가 `Execute()`로 같은 job을 재실행할 수 있다.
- 해결:
  - `ExecuteWithResult` 계약을 `(Result, hasResult, error)` 또는 동등한 형태로 바꿔 error와 "result handler 없음"을 구분한다.
  - result handler가 존재하고 error를 반환하면 scheduler `RunTick()`은 재실행하지 않고 즉시 failed `ItemResult`를 기록한다.
  - result handler가 없는 경우에만 legacy `Execute()` fallback을 사용한다.
  - context cancellation과 panic recovery도 error로 보존된다. 필요하면 result handler용 panic recovery helper를 추가한다.
  - 모든 mocks/direct implementers를 새 계약에 맞게 갱신한다.
- 테스트 결정:
  - `jobs.Runner.ExecuteWithResult`가 successful result, missing result handler, handler error, canceled context, panic을 구분하는 tests를 추가한다.
  - scheduler `RunTick()` test가 result handler error를 한 번만 실행하고 failed item/error를 기록하는지 검증한다.
- 중간 검증:
  - `go test -count=1 ./services/worker/internal/jobs ./services/worker/internal/scheduler -run 'Test(Runner|ExecuteWithResult|RegisterDailyBarImportHandler|RunTick)'`

### [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2] Jobs/scheduler result path tests must actually run

- 문제: 이전 검증 regex는 `services/worker/internal/jobs`에서 `[no tests to run]`이었다. 핵심 변경 패키지의 테스트가 실행되지 않으면 result/error contract를 신뢰할 수 없다.
- 해결:
  - 새 tests 이름이 plan의 중간 검증 regex와 매칭되게 한다. 불가피하면 plan 대비 변경 사항에 대체 명령과 이유를 기록하되, package-wide `go test -count=1 ./services/worker/internal/jobs ./services/worker/internal/scheduler`를 포함한다.
  - `RegisterDailyBarImportHandler` result path에서 fake importer result가 returned `jobs.Result`로 보존되는 성공 테스트와 fake importer error가 returned error로 보존되는 실패 테스트를 추가한다.
  - scheduler/worker loop tests가 direct `RecordTick()`만 보지 않고 actual `ExecuteWithResult` path를 통과하는지 확인한다.
- 중간 검증:
  - `go test -count=1 ./services/worker/internal/jobs ./services/worker/internal/scheduler ./services/worker/cmd/alt-worker -run 'Test(Runner|ExecuteWithResult|RegisterDailyBarImportHandler|RunTick|SchedulerLoop)'`

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

- 문제: 이전 active review는 plan의 고정 최종 검증 명령 전체를 실행/기록하지 않았다.
- 해결:
  - 아래 최종 검증을 실행하고 `CODE_REVIEW-local-G06.md`에 실제 stdout/stderr 또는 차단 사유를 붙인다.
  - touched Go files는 저장 전 `gofmt` 상태를 확인한다.
- 최종 검증:
  - `gofmt -l services/worker/internal/jobs/runner.go services/worker/internal/jobs/runner_test.go services/worker/internal/jobs/marketdata_jobs.go services/worker/internal/jobs/marketdata_jobs_test.go services/worker/internal/scheduler/runner.go services/worker/internal/scheduler/runner_test.go services/worker/internal/scheduler/status_store.go services/worker/cmd/alt-worker/main.go services/worker/cmd/alt-worker/main_test.go services/worker/internal/socket/market.go services/worker/internal/socket/scheduler_test.go`
  - `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_REVIEW_REVIEW_REVIEW_API-1] Result-returning execution preserves handler errors and scheduler does not re-execute a failed result handler job.
- [ ] [REVIEW_REVIEW_REVIEW_REVIEW_REVIEW_API-2] Jobs/scheduler result path tests actually run and cover success, missing handler fallback, error, context cancellation, and panic/error propagation as applicable.
- [ ] [TEST-1] Final verification commands are run and actual outputs are recorded in `CODE_REVIEW-local-G06.md`.
- [ ] `CODE_REVIEW-local-G06.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
