<!-- task=m-live-trading-boundary/04+01_account_sync plan=0 tag=LIVE_ACCOUNT -->

# Plan - LIVE_ACCOUNT

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채우는 것은 필수다. 구현 후 검증을 실행하고 실제 변경 내용, 설계 결정, 명령 출력(stdout/stderr)을 기록한 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 판정, 로그 rename, `complete.log`, archive 이동은 code-review 전용이다.

구현 중 사용자만 결정할 수 있는 사항, 사용자 소유 외부 환경/secret, 또는 계획 범위 충돌 없이는 안전하게 진행할 수 없으면 active review stub의 `사용자 리뷰 요청` 섹션에 정확한 결정, 근거, 실행 명령/출력, 재개 조건을 기록하고 중단한다. 구현 에이전트는 사용자에게 직접 묻거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

실거래 경계에는 주문뿐 아니라 broker 계좌/포지션을 worker-owned port로 동기화하는 표면이 필요하다. 기존 paper state는 `PaperTradingState`가 backtest run snapshot을 재사용하지만 live account는 broker snapshot이며, provider-specific raw payload나 account number를 tracked surface에 노출하면 안 된다. 이 계획은 live account sync contract와 fake broker 기반 worker/API/CLI workflow를 추가한다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active `CODE_REVIEW-*-G??.md`의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 형식을 따른다. 구현 중 직접 사용자 프롬프트는 금지이며, code-review가 요청의 타당성을 검증하고 실제 `USER_REVIEW.md` 작성을 소유한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/trading-expansion/milestones/live-trading-boundary.md`
- Task ids:
  - `account-sync`: 계좌/포지션 동기화 경계가 있다.
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-roadmap/current.md`
- `agent-roadmap/phase/trading-expansion/PHASE.md`
- `agent-roadmap/phase/trading-expansion/milestones/live-trading-boundary.md`
- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/project/domain/domain-model/rules.md`
- `agent-ops/rules/project/domain/contracts/rules.md`
- `agent-ops/rules/project/domain/worker/rules.md`
- `agent-ops/rules/project/domain/api/rules.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/domain-model-smoke.md`
- `agent-test/local/contracts-smoke.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/api-smoke.md`
- `agent-test/local/client-smoke.md`
- `packages/domain/backtest/types.go`
- `packages/domain/market/types.go`
- `packages/contracts/proto/alt/v1/paper_trading.proto`
- `bin/contracts-gen`
- `bin/contracts-check`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/handlers.go`
- `services/worker/internal/socket/paper.go`
- `services/worker/internal/socket/paper_mapping.go`
- `services/worker/internal/socket/paper_test.go`
- `services/api/internal/workerclient/client.go`
- `services/api/internal/socket/handlers.go`
- `services/api/internal/socket/paper.go`
- `services/api/internal/socket/paper_test.go`
- `apps/cli/internal/operator/scenario.go`
- `apps/cli/internal/operator/runner.go`
- `apps/cli/internal/operator/runner_paper_test.go`
- `apps/cli/internal/operator/client.go`
- `apps/cli/internal/operator/client_test.go`
- `apps/cli/internal/operator/output.go`
- `apps/cli/testdata/operator/headless_validation.md`

### 테스트 환경 규칙

`test_env=local`이다. contracts/worker/api/client/cli 변경이므로 `bin/contracts-gen`, `bin/contracts-check`, `go test ./packages/contracts/gen/go/...`, `go test ./services/worker/...`, `go test ./services/api/...`, `go test ./apps/cli/...`, `bin/lint`를 적용한다. Domain snapshot types가 바뀌면 `go test ./packages/domain/...`도 실행한다.

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

- Broker account snapshot port: 기존 테스트 없음.
- Account/position sync output: 기존 CLI output은 paper `account_id`, `cash`, `position_count` 중심이다. live account snapshot fields와 stale/synced timestamp 테스트가 필요하다.
- Raw account number redaction: 기존 테스트 없음. fixture와 output이 account alias/id만 쓰는지 확인해야 한다.

### 심볼 참조

Renamed/removed symbols: none. 새 account sync symbols만 추가한다.

### 분할 판단

공통 task group은 `m-live-trading-boundary`다. sibling split은 `01_broker_port`, `02+01_order_lifecycle`, `03+01,02_risk_kill_switch`, `04+01_account_sync`, `05+02,03,04_audit_trail`이다. 이 subtask는 predecessor `01`만 필요하다. 현재 active `01_broker_port/complete.log`는 없으므로 구현 시작 전 `01_broker_port` PASS가 필요하다.

