alt/agent-task/m-live-trading-boundary/03+01,02_risk_kill_switch/PLAN-cloud-G06.md
toki 5488c4bc8b feat(live-trading): 브로커 포트 경계를 추가한다
실거래 경계를 열기 위한 domain, contract, API, worker, client parser 등록을 함께 추가한다.

브로커 capability 조회와 live trading capability 광고가 각 런타임에서 일관되게 검증되도록 테스트와 review artifact를 포함한다.
2026-06-07 10:24:28 +09:00

14 KiB

Plan - LIVE_RISK

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

CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 채우는 것은 필수다. 구현 후 검증을 실행하고 실제 변경 내용, 설계 결정, 명령 출력(stdout/stderr)을 기록한 뒤 active 파일을 그대로 두고 리뷰 준비를 보고한다. 최종 판정, 로그 rename, complete.log, archive 이동은 code-review 전용이다.

구현 중 사용자만 결정할 수 있는 사항, 사용자 소유 외부 환경/secret, 또는 계획 범위 충돌 없이는 안전하게 진행할 수 없으면 active review stub의 사용자 리뷰 요청 섹션에 정확한 결정, 근거, 실행 명령/출력, 재개 조건을 기록하고 중단한다. 구현 에이전트는 사용자에게 직접 묻거나 선택지를 제시하거나 request_user_input을 호출하지 않는다. 후속 에이전트가 명령 재실행이나 산출물 수집으로 해소할 수 있는 검증 증거 공백은 사용자 리뷰 요청이 아니다.

배경

Live order lifecycle이 생긴 뒤에는 실거래 주문 제출 전 risk guard와 kill switch가 반드시 있어야 한다. 사용자는 수익률을 포기하지 말라고 결정했지만, 실거래 경계에는 손실 확대를 막는 최소 정책값과 즉시 차단 장치가 필요하다. 이 계획은 수치를 설계에 고정하지 않고 조정 가능한 기본값으로 두며, 기능 capability는 닫지 않는다.

사용자 리뷰 요청 흐름

구현 중 차단은 active CODE_REVIEW-*-G??.md사용자 리뷰 요청 섹션에 기록한다. 이 섹션은 agent-ops/skills/common/_templates/implementation-user-review-request-section.md 형식을 따른다. 구현 중 직접 사용자 프롬프트는 금지이며, code-review가 요청의 타당성을 검증하고 실제 USER_REVIEW.md 작성을 소유한다.

Roadmap Targets

  • Milestone: agent-roadmap/phase/trading-expansion/milestones/live-trading-boundary.md
  • Task ids:
    • risk-limits: 실거래 전 최소 risk limit과 kill switch가 있다.
  • Completion mode: check-on-pass

분석 결과

읽은 파일

  • agent-roadmap/current.md
  • agent-roadmap/phase/trading-expansion/PHASE.md
  • agent-roadmap/phase/trading-expansion/milestones/live-trading-boundary.md
  • agent-ops/rules/project/rules.md
  • agent-ops/rules/project/domain/domain-model/rules.md
  • agent-ops/rules/project/domain/contracts/rules.md
  • agent-ops/rules/project/domain/worker/rules.md
  • agent-ops/rules/project/domain/api/rules.md
  • agent-ops/skills/common/plan/SKILL.md
  • agent-test/local/rules.md
  • agent-test/local/domain-model-smoke.md
  • agent-test/local/contracts-smoke.md
  • agent-test/local/worker-smoke.md
  • agent-test/local/api-smoke.md
  • agent-test/local/client-smoke.md
  • packages/domain/backtest/types.go
  • packages/domain/backtest/types_test.go
  • packages/domain/market/types.go
  • packages/contracts/proto/alt/v1/paper_trading.proto
  • bin/contracts-gen
  • bin/contracts-check
  • services/worker/internal/papertrading/service.go
  • services/worker/internal/papertrading/service_test.go
  • services/worker/internal/socket/backtest.go
  • services/worker/internal/socket/handlers.go
  • services/worker/internal/socket/paper.go
  • services/worker/internal/socket/paper_mapping.go
  • services/worker/internal/socket/paper_test.go
  • services/api/internal/workerclient/client.go
  • services/api/internal/socket/paper.go
  • services/api/internal/socket/paper_test.go
  • apps/cli/internal/operator/scenario.go
  • apps/cli/internal/operator/runner.go
  • apps/cli/internal/operator/runner_paper_test.go
  • apps/cli/internal/operator/output.go

테스트 환경 규칙

test_env=local이다. domain/contracts/worker/api/client smoke profile을 적용한다. .proto 확장이 있으면 bin/contracts-gen, bin/contracts-check, go test ./packages/contracts/gen/go/..., bin/lint를 실행한다. Worker/API/CLI 변경은 각각 go test ./services/worker/..., go test ./services/api/..., go test ./apps/cli/...로 검증한다.

