<!-- task=m-command-first-operator-workflow/04+02_backtest_runtime_flow plan=0 tag=API -->

# Plan - Backtest Runtime Flow

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

이 plan은 구현 지시서이며, 구현 완료 전 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 내용과 검증 출력으로 채운다. 이 task는 `02+01_import_cli_scenario` 완료 뒤 구현한다. 최종 판정, archive 이동, `complete.log` 작성은 code-review-skill 전용이다. 사용자-only blocker가 있으면 review stub의 `사용자 리뷰 요청`을 채우고 멈춘다.

## 배경

CLI에는 start/poll/result action이 있지만 real API/worker rail에는 poll이 호출하는 `GetBacktestRun` handler가 없고, worker entrypoint는 backtest starter/executor를 store-backed deps에 연결하지 않는다. 이 작업은 imported daily bars를 `StorageBarSource`로 읽는 worker runtime을 연결하고, backtest scenario들이 import 후 start/poll/result를 확인하게 만든다. PASS 시 `backtest-command-flow` 기능 Task를 완료 대상으로 삼는다.

## 사용자 리뷰 요청 흐름

구현 중 사용자-only blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 이를 검증하고 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/operator-surface/milestones/command-first-operator-workflow.md`
- Task ids:
  - `backtest-command-flow`: imported daily bars를 입력으로 backtest start/poll/result를 command workflow에서 확인할 수 있다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-test/local/rules.md`
