19 KiB
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 기대 일치,1typed error/expectation mismatch,2parse/validation/flag 오류,3transport 실패. - 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/statuskey와 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_bars → ImportDailyBarsResponse.instrument_count/bar_count; ListInstrumentsResponse.instruments, ListBarsResponse.bars; collection_freshness → freshness_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_bars → ImportDailyBarsResponse.instrument_count/bar_count; ListInstrumentsResponse.instruments, ListBarsResponse.bars; collection_freshness → freshness_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_bars → ImportDailyBarsResponse.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, starting_cash, ending_equity, total_return, trade_count |
0 (성공), 1 (missing run/typed error) |
yes | backtest 결과 요약 (metrics chips) | GetBacktestResultResponse.result.summary |
차트/메트릭 레이아웃 미정 |
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 미정 |
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 종목 20240527–20240528 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_barsstep은 KIS import를 포함하므로 worker에KIS_ACTIVE_ENV및 대응 credential 환경변수가 주입된 상태여야 한다. backtest_matrix_seed_import.yaml은timeout: 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은 exit0으로 끝난다. 이는 API가 올바른 typedErrorInfo를 돌려준다는 evidence다.- 잘못된 fixture 자체(필수 step id 누락 등)의 non-zero exit는
testdata/operator/malformed_scenario.yaml을alt operator scenario validate로 돌리면 exit code2로 재현된다.
남은 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, 비밀번호 등)는 노출하지 않는다.
-
원격 호스트 및 코드 정합성 확인
- 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"
-
인프라(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"
- docker compose를 통해 실행 중인 DB 및 Redis 서비스 컨테이너의 준비 상태를 점검한다:
-
마이그레이션 적용 및 버전 확인
- 현재 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;'"
- 현재 checkout 기준으로 데이터베이스 마이그레이션을 실행한다: