feat(slot): BAT-029 M1 — intent 계약 키 slot_id 통일 (dcy-020/REQ-061)

매수 계약 키를 portfolio_id → slot_id 로 이동(매도는 이미 slot_id). 외부 전략
무중단 위해 portfolio_id 레거시 dual-accept.

- BuyIntent: slot_id 필드 추가, portfolio_id Optional 완화
- _admit_buy: 자산 중심 모드 우선 분기; _admit_buy_asset_centric 가 slot_id(canonical)
  또는 portfolio_id(legacy dual-accept)로 슬롯 해소, symbol 은 슬롯 portfolio_id 로 resolve.
  unknown_slot/unknown_portfolio 사유 구분.
- messaging_handlers: buy 파싱에 slot_id 수용
- 신규 테스트: slot_id 매수 / unknown_slot / 기존 dual-accept 회귀

검증: 전체 389 passed / 36 skipped, ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-10 12:09:47 +09:00
co-authored by Claude Opus 4.8
parent e10ac519c7
commit b80407098e
3 changed files with 63 additions and 19 deletions
@@ -34,6 +34,25 @@ def test_activates_asset_slot():
assert slot.status is SlotState.PENDING_BUY and slot.symbol == "005930"
def test_activates_by_slot_id():
# dcy-020/REQ-061: slot_id is the canonical buy contract key.
repo, svc, intents = _wire(max_active=2)
repo.reconcile_universe(INST, DAY, {"pf-a"})
slot = repo.find_slot_by_portfolio(INST, "pf-a")
d = intents.submit_buy(BuyIntent(intent_id="b1", trading_date=DAY, slot_id=slot.slot_id))
assert d.status == "admitted" and d.slot_id == slot.slot_id
got = repo.fetch_slot(slot.slot_id)
assert got.status is SlotState.PENDING_BUY and got.symbol == "005930" # resolved from slot.portfolio_id
def test_unknown_slot_for_bad_slot_id():
from uuid import uuid4
repo, svc, intents = _wire(max_active=2)
repo.reconcile_universe(INST, DAY, {"pf-a"})
d = intents.submit_buy(BuyIntent(intent_id="b1", trading_date=DAY, slot_id=uuid4()))
assert d.status == "rejected" and d.reason == "unknown_slot"
def test_already_active_is_rejected():
repo, svc, intents = _wire(max_active=2)
repo.reconcile_universe(INST, DAY, {"pf-a", "pf-b"})