<!-- task=m-operator-headless-workflow-validation/02+01_api_market plan=0 tag=OPS -->

# Plan - OPS API And Market Scenario Runner

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

`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고 실제 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 채우고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.

## 배경

`01_scenario_core`가 scenario file과 validate command를 만든 뒤에는 실제 API control plane에 연결해 hello와 market read를 실행해야 한다. API는 `services/api/internal/socket/handlers.go:55`, `market.go:43`, `market.go:68`에서 이미 Hello, ListInstruments, ListBars를 처리하지만 CLI는 `services/api/internal/contracts`를 import할 수 없는 `internal` 경계 밖에 있다. 따라서 CLI-local parser map과 proto-socket API client를 만들어 기존 API/worker rail 위에 얇게 붙인다.

## 사용자 리뷰 요청 흐름

구현 중 blocker는 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/operator-surface/milestones/operator-headless-workflow-validation.md`
- Task ids:
  - `terminal-run`: terminal에서 scenario를 실행하거나 대표 조회를 수행할 수 있다.
  - `market-check`: market data status/import 관련 결과를 화면 없이 확인할 수 있다.
- 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/rules/project/domain/operations/rules.md`
- `agent-ops/rules/project/domain/api/rules.md`
- `agent-ops/rules/project/domain/worker/rules.md`
- `agent-ops/rules/project/domain/contracts/rules.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-roadmap/current.md`
- `agent-roadmap/phase/operator-surface/PHASE.md`
- `agent-roadmap/phase/operator-surface/milestones/operator-headless-workflow-validation.md`
- `agent-test/local/rules.md`
- `agent-test/local/operations-smoke.md`
- `agent-test/local/api-smoke.md`
- `agent-test/local/worker-smoke.md`
- `agent-test/local/contracts-smoke.md`
- `go.work`
- `apps/cli/go.mod`
- `apps/cli/cmd/alt/main.go`
- `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/server.go`
- `services/api/internal/socket/server_test.go`
- `services/api/internal/socket/market.go`
- `services/api/internal/socket/market_test.go`
- `services/api/internal/workerclient/client.go`
- `services/api/internal/workerclient/client_test.go`
- `services/worker/internal/socket/market.go`
- `services/worker/internal/socket/market_test.go`
- `services/worker/internal/contracts/parser_map.go`
- `services/worker/internal/contracts/parser_map_test.go`
- `../proto-socket/go/ws_client.go`
- `../proto-socket/go/communicator.go`
- `../proto-socket/go/base_client.go`
- `../proto-socket/go/examples/ws_echo/main.go`

### 테스트 환경 규칙

- `test_env=local`.
- `agent-test/local/rules.md`는 존재하고 전문을 읽었다. 현재 shell 직접 테스트 금지, 원격 ALT checkout root에서 실행한다.
- 적용 profile: `agent-test/local/operations-smoke.md` because `apps/cli/**` 변경.
- 참고 profile: `api-smoke`, `worker-smoke`, `contracts-smoke`. API/worker/contracts runtime source는 수정하지 않지만, CLI runner가 이 surface를 호출하므로 final `bin/test`가 회귀 baseline이다.
- focused command `(cd apps/cli && go test ./...)`는 `bin/test`에서 앱 모듈 test만 분리한 fallback이다.

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

- CLI-local parser map: 기존 coverage 없음. 새 test가 Hello/ListInstruments/ListBars request/response parser를 검증해야 한다.
- API URL parsing and proto-socket dial: workerclient는 유사 구현을 `services/api/internal/workerclient/client.go:63`에서 갖지만 CLI에서는 재사용할 수 없다. 새 unit test와 in-process fake server test가 필요하다.
- Market scenario output: 기존 API/worker tests는 typed errors와 forwarding을 덮지만 CLI stdout/status contract는 없음. 새 runner/output tests가 필요하다.

### 심볼 참조

- renamed/removed symbol: none.

### 분할 판단

- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `agent-task/m-operator-headless-workflow-validation/`.
- 이 plan의 directory `02+01_api_market`는 predecessor index `01`을 요구한다.
- predecessor `01`: active `agent-task/m-operator-headless-workflow-validation/01_scenario_core/complete.log` 없음, archive complete.log 없음. runtime은 `01_scenario_core` PASS 후 이 plan을 구현해야 한다.

### 범위 결정 근거

- API/worker socket handlers는 기존 request/response와 typed error를 제공하므로 수정하지 않는다.
- 이 plan은 hello, list_instruments, list_bars, market-side unavailable/disconnected output까지만 다룬다. backtest start/list/detail/result/polling은 `03+02_backtest_workflows`로 분리한다.
- Flutter client와 provider live import는 제외한다.

### 빌드 등급

- build lane: `cloud-G07`, review lane: `cloud-G07`.
- 이유: proto-socket connection, terminal command, stdout/error/exit code contract가 있는 CLI workflow 작업이다.

## 구현 체크리스트

- [ ] `01_scenario_core`의 `complete.log`가 active 또는 archive에 있는지 확인하고, 없으면 구현을 시작하지 않고 review stub에 blocker를 기록한다.
- [ ] CLI-local ALT parser map과 API URL dialer를 추가한다.
- [ ] `alt operator scenario run --file <path> --api-url <ws-url>` command를 추가하고 exit code와 JSONL/grep 가능한 summary 출력을 고정한다.
- [ ] `api_connection_smoke`와 `market_data_status_query` scenario action을 구현한다. 검증: CLI 또는 script가 exit code와 요약 출력을 남기고 fixture/local smoke가 market 상태 출력을 확인한다.
- [ ] connection failure, typed ErrorInfo, expectation mismatch가 구분되는 출력 테스트를 추가한다.
- [ ] 원격 ALT checkout root에서 focused CLI test와 operations smoke 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 의존 관계 및 구현 순서

- 이 directory 이름의 runtime dependency는 `02+01_api_market`이다.
- 구현 시작 전 `agent-task/m-operator-headless-workflow-validation/01_scenario_core/complete.log` 또는 `agent-task/archive/*/*/m-operator-headless-workflow-validation/01_scenario_core/complete.log`가 있어야 한다.
- 추가 hidden dependency를 만들지 않는다.

### [OPS-1] CLI Parser Map And API Client

#### 문제

`services/api/internal/contracts/parser_map.go:14`와 `services/worker/internal/contracts/parser_map.go:10`은 ALT message parser list를 갖고 있지만 Go `internal` 경계 때문에 `apps/cli`가 import할 수 없다. proto-socket client dial API는 `../proto-socket/go/ws_client.go:58`과 `:62`에 있고, typed request는 `../proto-socket/go/communicator.go:357`에 있다.

#### 해결 방법

- `apps/cli/internal/operator/parser_map.go`에 CLI-local `ParserMap()`을 추가한다. API/worker parser map의 current ALT message set을 복제하되, CLI package 안에서 관리한다.
- `apps/cli/internal/operator/client.go`에 `APIClient`를 추가한다.
- URL parsing은 `services/api/internal/workerclient/client.go:71`의 방식과 같게 `net/url`, `net.SplitHostPort`, default port를 사용한다.

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

- [ ] `apps/cli/internal/operator/parser_map.go`: Hello, market, backtest message parser factories 추가.
- [ ] `apps/cli/internal/operator/client.go`: ws/wss URL parsing, heartbeat config, `Close`, `Hello`, `ListInstruments`, `ListBars` methods 추가.
- [ ] `apps/cli/internal/operator/client_test.go`: fake proto-socket server로 hello/list market round-trip 검증.
- [ ] `apps/cli/go.mod`, `apps/cli/go.sum`: direct imports에 맞게 `nhooyr.io/websocket`, `google.golang.org/protobuf` 필요 여부 반영.

#### 테스트 작성

- 작성: `apps/cli/internal/operator/parser_map_test.go`
  - `TestParserMapIncludesRunnerMessages`: Hello/ListInstruments/ListBars와 backtest messages parser coverage.
- 작성: `apps/cli/internal/operator/client_test.go`
  - `TestAPIClientHello`: fake server hello round-trip.
  - `TestAPIClientListInstruments`: market request fields round-trip.
  - `TestAPIClientConnectUnavailable`: refused port maps to transport status.

#### 중간 검증

원격 ALT checkout root 기준:

```bash
(cd apps/cli && go test ./internal/operator -run 'Test(ParserMap|APIClient)')
```

기대 결과: exit code 0. fake server tests pass without external services.

### [OPS-2] Run Command, Market Actions, And Output Contract

#### 문제

`apps/cli/cmd/alt/main.go:13` currently prints a static banner for non-version commands. There is no terminal entrypoint that turns a scenario into API requests or outputs machine-readable status keys.

#### 해결 방법

- `apps/cli/internal/operator/runner.go`를 추가해 scenario steps를 실행한다.
- action support:
  - `hello`
  - `list_instruments`
  - `list_bars`
- `apps/cli/internal/operator/output.go`는 JSONL을 기본으로 출력한다.
- Exit codes:
  - `0`: all steps matched expectations.
  - `1`: typed `ErrorInfo` or expectation mismatch when not expected.
  - `2`: flags/scenario parse/validation error.
  - `3`: transport connect/disconnect/timeout before typed response.
- `invalid_request_matrix`처럼 expected error를 선언한 scenario는 expected error가 오면 exit code 0이다.

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

- [ ] `apps/cli/internal/cli/cli.go`: `operator scenario run` flags `--file`, `--api-url`, `--timeout`, `--output` 추가.
- [ ] `apps/cli/internal/operator/scenario.go`: market action fields and expectations 추가.
- [ ] `apps/cli/internal/operator/runner.go`: hello/list_instruments/list_bars 실행과 expectation evaluation 구현.
- [ ] `apps/cli/internal/operator/output.go`: JSONL event and summary writer 구현.
- [ ] `apps/cli/testdata/operator/market_data_status_query.yaml`: market fixture 추가.
- [ ] `apps/cli/internal/cli/cli_test.go`: run command output/exit code tests 추가.
- [ ] `apps/cli/internal/operator/runner_market_test.go`: market response success, empty, typed error, transport error tests 추가.

#### 테스트 작성

- 작성/확장:
  - `TestRunScenarioAPISmoke`: fake API server hello response produces `status=ok`.
  - `TestRunScenarioMarketDataStatus`: list_instruments/list_bars output includes `scenario`, `step`, `action`, `status`, `count`.
  - `TestRunScenarioTransportFailure`: unavailable socket exits 3 and prints `status=transport_error`.
  - `TestRunScenarioExpectedTypedError`: expected `invalid_request` exits 0 and prints `error.code`.

#### 중간 검증

원격 ALT checkout root 기준:

```bash
(cd apps/cli && go test ./...)
```

기대 결과: exit code 0. CLI tests prove run command, output contract, and market scenarios without live worker credentials.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/cli/go.mod` | OPS-1 |
| `apps/cli/go.sum` | OPS-1 |
| `apps/cli/internal/operator/parser_map.go` | OPS-1 |
| `apps/cli/internal/operator/parser_map_test.go` | OPS-1 |
| `apps/cli/internal/operator/client.go` | OPS-1 |
| `apps/cli/internal/operator/client_test.go` | OPS-1 |
| `apps/cli/internal/cli/cli.go` | OPS-2 |
| `apps/cli/internal/cli/cli_test.go` | OPS-2 |
| `apps/cli/internal/operator/scenario.go` | OPS-2 |
| `apps/cli/internal/operator/runner.go` | OPS-2 |
| `apps/cli/internal/operator/output.go` | OPS-2 |
| `apps/cli/internal/operator/runner_market_test.go` | OPS-2 |
| `apps/cli/testdata/operator/market_data_status_query.yaml` | OPS-2 |

## 최종 검증

원격 ALT checkout root 기준으로 실행한다. CLI output/exit code tests should be fresh; use `-count=1`.

```bash
(cd apps/cli && go test -count=1 ./...)
bin/test
```

기대 결과: 모든 명령 exit code 0. 원격 host나 checkout 접근 불가 시 pass가 아니라 차단 사유와 미실행 명령을 review stub에 남긴다.

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