dita_v2: unknown != flat (VenuePostAckError) + lossless telemetry lane
BUG CLASS (doc: BUGCLASS_INDETERMINATE_OUTCOME_20260713.md): an operation with an external side effect has THREE outcomes — NOT_ATTEMPTED, ATTEMPTED_REFUSED, ATTEMPTED_INDETERMINATE — and rollback is sound only for the first two. Collapsing the third into 'failed' is what orphaned 6 live SHORTs: a post-ack TypeError reached rust_backend's 'except Exception -> synthetic REJECTED -> FSM rollback', which asserted 'no order exists' about an order that was already filled. Telemetry never had a veto; it hijacked the failure channel. FIXES (no new seams, no re-architecture): - venue.py: VenuePostAckError — typed channel meaning THE EFFECT EXISTS. Carries receipt. - bingx_venue submit/submit_async: point-of-no-return fence. Post-ack bookkeeping failures raise VenuePostAckError instead of a bare exception. - rust_backend (BOTH submit paths): catch VenuePostAckError FIRST -> no synthetic REJECT, no rollback. Slot stays working; E-feed FULL_FILL / reconcile settles the truth. LOSSLESS TELEMETRY (HJ: 'DITAv2 exists precisely because seams dropped 40% of inputs'): drop-oldest is data loss and is GONE. Exec path appends O(1) to an unbounded queue and returns. A SEPARATE spiller thread (which never touches the plane, so a wedged plane cannot starve it) parks the backlog above HWM into a durable append-only spool; the publisher replays the spool when the plane recovers. Proven: wedged-forever plane + 200k records -> 0 dropped, 195903 durable on disk, 4096 in memory, 7.4 us/call on the exec path. Lossless AND memory-bounded. Healthy plane: 2000/2000. STILL BROKEN, flagged to codex: the pre-ack branch rolls back on TIMEOUT — but a timeout is the definition of INDETERMINATE (the order may have filled). Same bug class, older, live. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -19,6 +19,27 @@ from .contracts import (
|
||||
from .exchange_event import ExchangeEvent
|
||||
|
||||
|
||||
class VenuePostAckError(Exception):
|
||||
"""Raised when submit fails AFTER the venue has already accepted the order.
|
||||
|
||||
THE POINT OF NO RETURN. A plain exception out of submit()/submit_async() is
|
||||
ambiguous: it may mean "the venue never got the order" (safe to roll the FSM
|
||||
back to IDLE) or "the venue filled it and a line below blew up" (rolling back
|
||||
is catastrophic — the venue keeps the position while the kernel believes it is
|
||||
flat). On 2026-07-13 a post-ack TypeError took the first interpretation and
|
||||
orphaned 6 live SHORTs.
|
||||
|
||||
Post-ack failure is NOT failure. It is UNKNOWN — and unknown is never flat.
|
||||
Callers MUST NOT synthesise a REJECTED event for this error: leave the slot in
|
||||
its working state and let the E-feed FILL / reconcile settle the truth.
|
||||
"""
|
||||
|
||||
def __init__(self, message: str, *, receipt: Any = None, events: Optional[List[VenueEvent]] = None):
|
||||
super().__init__(message)
|
||||
self.receipt = receipt
|
||||
self.events = events or []
|
||||
|
||||
|
||||
class VenueAdapter(Protocol):
|
||||
"""Abstract venue adapter used by the kernel."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user