feat: add cli internal packages, testdata, iOS/macos Podfiles, and workflow validation tasks

This commit is contained in:
toki 2026-06-01 09:27:00 +09:00
parent 8a9181f1f9
commit 2310c90f10
27 changed files with 2440 additions and 14 deletions

View file

@ -0,0 +1,227 @@
<!-- 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는 트리거하지 않는다.

View file

@ -0,0 +1,225 @@
<!-- task=m-operator-headless-workflow-validation/01_scenario_core plan=1 tag=REVIEW_OPS -->
# Code Review Reference - REVIEW_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=1, tag=REVIEW_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` 위치에서 체크한 뒤 보고한다.
---
## 구현 항목별 완료 여부
| 항목 | 완료 여부 |
|------|---------|
| [REVIEW_OPS-1] Invalid Duration Test Coverage | [x] |
| [REVIEW_OPS-2] Operations Smoke Verification Trust | [x] |
## 구현 체크리스트
- [x] `apps/cli/internal/operator/scenario_test.go`에 invalid duration rejection test를 추가하고 원격 focused CLI test에서 통과를 확인한다. (`TestParseScenarioRejectsInvalidDuration`. `go test -count=1 ./...` exit 0.)
- [x] 원격 ALT checkout root에서 `protoc-gen-go`를 확인/준비한 뒤 `bin/test`를 exit code 0으로 완주하고 stdout/stderr를 기록한다. (host에 `protoc-gen-go v1.36.11` 설치 후 `bin/test` exit 0 완주.)
- [x] 원격 ALT checkout root에서 scenario validate command를 재실행해 출력 계약을 재확인한다. (`scenario=api_connection_smoke status=valid steps=1`, exit 0.)
- [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 산출물이 추적 가능한지 확인한다.
- [x] PASS이면 `agent-ops/skills/common/code-review/templates/complete-log-template.md` 기준으로 `complete.log`를 작성하고 active `.md` 파일을 남기지 않는다.
- [x] 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 경로에서 이 체크리스트를 갱신한다.
- [x] PASS이고 task group이 `m-operator-headless-workflow-validation`이면 런타임이 읽을 완료 이벤트 메타데이터를 보고하고, roadmap 수정이나 `update-roadmap` 직접 호출을 하지 않는다.
- [x] PASS split 작업이면 이동 후 빈 active parent `agent-task/m-operator-headless-workflow-validation/`를 제거하거나, 남은 sibling/file이 있어 유지했다고 확인한다.
- [ ] 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로 이동한다.
## 계획 대비 변경 사항
- 계획대로 진행했고 repo source 측 계획 외 변경은 없다. 유일한 source 변경은 `apps/cli/internal/operator/scenario_test.go`에 `TestParseScenarioRejectsInvalidDuration` 추가다(REVIEW_OPS-1). `bin/test`는 source 수정 없이 통과해 REVIEW_OPS-2의 "필요한 최소 수정" 조항은 발동하지 않았다.
- REVIEW_OPS-2 toolchain 준비: 원격 macOS test host에 `protoc-gen-go`가 없어 `go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11`로 설치했다. 버전은 generated 파일 헤더(`packages/contracts/gen/go/**` = `protoc-gen-go v1.36.11`, `protoc v5.29.3`)와 일치시켜 `bin/contracts-check`의 regenerate-and-diff에서 drift가 나지 않게 했다. host `protoc`는 이미 `libprotoc 29.3`(= protoc v5.29.3)으로 헤더와 일치했고, dart plugin wrapper `apps/client/tool/protoc-gen-dart`도 실행 가능 상태였다. 이는 repo source가 아니라 test host toolchain 준비이며, 권한/정책 차단 없이 설치되어 `사용자 리뷰 요청`은 발생하지 않았다.
- 설치 결과 `bin/test`가 contracts check, 5개 Go 모듈 test, Flutter 27 test를 모두 통과하고 exit code 0으로 완주했다. 첫 리뷰에서 지적된 "대체 분해 실행" 대신 `bin/test` 자체의 exit 0 증거를 `검증 결과`에 남겼다.
## 주요 설계 결정
- invalid duration 테스트는 `ParseScenario`에 `timeout: not-a-duration` YAML을 직접 입력해 `operator.Duration.UnmarshalYAML` 거부 경로를 직접 탄다. 입력에 유효한 step(`id`+known `action`)을 둬서 step 검증이 아니라 duration parse가 실패 원인임을 분리했고, error 문자열에 `invalid timeout`이 포함되는지로 단언해 schema 회귀를 잡는다.
- toolchain 준비는 host 환경 변경에 그치고 repo source(`bin/*`, contracts, generated 출력)는 건드리지 않았다. 검증 host 도구 공백은 source 문제가 아니므로 generated 출력 재커밋이나 스크립트 수정이 필요 없다.
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- Invalid duration rejection test가 실제로 `operator.Duration.UnmarshalYAML` 경로를 타는지 확인한다.
- `bin/test`가 대체 분해 실행이 아니라 원격 ALT checkout root에서 그대로 exit code 0을 반환했는지 확인한다.
- follow-up이 `apps/cli/internal/operator/scenario_test.go`와 원격 검증 evidence 외의 source를 불필요하게 수정하지 않았는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 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 기준으로 실행했다. PATH에 `$HOME/go/bin`(protoc-gen-go)과 flutter/dart bin을 포함했다.
toolchain 준비(REVIEW_OPS-2):
```bash
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11
# exit 0
$ protoc-gen-go --version
protoc-gen-go v1.36.11
```
### REVIEW_OPS-1 중간 검증
```bash
$ (cd apps/cli && go test -count=1 ./...)
? 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.809s
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.427s
# exit code 0 — internal/operator가 cache 없이 실행되어 새 TestParseScenarioRejectsInvalidDuration 포함 통과
```
### REVIEW_OPS-2 중간 검증
```bash
$ command -v protoc-gen-go
/Users/toki/go/bin/protoc-gen-go
# exit code 0
$ bin/test
# exit code 0 (전체 출력은 아래 최종 검증과 동일)
```
### 최종 검증
```bash
$ export PATH="$HOME/SDK/flutter/bin:$HOME/SDK/flutter/bin/cache/dart-sdk/bin:/opt/homebrew/bin:/usr/local/bin:/usr/local/go/bin:$HOME/go/bin:$PATH"
$ command -v protoc-gen-go
/Users/toki/go/bin/protoc-gen-go
# exit code 0
$ (cd apps/cli && go test -count=1 ./...)
? 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.809s
ok git.toki-labs.com/toki/alt/apps/cli/internal/operator 0.427s
# exit code 0
$ (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
$ bin/test
? git.toki-labs.com/toki/alt/packages/contracts/gen/go/alt/v1 [no test files]
ok git.toki-labs.com/toki/alt/packages/domain/backtest (cached)
ok git.toki-labs.com/toki/alt/packages/domain/market (cached)
? 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 (cached)
ok git.toki-labs.com/toki/alt/services/api/internal/contracts (cached)
ok git.toki-labs.com/toki/alt/services/api/internal/socket (cached)
ok git.toki-labs.com/toki/alt/services/api/internal/workerclient (cached)
? git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker [no test files]
? git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker-data-check [no test files]
ok git.toki-labs.com/toki/alt/services/worker/cmd/alt-worker-migrate (cached)
ok git.toki-labs.com/toki/alt/services/worker/internal/backtest (cached)
ok git.toki-labs.com/toki/alt/services/worker/internal/config (cached)
ok git.toki-labs.com/toki/alt/services/worker/internal/contracts (cached)
ok git.toki-labs.com/toki/alt/services/worker/internal/jobs (cached)
ok git.toki-labs.com/toki/alt/services/worker/internal/marketdata/datacheck (cached)
ok git.toki-labs.com/toki/alt/services/worker/internal/marketdata/importer (cached)
ok git.toki-labs.com/toki/alt/services/worker/internal/providers/kis (cached)
ok git.toki-labs.com/toki/alt/services/worker/internal/rediskeys (cached)
ok git.toki-labs.com/toki/alt/services/worker/internal/socket (cached)
? git.toki-labs.com/toki/alt/services/worker/internal/storage [no test files]
ok git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres (cached)
? git.toki-labs.com/toki/alt/services/worker/internal/storage/postgres/sqlc [no test files]
? 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 0.236s
# (flutter test 단계)
00:00 +0: loading .../apps/client/test/app/bootstrap_test.dart
00:00 +1: .../test/widget_test.dart: shows ALT dashboard shell with default disconnected socket state
... (중략)
00:00 +27: All tests passed!
# exit code 0 — contracts check + 5개 Go 모듈 + Flutter 27 test 모두 통과
```
검증 요약:
- 첫 리뷰의 Required 2건이 모두 닫혔다: invalid duration test 추가(통과), `bin/test` 자체 exit 0 완주(대체 분해 실행 아님).
- 차단/생략 없음. toolchain 준비는 권한/정책 차단 없이 host에 protoc-gen-go 설치로 해소됨.
- 남은 위험: 없음. contracts-check가 drift 없이 통과해 generated 출력과 host toolchain 버전이 정합한다.
---
> **[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.
## 코드리뷰 결과
- 종합 판정: PASS
### 차원별 평가
- Correctness: Pass
- Completeness: Pass
- Test coverage: Pass
- API contract: Pass
- Code quality: Pass
- Plan deviation: Pass
- Verification trust: Pass
### 발견된 문제
- 없음
### 다음 단계
- PASS: active plan/review를 archive하고 `complete.log`를 작성한 뒤 task directory를 `agent-task/archive/2026/06/m-operator-headless-workflow-validation/01_scenario_core/`로 이동한다.

View file

@ -0,0 +1,44 @@
# Complete - m-operator-headless-workflow-validation/01_scenario_core
## 완료 일시
2026-06-01
## 요약
OPS scenario core task completed after 2 review loops; final verdict PASS.
## 루프 이력
| Plan | Review | Verdict | 메모 |
|------|--------|---------|------|
| `plan_cloud_G07_0.log` | `code_review_cloud_G07_0.log` | FAIL | invalid duration test coverage and `bin/test` verification trust were missing. |
| `plan_cloud_G07_1.log` | `code_review_cloud_G07_1.log` | PASS | invalid duration test added and remote `bin/test` completed with exit code 0. |
## 구현/정리 내용
- Added the operator scenario YAML schema, strict parser, dry-run validation, and `alt operator scenario validate --file <path>` CLI path.
- Added parser and CLI tests, including invalid duration rejection coverage.
- Prepared the remote test host `protoc-gen-go` toolchain and verified the operations smoke command directly.
## 최종 검증
- `command -v protoc-gen-go` - PASS; returned `/Users/toki/go/bin/protoc-gen-go`.
- `(cd apps/cli && go test -count=1 ./...)` - PASS; `cmd/alt` had no tests, `internal/cli` and `internal/operator` passed.
- `(cd apps/cli && go run ./cmd/alt operator scenario validate --file testdata/operator/api_connection_smoke.yaml)` - PASS; output `scenario=api_connection_smoke status=valid steps=1`.
- `bin/test` - PASS; contracts check, Go module tests, and Flutter tests completed with exit code 0.
## Roadmap Completion
- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-headless-workflow-validation.md`
- Completed task ids:
- `scenario-format`: PASS; evidence=`agent-task/archive/2026/06/m-operator-headless-workflow-validation/01_scenario_core/plan_cloud_G07_1.log`, `agent-task/archive/2026/06/m-operator-headless-workflow-validation/01_scenario_core/code_review_cloud_G07_1.log`; verification=`bin/test`, `(cd apps/cli && go test -count=1 ./...)`, `(cd apps/cli && go run ./cmd/alt operator scenario validate --file testdata/operator/api_connection_smoke.yaml)`
- Not completed task ids: 없음
## 잔여 Nit
- 없음
## 후속 작업
- 없음

View file

@ -0,0 +1,246 @@
<!-- 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,142 @@
<!-- task=m-operator-headless-workflow-validation/01_scenario_core plan=1 tag=REVIEW_OPS -->
# Plan - REVIEW_OPS Scenario Core Follow-up
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고 실제 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 채우고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.
## 배경
첫 구현은 `apps/cli` scenario parser와 validate command의 핵심 동작을 추가했고 focused CLI 검증은 원격에서 통과했다. 코드 리뷰는 두 가지 Required 공백을 발견했다.
- `plan_cloud_G07_0.log`의 테스트 커버리지 분석은 invalid duration 거부 테스트를 요구했지만 `apps/cli/internal/operator/scenario_test.go`에 해당 테스트가 없다.
- 필수 operations smoke인 `bin/test`는 원격 host에서 `protoc-gen-go` 미설치로 exit code 1을 반환했으므로, milestone task를 PASS 처리할 검증 신뢰도가 아직 닫히지 않았다.
## 사용자 리뷰 요청 흐름
구현 중 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
## 리뷰 근거
- Archived plan: `agent-task/m-operator-headless-workflow-validation/01_scenario_core/plan_cloud_G07_0.log`
- Archived review: `agent-task/m-operator-headless-workflow-validation/01_scenario_core/code_review_cloud_G07_0.log`
- Verdict: FAIL
- Required issues:
- invalid duration test coverage missing.
- `bin/test` required smoke not yet passing because `protoc-gen-go` is absent on the remote test host PATH.
## 범위 결정 근거
- 이 follow-up은 `apps/cli` test coverage와 원격 검증 신뢰도만 닫는다.
- `services/api/**`, `services/worker/**`, `packages/contracts/proto/**`, `apps/client/**`는 수정하지 않는다.
- `protoc-gen-go` 준비가 필요하면 repo source가 아니라 원격 test host toolchain 준비로 처리한다. toolchain 준비가 사용자만 할 수 있는 정책/권한 문제로 막힐 때만 `사용자 리뷰 요청`을 채운다.
- `bin/test`가 toolchain 준비 뒤 source 문제로 실패하면 해당 실패를 숨기지 말고 정확한 stdout/stderr와 함께 필요한 최소 코드 수정으로 처리한다.
## 빌드 등급
- build lane: `cloud-G07`, review lane: `cloud-G07`.
- 이유: `bin/test`, codegen toolchain, stdout/stderr evidence, exit-status contract를 다루는 terminal-agent follow-up이다.
## 구현 체크리스트
- [ ] `apps/cli/internal/operator/scenario_test.go`에 invalid duration rejection test를 추가하고 원격 focused CLI test에서 통과를 확인한다.
- [ ] 원격 ALT checkout root에서 `protoc-gen-go`를 확인/준비한 뒤 `bin/test`를 exit code 0으로 완주하고 stdout/stderr를 기록한다.
- [ ] 원격 ALT checkout root에서 scenario validate command를 재실행해 출력 계약을 재확인한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 변경 및 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
### [REVIEW_OPS-1] Invalid Duration Test Coverage
#### 문제
`plan_cloud_G07_0.log`의 테스트 커버리지 분석은 invalid duration을 덮어야 한다고 기록했지만 현재 테스트는 정상 fixture, unknown action, missing step id만 검증한다. `operator.Duration.UnmarshalYAML`이 실제로 invalid duration을 거부하더라도 테스트가 없어 schema 회귀를 잡지 못한다.
#### 해결 방법
- `apps/cli/internal/operator/scenario_test.go`에 invalid timeout YAML을 `ParseScenario`로 입력하는 테스트를 추가한다.
- 테스트 이름 예: `TestParseScenarioRejectsInvalidDuration`.
- 입력 예:
```yaml
name: invalid_timeout
timeout: not-a-duration
steps:
- id: hello
action: hello
```
- 기대: error가 nil이 아니고 `invalid timeout` 또는 duration parse 실패를 식별할 수 있는 문자열을 포함한다.
#### 수정 파일 및 체크리스트
- [ ] `apps/cli/internal/operator/scenario_test.go`: invalid duration rejection test를 추가한다.
#### 중간 검증
원격 ALT checkout root 기준:
```bash
(cd apps/cli && go test -count=1 ./...)
```
기대 결과: exit code 0. `internal/operator` 테스트가 cache 없이 실행된다.
### [REVIEW_OPS-2] Operations Smoke Verification Trust
#### 문제
첫 구현의 최종 검증에서 `bin/test`는 `contracts-gen: required command not found: protoc-gen-go`로 실패했다. 대체 모듈 테스트는 유용한 근거지만, operations smoke profile의 필수 command인 `bin/test` 자체가 exit code 0으로 완주했다는 증거를 대체하지 못한다.
#### 해결 방법
- 원격 ALT checkout root에서 테스트용 PATH를 잡고 `command -v protoc-gen-go`를 먼저 실행한다.
- 없으면 generated Go 파일에 기록된 버전과 맞춰 `go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11`로 host toolchain을 준비하고 `$HOME/go/bin`이 PATH에 포함됐는지 확인한다.
- 이후 `bin/test`를 그대로 재실행한다.
- `bin/test`가 drift나 source 문제로 실패하면 실패 stdout/stderr를 기록하고, 이 follow-up 범위에서 필요한 최소 수정만 수행한다.
#### 수정 파일 및 체크리스트
- [ ] 원격 host에서 `command -v protoc-gen-go` 결과를 기록한다.
- [ ] 필요한 경우 `go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.36.11`로 `protoc-gen-go`를 준비한다.
- [ ] `bin/test`를 exit code 0으로 완주하고 실제 stdout/stderr를 review stub에 붙인다.
#### 중간 검증
원격 ALT checkout root 기준:
```bash
command -v protoc-gen-go
bin/test
```
기대 결과: `command -v protoc-gen-go`는 경로를 출력하고 exit code 0. `bin/test`는 contracts check, Go module tests, Flutter tests를 모두 통과하고 exit code 0.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/cli/internal/operator/scenario_test.go` | REVIEW_OPS-1 |
| 원격 test host toolchain evidence | REVIEW_OPS-2 |
## 최종 검증
모든 명령은 원격 ALT checkout root 기준으로 실행한다. 현재 local shell에서 직접 실행하지 않는다.
```bash
export PATH="$HOME/SDK/flutter/bin:$HOME/SDK/flutter/bin/cache/dart-sdk/bin:/opt/homebrew/bin:/usr/local/bin:/usr/local/go/bin:$HOME/go/bin:$PATH"
command -v protoc-gen-go
(cd apps/cli && go test -count=1 ./...)
(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 toolchain 준비가 권한/정책상 불가능하면 pass가 아니라 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 사용자에게 필요한 결정, 재개 조건을 남긴다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,141 @@
<!-- task=m-operator-headless-workflow-validation/02+01_api_market 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/02+01_api_market, plan=0, tag=OPS
## 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
## 이 파일을 읽는 리뷰 에이전트에게
> **[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/02+01_api_market/`로 이동한다. 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 Parser Map And API Client | [ ] |
| [OPS-2] Run Command, Market Actions, And Output Contract | [ ] |
## 구현 체크리스트
- [ ] `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의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_cloud_G07_N.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_cloud_G07_M.log`로 아카이브한다.
- [ ] `.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/02+01_api_market/``agent-task/archive/YYYY/MM/m-operator-headless-workflow-validation/02+01_api_market/`로 이동하고 최종 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이 있어 유지했다고 확인한다.
- [ ] 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로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `01_scenario_core` predecessor completion이 확인되었는지 확인한다.
- CLI parser map이 API/worker current parser surface와 필요한 runner messages를 포함하는지 확인한다.
- Terminal output이 transport error, typed ErrorInfo, expectation mismatch를 구분하는지 확인한다.
- `apps/cli`가 API/worker internal packages를 import하지 않는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
### OPS-1 중간 검증
```bash
$ (cd apps/cli && go test ./internal/operator -run 'Test(ParserMap|APIClient)')
(output)
```
### OPS-2 중간 검증
```bash
$ (cd apps/cli && go test ./...)
(output)
```
### 최종 검증
```bash
$ (cd apps/cli && go test -count=1 ./...)
(output)
```
```bash
$ bin/test
(output)
```
---
> **[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.

View file

@ -0,0 +1,237 @@
<!-- 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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,140 @@
<!-- task=m-operator-headless-workflow-validation/03+02_backtest_workflows 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/03+02_backtest_workflows, plan=0, tag=OPS
## Roadmap Targets
- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-headless-workflow-validation.md`
- Task ids:
- `backtest-check`: backtest list/detail/result/start 관련 결과를 화면 없이 확인할 수 있다.
- `error-output`: unavailable/error/disconnected 상태가 화면 없이도 판별 가능한 출력으로 남는다.
- 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/03+02_backtest_workflows/`로 이동한다. 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] Backtest Request Actions And Polling | [ ] |
| [OPS-2] Invalid Request Matrix And Error Output | [ ] |
## 구현 체크리스트
- [ ] `02+01_api_market``complete.log`가 active 또는 archive에 있는지 확인하고, 없으면 구현을 시작하지 않고 review stub에 blocker를 기록한다.
- [ ] Backtest start/list/detail/result/compare/poll actions를 scenario schema와 runner에 추가한다.
- [ ] `backtest_run_request`, `backtest_run_polling`, `backtest_result_summary`, `invalid_request_matrix` fixture를 추가한다. 검증: fixture 또는 local smoke가 성공/실패 결과를 확인한다.
- [ ] typed error, unavailable, disconnected, expectation mismatch 출력이 grep 가능한 field로 남는지 테스트한다. 검증: typed error 또는 상태 코드 출력 테스트가 통과한다.
- [ ] 원격 ALT checkout root에서 focused CLI test와 operations smoke 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_cloud_G07_N.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_cloud_G07_M.log`로 아카이브한다.
- [ ] `.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/03+02_backtest_workflows/``agent-task/archive/YYYY/MM/m-operator-headless-workflow-validation/03+02_backtest_workflows/`로 이동하고 최종 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이 있어 유지했다고 확인한다.
- [ ] 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로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `02+01_api_market` predecessor completion이 확인되었는지 확인한다.
- Backtest actions가 existing contract fields를 사용하고 schema/contract source를 임의 변경하지 않았는지 확인한다.
- Polling timeout과 expected typed errors가 deterministic tests로 검증되었는지 확인한다.
- Error output이 typed error와 transport failure를 구분하는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
### OPS-1 중간 검증
```bash
$ (cd apps/cli && go test -count=1 ./internal/operator -run 'TestRunBacktest')
(output)
```
### OPS-2 중간 검증
```bash
$ (cd apps/cli && go test -count=1 ./internal/operator -run 'Test(ExpectedTypedError|UnexpectedTypedError|TransportFailure|ExpectationMismatch)')
(output)
```
### 최종 검증
```bash
$ (cd apps/cli && go test -count=1 ./...)
(output)
```
```bash
$ bin/test
(output)
```
---
> **[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.

View file

@ -0,0 +1,229 @@
<!-- task=m-operator-headless-workflow-validation/03+02_backtest_workflows plan=0 tag=OPS -->
# Plan - OPS Backtest Workflow Scenarios
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고 실제 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 채우고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.
## 배경
Market scenario runner가 준비되면 backtest list/detail/result/start/polling과 invalid request matrix를 같은 headless contract로 검증해야 한다. API는 `services/api/internal/socket/backtest.go:74`부터 start/list/detail/result/compare handlers를 갖고, worker는 `services/worker/internal/socket/backtest.go:112`부터 worker-owned execution/read handlers를 갖고 있다. 이 작업은 서비스 내부를 바꾸지 않고 CLI runner에 backtest actions, polling, summary output, typed error matrix를 추가한다.
## 사용자 리뷰 요청 흐름
구현 중 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:
- `backtest-check`: backtest list/detail/result/start 관련 결과를 화면 없이 확인할 수 있다.
- `error-output`: unavailable/error/disconnected 상태가 화면 없이도 판별 가능한 출력으로 남는다.
- Completion mode: check-on-pass
## 분석 결과
### 읽은 파일
- `agent-ops/rules/project/rules.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`
- `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/socket/backtest.go`
- `services/api/internal/socket/backtest_test.go`
- `services/api/internal/socket/market.go`
- `services/api/internal/socket/market_test.go`
- `services/api/internal/contracts/parser_map.go`
- `services/api/internal/contracts/parser_map_test.go`
- `services/api/internal/workerclient/client.go`
- `services/api/internal/workerclient/client_test.go`
- `services/worker/internal/socket/backtest.go`
- `services/worker/internal/socket/backtest_test.go`
- `services/worker/internal/socket/backtest_mapping.go`
- `services/worker/internal/socket/backtest_mapping_test.go`
- `services/worker/internal/storage/ports.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`
### 테스트 환경 규칙
- `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`. Backtest runner가 API/worker contract surface를 호출하므로 final `bin/test`로 baseline 회귀를 확인한다.
- focused command `(cd apps/cli && go test ./...)``bin/test`의 CLI module subset fallback이다.
### 테스트 커버리지 공백
- Existing API/worker tests cover backtest handler registration, validation, forwarding, unavailable/not_found/internal typed errors.
- CLI runner does not yet build backtest protobuf requests, poll run status, summarize result metrics, or print invalid request matrix output. New tests must cover these.
- Real infra-backed completed backtest result is not required in this subtask; fake proto-socket server tests are the deterministic verification path.
### 심볼 참조
- renamed/removed symbol: none.
### 분할 판단
- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `agent-task/m-operator-headless-workflow-validation/`.
- 이 plan의 directory `03+02_backtest_workflows`는 predecessor index `02`를 요구한다.
- predecessor `02`: active `agent-task/m-operator-headless-workflow-validation/02+01_api_market/complete.log` 없음, archive complete.log 없음. runtime은 `02+01_api_market` PASS 후 이 plan을 구현해야 한다.
### 범위 결정 근거
- `services/api/**`, `services/worker/**`, `packages/contracts/**`는 수정하지 않는다. Existing handlers already cover start/list/detail/result/compare.
- Strategy execution correctness, storage migration, provider import, real PostgreSQL/Redis smoke는 worker milestone 범위로 보며 이 CLI runner subtask에서는 fake socket 검증으로 제한한다.
- Flutter UI handoff 문서는 `04+02,03_evidence_handoff`로 분리한다.
### 빌드 등급
- build lane: `cloud-G07`, review lane: `cloud-G07`.
- 이유: terminal scenario execution, polling timeout, stdout/exit-status/error contract가 있는 CLI workflow 작업이다.
## 구현 체크리스트
- [ ] `02+01_api_market``complete.log`가 active 또는 archive에 있는지 확인하고, 없으면 구현을 시작하지 않고 review stub에 blocker를 기록한다.
- [ ] Backtest start/list/detail/result/compare/poll actions를 scenario schema와 runner에 추가한다.
- [ ] `backtest_run_request`, `backtest_run_polling`, `backtest_result_summary`, `invalid_request_matrix` fixture를 추가한다. 검증: fixture 또는 local smoke가 성공/실패 결과를 확인한다.
- [ ] typed error, unavailable, disconnected, expectation mismatch 출력이 grep 가능한 field로 남는지 테스트한다. 검증: typed error 또는 상태 코드 출력 테스트가 통과한다.
- [ ] 원격 ALT checkout root에서 focused CLI test와 operations smoke 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 의존 관계 및 구현 순서
- 이 directory 이름의 runtime dependency는 `03+02_backtest_workflows`이다.
- 구현 시작 전 `agent-task/m-operator-headless-workflow-validation/02+01_api_market/complete.log` 또는 `agent-task/archive/*/*/m-operator-headless-workflow-validation/02+01_api_market/complete.log`가 있어야 한다.
- 추가 hidden dependency를 만들지 않는다.
### [OPS-1] Backtest Request Actions And Polling
#### 문제
`packages/contracts/proto/alt/v1/backtest.proto:35` defines `StartBacktestRequest`; `:98`, `:107`, `:89`, `:117` define list/detail/result/compare requests. Existing API handlers expose those messages, but the CLI runner from prior subtasks only handles hello/market actions.
#### 해결 방법
- Extend scenario step actions:
- `start_backtest`
- `list_backtest_runs`
- `get_backtest_run_detail`
- `get_backtest_result`
- `compare_backtest_runs`
- `poll_backtest_run`
- Support `save_as` and interpolation from prior step response for run id, using explicit `{{steps.<id>.run.id}}` paths only.
- Poll action repeats detail/list request until status is in expected terminal set or timeout expires.
#### 수정 파일 및 체크리스트
- [ ] `apps/cli/internal/operator/scenario.go`: backtest fields, run spec, polling config, expected status/result fields 추가.
- [ ] `apps/cli/internal/operator/runner.go`: backtest action dispatch와 saved value lookup 추가.
- [ ] `apps/cli/internal/operator/output.go`: backtest run/result summary fields 추가.
- [ ] `apps/cli/internal/operator/runner_backtest_test.go`: fake server start/list/detail/result/compare/poll tests 추가.
- [ ] `apps/cli/testdata/operator/backtest_run_request.yaml`: start fixture 추가.
- [ ] `apps/cli/testdata/operator/backtest_run_polling.yaml`: polling fixture 추가.
- [ ] `apps/cli/testdata/operator/backtest_result_summary.yaml`: result fixture 추가.
#### 테스트 작성
- 작성: `apps/cli/internal/operator/runner_backtest_test.go`
- `TestRunBacktestRequestOutputsRunID`: start response run id/status output.
- `TestRunBacktestPollingStopsOnSucceeded`: polling uses saved run id and terminal status.
- `TestRunBacktestResultSummaryOutputsMetrics`: summary fields output.
- `TestRunBacktestMissingRunIDTypedError`: missing run id expected error path.
#### 중간 검증
원격 ALT checkout root 기준:
```bash
(cd apps/cli && go test -count=1 ./internal/operator -run 'TestRunBacktest')
```
기대 결과: exit code 0. Fake API server tests prove request build, polling, and summary output.
### [OPS-2] Invalid Request Matrix And Error Output
#### 문제
Milestone requires unavailable/error/disconnected to remain machine-readable without a UI. Existing API/worker handlers return typed `ErrorInfo`, but CLI must normalize typed errors and transport failures into stable terminal output and exit codes.
#### 해결 방법
- Add scenario expectation fields:
- `expect.error_code`
- `expect.transport_status`
- `expect.exit_code`
- Add invalid matrix fixture with missing spec, missing run id, empty compare id, invalid market/timeframe/date range.
- Output JSONL must include at least `scenario`, `step`, `action`, `status`, `error.code`, `error.message`, `exit_code` in summary.
#### 수정 파일 및 체크리스트
- [ ] `apps/cli/internal/operator/scenario.go`: expected error/status validation 추가.
- [ ] `apps/cli/internal/operator/runner.go`: typed error expectation and matrix execution behavior 추가.
- [ ] `apps/cli/internal/operator/output.go`: transport/typed/expectation status fields 고정.
- [ ] `apps/cli/testdata/operator/invalid_request_matrix.yaml`: full invalid matrix로 확장.
- [ ] `apps/cli/internal/operator/runner_error_test.go`: typed error expected/unexpected, transport failure, disconnected test 추가.
#### 테스트 작성
- 작성: `apps/cli/internal/operator/runner_error_test.go`
- `TestExpectedTypedErrorExitsZero`
- `TestUnexpectedTypedErrorExitsOne`
- `TestTransportFailureExitsThree`
- `TestExpectationMismatchExitsOne`
#### 중간 검증
원격 ALT checkout root 기준:
```bash
(cd apps/cli && go test -count=1 ./internal/operator -run 'Test(ExpectedTypedError|UnexpectedTypedError|TransportFailure|ExpectationMismatch)')
```
기대 결과: exit code 0.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/cli/internal/operator/scenario.go` | OPS-1, OPS-2 |
| `apps/cli/internal/operator/runner.go` | OPS-1, OPS-2 |
| `apps/cli/internal/operator/output.go` | OPS-1, OPS-2 |
| `apps/cli/internal/operator/runner_backtest_test.go` | OPS-1 |
| `apps/cli/internal/operator/runner_error_test.go` | OPS-2 |
| `apps/cli/testdata/operator/backtest_run_request.yaml` | OPS-1 |
| `apps/cli/testdata/operator/backtest_run_polling.yaml` | OPS-1 |
| `apps/cli/testdata/operator/backtest_result_summary.yaml` | OPS-1 |
| `apps/cli/testdata/operator/invalid_request_matrix.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`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -0,0 +1,154 @@
<!-- task=m-operator-headless-workflow-validation/04+02,03_evidence_handoff 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/04+02,03_evidence_handoff, plan=0, tag=OPS
## Roadmap Targets
- Milestone: `agent-roadmap/phase/operator-surface/milestones/operator-headless-workflow-validation.md`
- Task ids:
- `handoff`: Flutter 화면 MVP로 넘길 수 있는 검증 evidence와 남은 wireframe 의존성을 정리한다.
- 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/04+02,03_evidence_handoff/`로 이동한다. 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] Fixture Evidence Matrix | [ ] |
| [OPS-2] Developer Entrypoint Notes | [ ] |
## 구현 체크리스트
- [ ] `02+01_api_market``03+02_backtest_workflows``complete.log`가 active 또는 archive에 있는지 확인하고, 없으면 구현을 시작하지 않고 review stub에 blocker를 기록한다.
- [ ] `apps/cli/testdata/operator/` 아래에 scenario별 command/input/expected output/exit code/protobuf field handoff artifact를 추가한다.
- [ ] `bin/dev`에 headless scenario validate/run entrypoint를 추가하되 secret, remote path, personal endpoint를 쓰지 않는다.
- [ ] Handoff artifact completeness를 검증하는 focused test 또는 deterministic shell check를 추가한다.
- [ ] 원격 ALT checkout root에서 focused CLI test와 operations smoke 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 코드리뷰 전용 체크리스트
> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.
> 구현 에이전트는 이 섹션을 수정하거나 체크하지 않는다.
- [ ] `코드리뷰 결과``PASS`, `WARN`, `FAIL` 중 하나의 판정을 append한다.
- [ ] 판정과 `차원별 평가`, Required/Suggested/Nit 분류가 서로 일치한다.
- [ ] active `CODE_REVIEW-*-G??.md``code_review_cloud_G07_N.log`로 아카이브한다.
- [ ] active `PLAN-*-G??.md``plan_cloud_G07_M.log`로 아카이브한다.
- [ ] `.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/04+02,03_evidence_handoff/``agent-task/archive/YYYY/MM/m-operator-headless-workflow-validation/04+02,03_evidence_handoff/`로 이동하고 최종 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이 있어 유지했다고 확인한다.
- [ ] 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로 이동한다.
## 계획 대비 변경 사항
_구현 에이전트가 계획과 다르게 구현한 부분을 이유와 함께 기록한다._
## 주요 설계 결정
_구현 에이전트가 주요 설계 결정 사항을 기록한다._
## 사용자 리뷰 요청
_기본값은 `없음`이다. 구현 중 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 변경 없이는 안전하게 진행할 수 없으면 아래 항목을 실제 내용으로 교체하고, 구현을 중단한 뒤 active 파일을 그대로 둔 채 리뷰를 요청한다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백만으로는 사용자 리뷰 요청을 작성하지 않는다._
- 상태: 없음
- 사유 유형: 없음
- 결정 필요: 없음
- 차단 근거: 없음
- 실행한 검증/명령: 없음
- 자동 후속 불가 이유: 없음
- 재개 조건: 없음
## 리뷰어를 위한 체크포인트
- `02+01_api_market``03+02_backtest_workflows` predecessor completion이 확인되었는지 확인한다.
- Handoff matrix가 milestone handoff table의 six scenarios를 모두 포함하는지 확인한다.
- Expected output fixtures가 secret-free이고 machine-readable인지 확인한다.
- `bin/dev` examples가 local defaults and fixture paths만 포함하는지 확인한다.
## 검증 결과
_구현 에이전트가 각 중간 검증 및 최종 검증 명령 실행 후 출력을 여기에 붙여 넣는다._
필수 규칙:
- 검증 명령은 고정된 계약이다. 임의로 대체하지 않는다.
- 대체가 필요하면 `계획 대비 변경 사항`에 이유와 대체 명령을 기록한다.
- `검증 결과`에는 실제 stdout/stderr를 붙여 넣는다.
- 사용자 리뷰 요청으로 명령을 끝까지 실행하지 못했다면 `사용자 리뷰 요청`에 실행한 명령, 실제 출력, 미실행 명령의 사유를 기록한다.
- mobile/UI hang, timeout, 또는 2분 무진행은 blind retry를 중단하고 focused rerun 명령과 screenshot/window/UI-tree evidence path를 남기며, 불가능하면 정확한 사유를 남긴다.
### OPS-1 중간 검증
```bash
$ (cd apps/cli && go test -count=1 ./internal/operator -run 'TestHeadlessValidationHandoff|TestExpectedOutputFixtures')
(output)
```
### OPS-2 중간 검증
```bash
$ bin/dev
(output)
```
```bash
$ (cd apps/cli && go test -count=1 ./...)
(output)
```
### 최종 검증
```bash
$ bin/dev
(output)
```
```bash
$ (cd apps/cli && go test -count=1 ./...)
(output)
```
```bash
$ bin/test
(output)
```
```bash
$ bin/build
(output)
```
---
> **[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.

View file

@ -0,0 +1,221 @@
<!-- task=m-operator-headless-workflow-validation/04+02,03_evidence_handoff plan=0 tag=OPS -->
# Plan - OPS Evidence Handoff
## 이 파일을 읽는 구현 에이전트에게
`CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채우는 것이 필수다. 구현 후 검증을 실행하고 실제 stdout/stderr를 붙여 넣은 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 사용자 결정, 사용자 소유 외부 환경/secret/서비스 준비, 또는 계획 범위 충돌로 막히면 review stub의 `사용자 리뷰 요청` 섹션에 근거를 채우고 멈춘다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.
## 배경
마일스톤의 마지막 task는 Flutter 화면 MVP로 넘길 검증 evidence와 남은 wireframe 의존성을 정리하는 것이다. `agent-roadmap/phase/operator-surface/milestones/operator-headless-workflow-validation.md:69`의 handoff table은 scenario별 command/input/output/exit code/protobuf field evidence를 요구한다. 이 작업은 구현된 CLI fixtures와 expected output을 정리해 후속 Flutter Operator Console MVP가 화면 구현 전에 검증할 수 있는 산출물을 남긴다.
## 사용자 리뷰 요청 흐름
구현 중 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:
- `handoff`: Flutter 화면 MVP로 넘길 수 있는 검증 evidence와 남은 wireframe 의존성을 정리한다.
- Completion mode: check-on-pass
## 분석 결과
### 읽은 파일
- `agent-ops/rules/project/rules.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`
- `bin/dev`
- `bin/test`
- `bin/build`
- `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/socket/handlers.go`
- `services/api/internal/socket/market.go`
- `services/api/internal/socket/backtest.go`
- `services/worker/internal/socket/market.go`
- `services/worker/internal/socket/backtest.go`
### 테스트 환경 규칙
- `test_env=local`.
- `agent-test/local/rules.md`는 존재하고 전문을 읽었다. 현재 shell 직접 테스트 금지, 원격 ALT checkout root에서 실행한다.
- 적용 profile: `agent-test/local/operations-smoke.md` because `apps/cli/testdata/**` and `bin/dev` are operations surface.
- focused fixture command는 `bin/test`의 CLI module subset fallback이다.
### 테스트 커버리지 공백
- Handoff artifact completeness has no existing test. Add a test or deterministic check that each scenario listed in milestone lines 73-80 has fixture, expected output, and command record.
- `bin/dev` currently lists only generic CLI command at `bin/dev:17`; it does not show scenario validate/run entrypoints.
### 심볼 참조
- renamed/removed symbol: none.
### 분할 판단
- split decision policy를 plan 파일 선택 전에 평가했다.
- shared task group: `agent-task/m-operator-headless-workflow-validation/`.
- 이 plan의 directory `04+02,03_evidence_handoff`는 predecessor indices `02`, `03`을 요구한다.
- predecessor `02`: active `agent-task/m-operator-headless-workflow-validation/02+01_api_market/complete.log` 없음, archive complete.log 없음.
- predecessor `03`: active `agent-task/m-operator-headless-workflow-validation/03+02_backtest_workflows/complete.log` 없음, archive complete.log 없음.
- runtime은 `02+01_api_market``03+02_backtest_workflows` PASS 후 이 plan을 구현해야 한다.
### 범위 결정 근거
- Roadmap/milestone 문서는 이 plan에서 직접 수정하지 않는다. PASS 완료 이벤트 후 roadmap 업데이트는 runtime 책임이다.
- tracked `docs/`는 사람용 최신 가이드만 두는 규칙이 있으므로 handoff 산출물은 CLI fixture 영역인 `apps/cli/testdata/operator/` 아래에 둔다.
- Flutter 화면 wireframe 자체는 제외하고, 남은 wireframe 의존성만 handoff에 기록한다.
### 빌드 등급
- build lane: `cloud-G07`, review lane: `cloud-G07`.
- 이유: terminal smoke evidence, expected output, exit-status contract를 후속 UI 구현 gate로 넘기는 작업이다.
## 구현 체크리스트
- [ ] `02+01_api_market``03+02_backtest_workflows``complete.log`가 active 또는 archive에 있는지 확인하고, 없으면 구현을 시작하지 않고 review stub에 blocker를 기록한다.
- [ ] `apps/cli/testdata/operator/` 아래에 scenario별 command/input/expected output/exit code/protobuf field handoff artifact를 추가한다.
- [ ] `bin/dev`에 headless scenario validate/run entrypoint를 추가하되 secret, remote path, personal endpoint를 쓰지 않는다.
- [ ] Handoff artifact completeness를 검증하는 focused test 또는 deterministic shell check를 추가한다.
- [ ] 원격 ALT checkout root에서 focused CLI test와 operations smoke 검증을 실행한다.
- [ ] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.
## 의존 관계 및 구현 순서
- 이 directory 이름의 runtime dependencies는 `04+02,03_evidence_handoff`이다.
- 구현 시작 전 `agent-task/m-operator-headless-workflow-validation/02+01_api_market/complete.log``agent-task/m-operator-headless-workflow-validation/03+02_backtest_workflows/complete.log`, 또는 대응 archive complete.log가 있어야 한다.
- 추가 hidden dependency를 만들지 않는다.
### [OPS-1] Fixture Evidence Matrix
#### 문제
Milestone handoff 기준은 `api_connection_smoke`, `market_data_status_query`, `backtest_run_request`, `backtest_run_polling`, `backtest_result_summary`, `invalid_request_matrix` 각각의 command/input/output/exit code/protobuf field evidence를 요구한다. Prior subtasks add fixtures, but no single artifact proves matrix completeness.
#### 해결 방법
- `apps/cli/testdata/operator/headless_validation.md`를 추가한다.
- 각 scenario마다:
- command
- input fixture path
- expected output fixture path
- expected exit code
- checked protobuf/view-model field
- remaining Flutter wireframe dependency
를 기록한다.
- `apps/cli/testdata/operator/expected/*.jsonl` expected output fixtures를 추가한다.
#### 수정 파일 및 체크리스트
- [ ] `apps/cli/testdata/operator/headless_validation.md`: scenario evidence matrix 추가.
- [ ] `apps/cli/testdata/operator/expected/api_connection_smoke.jsonl`: expected output 추가.
- [ ] `apps/cli/testdata/operator/expected/market_data_status_query.jsonl`: expected output 추가.
- [ ] `apps/cli/testdata/operator/expected/backtest_run_request.jsonl`: expected output 추가.
- [ ] `apps/cli/testdata/operator/expected/backtest_run_polling.jsonl`: expected output 추가.
- [ ] `apps/cli/testdata/operator/expected/backtest_result_summary.jsonl`: expected output 추가.
- [ ] `apps/cli/testdata/operator/expected/invalid_request_matrix.jsonl`: expected output 추가.
- [ ] `apps/cli/internal/operator/handoff_test.go`: matrix references all required scenario fixture and expected output paths.
#### 테스트 작성
- 작성: `apps/cli/internal/operator/handoff_test.go`
- `TestHeadlessValidationHandoffCoversRequiredScenarios`: six required scenarios present.
- `TestExpectedOutputFixturesAreValidJSONLines`: expected output fixtures parse as JSON lines and include scenario/status/action fields.
#### 중간 검증
원격 ALT checkout root 기준:
```bash
(cd apps/cli && go test -count=1 ./internal/operator -run 'TestHeadlessValidationHandoff|TestExpectedOutputFixtures')
```
기대 결과: exit code 0.
### [OPS-2] Developer Entrypoint Notes
#### 문제
`bin/dev:17` currently only shows `cd apps/cli && go run ./cmd/alt`. It does not reveal scenario validate/run commands, so the headless gate remains hard to rediscover.
Before:
```bash
# bin/dev:17
CLI:
cd apps/cli && go run ./cmd/alt
```
#### 해결 방법
- `bin/dev` CLI block에 scenario validate/run examples를 추가한다.
- Examples use localhost defaults and fixture paths only; no remote checkout path, secret, account, provider credential, or personal endpoint.
#### 수정 파일 및 체크리스트
- [ ] `bin/dev`: CLI section에 `operator scenario validate``operator scenario run` examples 추가.
- [ ] `apps/cli/internal/cli/cli_test.go` 또는 `apps/cli/internal/operator/handoff_test.go`: documented fixture path가 존재하는지 확인.
#### 테스트 작성
- 작성 또는 확장: `apps/cli/internal/operator/handoff_test.go`
- `TestHandoffDocumentedScenarioFixturesExist`.
- 별도 shell test는 만들지 않는다. `bin/dev`는 static output script이고 operations smoke `bin/test`가 workspace baseline이다.
#### 중간 검증
원격 ALT checkout root 기준:
```bash
bin/dev
(cd apps/cli && go test -count=1 ./...)
```
기대 결과: `bin/dev` exit code 0 and output includes `operator scenario validate`. CLI tests exit code 0.
## 수정 파일 요약
| 파일 | 항목 |
|------|------|
| `apps/cli/testdata/operator/headless_validation.md` | OPS-1 |
| `apps/cli/testdata/operator/expected/api_connection_smoke.jsonl` | OPS-1 |
| `apps/cli/testdata/operator/expected/market_data_status_query.jsonl` | OPS-1 |
| `apps/cli/testdata/operator/expected/backtest_run_request.jsonl` | OPS-1 |
| `apps/cli/testdata/operator/expected/backtest_run_polling.jsonl` | OPS-1 |
| `apps/cli/testdata/operator/expected/backtest_result_summary.jsonl` | OPS-1 |
| `apps/cli/testdata/operator/expected/invalid_request_matrix.jsonl` | OPS-1 |
| `apps/cli/internal/operator/handoff_test.go` | OPS-1, OPS-2 |
| `bin/dev` | OPS-2 |
## 최종 검증
원격 ALT checkout root 기준으로 실행한다. Handoff fixture checks should be fresh; use `-count=1`.
```bash
bin/dev
(cd apps/cli && go test -count=1 ./...)
bin/test
bin/build
```
기대 결과: 모든 명령 exit code 0. `bin/dev` output includes scenario validate/run examples. 원격 host나 checkout 접근 불가 시 pass가 아니라 차단 사유와 미실행 명령을 review stub에 남긴다.
모든 코드 변경 완료 후 반드시 `CODE_REVIEW-*-G??.md`의 구현 에이전트 소유 섹션을 채운다. 이 파일 작성이 구현의 마지막 단계다.

View file

@ -1,14 +1,11 @@
package main
import (
"fmt"
"os"
"git.toki-labs.com/toki/alt/apps/cli/internal/cli"
)
func main() {
if len(os.Args) > 1 && os.Args[1] == "version" {
fmt.Println("alt dev")
return
}
fmt.Println("ALT operator CLI")
os.Exit(cli.Run(os.Args[1:], os.Stdout, os.Stderr))
}

View file

@ -1,3 +1,5 @@
module git.toki-labs.com/toki/alt/apps/cli
go 1.22
require gopkg.in/yaml.v3 v3.0.1

4
apps/cli/go.sum Normal file
View file

@ -0,0 +1,4 @@
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View file

@ -0,0 +1,78 @@
// Package cli implements the ALT operator CLI dispatch. It is a thin command
// layer: argument parsing and exit-code/output contracts live here, while the
// scenario schema and validation live in the operator package.
package cli
import (
"flag"
"fmt"
"io"
"git.toki-labs.com/toki/alt/apps/cli/internal/operator"
)
// Exit codes form the CLI contract consumed by scripts and tests.
const (
exitOK = 0 // success
exitUsage = 1 // usage or unknown-command error
exitParseError = 2 // scenario parse/validation failure
)
// Run dispatches the operator CLI and returns a process exit code. stdout
// carries machine-readable status lines; stderr carries human-readable
// usage and error detail.
func Run(args []string, stdout, stderr io.Writer) int {
if len(args) == 0 {
usage(stderr)
return exitUsage
}
switch args[0] {
case "version":
fmt.Fprintln(stdout, "alt dev")
return exitOK
case "operator":
return runOperator(args[1:], stdout, stderr)
default:
fmt.Fprintf(stderr, "unknown command %q\n", args[0])
usage(stderr)
return exitUsage
}
}
// runOperator handles the `operator scenario validate --file <path>` command.
func runOperator(args []string, stdout, stderr io.Writer) int {
if len(args) < 2 || args[0] != "scenario" || args[1] != "validate" {
fmt.Fprintln(stderr, "usage: alt operator scenario validate --file <path>")
return exitUsage
}
fs := flag.NewFlagSet("operator scenario validate", flag.ContinueOnError)
fs.SetOutput(stderr)
file := fs.String("file", "", "path to operator scenario YAML file")
if err := fs.Parse(args[2:]); err != nil {
return exitUsage
}
if *file == "" {
fmt.Fprintln(stderr, "scenario file is required: --file <path>")
return exitUsage
}
sc, err := operator.LoadScenario(*file)
if err != nil {
fmt.Fprintln(stdout, "scenario=unknown status=parse_error")
fmt.Fprintf(stderr, "validate failed: %v\n", err)
return exitParseError
}
fmt.Fprintf(stdout, "scenario=%s status=valid steps=%d\n", sc.Name, len(sc.Steps))
return exitOK
}
// usage prints the top-level command summary.
func usage(w io.Writer) {
fmt.Fprintln(w, "usage: alt <command>")
fmt.Fprintln(w, "commands:")
fmt.Fprintln(w, " version")
fmt.Fprintln(w, " operator scenario validate --file <path>")
}

View file

@ -0,0 +1,46 @@
package cli
import (
"bytes"
"path/filepath"
"strings"
"testing"
)
func TestRunVersion(t *testing.T) {
var stdout, stderr bytes.Buffer
code := Run([]string{"version"}, &stdout, &stderr)
if code != 0 {
t.Fatalf("exit code = %d, want 0 (stderr=%q)", code, stderr.String())
}
if got := strings.TrimSpace(stdout.String()); got != "alt dev" {
t.Errorf("stdout = %q, want \"alt dev\"", got)
}
}
func TestRunScenarioValidate(t *testing.T) {
path := filepath.Join("..", "..", "testdata", "operator", "api_connection_smoke.yaml")
var stdout, stderr bytes.Buffer
code := Run([]string{"operator", "scenario", "validate", "--file", path}, &stdout, &stderr)
if code != 0 {
t.Fatalf("exit code = %d, want 0 (stderr=%q)", code, stderr.String())
}
out := stdout.String()
for _, want := range []string{"scenario=api_connection_smoke", "status=valid", "steps=1"} {
if !strings.Contains(out, want) {
t.Errorf("stdout %q missing %q", out, want)
}
}
}
func TestRunScenarioValidateParseError(t *testing.T) {
path := filepath.Join("..", "..", "testdata", "operator", "invalid_request_matrix.yaml")
var stdout, stderr bytes.Buffer
code := Run([]string{"operator", "scenario", "validate", "--file", path}, &stdout, &stderr)
if code != 2 {
t.Fatalf("exit code = %d, want 2 (stdout=%q stderr=%q)", code, stdout.String(), stderr.String())
}
if !strings.Contains(stdout.String(), "status=parse_error") {
t.Errorf("stdout %q missing status=parse_error", stdout.String())
}
}

View file

@ -0,0 +1,123 @@
// Package operator defines the headless operator scenario schema and the
// dry-run validation used before the API/worker runtime exists. Keeping the
// scenario format and its checks here lets the CLI verify operator workflows
// as plain YAML fixtures, in line with ALT's headless-first operations gate.
package operator
import (
"bytes"
"fmt"
"os"
"time"
"gopkg.in/yaml.v3"
)
// Action enumerates the operator step actions the headless validator
// understands. The set is intentionally small: it grows as later subtasks add
// real API/worker request runners. Any action outside this set is rejected at
// dry-run time so scenario files cannot reference unimplemented behaviour.
type Action string
const (
// ActionHello validates a basic API connection handshake.
ActionHello Action = "hello"
)
// validActions is the closed set used for strict dry-run validation.
var validActions = map[Action]bool{
ActionHello: true,
}
// Duration wraps time.Duration so scenario YAML can express timeouts as Go
// duration strings (for example "5s") rather than raw nanoseconds.
type Duration time.Duration
// UnmarshalYAML decodes a duration string such as "5s" into a Duration.
func (d *Duration) UnmarshalYAML(value *yaml.Node) error {
var s string
if err := value.Decode(&s); err != nil {
return fmt.Errorf("timeout must be a duration string: %w", err)
}
parsed, err := time.ParseDuration(s)
if err != nil {
return fmt.Errorf("invalid timeout %q: %w", s, err)
}
*d = Duration(parsed)
return nil
}
// Expect captures the assertions a step makes against its response. The fields
// are descriptive only at this stage; later subtasks compare them with real
// API/worker responses.
type Expect struct {
Status string `yaml:"status"`
Capabilities []string `yaml:"capabilities"`
}
// Step is a single operator action within a scenario.
type Step struct {
ID string `yaml:"id"`
Action Action `yaml:"action"`
Expect Expect `yaml:"expect"`
}
// Scenario is the top-level operator scenario document.
type Scenario struct {
Name string `yaml:"name"`
Timeout Duration `yaml:"timeout"`
Steps []Step `yaml:"steps"`
}
// LoadScenario reads a scenario file and returns it after strict decode and
// dry-run validation.
func LoadScenario(path string) (*Scenario, error) {
data, err := os.ReadFile(path)
if err != nil {
return nil, fmt.Errorf("read scenario: %w", err)
}
return ParseScenario(data)
}
// ParseScenario strictly decodes scenario YAML and validates it. Unknown YAML
// fields are rejected so typos in fixtures surface immediately.
func ParseScenario(data []byte) (*Scenario, error) {
dec := yaml.NewDecoder(bytes.NewReader(data))
dec.KnownFields(true)
var s Scenario
if err := dec.Decode(&s); err != nil {
return nil, fmt.Errorf("decode scenario: %w", err)
}
if err := s.Validate(); err != nil {
return nil, err
}
return &s, nil
}
// Validate runs the dry-run checks: a named scenario, at least one step, and
// every step having a unique id and a known action.
func (s *Scenario) Validate() error {
if s.Name == "" {
return fmt.Errorf("scenario name is required")
}
if len(s.Steps) == 0 {
return fmt.Errorf("scenario %q has no steps", s.Name)
}
seen := make(map[string]bool, len(s.Steps))
for i, step := range s.Steps {
if step.ID == "" {
return fmt.Errorf("step %d: id is required", i)
}
if seen[step.ID] {
return fmt.Errorf("step %q: duplicate id", step.ID)
}
seen[step.ID] = true
if !validActions[step.Action] {
return fmt.Errorf("step %q: unknown action %q", step.ID, step.Action)
}
}
return nil
}

View file

@ -0,0 +1,60 @@
package operator
import (
"path/filepath"
"strings"
"testing"
)
func fixture(t *testing.T, name string) string {
t.Helper()
return filepath.Join("..", "..", "testdata", "operator", name)
}
func TestLoadScenarioValidatesExample(t *testing.T) {
sc, err := LoadScenario(fixture(t, "api_connection_smoke.yaml"))
if err != nil {
t.Fatalf("LoadScenario returned error: %v", err)
}
if sc.Name != "api_connection_smoke" {
t.Errorf("name = %q, want api_connection_smoke", sc.Name)
}
if len(sc.Steps) != 1 {
t.Fatalf("steps = %d, want 1", len(sc.Steps))
}
if sc.Steps[0].Action != ActionHello {
t.Errorf("step action = %q, want %q", sc.Steps[0].Action, ActionHello)
}
}
func TestLoadScenarioRejectsUnknownAction(t *testing.T) {
_, err := LoadScenario(fixture(t, "invalid_request_matrix.yaml"))
if err == nil {
t.Fatal("expected error for unknown action, got nil")
}
if !strings.Contains(err.Error(), "unknown action") {
t.Errorf("error = %v, want it to mention unknown action", err)
}
}
func TestLoadScenarioRejectsMissingStepID(t *testing.T) {
data := []byte("name: missing_id\ntimeout: 1s\nsteps:\n - action: hello\n")
_, err := ParseScenario(data)
if err == nil {
t.Fatal("expected error for missing step id, got nil")
}
if !strings.Contains(err.Error(), "id is required") {
t.Errorf("error = %v, want it to mention id is required", err)
}
}
func TestParseScenarioRejectsInvalidDuration(t *testing.T) {
data := []byte("name: invalid_timeout\ntimeout: not-a-duration\nsteps:\n - id: hello\n action: hello\n")
_, err := ParseScenario(data)
if err == nil {
t.Fatal("expected error for invalid timeout, got nil")
}
if !strings.Contains(err.Error(), "invalid timeout") {
t.Errorf("error = %v, want it to mention invalid timeout", err)
}
}

View file

@ -0,0 +1,10 @@
name: api_connection_smoke
timeout: 5s
steps:
- id: hello
action: hello
expect:
status: ok
capabilities:
- hello
- request-response

View file

@ -0,0 +1,11 @@
# Validation-only fixture. Each step uses an action the dry-run validator must
# reject, so `alt operator scenario validate` exits non-zero with
# status=parse_error. It documents that unimplemented request actions cannot be
# referenced until a later subtask adds their runner.
name: invalid_request_matrix
timeout: 5s
steps:
- id: unsupported_request
action: send_unsupported_request
expect:
status: error

View file

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "Generated.xcconfig"

View file

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "Generated.xcconfig"

43
apps/client/ios/Podfile Normal file
View file

@ -0,0 +1,43 @@
# Uncomment this line to define a global platform for your project
# platform :ios, '13.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_ios_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_ios_build_settings(target)
end
end

View file

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

View file

@ -1 +1,2 @@
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
#include "ephemeral/Flutter-Generated.xcconfig"

42
apps/client/macos/Podfile Normal file
View file

@ -0,0 +1,42 @@
platform :osx, '10.15'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
project 'Runner', {
'Debug' => :debug,
'Profile' => :release,
'Release' => :release,
}
def flutter_root
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'ephemeral', 'Flutter-Generated.xcconfig'), __FILE__)
unless File.exist?(generated_xcode_build_settings_path)
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure \"flutter pub get\" is executed first"
end
File.foreach(generated_xcode_build_settings_path) do |line|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
return matches[1].strip if matches
end
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Flutter-Generated.xcconfig, then run \"flutter pub get\""
end
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
flutter_macos_podfile_setup
target 'Runner' do
use_frameworks!
flutter_install_all_macos_pods File.dirname(File.realpath(__FILE__))
target 'RunnerTests' do
inherit! :search_paths
end
end
post_install do |installer|
installer.pods_project.targets.each do |target|
flutter_additional_macos_build_settings(target)
end
end

