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

# Plan - OPS Scenario Core

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

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

## 배경

현재 마일스톤은 Flutter 화면 전에 terminal/YAML 기반 운영 흐름을 먼저 검증해야 한다. `apps/cli/cmd/alt/main.go:8`은 `version`과 banner만 처리하므로 operator scenario 파일을 표현하거나 dry-run 검증할 경로가 없다. 이 작업은 API/worker 실행 전에도 검증 가능한 scenario schema와 CLI command skeleton을 먼저 만든다.

## 사용자 리뷰 요청 흐름

구현 중 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:
  - `scenario-format`: operator scenario를 YAML 또는 동등한 파일 형식으로 표현한다.
- 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/router.md`
- `agent-ops/skills/common/plan/SKILL.md`
- `agent-ops/skills/common/_templates/implementation-user-review-request-section.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`
- `.gitignore`
- `go.work`
- `go.work.sum`
- `bin/test`
- `bin/lint`
- `bin/build`
- `bin/dev`
- `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`
- `packages/contracts/gen/go/go.mod`
- `services/api/go.mod`
- `services/api/internal/config/config.go`
- `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/socket/backtest.go`
- `services/api/internal/socket/backtest_test.go`
- `services/api/internal/workerclient/client.go`
- `services/api/internal/workerclient/client_test.go`
- `services/worker/go.mod`
- `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/server.go`
- `services/worker/internal/socket/market.go`
- `services/worker/internal/socket/market_test.go`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/backtest_test.go`
- `services/worker/internal/socket/market_mapping.go`
- `services/worker/internal/socket/backtest_mapping.go`
- `services/worker/internal/storage/ports.go`
- `../proto-socket/go/go.mod`
- `../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`는 존재하고 전문을 읽었다.
- local 검증은 현재 shell에서 직접 실행 금지다. 모든 검증 명령은 `ssh toki@toki-labs.com` 접속 후 원격 ALT checkout root에서 실행해야 한다.
- 적용 profile: `agent-test/local/operations-smoke.md`. `apps/cli/**`는 operations domain이고 profile의 smoke는 `bin/test`, full-cycle은 `bin/build`다.
- 참고 profile: `api-smoke`, `worker-smoke`, `contracts-smoke`를 읽었다. 이번 subtask는 API/worker/contracts source를 수정하지 않으므로 필수 검증으로 적용하지 않는다.
- focused command `(cd apps/cli && go test ./...)`는 `bin/test`가 `apps/cli`에서 실행하는 module test를 분리한 fallback이다. 원격 root 기준 실행한다.
- `<확인 필요>` 값은 없었다.

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

- Scenario YAML schema parse/dry-run: 기존 테스트 없음. 새 `apps/cli/internal/operator/scenario_test.go`가 정상/누락 필드/unknown action/invalid duration을 덮어야 한다.
- CLI command dispatch and exit code: 기존 `apps/cli` 테스트 없음. 새 `apps/cli/internal/cli/cli_test.go`가 validate 성공, parse 실패 exit code, 기존 `version` 호환을 덮어야 한다.
- API/worker runtime request behavior: 기존 API/worker socket tests가 handler registration, typed errors, forwarding을 덮는다. 이 subtask에서는 runtime request 실행을 만들지 않는다.

### 심볼 참조

- renamed/removed symbol: none.

### 분할 판단

- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `agent-task/m-operator-headless-workflow-validation/`.
- sibling subtasks:
  - `01_scenario_core`: scenario format, parser, CLI skeleton. no dependencies.
  - `02+01_api_market`: API connection and market scenario runner. depends on `01`.
  - `03+02_backtest_workflows`: backtest actions, polling, result summary, typed error matrix. depends on `02`.
  - `04+02,03_evidence_handoff`: fixture/expected-output handoff and dev entrypoint notes. depends on `02`, `03`.
- 이 plan은 `01_scenario_core`라 predecessor가 없다.

### 범위 결정 근거

- `services/api/**`, `services/worker/**`, `packages/contracts/proto/**`는 수정하지 않는다. 기존 proto-socket handler surface가 current milestone scenario에 필요한 request/response를 이미 제공한다.
- Flutter UI, chart, dashboard layout, push notification은 milestone 범위 제외와 Phase gate에 따라 제외한다.
- live provider/KIS credential 흐름은 제외한다. scenario format은 secret-free fixture로 검증한다.

### 빌드 등급

- build lane: `cloud-G07`, review lane: `cloud-G07`.
- 이유: CLI workflow, YAML parsing, stdout/exit-status contract를 설계하는 terminal-agent 작업이다.

## 구현 체크리스트

- [ ] `apps/cli`에 operator scenario schema, YAML parser, dry-run validation을 추가한다. 검증: 예시 scenario가 parser 또는 dry-run 검증을 통과한다.
- [ ] `alt operator scenario validate --file <path>` command를 추가하고 exit code와 summary 출력을 고정한다.
- [ ] scenario parser와 CLI validate command 단위 테스트를 추가한다.
- [ ] 원격 ALT checkout root에서 focused CLI test와 operations smoke 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

### [OPS-1] CLI Command Skeleton And Scenario Parser

