<!-- task=m-api-centered-proto-socket-rail/04+02_backtest_rail plan=0 tag=BACKTEST -->
# CODE_REVIEW-cloud-G08: Backtest API to Worker Proto-Socket Rail

## 구현 에이전트 소유 섹션
- 구현 요약:
  - backtest의 client-facing 요청을 `client -> services/api -> services/worker` rail로 연결했다. API는 얇은 control plane으로 요청을 검증·중계만 하고, worker가 실행(runner 재사용)과 store 조회를 소유한다.
  - [BACKTEST-1] API: `workerclient`에 start/list/detail/result/compare 5개 메서드를 추가하고, Hello에 있던 ctx/타임아웃/취소 처리 보일러플레이트를 generic `sendTyped` 헬퍼로 통합해 drift를 막았다. socket에 backtest handler 5개를 등록했고, validation 실패와 worker unavailable/timeout 에러를 `mapWorkerError`로 일관 변환한다. API는 worker `internal/storage`를 import하지 않는다.
  - [BACKTEST-2] worker: socket에 start/list/detail/result/compare handler를 추가했다. start는 새 `jobs.BacktestStarter`가 pending run을 persist한 뒤 기존 `Runner`로 `KindRunBacktest` job을 비동기 실행한다(새 execution engine 만들지 않음). queries는 worker-owned store port(`BacktestAnalysisStore`, `BacktestResultStore`)로 처리한다.
  - [BACKTEST-3] worker: domain/store 모델 <-> proto 변환 mapper를 `internal/socket/backtest_mapping.go`에 모았다. timestamps(unix ms), Market/Timeframe/RunStatus enum, Price/Quantity/Decimal, nil/empty collection 동작을 mapper에서 단일 책임으로 처리한다. API는 같은 proto 메시지를 그대로 pass-through 한다.
  - not found/empty semantics: detail은 `HasResult=false`면 result 필드를 nil로 둔다. compare는 빈 요청을 no-op(빈 결과)로, 빈 id 값은 검증 에러로 처리한다. list 빈 상태는 빈 목록을 반환한다. ListRuns의 unspecified status는 빈 status로 매핑되어 store가 전체 목록을 반환한다.
  - wiring: worker `NewServer(cfg, BacktestDeps)`로 의존성을 주입한다. `cmd/alt-worker`는 pgxpool로 store를 만들어 deps를 채우고, pool 생성 실패 시 backtest surface를 비활성(handler가 unavailable 반환)으로 두고 hello는 계속 제공한다.
- 변경 파일:
  - worker(신규): `services/worker/internal/socket/backtest.go`, `services/worker/internal/socket/backtest_mapping.go`, `services/worker/internal/jobs/backtest_starter.go`
  - worker(수정): `services/worker/internal/socket/handlers.go`, `services/worker/internal/socket/server.go`, `services/worker/cmd/alt-worker/main.go`
  - worker(테스트): `services/worker/internal/socket/backtest_test.go`, `services/worker/internal/socket/backtest_mapping_test.go`, `services/worker/internal/jobs/backtest_starter_test.go`, `services/worker/internal/socket/server_test.go`(시그니처 반영)
  - api(신규): `services/api/internal/socket/backtest.go`
  - api(수정): `services/api/internal/socket/handlers.go`, `services/api/internal/workerclient/client.go`
  - api(테스트): `services/api/internal/socket/backtest_test.go`, `services/api/internal/workerclient/client_test.go`
- 실행한 검증/명령:
  - 로컬 테스트는 `agent-test/local/rules.md`에서 금지(기본 테스트 환경 = 원격)이므로 `go test`는 로컬에서 실행하지 않았다.
  - 로컬에서는 타입체크만 수행: `go build ./...`(worker, api 각각 통과), `go vet ./internal/socket/... ./internal/jobs/... ./cmd/...`(worker 통과), `go vet ./internal/socket/... ./internal/workerclient/...`(api 통과) — 테스트 파일 포함 컴파일 클린, 테스트 로직은 실행 안 함.
  - 원격 검증 필요(미실행): `go test ./services/api/...`, `go test ./services/worker/...`, API/worker smoke.