테스트 커버리지 공백

  • Live risk policy defaults: 기존 테스트 없음. 새 domain/worker tests가 필요하다.
  • Kill switch default deny와 operator override flow: 기존 paper risk는 short selling만 막는다(services/worker/internal/papertrading/service.go:277). live kill switch 테스트가 필요하다.
  • Submit path risk guard: 02의 live submit path에 risk check가 붙어야 하므로 통합 worker socket test가 필요하다.
  • CLI risk/kill output: 기존 paper output은 risk=clear/blocked만 있다(apps/cli/internal/operator/output.go:52). live risk decision fields가 필요하다.

심볼 참조

Renamed/removed symbols: none. 새 risk/kill switch symbols만 추가한다.

분할 판단

공통 task group은 m-live-trading-boundary다. sibling split은 01_broker_port, 02+01_order_lifecycle, 03+01,02_risk_kill_switch, 04+01_account_sync, 05+02,03,04_audit_trail이다. 이 subtask는 predecessor 0102가 필요하다. 현재 active 01_broker_port/complete.log02+01_order_lifecycle/complete.log는 없으므로 구현 시작 전 두 predecessor PASS가 필요하다.

범위 결정 근거

이 subtask는 account sync와 durable audit을 구현하지 않는다. Risk policy 값은 기본값으로 제안하되 config/test fixture로 분리한다. 구체 주문 유형을 제한하지 않으며, risk check는 notional/quantity/order count/kill switch처럼 주문 type과 독립적인 정책을 먼저 평가한다.

빌드 등급

Build lane cloud-G06, review lane cloud-G06. 실거래 제출 차단 경계와 protocol/API/CLI 영향이 있으나 storage나 external broker network는 없다.

의존 관계 및 구현 순서

Directory dependency source of truth: 03+01,02_risk_kill_switch means predecessor indices 01 and 02. 구현은 01_broker_port02+01_order_lifecyclecomplete.log가 생긴 뒤 시작한다.

구현 체크리스트

  • [LIVE_RISK-1] live risk policy domain/config defaults를 추가한다.
  • [LIVE_RISK-2] worker submit path에 risk guard와 kill switch를 적용한다.
  • [LIVE_RISK-3] risk/kill switch contract, API forwarder, CLI actions/output을 추가한다.
  • [LIVE_RISK-4] focused verification과 affected module tests를 실행한다.
  • CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

[LIVE_RISK-1] Risk Policy Defaults

문제

packages/domain/backtest/types.go:395CheckRisk는 paper account short selling만 평가한다. Live risk는 order notional, order count, kill switch를 표현할 domain/config 경계가 없다.

해결 방법

packages/domain/tradingRiskPolicy, RiskDecision, KillSwitchState를 추가한다. 기본값은 조정 가능한 값으로 둔다: kill switch default halted=true, max notional KRW 1000000, USD 1000, max daily orders 5, max open live orders 3, allow short selling false. 구현자가 config struct와 test fixture로 이 값을 주입하게 해서 후속 수치 조정은 코드 구조 변경 없이 가능하게 한다.

Before:

