<!-- task=m-backtest-engine-baseline/01_worker_lifecycle plan=0 tag=API -->

# Backtest Worker Lifecycle Plan

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션 작성은 필수다. 구현 후 검증을 실행하고 실제 변경 내용, 검증 출력, 계획 대비 변경 사항을 채운 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 사용자 결정, 외부 환경 준비, 범위 충돌 없이는 진행할 수 없으면 review stub의 `사용자 리뷰 요청` 섹션을 근거와 함께 채우고 멈춘다. `USER_REVIEW.md`, archive log, `complete.log` 작성은 code-review 전용이다.

## 배경

현재 worker는 `run_backtest` kind를 갖고 있지만 built-in placeholder만 등록한다. `Backtest Engine Baseline`의 lifecycle 조건을 만족하려면 worker가 run 상태 전이를 소유하고 API는 요청/조회 경계에 머물 수 있는 실행 포트가 필요하다. 이 subtask는 result persistence 전 단계로, 실행 시작부터 성공/실패 상태 기록까지의 worker lifecycle skeleton을 만든다.

## 사용자 리뷰 요청 흐름

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

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/backtest-loop/PHASE.md`
- `agent-roadmap/phase/backtest-loop/milestones/backtest-engine-baseline.md`
- `agent-ops/rules/project/domain/domain-model/rules.md`
- `agent-ops/rules/project/domain/worker/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/domain-model-smoke.md`
- `agent-test/local/worker-smoke.md`
- `packages/domain/backtest/types.go`
- `packages/domain/backtest/types_test.go`
- `packages/domain/market/types.go`
- `services/worker/internal/jobs/job.go`
- `services/worker/internal/jobs/runner.go`
- `services/worker/internal/jobs/builtin.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/storage/ports.go`
- `services/worker/internal/storage/postgres/store.go`
- `services/worker/internal/storage/postgres/mapping.go`
- `services/worker/internal/storage/postgres/mapping_test.go`
- `services/worker/cmd/alt-worker/main.go`

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

- `KindRunBacktest`는 `services/worker/internal/jobs/builtin.go:21`에서 placeholder만 테스트 없이 성공한다. 새 handler decode, store 상태 전이, strategy 호출, 실패 상태 전이 테스트가 필요하다.
- `backtest.Strategy`와 `PortfolioState`는 `packages/domain/backtest/types_test.go`에서 주입/계산 기본 테스트가 있다.
- worker runner dispatch/panic/cancel coverage는 `services/worker/internal/jobs/runner_test.go`에 있다.

### 심볼 참조

- renamed/removed symbols: none.
- `KindRunBacktest` call sites: `services/worker/internal/jobs/job.go:11`, `services/worker/internal/jobs/builtin.go:21`.

### 분할 판단

Split policy를 먼저 평가했다. Shared task group은 `m-backtest-engine-baseline`이다.

- `01_worker_lifecycle`: worker execution lifecycle skeleton. 독립 시작 가능.
- `02+01_result_store`: storage/protobuf result persistence. `01_worker_lifecycle`의 run execution boundary에 의존한다.
- `03+01,02_fixture_verification`: deterministic fixture full-path test. lifecycle과 result store 둘 다에 의존한다.

worker lifecycle, storage/schema, fixture verification은 ownership과 위험이 달라 multi-plan이 맞다.

### 범위 결정 근거

이 plan은 `run_backtest` worker handler와 execution skeleton만 다룬다. `backtest_results` schema, protobuf result 확장, API/client 조회 연결은 `02+01_result_store`에서 다룬다. fixture full-cycle과 `bin/test`/`bin/lint` milestone 검증은 `03+01,02_fixture_verification`에서 다룬다.

### 빌드 등급

build=`local-G05`, review=`cloud-G06`. worker 내부 handler와 unit test 중심이라 local 구현이 가능하지만 lifecycle 상태 전이는 review에서 의미 검증이 필요하다.

## 구현 체크리스트

- [ ] `run_backtest` payload decode와 validation을 추가한다.
- [ ] worker-owned lifecycle handler를 추가해 pending/running/succeeded/failed 상태를 `BacktestRunStore`에 기록한다.
- [ ] strategy port와 bar source를 주입받는 최소 executor를 추가한다.
- [ ] lifecycle 성공/실패 unit test를 작성하고 `go test ./services/worker/...`를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Worker-owned run lifecycle

#### 문제

`services/worker/internal/jobs/builtin.go:21`-`24`는 `KindRunBacktest`를 성공하는 placeholder로 처리한다.

```go
runner.Register(KindRunBacktest, func(ctx context.Context, payload json.RawMessage) error {
	slog.Info("executing built-in job: run backtest", "payload_len", len(payload))
	return nil
})
```

이 상태에서는 worker가 backtest 실행을 소유한다는 검증 조건을 충족하지 못하고, run 상태도 `pending/running/succeeded/failed`로 전이되지 않는다.

#### 해결 방법

`services/worker/internal/jobs/backtest_jobs.go`를 추가해 `RunBacktestPayload`, `DecodeRunBacktestPayload`, `RegisterRunBacktestHandler`를 둔다. handler는 payload를 `backtest.RunSpec`으로 변환하고, `storage.BacktestRunStore`에 running 상태를 기록한 뒤 executor를 호출하고 succeeded/failed 상태로 갱신한다.

```go
type BacktestExecutor interface {
	Execute(ctx context.Context, run backtest.Run) error
}

func RegisterRunBacktestHandler(runner *Runner, store storage.BacktestRunStore, executor BacktestExecutor, now func() time.Time)
```

실제 engine 계산은 작게 유지한다. bar source와 strategy port를 받는 executor skeleton은 `services/worker/internal/backtest/` 아래에 두고, result persistence는 다음 subtask로 넘긴다.

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

- [ ] `services/worker/internal/jobs/backtest_jobs.go` 추가
- [ ] `services/worker/internal/jobs/backtest_jobs_test.go` 추가
- [ ] `services/worker/internal/jobs/builtin.go`는 placeholder 유지 여부를 결정하되 live handler가 override하는 패턴을 daily import와 맞춘다.
- [ ] `services/worker/internal/backtest/engine.go` 추가
- [ ] `services/worker/internal/backtest/engine_test.go` 추가

#### 테스트 작성

작성한다.

- `TestRegisterRunBacktestHandlerTransitionsSucceeded`: decoded payload -> running -> succeeded UpsertRun 순서와 executor 호출 확인
- `TestRegisterRunBacktestHandlerTransitionsFailed`: executor error -> failed UpsertRun 확인
- `TestDecodeRunBacktestPayloadRejectsMissingFields`: strategy_id, market, timeframe, from/to 누락 또는 역전 거부
- `TestEngineCallsStrategyForBars`: fixture bars를 strategy input으로 전달하는 최소 engine test

#### 중간 검증

```bash
go test ./services/worker/...
```

기대 결과: exit code 0. Go test cache 출력은 허용한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `services/worker/internal/jobs/backtest_jobs.go` | API-1 |
| `services/worker/internal/jobs/backtest_jobs_test.go` | API-1 |
| `services/worker/internal/jobs/builtin.go` | API-1 |
| `services/worker/internal/backtest/engine.go` | API-1 |
| `services/worker/internal/backtest/engine_test.go` | API-1 |

## 최종 검증

```bash
go test ./services/worker/...
```

기대 결과: exit code 0. Go test cache 출력은 허용한다.

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