<!-- task=m-us-market-expansion/02+01_kis_us_daily_bars plan=1 tag=REVIEW_KIS_US_DAILY -->

# Implementation Plan - REVIEW_KIS_US_DAILY

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

구현 완료의 마지막 단계는 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용, 결정, 검증 출력으로 채우는 것이다. 검증 명령은 이 plan의 계약이므로 그대로 실행하고 stdout/stderr를 review stub에 붙인다. finalization, archive, `complete.log` 작성은 code-review 전용이다. 사용자-only 결정, 사용자 소유 외부 환경/secret, 또는 범위 충돌로 막히면 active review stub의 `사용자 리뷰 요청`에 근거를 쓰고 멈춘다. 직접 사용자에게 질문하거나 `request_user_input`을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

1차 구현은 `kis.Capability()`가 NASDAQ/NYSE daily bars를 지원한다고 선언했고, worker main은 이 capability를 실제 KIS importer에 연결했다. 그러나 `LiveProvider.FetchDailyBars`는 venue와 무관하게 domestic KIS endpoint와 KR normalizer만 호출한다. 따라서 real worker US import는 gate를 통과한 뒤 `AAPL`을 domestic request로 fetch하고 `KRX:AAPL`/KRW로 저장할 수 있다.

## 사용자 리뷰 요청 흐름

구현 중 차단은 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 직접 사용자에게 질문하지 않는다. code-review가 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## Roadmap Targets

- Milestone: `agent-roadmap/phase/trading-expansion/milestones/us-market-expansion.md`
- Task ids:
  - `us-daily-bars`: KIS를 초기 provider로 사용해 미국장 일봉 데이터를 normalized bar model로 저장
