alt/apps/cli/testdata/operator/headless_validation.md
toki 76d916f1df feat: backtest multi-timeframe coverage - minute import rejection archive, monthly aggregation, runner/scenario fixes
- Archive G06 minute import rejection task files (plan, code review, complete)
- Add monthly aggregation module (services/worker/internal/marketdata/aggregation/)
- Fix runner and scenario handling for rejected minute imports
- Update tests and testdata for minute import rejection flow
- Update G05 code review for monthly aggregation
2026-06-19 08:23:28 +09:00

20 KiB
Raw Blame History

Headless Validation Handoff

이 문서는 operator-headless-workflow-validation 마일스톤의 handoff table 기준 (agent-roadmap/phase/operator-surface/milestones/operator-headless-workflow-validation.md) 을 충족하는 검증 evidence를 한곳에 모은다. 후속 Flutter Operator Console MVP는 실화면을 만들기 전에 이 matrix로 각 scenario의 command/input/expected output/exit code/protobuf field를 확인한다.

ALT 운영 UI 게이트(agent-ops/rules/project/rules.md의 "운영 UI 구현 게이트")에 따라 모든 검증은 화면 없이 CLI와 machine-readable 출력으로 먼저 수행한다.

공통 실행 형식

  • validate(offline, 소켓 없이 dry-run): alt operator scenario validate --file <yaml>
  • run(실 API 연결): alt operator scenario run --file <yaml> --api-url ws://127.0.0.1:18030/socket --output jsonl
  • 로컬 개발에서는 cd apps/cli && go run ./cmd/alt <위 인자>로 실행한다(bin/dev 참고).
  • exit code 계약: 0 전 step 기대 일치, 1 typed error/expectation mismatch, 2 parse/validation/flag 오류, 3 transport 실패.
  • expected output fixture는 --output jsonl 기준의 step/summary line이다. run_id, count, starting_cash, ending_equity, total_return, trade_count, error_message 값은 seed 데이터에 따라 달라지는 illustrative 값이며, 후속 MVP가 계약으로 의존하는 부분은 line별 scenario/step/action/status key와 step별로 검증하는 protobuf field의 존재 여부다.

command-first workflow handoff matrix

이 matrix는 import/status/backtest workflow별로 command, expected output keys, 반복 운영 여부, UI candidate/defer reason을 명시한다. 화면 레이아웃·위젯·차트는 범위 밖이며 Flutter Operator Console MVP phase에서 별도로 정의한다.

