<!-- task=m-api-centered-proto-socket-rail/04+02_backtest_rail plan=1 tag=REVIEW_BACKTEST -->

# PLAN-cloud-G09: Backtest Rail Review Fixes

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

이 계획은 1차 코드리뷰에서 발견된 backtest rail의 protocol/error semantics, compare empty contract, start execution availability, verification evidence 문제만 고친다. 구현 완료 후 active `CODE_REVIEW-cloud-G09.md`의 구현 에이전트 소유 섹션을 실제 변경 내용과 검증 출력으로 채우고, active 파일을 그대로 둔 채 리뷰를 요청한다. archive, `complete.log`, verdict 작성은 code-review 전용이다.

구현 중 사용자만 결정할 수 있는 범위 변경, 사용자 소유 외부 환경/secret/service 준비가 필요하면 `CODE_REVIEW-cloud-G09.md`의 `사용자 리뷰 요청` 섹션을 구체적 증거와 함께 채우고 중단한다. 명령 재실행이나 산출물 수집으로 해소 가능한 검증 공백은 사용자 리뷰 요청이 아니라 이 계획 안에서 해소한다.

## 배경

기존 구현은 API/worker handler에서 Go error를 반환하지만 현재 proto-socket typed request listener는 handler error를 응답으로 보내지 않는다. 그래서 validation, unavailable, not found 같은 client-facing 실패가 계획된 오류 응답이 아니라 timeout으로 보인다. 또한 API와 worker의 empty compare semantics가 다르고, `StartBacktest`는 production에서 placeholder runner만 실행해 run을 pending에 남긴다.

## 사용자 리뷰 요청 흐름

구현-time 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. code-review가 해당 요청을 검증하고, 진짜 user-only blocker일 때만 `USER_REVIEW.md`를 작성한다.

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/private/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-roadmap/current.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-ops/rules/project/domain/api/rules.md`
- `agent-ops/rules/project/domain/worker/rules.md`
- `agent-ops/rules/project/domain/contracts/rules.md`
- `agent-ops/rules/project/domain/client/rules.md`
- `agent-test/local/rules.md`
- `agent-test/local/api-smoke.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/contracts-smoke.md`
- `agent-test/local/client-smoke.md`
- `agent-task/m-api-centered-proto-socket-rail/04+02_backtest_rail/plan_cloud_G08_0.log`
- `agent-task/m-api-centered-proto-socket-rail/04+02_backtest_rail/code_review_cloud_G08_0.log`
- `packages/contracts/proto/alt/v1/common.proto`
- `packages/contracts/proto/alt/v1/backtest.proto`
- `packages/contracts/README.md`
- `services/api/internal/socket/backtest.go`
- `services/api/internal/socket/backtest_test.go`
- `services/api/internal/socket/handlers.go`
- `services/api/internal/workerclient/client.go`
- `services/api/internal/workerclient/client_test.go`
- `services/api/internal/contracts/parser_map.go`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/backtest_mapping.go`
- `services/worker/internal/socket/backtest_test.go`
- `services/worker/internal/socket/backtest_mapping_test.go`
- `services/worker/internal/socket/handlers.go`
- `services/worker/internal/socket/server.go`
- `services/worker/internal/socket/server_test.go`
- `services/worker/internal/jobs/backtest_starter.go`
- `services/worker/internal/jobs/backtest_starter_test.go`
- `services/worker/internal/jobs/backtest_jobs.go`
- `services/worker/internal/jobs/builtin.go`
- `services/worker/internal/jobs/runner.go`
- `services/worker/internal/storage/ports.go`
- `services/worker/internal/storage/postgres/store.go`
- `services/worker/internal/backtest/engine.go`
- `services/worker/cmd/alt-worker/main.go`
- `services/worker/internal/contracts/parser_map.go`
- `apps/client/lib/src/contracts/alt_contracts.dart`
- `../proto-socket/go/communicator.go`

### 테스트 환경 규칙

