fix: close broker uncertainty audit gaps
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user