<!-- task=m-us-market-expansion/01_market_foundation plan=0 tag=US_FOUNDATION -->

# Implementation Plan - US_FOUNDATION

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

구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 결정, 검증 출력으로 채우는 것이다. 검증을 실행하고 실제 stdout/stderr를 붙이며, active 파일은 그대로 둔 채 리뷰 준비를 보고한다. 구현 중 사용자만 결정할 수 있는 범위 변경, 외부 환경/secret 준비, 또는 계획 충돌이 생기면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 쓰고 멈춘다. 직접 사용자에게 질문하거나 선택지를 제시하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 재실행으로 모을 수 있는 증거 공백은 사용자 리뷰 요청이 아니다. finalization, log rename, `complete.log`, archive 이동은 code-review 전용이다.

## 배경

US Market Expansion은 한국장 market data 구조를 복제하지 않고 market metadata와 provider-neutral instrument model을 확장해야 한다. 현재 `MarketUS`, `VenueNASDAQ`, `VenueNYSE`, `CurrencyUSD` enum은 있지만 instrument에는 ETF/개별주를 구분할 `asset_type`이 없고, persistence/contract도 같은 정보를 보존하지 않는다. 이 선행 작업은 뒤의 KIS US import, shared query, US backtest가 같은 model을 쓰게 하는 foundation이다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 템플릿은 `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/us-market-expansion.md`
- Task ids:
  - `us-instruments`: 미국장 instrument identity와 provider symbol mapping
  - `us-calendar`: 미국장 timezone/currency/venue 기준
- Completion mode: check-on-pass

## 분석 결과

### 읽은 파일

- `agent-ops/rules/project/rules.md`
- `agent-ops/rules/private/rules.md`
- `agent-ops/rules/common/rules-roadmap.md`
- `agent-ops/skills/common/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/trading-expansion/PHASE.md`
- `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.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`
- `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`
- `services/worker/internal/storage/ports.go`
- `services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql`
- `services/worker/internal/storage/postgres/queries/queries.sql`
- `services/worker/internal/storage/postgres/mapping.go`
- `services/worker/internal/storage/postgres/store.go`
- `services/worker/internal/storage/postgres/mapping_test.go`
- `services/worker/internal/storage/postgres/store_test.go`
- `services/worker/testdata/providers/kis/provider_symbols.sample.json`
- `services/worker/testdata/providers/kis/daily_bars_normalized.expected.json`

### 테스트 환경 규칙

`test_env=local`. `agent-test/local/rules.md`는 존재하고 읽었다. 적용 profile은 domain-model-smoke, contracts-smoke, worker-smoke다. 적용 명령은 `go test ./packages/domain/...`, `.proto` 변경 시 `bin/contracts-gen`, `bin/contracts-check`, generated Go 변경 시 `go test ./packages/contracts/gen/go/...`, worker storage/schema 변경 시 `go test ./services/worker/...`와 `bin/worker-storage-check`, milestone confidence용 `bin/test`다. storage integration은 `DATABASE_URL` 또는 Docker-backed infra가 없으면 실행 차단/skip 사유를 review에 기록한다.

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

- `Instrument`의 provider symbol round-trip은 storage mapping 일부로만 간접 검증된다. `asset_type` round-trip test가 필요하다.
- US venue/currency enum은 contracts에 있지만 `asset_type` contract field는 없다. proto/generated drift 검증이 필요하다.
- provider symbol fixture는 KR instruments만 있고 US watchlist/ETF/equity metadata가 없다. fixture vocabulary test 확장이 필요하다.
- market timezone 기준은 KIS normalizer에 KST 상수로만 있다. domain-level market/venue metadata test가 필요하다.

### 심볼 참조

Renamed/removed symbol: none. New symbols only: `AssetType`, `AssetTypeEquity`, `AssetTypeETF`, market/venue metadata helpers, provider capability vocabulary.

### 분할 판단

Split decision policy를 먼저 평가했다. 공유 domain/contract/storage foundation, provider import, query surface, backtest smoke는 서로 다른 도메인과 risk profile을 가진다. 따라서 shared task group은 `m-us-market-expansion`이고 sibling은 `01_market_foundation`(독립), `02+01_kis_us_daily_bars`(01 의존), `03+01_shared_query_surface`(01 의존), `04+02,03_us_backtest_smoke`(02,03 의존)이다. 이 plan은 독립 선행 작업이라 predecessor 없음.

