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
+5 -1
View File
@@ -579,6 +579,7 @@ class ExecutionService:
slot.slot_id,
str(slot.broker_order_id),
CancelState.UNKNOWN.value,
only_if_unset=True,
):
_log.error(
"cancel attempt reservation conflict: slot=%s broker_order_id=%s",
@@ -719,6 +720,8 @@ class ExecutionService:
if not slot.broker_order_id:
# Claimed but not yet dispatched — execution loop will pick up or rollback.
return "not_dispatched"
if slot.cancel_state is not None:
return "cancel_already_recorded"
side = "buy" if slot.status is SlotState.PENDING_BUY else "sell"
qty = int(slot.qty or 0)
try:
@@ -735,8 +738,9 @@ class ExecutionService:
slot.slot_id,
str(slot.broker_order_id),
CancelState.UNKNOWN.value,
only_if_unset=True,
):
return "cancel_state_conflict"
return "cancel_already_recorded"
outcome = self._broker.cancel_order(slot.broker_order_id, **cancel_kwargs)
except Exception: # noqa: BLE001 - adapter failure becomes durable unknown
outcome = CancelOutcome(CancelState.UNKNOWN, "adapter_exception")
+22 -12
View File
@@ -30,6 +30,7 @@ from uuid import UUID
from trex_core.application.ports import IntentStore
from trex_core.domain.slot import (
SlotAdmissionRejectedError,
SlotRepository,
SlotService,
SlotState,
@@ -275,20 +276,24 @@ class IntentService:
symbol = _resolve(slot)
if not symbol:
return IntentDecision(intent.intent_id, "rejected", None, "unknown_portfolio")
if self._buy_guard_fn is not None:
def _admission_precondition(_locked_slot) -> Optional[str]:
# Runs inside SlotService's instance-scoped advisory lock. This
# makes capacity and cached-cash reservation checks atomic with
# the PENDING_BUY transition across same-instance daemons.
if (
not is_increase
and self._max_active_slots is not None
and self._repo.count_active(self._instance_key)
>= self._max_active_slots
):
return "capacity_full"
if self._buy_guard_fn is None:
return None
try:
guard_reason = self._buy_guard_fn(intent)
return self._buy_guard_fn(intent)
except Exception: # noqa: BLE001 - buy guard fails closed
guard_reason = "account_snapshot_unavailable"
if guard_reason is not None:
return IntentDecision(
intent.intent_id, "rejected", None, guard_reason
)
# M cap applies only to NEW activations (initial buy). An increase on
# an already-active asset does not change the active asset count.
if (not is_increase and self._max_active_slots is not None
and self._repo.count_active(self._instance_key) >= self._max_active_slots):
return IntentDecision(intent.intent_id, "rejected", None, "capacity_full")
return "account_snapshot_unavailable"
# dcy-022 / REQ-068: resolve effective timeout (per-intent > config default).
effective_timeout = (
intent.timeout_sec
@@ -317,6 +322,11 @@ class IntentService:
slippage_pct=intent.slippage_pct,
venue=intent.venue,
extra={"intent_id": intent.intent_id, "symbol": symbol},
precondition=_admission_precondition,
)
except SlotAdmissionRejectedError as exc:
return IntentDecision(
intent.intent_id, "rejected", None, exc.reason
)
except SlotStateMismatchError:
continue # slot changed under us; re-fetch and re-evaluate