- `agent-test/local/api-smoke.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/operations-smoke.md`
- `agent-ops/rules/project/domain/api/rules.md`
- `agent-ops/rules/project/domain/worker/rules.md`
- `agent-ops/rules/project/domain/operations/rules.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/operator-surface/PHASE.md`
- `agent-roadmap/phase/operator-surface/milestones/command-first-operator-workflow.md`
- `packages/contracts/proto/alt/v1/backtest.proto`
- `services/api/internal/socket/backtest.go`
- `services/api/internal/socket/backtest_test.go`
- `services/api/internal/socket/server_test.go`
- `services/api/internal/workerclient/client.go`
- `services/api/internal/workerclient/client_test.go`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/backtest_test.go`
- `services/worker/internal/socket/backtest_mapping.go`
- `services/worker/internal/socket/backtest_mapping_test.go`
- `services/worker/internal/socket/handlers.go`
- `services/worker/internal/socket/server_test.go`
- `services/worker/internal/jobs/backtest_starter.go`
- `services/worker/internal/jobs/backtest_jobs.go`
- `services/worker/internal/backtest/engine.go`
- `services/worker/internal/backtest/engine_test.go`
- `services/worker/internal/backtest/bar_source.go`
- `services/worker/internal/backtest/bar_source_test.go`
- `services/worker/internal/backtest/fixture_test.go`
- `services/worker/internal/storage/ports.go`
- `services/worker/cmd/alt-worker/main.go`
- `packages/domain/backtest/types.go`
- `apps/cli/internal/operator/runner.go`
- `apps/cli/internal/operator/output.go`
- `apps/cli/testdata/operator/backtest_run_request.yaml`
- `apps/cli/testdata/operator/backtest_run_polling.yaml`
- `apps/cli/testdata/operator/backtest_result_summary.yaml`
- `apps/cli/testdata/operator/expected/backtest_run_request.jsonl`
- `apps/cli/testdata/operator/expected/backtest_run_polling.jsonl`
- `apps/cli/testdata/operator/expected/backtest_result_summary.jsonl`

### 테스트 환경 규칙

- test_env: `local`.
- Matched profiles: `api-smoke`, `worker-smoke`, `operations-smoke`.
- Applied commands: `go test ./services/api/...`, `go test ./services/worker/...`, `go test ./apps/cli/...`, scenario validate commands, `git diff --check`.
- `bin/worker-storage-check` is not required unless implementation changes SQL/storage generated files.

### 테스트 커버리지 공백

- `GetBacktestRunRequest` exists in protobuf but API/worker socket handlers are missing; add forwarding/handler tests.
- `jobs.BacktestStarter` and `RegisterRunBacktestHandler` have unit tests but `cmd/alt-worker` does not wire them into deps; add focused wiring coverage through helper extraction or capability tests.
- Backtest engine has fake strategy tests but no production strategy resolver. Add built-in `strategy-v1` or equivalent deterministic strategy port for command validation.
- Existing backtest YAML fixtures start runs without importing bars first. Update them to import first.

### 심볼 참조

- Renamed/removed symbols: none.
- Additive call sites: workerclient interface fake implementations, API/worker handler registries, parser maps are already aware of `GetBacktestRunRequest/Response`.

### 분할 판단

- Shared task group: `agent-task/m-command-first-operator-workflow/`.
- This subtask directory is `04+02_backtest_runtime_flow`; predecessor index `02` is `02+01_import_cli_scenario`.
- Predecessor completion status at plan creation: missing active/archive `complete.log`; implementation waits for predecessor completion.

### 범위 결정 근거

- Excludes import CLI implementation; predecessor owns it.
- Excludes market status handoff finalization; `03` and `05` own it.
- Excludes trading order/paper trading/live trading APIs and production scheduling.
- Excludes Flutter UI.

### 빌드 등급

- build=`cloud-G08`, review=`cloud-G08`: worker runtime wiring, async execution, API forwarding, storage-backed backtest input, and CLI command flow are cross-domain.

## 의존 관계 및 구현 순서

- Wait for `02+01_import_cli_scenario` complete.log.
- Implement `GetBacktestRun` rail first, then worker runtime wiring, then backtest fixtures.

## 구현 체크리스트

- [ ] [API-1] Add API and worker `GetBacktestRun` forwarding/handler support for polling.
- [ ] [API-2] Wire worker `alt-worker` with `BacktestStarter`, `RegisterRunBacktestHandler`, `StorageBarSource`, result store, and a built-in strategy resolver.
- [ ] [API-3] Add/update tests for built-in strategy resolver, worker execution wiring, and typed run polling.
- [ ] [API-4] Update backtest YAML scenarios to import daily bars before start/poll/result. 검증: start/poll/result scenario가 run id, terminal status, result summary key를 출력한다.
- [ ] [API-5] Run API/worker/CLI verification and `git diff --check`.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [API-1] GetBacktestRun Rail

### 문제

[packages/contracts/proto/alt/v1/backtest.proto](/config/workspace/alt/packages/contracts/proto/alt/v1/backtest.proto:44) defines `GetBacktestRunRequest`, and [apps/cli/internal/operator/runner.go](/config/workspace/alt/apps/cli/internal/operator/runner.go:317) polls it. But API and worker session handlers only register start/list/detail/result/compare: [services/worker/internal/socket/backtest.go](/config/workspace/alt/services/worker/internal/socket/backtest.go:52) and API tests do not include `GetBacktestRunRequest`.

### 해결 방법

Add `GetBacktestRun` to `workerclient.WorkerClient`, implement a one-line socket client method, add API forwarding handler, and add worker handler that validates `run_id` and reads the run through store-backed analysis/run store. Prefer using `Analysis.GetRunDetail` to avoid growing `Deps` unless direct `BacktestRunStore` access is clearer.

Before (`workerclient/client.go:31`):

```go
StartBacktest(ctx context.Context, req *altv1.StartBacktestRequest) (*altv1.StartBacktestResponse, error)
ListBacktestRuns(ctx context.Context, req *altv1.ListBacktestRunsRequest) (*altv1.ListBacktestRunsResponse, error)
```

After:

```go
StartBacktest(ctx context.Context, req *altv1.StartBacktestRequest) (*altv1.StartBacktestResponse, error)
GetBacktestRun(ctx context.Context, req *altv1.GetBacktestRunRequest) (*altv1.GetBacktestRunResponse, error)
ListBacktestRuns(ctx context.Context, req *altv1.ListBacktestRunsRequest) (*altv1.ListBacktestRunsResponse, error)
```

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

- [ ] `services/api/internal/workerclient/client.go`: interface and method.
- [ ] `services/api/internal/workerclient/client_test.go`: fake worker `GetBacktestRun` round-trip.
- [ ] `services/api/internal/socket/backtest.go`: forwarding handler.
- [ ] `services/api/internal/socket/backtest_test.go`: fake client method, validation, forwarding, socket test, handler registration.
- [ ] `services/api/internal/socket/server_test.go`: capability sync if needed.
- [ ] `services/worker/internal/socket/backtest.go`: handler registration and implementation.
- [ ] `services/worker/internal/socket/backtest_test.go`: success, validation, not found, unavailable, socket test, handler coverage.

### 테스트 작성

Add `TestHandleGetBacktestRunForwards`, `TestHandleGetBacktestRunRequiresRunID`, worker success/not_found, and socket typed validation tests.

### 중간 검증

```bash
go test ./services/api/internal/workerclient ./services/api/internal/socket ./services/worker/internal/socket -run 'GetBacktestRun|BacktestHandlers|Capabilities' -count=1
```

Expected: API and worker poll rail tests pass.

## [API-2] Worker Runtime Wiring

### 문제

[services/worker/cmd/alt-worker/main.go](/config/workspace/alt/services/worker/cmd/alt-worker/main.go:42) sets Analysis/Results/Instruments/Bars but leaves [services/worker/cmd/alt-worker/main.go](/config/workspace/alt/services/worker/cmd/alt-worker/main.go:61) `backtest_start_surface` false. `RegisterRunBacktestHandler` and `NewBacktestStarter` exist but are not wired in production.

### 해결 방법

Add a worker-internal built-in strategy resolver, e.g. `NewBuiltInStrategyPort()` with `strategy-v1` buy-and-hold or deterministic minimal strategy. In `main.go`, build:

```go
barSource := workerbacktest.NewStorageBarSource(store, store)
strategyPort := workerbacktest.NewBuiltInStrategyPort()
engine := workerbacktest.NewEngine(barSource, strategyPort, store)
jobs.RegisterRunBacktestHandler(runner, store, engine, time.Now)
starter := jobs.NewBacktestStarter(runner, store)
deps.Starter = starter
```

Keep import aliases clear (`workerbacktest`) to avoid package name collisions.

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

- [ ] `services/worker/internal/backtest/strategies.go`: built-in strategy resolver and strategy implementation.
- [ ] `services/worker/internal/backtest/strategies_test.go`: known strategy success and unknown strategy error.
- [ ] `services/worker/cmd/alt-worker/main.go`: wire bar source, strategy port, engine, job handler, starter.
- [ ] `services/worker/internal/socket/server_test.go`: capability now reflects Starter when deps include it.

### 테스트 작성

Add strategy tests and, if feasible, extract a small helper for store-backed deps wiring so a unit test can assert `Starter != nil` without launching the process. If extraction adds too much scope, document in `계획 대비 변경 사항` and rely on strategy/job/socket tests.

### 중간 검증

```bash
go test ./services/worker/internal/backtest ./services/worker/internal/jobs ./services/worker/internal/socket -count=1
```

Expected: worker backtest engine/job/socket tests pass.

## [API-3] Execution Evidence Tests

### 문제

Existing job and engine tests are separate. They do not prove command start, poll, and result can use storage-backed bars together.

### 해결 방법

Add or update a worker test using in-memory/fake stores to import/upsert bars, start a run synchronously via `BacktestStarter` with `WithStarterLaunch(func(fn func()) { fn() })`, poll/get run, and get result. Keep it unit-level; no Docker is required.

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

- [ ] `services/worker/internal/jobs/backtest_starter_test.go` or new worker backtest flow test: synchronous launch flow.
- [ ] `services/worker/internal/backtest/fixture_test.go`: reuse or extend imported bars fixture if lower risk.
- [ ] `services/worker/internal/socket/backtest_test.go`: typed run status/result queries as needed.

### 테스트 작성

Write a test whose assertions cover pending/running/succeeded transition enough for the command flow. At minimum assert terminal status `succeeded` and `GetResult` summary fields exist.

### 중간 검증

```bash
go test ./services/worker/... -run 'Backtest.*Flow|StorageBarSource|RunBacktest|BacktestStarter' -count=1
```

Expected: focused worker flow tests pass.

## [API-4] Backtest YAML Flow

### 문제

[apps/cli/testdata/operator/backtest_run_request.yaml](/config/workspace/alt/apps/cli/testdata/operator/backtest_run_request.yaml:1), [apps/cli/testdata/operator/backtest_run_polling.yaml](/config/workspace/alt/apps/cli/testdata/operator/backtest_run_polling.yaml:1), and [apps/cli/testdata/operator/backtest_result_summary.yaml](/config/workspace/alt/apps/cli/testdata/operator/backtest_result_summary.yaml:1) start runs without importing bars first.

### 해결 방법

Add an `import_daily_bars` step to each backtest scenario before `start_backtest`, using the same symbol/date range as the import/status scenario. Keep `strategy_id: strategy-v1` if the built-in resolver uses that ID. Update expected JSONL fixtures to include import line and adjusted step counts.

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

- [ ] `apps/cli/testdata/operator/backtest_run_request.yaml`
- [ ] `apps/cli/testdata/operator/backtest_run_polling.yaml`
- [ ] `apps/cli/testdata/operator/backtest_result_summary.yaml`
- [ ] matching expected JSONL fixtures.
- [ ] `apps/cli/internal/operator/runner_error_test.go` or runner tests if expectations change.

### 테스트 작성

Update fake API tests so backtest scenarios include import line and still assert `run_id`, terminal `run_status`, and summary keys.

### 중간 검증

```bash
go test ./apps/cli/internal/operator -run 'Backtest|Handoff' -count=1
go run ./apps/cli/cmd/alt operator scenario validate --file apps/cli/testdata/operator/backtest_run_polling.yaml
go run ./apps/cli/cmd/alt operator scenario validate --file apps/cli/testdata/operator/backtest_result_summary.yaml
```

Expected: tests and scenario validation pass.

## [API-5] Verification

### 문제

This plan spans API, worker, and CLI; a package-only test can miss an interface compile failure in another module.

### 해결 방법

Run the final commands exactly.

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

- [ ] No extra files.

### 테스트 작성

No additional tests.

### 중간 검증

```bash
git diff --check
```

Expected: no warnings.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/api/internal/workerclient/client.go` | API-1 |
| `services/api/internal/workerclient/client_test.go` | API-1 |
| `services/api/internal/socket/backtest.go` | API-1 |
| `services/api/internal/socket/backtest_test.go` | API-1 |
| `services/api/internal/socket/server_test.go` | API-1 |
| `services/worker/internal/socket/backtest.go` | API-1 |
| `services/worker/internal/socket/backtest_test.go` | API-1 |
| `services/worker/internal/backtest/strategies.go` | API-2 |
| `services/worker/internal/backtest/strategies_test.go` | API-2 |
| `services/worker/cmd/alt-worker/main.go` | API-2 |
| `services/worker/internal/socket/server_test.go` | API-2 |
| `services/worker/internal/jobs/backtest_starter_test.go` | API-3 |
| `services/worker/internal/backtest/fixture_test.go` | API-3 |
| `apps/cli/testdata/operator/backtest_run_request.yaml` | API-4 |
| `apps/cli/testdata/operator/backtest_run_polling.yaml` | API-4 |
| `apps/cli/testdata/operator/backtest_result_summary.yaml` | API-4 |
| `apps/cli/testdata/operator/expected/backtest_run_request.jsonl` | API-4 |
| `apps/cli/testdata/operator/expected/backtest_run_polling.jsonl` | API-4 |
| `apps/cli/testdata/operator/expected/backtest_result_summary.jsonl` | API-4 |
| `apps/cli/internal/operator/runner_error_test.go` | API-4 |

## 최종 검증

```bash
go test ./services/api/... -count=1
go test ./services/worker/... -count=1
go test ./apps/cli/... -count=1
go run ./apps/cli/cmd/alt operator scenario validate --file apps/cli/testdata/operator/backtest_run_polling.yaml
go run ./apps/cli/cmd/alt operator scenario validate --file apps/cli/testdata/operator/backtest_result_summary.yaml
git diff --check
```

Expected: all commands exit 0. Fresh `go test -count=1` is required.

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