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

# Backtest Result Store Plan

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

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

## 배경

`BacktestResult` 계약은 이미 있지만 worker storage에는 run만 있고 result 저장/조회가 없다. 마일스톤 범위는 starting cash, ending equity, trades/positions summary를 요구하므로 domain, storage, contract가 같은 shape로 연결되어야 한다. 이 subtask는 lifecycle handler가 만든 run을 기준으로 result persistence와 query port를 만든다.

## 사용자 리뷰 요청 흐름

구현 중 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/contracts/rules.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/contracts-smoke.md`
- `agent-test/local/domain-model-smoke.md`
- `agent-test/local/worker-smoke.md`
- `packages/domain/backtest/types.go`
- `packages/contracts/proto/alt/v1/backtest.proto`
- `services/api/internal/contracts/parser_map.go`
- `services/api/internal/contracts/parser_map_test.go`
- `services/worker/internal/storage/ports.go`
- `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql`
- `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.down.sql`
- `services/worker/internal/storage/postgres/queries/queries.sql`
- `services/worker/internal/storage/postgres/store.go`
- `services/worker/internal/storage/postgres/mapping.go`
- `services/worker/internal/storage/postgres/mapping_test.go`
- `services/worker/sqlc.yaml`
- `bin/contracts-gen`
- `bin/contracts-check`
- `bin/worker-storage-gen`

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

- `services/worker/internal/storage/ports.go:22`-`25` exposes run persistence only; result store behavior has no tests.
- `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql:23`-`33` creates `backtest_runs`, but no `backtest_results` table exists.
- `packages/contracts/proto/alt/v1/backtest.proto:51`-`55` has cash/equity only, so trades/positions summary is not representable yet.
- API parser already registers `BacktestResult` and result request/response, but generated changes still need parser map tests rerun.

### 심볼 참조

- renamed/removed symbols: none. Additive proto fields only.
- `BacktestResult` references: `packages/contracts/proto/alt/v1/backtest.proto`, generated Go/Dart contracts, `services/api/internal/contracts/parser_map.go`, `services/api/internal/contracts/parser_map_test.go`, `apps/client/lib/src/contracts/alt_contracts.dart`.

### 분할 판단

Split policy를 먼저 평가했다. 이 subtask는 `02+01_result_store`이며 `01_worker_lifecycle` complete.log에 의존한다. Storage/migration/protocol schema가 함께 움직이므로 별도 cloud-grade plan이 필요하다. `03+01,02_fixture_verification`은 이 subtask 이후에 전체 deterministic result를 검증한다.

### 범위 결정 근거

이 plan은 result shape, persistence, query store, generated contract drift만 다룬다. 실제 worker execution wiring은 `01_worker_lifecycle` 범위다. fixture full-cycle과 `bin/test`/`bin/lint` 완료 판정은 `03+01,02_fixture_verification`에서 다룬다.

### 빌드 등급

build=`cloud-G07`, review=`cloud-G07`. Migration, sqlc generation, protobuf compatibility, generated code가 함께 움직이는 고위험 cross-domain 작업이다.

## 의존 관계 및 구현 순서

이 task directory 이름은 `02+01_result_store`다. 같은 task group의 `01_worker_lifecycle`이 `complete.log`를 만든 뒤 시작한다.

## 구현 체크리스트

- [ ] `backtest.Result`에 trades/positions summary를 additive로 확장한다.
- [ ] protobuf `BacktestResult`를 additive field number로 확장하고 generated Go/Dart code를 갱신한다.
- [ ] PostgreSQL `backtest_results` schema, sqlc queries, storage port/store/mapping을 추가한다.
- [ ] result mapping/store tests와 parser map regression을 작성하거나 갱신한다.
- [ ] `bin/contracts-check`, `bin/worker-storage-gen`, `go test ./packages/domain/...`, `go test ./services/worker/...`, `go test ./services/api/...`를 통과시킨다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [API-1] Result domain and contract shape

#### 문제

`packages/contracts/proto/alt/v1/backtest.proto:51`-`55`는 `BacktestResult`에 `run_id`, `starting_cash`, `ending_equity`만 둔다.

```proto
message BacktestResult {
  string run_id = 1;
  Price starting_cash = 2;
  Price ending_equity = 3;
}
```

마일스톤 범위의 trades/positions summary가 wire format과 domain vocabulary에 없다.

#### 해결 방법

`packages/domain/backtest/types.go`의 `Result`에 `Trades []TradeSummary`, `Positions []PositionSummary`를 추가한다. `backtest.proto`에는 `BacktestTrade`와 `BacktestPosition` message를 추가하고 `BacktestResult`에 `repeated` fields를 새 field number로 추가한다. 기존 field number 1-3은 변경하지 않는다.

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

- [ ] `packages/domain/backtest/types.go`
- [ ] `packages/domain/backtest/types_test.go`
- [ ] `packages/contracts/proto/alt/v1/backtest.proto`
- [ ] `packages/contracts/gen/go/alt/v1/backtest.pb.go` generated
- [ ] `apps/client/lib/src/generated/alt/v1/backtest.pb.dart` generated
- [ ] 관련 generated enum/json 파일 generated

#### 테스트 작성

작성한다. Domain result summary field construction test를 추가하고, contract generation 후 API parser map test가 계속 통과하는지 확인한다.

#### 중간 검증

```bash
bin/contracts-check
```

기대 결과: exit code 0. Generated drift가 있으면 `bin/contracts-gen`으로 반영 후 같은 명령을 재실행한다.

### [API-2] Result persistence and query

#### 문제

`services/worker/internal/storage/ports.go:22`-`25`는 `BacktestRunStore`만 제공한다.

```go
type BacktestRunStore interface {
	UpsertRun(ctx context.Context, run backtest.Run) error
	GetRun(ctx context.Context, id backtest.RunID) (backtest.Run, error)
}
```

`services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql:23`-`33`에도 run table만 있고 result table이 없다.

#### 해결 방법

`BacktestResultStore`를 추가하고 `UpsertResult`, `GetResult`를 제공한다. Migration에는 `backtest_results`를 `run_id` PK/FK로 추가하고 cash/equity numeric fields와 trades/positions JSONB summary를 저장한다. `queries.sql`에 `UpsertResult`, `GetResult`를 추가하고 `bin/worker-storage-gen`으로 sqlc code를 갱신한다.

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

- [ ] `services/worker/internal/storage/ports.go`
- [ ] `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql`
- [ ] `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.down.sql`
- [ ] `services/worker/internal/storage/postgres/queries/queries.sql`
- [ ] `services/worker/internal/storage/postgres/sqlc/*.go` generated
- [ ] `services/worker/internal/storage/postgres/store.go`
- [ ] `services/worker/internal/storage/postgres/mapping.go`
- [ ] `services/worker/internal/storage/postgres/mapping_test.go`

#### 테스트 작성

작성한다.

- `TestBacktestResultMappingRoundTrip`: domain result -> sqlc params/row -> domain result
- `TestBacktestResultMappingRejectsInvalidDecimal`: invalid numeric fields fail before persistence
- Migration embed test는 기존 `migrate_test.go`가 up/down count를 확인하므로 추가 migration 파일을 만들면 version order를 맞춘다.

#### 중간 검증

```bash
bin/worker-storage-gen && go test ./services/worker/...
```

기대 결과: exit code 0. sqlc generated output이 repo 안에 반영되어야 한다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/domain/backtest/types.go` | API-1 |
| `packages/domain/backtest/types_test.go` | API-1 |
| `packages/contracts/proto/alt/v1/backtest.proto` | API-1 |
| `packages/contracts/gen/go/alt/v1/backtest.pb.go` | API-1 |
| `apps/client/lib/src/generated/alt/v1/backtest.pb.dart` | API-1 |
| `services/worker/internal/storage/ports.go` | API-2 |
| `services/worker/internal/storage/postgres/migrations/*.sql` | API-2 |
| `services/worker/internal/storage/postgres/queries/queries.sql` | API-2 |
| `services/worker/internal/storage/postgres/sqlc/*.go` | API-2 |
| `services/worker/internal/storage/postgres/store.go` | API-2 |
| `services/worker/internal/storage/postgres/mapping.go` | API-2 |
| `services/worker/internal/storage/postgres/mapping_test.go` | API-2 |

## 최종 검증

```bash
bin/contracts-check
bin/worker-storage-gen
go test ./packages/domain/...
go test ./services/worker/...
go test ./services/api/...
```

기대 결과: 모든 명령 exit code 0. Generated output이 바뀌면 변경으로 포함한다.

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