dita_v2(exec): INDETERMINATE submit is not REJECTED — unknown is never flat
A BingX read-timeout/reset/5xx after send means the answer was lost, not that the order failed. Classify every submit failure by what it PROVES: NOT_ATTEMPTED / REFUSED -> rollback sound; INDETERMINATE -> point-lookup our own clientOrderId (read-only, bounded, never a reconcile); unresolved stays UNKNOWN — no synthetic REJECT, no slot rollback, E-feed FILL settles truth. - prod/bingx/http.py: BingxHttpError.effect + order_may_exist, 9 raise sites tagged - adapters/bingx_direct.py: _lookup_own_order_by_client_id (never POSTs) - dita_v2/venue.py: VenueIndeterminateError(VenuePostAckError) — existing fences catch it - dita_v2/bingx_venue.py: both submit paths escalate INDETERMINATE receipts - 14 tests incl. kernel no-rollback invariant + genuine-REFUSED contrast Suite: 3416 passed, 19 skipped, 3 xfailed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -45,6 +45,7 @@ from .utils import json_safe
|
||||
from .utils import safe_float
|
||||
from .venue import VenueAdapter
|
||||
from .venue import VenuePostAckError
|
||||
from .venue import VenueIndeterminateError
|
||||
|
||||
|
||||
def _row_text(row: dict[str, Any], *keys: str, default: str = "") -> str:
|
||||
@@ -853,6 +854,14 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
submitted = replace(intent, target_size=float(legacy.target_size))
|
||||
# ── PRE-ACK ── safe to roll back if this raises.
|
||||
receipt = self._call_backend("submit_intent", legacy)
|
||||
# ── UNKNOWN OUTCOME ── see submit_async(): the order may be live, so the
|
||||
# kernel must NOT roll back to flat on it.
|
||||
if str(getattr(receipt, "status", "") or "").upper() == "INDETERMINATE":
|
||||
raise VenueIndeterminateError(
|
||||
f"submit outcome UNKNOWN for intent={intent.intent_id} asset={intent.asset} "
|
||||
f"— order may be live at the venue; refusing to claim rejection",
|
||||
receipt=receipt,
|
||||
)
|
||||
# ── POINT OF NO RETURN ── order is LIVE; see submit_async().
|
||||
try:
|
||||
events = self._events_from_submit(submitted, receipt, None, None)
|
||||
@@ -920,6 +929,17 @@ class BingxVenueAdapter(VenueAdapter):
|
||||
# ── PRE-ACK ── an exception here means the venue never took the order.
|
||||
# Safe for the caller to roll the FSM back.
|
||||
receipt = await self.backend.submit_intent(legacy)
|
||||
# ── UNKNOWN OUTCOME ── the submit timed out / 5xx'd and the adapter could
|
||||
# not establish the truth even after asking the venue about our own
|
||||
# clientOrderId. The order MAY be live. Escalate as indeterminate so the
|
||||
# kernel does NOT roll the slot back to flat (VenueIndeterminateError is a
|
||||
# VenuePostAckError, so the existing no-rollback fences catch it).
|
||||
if str(getattr(receipt, "status", "") or "").upper() == "INDETERMINATE":
|
||||
raise VenueIndeterminateError(
|
||||
f"submit outcome UNKNOWN for intent={intent.intent_id} asset={intent.asset} "
|
||||
f"— order may be live at the venue; refusing to claim rejection",
|
||||
receipt=receipt,
|
||||
)
|
||||
# ── POINT OF NO RETURN ── the order is LIVE at the venue from here on.
|
||||
# Nothing below may reach the caller as a bare exception: that channel
|
||||
# means "no order exists", and the caller rolls back to flat on it.
|
||||
|
||||
Reference in New Issue
Block a user