#### 문제

`apps/cli/cmd/alt/main.go:8`은 `version`만 특별 처리하고 나머지는 `ALT operator CLI` banner를 출력한다. 현재 `apps/cli/go.mod:1`에는 module/go version만 있어 contracts, proto-socket, YAML parser 의존성이 없다.

Before:

```go
// apps/cli/cmd/alt/main.go:8
func main() {
	if len(os.Args) > 1 && os.Args[1] == "version" {
		fmt.Println("alt dev")
		return
	}
	fmt.Println("ALT operator CLI")
}
```

#### 해결 방법

- `apps/cli/cmd/alt/main.go`를 thin entrypoint로 바꾸고 `internal/cli.Run(args, stdout, stderr) int`로 위임한다.
- `apps/cli/internal/cli/cli.go`를 추가한다.
- `apps/cli/internal/operator/scenario.go`를 추가해 YAML scenario schema를 정의한다.
- YAML schema는 최소한 아래 필드를 지원한다.

```yaml
name: api_connection_smoke
timeout: 5s
steps:
  - id: hello
    action: hello
    expect:
      status: ok
      capabilities:
        - hello
        - request-response
```

After:

```go
// apps/cli/cmd/alt/main.go:8
func main() {
	os.Exit(cli.Run(os.Args[1:], os.Stdout, os.Stderr))
}
```

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

- [ ] `apps/cli/go.mod`: `git.toki-labs.com/toki/alt/packages/contracts/gen/go`, `git.toki-labs.com/toki/proto-socket/go`, `gopkg.in/yaml.v3` 의존성을 추가한다. direct import만 direct require로 둔다.
- [ ] `apps/cli/go.sum`: `go mod tidy` 결과를 반영한다.
- [ ] `apps/cli/cmd/alt/main.go`: `internal/cli` 위임 entrypoint로 변경한다.
- [ ] `apps/cli/internal/cli/cli.go`: `version`, `operator scenario validate` dispatch와 usage/error 출력 계약을 구현한다.
- [ ] `apps/cli/internal/operator/scenario.go`: scenario struct, strict decode, validation, action enum을 구현한다.
- [ ] `apps/cli/testdata/operator/api_connection_smoke.yaml`: 정상 예시 fixture를 추가한다.
- [ ] `apps/cli/testdata/operator/invalid_request_matrix.yaml`: validation-only 예시 fixture를 추가한다.

#### 테스트 작성

- 작성: `apps/cli/internal/operator/scenario_test.go`
  - `TestLoadScenarioValidatesExample`: `testdata/operator/api_connection_smoke.yaml` 로드와 dry-run 검증 성공.
  - `TestLoadScenarioRejectsUnknownAction`: unknown `action`이 parse/validation error.
  - `TestLoadScenarioRejectsMissingStepID`: missing `id`가 validation error.
- 작성: `apps/cli/internal/cli/cli_test.go`
  - `TestRunVersion`: 기존 `version` 출력 유지.
  - `TestRunScenarioValidate`: valid fixture exit code 0 and summary output.
  - `TestRunScenarioValidateParseError`: invalid fixture exit code 2 and grep 가능한 `status=parse_error`.

#### 중간 검증

원격 ALT checkout root 기준:

```bash
(cd apps/cli && go test ./...)
(cd apps/cli && go run ./cmd/alt operator scenario validate --file testdata/operator/api_connection_smoke.yaml)
```

기대 결과: 두 명령 모두 exit code 0. validate 출력에 `scenario=api_connection_smoke`, `status=valid`, `steps=1` 또는 동등한 machine-readable field가 포함된다.

## 수정 파일 요약

| 파일 | 항목 |
|------|------|
| `apps/cli/go.mod` | OPS-1 |
| `apps/cli/go.sum` | OPS-1 |
| `apps/cli/cmd/alt/main.go` | OPS-1 |
| `apps/cli/internal/cli/cli.go` | OPS-1 |
| `apps/cli/internal/cli/cli_test.go` | OPS-1 |
| `apps/cli/internal/operator/scenario.go` | OPS-1 |
| `apps/cli/internal/operator/scenario_test.go` | OPS-1 |
| `apps/cli/testdata/operator/api_connection_smoke.yaml` | OPS-1 |
| `apps/cli/testdata/operator/invalid_request_matrix.yaml` | OPS-1 |

## 최종 검증

원격 ALT checkout root 기준으로 실행한다. Go test cache output은 이 parser/CLI skeleton 단계에서는 허용한다. fresh execution이 필요하다고 판단되면 구현 에이전트가 `-count=1`로 대체하고 `계획 대비 변경 사항`에 남긴다.

```bash
(cd apps/cli && go test ./...)
(cd apps/cli && go run ./cmd/alt operator scenario validate --file testdata/operator/api_connection_smoke.yaml)
bin/test
```

기대 결과: 모든 명령 exit code 0. `bin/test`가 contracts check, Go module tests, Flutter tests를 통과한다. 원격 host나 checkout 접근 불가 시 pass가 아니라 차단 사유와 미실행 명령을 review stub에 남긴다.

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