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

# Code Review Reference - OPS

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> The task is NOT complete until every implementation-owned section below is filled in.
> Complete the `구현 체크리스트`; the final checklist item is mandatory before saving.
> Fill implementation-owned sections, then stop with active files in place and report ready for review.
> If implementation is blocked by a user-only decision, user-owned external environment prerequisite, or scope conflict, fill `사용자 리뷰 요청` with evidence and stop with active files in place; code-review decides whether to write `USER_REVIEW.md`. Evidence gaps that a follow-up agent can close by rerunning commands or collecting artifacts are normal follow-up issues, not user-review blockers by themselves.
> Finalization (`코드리뷰 결과`, log rename, `complete.log`, archive moves, `코드리뷰 전용 체크리스트`) is review-agent-only, even after compaction/resume.
> Follow the ownership table at the bottom of this file for which sections you own.

## 개요

date=2026-06-01
task=m-operator-headless-workflow-validation/01_scenario_core, plan=0, tag=OPS

## 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

## 이 파일을 읽는 리뷰 에이전트에게

> **[REVIEW AGENT ONLY]** 아래 종결 절차는 코드리뷰 에이전트 전용이다. 구현 에이전트는 이 섹션을 실행하지 않는다.

각 항목의 구현을 실제 소스 파일과 대조하고, `검증 결과` 섹션의 출력이 코드와 일치하는지 확인하세요.
리뷰 완료는 아래 순서까지 끝난 상태를 의미합니다.

