feat(execution): reconcile timed-out orders safely

This commit is contained in:
Hermes Agent
2026-08-14 15:15:52 +09:00
parent 482b00e54d
commit 147873cb5d
21 changed files with 801 additions and 87 deletions
+26 -3
View File
@@ -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