feat(strategy-state): persist instance-scoped slot maps

This commit is contained in:
Hermes Agent
2026-08-29 15:11:43 +09:00
parent 3f94c19162
commit 4699de61cc
18 changed files with 301 additions and 646 deletions
@@ -155,8 +155,10 @@ def test_cancel_attempt_reservation_is_compare_and_set(pg_clean):
assert repo.fetch_slot(decision.slot_id).cancel_state == "requested"
# dcy-028 / REQ-079·081 (BAT-042): strategy-state store + occupancy_seq column
# REQ-0009: slot-scoped strategy-instance state map.
def test_strategy_state_store_pg(pg_clean):
from concurrent.futures import ThreadPoolExecutor
from trex_core.adapters.persistence.postgresql import PgStrategyStateStore
from trex_core.application import strategy_state as ss
@@ -169,31 +171,30 @@ def test_strategy_state_store_pg(pg_clean):
reason="buy", actor="t")
svc.transit_slot(slot.slot_id, SlotState.PENDING_BUY, SlotState.HOLDING,
reason="filled", actor="t", qty=10, avg_price=100)
# occupancy_seq persisted
held = repo.fetch_slot(slot.slot_id)
assert held.occupancy_seq == 1
# first-create
r1 = store.set(slot.slot_id, "tsl_v1", expected_version=0,
expected_occupancy_seq=1, data="peak", current_occupancy_seq=1,
slot_active=True)
r1 = store.set(slot.slot_id, "instance-a", expected_version=0, data="peak", slot_active=True)
assert r1.ok and r1.version == 1
# version conflict
r2 = store.set(slot.slot_id, "tsl_v1", expected_version=0,
expected_occupancy_seq=1, data="stale", current_occupancy_seq=1,
slot_active=True)
r2 = store.set(slot.slot_id, "instance-a", expected_version=0, data="stale", slot_active=True)
assert not r2.ok and r2.reason == ss.REASON_VERSION_CONFLICT
# batched read
got = store.get_for_slots([slot.slot_id], {slot.slot_id: 1})
assert got[slot.slot_id]["tsl_v1"].data == "peak"
missing = store.set(slot.slot_id, "instance-missing", expected_version=1, data="stale", slot_active=True)
assert not missing.ok and missing.reason == ss.REASON_VERSION_CONFLICT and missing.version == 0
store.set(slot.slot_id, "instance-b", expected_version=0, data="other", slot_active=True)
got = store.get_for_slots([slot.slot_id])
assert got[slot.slot_id]["instance-a"].data == "peak"
assert got[slot.slot_id]["instance-b"].data == "other"
with ThreadPoolExecutor(max_workers=2) as executor:
writes = list(executor.map(
lambda item: store.set(slot.slot_id, item, expected_version=0, data=item, slot_active=True),
("instance-c", "instance-d"),
))
assert all(write.ok for write in writes)
assert {"instance-c", "instance-d"} <= set(store.get_for_slots([slot.slot_id])[slot.slot_id])
# close deletes state (C-3)
svc.transit_slot(slot.slot_id, SlotState.HOLDING, SlotState.PENDING_SELL,
reason="sell", actor="t")
svc.transit_slot(slot.slot_id, SlotState.PENDING_SELL, SlotState.INACTIVE,
reason="sold", actor="t")
assert store.get_for_slots([slot.slot_id], {slot.slot_id: 1}) == {}
# reuse bumps occupancy_seq
assert store.get_for_slots([slot.slot_id])[slot.slot_id]["instance-a"].version == 1
reused = svc.transit_slot(slot.slot_id, SlotState.INACTIVE, SlotState.PENDING_BUY,
reason="buy", actor="t")
assert reused.occupancy_seq == 2