- 선택한 test_env는 `local`이지만 `agent-test/local/rules.md`가 local 테스트/검증을 금지한다. 이 follow-up은 `cloud` lane으로 원격/허용 검증 환경에서 명령을 실행하고, active review stub에 실제 stdout/stderr를 붙여야 한다.
- 적용 profile: `api-smoke`, `worker-smoke`, `contracts-smoke`, `client-smoke`.
- contracts 변경 시 `bin/contracts-gen`, `bin/contracts-check`, `go test ./packages/contracts/gen/go/...`가 필요하다.
- API/worker 변경 시 `go test ./services/api/...`, `go test ./services/worker/...`가 필요하다.
- Dart generated/client wrapper 영향이 있으면 `cd apps/client && flutter test`가 필요하다.

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

- Handler error path: 현재 direct handler tests만 있고 socket request-response에서 error가 timeout이 되지 않는지 검증하지 않는다.
- Empty compare: worker no-op test는 있으나 API test는 empty request를 validation error로 고정해 contract drift를 잡지 못한다.
- Start execution availability: `BacktestStarter` unit test는 runner dispatch만 검증하고 production `cmd/alt-worker`가 executable `KindRunBacktest` handler를 실제로 등록하는지 검증하지 않는다.
- Verification evidence: 이전 review log는 command output이 요약뿐이라 code-review가 재검증할 수 없다.

### 심볼 참조

- `StartBacktest`, `ListBacktestRuns`, `GetBacktestRunDetail`, `GetBacktestResult`, `CompareBacktestRuns` call sites were checked in API handler/client, worker handler, parser maps, and generated/client parser surfaces.
- `NewServer` worker signature call sites were checked in `services/worker/cmd/alt-worker/main.go` and `services/worker/internal/socket/server_test.go`.

### 분할 판단

이 follow-up은 contracts/API/worker/client-generated 경계를 건드릴 수 있지만 네 개의 Required findings가 같은 backtest rail correctness를 공유한다. 별도 split을 만들면 error contract와 handler tests가 서로 기다리게 되므로 같은 selected subtask 안의 단일 follow-up plan으로 유지한다.

### 범위 결정 근거

- market rail(`05+02_market_rail`) 구현은 수정하지 않는다.
- existing client wrapper의 unrelated `listInstruments`/`listBars` 변경은 이 plan 범위가 아니다. Generated Dart contract drift가 생기는 경우에만 codegen 산출물을 반영한다.
- proto-socket transport 자체를 큰 폭으로 재설계하지 않는다. 현재 ALT contracts에 이미 있는 `ErrorInfo`를 우선 사용해 response payload로 오류를 표현하고, proto-socket 변경은 이 방식이 불가능하다는 코드 근거가 있을 때만 최소로 검토한다.
- 실제 strategy/bar adapter wiring이 아직 없다면 `StartBacktest`를 성공으로 노출하지 않는 쪽을 기본값으로 한다. pending run을 영구 생성하는 상태를 남기지 않는다.

### 빌드 등급

- Build lane: `cloud-G09`
- Review lane: `cloud-G09`
- 근거: protobuf contract, generated Go/Dart, API/worker socket behavior, production worker wiring, verification trust를 함께 복구하는 protocol/schema cross-domain follow-up이다.

## 구현 체크리스트

- [ ] [REVIEW_BACKTEST-1] Backtest response error semantics를 `ErrorInfo` 기반 typed response로 정리하고 socket-level timeout 회귀 테스트를 추가한다.
- [ ] [REVIEW_BACKTEST-2] `CompareBacktestRuns` empty request semantics를 API/worker/테스트에서 하나로 맞춘다.
- [ ] [REVIEW_BACKTEST-3] `StartBacktest` production path가 placeholder dispatch로 pending run을 영구 생성하지 않게 한다.
- [ ] [REVIEW_BACKTEST-4] 원격/허용 환경에서 필수 검증을 실행하고 실제 stdout/stderr를 review stub에 기록한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [REVIEW_BACKTEST-1] Error Response Semantics