View file

@ -362,10 +362,10 @@ packages:
dependency: transitive
description:
name: meta
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
sha256: "1741988757a65eb6b36abe716829688cf01910bbf91c34354ff7ec1c3de2b349"
url: "https://pub.dev"
source: hosted
version: "1.17.0"
version: "1.18.0"
mime:
dependency: transitive
description:
@ -589,26 +589,26 @@ packages:
dependency: transitive
description:
name: test
sha256: "280d6d890011ca966ad08df7e8a4ddfab0fb3aa49f96ed6de56e3521347a9ae7"
sha256: "8d9ceddbab833f180fbefed08afa76d7c03513dfdba87ffcec2718b02bbcbf20"
url: "https://pub.dev"
source: hosted
version: "1.30.0"
version: "1.31.0"
test_api:
dependency: transitive
description:
name: test_api
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
sha256: "949a932224383300f01be9221c39180316445ecb8e7547f70a41a35bf421fb9e"
url: "https://pub.dev"
source: hosted
version: "0.7.10"
version: "0.7.11"
test_core:
dependency: transitive
description:
name: test_core
sha256: "0381bd1585d1a924763c308100f2138205252fb90c9d4eeaf28489ee65ccde51"
sha256: "1991d4cfe85d5043241acac92962c3977c8d2f2add1ee73130c7b286417d1d34"
url: "https://pub.dev"
source: hosted
version: "0.6.16"
version: "0.6.17"
typed_data:
dependency: transitive
description: