<!-- task=m-live-trading-boundary/01_broker_port plan=0 tag=LIVE_PORT -->

# Plan - LIVE_PORT

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

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

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

## 배경

Live Trading Boundary의 첫 작업은 paper/backtest에 묶인 주문 vocabulary를 그대로 실거래에 재사용하지 않고 broker-neutral port를 여는 것이다. 현재 paper order는 `backtest.OrderIntent`와 worker in-memory paper service에 묶여 있으며, KIS-first 결정은 유지하되 live domain/contract/worker 경계는 후속 broker 확장을 막으면 안 된다. 이 계획은 실제 주문 제출 없이 live broker capability와 공통 주문/account/audit vocabulary를 만든다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 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:
  - `broker-port`: broker adapter port가 paper/live 공통 경계를 갖는다.
- 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/private/rules.md`
- `agent-ops/rules/common/rules-roadmap.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/rules/project/domain/operations/rules.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.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`
- `agent-test/local/operations-smoke.md`
- `packages/domain/backtest/types.go`
- `packages/domain/backtest/types_test.go`
- `packages/domain/market/types.go`
- `packages/domain/market/types_test.go`
- `packages/contracts/proto/alt/v1/common.proto`
- `packages/contracts/proto/alt/v1/market.proto`
- `packages/contracts/proto/alt/v1/backtest.proto`
- `packages/contracts/proto/alt/v1/paper_trading.proto`
- `bin/contracts-gen`
- `bin/contracts-check`
- `go.work`
- `packages/domain/go.mod`
- `services/worker/go.mod`
- `services/api/go.mod`
- `apps/cli/go.mod`
- `services/worker/internal/contracts/parser_map.go`
- `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/worker/internal/socket/server_test.go`
- `services/api/internal/contracts/parser_map.go`
- `services/api/internal/socket/handlers.go`
- `services/api/internal/socket/paper.go`
- `services/api/internal/socket/paper_test.go`
- `services/api/internal/socket/server_test.go`
- `services/api/internal/workerclient/client.go`
- `apps/cli/internal/operator/parser_map.go`
- `apps/cli/internal/operator/parser_map_test.go`
- `apps/cli/internal/operator/client.go`

### 테스트 환경 규칙

`test_env=local`을 선택했다. `agent-test/local/rules.md`는 존재하고 읽었으며, 변경 경로가 domain/contracts/worker/api/client/operations를 걸치므로 `domain-model-smoke`, `contracts-smoke`, `worker-smoke`, `api-smoke`, `client-smoke`, `operations-smoke`를 모두 읽었다.

적용 명령은 `go test ./packages/domain/...`, `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`다. `.proto`가 바뀌므로 generated drift 확인은 `bin/contracts-check`로 고정하고, generated Dart contract가 생기거나 바뀌면 `client-smoke` 기준으로 `bin/lint` 실행 가능 여부를 확인한다.

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

- Live broker-neutral domain vocabulary: 기존 테스트 없음. 새 `packages/domain/trading/types_test.go`가 정상/경계 테스트를 추가해야 한다.
- Live protobuf capability surface: 기존 parser map 테스트는 paper 메시지만 포함한다. worker/API/CLI parser map 테스트에 live capability request/response를 추가해야 한다.
- Worker live capability handler: 기존 worker handler registry는 paper까지 등록한다. 새 worker socket test가 live handler registration, nil service unavailable, fake service success를 검증해야 한다.
- API live capability forwarding: 기존 API paper forwarding 테스트만 있다. 새 API live forwarding 테스트가 validation, nil worker, worker timeout/unavailable mapping을 검증해야 한다.

### 심볼 참조

Renamed/removed symbols: none. 새 symbol만 추가한다. 기존 `PaperOrder`, `SubmitPaperOrder`, `paper-trading` 참조는 `rg --sort path`로 확인했고 paper 동작은 변경하지 않는다.

### 분할 판단

split decision policy를 먼저 평가했다. 공유 foundation(domain/proto/capability)은 broad call-site rollout과 분리해야 하므로 multi-plan이 필요하다. 공통 task group은 `m-live-trading-boundary`이고 sibling은 `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가 없다.

### 범위 결정 근거