Scenario Command Input fixture Expected output fixture Expected output keys Expected exit code Repeatable operation UI candidate Checked protobuf/view-model field UI defer reason
api_connection_smoke alt operator scenario run --file testdata/operator/api_connection_smoke.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/api_connection_smoke.yaml testdata/operator/expected/api_connection_smoke.jsonl scenario, status, type, action 0 (성공), 3 (transport 실패 시) yes connection status display HelloResponse.capabilities; summary status/exit_code Flutter capability chip layout 미정
market_data_status_query alt operator scenario run --file testdata/operator/market_data_status_query.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/market_data_status_query.yaml testdata/operator/expected/market_data_status_query.jsonl scenario, status, type, action, provider, instrument_count, bar_count, count, universe, freshness_status, missing_count, latest_yyyymmdd, gap_status, gap_count, duplicate_count, provider_delay_days 0 (성공/empty), 1 (mismatch) yes market data status card (종목수·바개수·최신도) import_daily_barsImportDailyBarsResponse.instrument_count/bar_count; ListInstrumentsResponse.instruments, ListBarsResponse.bars; collection_freshnessfreshness_status, missing_count, latest_yyyymmdd; ErrorInfo.code 차트/바 시각화 레이아웃 미정
us_market_data_status_query alt operator scenario run --file testdata/operator/us_market_data_status_query.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/us_market_data_status_query.yaml testdata/operator/expected/us_market_data_status_query.jsonl scenario, status, type, action, provider, instrument_count, bar_count, count, universe, freshness_status, missing_count, latest_yyyymmdd, gap_status, gap_count, duplicate_count, provider_delay_days 0 (성공/empty), 1 (mismatch) yes market data status card (종목수·바개수·최신도) import_daily_barsImportDailyBarsResponse.instrument_count/bar_count; ListInstrumentsResponse.instruments, ListBarsResponse.bars; collection_freshnessfreshness_status, missing_count, latest_yyyymmdd; ErrorInfo.code 차트/바 시각화 레이아웃 미정
backtest_run_request alt operator scenario run --file testdata/operator/backtest_run_request.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/backtest_run_request.yaml testdata/operator/expected/backtest_run_request.jsonl scenario, status, type, action, run_id, run_status 0 (성공), 1 (typed error/mismatch) yes backtest 생성 form (parameter 입력/검증) StartBacktestRequest.spec.selector; StartBacktestResponse.run.id, BacktestRun.status form validation layout 미정
backtest_run_polling alt operator scenario run --file testdata/operator/backtest_run_polling.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/backtest_run_polling.yaml testdata/operator/expected/backtest_run_polling.jsonl scenario, status, type, action, provider, instrument_count, bar_count, run_id, run_status 0 (terminal 도달), 3 (polling timeout) yes backtest 진행 상태 표시 (상태 칩/진행바) named universe import_daily_barsImportDailyBarsResponse.provider/instrument_count/bar_count; StartBacktestRequest.spec.selector; StartBacktestResponse.run.id; GetBacktestRunResponse.run.status 전이 (pending→running→succeeded/failed/canceled) 진행바/상태 전이 애니메이션 미정
backtest_result_summary alt operator scenario run --file testdata/operator/backtest_result_summary.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/backtest_result_summary.yaml testdata/operator/expected/backtest_result_summary.jsonl scenario, status, type, action, run_id, run_status, starting_cash, ending_equity, total_return, trade_count 0 (성공), 1 (missing run/typed error) yes backtest 결과 요약 (metrics chips) GetBacktestResultResponse.result.summary; GetBacktestRunResponse.run.status 차트/메트릭 레이아웃 미정
backtest_matrix_success alt operator scenario run --file testdata/operator/backtest_matrix_success.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/backtest_matrix_success.yaml testdata/operator/expected/backtest_matrix_success.jsonl scenario, status, type, action, matrix_run_id, universe, strategy_id, timeframe, period_id, run_id, run_status, exit_code 0 (성공) yes backtest matrix batch 성공 상태 표시 StartBacktestResponse.run.id, BacktestRun.status; summary exit_code batch 결과 화면 레이아웃 미정
backtest_matrix_failed alt operator scenario run --file testdata/operator/backtest_matrix_failed.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/backtest_matrix_failed.yaml testdata/operator/expected/backtest_matrix_failed.jsonl scenario, status, type, action, matrix_run_id, universe, strategy_id, timeframe, period_id, run_id, run_status, exit_code 0 (terminal failed) yes backtest matrix batch 실패 상태 표시 StartBacktestResponse.run.id, BacktestRun.status; summary exit_code batch 결과 화면 레이아웃 미정
backtest_matrix_timeout alt operator scenario run --file testdata/operator/backtest_matrix_timeout.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/backtest_matrix_timeout.yaml testdata/operator/expected/backtest_matrix_timeout.jsonl scenario, status, type, action, matrix_run_id, universe, strategy_id, timeframe, period_id, run_id, run_status, exit_code 3 (timeout) yes backtest matrix batch 타임아웃 표시 StartBacktestResponse.run.id; summary exit_code batch 결과 화면 레이아웃 미정
backtest_matrix_mismatch alt operator scenario run --file testdata/operator/backtest_matrix_mismatch.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/backtest_matrix_mismatch.yaml testdata/operator/expected/backtest_matrix_mismatch.jsonl scenario, status, type, action, matrix_run_id, universe, strategy_id, timeframe, period_id, run_id, run_status, exit_code 1 (mismatch) yes backtest matrix batch 기대 불일치 표시 StartBacktestResponse.run.id, BacktestRun.status; summary exit_code batch 결과 화면 레이아웃 미정
invalid_request_matrix alt operator scenario run --file testdata/operator/invalid_request_matrix.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/invalid_request_matrix.yaml testdata/operator/expected/invalid_request_matrix.jsonl scenario, status, type, action, error_code, error_message 0 (기대한 typed error 도달), 2 (malformed scenario) no (개발/검증 전용) error/message 노출 (검증용) ErrorInfo.code (invalid_request, not_found) error 메시지 UI 미정
kis_daily_import_smoke alt operator scenario run --file testdata/operator/kis_daily_import_smoke.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/kis_daily_import_smoke.yaml testdata/operator/expected/kis_daily_import_smoke.jsonl scenario, status, type, action, provider, instrument_count, bar_count 0 (성공), 1 (mismatch/typed error) yes KIS import status (진행률/결과) ImportDailyBarsResponse.provider, instrument_count, bar_count import 진행 UI 미정
minute_import_rejected alt operator scenario run --file testdata/operator/minute_import_rejected.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/minute_import_rejected.yaml testdata/operator/expected/minute_import_rejected.jsonl scenario, status, type, action, error_code, error_message 0 (기대한 typed error 도달), 2 (malformed scenario) no (개발/검증 전용) error/message 노출 (검증용) ErrorInfo.code (invalid_request); ImportDailyBarsResponse.error error 메시지 UI 미정
paper_trading_state alt operator scenario run --file testdata/operator/paper_trading_state.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/paper_trading_state.yaml testdata/operator/expected/paper_trading_state.jsonl scenario, status, type, action, account_id, run_id, run_status, cash, equity_point_count, fill_count, latest_equity, position_count, risk 0 (성공/terminal 도착), 1 (typed error/mismatch) yes paper trading dashboard (account 상태/포지션/평가금액/리스크) StartPaperTradingResponse.run.id, run_status; GetPaperTradingStateResponse.account_id, cash, equity_point_count, fill_count, latest_equity, position_count, risk_rejections; summary status/exit_code Flutter dashboard 레이아웃 미정
paper_order_lifecycle alt operator scenario run --file testdata/operator/paper_order_lifecycle.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/paper_order_lifecycle.yaml testdata/operator/expected/paper_order_lifecycle.jsonl scenario, status, type, action, account_id, run_id, run_status, cash, equity_point_count, fill_count, latest_equity, position_count, risk, order_id, order_status, fill_price 0 (성공/terminal 도착), 1 (typed error/mismatch) yes paper order management list (주문 상태/체결가/취소) StartPaperTradingResponse.run.id, run_status; SubmitPaperOrderResponse.order.id, order_status; FillPaperOrderResponse.fill_price, order_status; CancelPaperOrderResponse.order_status; GetPaperTradingStateResponse.account_id, cash, fill_count; summary status/exit_code Flutter 주문 목록/상세 레이아웃 미정
live_order_lifecycle alt operator scenario run --file testdata/operator/live_order_lifecycle.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/live_order_lifecycle.yaml testdata/operator/expected/live_order_lifecycle.jsonl scenario, status, type, action, live_order_id, live_order_status, broker_order_id, broker_status, operator_confirmed 0 (성공/operator gate 통과), 1 (typed error/confirmation missing/mismatch) yes live order management (실주문 상태/취소/브로커 상태) SubmitLiveOrderResponse.order.id, order.status, order.broker_id, order.broker_status; OperatorConfirmation.confirmed; CancelLiveOrderResponse.order.status; GetLiveOrderResponse.order.status; summary status/exit_code Flutter 실주문 목록/상세 레이아웃 미정
live_risk_kill_switch alt operator scenario run --file testdata/operator/live_risk_kill_switch.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/live_risk_kill_switch.yaml testdata/operator/expected/live_risk_kill_switch.jsonl scenario, status, type, action, max_daily_orders, max_open_orders, allow_short_selling, kill_switch_halted, kill_switch_reason 0 (성공/정책 조회·킬스위치 해제), 1 (typed error/mismatch) yes live risk 제어 패널 (정책 표시·킬스위치 토글) GetLiveRiskPolicyResponse.policy.max_daily_orders, max_open_orders, allow_short_selling; GetLiveKillSwitchResponse.state.halted, state.reason; SetLiveKillSwitchResponse.state.halted, state.reason; summary status/exit_code Flutter 킬스위치 토글·정책 표시 레이아웃 미정
live_account_sync alt operator scenario run --file testdata/operator/live_account_sync.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/live_account_sync.yaml testdata/operator/expected/live_account_sync.jsonl scenario, status, type, action, live_account_id, live_broker, cash_count, live_position_count, synced_at_unix_ms, live_stale 0 (sync 성공·snapshot 조회 성공·not_found 기대 충족), 1 (typed error/mismatch) yes live 계좌 잔고·포지션 스냅샷 (계좌 현황 패널) SyncLiveAccountResponse.snapshot.account_id, broker, cash[], positions[], synced_at_unix_ms, stale; GetLiveAccountSnapshotResponse.snapshot; summary status/exit_code Flutter 계좌 잔고·포지션 목록 레이아웃 미정
live_audit_query alt operator scenario run --file testdata/operator/live_audit_query.yaml --api-url ws://127.0.0.1:18030/socket --output jsonl testdata/operator/live_audit_query.yaml testdata/operator/expected/live_audit_query.jsonl scenario, status, type, action, audit_event_count 0 (조회 성공·필터 조회 성공), 1 (typed error/mismatch) yes live 감사 이력 조회 (운영 감사 패널) ListLiveAuditEventsResponse.events[]; LiveAuditEvent.event_id, type, account_id, order_id, timestamp_unix_ms; summary status/exit_code Flutter 감사 이력 목록 레이아웃 미정