### 범위 결정 근거

이 subtask는 durable storage를 추가하지 않는다. Account snapshot은 worker service memory와 broker port response boundary로 제한하고, audit plan에서 persistence와 event query를 다룬다. KIS live account network call은 구현하지 않으며 fake broker/account port tests로 boundary를 증명한다.

### 빌드 등급

Build lane `cloud-G05`, review lane `cloud-G05`. Account sync는 live broker/account boundary라 주의가 필요하지만 storage와 real external provider 호출은 제외한다.

## 의존 관계 및 구현 순서

Directory dependency source of truth: `04+01_account_sync` means predecessor index `01`. `01_broker_port`의 `complete.log`가 필요하다.

## 구현 체크리스트

- [ ] [LIVE_ACCOUNT-1] account snapshot domain/contract를 확장한다.
- [ ] [LIVE_ACCOUNT-2] worker broker account sync service와 socket handlers를 추가한다.
- [ ] [LIVE_ACCOUNT-3] API forwarder와 CLI headless account sync workflow를 추가한다.
- [ ] [LIVE_ACCOUNT-4] focused verification과 affected module tests를 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [LIVE_ACCOUNT-1] Domain And Contract

#### 문제

`packages/contracts/proto/alt/v1/paper_trading.proto:36`의 `PaperTradingState`는 backtest run/cash/positions/fills를 담지만 live broker account snapshot에는 broker, account alias, cash balances, positions, synced timestamp, stale flag가 필요하다.

#### 해결 방법

`packages/domain/trading`의 `AccountSnapshot`과 `PositionSnapshot`을 확장하고, `live_trading.proto`에 `SyncLiveAccountRequest/Response`, `GetLiveAccountSnapshotRequest/Response`, `LiveAccountSnapshot`, `LivePositionSnapshot`, `LiveCashBalance`를 추가한다. Raw account number 필드는 두지 않는다. `account_id`는 operator alias 또는 internal id로만 사용한다.

Before:

```proto
// packages/contracts/proto/alt/v1/paper_trading.proto:36
message PaperTradingState {
  string account_id = 1;
  BacktestRun run = 2;
  Price cash = 3;
  repeated BacktestPosition positions = 4;
```

After:

```proto
message LiveAccountSnapshot {
  string account_id = 1;
  string broker = 2;
  repeated LiveCashBalance cash = 3;
  repeated LivePositionSnapshot positions = 4;
  int64 synced_at_unix_ms = 5;
  bool stale = 6;
}
```

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

- [ ] `packages/domain/trading/types.go` 또는 `account.go`를 확장한다.
- [ ] `packages/domain/trading/account_test.go`를 추가한다.
- [ ] `packages/contracts/proto/alt/v1/live_trading.proto`를 확장한다.
- [ ] `bin/contracts-gen`/generated output을 갱신한다.
- [ ] parser maps에 account messages를 등록한다.

#### 테스트 작성

작성한다. Snapshot sorting/stability, raw account number absence, multi-currency cash preservation을 검증한다.

#### 중간 검증

```bash
bin/contracts-gen
bin/contracts-check
go test ./packages/domain/... ./packages/contracts/gen/go/...
```

기대 결과: exit code 0.

### [LIVE_ACCOUNT-2] Worker Sync

#### 문제

Worker `Deps`에는 broker account reader가 없고, paper state reader는 `services/worker/internal/socket/paper.go:110`의 `handleGetPaperTradingState`로 paper service만 호출한다.

#### 해결 방법

`livetrading.Service`에 `SyncLiveAccount(ctx, accountID)`와 `GetLiveAccountSnapshot(ctx, accountID)`를 추가한다. `BrokerPort`에는 `FetchAccountSnapshot` method를 둔다. Sync는 broker snapshot을 sanitized domain snapshot으로 저장하고 Get은 마지막 snapshot을 반환한다. Nil broker/service는 unavailable, missing snapshot은 not_found로 매핑한다.

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

- [ ] `services/worker/internal/livetrading/service.go`를 확장한다.
- [ ] `services/worker/internal/livetrading/service_test.go`에 sync/get tests를 추가한다.
- [ ] `services/worker/internal/socket/live.go`와 `live_mapping.go`를 확장한다.
- [ ] `services/worker/internal/socket/live_test.go`에 sync/get socket tests를 추가한다.

#### 테스트 작성

작성한다. Fake broker snapshot success, missing account, nil broker unavailable, no raw account number output을 검증한다.

#### 중간 검증

```bash
go test ./services/worker/internal/livetrading ./services/worker/internal/socket
```

