feat(execution): reconcile timed-out orders safely
This commit is contained in:
@@ -38,6 +38,7 @@ from trex_core.domain.slot import (
|
||||
)
|
||||
|
||||
_MAX_ADMIT_RETRY = 8
|
||||
_HARD_ORDER_TIMEOUT_SEC = 3600
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@@ -129,9 +130,15 @@ class IntentService:
|
||||
# expected to set max_active_slots in the instance TOML — design §5).
|
||||
self._instance_key = instance_key
|
||||
self._max_active_slots = max_active_slots
|
||||
# dcy-022 / REQ-068: default timeout when intent omits timeout_sec.
|
||||
# None = OFF = indefinite wait (legacy behavior). Per-intent value overrides.
|
||||
self._default_order_timeout_sec = default_order_timeout_sec
|
||||
# Omission never disables timeout. Runtime validates configured values;
|
||||
# this seam remains fail-safe for direct application construction.
|
||||
self._default_order_timeout_sec = (
|
||||
default_order_timeout_sec
|
||||
if isinstance(default_order_timeout_sec, int)
|
||||
and not isinstance(default_order_timeout_sec, bool)
|
||||
and 1 <= default_order_timeout_sec <= _HARD_ORDER_TIMEOUT_SEC
|
||||
else _HARD_ORDER_TIMEOUT_SEC
|
||||
)
|
||||
# BAT-050 / REQ-104: reconcile-warning gate store (Optional).
|
||||
# When None, gate is skipped (backward compat, NFR-4).
|
||||
self._warn_store = warn_store
|
||||
@@ -219,6 +226,9 @@ class IntentService:
|
||||
dcy-036 / REQ-114: entry gate — if entry_allowed_fn returns False,
|
||||
BuyIntent is rejected before any slot lookup."""
|
||||
|
||||
timeout_error = self._timeout_error(intent.timeout_sec)
|
||||
if timeout_error is not None:
|
||||
return IntentDecision(intent.intent_id, "rejected", None, timeout_error)
|
||||
if intent.venue not in {"AUTO", "KRX", "NXT", "SOR"}:
|
||||
return IntentDecision(intent.intent_id, "rejected", None, "invalid_venue")
|
||||
|
||||
@@ -334,6 +344,9 @@ class IntentService:
|
||||
return IntentDecision(intent.intent_id, "rejected", None, "already_active")
|
||||
|
||||
def _admit_sell(self, intent: SellIntent) -> IntentDecision:
|
||||
timeout_error = self._timeout_error(intent.timeout_sec)
|
||||
if timeout_error is not None:
|
||||
return IntentDecision(intent.intent_id, "rejected", None, timeout_error)
|
||||
if intent.venue not in {"AUTO", "KRX", "NXT", "SOR"}:
|
||||
return IntentDecision(intent.intent_id, "rejected", None, "invalid_venue")
|
||||
|
||||
@@ -385,3 +398,13 @@ class IntentService:
|
||||
except SlotStateMismatchError:
|
||||
return IntentDecision(intent.intent_id, "rejected", None, "slot_state_changed")
|
||||
return IntentDecision(intent.intent_id, "admitted", updated.slot_id, None)
|
||||
|
||||
@staticmethod
|
||||
def _timeout_error(timeout_sec: object) -> Optional[str]:
|
||||
if timeout_sec is None:
|
||||
return None
|
||||
if not isinstance(timeout_sec, int) or isinstance(timeout_sec, bool):
|
||||
return "bad_request:timeout_sec_must_be_integer"
|
||||
if not 1 <= timeout_sec <= _HARD_ORDER_TIMEOUT_SEC:
|
||||
return "bad_request:timeout_sec_out_of_range"
|
||||
return None
|
||||
|
||||
Reference in New Issue
Block a user