이 subtask는 실제 live order submit/cancel/fill을 구현하지 않는다. KIS-first는 provider/broker id와 capability fixture로 표현하되 KIS 네트워크 주문 API 호출, secret/env, account number, real order placement는 제외한다. Paper trading의 `backtest.OrderIntent`와 기존 paper socket messages는 호환성을 위해 변경하지 않고, live 전용 `packages/domain/trading`과 `live_trading.proto`를 추가한다.

### 빌드 등급

Build lane `local-G07`, review lane `cloud-G06`. 새 domain/proto/socket capability 표면이 있으나 실제 money movement와 storage가 없고 테스트 명령이 deterministic하게 쪼개져 local 구현 가능하다. Review는 protocol/schema 경계 판단이 있어 cloud가 적합하다.

## 구현 체크리스트

- [ ] [LIVE_PORT-1] broker-neutral live domain model을 추가하고 paper/backtest domain과 분리한다.
- [ ] [LIVE_PORT-2] live capability protobuf surface를 추가하고 generated contract를 갱신한다.
- [ ] [LIVE_PORT-3] worker/API/CLI parser map과 worker/API capability handler/forwarder를 추가한다.
- [ ] [LIVE_PORT-4] domain, contracts, worker, API, CLI focused tests를 추가/갱신하고 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [LIVE_PORT-1] Live Domain Model

#### 문제

현재 공통 주문 개념은 `packages/domain/backtest/types.go:105`의 `OrderSide`, `types.go:112`의 `OrderType`, `types.go:119`의 `OrderIntent`에 있고, paper service는 `services/worker/internal/papertrading/service.go:199`에서 `PaperOrder`를 backtest intent로 보관한다. 이 경계는 paper/backtest 전용이며 live broker capability와 provider 확장성을 표현하지 않는다.

#### 해결 방법

새 패키지 `packages/domain/trading`을 추가한다. 타입은 `Broker`, `BrokerCapability`, `OrderIntent`, `OrderSide`, `OrderType`, `OrderTimeInForce`, `OrderStatus`, `BrokerStatus`, `BrokerOrderRef`, `AccountSnapshot`, `PositionSnapshot`, `AuditEvent`를 포함한다. 주문 type/status는 string 기반으로 두고 알려진 상수만 제공해서 KIS와 후속 broker가 제공하는 주문 유형과 상태를 닫지 않는다. money/quantity는 `market.Price`, `market.Quantity`, `market.Decimal`만 사용하고 float를 쓰지 않는다.

Before:

```go
// packages/domain/backtest/types.go:105
type OrderSide string

const (
	OrderSideBuy  OrderSide = "buy"
	OrderSideSell OrderSide = "sell"
)
```

After:

```go
package trading

import (
	"time"

	"git.toki-labs.com/toki/alt/packages/domain/market"
)

type Broker string

const BrokerKIS Broker = "kis"

type OrderType string

const (
	OrderTypeMarket OrderType = "market"
	OrderTypeLimit  OrderType = "limit"
)

type BrokerCapability struct {
	Broker            Broker
	Markets           map[market.Market]bool
	Venues            map[market.Venue]bool
	OrderTypes        []OrderType
	SupportsAmend     bool
	SupportsCancel    bool
	SupportsAccount   bool
	SupportsAuditTags bool
	CheckedAt         time.Time
}
```

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

- [ ] `packages/domain/trading/types.go`를 새로 만든다.
- [ ] `packages/domain/trading/types_test.go`를 새로 만든다.
- [ ] `packages/domain/backtest/types.go`는 변경하지 않는다.

#### 테스트 작성

작성한다. `TestBrokerCapabilitySupportsVenueAndOrderType`, `TestLiveOrderIntentKeepsCustomBrokerOrderType`, `TestAccountSnapshotRequiresNoFloatMath`를 추가한다. 커스텀 order type 예시는 `"kis_best_limit"`처럼 알려진 상수 밖 문자열도 보존되는지 확인한다.

#### 중간 검증

```bash
go test ./packages/domain/...
```

기대 결과: exit code 0. Go test cache output은 새 파일 검증에는 부적합하므로 구현자는 `go test -count=1 ./packages/domain/...`로 대체해도 되며, 대체 사유를 review stub에 기록한다.

### [LIVE_PORT-2] Live Contract Surface

#### 문제

