dita_v2(bingx_venue): make _publish_telemetry TOTAL — telemetry cannot raise into the order path

The pre-existing guard covered only publish(). The attribute/coercion block above it
(int()/str()/.metadata.get() on intent+order) was UNGUARDED, and kwarg binding happens
before the body runs at all — so a signature skew sailed straight past a defence that
existed precisely to prevent this, and orphaned 6 live positions.

Everything that can raise now lives inside one try. Failures log loud and are swallowed;
the order path is never affected. Proven against: exploding plane, poisoned intent
coercion (the formerly-unguarded region), and absent plane.

ARCHITECTURAL DEBT (raised by HJ, not fixed here): telemetry has no business being called
inline on the exec path at all. Correct shape = fire-and-forget enqueue drained by the M6
journal lane. This commit makes it harmless, not absent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-07-13 08:54:01 +02:00
parent aec0b4670f
commit 832c85b233

View File

@@ -302,29 +302,39 @@ class BingxVenueAdapter(VenueAdapter):
client_order_id: str = "",
details: dict[str, Any] | None = None,
) -> None:
plane = self._telemetry_plane
publish = getattr(plane, "publish_venue", None) if plane is not None else None
if publish is None:
return
slot_id = 0
trade_id = ""
asset = ""
side = TradeSide.FLAT
action = ""
intent_id = ""
if intent is not None:
slot_id = int(getattr(intent, "slot_id", 0) or 0)
trade_id = str(getattr(intent, "trade_id", "") or "")
asset = str(getattr(intent, "asset", "") or "")
side = getattr(intent, "side", TradeSide.FLAT)
action = str(getattr(intent, "action", "") or "")
intent_id = str(getattr(intent, "intent_id", "") or "")
if order is not None:
slot_id = int(order.metadata.get("slot_id", slot_id) or slot_id)
trade_id = str(order.internal_trade_id or trade_id)
asset = str(order.metadata.get("asset") or asset)
side = order.side or side
# TOTAL: this method cannot raise. It runs INLINE on the order path —
# submit()/submit_async() call it immediately after the venue has already
# accepted the order. An exception escaping here reaches the caller's
# submit guard, which synthesises a REJECTED event and rolls the FSM back
# while the venue keeps the position (orphan; 6 of them on 2026-07-13).
#
# The guard used to cover only publish(), leaving the attribute/coercion
# block below it unprotected — which is how a signature skew orphaned live
# positions past a defence that existed precisely to prevent it. Everything
# that can raise now lives inside the try. Observability never vetoes a fill.
try:
plane = self._telemetry_plane
publish = getattr(plane, "publish_venue", None) if plane is not None else None
if publish is None:
return
slot_id = 0
trade_id = ""
asset = ""
side = TradeSide.FLAT
action = ""
intent_id = ""
if intent is not None:
slot_id = int(getattr(intent, "slot_id", 0) or 0)
trade_id = str(getattr(intent, "trade_id", "") or "")
asset = str(getattr(intent, "asset", "") or "")
side = getattr(intent, "side", TradeSide.FLAT)
action = str(getattr(intent, "action", "") or "")
intent_id = str(getattr(intent, "intent_id", "") or "")
if order is not None:
slot_id = int(order.metadata.get("slot_id", slot_id) or slot_id)
trade_id = str(order.internal_trade_id or trade_id)
asset = str(order.metadata.get("asset") or asset)
side = order.side or side
publish(
VenueTelemetrySnapshot(
phase=phase,
@@ -350,8 +360,14 @@ class BingxVenueAdapter(VenueAdapter):
details=dict(details or {}),
)
)
except Exception:
pass
except Exception as exc:
import logging as _log
_log.getLogger(__name__).warning(
"FIX(bingx_venue): telemetry publish failed (phase=%s) — swallowed, "
"order path unaffected: %s",
phase, exc,
)
def _call_backend(self, method_name: str, *args: Any, **kwargs: Any) -> Any:
method = getattr(self.backend, method_name, None)