feat(intents): dcy-011 buy intent portfolio_id 필수 계약 + ledger skip 가시화 (BAT-015)
dcy-011 전 주기 delivery. issue #6(buy 식별자 정정) + issue #11(ledger silent skip) 흡수. 거버넌스: 기존 Implemented REQ는 change-req, 신규 기능만 신규 REQ(OQ-3). - REQ-007 (change-req): buy admission portfolio_id 필수화 + SnapshotStore symbol resolve + unknown_portfolio 거부 (hard cutover, OQ-1/OQ-2) - REQ-021 (change-req): buy 메시지 payload symbol 제거 - REQ-018 (change-req): _record_ledger skip 가시화 (WARN + ledger_skipped 카운터) - REQ-055 (신규): closed↔ledger_outbox anti-join 운영 점검 find_ledger_gap_slots 코드: intents.py(BuyIntent portfolio_id 필수 + resolve_symbol 주입 + _admit_buy reject 분기), messaging_handlers.py, runtime/daemon.py(_resolve_symbol 배선), execution.py(skip WARN 로그), adapters/persistence/ops_checks.py(신규 anti-join). 검증: 단위 282 passed/32 skipped, 인프라 게이트(PG+RabbitMQ) 314 passed, ruff clean. BAT-015(standard) release-candidate. srs/index Next ID → REQ-056. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# BAT-015 Design
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-015
|
||||
- Profile: standard
|
||||
|
||||
## 설계 목표
|
||||
- buy intent 식별자 계약을 정식 식별자(portfolio_id=asset_id) 기준으로 hard cutover 하면서, symbol 입력 제거에 따른 다수 호출부 회귀를 통제한다.
|
||||
- issue #11 의 ledger silent skip 을 무성 → 가시화로 전환하되 전이 동작과 outbox 스키마는 불변으로 유지한다(OQ-4).
|
||||
|
||||
## 1. buy admission 계약 (REQ-007)
|
||||
|
||||
### BuyIntent 구조
|
||||
- `portfolio_id: str` 를 필수 위치 인자로 승격(intent_id, trading_date 다음). `symbol: Optional[str] = None` 으로 강등 — 클라이언트 입력이 아니라 admission 이 universe 에서 resolve 해 슬롯에 싣는 값.
|
||||
- 나머지 필드(qty/allocated_capital/entry_price/strategy_id/thresholds/extra)는 불변.
|
||||
|
||||
### symbol resolve 주입 경계
|
||||
- 결정: `IntentService(resolve_symbol=callable|None)` 콜백 주입(SnapshotStore 직접 의존 대신).
|
||||
- 근거: (a) application 계층이 portfolio.SnapshotStore 에 직접 결합되지 않아 테스트 더블 주입이 쉽다. (b) `resolve_symbol=None` 폴백을 두면 symbol resolve 가 필요 없는 기존 단위 테스트는 portfolio_id 추가만으로 무회귀(R-1 통제). 프로덕션 daemon 은 항상 `_resolve_symbol`(SnapshotStore.get) 주입.
|
||||
- `_resolve_symbol(portfolio_id) -> Optional[str]`. 부재 시 falsy.
|
||||
|
||||
### `_admit_buy` reject 분기 (FR-1/FR-2/FR-3)
|
||||
1. `not intent.portfolio_id` → `reject(portfolio_id_required)` (FR-1, 근본 원인 제거).
|
||||
2. resolver 주입됨: `symbol = resolve_symbol(portfolio_id)`; falsy 면 `reject(unknown_portfolio)` (FR-3, 단일 reject 정책 OQ-1 — symbol fallback 없음).
|
||||
3. resolver 미주입 + `intent.symbol` 도 falsy → `reject(unknown_portfolio)` (호출 대상 미상).
|
||||
4. resolve 성공 시 기존 idle-slot 배정·`transit_slot(IDLE→PENDING_BUY, symbol=symbol, portfolio_id=...)` 흐름 유지. extra 에도 resolved symbol 기록.
|
||||
|
||||
## 2. buy 메시지 payload 계약 (REQ-021)
|
||||
- buy 핸들러는 payload 에서 `symbol` 을 읽지 않는다. `portfolio_id = _opt_str(body.get("portfolio_id")) or ""` 로 매핑(빈 문자열 → admission 의 portfolio_id_required 로 일관 거부).
|
||||
- symbol 은 핸들러에서 BuyIntent 에 싣지 않음(서버 resolve 가 admission 책임).
|
||||
|
||||
## 3. daemon 배선 (REQ-007)
|
||||
- `runtime/daemon.py::_resolve_symbol(self, portfolio_id)` → `asset.symbol if (asset := self.snapshot_store.get(portfolio_id)) else None`.
|
||||
- `IntentService(..., resolve_symbol=self._resolve_symbol)` 주입.
|
||||
|
||||
## 4. ledger skip 가시화 (REQ-018, issue #11)
|
||||
- `_record_ledger` 두 조기 반환 경로 모두에서 `r.ledger_skipped += 1` 후 `_log.warning(...)`:
|
||||
- portfolio_id 누락: `reason=missing_portfolio_id` (slot_id/symbol/side).
|
||||
- execution_id 누락: `reason=missing_execution_id` (slot_id/portfolio_id/side, 브로커 계약 회귀 방어 케이스).
|
||||
- 슬롯 전이는 그대로(관측만 추가, OQ-4). outbox 마커 행/스키마 변경 없음.
|
||||
- `execution.py` 에 `import logging` + `_log = logging.getLogger(__name__)` 추가.
|
||||
|
||||
## 5. 운영 점검 쿼리 (REQ-055, FR-5)
|
||||
- `adapters/persistence/ops_checks.py::find_ledger_gap_slots(dsn, *, trading_date=None, psycopg_module=None)` — 읽기 전용.
|
||||
- anti-join SQL: `daily_slot s WHERE s.status='closed' AND (td IS NULL OR trading_date=td) AND (portfolio_id IS NULL OR NOT EXISTS(SELECT 1 FROM ledger_outbox o WHERE o.asset_id=s.portfolio_id))`.
|
||||
- 반환: `slot_id/trading_date/symbol/portfolio_id/broker_order_id` dict 리스트. `daily_slot` durable → outbox 마커 없이 영구 탐지(OQ-4 위임).
|
||||
- 스키마 변경 없음. 알람/주기 실행 배선은 운영 절차(Out of Scope).
|
||||
|
||||
## 책임 분리
|
||||
- 런타임 관측(REQ-018, WARN+카운터): "지금 skip 났다"를 즉시 노출.
|
||||
- 사후 탐지(REQ-055, anti-join): durable 슬롯 기준으로 누락을 영구·집계 탐지.
|
||||
- 둘 다 outbox 마커 행 없이 성립 → outbox 전송 큐 의미 오염 회피(OQ-4 근거).
|
||||
|
||||
## 하위호환 / cutover (NFR-3)
|
||||
- 호환 창구 없음(OQ-2). symbol 기반 호출은 portfolio_id 누락 시 명확히 reject.
|
||||
- 호출부 전환 전략:
|
||||
- 비-핸들러 intent/동시성/repository 테스트: BuyIntent 에 portfolio_id 추가(resolver None → intent.symbol 폴백).
|
||||
- 핸들러/RabbitMQ e2e 테스트: resolve_symbol 람다 + payload portfolio_id 동시 전환.
|
||||
- ledger skip 테스트: admission 우회(`transit_slot(portfolio_id=None)`)로 skip 전제 재구성 + caplog 검증.
|
||||
|
||||
## 문서 (FR-6)
|
||||
- `docs/interface-contract.md`: buy payload portfolio_id 필수 + symbol 제거/서버 resolve.
|
||||
- `docs/runtime-flows.md`: buy admission step 에 portfolio_id 필수 + symbol resolve + unknown_portfolio + ledger skip 가시화 단락.
|
||||
@@ -0,0 +1,40 @@
|
||||
# BAT-015 Implementation
|
||||
|
||||
- Status: completed
|
||||
- Batch: BAT-015
|
||||
|
||||
## 구현 요약
|
||||
- dcy-011 buy intent 계약 hard cutover + ledger skip 가시화 + 운영 점검 쿼리를 단일 standard batch 로 구현. issue #6/#11 흡수.
|
||||
|
||||
## 변경 내역
|
||||
|
||||
### REQ-007 — buy admission portfolio_id 필수 + symbol resolve
|
||||
- `trex_core/application/intents.py`:
|
||||
- `BuyIntent`: `portfolio_id: str` 를 required 위치 인자로 승격, `symbol: Optional[str] = None` 로 강등(서버 resolve 결과를 담는 필드).
|
||||
- `IntentService.__init__(..., resolve_symbol=None)` — `callable(portfolio_id) -> Optional[str]` 주입점.
|
||||
- `_admit_buy`: ① `not portfolio_id` → `reject(portfolio_id_required)`; ② resolver 주입 시 `symbol = resolve_symbol(portfolio_id)`, falsy 면 `reject(unknown_portfolio)`; ③ resolver 미주입 + symbol falsy → `reject(unknown_portfolio)`; 이후 `transit_slot(symbol=symbol, portfolio_id=..., extra={..., "symbol": symbol})`.
|
||||
- `trex_core/runtime/daemon.py`: `_resolve_symbol(self, portfolio_id)` → `asset.symbol if (asset := self.snapshot_store.get(portfolio_id)) else None`; `IntentService(..., resolve_symbol=self._resolve_symbol)` 배선.
|
||||
|
||||
### REQ-021 — buy 메시지 payload symbol 제거
|
||||
- `trex_core/application/messaging_handlers.py`: buy 핸들러가 `symbol` 을 더 이상 읽지 않음; `portfolio_id=_opt_str(body.get("portfolio_id")) or ""` 매핑.
|
||||
|
||||
### REQ-018 — ledger skip 가시화 (issue #11)
|
||||
- `trex_core/application/execution.py`: `import logging` + `_log = logging.getLogger(__name__)`; `_record_ledger` 두 skip 경로(portfolio_id 누락 `reason=missing_portfolio_id`, execution_id 누락 `reason=missing_execution_id`)에서 `r.ledger_skipped += 1` + `_log.warning(...)`. 슬롯 전이 동작 불변, outbox 스키마 무변경(OQ-4).
|
||||
|
||||
### REQ-055 — closed↔outbox anti-join 운영 점검 (신규)
|
||||
- `trex_core/adapters/persistence/ops_checks.py`(신규): `find_ledger_gap_slots(dsn, *, trading_date=None, psycopg_module=None)` 읽기 전용. `status='closed'` 슬롯 중 `portfolio_id IS NULL` 또는 대응 `ledger_outbox` 행 부재(anti-join) 식별 → `slot_id/trading_date/symbol/portfolio_id/broker_order_id` dict 리스트. 스키마 변경 없음.
|
||||
|
||||
### FR-6 — 문서 정합
|
||||
- `docs/interface-contract.md`: buy payload `portfolio_id` 필수 + `symbol` 제거(서버 resolve). Last Updated → BAT-015.
|
||||
- `docs/runtime-flows.md`: buy admission step 에 portfolio_id 필수 + symbol resolve + unknown_portfolio + ledger skip 가시화 단락. Last Updated → BAT-015.
|
||||
|
||||
## 테스트 마이그레이션 (hard cutover, NFR-3)
|
||||
- 비-핸들러(resolver None → intent.symbol 폴백, portfolio_id 추가만): `test_execution.py`, `test_intent_concurrency.py`, `test_messaging_snapshot_consumer.py`, `test_postgresql_repository.py`.
|
||||
- 핸들러/e2e(resolver 람다 + payload portfolio_id): `test_messaging_intent_consumer.py`(+ 신규 3: without_portfolio_id_rejected / unknown_portfolio_rejected / symbol_resolved_from_universe), `test_rabbitmq_intent_e2e.py`.
|
||||
- ledger skip(admission 우회 + caplog): `test_execution_ledger.py`(skip 테스트 재작성 + 신규 `test_ledger_skip_emits_warning`).
|
||||
- 신규 PG 게이트: `tests/adapters/persistence/test_ops_checks.py`(gap 탐지 / 정상 0건).
|
||||
|
||||
## 검증 실행
|
||||
- 단위: `TREX_BROKER=paper .venv/bin/python -m pytest -q` → `282 passed / 32 skipped`(`::test_sandbox_token_and_submit` 는 LIVE `.env`(sandbox URL 아님)로 deselect — 본 batch 무관).
|
||||
- 인프라 게이트: `TREX_POSTGRESQL_TEST_DSN=...(trex_core_test) TREX_RABBITMQ_TEST_URL=... pytest -q` → `314 passed`(PG anti-join 점검 + buy intent RabbitMQ e2e + 마이그레이션 round-trip 포함).
|
||||
- `ruff check trex_core tests` → All checks passed.
|
||||
@@ -0,0 +1,39 @@
|
||||
# BAT-015: buy intent portfolio_id 필수 계약 + symbol 서버 resolve + ledger skip 가시화 + 누락 점검 쿼리
|
||||
|
||||
- Status: release-candidate
|
||||
- Profile: standard
|
||||
- Source Discovery: dcy-011
|
||||
- Included REQ: REQ-007 (CHG-20260608-01), REQ-018 (CHG-20260608-01), REQ-021 (CHG-20260608-01), REQ-055
|
||||
|
||||
## Summary
|
||||
- dcy-011(buy intent — portfolio_id 필수 / symbol 서버 resolve + ledger silent skip 가시화)을 단일 standard batch 로 delivery 한다. issue #6(buy 식별자 정정)과 issue #11(portfolio_id 없는 체결의 ledger silent skip)을 한 주기로 흡수한다.
|
||||
- buy intent 계약을 hard cutover: `portfolio_id` 필수화 + admission 시 `SnapshotStore` universe 에서 `symbol` resolve + universe 부재 시 `unknown_portfolio` 거부(REQ-007), buy 메시지 payload 에서 `symbol` 제거(REQ-021), `_record_ledger` skip 가시화(WARN 로그 + `ExecutionResult.ledger_skipped` 카운터, REQ-018), closed↔`ledger_outbox` anti-join 운영 점검 `find_ledger_gap_slots` 신규(REQ-055).
|
||||
- 거버넌스: dcy-011 OQ-3 결정에 따라 기존 Implemented REQ(007/018/021)는 `change-req`, 기존 REQ 가 다루지 않는 신규 기능(FR-5 운영 점검 쿼리)만 신규 REQ-055.
|
||||
|
||||
## Scope
|
||||
- In Scope:
|
||||
- `BuyIntent` portfolio_id 필수화 + symbol Optional(서버 resolve), `IntentService(resolve_symbol=...)` 주입, `_admit_buy` portfolio_id_required/unknown_portfolio reject + symbol resolve (REQ-007)
|
||||
- buy 메시지 핸들러 payload 에서 symbol 제거·portfolio_id 매핑 (REQ-021)
|
||||
- `runtime/daemon.py` `_resolve_symbol`(SnapshotStore.get) 배선 (REQ-007)
|
||||
- `_record_ledger` 두 skip 경로(portfolio_id/execution_id 누락) WARN 로그 + 카운터 가시화 (REQ-018)
|
||||
- `adapters/persistence/ops_checks.py::find_ledger_gap_slots` 읽기 전용 anti-join 점검 (REQ-055)
|
||||
- `docs/interface-contract.md` + `docs/runtime-flows.md` 정합 (FR-6, REQ-007/021/018 본문에 흡수)
|
||||
- Out of Scope:
|
||||
- sell intent 계약 변경(이미 portfolio_id/slot 기반)
|
||||
- universe refresh 주기/스케줄 재설계
|
||||
- symbol-only 레거시 호환 창구(OQ-2 hard cutover)
|
||||
- `ledger_outbox` skip 마커 행 도입(OQ-4 — 로그+메트릭만, 영구 탐지는 REQ-055 anti-join 위임)
|
||||
- 과거 누락분 데이터 복구(별도 운영 이슈)
|
||||
- skip 점검 쿼리의 알람/주기 실행 배선(쿼리/함수가 산출물; 운영 배포 절차)
|
||||
|
||||
## Documents
|
||||
- Planning: [planning.md](./planning.md)
|
||||
- Design: [design.md](./design.md)
|
||||
- Implementation: [implementation.md](./implementation.md)
|
||||
- Verification: [verification.md](./verification.md)
|
||||
|
||||
## Notes
|
||||
- Source discovery 단일(dcy-011). OQ-1~OQ-5 모두 Answered.
|
||||
- hard cutover(NFR-3): symbol 기반 호출부(테스트 다수) 동시 전환. `IntentService(resolve_symbol=None)` 는 `intent.symbol` 로 폴백 → 비-핸들러 테스트 churn 최소화, 프로덕션 daemon 은 항상 SnapshotStore resolve.
|
||||
- ledger skip 가시화(REQ-018)는 전이 동작 불변, 관측만 추가(OQ-4).
|
||||
- REQ-055 anti-join 은 `daily_slot` durable 특성에 의존(outbox 마커 없이 영구 탐지).
|
||||
@@ -0,0 +1,53 @@
|
||||
# BAT-015 Planning
|
||||
|
||||
- Status: confirmed
|
||||
- Batch: BAT-015
|
||||
- Profile: standard
|
||||
|
||||
## Included REQ
|
||||
- REQ-007 (Interface, change-req CHG-20260608-01): buy admission 계약 — `portfolio_id` 필수화, admission 시 `SnapshotStore` 에서 symbol resolve, universe 부재 시 `unknown_portfolio` 거부. symbol-필수/portfolio_id-optional 폐기(hard cutover).
|
||||
- REQ-021 (Integration, change-req CHG-20260608-01): buy 메시지 payload 계약에서 `symbol` 제거, `portfolio_id` 필수.
|
||||
- REQ-018 (Integration, change-req CHG-20260608-01): `_record_ledger` ledger skip 가시화(WARN 로그 + `ledger_skipped` 카운터, 두 skip 경로).
|
||||
- REQ-055 (Exception, 신규): closed↔`ledger_outbox` anti-join 운영 점검 `find_ledger_gap_slots`(읽기 전용).
|
||||
|
||||
## Delivery Plan
|
||||
1. REQ-007: `BuyIntent`(portfolio_id 필수 + symbol Optional 서버 resolve), `IntentService(resolve_symbol=...)`, `_admit_buy`(portfolio_id_required → resolve → unknown_portfolio). intent 단위 + 동시성 회귀.
|
||||
2. REQ-021: buy 핸들러 payload 파싱(symbol 제거, portfolio_id 매핑). 핸들러 단위 + RabbitMQ intent e2e.
|
||||
3. REQ-007 배선: `runtime/daemon.py::_resolve_symbol`(SnapshotStore.get) + IntentService 주입.
|
||||
4. REQ-018: `_record_ledger` 두 skip 경로 WARN 로그 + 카운터. caplog 단위.
|
||||
5. REQ-055: `adapters/persistence/ops_checks.py::find_ledger_gap_slots` anti-join SQL. PG 게이트 단위.
|
||||
6. FR-6 문서: interface-contract / runtime-flows 정합.
|
||||
7. 전체 회귀 + ruff + 인프라 게이트(PG + RabbitMQ).
|
||||
|
||||
## Dependencies
|
||||
- Internal:
|
||||
- dcy-002/BAT-002 intent 제출(REQ-007) — admission 변경 대상.
|
||||
- dcy-006/BAT-007 messaging 핸들러(REQ-021) — buy payload 파싱 변경 대상.
|
||||
- dcy-004/BAT-006 fill→ledger(REQ-018) — `_record_ledger` skip 가시화 대상.
|
||||
- dcy-005/BAT-008 SnapshotStore(REQ-027/028) — portfolio_id→symbol resolve 소스.
|
||||
- REQ-019 ledger outbox 스키마 — REQ-055 anti-join 대상(스키마 변경 없음).
|
||||
- External:
|
||||
- PostgreSQL(anti-join 점검). RabbitMQ(buy intent e2e).
|
||||
|
||||
## Design Gate
|
||||
- Design Required Before Implementation: yes
|
||||
- Reason: (a) buy 계약 hard cutover 는 `BuyIntent` 구조 + admission + 메시지 핸들러 + daemon 배선 + 다수 테스트 표면에 동시 영향 — symbol 서버 resolve 의 주입 경계(`resolve_symbol` 콜백 vs SnapshotStore 직결)와 폴백 규칙을 설계에서 고정해야 회귀를 막는다. (b) reject 정책 분기(portfolio_id_required vs unknown_portfolio)와 universe 부재 판정 경로를 명확히 해야 admission 의미가 일관된다. (c) ledger skip 가시화(REQ-018)와 anti-join 점검(REQ-055)의 책임 분리(런타임 관측 vs 사후 탐지) + OQ-4 결정(outbox 마커 미도입)을 설계에서 못박아야 스키마 오염을 피한다. → design.md 동일 턴 scaffold.
|
||||
|
||||
## Milestones
|
||||
1. M1 — REQ-007 BuyIntent + IntentService resolve_symbol + _admit_buy reject/resolve, intent 단위 green
|
||||
2. M2 — REQ-021 buy 핸들러 payload portfolio_id 기반, 핸들러 단위 + e2e green
|
||||
3. M3 — REQ-007 daemon _resolve_symbol 배선
|
||||
4. M4 — REQ-018 _record_ledger 가시화, caplog 단위 green
|
||||
5. M5 — REQ-055 find_ledger_gap_slots, PG 게이트 단위 green
|
||||
6. M6 — FR-6 문서 정합 + 전체 회귀 + ruff + 인프라 게이트
|
||||
|
||||
## Risks
|
||||
- R-1 (High): hard cutover 가 symbol 기반 기존 테스트/호출부 다수를 깬다. 대응: `IntentService(resolve_symbol=None)` 는 `intent.symbol` 폴백 → 비-핸들러 테스트는 portfolio_id 추가만으로 무회귀; 핸들러/e2e 는 resolver + payload portfolio_id 동시 전환. 모든 호출부 식별·전환(NFR-3).
|
||||
- R-2 (Medium): 매수 시점 universe 부재 시 정상 매수가 unknown_portfolio 로 거부. 대응: 단일 reject 정책(OQ-1) + 진단 reason/로그. 거래일 refresh 정합은 운영 점검(R-4와 연계).
|
||||
- R-3 (Low, OQ-4): skip 마커 행 미도입 → 영구 탐지는 REQ-055 anti-join 으로 충족. outbox 스키마/dispatcher 무변경.
|
||||
- R-4 (Medium, issue #11): 잔존/과거 누락 체결 탐지 지연. 대응: skip 가시화(REQ-018) + anti-join 점검(REQ-055). 과거분 복구는 범위 밖.
|
||||
|
||||
## Notes
|
||||
- hard cutover 1차 게이트: 전체 회귀가 green 이어야 호출부 전환 누락이 없음을 보장.
|
||||
- REQ-055 anti-join 은 PG 게이트(실 PostgreSQL) 통합으로 검증; gap 탐지 + 정상 0건.
|
||||
- buy intent e2e(RabbitMQ)는 payload portfolio_id + resolver 경로 확인.
|
||||
@@ -0,0 +1,65 @@
|
||||
# BAT-015 Verification
|
||||
|
||||
- Status: verified
|
||||
- Batch: BAT-015
|
||||
|
||||
## Acceptance Mapping
|
||||
|
||||
### REQ-007 (buy admission portfolio_id 필수 + symbol resolve, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| AC-1 idle→pending_buy admit (portfolio_id 기반) | tests/application/test_messaging_intent_consumer.py::test_buy_symbol_resolved_from_universe + test_intent_concurrency.py::test_concurrent_buys_admit_exactly_idle_count |
|
||||
| AC-2 sell 전이 (회귀 불변) | tests/application/test_execution_ledger.py (sell fill) + 전체 회귀 |
|
||||
| AC-3 멱등 재전송 | test_intent_concurrency.py::test_duplicate_intent_id_admits_once_under_concurrency |
|
||||
| AC-4 동시성 단일 writer | test_intent_concurrency.py::test_concurrent_buys_admit_exactly_idle_count |
|
||||
| AC-5 결정 재조회 | 전체 회귀(IntentService.lookup) |
|
||||
| portfolio_id_required 거부 (CHG delta) | test_messaging_intent_consumer.py::test_buy_without_portfolio_id_rejected |
|
||||
| unknown_portfolio 거부 (CHG delta) | test_messaging_intent_consumer.py::test_buy_unknown_portfolio_rejected |
|
||||
| symbol 서버 resolve (CHG delta) | test_messaging_intent_consumer.py::test_buy_symbol_resolved_from_universe |
|
||||
|
||||
### REQ-021 (buy payload symbol 제거, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| buy payload portfolio_id 매핑(symbol 미사용) | test_messaging_intent_consumer.py (3 buy 테스트) |
|
||||
| RabbitMQ buy intent e2e | tests/adapters/messaging/test_rabbitmq_intent_e2e.py (인프라 게이트) |
|
||||
|
||||
### REQ-018 (ledger skip 가시화, change-req)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| AC-1 buy fill → 원장 1건 + holding | tests/application/test_execution_ledger.py::test_buy_fill_enqueues_one_ledger_item_and_writer_records_it |
|
||||
| AC-2 sell fill → 원장 1건 + closed | test_execution_ledger.py::test_sell_fill_enqueues_sell_ledger_item |
|
||||
| AC-3 FillStatus execution_id/fee | 위 buy 테스트 |
|
||||
| AC-4 skip 시 전이 정상 + **가시화(WARN+카운터)** | test_execution_ledger.py::test_portfolio_id_absent_skips_ledger_but_transitions_slot + ::test_ledger_skip_emits_warning (caplog) |
|
||||
| AC-5 단일 기록 주체 | test_domain_import_sweep + execution.py 단일 enqueue 지점 |
|
||||
|
||||
### REQ-055 (closed↔outbox anti-join 운영 점검, 신규)
|
||||
| AC | Evidence |
|
||||
|----|----------|
|
||||
| AC-1 portfolio_id NULL closed 포함 (S-4) | tests/adapters/persistence/test_ops_checks.py::test_detects_only_gap_slots (PG 게이트) |
|
||||
| AC-2 portfolio_id 있으나 outbox 부재 포함 | ::test_detects_only_gap_slots |
|
||||
| AC-3 정상(outbox 일치) 제외 → 0건 | ::test_no_gaps_when_all_recorded |
|
||||
| AC-4 비-closed 제외 | ::test_detects_only_gap_slots |
|
||||
|
||||
## Evidence
|
||||
- Test:
|
||||
- 단위 전체 회귀: `282 passed / 32 skipped`(`::test_sandbox_token_and_submit` deselect — LIVE `.env` sandbox URL 아님, 본 batch 무관).
|
||||
- 인프라 게이트(PG trex_core_test + RabbitMQ): `314 passed`(PG anti-join 점검 gap 탐지/정상 0건 + buy intent RabbitMQ e2e + 마이그레이션 round-trip 포함).
|
||||
- `ruff check trex_core tests` → All checks passed.
|
||||
- Logs: `282 passed, 32 skipped, 1 deselected` / `314 passed, 1 deselected ~15.5s`.
|
||||
|
||||
## 성공 기준(dcy-011) 대응
|
||||
- S-1(portfolio_id 누락 거부 + portfolio_id 만으로 resolve admit): test_buy_without_portfolio_id_rejected + test_buy_symbol_resolved_from_universe.
|
||||
- S-2(매수 슬롯 portfolio_id 귀속 + outbox/ledger 정합): buy fill 원장 테스트 + e2e.
|
||||
- S-3(ledger skip 무성 금지, WARN 100%): test_ledger_skip_emits_warning(caplog).
|
||||
- S-4(점검 쿼리 누락 정확 식별, 정상 0건): test_detects_only_gap_slots + test_no_gaps_when_all_recorded.
|
||||
- S-5(코드-문서 정합): interface-contract / runtime-flows 갱신(FR-6).
|
||||
|
||||
## Carried-Over / 비차단
|
||||
- 매수 시점 universe(SnapshotStore) portfolio_id 존재 보장은 운영 런타임 데이터(거래일 refresh 정합) — 부재는 `unknown_portfolio` reject 로 안전 처리(R-2).
|
||||
- REQ-055 점검 쿼리의 알람/주기 실행 배선은 운영 배포 절차(Out of Scope) — 쿼리/함수가 산출물.
|
||||
- 과거 누락분 데이터 복구는 본 주기 범위 밖(필요 시 별도 운영 이슈, R-4).
|
||||
|
||||
## Result
|
||||
- Ready for Release: true
|
||||
- Blocking Issues:
|
||||
- 없음. hard cutover 호출부 전환 누락은 전체 회귀(314 passed) green 으로 부재 확인. LIVE sandbox 토큰 테스트 deselect 는 `.env` 환경 의존(본 batch 무관).
|
||||
@@ -22,3 +22,4 @@
|
||||
| BAT-012 | standard | release-candidate | REQ-043, REQ-044, REQ-045, REQ-046, REQ-047 | dcy-008 | [bat-012](./bat-012_20260607_kill-switch-control/) |
|
||||
| BAT-013 | standard | release-candidate | REQ-048, REQ-049, REQ-050, REQ-051 | dcy-009 | [bat-013](./bat-013_20260607_multi-instance-topology/) |
|
||||
| BAT-014 | batch-lite | release-candidate | REQ-052, REQ-053, REQ-054 | dcy-010 | [bat-014](./bat-014_20260608_safety-sell-cooldown/) |
|
||||
| BAT-015 | standard | release-candidate | REQ-007 (CHG-20260608-01), REQ-018 (CHG-20260608-01), REQ-021 (CHG-20260608-01), REQ-055 | dcy-011 | [bat-015](./bat-015_20260608_buy-intent-portfolio-id-contract/) |
|
||||
|
||||
Reference in New Issue
Block a user