기대 결과: exit code 0.

### [LIVE_ACCOUNT-3] API And CLI

#### 문제

API/CLI는 paper account state만 forward/render한다. Live account sync action과 stable output fixture가 없다.

#### 해결 방법

API workerclient와 socket live handlers에 sync/get account methods를 추가한다. CLI에는 `sync_live_account`, `get_live_account_snapshot` actions를 추가하고 output에 `live_account_id`, `broker`, `cash_count`, `position_count`, `synced_at_unix_ms`, `stale`을 추가한다. `apps/cli/testdata/operator/live_account_sync.yaml`과 expected JSONL을 추가하고 handoff matrix에 row를 추가한다.

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

- [ ] `services/api/internal/workerclient/client.go`를 확장한다.
- [ ] `services/api/internal/socket/live.go`와 tests를 확장한다.
- [ ] `apps/cli/internal/operator/scenario.go`, `runner.go`, `client.go`, `output.go`를 확장한다.
- [ ] `apps/cli/internal/operator/runner_live_test.go` 또는 `runner_account_test.go`를 추가한다.
- [ ] `apps/cli/testdata/operator/live_account_sync.yaml`과 expected JSONL을 추가한다.
- [ ] `apps/cli/testdata/operator/headless_validation.md`에 account sync row를 추가한다.

#### 테스트 작성

작성한다. CLI sync/get happy path, typed not_found, fixture parse, output key stability를 검증한다.

#### 중간 검증

```bash
go test ./services/api/internal/socket ./apps/cli/internal/operator
```

기대 결과: exit code 0.

### [LIVE_ACCOUNT-4] Verification

#### 문제

Account sync는 contract, worker, API, CLI 모두를 건드린다.

#### 해결 방법

영향 모듈 전체 검증을 실행한다.

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

- [ ] `bin/contracts-check`를 실행한다.
- [ ] `go test ./packages/domain/...`를 실행한다.
- [ ] `go test ./packages/contracts/gen/go/...`를 실행한다.
- [ ] `go test ./services/worker/...`를 실행한다.
- [ ] `go test ./services/api/...`를 실행한다.
- [ ] `go test ./apps/cli/...`를 실행한다.

#### 테스트 작성

추가 테스트는 LIVE_ACCOUNT-1부터 LIVE_ACCOUNT-3에 포함한다.

#### 중간 검증

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

기대 결과: 모두 exit code 0.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/domain/trading/account.go` | LIVE_ACCOUNT-1 |
| `packages/domain/trading/account_test.go` | LIVE_ACCOUNT-1 |
| `packages/contracts/proto/alt/v1/live_trading.proto` | LIVE_ACCOUNT-1 |
| `services/worker/internal/contracts/parser_map.go` | LIVE_ACCOUNT-1 |
| `services/api/internal/contracts/parser_map.go` | LIVE_ACCOUNT-1 |
| `apps/cli/internal/operator/parser_map.go` | LIVE_ACCOUNT-1 |
| `services/worker/internal/livetrading/service.go` | LIVE_ACCOUNT-2 |
| `services/worker/internal/livetrading/service_test.go` | LIVE_ACCOUNT-2 |
| `services/worker/internal/socket/live.go` | LIVE_ACCOUNT-2 |
| `services/worker/internal/socket/live_mapping.go` | LIVE_ACCOUNT-2 |
| `services/worker/internal/socket/live_test.go` | LIVE_ACCOUNT-2 |
| `services/api/internal/workerclient/client.go` | LIVE_ACCOUNT-3 |
| `services/api/internal/socket/live.go` | LIVE_ACCOUNT-3 |
| `services/api/internal/socket/live_test.go` | LIVE_ACCOUNT-3 |
| `apps/cli/internal/operator/scenario.go` | LIVE_ACCOUNT-3 |
| `apps/cli/internal/operator/runner.go` | LIVE_ACCOUNT-3 |
| `apps/cli/internal/operator/client.go` | LIVE_ACCOUNT-3 |
| `apps/cli/internal/operator/output.go` | LIVE_ACCOUNT-3 |
| `apps/cli/internal/operator/runner_live_test.go` | LIVE_ACCOUNT-3 |
| `apps/cli/testdata/operator/live_account_sync.yaml` | LIVE_ACCOUNT-3 |
| `apps/cli/testdata/operator/expected/live_account_sync.jsonl` | LIVE_ACCOUNT-3 |
| `apps/cli/testdata/operator/headless_validation.md` | LIVE_ACCOUNT-3 |

## 최종 검증

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

기대 결과: 모든 명령 exit code 0.

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