# BAT-039 Design - Status: confirmed - Batch: BAT-039 ## Architecture Summary - paper 어댑터의 order seq 발번을 optional `SeqStore` 위로 옮긴다. 주입되면 영속 단조 카운터(Redis `INCR`)를, 미주입이면 기존 in-memory `_seq` 를 사용한다(byte-for-byte 기존 동작 유지). - `SeqStore` 는 adapters/broker 레이어의 작은 포트로, REQ-016 `token_cache.py`(Redis best-effort)와 같은 패턴·같은 레이어에 둔다. 도메인은 의존하지 않는다(NFR-1 불변). - 데몬은 `TREX_PAPER_REDIS_URL` 이 설정되면 `RedisSeqStore` 를 만들어 paper 브로커에 주입한다. ## Changed Areas - Modules: - `trex_core/adapters/broker/seq_store.py`(신규): - `SeqStore` Protocol — `next_seq() -> int` - `RedisSeqStore(redis_url, *, key, redis_module=None)` — `INCR key` 로 원자·영속 단조 정수 반환. `from_url(socket_connect_timeout=2)`(token_cache 정합). - `InMemorySeqStore` — thread-safe 카운터(테스트/명시 폴백) - `trex_core/adapters/broker/paper.py` — `__init__(..., seq_store: Optional[SeqStore] = None)`; `submit_order` 에서 `seq = self._seq_store.next_seq()` (주입 시) / `self._seq += 1` (미주입) - `trex_core/runtime/config.py` — `PaperBrokerConfig.redis_url: str = ""` + `from_config` `TREX_PAPER_REDIS_URL` - `trex_core/runtime/daemon.py` — `_build_broker`: paper + `config.paper.redis_url` → `RedisSeqStore(url, key=f"trex:broker:paper:{config.instance_key}:order-seq")` 주입 - Tests: - `tests/adapters/broker/test_paper_seq_store.py`(신규) - Docs: 없음(runtime-flows 흐름 불변 — 발번 영속화는 내부 결정성, 외부 흐름 동일) ## Key Decisions 1. **`SeqStore.next_seq() -> int` 단일 메서드**: paper 가 필요로 하는 것은 단조 정수뿐. order_id 포맷(`PAPER-{seq:06d}`)은 paper 책임으로 유지. 포트는 최소. 2. **Redis `INCR`(원자·영속)**: GET/SET race 없이 단일 명령으로 단조 보장. 키 부재 시 0→1 로 시작해 첫 order_id 가 `PAPER-000001`(기존과 동일 시작값). 3. **키 스코프 = instance_key**: `trex:broker:paper:{instance_key}:order-seq`. 인스턴스별 독립 카운터(다중 인스턴스 교차 방지). 단일 인스턴스(default)는 단일 키. 4. **폴백 의미 = 설정 부재만**: `TREX_PAPER_REDIS_URL` 미설정 → seq store 미주입 → in-memory `_seq`(기존 동작). **런타임 Redis 오류는 전파**(silent in-memory 폴백 금지 — 그 폴백이 곧 버그 재현). paper 는 테스트 하네스이므로 오류 전파가 안전. (R-1) 5. **token_cache best-effort 와의 차이**: 토큰 캐시는 miss/error → 재발급(무해)이라 best-effort 폴백. seq 는 error → 폴백이 곧 멱등 키 충돌이므로 best-effort 폴백을 적용하지 않는다. ## Edge Cases - seq store 미주입: 기존 `self._seq += 1`, `PAPER-000001`부터. AC-1~3 불변. - INCR 결과 > 999999: `f"{seq:06d}"` 는 최소 패딩 → `PAPER-1000000` 정상 표기, 고유성 유지. - 재시작 모사(같은 key 공유 RedisSeqStore/InMemorySeqStore 인스턴스): 두 번째 PaperBroker 가 첫 인스턴스 이후 seq 로 시작 → 매도 order_id ≠ 직전 매수 → correlation_id 비충돌. - 동시 submit(스레드): `INCR` 원자 / `InMemorySeqStore` 는 lock 으로 보호. ## Architecture Impact - none (runtime-flows 흐름 불변. 발번 경로 내부 결정성 보강만, 외부 계약/흐름 동일) ## Reference Doc Update Plan - project-structure.md: none (adapters/broker 에 파일 1개 추가 — 구조 패턴 불변, token_cache 와 동급) - runtime-flows.md: none