- 남은 위험/후속 작업:
  - production 실행 경로는 현재 runner의 builtin placeholder를 사용한다. 실제 backtest 실행은 `backtest.Engine`이 `StrategyPort`+`BarSource` adapter를 필요로 하는데 아직 wiring 대상이 없어 이번 rail 범위에서 제외했다. 따라서 start는 pending run을 기록·중계하지만 placeholder가 끝나면 run이 pending에 머문다. 실제 engine adapter wiring은 후속 작업(전략/마켓 데이터 경계)이 필요하다.
  - start 실행은 detached goroutine(background context)으로 fire-and-forget이다. 실 queue 인프라 도입 시 lifecycle/재시도/관측을 다시 설계해야 한다.
  - `go.work`/모듈 의존성은 변경하지 않았고 새 외부 의존성도 추가하지 않았다(run id는 crypto/rand 사용).

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 사용자 결정, 외부 환경 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. code-review가 이 내용을 검증해 `USER_REVIEW.md`를 작성한다._

- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 재개 조건: 없음

## 코드 리뷰어 소유 섹션
- 리뷰 상태: 미작성
- 주요 발견사항: 미작성
- 테스트/검증 평가: 미작성
- 판정: 미작성

## 코드리뷰 결과

- 종합 판정: FAIL
- 차원별 평가:
  - Correctness: Fail
  - Completeness: Fail
  - Test coverage: Fail
  - API contract: Fail
  - Code quality: Pass
  - Plan deviation: Fail
  - Verification trust: Fail
- 발견된 문제:
  - Required: `services/api/internal/socket/backtest.go:28` and `services/worker/internal/socket/backtest.go:48` register handlers through `AddRequestListenerTyped`, but that helper drops handler errors without sending a response, so every validation/store/unavailable error returned by these handlers becomes a client-side timeout instead of the planned proto-socket response error. Fix by adding and using an error-aware request response path, or by changing the ALT contract to carry explicit error responses, then add socket-level tests for invalid request and worker unavailable paths that prove the caller receives the intended error semantics rather than a timeout.
  - Required: `services/api/internal/socket/backtest.go:95` rejects empty `CompareBacktestRunsRequest`, while the worker implementation and implementation note define empty compare as a no-op empty response. Fix API and worker semantics to one contract; if empty compare is a no-op, let the API pass it through or return the same empty response and update API/worker tests accordingly.
  - Required: `services/worker/cmd/alt-worker/main.go:24` only registers builtin placeholder jobs, while `services/worker/cmd/alt-worker/main.go:38` enables `StartBacktest` by wiring `BacktestStarter`. A successful start therefore persists a pending run and dispatches a placeholder `KindRunBacktest` handler that never transitions the run or writes a result. Fix by wiring a real `RegisterRunBacktestHandler` path when an executable backtest adapter is available, or keep `Starter` unavailable until start can execute instead of returning permanently pending runs.
  - Required: `agent-task/m-api-centered-proto-socket-rail/04+02_backtest_rail/CODE_REVIEW-cloud-G08.md:19` records summarized local build/vet claims and unrun remote tests without actual stdout/stderr, and the active review file does not contain the plan-matching implementation checklist required by the loop. Fix the follow-up implementation to run permitted remote verification, paste exact stdout/stderr, and complete the generated `CODE_REVIEW-*-G??.md` checklist.
- 다음 단계: FAIL 후속 루프로 active plan/review를 archive하고, 같은 task directory에 `PLAN-cloud-G09.md` 및 `CODE_REVIEW-cloud-G09.md`를 작성한다.

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

- [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`로 아카이브한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/m-api-centered-proto-socket-rail/04+02_backtest_rail/`를 `agent-task/archive/YYYY/MM/m-api-centered-proto-socket-rail/04+02_backtest_rail/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-<milestone-slug>`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-api-centered-proto-socket-rail/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G09.md`와 `CODE_REVIEW-cloud-G09.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.
