<!-- task=m-command-first-operator-workflow/01_import_contract_worker_api plan=0 tag=API -->

# Plan - Import Contract Worker API

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

이 plan은 구현 지시서이며, 구현 완료 전 active `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 반드시 실제 내용과 검증 출력으로 채운다. 검증은 아래 명령을 실행하고 실제 stdout/stderr를 review stub에 붙인다. 최종 판정, archive 이동, `complete.log` 작성은 code-review-skill 전용이다. 사용자 결정, 사용자 소유 외부 환경/secret, 또는 범위 충돌 없이는 진행할 수 없을 때만 review stub의 `사용자 리뷰 요청`을 채우고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

현재 KIS provider와 importer는 준비되어 있지만 operator가 API/worker socket을 통해 daily bars import를 명령할 contract가 없다. 이 작업은 CLI/YAML이 호출할 수 있는 typed request/response와 API forwarding, worker import handler를 추가한다. 이 foundation만으로는 마일스톤의 `live-import-command`를 완료하지 않으며, 후속 `02+01_import_cli_scenario`가 command surface를 완성한다.

## 사용자 리뷰 요청 흐름

구현 중 사용자-only blocker는 active review stub의 `사용자 리뷰 요청` 섹션에 기록한다. 이 섹션은 `agent-ops/skills/common/_templates/implementation-user-review-request-section.md` 기준이며, code-review가 검증 후 실제 `USER_REVIEW.md` 작성 여부를 결정한다.

## 분석 결과

### 읽은 파일

- `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-ops/skills/common/_templates/implementation-user-review-request-section.md`
- `agent-test/local/rules.md`
- `agent-test/local/contracts-smoke.md`
- `agent-test/local/api-smoke.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/operations-smoke.md`
- `agent-ops/rules/project/domain/contracts/rules.md`
- `agent-ops/rules/project/domain/api/rules.md`
- `agent-ops/rules/project/domain/worker/rules.md`
- `agent-ops/rules/project/domain/operations/rules.md`
- `agent-ops/rules/private/kis-api-local-cache.md`
- `agent-ops/rules/private/testing-env.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/operator-surface/PHASE.md`
- `agent-roadmap/phase/operator-surface/milestones/command-first-operator-workflow.md`
- `packages/contracts/proto/alt/v1/common.proto`
- `packages/contracts/proto/alt/v1/market.proto`
- `packages/contracts/proto/alt/v1/backtest.proto`
- `services/api/internal/contracts/parser_map.go`
- `services/api/internal/contracts/parser_map_test.go`
- `services/api/internal/socket/handlers.go`
- `services/api/internal/socket/market.go`
- `services/api/internal/socket/market_test.go`
- `services/api/internal/socket/backtest_test.go`
- `services/api/internal/socket/server_test.go`
- `services/api/internal/workerclient/client.go`
- `services/api/internal/workerclient/client_test.go`
- `services/worker/internal/contracts/parser_map.go`
- `services/worker/internal/contracts/parser_map_test.go`
- `services/worker/internal/socket/handlers.go`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/market.go`
- `services/worker/internal/socket/market_mapping.go`
- `services/worker/internal/socket/market_test.go`
- `services/worker/internal/socket/server_test.go`
- `services/worker/internal/jobs/marketdata_jobs.go`
- `services/worker/internal/jobs/runner.go`
- `services/worker/internal/marketdata/importer/importer.go`
- `services/worker/cmd/alt-worker/main.go`
- `bin/contracts-gen`
- `bin/contracts-check`
- `bin/test`
- `go.work`
- `services/api/go.mod`
- `services/worker/go.mod`
- `packages/contracts/gen/go/go.mod`

### 테스트 환경 규칙

- test_env: `local`.
- `agent-test/local/rules.md` present/read. Matched profiles: `contracts-smoke`, `api-smoke`, `worker-smoke`, `operations-smoke`.
- Applied commands: `bin/contracts-gen` for proto changes, `bin/contracts-check`, `go test ./packages/contracts/gen/go/...`, `go test ./services/api/...`, `go test ./services/worker/...`.
- Fallback: none. No `<확인 필요>` command values in matched profiles.

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

- Import protobuf parser coverage: existing parser map tests cover required messages but must be extended for `ImportDailyBarsRequest/Response`.
- API forwarding: existing market handler and workerclient tests cover list requests; import request tests are missing and must be added.
- Worker import handler: existing market read tests and importer/job tests cover adjacent behavior; direct socket import command tests are missing and must be added.
- Live KIS end-to-end command smoke remains outside this foundation; fake importer tests are sufficient here because KIS provider live smoke is already covered by the completed predecessor milestone.

