<!-- task=m-paper-trading-command-workflow/02+01_risk_command plan=0 tag=PAPER_RISK -->

# Code Review Reference - PAPER_RISK

> **[IMPLEMENTING AGENT — READ FIRST] Filling in this file is the mandatory final step of implementation.**
> Complete implementation-owned sections, paste actual verification output, and stop with active files in place for review.
> If user-only input is required, fill `사용자 리뷰 요청` and do not ask the user directly.

## 개요

date=2026-06-05
task=m-paper-trading-command-workflow/02+01_risk_command, plan=0, tag=PAPER_RISK

## Roadmap Targets

- Milestone: `agent-roadmap/phase/trading-expansion/milestones/paper-trading-command-workflow.md`
- Task ids:
  - `paper-risk-command`: risk guard command workflow
- Completion mode: check-on-pass

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

> **[REVIEW AGENT ONLY]** PASS/WARN/FAIL 판정, log archive, `complete.log`, task archive는 리뷰 에이전트 전용이다.

---

## 구현 항목별 완료 여부

| 항목 | 완료 여부 |
|------|---------|
| [PAPER_RISK-1] risk state contract and worker mapping | [x] |
| [PAPER_RISK-2] CLI risk output keys | [x] |
| [PAPER_RISK-3] risk fixture/tests | [x] |

## 구현 체크리스트

- [x] [PAPER_RISK-1] `Snapshot.Rejected`를 service state와 protobuf state로 additive하게 노출한다.
- [x] [PAPER_RISK-2] worker/API/CLI mapping이 risk summary를 stable key로 출력하게 한다.
- [x] [PAPER_RISK-3] risk allow/block fixture 또는 fake API test가 typed status/error key를 검증하게 한다. 검증: `bin/contracts-check`, `go test ./services/worker/...`, `go test ./services/api/...`, `go test ./apps/cli/internal/operator` 통과.
- [x] CODE_REVIEW-*-G??.md의 구현 에이전트 소유 섹션을 실제 구현 내용과 검증 출력으로 채운다. 이 항목이 완료되기 전에는 구현이 완료된 것이 아니다.

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

> **[REVIEW AGENT ONLY]** 이 체크리스트는 코드리뷰 에이전트만 사용한다.

- [x] PASS/WARN/FAIL 판정을 append한다.
- [x] active files를 `.log`로 아카이브하고 PASS이면 `complete.log` 작성 후 task directory를 archive로 이동한다.
- [x] PASS이면 roadmap completion metadata를 보고한다.

## 계획 대비 변경 사항

- 계획은 `risk=clear|blocked`, `risk_rejected_count`, `risk_reason` 키만 명시했으나, 기존 paper output은 `risk=not_evaluated` placeholder를 emit하고 있었다. additive로 추가하는 대신 placeholder를 실제 derive 값(`clear`/`blocked`)으로 **대체**했다. 이유: worker가 항상 rejection summary를 채우므로 `not_evaluated`를 유지하면 의미 없는 third state가 남는다. 이에 따라 기존 테스트 단언과 expected fixture의 `not_evaluated`를 `clear`로 갱신했다.
- proto는 단일 `repeated PaperRiskRejection risk_rejections = 7` field만 additive로 추가했다. 별도 `risk_status` enum field는 두지 않았다 — clear vs blocked는 list 길이로 결정 가능하고, 이렇게 두면 worker가 단일 source(`Snapshot.Rejected`)만 매핑하면 되어 drift 가능성이 줄어든다.
- API(`services/api`)는 순수 worker pass-through라 코드 변경이 필요 없었다(계획 수정 파일 요약에도 API 파일은 없음). `go test ./services/api/...`로 회귀만 확인했다.
- 계획 요청대로 `TestRunGetPaperTradingStateOutputsRiskFields`(blocked)를 추가했고, 추가로 `TestRunStartPaperTradingClearRiskOmitsRejectionFields`(clear)를 두어 clear/blocked 양쪽 case를 모두 고정했다.

## 주요 설계 결정

- **risk summary는 broker 동작을 fake하지 않는다.** blocked는 typed error가 아니라 paper state의 `risk_rejections`(engine이 이미 추적하던 `Snapshot.Rejected`)로만 표현한다. 진짜 invalid request(account_id 누락 등)는 기존 typed error 경로를 그대로 유지한다.
- **rejection instrument_id는 `RejectedOrder.Instrument`에서 매핑한다.** engine은 risk/applyfill reject에는 `order.InstrumentID`를, empty-order-id strategy bug case에는 `bar.InstrumentID`를 `.Instrument`에 넣으므로 항상 non-empty 값이 보장된다.
- **clear run output 최소화.** `risk_rejected_count`/`risk_reason`은 blocked일 때만 emit하여 clear run의 evidence 라인이 안정적으로 유지된다(`risk=clear`만 출력).
- **codegen은 protoc 29.3로 수행.** 생성 header가 `protoc v5.29.3`(libprotoc 29.3)이며, 시스템 protoc 3.21.12로 생성하면 header drift로 `bin/contracts-check`/`bin/test`가 실패한다(선행 `01_loop_smoke` G07에서 동일 이슈 확인됨). `PATH="/tmp/protoc293/bin:$PATH" bin/contracts-gen`으로 Go/Dart 생성물을 재생성했다. 후속 리뷰/CI도 동일 protoc 버전을 사용해야 한다.

## 사용자 리뷰 요청

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

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

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

- protobuf changes must be additive and generated outputs must match.
- risk summary must distinguish clear vs blocked without faking broker behavior.
- API must stay thin and worker-owned runtime must remain worker-owned.

## 검증 결과

### PAPER_RISK-1 중간 검증
```text
$ PATH="/tmp/protoc293/bin:$PATH" bin/contracts-gen
(no output; Go + Dart regenerated)
$ PATH="/tmp/protoc293/bin:$PATH" bin/contracts-check
(exit=0; no drift)
```

### PAPER_RISK-1/2/3 focused 검증
```text
$ go test ./services/worker/internal/socket
ok  	git.toki-labs.com/toki/alt/services/worker/internal/socket
$ go test ./services/api/internal/socket
ok  	git.toki-labs.com/toki/alt/services/api/internal/socket
$ go test ./apps/cli/internal/operator
ok  	git.toki-labs.com/toki/alt/apps/cli/internal/operator	0.161s
```

worker 전체:
```text
$ go test ./services/worker/...
ok ... all worker packages (papertrading, socket 포함)
```

### 최종 검증
```text
$ PATH="/tmp/protoc293/bin:$PATH" bin/test
... (Go modules) ok ...
00:02 +60: All tests passed!   # Flutter
exit=0
```

---

> **[IMPLEMENTING AGENT — BEFORE SAVING] Have you filled in every implementation-owned section?**

## 코드리뷰 결과

- 종합 판정: PASS
- 차원별 평가:
  - correctness: Pass
  - completeness: Pass
  - test coverage: Pass
  - API contract: Pass
  - code quality: Pass
  - plan deviation: Pass
  - verification trust: Pass
- 발견된 문제: 없음
- 다음 단계: PASS 처리한다. active `CODE_REVIEW-cloud-G08.md`와 `PLAN-cloud-G08.md`를 `.log`로 아카이브하고, `complete.log` 작성 후 task directory를 archive로 이동한다.