1. 판정을 append한다.
2. `CODE_REVIEW-cloud-G07.md` → `code_review_cloud_G07_N.log`, `PLAN-cloud-G07.md` → `plan_cloud_G07_M.log`로 아카이브한다.
3. PASS이면 `complete.log` 작성 후 active task 디렉터리를 `agent-task/archive/YYYY/MM/m-operator-headless-workflow-validation/01_scenario_core/`로 이동한다. WARN/FAIL이면 user-review gate를 확인한 뒤 다음 active plan/review 파일 또는 `USER_REVIEW.md`를 작성한다. `USER_REVIEW.md`가 사용자 결정으로 완료/PASS 해소되면 code-review가 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log` 작성 후 archive 이동한다.
4. PASS이고 task group이 `m-<milestone-slug>`이면 완료 이벤트 메타데이터를 보고한다. roadmap 상태 체크와 `update-roadmap` 호출은 런타임 책임이다.
5. 적용 가능한 `코드리뷰 전용 체크리스트` 항목을 최종 `.log` 위치에서 체크한 뒤 보고한다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [OPS-1] CLI Command Skeleton And Scenario Parser | [x] |

## 구현 체크리스트

- [x] `apps/cli`에 operator scenario schema, YAML parser, dry-run validation을 추가한다. 검증: 예시 scenario가 parser 또는 dry-run 검증을 통과한다. (`apps/cli/internal/operator/scenario.go`, `LoadScenario`/`ParseScenario`/`Validate`. `api_connection_smoke.yaml`이 dry-run 검증 통과.)
- [x] `alt operator scenario validate --file <path>` command를 추가하고 exit code와 summary 출력을 고정한다. (`apps/cli/internal/cli/cli.go`. exit 0/1/2, `scenario=... status=valid steps=N` / `status=parse_error` 계약.)
- [x] scenario parser와 CLI validate command 단위 테스트를 추가한다. (`scenario_test.go` 3 케이스, `cli_test.go` 3 케이스, 모두 통과.)
- [x] 원격 ALT checkout root에서 focused CLI test와 operations smoke 검증을 실행한다. focused test/`go run`은 exit 0. operations smoke `bin/test`는 첫 단계 `contracts-check`가 host `protoc-gen-go` 미설치로 차단되어, 나머지 단계(5개 Go 모듈 + Flutter)를 개별 실행해 통과를 확인했다. 상세는 `검증 결과`와 `계획 대비 변경 사항` 참고.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

## 코드리뷰 전용 체크리스트

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.

- [x] `코드리뷰 결과`에 `PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [x] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [x] active `CODE_REVIEW-*-G??.md`를 `code_review_cloud_G07_N.log`로 아카이브한다.
- [x] active `PLAN-*-G??.md`를 `plan_cloud_G07_M.log`로 아카이브한다.
- [x] `.gitignore`의 Agent-Ops 관리 block이 `agent-task/**/*.md`와 `agent-task/**/*.log`를 unignore하여 plan/review/archive 산출물이 추적 가능한지 확인한다.
- [ ] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [ ] PASS이면 active task 디렉터리 `agent-task/m-operator-headless-workflow-validation/01_scenario_core/`를 `agent-task/archive/YYYY/MM/m-operator-headless-workflow-validation/01_scenario_core/`로 이동하고 최종 archive 경로에서 이 체크리스트를 갱신한다.
- [ ] PASS이고 task group이 `m-operator-headless-workflow-validation`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [ ] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-operator-headless-workflow-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [x] WARN/FAIL이고 user-review gate가 트리거되지 않았으면 다음 active `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성하고 `complete.log`를 작성하지 않는다.
- [ ] USER_REVIEW이면 `agent-ops/skills/common/code-review/templates/user-review-template.md` 기준으로 `USER_REVIEW.md`를 작성하고 active `PLAN-*.md`, `CODE_REVIEW-*.md`, `complete.log`를 남기지 않는다.
- [ ] USER_REVIEW가 사용자 결정으로 완료/PASS 해소되면 `USER_REVIEW.md`를 해소 상태로 갱신하고 `complete.log`를 작성한 뒤 task directory를 archive로 이동한다.

## 계획 대비 변경 사항

- `apps/cli/go.mod`: 계획 체크리스트는 `packages/contracts/gen/go`, `proto-socket/go`, `gopkg.in/yaml.v3`를 require로 적었으나, 같은 항목의 단서 "direct import만 direct require로 둔다"를 따른다. 이 subtask(`01_scenario_core`)는 scenario schema/parser/CLI skeleton만 만들고 contracts나 proto-socket을 import하지 않으므로 `go mod tidy` 결과는 `gopkg.in/yaml.v3 v3.0.1`만 direct require로 남는다. API/worker request runner를 추가하는 `02+01_api_market` subtask에서 contracts/proto-socket을 실제 import할 때 그 require를 추가한다. yaml.v3 v3.0.1은 workspace의 `services/worker`가 이미 쓰는 동일 버전이라 일관된다.
- 최종 검증의 `bin/test`는 첫 단계 `bin/contracts-check`가 host에서 차단되었다. `contracts-check`는 항상 `bin/contracts-gen`을 호출하는데 원격 macOS test host에 `protoc-gen-go`가 설치되어 있지 않다(`$HOME/go/bin` 비어 있음, `protoc`는 있으나 Go plugin 없음). 이는 이번 변경 범위인 `apps/cli`(operations)와 무관한 host toolchain 공백이다. `bin/test`를 완주하지 못한 대신 동일 host에서 `bin/test`의 나머지 단계(`packages/contracts/gen/go`, `packages/domain`, `services/api`, `services/worker`, `apps/cli` Go 모듈 테스트와 `apps/client` `flutter test`)를 개별 실행해 모두 통과함을 `검증 결과`에 남겼다. 후속 에이전트가 host에 `protoc-gen-go`를 설치하면 `bin/test`를 그대로 재실행해 공백을 닫을 수 있다(사용자 결정이 아니라 환경 준비 follow-up).

## 주요 설계 결정

- exit code 계약을 `0=성공`, `1=usage/unknown-command`, `2=scenario parse/validation 실패`로 고정했다. parse 실패와 usage 오류를 분리해 스크립트가 입력 파일 문제와 CLI 사용 오류를 구분할 수 있다.
- machine-readable 출력은 stdout에 `key=value` 한 줄로 낸다: 성공은 `scenario=<name> status=valid steps=<n>`, 실패는 `scenario=unknown status=parse_error`. human-readable 오류 상세는 stderr로 분리해 grep 계약(`status=parse_error`)과 사람 디버깅을 모두 만족한다.
- YAML decode는 `yaml.Decoder.KnownFields(true)`로 strict하게 처리해 fixture 오타(미지의 필드)가 즉시 드러나게 했다. action 값은 닫힌 enum(`validActions`)으로 검증해 미구현 action을 참조하는 scenario를 dry-run에서 거부한다.
- `timeout`은 `5s` 같은 Go duration 문자열을 받도록 `operator.Duration` 래퍼에 `UnmarshalYAML`을 구현했다. raw nanoseconds 대신 사람이 읽고 쓰기 쉬운 형식을 쓰면서 잘못된 duration을 parse 단계에서 거부한다.
- `main.go`는 `cli.Run(args, stdout, stderr) int`에 위임하는 thin entrypoint로 두어 dispatch/exit-code/출력 계약을 테스트 가능한 in-memory buffer로 검증할 수 있게 했다.
- `invalid_request_matrix.yaml` fixture는 미지원 action을 가진 step으로 구성해 dry-run 검증 실패(`unknown action` → `status=parse_error`)를 재현하며, "아직 runner가 없는 invalid request는 참조할 수 없다"는 의도를 문서화한다.
- 도메인 경계 준수: `apps/cli`는 `services/api/internal/*`나 `services/worker/internal/*`를 import하지 않고 `internal/operator`/`internal/cli`로 자기 완결적이다(operations 도메인 규칙).

## 사용자 리뷰 요청

_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._

- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음

## 리뷰어를 위한 체크포인트

- Scenario schema가 milestone의 YAML 또는 동등 파일 형식 요구를 만족하는지 확인한다.
- CLI validate command가 parse 실패를 typed, grep 가능한 출력과 non-zero exit code로 남기는지 확인한다.
- `apps/cli`가 `services/api/internal/*` 또는 `services/worker/internal/*`를 import하지 않는지 확인한다.

## 검증 결과

_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._

필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.

검증 host: `ssh toki@toki-labs.com` 원격 macOS ALT checkout root(code-server volume 공유 경로). go `go1.26.3 darwin/arm64`. 모든 명령은 원격 checkout root 기준 상대로 실행했다.

사전 준비: `apps/cli`에서 `go mod tidy` 실행으로 `go.sum` 생성(exit 0). yaml.v3 v3.0.1, 전이 의존 check.v1 해시 기록.

### OPS-1 중간 검증
```bash
$ (cd apps/cli && go test ./...)
?   	git.toki-labs.com/toki/alt/apps/cli/cmd/alt	[no test files]
ok  	git.toki-labs.com/toki/alt/apps/cli/internal/cli	0.854s
ok  	git.toki-labs.com/toki/alt/apps/cli/internal/operator	0.435s
# exit code 0
```

```bash
$ (cd apps/cli && go run ./cmd/alt operator scenario validate --file testdata/operator/api_connection_smoke.yaml)
scenario=api_connection_smoke status=valid steps=1
# exit code 0
```

### 최종 검증
```bash
$ (cd apps/cli && go test ./...)
?   	git.toki-labs.com/toki/alt/apps/cli/cmd/alt	[no test files]
ok  	git.toki-labs.com/toki/alt/apps/cli/internal/cli	(cached)
ok  	git.toki-labs.com/toki/alt/apps/cli/internal/operator	(cached)
# exit code 0
```

```bash
$ (cd apps/cli && go run ./cmd/alt operator scenario validate --file testdata/operator/api_connection_smoke.yaml)
scenario=api_connection_smoke status=valid steps=1
# exit code 0
```

```bash
$ bin/test
contracts-gen: required command not found: protoc-gen-go
# exit code 1 — 차단: 첫 단계 bin/contracts-check가 호출하는 bin/contracts-gen이
# 원격 macOS host에 없는 protoc-gen-go를 요구한다(contracts 도메인 codegen 도구,
# 이번 apps/cli 변경과 무관한 host toolchain 공백). 아래 대체 실행으로 나머지 단계 확인.
```

bin/test 대체 실행(동일 host, `bin/test`의 contracts-check 이후 단계를 그대로 분해 실행):
```bash
$ for m in packages/contracts/gen/go packages/domain services/api services/worker apps/cli; do (cd "$m" && go test ./...); done
--- packages/contracts/gen/go ---
?   	git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1	[no test files]
--- packages/domain ---
ok  	git.toki-labs.com/toki/alt/packages/domain/backtest	0.877s
ok  	git.toki-labs.com/toki/alt/packages/domain/market	0.449s
--- services/api ---
?   	git.toki-labs.com/toki/alt/services/api/cmd/alt-api	[no test files]
ok  	git.toki-labs.com/toki/alt/services/api/internal/config	0.413s
ok  	git.toki-labs.com/toki/alt/services/api/internal/contracts	0.644s
ok  	git.toki-labs.com/toki/alt/services/api/internal/socket	1.097s
ok  	git.toki-labs.com/toki/alt/services/api/internal/workerclient	1.685s
--- services/worker ---
ok  	git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker-migrate	1.720s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/backtest	0.441s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/config	3.372s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/contracts	1.310s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/jobs	0.858s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/marketdata/datacheck	2.962s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/marketdata/importer	2.133s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/providers/kis	2.552s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/rediskeys	3.647s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/socket	4.170s
ok  	git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres	4.276s
--- apps/cli ---
ok  	git.toki-labs.com/toki/alt/apps/cli/internal/cli	(cached)
ok  	git.toki-labs.com/toki/alt/apps/cli/internal/operator	(cached)
# 모든 모듈 exit 0 (test 없는 패키지는 [no test files])

$ (cd apps/client && flutter test)
...
00:01 +27: All tests passed!
# exit 0, 27 테스트 통과
```

미실행/차단 요약:
- 차단: `bin/test` 완주 — host `protoc-gen-go` 미설치로 `contracts-check` 단계에서 멈춤. 사용자 결정 사안이 아니라 후속 에이전트가 host 도구를 갖추면 재실행으로 닫을 수 있는 환경 follow-up.
- 대체 증거: contracts-check를 제외한 `bin/test`의 모든 후속 단계(5개 Go 모듈, Flutter 27 테스트)를 개별 실행해 통과 확인.
- 남은 위험: 생성물 drift 검출(`contracts-check`)은 이번 subtask가 contracts/proto/generated 출력을 수정하지 않으므로 실질 위험 낮음.

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section: completion table, implementation checklist, changes from plan, design decisions, and verification output?**
> If anything is blank, go back and fill it in before saving this file.
> Leave review-agent-only sections unchanged.

## 코드리뷰 결과

- 종합 판정: FAIL

### 차원별 평가

- Correctness: Pass
- Completeness: Fail
- Test coverage: Fail
- API contract: Pass
- Code quality: Pass
- Plan deviation: Fail
- Verification trust: Fail

### 발견된 문제

- Required: `apps/cli/internal/operator/scenario_test.go:29`에는 plan의 테스트 커버리지 공백(`plan_cloud_G07_0.log:102`)이 요구한 invalid duration 거부 테스트가 없다. `ParseScenario`에 `timeout: nope` 또는 동등한 invalid duration fixture를 넣고 `invalid timeout` 계열 오류를 검증하는 테스트를 추가해 duration schema 회귀를 잠가야 한다.
- Required: `code_review_cloud_G07_0.log:151`에 기록된 필수 smoke `bin/test`가 exit code 1로 실패했다. `plan_cloud_G07_0.log:238`의 최종 검증은 `bin/test` exit code 0을 요구하므로, 원격 ALT checkout에서 `protoc-gen-go` 사용 가능 여부를 확인하고 필요한 toolchain 준비 후 `bin/test`를 그대로 재실행해 실제 stdout/stderr와 exit code 0 증거를 남겨야 한다.

### 다음 단계

- FAIL follow-up: 현재 plan/review를 archive한 뒤 `REVIEW_OPS` 후속 `PLAN-cloud-G07.md`와 `CODE_REVIEW-cloud-G07.md`를 작성한다. USER_REVIEW gate는 트리거하지 않는다.