현재 contract는 `packages/contracts/proto/alt/v1/paper_trading.proto:63`에서 paper order만 정의하고, `bin/contracts-gen:25`의 proto 목록은 `paper_trading.proto`까지만 생성한다. Live broker capability를 요청/응답할 wire surface가 없다.

#### 해결 방법

`packages/contracts/proto/alt/v1/live_trading.proto`를 추가하고 `GetLiveBrokerCapabilitiesRequest`, `GetLiveBrokerCapabilitiesResponse`, `LiveBrokerCapability`, `LiveOrderIntent`, `LiveOrder`, `LiveAccountSnapshot`, `LivePositionSnapshot`, `LiveAuditEvent` 기본 메시지를 정의한다. 이 subtask의 request handler는 capability 조회만 사용하지만, 후속 subtask가 같은 proto를 additive하게 확장할 수 있도록 foundational messages를 둔다. `bin/contracts-gen`의 `protos` 배열에 `alt/v1/live_trading.proto`를 추가하고 `bin/contracts-gen`으로 generated Go/Dart output을 갱신한다. generated 파일은 손으로 편집하지 않는다.

Before:

```bash
# bin/contracts-gen:25
protos=(
  "alt/v1/common.proto"
  "alt/v1/market.proto"
  "alt/v1/backtest.proto"
  "alt/v1/paper_trading.proto"
)
```

After:

```bash
protos=(
  "alt/v1/common.proto"
  "alt/v1/market.proto"
  "alt/v1/backtest.proto"
  "alt/v1/paper_trading.proto"
  "alt/v1/live_trading.proto"
)
```

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

- [ ] `packages/contracts/proto/alt/v1/live_trading.proto`를 추가한다.
- [ ] `bin/contracts-gen`에 proto 목록을 추가한다.
- [ ] `packages/contracts/gen/go/alt/v1/live_trading.pb.go` 등 generated output은 `bin/contracts-gen`으로만 만든다.
- [ ] `apps/client/lib/src/generated/**`가 생성되면 손수 편집하지 않고 산출물 그대로 둔다.

#### 테스트 작성

직접 unit test 파일은 추가하지 않는다. contract 생성/드리프트 검증이 테스트다. Parser map 테스트는 LIVE_PORT-3에서 추가한다.

#### 중간 검증

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

기대 결과: 모두 exit code 0.

### [LIVE_PORT-3] Capability Handler And Parser Maps

#### 문제

worker parser map은 `services/worker/internal/contracts/parser_map.go:36`부터 paper messages만 등록한다. API parser map은 `services/api/internal/contracts/parser_map.go:40`부터 paper messages만 등록한다. CLI parser map도 `apps/cli/internal/operator/parser_map.go:42`부터 paper messages만 등록한다. Worker session handler는 `services/worker/internal/socket/handlers.go:23`에서 paper handlers까지만 append하고, API session handler도 `services/api/internal/socket/handlers.go:32`에서 paper까지만 append한다.

#### 해결 방법

worker에 `services/worker/internal/livetrading` 패키지 또는 `services/worker/internal/socket/live.go`에서 쓰는 좁은 `LiveService` interface를 추가한다. 이 subtask에서는 `GetLiveBrokerCapabilities(ctx)`만 요구한다. `Deps`에 `Live LiveService` 필드를 추가하고 `liveHandlers(deps)`를 `sessionHandlers`에 append한다. API는 `workerclient.WorkerClient`에 `GetLiveBrokerCapabilities`를 추가하고 `services/api/internal/socket/live.go`에서 validation 없이 thin forward한다. CLI parser map에는 live capability request/response를 등록하되 CLI command action은 후속 order/account subtask에서 만든다.

Before:

```go
// services/worker/internal/socket/handlers.go:21
handlers = append(handlers, marketHandlers(deps)...)
handlers = append(handlers, backtestHandlers(deps)...)
handlers = append(handlers, paperHandlers(deps)...)
return handlers
```

After:

```go
handlers = append(handlers, marketHandlers(deps)...)
handlers = append(handlers, backtestHandlers(deps)...)
handlers = append(handlers, paperHandlers(deps)...)
handlers = append(handlers, liveHandlers(deps)...)
return handlers
```

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

