fix: close broker uncertainty audit gaps

This commit is contained in:
jordan
2026-07-29 17:53:39 +09:00
parent de1e3eb44c
commit 611805dcec
19 changed files with 236 additions and 42 deletions
+4 -1
View File
@@ -11,11 +11,13 @@ Public API:
- EXPECTED_SLOT_FIELDS: canonical `Slot` column contract (guarded by test_contract)
- SlotService: main domain service (transit, sell, thresholds, capacity)
- SlotRepository: abstract storage protocol
- SlotTransitionError / SlotStateMismatchError / SlotNotFoundError: domain errors
- SlotTransitionError / SlotStateMismatchError / SlotAdmissionRejectedError /
SlotNotFoundError: domain errors
"""
from __future__ import annotations
from trex_core.domain.slot.errors import (
SlotAdmissionRejectedError,
SlotDomainError,
SlotNotFoundError,
SlotStateMismatchError,
@@ -42,6 +44,7 @@ __all__ = [
"EXPECTED_SLOT_FIELDS",
"SLOT_ADVISORY_LOCK_ID",
"Slot",
"SlotAdmissionRejectedError",
"SlotCapacity",
"SlotDomainError",
"SlotNotFoundError",
+8
View File
@@ -21,3 +21,11 @@ class SlotStateMismatchError(SlotDomainError):
different state between read and write (race detection inside the
transition function).
"""
class SlotAdmissionRejectedError(SlotDomainError):
"""Raised when an advisory-locked admission precondition rejects."""
def __init__(self, reason: str) -> None:
self.reason = reason
super().__init__(reason)
@@ -224,6 +224,8 @@ class InMemorySlotRepository:
slot_id: UUID,
broker_order_id: str,
state: str,
*,
only_if_unset: bool = False,
) -> bool:
"""Persist one cancel attempt iff the same order is still pending."""
s = self._slots.get(slot_id)
@@ -231,6 +233,7 @@ class InMemorySlotRepository:
s is None
or s.broker_order_id != broker_order_id
or s.status not in self._PENDING
or (only_if_unset and s.cancel_state is not None)
):
return False
now = datetime.now(tz=timezone.utc)
+7 -1
View File
@@ -15,10 +15,11 @@ from __future__ import annotations
from dataclasses import replace
from datetime import datetime, timezone
from decimal import Decimal
from typing import Any, Optional
from typing import Any, Callable, Optional
from uuid import UUID
from trex_core.domain.slot.errors import (
SlotAdmissionRejectedError,
SlotNotFoundError,
SlotStateMismatchError,
SlotTransitionError,
@@ -87,6 +88,7 @@ class SlotService:
slippage_pct: Optional[float] = None,
venue: Optional[str] = None,
extra: Optional[dict[str, Any]] = None,
precondition: Optional[Callable[[Slot], Optional[str]]] = None,
) -> Slot:
"""Transition slot from one state to another atomically.
@@ -116,6 +118,10 @@ class SlotService:
f"slot {slot_id} current state {slot.status.value} "
f"!= requested from_state {from_state.value}"
)
if precondition is not None:
rejection_reason = precondition(slot)
if rejection_reason is not None:
raise SlotAdmissionRejectedError(rejection_reason)
updated = self._apply_transition_fields(
slot,