Matrix pre-seed 절차

backtest_matrix_success.yaml은 matrix run마다 import_daily_bars step 없이 바로 start/poll을 수행한다. 신규 원격 DB처럼 해당 종목·기간 bar 데이터가 없는 상태에서 실행하면 backtest가 failed 상태로 끝나거나 data-missing 오류가 발생해 deterministic 성공을 보장할 수 없다.

따라서 신규 원격 DB에서 matrix를 실행하기 전에 반드시 backtest_matrix_seed_import.yaml을 먼저 실행해 seed 데이터를 DB에 적재해야 한다. backtest_matrix_seed_import.yaml은 import-only scenario로 kr-backtest-smoke universe의 005930 종목 2024052720240528 bar를 KIS에서 가져와 DB에 저장하고 exit 0으로 종료한다. backtest 실행이나 result polling을 포함하지 않으므로 07+05_result_poll_remote 작업 완료 여부와 독립적으로 사용할 수 있다.

실행 순서 (신규 원격 DB 기준)

# Step 1: seed/import — import_daily_bars(20240527-20240528, 005930)만 실행, exit 0 확인
go run ./cmd/alt operator scenario run \
  --file testdata/operator/backtest_matrix_seed_import.yaml \
  --api-url ws://127.0.0.1:18030/socket \
  --output jsonl