문제:
`services/api/internal/socket/backtest.go:28` and `services/worker/internal/socket/backtest.go:48` register typed request handlers that return Go errors, but `../proto-socket/go/communicator.go:340` drops handler errors without sending a response. Client-visible validation/unavailable/not-found failures therefore become timeouts.

해결 방법:
Prefer the existing ALT `ErrorInfo` payload contract. Add additive `ErrorInfo error` fields to backtest response messages that can fail, regenerate Go/Dart contracts, and update API/worker handlers to return typed responses with `Error` populated and nil Go error for expected domain/request failures. Keep Go errors only for unexpected transport/runtime failures. Add socket-level tests that send invalid requests and unavailable dependency requests through proto-socket and assert a typed response with `ErrorInfo`, not request timeout.

수정 파일 및 체크리스트:
- `packages/contracts/proto/alt/v1/backtest.proto`
- generated Go under `packages/contracts/gen/go/alt/v1/` via `bin/contracts-gen`
- generated Dart under `apps/client/lib/src/generated/alt/v1/` via `bin/contracts-gen`
- `services/api/internal/socket/backtest.go`
- `services/api/internal/socket/backtest_test.go`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/backtest_test.go`
- [ ] Existing response fields keep their current field numbers.
- [ ] New error fields use new field numbers and `alt.v1.ErrorInfo`.
- [ ] Expected validation/unavailable/not-found failures produce typed responses with `Error`.
- [ ] Socket-level tests prove invalid/unavailable paths complete without timeout.

테스트 작성:
- Add API socket-level tests for invalid start/detail/result/compare and fake worker unavailable.
- Add worker socket-level tests for invalid start and missing deps.
- Add parser/codegen tests only if parser maps need explicit `ErrorInfo` registration; nested `ErrorInfo` does not need a separate top-level parser.

중간 검증:
- 원격/허용 환경에서 `bin/contracts-gen`.
- 원격/허용 환경에서 `bin/contracts-check`.
- 원격/허용 환경에서 `go test ./services/api/...`.
- 원격/허용 환경에서 `go test ./services/worker/...`.

### [REVIEW_BACKTEST-2] Compare Empty Semantics

문제:
`services/api/internal/socket/backtest.go:95` rejects empty compare requests, while `services/worker/internal/socket/backtest.go:192` treats them as no-op empty responses and the implementation log records no-op semantics.

해결 방법:
Use no-op empty response semantics unless the contract is explicitly changed. Remove the API empty-list rejection. In worker, return the empty response before requiring `deps.Analysis` if no store access is needed for no-op. Keep empty string IDs as validation errors with `ErrorInfo`.

수정 파일 및 체크리스트:
- `services/api/internal/socket/backtest.go`
- `services/api/internal/socket/backtest_test.go`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/backtest_test.go`
- [ ] Empty `run_ids` returns an empty `CompareBacktestRunsResponse`.
- [ ] Empty string inside `run_ids` still returns a validation error.
- [ ] API and worker tests assert the same behavior.

테스트 작성:
- Replace API empty compare validation test with no-op response coverage.
- Keep or add API/worker empty-ID validation tests.

중간 검증:
- 원격/허용 환경에서 `go test ./services/api/...`.
- 원격/허용 환경에서 `go test ./services/worker/...`.

### [REVIEW_BACKTEST-3] Start Execution Availability

문제:
`services/worker/cmd/alt-worker/main.go:24` registers only builtin placeholder jobs, then `services/worker/cmd/alt-worker/main.go:38` exposes `StartBacktest`. In production this can return success while the placeholder `KindRunBacktest` handler leaves the run pending forever.