### 범위 결정 근거

이 작업은 domain/contract/storage/fixture foundation만 다룬다. KIS overseas endpoint, live REST client, operator scenario, backtest execution fixture는 후속 split에서 다룬다. 실거래 주문, live trading, Flutter UI는 milestone 범위 제외와 UI gate에 따라 제외한다.

### 빌드 등급

`cloud-G07`: protobuf schema, generated code, storage migration/sqlc, shared domain model이 함께 바뀌는 cross-domain foundation이라 local보다 넓은 contract/storage 리뷰가 필요하다.

## 구현 체크리스트

- [ ] [US_FOUNDATION-1] domain market model에 `AssetType`과 market/venue metadata를 추가하고 KR/US fixture vocabulary를 갱신한다. 검증: watchlist fixture가 `market=us`, `venue`, `currency=USD`, `asset_type`, provider symbol을 분리해 가진다.
- [ ] [US_FOUNDATION-2] contract와 PostgreSQL persistence가 `asset_type`을 round-trip하도록 schema/query/mapping/generated output을 갱신한다. 검증: market/currency/venue/asset_type 차이가 domain model과 contracts에 일관되게 반영된다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [US_FOUNDATION-1] Domain Asset Type And Market Metadata

문제: [packages/domain/market/types.go](/config/workspace/alt/packages/domain/market/types.go:37)의 `Instrument`는 `Market`, `Venue`, `Currency`, `ProviderSymbols`만 보존해 ETF와 개별주를 열어둘 metadata가 없다. KIS fixture [services/worker/testdata/providers/kis/provider_symbols.sample.json](/config/workspace/alt/services/worker/testdata/providers/kis/provider_symbols.sample.json:1)도 KR instruments만 가진다.

해결 방법:

```go
// Before packages/domain/market/types.go:37
type Instrument struct {
    ID              InstrumentID
    Market          Market
    Venue           Venue
    Symbol          string
    Name            string
    Currency        Currency
    ProviderSymbols map[string]string
}
```

```go
// After
type AssetType string

const (
    AssetTypeEquity AssetType = "equity"
    AssetTypeETF    AssetType = "etf"
)

type VenueMetadata struct {
    Venue    Venue
    Market   Market
    Currency Currency
    Timezone string
}

type Instrument struct {
    ID              InstrumentID
    Market          Market
    Venue           Venue
    Symbol          string
    Name            string
    Currency        Currency
    AssetType       AssetType
    ProviderSymbols map[string]string
}
```

수정 파일 및 체크리스트:

- [ ] `packages/domain/market/types.go`: `AssetType`, metadata helper, `Instrument.AssetType` 추가.
- [ ] `packages/domain/market/types_test.go`: KR/US venue metadata, asset type vocabulary, KIS provider vocabulary test 추가.
- [ ] `services/worker/testdata/providers/kis/provider_symbols.sample.json`: KR instruments에 `asset_type: "equity"` 추가, US watchlist selector와 `NASDAQ:AAPL` equity, `NASDAQ:SPY` ETF fixture 추가.
- [ ] `services/worker/internal/providers/kis/daily_itemchartprice_test.go`: fixture view struct와 assertions에 `asset_type` 추가.

테스트 작성: 작성한다. `TestMarketVenueMetadata`와 `TestProviderSymbolsFixtureVocabulary` 확장으로 US watchlist, USD, NASDAQ/NYSE, equity/etf를 검증한다.

중간 검증:

```bash
go test -count=1 ./packages/domain/...
go test -count=1 ./services/worker/internal/providers/kis
```

### [US_FOUNDATION-2] Contract And Storage Asset Type Round Trip

문제: [packages/contracts/proto/alt/v1/market.proto](/config/workspace/alt/packages/contracts/proto/alt/v1/market.proto:9)의 `Instrument`는 `provider_symbols = 7`까지만 있고 `asset_type`이 없다. storage schema [services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql](/config/workspace/alt/services/worker/internal/storage/postgres/migrations/000001_worker_backbone.up.sql:1), query [services/worker/internal/storage/postgres/queries/queries.sql](/config/workspace/alt/services/worker/internal/storage/postgres/queries/queries.sql:1), mapping [services/worker/internal/storage/postgres/mapping.go](/config/workspace/alt/services/worker/internal/storage/postgres/mapping.go:54)도 같은 field를 보존하지 않는다.

해결 방법:

