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:
Codex
2026-07-13 15:43:55 +02:00
parent d9b7e05531
commit bdc54fbeaa
5 changed files with 529 additions and 19 deletions

View File

@@ -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.