### 심볼 참조

- Renamed/removed symbols: none. This is additive.
- New message registration call sites must be added in API, worker, and CLI parser maps so proto-socket decoding does not drift.

### 분할 판단

- Split policy evaluated before writing files.
- Shared task group: `agent-task/m-command-first-operator-workflow/`.
- Siblings:
  - `01_import_contract_worker_api`: foundation, no predecessor.
  - `02+01_import_cli_scenario`: waits for this contract/API/worker rail.
  - `03+02_market_status_scenario`: waits for import CLI scenario.
  - `04+02_backtest_runtime_flow`: waits for import CLI scenario.
  - `05+03,04_operator_handoff`: waits for status and backtest flow.
- This plan has no predecessor. It intentionally has no `Roadmap Targets` because it does not by itself complete a Milestone 기능 Task.

### 범위 결정 근거

- Excludes CLI YAML action implementation; that belongs to `02+01_import_cli_scenario`.
- Excludes market status fixture updates; that belongs to `03+02_market_status_scenario`.
- Excludes backtest runtime wiring and strategy resolver; that belongs to `04+02_backtest_runtime_flow`.
- Excludes Flutter UI, wireframes, production scheduling, and raw KIS secret handling per milestone scope.

### 빌드 등급

- build=`cloud-G08`, review=`cloud-G08`: additive but cross-domain protobuf/API/worker protocol work with parser registration and generated-output drift risk.

## 구현 체크리스트

- [ ] [API-1] Add `ImportDailyBarsRequest/Response` protobuf messages, regenerate contracts, and register parsers in API/worker/CLI maps.
- [ ] [API-2] Add API workerclient and API socket forwarding for import daily bars with typed unavailable/timeout/internal errors.
- [ ] [API-3] Add worker socket import handler that validates request shape, calls the importer dependency, and returns instrument/bar counts.
- [ ] [API-4] Wire `alt-worker` store-backed deps with the KIS importer command surface without exposing secrets.
- [ ] [API-5] Run focused contract/API/worker verification and `git diff --check`.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## [API-1] Contract And Parser Surface

### 문제

[packages/contracts/proto/alt/v1/market.proto](/config/workspace/alt/packages/contracts/proto/alt/v1/market.proto:39) only has market read messages. There is no typed import command request/response for CLI/API/worker use. Parser maps currently register only list/read and backtest messages in [services/api/internal/contracts/parser_map.go](/config/workspace/alt/services/api/internal/contracts/parser_map.go:18), [services/worker/internal/contracts/parser_map.go](/config/workspace/alt/services/worker/internal/contracts/parser_map.go:14), and [apps/cli/internal/operator/parser_map.go](/config/workspace/alt/apps/cli/internal/operator/parser_map.go:20).

### 해결 방법

Add messages after `ListBarsResponse` using new field numbers only.

Before (`market.proto:49`):

```proto
message ListBarsRequest {
  string instrument_id = 1;
  Timeframe timeframe = 2;
  int64 from_unix_ms = 3;
  int64 to_unix_ms = 4;
}
```

After:

```proto
message ImportDailyBarsRequest {
  string provider = 1;
  string selector_kind = 2;
  Market market = 3;
  Venue venue = 4;
  string name = 5;
  repeated string symbols = 6;
  string from_yyyymmdd = 7;
  string to_yyyymmdd = 8;
}

message ImportDailyBarsResponse {
  string provider = 1;
  int32 instrument_count = 2;
  int32 bar_count = 3;
  ErrorInfo error = 4;
}
```

Run `bin/contracts-gen`. Do not hand-edit generated files.

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

- [ ] `packages/contracts/proto/alt/v1/market.proto`: add request/response.
- [ ] `packages/contracts/gen/go/alt/v1/market.pb.go`: regenerated only.
- [ ] `apps/client/lib/src/generated/alt/v1/market.pb*.dart`: regenerated only if `bin/contracts-gen` updates them.
- [ ] `services/api/internal/contracts/parser_map.go`: register both messages.
- [ ] `services/api/internal/contracts/parser_map_test.go`: require both messages.
- [ ] `services/worker/internal/contracts/parser_map.go`: register both messages.
- [ ] `services/worker/internal/contracts/parser_map_test.go`: require both messages.
- [ ] `apps/cli/internal/operator/parser_map.go`: register both messages for later CLI action.
- [ ] `apps/cli/internal/operator/parser_map_test.go`: require at least the response parser.

### 테스트 작성

