Resolve dcy-004 OQ-1~3 from trex-orbit reference

- OQ-1 asset_id: asset_id == portfolio_id — use the slot's portfolio_id
  directly as the ledger path param (orbit ledger.py / migration 003 /
  coordinator). No symbol mapping or asset lookup.
- OQ-2 durability: ledger outbox (work item) + retry loop, correlation_id
  {asset_id}:{order_id}:{execution_id} dedup, outcome vocabulary
  (completed/duplicate_completed/retryable_failure/conflict_blocked) —
  orbit LedgerWriter pattern; recording decoupled from slot transition.
- OQ-3 exec data: deterministic execution_id (KIS
  kis-daily-ccld:{ord_dt}:{brno}:{odno}:{tot_ccld_qty} / paper synth),
  fee via BrokerPort calculate_fee, tax default 0.

All dcy-004 open questions answered; R-2/R-4 resolved. Remaining gates
(scope-defer confirm, portfolio-hub contract data, Confirmed By) left for
confirm-discovery.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-04 19:44:00 +09:00
co-authored by Claude Opus 4.8
parent b37b16be93
commit 88f5af434b
@@ -78,9 +78,9 @@
## 기능 요구사항 ## 기능 요구사항
- FR-1: `LedgerPort.record_trade(asset, order_id, execution_id, side, qty, price, fee, tax, executed_at)` 추상화 + portfolio-hub 어댑터(`POST /v1/assets/{asset_id}/ledger/trades`) + 결정적 검증용 stub/in-memory 원장을 제공한다. - FR-1: `LedgerPort.record_trade(asset, order_id, execution_id, side, qty, price, fee, tax, executed_at)` 추상화 + portfolio-hub 어댑터(`POST /v1/assets/{asset_id}/ledger/trades`) + 결정적 검증용 stub/in-memory 원장을 제공한다.
- FR-2: 집행 루프가 매수 체결(`pending_buy→holding`)·매도 체결(`pending_sell→closed`) 시 해당 거래를 원장에 기록한다(슬롯 `portfolio_id`/symbol + 체결 데이터 사용). - FR-2: 집행 루프가 매수 체결(`pending_buy→holding`)·매도 체결(`pending_sell→closed`) 시 해당 거래를 원장에 기록한다. ledger path 의 `asset_id` 는 슬롯 `portfolio_id`(=asset_id, OQ-1 결정)를 사용하고 side/qty/price/체결데이터를 함께 보낸다.
- FR-3: 원장 기록은 `execution_id` 로 멱등하다 — 동일 체결 재기록 시 portfolio-hub 중복/409(LedgerConflict)를 안전 처리하고 중복 원장 0. - FR-3: 원장 기록은 correlation_id(`{asset_id}:{order_id}:{execution_id}`)로 멱등하다 — 동일 체결 재기록 시 로컬 dedup + portfolio-hub 중복/409(LedgerConflict)를 안전 처리하고 중복 원장 0. execution_id 는 결정적 합성(OQ-3).
- FR-4: portfolio-hub 일시 장애 시 원장 기록을 재시도/보류해 유실하지 않는다(슬롯 전이 일관성과 분리). - FR-4: 체결 시 원장 기록을 ledger outbox(work item)에 적재하고 재시도 루프가 portfolio-hub write 를 수행한다(orbit LedgerWriter 패턴). outcome = completed/duplicate_completed/retryable_failure/conflict_blocked. 슬롯 전이와 분리되어 일시 장애에도 유실 없이 결국 기록(conflict 는 운영자 해소).
- FR-5: baseline 문서(runtime-flows/interface-contract/project-structure/data-model)를 ledger 레이어에 맞게 갱신한다. - FR-5: baseline 문서(runtime-flows/interface-contract/project-structure/data-model)를 ledger 레이어에 맞게 갱신한다.
## 비기능 요구사항 ## 비기능 요구사항
@@ -105,13 +105,11 @@
## 리스크 ## 리스크
- R-1 (High): 원장 기록 유실/중복 — 체결은 됐는데 원장 미기록(장애) 또는 중복 기록 시 회계 부정확. - R-1 (High): 원장 기록 유실/중복 — 체결은 됐는데 원장 미기록(장애) 또는 중복 기록 시 회계 부정확.
- 완화 방안: execution_id 멱등(409/duplicate 안전 처리, FR-3) + 장애 재시도/보류(FR-4). stub 으로 결정적 테스트. - 완화 방안(OQ-2/OQ-3): correlation_id 멱등(409/duplicate 안전) + ledger outbox 재시도 루프(무유실) + conflict_blocked 운영자 해소. stub 으로 결정적 테스트.
- R-2 (Medium): asset_id 해석 — portfolio-hub 경로는 `asset_id`(canonical) 인데 슬롯은 symbol/portfolio_id 보유. 매핑 불명확. - R-2 (해소, OQ-1): asset_id 해석 — orbit 근거로 `asset_id == portfolio_id` 확정. 슬롯 `portfolio_id` 를 path 로 직접 사용(추가 매핑/조회 없음).
- 완화 방안: 매핑 규칙을 OQ-1 로 확정(portfolio_id/asset 매핑 또는 symbol→asset_id).
- R-3 (Medium): 외부 의존 결합 — portfolio-hub 장애가 집행 경로를 막을 위험. - R-3 (Medium): 외부 의존 결합 — portfolio-hub 장애가 집행 경로를 막을 위험.
- 완화 방안: 원장 기록을 슬롯 전이와 분리(전이는 우선, 기록은 재시도/보류). 어댑터 격리(NFR-1). - 완화 방안: 원장 기록을 슬롯 전이와 분리(전이는 우선, 기록은 재시도/보류). 어댑터 격리(NFR-1).
- R-4 (Medium): fee/tax/execution_id 출처 — 체결 데이터에 수수료/세금/체결ID가 항상 있지는 않음(paper vs KIS). - R-4 (해소, OQ-3): fee/tax/execution_id 출처 — execution_id 결정적 합성(KIS 규칙/paper synth), fee 는 BrokerPort 계산(orbit calculate_fee), tax 기본 0(정밀 모델 후속).
- 완화 방안: KIS 체결 조회/주문 응답에서 추출, 없으면 0/derived. OQ-3.
## 가정 ## 가정
아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. 아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다.
@@ -124,18 +122,17 @@
답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. 답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다.
- OQ-1: asset_id 해석 — portfolio-hub `{asset_id}` 를 슬롯의 무엇으로 채울지(`portfolio_id` == asset_id? symbol→asset_id 매핑? portfolio-hub get_assets 조회?). - OQ-1: asset_id 해석 — portfolio-hub `{asset_id}` 를 슬롯의 무엇으로 채울지(`portfolio_id` == asset_id? symbol→asset_id 매핑? portfolio-hub get_assets 조회?).
- 상태: Open - 상태: Answered (2026-06-04)
- 처리 방안: 운영자/portfolio-hub 계약 확인. 기본안 검토: 슬롯 `portfolio_id` 를 asset_id 로 직접 사용 또는 symbol 기반 매핑. - 결정: **`asset_id == portfolio_id`** — 슬롯의 `portfolio_id` 를 ledger path param 으로 직접 사용. symbol 매핑/get_assets 조회 불필요. 근거: trex-orbit `app/domain/ledger.py`("asset_id == portfolio_id for this system"), migration 003(Order Execution 이 `{portfolio_id}` path 로 write-back, 매수 커밋 시 슬롯에 캐시), `coordinator.py`(`asset_id = asset_id or portfolio_id`). trex-core 는 이미 buy intent 가 `portfolio_id` 를 공급해 슬롯에 캐시하므로 추가 조회 없이 그대로 사용.
- 종료 조건: asset_id 소스가 정해져 FR-1/FR-2 에 연결되면 Answered. - 반영 위치: `# 5` FR-1·FR-2, `# 6` R-2(해소). buy intent 의 `portfolio_id` 필수 여부는 구현 시 결정(없으면 기록 skip 또는 거부).
- OQ-2: 기록 내구성 방식 — fill 전이와 동일 경로 동기 기록(실패 시 재시도) vs 별도 ledger outbox(BAT-004 옵션 B 와 유사) 비동기. - OQ-2: 기록 내구성 방식 — fill 전이와 동일 경로 동기 기록 vs 별도 ledger outbox 비동기.
- 상태: Open - 상태: Answered (2026-06-04)
- 처리 방안: 운영자 확인. 기본안: 동기 기록 + 실패 시 재시도/보류 마킹(슬롯에 ledger_recorded 플래그 또는 보류 큐). 강한 내구성 필요 시 outbox. - 결정: **별도 ledger outbox(work item) + 재시도 루프** (orbit `LedgerWriter`/recovery_work_item 패턴 차용). 체결 시 ledger 기록을 outbox 에 적재(슬롯 전이와 분리 → 전이는 우선, 기록은 eventually-consistent), 루프가 portfolio-hub write 시도. correlation_id = `{asset_id}:{order_id}:{execution_id}` 로 dedup, outcome 어휘 = completed / duplicate_completed / retryable_failure(재시도) / conflict_blocked(409, 운영자 해소). 근거: orbit `app/domain/ledger.py` 가 동일 패턴으로 무유실+멱등을 보장.
- 종료 조건: 내구성 방식이 정해져 FR-4 수용 기준에 연결되면 Answered. - 반영 위치: `# 4`, `# 5` FR-3·FR-4, `# 6` R-1. (BAT-004 의 옵션 B outbox 와 유사 구조 — ledger 전용.)
- OQ-3: 체결 데이터(execution_id/fee/tax) 출처 — KIS 체결조회/주문응답에서 추출 가능 범위, paper 더블의 합성 값. - OQ-3: 체결 데이터(execution_id/fee/tax) 출처 — KIS 체결조회/주문응답에서 추출 가능 범위, paper 더블의 합성 값.
- 상태: Open - 상태: Answered (2026-06-04)
- 처리 방안: KIS inquire-daily-ccld 응답 필드 확인 + paper 더블이 결정적 execution_id/fee 제공. - 결정: **execution_id 는 결정적 합성** — KIS: `kis-daily-ccld:{ord_dt}:{ord_gno_brno}:{odno}:{tot_ccld_qty}`(orbit kis.py 규칙 차용), paper: 결정적 synth(예: `paper:{broker_order_id}:{filled_qty}`). **fee 는 BrokerPort fee 계산**(orbit `calculate_fee(side, amount)` 차용), **tax 는 기본 0**(정밀 세금 모델은 후속). FillStatus/브로커가 execution_id·fee 를 운반하도록 contract 확장.
- 종료 조건: 필드 출처/기본값이 정해지면 Answered. - 반영 위치: `# 5` FR-1·FR-3, `# 6` R-4. (BrokerPort 에 fee 계산 + FillStatus.execution_id 추가는 구현 세부.)
### 오픈 질문 처리 기준 ### 오픈 질문 처리 기준
- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. - `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다.
- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. - `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다.
@@ -172,7 +169,7 @@ REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지
- [ ] High 영향 리스크(R-1 원장 유실/중복) 처리 방침 확정 - [ ] High 영향 리스크(R-1 원장 유실/중복) 처리 방침 확정
- 기준: execution_id 멱등 + 재시도/보류로 수용 가능한지 운영자 확인. - 기준: execution_id 멱등 + 재시도/보류로 수용 가능한지 운영자 확인.
- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 - [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인
- 기준: OQ 목록에 `Open` 0개 또는 잔여 전부 `Deferred` (현재 OQ-1~OQ-3 Open) - 기준: OQ 목록에 `Open` 0개 또는 잔여 전부 `Deferred` (OQ-1~OQ-3 전부 Answered → `Open` 0개)
- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 - [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정
- 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다. - 기준: 운영자가 confirm-discovery 시점에 CONFIRMED_BY 를 지정한다.
@@ -189,12 +186,11 @@ REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지
# 10. 사용자 결정 필요 항목 요약 # 10. 사용자 결정 필요 항목 요약
사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. 사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다.
## DECIDE - ~~asset_id 소스~~ → 결정됨: `asset_id == portfolio_id`(슬롯 portfolio_id 직접 사용, orbit 근거) (OQ-1, 2026-06-04)
- {{DECIDE_ASSET_ID_SOURCE}} — portfolio-hub `{asset_id}` 소스: 슬롯 portfolio_id 직접 vs symbol→asset_id 매핑 vs get_assets 조회 (OQ-1) - ~~기록 내구성~~ → 결정됨: 별도 ledger outbox + 재시도 루프(orbit LedgerWriter 패턴), correlation_id dedup, outcome 어휘 (OQ-2, 2026-06-04)
- {{DECIDE_LEDGER_DURABILITY}} — 기록 내구성: 동기+재시도/보류 vs 별도 ledger outbox (OQ-2)
## CONFIRM ## CONFIRM
- {{CONFIRM_EXEC_DATA_SOURCE}} — 체결 데이터(execution_id/fee/tax) 출처(KIS 추출 + paper 합성) 확정 (OQ-3) - ~~체결 데이터 출처~~ → 결정됨: execution_id 결정적 합성(KIS 규칙/paper) + fee BrokerPort 계산 + tax 0 (OQ-3, 2026-06-04)
- {{CONFIRM_SCOPE_DEFER}} — 거래 외 트랜잭션·asset 동기화·reconciliation 을 후속으로 미루는 범위 경계 확정 - {{CONFIRM_SCOPE_DEFER}} — 거래 외 트랜잭션·asset 동기화·reconciliation 을 후속으로 미루는 범위 경계 확정
## DATA ## DATA