- 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/code-review/SKILL.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-test/local/rules.md`
- `agent-test/local/domain-model-smoke.md`
- `agent-test/local/worker-smoke.md`
- `agent-task/m-us-market-expansion/02+01_kis_us_daily_bars/plan_cloud_G07_0.log`
- `agent-task/m-us-market-expansion/02+01_kis_us_daily_bars/code_review_cloud_G07_0.log`
- `agent-task/archive/2026/06/m-us-market-expansion/01_market_foundation/complete.log`
- `packages/domain/market/types.go`
- `services/worker/cmd/alt-worker/main.go`
- `services/worker/internal/jobs/marketdata_jobs.go`
- `services/worker/internal/jobs/marketdata_jobs_test.go`
- `services/worker/internal/marketdata/importer/importer.go`
- `services/worker/internal/marketdata/importer/importer_test.go`
- `services/worker/internal/providers/kis/daily_itemchartprice.go`
- `services/worker/internal/providers/kis/daily_itemchartprice_test.go`
- `services/worker/internal/providers/kis/live_client.go`
- `services/worker/internal/providers/kis/live_client_test.go`
- `services/worker/internal/socket/market_test.go`
- `services/worker/testdata/providers/kis/overseas_dailyprice_request.sample.json`
- `services/worker/testdata/providers/kis/overseas_dailyprice_response.sample.json`
- `services/worker/testdata/providers/kis/overseas_daily_bars_normalized.expected.json`

### 테스트 환경 규칙

`test_env=local`. `agent-test/local/rules.md`를 읽었고, 변경 범위는 worker + domain-model smoke에 걸린다. 이 follow-up은 worker live provider 경로 보정이므로 기본 검증은 `go test -count=1 ./services/worker/...`와 focused provider test다. 전체 milestone 신뢰를 위해 `bin/test`와 `git diff --check`도 유지한다. live KIS credential smoke는 여전히 범위 밖이며 tracked 파일에 secret을 쓰지 않는다.

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

- 현재 `TestLiveProviderFetchesAndNormalizesDailyBars`는 KRX/domestic path만 검증한다.
- `TestRegisterDailyBarImportHandlerDispatchesUSWatchlist`는 capability가 US request를 importer로 넘기는지만 보고, 실제 `kis.LiveProvider`가 overseas endpoint를 쓰는지 검증하지 않는다.
- `NormalizeOverseasDailyBars` fixture test는 decoder/normalizer만 잠그며 live client request path, TR_ID, params, imported instrument id/currency를 end-to-end로 잠그지 않는다.

### 심볼 참조

Renamed/removed symbol: none. New or changed references to inspect after implementation: `FetchDailyBars`, `InquireDailyItemChartPrice`, new overseas query/client helper, `NormalizeOverseasDailyBars`, `kis.Capability()`.

### 분할 판단

이 task는 기존 split subtask `02+01_kis_us_daily_bars` 안의 review follow-up이다. 디렉터리명 기준 선행 `01_market_foundation`은 `agent-task/archive/2026/06/m-us-market-expansion/01_market_foundation/complete.log`로 충족되었다. Required 이슈가 하나이며 수정 파일과 검증이 `services/worker/internal/providers/kis` 중심으로 좁으므로 새 subtask로 쪼개지 않는다.

### 범위 결정 근거

이 follow-up은 US daily bars를 허용한 live KIS provider path의 misrouting만 고친다. KIS live credential smoke, provider symbol enrichment, ETF 정확 분류, shared query surface, backtest smoke는 후속 split 범위다. 해외 dailyprice 응답에 이름/asset type이 없으면 이 plan에서 외부 조회를 추가하지 말고, instrument id/market/venue/currency/provider symbol과 bars correctness를 우선 고정한다.

### 빌드 등급

`cloud-G07`: 외부 provider request shape, live worker wiring, fixture/httptest evidence가 함께 걸린 결함이며 1차 검증이 놓친 runtime 경로를 복구해야 한다.

## 구현 체크리스트

- [ ] [REVIEW_KIS_US_DAILY-1] `kis.LiveProvider`가 NASDAQ/NYSE daily import를 overseas `dailyprice` endpoint로 fetch/normalize하고, real worker가 허용한 US request를 `NASDAQ:AAPL`/USD daily bars로 저장할 수 있음을 regression test로 증명한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

1. overseas client request helper와 tests를 먼저 추가한다.
2. `LiveProvider.FetchDailyBars` venue branch를 연결한다.
3. importer-backed live provider regression으로 storage result까지 잠근다.

### [REVIEW_KIS_US_DAILY-1] Live KIS Overseas Daily Provider Path

문제: [services/worker/internal/providers/kis/live_client.go](/config/workspace/alt/services/worker/internal/providers/kis/live_client.go:306)는 모든 symbol에 대해 domestic `FetchDailyItemChartPrice`를 호출하고, [services/worker/internal/providers/kis/live_client.go](/config/workspace/alt/services/worker/internal/providers/kis/live_client.go:315)는 KR normalizer를 사용한다. 하지만 [services/worker/internal/providers/kis/daily_itemchartprice.go](/config/workspace/alt/services/worker/internal/providers/kis/daily_itemchartprice.go:32)는 NASDAQ/NYSE capability를 선언하고 [services/worker/cmd/alt-worker/main.go](/config/workspace/alt/services/worker/cmd/alt-worker/main.go:82)는 이 capability를 실제 worker importer에 연결한다.

해결 방법:

```go
// Before services/worker/internal/providers/kis/live_client.go:305
for _, symbol := range request.Selector.Symbols {
    resp, err := p.client.FetchDailyItemChartPrice(ctx, DailyItemChartPriceQuery{
        Symbol: symbol,
        From:   from,
        To:     to,
    })
    ...
    inst := instrumentFromDailyResponse(resp, symbol)
    bars, err := NormalizeDailyBars(resp, inst)
}
```

```go
// After
switch request.Selector.Venue {
case "", market.VenueKRX:
    // existing domestic path, preserved for KR smoke.
case market.VenueNASDAQ, market.VenueNYSE:
    // FetchOverseasDailyPrice with OverseasDailyPricePath/TR_ID,
    // EXCD derived from venue (NASDAQ=NAS, NYSE=NYS), SYMB=symbol,
    // BYMD=request.To.Format(kisDateLayout), then NormalizeOverseasDailyBars.
default:
    return nil, &Error{Kind: ErrorMalformed, Op: "provider", Message: ...}
}
```

수정 파일 및 체크리스트:

- [ ] `services/worker/internal/providers/kis/live_client.go`: add an overseas daily price query/client method using `OverseasDailyPricePath`, `OverseasDailyPriceTRID`, `AUTH`, `EXCD`, `SYMB`, `GUBN`, `BYMD`, `MODP` from the fixture contract.
- [ ] `services/worker/internal/providers/kis/live_client.go`: branch `FetchDailyBars` by venue. KRX keeps the existing domestic path. NASDAQ/NYSE uses overseas dailyprice, `NormalizeOverseasDailyBars`, venue metadata currency, and `InstrumentID("<VENUE>:<SYMBOL>")`.
- [ ] `services/worker/internal/providers/kis/live_client.go`: reject unsupported venues in the provider itself so direct provider tests cannot bypass capability validation and silently fall back to domestic behavior.
- [ ] `services/worker/internal/providers/kis/live_client_test.go`: add `TestInquireOverseasDailyPriceSendsHeadersAndDecodes` asserting path, `tr_id`, `EXCD=NAS`, `SYMB=AAPL`, `BYMD`, and fixture response decode.
- [ ] `services/worker/internal/providers/kis/live_client_test.go`: add `TestLiveProviderFetchesAndNormalizesOverseasDailyBars` asserting a NASDAQ request hits `OverseasDailyPricePath` rather than `DailyItemChartPricePath`, returns `NASDAQ:AAPL`, `MarketUS`, `VenueNASDAQ`, USD close currency, and New York timestamp.
- [ ] `services/worker/internal/providers/kis/live_client_test.go`: add importer-backed regression, for example `TestLiveProviderImportStoresUSDailyBars`, proving `importer.New(provider, store).ImportDailyBars` stores one US instrument and two USD bars for a NASDAQ request.
- [ ] Keep existing KRX `TestLiveProviderFetchesAndNormalizesDailyBars` passing.

테스트 작성: 작성한다. 새 tests는 live credentials 없이 `httptest.Server`와 existing overseas fixture를 사용한다. If overseas dailyprice cannot represent exact asset type, do not add a fake external lookup; assert market/venue/currency/provider symbol and document the asset-type boundary in the review file.

중간 검증:

```bash
go test -count=1 -run 'Test(InquireOverseasDailyPriceSendsHeadersAndDecodes|LiveProviderFetchesAndNormalizesOverseasDailyBars|LiveProviderImportStoresUSDailyBars|LiveProviderFetchesAndNormalizesDailyBars)' ./services/worker/internal/providers/kis
```

## 수정 파일 요약

| 파일 | 항목 |
|---|---|
| `services/worker/internal/providers/kis/live_client.go` | REVIEW_KIS_US_DAILY-1 |
| `services/worker/internal/providers/kis/live_client_test.go` | REVIEW_KIS_US_DAILY-1 |

## 최종 검증

```bash
go test -count=1 -run 'Test(InquireOverseasDailyPriceSendsHeadersAndDecodes|LiveProviderFetchesAndNormalizesOverseasDailyBars|LiveProviderImportStoresUSDailyBars|LiveProviderFetchesAndNormalizesDailyBars)' ./services/worker/internal/providers/kis
go test -count=1 ./services/worker/internal/providers/kis ./services/worker/internal/marketdata/importer ./services/worker/internal/jobs ./services/worker/internal/socket
go test -count=1 ./services/worker/...
bin/test
git diff --check
git diff --name-only -- '*.go' | xargs -r gofmt -l
```

`go test -count=1`은 fresh 실행을 요구한다. 마지막 `gofmt -l` 명령은 출력이 없어야 통과다.

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