해결 방법:
Make the start command honest. If a real executable backtest handler can be wired from existing adapters, register `jobs.RegisterRunBacktestHandler` after `RegisterBuiltins` and before exposing `Starter`. If the required `StrategyPort`/`BarSource` adapters do not exist in this milestone, keep `BacktestDeps.Starter` nil and return a typed unavailable error for start while still allowing read/query handlers whose stores are available. Do not create pending runs for starts that cannot execute.

수정 파일 및 체크리스트:
- `services/worker/cmd/alt-worker/main.go`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/backtest_test.go`
- `services/worker/internal/jobs/backtest_starter_test.go` if starter behavior changes
- [ ] Production start path either has an executable handler or returns unavailable before persisting a run.
- [ ] Query handlers can remain available when stores exist.
- [ ] Tests cover the chosen behavior.

테스트 작성:
- Add a worker wiring/unit test or focused handler test proving disabled start does not persist/dispatch.
- If a live handler is wired, test that `KindRunBacktest` transitions status via `RegisterRunBacktestHandler`.

중간 검증:
- 원격/허용 환경에서 `go test ./services/worker/...`.

### [REVIEW_BACKTEST-4] Verification Evidence Recovery

문제:
`code_review_cloud_G08_0.log:19` contains summarized local build/vet claims and unrun remote checks without stdout/stderr. The previous active review also lacked a plan-matching implementation checklist, so verification trust and loop completeness failed.

해결 방법:
Run required verification in the permitted environment and paste exact stdout/stderr into `CODE_REVIEW-cloud-G09.md`. Complete every implementation-owned checklist item. Do not summarize command results as "pass" without output.

수정 파일 및 체크리스트:
- `agent-task/m-api-centered-proto-socket-rail/04+02_backtest_rail/CODE_REVIEW-cloud-G09.md`
- [ ] Every command has exact stdout/stderr in `검증 결과`.
- [ ] Any skipped command has a concrete environment/blocker reason.
- [ ] Implementation checklist and item completion table are filled.

테스트 작성:
- No new product test for this item; this is evidence recovery for the review loop.

중간 검증:
- Review stub contains exact command blocks for all final verification commands below.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/contracts/proto/alt/v1/backtest.proto` | REVIEW_BACKTEST-1 |
| `packages/contracts/gen/go/alt/v1/*.pb.go` | REVIEW_BACKTEST-1 |
| `apps/client/lib/src/generated/alt/v1/*.dart` | REVIEW_BACKTEST-1 |
| `services/api/internal/socket/backtest.go` | REVIEW_BACKTEST-1, REVIEW_BACKTEST-2 |
| `services/api/internal/socket/backtest_test.go` | REVIEW_BACKTEST-1, REVIEW_BACKTEST-2 |
| `services/worker/internal/socket/backtest.go` | REVIEW_BACKTEST-1, REVIEW_BACKTEST-2, REVIEW_BACKTEST-3 |
| `services/worker/internal/socket/backtest_test.go` | REVIEW_BACKTEST-1, REVIEW_BACKTEST-2, REVIEW_BACKTEST-3 |
| `services/worker/cmd/alt-worker/main.go` | REVIEW_BACKTEST-3 |
| `services/worker/internal/jobs/backtest_starter_test.go` | REVIEW_BACKTEST-3 if needed |
| `agent-task/m-api-centered-proto-socket-rail/04+02_backtest_rail/CODE_REVIEW-cloud-G09.md` | REVIEW_BACKTEST-4 |

## 최종 검증

- 원격/허용 환경에서 `bin/contracts-gen`
- 원격/허용 환경에서 `bin/contracts-check`
- 원격/허용 환경에서 `go test ./packages/contracts/gen/go/...`
- 원격/허용 환경에서 `go test ./services/api/...`
- 원격/허용 환경에서 `go test ./services/worker/...`
- 원격/허용 환경에서 `cd apps/client && flutter test`
- 원격/허용 환경에서 API/worker smoke를 실행하거나, 아직 smoke harness가 없으면 추가한 socket-level tests가 invalid/unavailable paths를 timeout 없이 검증한다는 근거를 기록한다.

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