Write/extend parser map tests in API, worker, and CLI to assert `protoSocket.TypeNameOf(&altv1.ImportDailyBarsRequest{})` and response are registered and parse.

### 중간 검증

```bash
bin/contracts-check
go test ./packages/contracts/gen/go/... -count=1
go test ./services/api/internal/contracts ./services/worker/internal/contracts ./apps/cli/internal/operator -run ParserMap -count=1
```

Expected: all commands exit 0.

## [API-2] API Forwarding

### 문제

[services/api/internal/workerclient/client.go](/config/workspace/alt/services/api/internal/workerclient/client.go:25) has no import method in `WorkerClient`, and [services/api/internal/socket/market.go](/config/workspace/alt/services/api/internal/socket/market.go:22) only registers list/read handlers.

### 해결 방법

Add `ImportDailyBars(ctx, *altv1.ImportDailyBarsRequest)` to `WorkerClient`, implement it as a one-line `sendTyped`, and add an API market handler that validates minimal shape then forwards. Validation should reject empty provider, empty selector kind, empty symbols, unsupported market/venue, and malformed date strings before connecting to worker.

Before (`client.go:39`):

```go
// Market query surface.
ListInstruments(ctx context.Context, req *altv1.ListInstrumentsRequest) (*altv1.ListInstrumentsResponse, error)
ListBars(ctx context.Context, req *altv1.ListBarsRequest) (*altv1.ListBarsResponse, error)
```

After:

```go
ImportDailyBars(ctx context.Context, req *altv1.ImportDailyBarsRequest) (*altv1.ImportDailyBarsResponse, error)
ListInstruments(ctx context.Context, req *altv1.ListInstrumentsRequest) (*altv1.ListInstrumentsResponse, error)
ListBars(ctx context.Context, req *altv1.ListBarsRequest) (*altv1.ListBarsResponse, error)
```

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

- [ ] `services/api/internal/workerclient/client.go`: interface and socket client method.
- [ ] `services/api/internal/workerclient/client_test.go`: fake worker server round-trip and unavailable guard.
- [ ] `services/api/internal/socket/market.go`: import handler registration, validation, forwarding, nil response mapping.
- [ ] `services/api/internal/socket/market_test.go`: forwarding, validation, nil worker, connect failure.
- [ ] `services/api/internal/socket/backtest_test.go`: update `fakeWorkerClient` to implement the new interface.
- [ ] `services/api/internal/socket/server_test.go`: capability/handler sync if capability is advertised.

### 테스트 작성

Add `TestWorkerClient_ImportDailyBars_Success`, `TestHandleImportDailyBarsForwards`, validation table, nil worker response, and socket typed validation tests.

### 중간 검증

```bash
go test ./services/api/... -count=1
```

Expected: API tests pass and no fake worker interface compile failure remains.

## [API-3] Worker Import Handler

### 문제

[services/worker/internal/socket/market.go](/config/workspace/alt/services/worker/internal/socket/market.go:23) only exposes reads. [services/worker/internal/marketdata/importer/importer.go](/config/workspace/alt/services/worker/internal/marketdata/importer/importer.go:73) already returns import counts, but no socket handler returns them.

### 해결 방법

Add a narrow socket dependency interface for `ImportDailyBars`, add it to `Deps`, register `ImportDailyBarsRequest`, validate and convert request fields into `importer.DailyBarRequest`, call the importer, and map errors to `ErrorInfo`. Add `venueFromProto` next to `venueToProto`.

Before (`backtest.go:38`):

```go
type Deps struct {
    Starter     BacktestStarter
    Analysis    storage.BacktestAnalysisStore
    Results     storage.BacktestResultStore
    Instruments storage.InstrumentStore
    Bars        storage.BarStore
}
```

After:

```go
type DailyBarImporter interface {
    ImportDailyBars(ctx context.Context, request importer.DailyBarRequest) (importer.Result, error)
}

type Deps struct {
    Starter      BacktestStarter
    Analysis     storage.BacktestAnalysisStore
    Results      storage.BacktestResultStore
    Instruments  storage.InstrumentStore
    Bars         storage.BarStore
    DailyBarImport DailyBarImporter
}
```

Use a gofmt-friendly field name such as `DailyBarImporter` if preferred; keep the capability name stable.

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

- [ ] `services/worker/internal/socket/backtest.go`: add dependency interface/field.
- [ ] `services/worker/internal/socket/market.go`: handler registration, validation, conversion, error mapping.
- [ ] `services/worker/internal/socket/market_mapping.go`: add `venueFromProto`.
- [ ] `services/worker/internal/socket/market_test.go`: fake importer, success counts, validation, unavailable, socket round-trip.
- [ ] `services/worker/internal/socket/server_test.go`: capability expectations.