// packages/domain/backtest/types.go:395
func CheckRisk(account PaperAccount, order OrderIntent) RiskDecision {
	if account.RiskSettings.AllowShortSelling {
		return RiskDecision{Allowed: true, Reason: "risk check passed"}
	}

After:

type RiskPolicy struct {
	MaxOrderNotionalByCurrency map[market.Currency]market.Decimal
	MaxDailyOrders             int
	MaxOpenOrders              int
	AllowShortSelling          bool
}

type KillSwitchState struct {
	Halted bool
	Reason string
}

수정 파일 및 체크리스트

  • packages/domain/trading/types.go 또는 risk.go에 risk types를 추가한다.
  • packages/domain/trading/types_test.go 또는 risk_test.go에 default/boundary tests를 추가한다.
  • services/worker/internal/livetrading/config.go에 worker runtime 기본값을 둔다.

테스트 작성

작성한다. Notional below/above threshold, kill switch halted, custom order type unaffected, empty/invalid default handling을 검증한다.

중간 검증

go test ./packages/domain/...

기대 결과: exit code 0.

[LIVE_RISK-2] Worker Guard

문제

02의 live submit path는 operator confirmation 뒤 broker port를 호출한다. Risk guard가 없으면 confirmation만으로 live broker 호출이 가능해진다.

해결 방법

livetrading.Service.SubmitLiveOrder에서 operator confirmation 후, broker call 전 risk guard를 실행한다. Kill switch가 halted이면 invalid_request 또는 risk_blocked typed error로 응답하고 broker를 호출하지 않는다. Risk denial은 order status rejected 또는 blocked로 저장하고 reason을 반환한다. Error vocabulary는 기존 invalid_request, unavailable, not_found, timeout, internal을 재사용하거나 live-specific code를 contract에 추가하되 API/CLI tests와 일치시킨다.

수정 파일 및 체크리스트

  • services/worker/internal/livetrading/service.go에 risk evaluator를 주입한다.
  • services/worker/internal/livetrading/service_test.go에 broker-not-called tests를 추가한다.
  • services/worker/internal/socket/live.go에 risk error mapping을 추가한다.
  • services/worker/internal/socket/live_test.go에 submit risk blocked tests를 추가한다.

테스트 작성

작성한다. TestSubmitLiveOrderKillSwitchBlocksBeforeBroker, TestSubmitLiveOrderMaxNotionalBlocks, TestSubmitLiveOrderRiskAllowedCallsBroker를 포함한다.

중간 검증

go test ./services/worker/internal/livetrading ./services/worker/internal/socket

기대 결과: exit code 0.

[LIVE_RISK-3] Contract/API/CLI Surface

문제

Operator가 현재 risk policy와 kill switch 상태를 조회/전환할 headless command가 없다. Live submit 실패 이유도 CLI output에 안정적으로 남지 않는다.

해결 방법

live_trading.protoGetLiveRiskPolicyRequest/Response, GetLiveKillSwitchRequest/Response, SetLiveKillSwitchRequest/Response, LiveRiskDecision을 추가한다. API는 thin forwarder로 구현한다. CLI는 get_live_risk_policy, get_live_kill_switch, set_live_kill_switch actions와 live submit risk output fields를 추가한다.

수정 파일 및 체크리스트

  • packages/contracts/proto/alt/v1/live_trading.proto를 확장한다.
  • bin/contracts-gen/generated output을 갱신한다.
  • worker/API/CLI parser map에 새 messages를 등록한다.
  • services/api/internal/workerclient/client.goservices/api/internal/socket/live.go를 확장한다.
  • apps/cli/internal/operator/scenario.go, runner.go, output.go, client.go를 확장한다.
  • apps/cli/testdata/operator/live_risk_kill_switch.yaml과 expected JSONL을 추가한다.

테스트 작성

작성한다. API forwarder tests, CLI validation/output tests, fixture parse tests를 추가한다.

중간 검증

bin/contracts-gen
bin/contracts-check
go test ./services/api/internal/socket ./apps/cli/internal/operator

기대 결과: exit code 0.

[LIVE_RISK-4] Verification

문제

Risk guard는 money-moving boundary에 직접 영향을 주므로 domain/worker/API/CLI를 함께 검증해야 한다.

해결 방법

focused와 affected module tests를 실행한다.

수정 파일 및 체크리스트

  • bin/contracts-check를 실행한다.
  • go test ./packages/domain/...를 실행한다.
  • go test ./packages/contracts/gen/go/...를 실행한다.
  • go test ./services/worker/...를 실행한다.
  • go test ./services/api/...를 실행한다.
  • go test ./apps/cli/...를 실행한다.

테스트 작성

추가 테스트는 LIVE_RISK-1부터 LIVE_RISK-3에 포함한다.

중간 검증

bin/contracts-check
go test ./packages/domain/...
go test ./packages/contracts/gen/go/...
go test ./services/worker/...
go test ./services/api/...
go test ./apps/cli/...
bin/lint

기대 결과: 모두 exit code 0. Go test cache output은 최종 검증에서 허용한다. Flutter/Dart toolchain 부재로 bin/lint가 실행 불가하면 pass가 아니며 차단 근거를 기록한다.

수정 파일 요약

파일 항목
packages/domain/trading/risk.go LIVE_RISK-1
packages/domain/trading/risk_test.go LIVE_RISK-1
services/worker/internal/livetrading/config.go LIVE_RISK-1
services/worker/internal/livetrading/service.go LIVE_RISK-2
services/worker/internal/livetrading/service_test.go LIVE_RISK-2
services/worker/internal/socket/live.go LIVE_RISK-2, LIVE_RISK-3
services/worker/internal/socket/live_test.go LIVE_RISK-2
packages/contracts/proto/alt/v1/live_trading.proto LIVE_RISK-3
services/worker/internal/contracts/parser_map.go LIVE_RISK-3
services/api/internal/contracts/parser_map.go LIVE_RISK-3
apps/cli/internal/operator/parser_map.go LIVE_RISK-3
services/api/internal/workerclient/client.go LIVE_RISK-3
services/api/internal/socket/live.go LIVE_RISK-3
services/api/internal/socket/live_test.go LIVE_RISK-3
apps/cli/internal/operator/scenario.go LIVE_RISK-3
apps/cli/internal/operator/runner.go LIVE_RISK-3
apps/cli/internal/operator/output.go LIVE_RISK-3
apps/cli/internal/operator/client.go LIVE_RISK-3
apps/cli/internal/operator/runner_live_test.go LIVE_RISK-3
apps/cli/testdata/operator/live_risk_kill_switch.yaml LIVE_RISK-3
apps/cli/testdata/operator/expected/live_risk_kill_switch.jsonl LIVE_RISK-3

최종 검증

bin/contracts-check
go test ./packages/domain/...
go test ./packages/contracts/gen/go/...
go test ./services/worker/...
go test ./services/api/...
go test ./apps/cli/...
bin/lint

기대 결과: 모든 명령 exit code 0.

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