- [ ] `services/worker/internal/socket/backtest.go`의 `Deps`에 `Live LiveService`를 추가한다.
- [ ] `services/worker/internal/socket/live.go`를 추가한다.
- [ ] `services/worker/internal/socket/live_mapping.go`를 추가한다.
- [ ] `services/worker/internal/socket/handlers.go`에 `liveHandlers(deps)`를 append한다.
- [ ] `services/worker/internal/contracts/parser_map.go`에 live messages를 등록한다.
- [ ] `services/api/internal/workerclient/client.go`에 live capability forwarder를 추가한다.
- [ ] `services/api/internal/socket/live.go`를 추가한다.
- [ ] `services/api/internal/socket/handlers.go`에 `liveHandlers(worker)`를 append한다.
- [ ] `services/api/internal/contracts/parser_map.go`에 live messages를 등록한다.
- [ ] `apps/cli/internal/operator/parser_map.go`에 live messages를 등록한다.

#### 테스트 작성

작성한다.

- `services/worker/internal/socket/live_test.go`: fake live service success, nil service unavailable, handler registration.
- `services/api/internal/socket/live_test.go`: worker forward, nil worker unavailable, worker timeout mapping.
- `apps/cli/internal/operator/parser_map_test.go`: live messages required list에 추가.
- worker/API parser map 테스트가 별도 파일에 있으면 live messages를 추가한다.

#### 중간 검증

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

기대 결과: exit code 0.

### [LIVE_PORT-4] Focused Verification

#### 문제

이 subtask는 domain, contract, worker, API, CLI parser/handler 경계를 함께 건드린다. 한 모듈 테스트만으로는 generated drift나 handler registration 누락을 잡을 수 없다.

#### 해결 방법

변경 범위별 검증을 모두 실행한다. `bin/contracts-check`는 generated drift가 없음을 보장하고, Go test는 모듈별 focused regression을 확인한다.

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

- [ ] `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_PORT-1, LIVE_PORT-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. Go test cache output은 최종 검증에서는 허용한다. 새 테스트를 추가한 패키지에서 fresh 실행이 필요하면 구현자는 해당 패키지만 `-count=1`로 대체하고 이유를 기록한다. Flutter/Dart toolchain 부재로 `bin/lint`가 실행 불가하면 pass가 아니며 차단 근거를 기록한다.

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/domain/trading/types.go` | LIVE_PORT-1 |
| `packages/domain/trading/types_test.go` | LIVE_PORT-1 |
| `packages/contracts/proto/alt/v1/live_trading.proto` | LIVE_PORT-2 |
| `bin/contracts-gen` | LIVE_PORT-2 |
| `packages/contracts/gen/go/alt/v1/live_trading.pb.go` | LIVE_PORT-2 |
| `apps/client/lib/src/generated/*live_trading*` | LIVE_PORT-2 |
| `services/worker/internal/socket/backtest.go` | LIVE_PORT-3 |
| `services/worker/internal/socket/handlers.go` | LIVE_PORT-3 |
| `services/worker/internal/socket/live.go` | LIVE_PORT-3 |
| `services/worker/internal/socket/live_mapping.go` | LIVE_PORT-3 |
| `services/worker/internal/socket/live_test.go` | LIVE_PORT-3 |
| `services/worker/internal/contracts/parser_map.go` | LIVE_PORT-3 |
| `services/api/internal/workerclient/client.go` | LIVE_PORT-3 |
| `services/api/internal/socket/handlers.go` | LIVE_PORT-3 |
| `services/api/internal/socket/live.go` | LIVE_PORT-3 |
| `services/api/internal/socket/live_test.go` | LIVE_PORT-3 |
| `services/api/internal/contracts/parser_map.go` | LIVE_PORT-3 |
| `apps/cli/internal/operator/parser_map.go` | LIVE_PORT-3 |
| `apps/cli/internal/operator/parser_map_test.go` | LIVE_PORT-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. Go test cache output은 최종 검증에서 허용한다. `bin/contracts-check`가 generator/toolchain 부재로 실행 불가하면 pass가 아니며, `command -v protoc`와 `command -v protoc-gen-go` 결과를 포함해 차단 근거를 기록한다. `bin/lint`가 Flutter/Dart toolchain 부재로 실행 불가하면 `command -v flutter` 결과와 함께 차단 근거를 기록한다.

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