# Step 2: matrix batch — seeded data로 deterministic 성공 검증
go run ./cmd/alt operator scenario run \
  --file testdata/operator/backtest_matrix_success.yaml \
  --api-url ws://127.0.0.1:18030/socket \
  --output jsonl
  • Step 1의 import_kr_daily_bars step은 KIS import를 포함하므로 worker에 KIS_ACTIVE_ENV 및 대응 credential 환경변수가 주입된 상태여야 한다.
  • backtest_matrix_seed_import.yamltimeout: 30s(per-step)를 사용해 KIS 응답 시간 변동에 여유를 둔다. 이 fixture는 backtest 실행·결과 조회·polling을 포함하지 않는다.
  • backtest_matrix_success.yaml은 matrix 고유의 timeout: 5s 설정을 사용한다. Step 1 완료 후 data가 DB에 있으면 각 matrix run은 5초 이내에 succeeded로 종료된다.
  • runner 구조 변경(matrix 내 seed step 자동 실행) 없이 handoff 절차로만 seed를 보장한다.

Exit code 계약 보강 노트

  • invalid_request_matrix.yaml은 typed error가 "도착하는지"를 검증하므로, 의도한 error code가 오면 step은 통과하고 run은 exit 0으로 끝난다. 이는 API가 올바른 typed ErrorInfo를 돌려준다는 evidence다.
  • 잘못된 fixture 자체(필수 step id 누락 등)의 non-zero exit는 testdata/operator/malformed_scenario.yamlalt operator scenario validate로 돌리면 exit code 2로 재현된다.