```proto
// Before packages/contracts/proto/alt/v1/market.proto:9
message Instrument {
  string id = 1;
  Market market = 2;
  Venue venue = 3;
  string symbol = 4;
  string name = 5;
  Currency currency = 6;
  map<string, string> provider_symbols = 7;
}
```

```proto
// After
enum AssetType {
  ASSET_TYPE_UNSPECIFIED = 0;
  ASSET_TYPE_EQUITY = 1;
  ASSET_TYPE_ETF = 2;
}

message Instrument {
  string id = 1;
  Market market = 2;
  Venue venue = 3;
  string symbol = 4;
  string name = 5;
  Currency currency = 6;
  map<string, string> provider_symbols = 7;
  AssetType asset_type = 8;
}
```

수정 파일 및 체크리스트:

- [ ] `packages/contracts/proto/alt/v1/common.proto` 또는 `market.proto`: `AssetType` enum 추가 위치를 기존 enum ownership에 맞춰 결정한다.
- [ ] `packages/contracts/proto/alt/v1/market.proto`: `Instrument.asset_type = 8` 추가.
- [ ] `bin/contracts-gen` 실행으로 generated Go/Dart contract를 갱신한다.
- [ ] `services/worker/internal/storage/postgres/migrations/000003_instrument_asset_type.up.sql`/`.down.sql`: 기존 rows를 깨지 않도록 `asset_type TEXT NOT NULL DEFAULT ''` 또는 명시 default 정책 추가.
- [ ] `services/worker/internal/storage/postgres/queries/queries.sql`: insert/select/update에 `asset_type` 포함.
- [ ] `services/worker/internal/storage/postgres/sqlc/*.go`: sqlc generated output 갱신.
- [ ] `services/worker/internal/storage/postgres/mapping.go`: domain <-> sqlc row mapping에 `AssetType` 추가.
- [ ] `services/worker/internal/socket/market_mapping.go`: domain <-> proto mapping에 `AssetType` 추가.
- [ ] `services/worker/internal/storage/postgres/mapping_test.go`: asset_type round-trip test 추가.
- [ ] `services/worker/internal/socket/market_mapping_test.go`: proto mapping round-trip test 추가.

테스트 작성: 작성한다. storage mapping round-trip과 socket proto mapping에서 equity/etf/unspecified를 모두 확인한다.

중간 검증:

```bash
bin/contracts-gen
bin/contracts-check
go test -count=1 ./packages/contracts/gen/go/...
go test -count=1 ./services/worker/internal/storage/postgres ./services/worker/internal/socket
```

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `packages/domain/market/types.go` | US_FOUNDATION-1 |
| `packages/domain/market/types_test.go` | US_FOUNDATION-1 |
| `services/worker/testdata/providers/kis/provider_symbols.sample.json` | US_FOUNDATION-1 |
| `services/worker/internal/providers/kis/daily_itemchartprice_test.go` | US_FOUNDATION-1 |
| `packages/contracts/proto/alt/v1/common.proto` | US_FOUNDATION-2 |
| `packages/contracts/proto/alt/v1/market.proto` | US_FOUNDATION-2 |
| `packages/contracts/gen/**` | US_FOUNDATION-2 |
| `services/worker/internal/storage/postgres/migrations/*.sql` | US_FOUNDATION-2 |
| `services/worker/internal/storage/postgres/queries/queries.sql` | US_FOUNDATION-2 |
| `services/worker/internal/storage/postgres/sqlc/*.go` | US_FOUNDATION-2 |
| `services/worker/internal/storage/postgres/mapping.go` | US_FOUNDATION-2 |
| `services/worker/internal/storage/postgres/mapping_test.go` | US_FOUNDATION-2 |
| `services/worker/internal/socket/market_mapping.go` | US_FOUNDATION-2 |
| `services/worker/internal/socket/market_mapping_test.go` | US_FOUNDATION-2 |

## 최종 검증

```bash
bin/contracts-gen
bin/contracts-check
go test -count=1 ./packages/domain/...
go test -count=1 ./packages/contracts/gen/go/...
go test -count=1 ./services/worker/internal/storage/postgres ./services/worker/internal/socket ./services/worker/internal/providers/kis
bin/worker-storage-check
bin/test
git diff --check
```

`go test` cache는 허용하지 않는다. `bin/worker-storage-check`가 infra 준비 부족으로 실행 불가하면 `command -v`/환경 확인 출력과 함께 차단 또는 skip 근거를 review에 기록한다.

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