### 테스트 작성

Add tests for:

- success count response from fake importer,
- request conversion provider/selector/date range,
- missing provider/selector/symbol and unsupported market/venue,
- importer nil returns `unavailable`,
- socket request returns typed response without timeout.

### 중간 검증

```bash
go test ./services/worker/internal/socket -count=1
```

Expected: worker socket tests pass.

## [API-4] Worker Entrypoint Wiring

### 문제

[services/worker/cmd/alt-worker/main.go](/config/workspace/alt/services/worker/cmd/alt-worker/main.go:40) creates the KIS live provider and registers the job handler, but [services/worker/cmd/alt-worker/main.go](/config/workspace/alt/services/worker/cmd/alt-worker/main.go:42) does not expose the importer through socket deps.

### 해결 방법

Create one importer instance and pass it both to `jobs.RegisterDailyBarImportHandler` and the socket dependency field. Do not log app key, token, account, raw headers, or secret item names. Add an `import_daily_bars` or `market-import` capability only when the importer dependency exists.

Before (`main.go:40`):

```go
kisProvider := kis.NewLiveProvider(kis.NewClient(kis.ConfigFromEnv()))
jobs.RegisterDailyBarImportHandler(runner, market.ProviderKIS, importer.New(kisProvider, store))
deps = socket.Deps{
    Analysis:    store,
    Results:     store,
    Instruments: store,
    Bars:        store,
}
```

After:

```go
kisImporter := importer.New(kisProvider, store)
jobs.RegisterDailyBarImportHandler(runner, market.ProviderKIS, kisImporter)
deps = socket.Deps{
    Analysis:          store,
    Results:           store,
    Instruments:       store,
    Bars:              store,
    DailyBarImporter:  kisImporter,
}
```

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

- [ ] `services/worker/cmd/alt-worker/main.go`: reuse importer and set deps.
- [ ] `services/worker/internal/socket/handlers.go`: add capability if implemented.
- [ ] `services/worker/internal/socket/server_test.go`: capability only present when import dependency exists.

### 테스트 작성

Entrypoint has no current direct main test. Cover behavior through `capabilitiesForDeps` and worker socket handler tests.

### 중간 검증

```bash
go test ./services/worker/... -count=1
```

Expected: worker module passes.

## [API-5] Focused Verification

### 문제

Protocol and parser changes can pass local unit tests while generated files drift. The matched contract profile requires generated drift checks.

### 해결 방법

Run the exact final commands and paste output into the review stub.

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

- [ ] No extra files.

### 테스트 작성

No new test beyond items above.

### 중간 검증

```bash
git diff --check
```

Expected: no whitespace or patch-format warnings.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `packages/contracts/proto/alt/v1/market.proto` | API-1 |
| `packages/contracts/gen/go/alt/v1/market.pb.go` | API-1 |
| `apps/client/lib/src/generated/alt/v1/market.pb*.dart` | API-1 |
| `services/api/internal/contracts/parser_map.go` | API-1 |
| `services/api/internal/contracts/parser_map_test.go` | API-1 |
| `services/worker/internal/contracts/parser_map.go` | API-1 |
| `services/worker/internal/contracts/parser_map_test.go` | API-1 |
| `apps/cli/internal/operator/parser_map.go` | API-1 |
| `apps/cli/internal/operator/parser_map_test.go` | API-1 |
| `services/api/internal/workerclient/client.go` | API-2 |
| `services/api/internal/workerclient/client_test.go` | API-2 |
| `services/api/internal/socket/market.go` | API-2 |
| `services/api/internal/socket/market_test.go` | API-2 |
| `services/api/internal/socket/backtest_test.go` | API-2 |
| `services/api/internal/socket/server_test.go` | API-2 |
| `services/worker/internal/socket/backtest.go` | API-3 |
| `services/worker/internal/socket/market.go` | API-3 |
| `services/worker/internal/socket/market_mapping.go` | API-3 |
| `services/worker/internal/socket/market_test.go` | API-3 |
| `services/worker/internal/socket/server_test.go` | API-3, API-4 |
| `services/worker/internal/socket/handlers.go` | API-4 |
| `services/worker/cmd/alt-worker/main.go` | API-4 |

## 최종 검증

```bash
bin/contracts-check
go test ./packages/contracts/gen/go/... -count=1
go test ./services/api/... -count=1
go test ./services/worker/... -count=1
git diff --check
```

Expected: all commands exit 0. Fresh `go test -count=1` is required; cached output is not acceptable for this protocol change.

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