남은 wireframe 의존성 요약

마일스톤 비범위(out-of-scope) 항목과 직접 연결된다:

  • Flutter 실화면 구현 및 dashboard layout/navigation/card/chart/form 시각 디자인 확정.
  • 위 matrix의 scenario별 "UI defer reason" 열에 적힌 화면 정의.
  • push notification, production 운영 자동화는 이번 마일스톤 handoff 범위 밖이다.

이 화면 정의는 Flutter Operator Console UX Plan / Flutter Operator Console MVP에서 wireframe 또는 동등한 화면 정의 산출물과 사용자 승인 기준이 준비되어야 구현 잠금이 풀린다(운영 UI 구현 게이트).

원격 환경 동기화 및 마이그레이션 확인 절차

원격 runner(macOS host) 및 code-server 컨테이너 네트워크 내의 DB/Redis 인프라 정합성과 마이그레이션 확인은 아래 절차를 통해 검증한다. 환경 정보(IP, 비밀번호 등)는 노출하지 않는다.

  1. 원격 호스트 및 코드 정합성 확인

    • macOS host와 로컬 code-server 컨테이너는 동일한 workspace 디렉토리를 마운트하여 사용하므로 코드는 동기화된다.
    • git commit hash 및 dirty 파일 상태가 일치하는지 확인한다:
      ssh <ALT_REMOTE_SSH> "cd <ALT_REMOTE_ALT_ROOT> && git rev-parse HEAD && git status --short"
      
  2. 인프라(PostgreSQL / Redis) 헬스체크

    • docker compose를 통해 실행 중인 DB 및 Redis 서비스 컨테이너의 준비 상태를 점검한다:
      ssh <ALT_REMOTE_SSH> "docker exec code-server-postgres pg_isready -U <DB_USER> -d <DB_NAME>"
      ssh <ALT_REMOTE_SSH> "docker exec code-server-redis redis-cli ping"
      
  3. 마이그레이션 적용 및 버전 확인

    • 현재 checkout 기준으로 데이터베이스 마이그레이션을 실행한다:
      ssh <ALT_REMOTE_SSH> "docker exec code-server bash -lc 'cd <CONTAINER_ALT_ROOT>/services/worker && DATABASE_URL=<DATABASE_URL> REDIS_URL=<REDIS_URL> go run ./cmd/alt-worker-migrate'"
      
    • schema_migrations 테이블에서 적용된 최신 마이그레이션 버전을 확인하여 일치하는지 점검한다:
      ssh <ALT_REMOTE_SSH> "docker exec code-server-postgres psql -U <DB_USER> -d <DB_NAME> -t -A -c 'select max(version) from schema_migrations;'"