fix(paper): BAT-039 — paper order seq Redis 영속화 (재시작 멱등 키 충돌 방지, REQ-011/issue #4)

REQ-011 change-req(CHG-20260612-01)→구현→confirm-req-implemented(CHG-20260612-02)
전체 delivery. paper 한정 결정성 버그(프로덕션 무관).

- seq_store.py 신규: SeqStore 포트 + RedisSeqStore(원자 INCR, 재시작 단조·영속)
  + InMemorySeqStore(폴백/테스트). REQ-016 token_cache 패턴 재사용.
- paper.py: optional seq_store 주입. 미주입 시 기존 in-memory 동작 byte-for-byte 유지.
- config.py/daemon.py: TREX_PAPER_REDIS_URL → RedisSeqStore wiring
  (key=trex:broker:paper:{instance_key}:order-seq).
- 런타임 Redis 오류는 전파(silent in-memory 폴백 금지 — 버그 재현 방지).
- tests: 재시작 단조성/correlation_id 비충돌/in-memory 폴백/fake redis INCR (5).
  434 passed (+5), ruff clean.

REQ-011 Approved→Implemented, BAT-039 release-candidate. issue #4 close.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-12 18:16:11 +09:00
co-authored by Claude Opus 4.8
parent 0e5ce32f6b
commit 9871008bb6
13 changed files with 470 additions and 6 deletions
@@ -10,20 +10,23 @@
## Requirement
- `BrokerPort`(application 포트)에 `submit_order(side, symbol, qty, ...) -> broker_order_id`, `cancel_order(broker_order_id)`, `poll_fill(broker_order_id) -> FillStatus(accepted/partially_filled/filled/rejected/cancelled, avg_price, filled_qty)` 를 정의한다. paper/stub 어댑터는 동기 단축 없이 submit→poll 비동기 계약을 따르되 체결 시점·부분체결·거부를 프로그래밍 가능한 결정적 더블로 제공한다. 어댑터는 adapters 레이어에 두고 도메인은 의존하지 않는다.
- paper 어댑터의 order seq 발번은 optional seq store(주입 가능) 위에서 동작한다. seq store 가 주입되면 order seq 는 데몬 재시작 간에도 단조 증가해야 하며(Redis 영속, REQ-016 `RedisTokenCache` 패턴 재사용·best-effort), 미주입 시 기존 in-memory 카운터로 폴백한다. 이는 재시작 후 결정적 `execution_id` 재생성으로 인한 원장 `correlation_id` 충돌(매도 원장 누락)을 방지하기 위한 paper 한정 결정성 보강이다(issue #4).
## Acceptance Criteria
1. Given `BrokerPort` 정의, When paper 어댑터로 `submit_order`, Then `accepted` + broker_order_id 를 반환하고 즉시 `filled` 로 단축하지 않는다(다음 `poll_fill` 에서 체결).
2. Given paper 더블의 fill 시나리오 설정(즉시/N틱 후/부분/거부), When `poll_fill` 반복, Then 설정대로 결정적으로 상태가 진행된다.
3. Given 코드, When import 스윕, Then `trex_core.domain.slot` 은 broker/네트워크 의존이 0이고 BrokerPort 구현은 adapters 레이어에만 있다. (NFR-1/NFR-2)
4. Given paper 어댑터에 seq store 가 주입된 상태, When 데몬 재시작(seq store 영속)을 모사해 두 번째 인스턴스에서 `submit_order`, Then order seq 는 첫 인스턴스 이후로 단조 증가하고, 같은 자산·같은 수량의 매도가 직전 run 매수와 `correlation_id` 가 충돌하지 않는다. seq store 미주입 시 기존 in-memory `PAPER-000001` 발번으로 폴백하며 AC-1~3 비동기 계약은 불변이다. (issue #4)
## Impacted Area
- Docs: docs/interface-contract.md(브로커 경계) — REQ-015 에서 정합
- Modules: trex_core/application(BrokerPort), trex_core/adapters/broker(paper/stub)
- Tests: paper 더블 fill 시나리오 테스트
- Modules: trex_core/application(BrokerPort), trex_core/adapters/broker(paper/stub, `seq_store.py` 신규), trex_core/runtime(config `PaperBrokerConfig.redis_url`, daemon `_build_broker` wiring)
- Tests: paper 더블 fill 시나리오 테스트, paper seq store 재시작 단조성 + correlation_id 비충돌 + in-memory 폴백 테스트
## Notes
- 불변식 B 의 단일 경계. KIS 어댑터는 REQ-012, 집행 루프는 REQ-013.
- 출처: dcy-003 FR-1(포트+paper), NFR-1/NFR-2 흡수.
- (issue #4/CHG-20260612-01) paper seq Redis 영속화는 paper 한정 테스트 하네스 결정성 보강이며 프로덕션(KIS `odno` 고유) 버그가 아니다. 원장 `correlation_id` 에 side 를 추가하는 대안 대신, 발번 단조성 보강(영향 최소·계약 불변)을 선택했다. Redis 인프라는 REQ-016 token_cache 패턴을 재사용한다.
## Change Log
@@ -147,3 +150,96 @@
#### Validation Plan
- BAT-004 verification evidence 재참조로 충분.
### CHG-20260612-01
- Date: 2026-06-12
- Author: Justin Ko
#### Change Summary
- (change-req, issue #4) paper 브로커 order seq 를 **재시작 간 단조 증가**하도록 영속화한다. 현재 `_seq` in-memory 카운터는 데몬 재시작 시 0 으로 리셋되어 `PAPER-000001` 부터 재발번 → 결정적 `execution_id`(`paper:{order_id}:{filled_qty}`) 재생성 → 원장 멱등 키 `correlation_id = {asset_id}:{order_id}:{execution_id}`(side 미포함)가 직전 run 의 매수와 충돌 → `ON CONFLICT DO NOTHING` dedup 으로 **정상 매도 원장 행이 조용히 누락**. paper 어댑터에 optional seq store(Redis-backed, REQ-016 `RedisTokenCache` 패턴 재사용)를 주입해 해소하고, Redis 미구성 시 기존 in-memory 동작으로 폴백. 신규 AC-4 가 추가 구현·검증을 필요로 하므로 **Implemented → Approved 되돌림**.
#### Intent
- paper 모드 테스트 하네스의 재시작 후 멱등 키 충돌(매도 원장 누락)을 제거한다. 실 KIS 는 broker order_id(`odno`)가 주문마다 고유해 충돌이 없으므로 **프로덕션 버그가 아닌 paper 한정 결정성 문제**다. 기존 BrokerPort/paper 비동기 계약(AC-1~3)은 불변, seq 발번 영속화만 추가.
#### Acceptance Criteria Delta
- Added:
- AC-4: paper 브로커에 seq store 가 주입되면 order seq 는 재시작 후에도 단조 증가하고, 같은 자산·같은 수량의 매도가 직전 run 의 매수와 `correlation_id` 가 충돌하지 않는다. seq store 미주입(Redis 미구성) 시 기존 in-memory `_seq` 동작으로 폴백하며 비동기 fill 계약(AC-1~3)은 불변이다.
- Updated:
- 없음
- Removed:
- 없음
#### Impacted Area Delta
- Docs:
- docs/srs/index.md (Register Implemented → Approved)
- Modules:
- `trex_core/adapters/broker/paper.py`(optional seq store 주입 + `submit_order` seq 발번 경로)
- `trex_core/adapters/broker/seq_store.py`(신규: `SeqStore` 포트 + `RedisSeqStore`(INCR 기반, best-effort) + `InMemorySeqStore` 폴백, token_cache 패턴 정합)
- `trex_core/runtime/config.py`(`PaperBrokerConfig.redis_url` 추가)
- `trex_core/runtime/daemon.py`(`_build_broker` paper 경로 seq store wiring)
- Tests:
- 재시작 시뮬레이션(seq store 공유) 후 매도 correlation_id 비충돌 + 폴백 동작 단위 테스트
#### Delivery Trace Delta
- Batches:
- BAT-039 (예정, issue #4)
- Releases:
- none
#### Revalidation Impact
- Reverification Needed: yes (BAT-039 에서 신규 AC-4 + AC-1~3 회귀)
- Existing Implementation Invalidated: no (기존 비동기 계약/도메인 순수성 불변, 발번 경로에 optional 영속화만 추가). 단 신규 AC-4 미구현으로 Implemented 유지 불가 → Approved.
- Status Recommendation: Implemented → **Approved** (구현·검증 후 confirm-req-implemented 로 복귀)
- Follow-up Action: confirm-req → draft-batch(BAT-039) → 구현·검증 → confirm-req-implemented → issue #4 close
#### Validation Plan
- BAT-039: (a) 같은 seq store 를 공유한 두 PaperBroker 인스턴스(재시작 모사)에서 두 번째 인스턴스의 첫 order_id 가 첫 인스턴스 이후로 단조 증가, (b) 매수 후 재시작 매도 시 correlation_id 비충돌(원장 dedup 미발생), (c) seq store 미주입 시 기존 `PAPER-000001` 부터 in-memory 동작 폴백, (d) AC-1~3 기존 paper 테스트 회귀 없음. `pytest` 전체 + `ruff check` clean.
### CHG-20260612-02
- Date: 2026-06-12
- Author: Justin Ko
#### Change Summary
- Status 변경: Approved → **Implemented** (confirm-req-implemented, 근거 batch BAT-039 release-candidate).
#### Intent
- BAT-039 verification.md 의 acceptance mapping + evidence(434 passed / seq store 재시작 단조·correlation_id 비충돌·in-memory 폴백 / fake redis INCR 단조)로 AC-1~4 충족 → 구현 완료 동기화.
#### Acceptance Criteria Delta
- Added/Updated/Removed: 없음 (상태 전이만)
#### Impacted Area Delta
- Docs/Modules/Tests: 없음 (상태 전이만)
#### Delivery Trace Delta
- Batches:
- BAT-039 (release-candidate)
- Releases:
- none (release 편성은 별도 진행)
#### Revalidation Impact
- Reverification Needed: no
- Existing Implementation Invalidated: no
- Status Recommendation: Implemented
- Follow-up Action: draft-release 로 release 편성(BAT-039 포함) + issue #4 close
#### Validation Plan
- BAT-039 verification evidence 재참조로 충분.
- AC-4: `test_shared_seq_store_is_monotonic_across_restart`, `test_post_restart_sell_does_not_collide_with_prior_buy`, `test_in_memory_fallback_starts_at_one`, `test_redis_seq_store_incr_is_monotonic_and_shared`.
- AC-1~3: 전체 회귀 434 passed(기존 paper 더블 시나리오 + 도메인 순수성 회귀 없음).
+2 -2
View File
@@ -159,8 +159,8 @@
## Recent Change Log Summary
- 2026-06-12 :: REQ-011 Approved → **Implemented** (confirm-req-implemented, BAT-039): paper order seq Redis 영속화 구현·검증 완료. 434 passed (seq store 5 신규 포함), 재시작 단조·correlation_id 비충돌·in-memory 폴백 green. Blocking Issues 없음. issue #4 close.
- 2026-06-12 :: REQ-011 change-req (CHG-20260612-01, issue #4): paper 브로커 order seq Redis 영속화 — 재시작 후 `correlation_id` 충돌(매도 원장 누락) 방지. optional seq store(RedisSeqStore INCR + in-memory 폴백) 주입. AC-4 신규. 기존 비동기 계약 불변이나 신규 AC 미구현 → Implemented → **Approved** 되돌림. BAT-039 구현·검증 후 복귀. paper 한정(프로덕션 버그 아님).
- 2026-06-12 :: REQ-018 Approved → **Implemented** (confirm-req-implemented, BAT-038): 매도 증권거래세 정책 계산 + `tax=0.0` 제거 구현·검증 완료. 429 passed (fee·tax 정책 6 신규 포함), issue #24 1,376.9원 회귀 green, `tax=0.0` 하드코딩 grep 0건, runtime-flows 동기화. Blocking Issues 없음.
- 2026-06-12 :: REQ-018 change-req (CHG-20260612-01, dcy-026/issue #24): 매도 증권거래세 누락 정정 — `FillStatus.tax` 매도 정책 세율(0.2%) 계산, `tax=0.0` 하드코딩(paper/kis) 제거, 단일 정책 지점 + env(`TREX_KIS_FEE_BPS`/`TREX_KRX_TAX_SELL_BPS`). AC-3 갱신·AC-6~9 신규. `tax=0` evidence 무효 → Implemented → **Approved** 되돌림. BAT-038 구현·검증 후 복귀.
- 2026-06-12 :: REQ-073 Approved → **Implemented** (confirm-req-implemented, BAT-037): `available_slots: int|null` capacity hint 추가. AC-3 반전·AC-6 신규. 16 tests passed (AC-6 3개 포함), 423 전체 회귀 없음. Blocking Issues 없음.
- 2026-06-12 :: REQ-073 change-req (CHG-20260612-01): 응답 payload 에 `available_slots: int|null` 추가. AC-3 반전(available_slots 포함), AC-6 신규(hint 값 규칙). BAT-035 AC-3 evidence 무효 → Implemented → **Approved** 되돌림. 차기 batch 구현·검증 후 복귀.
- 2026-06-11 :: REQ-058 change-req (CHG-20260611-01): `reconcile_universe` 슬롯 생성 시 `symbol` 누락 수정. 시그니처 `set[str]` → `dict[str, str]`(portfolio_id→symbol), INSERT에 symbol 포함, `symbol IS NULL` 기존 슬롯 backfill 추가. AC-5 신규 추가. 기존 AC 1~4 evidence 유효 → **Implemented 유지**.