VIOLET V2a: V-TYPES domain layer + hypothesis properties + divergence reject-at-source

domain.py: refined scalar aliases (BarsHeld kills the bars_held=-106
UInt16 poison class by construction), DivergenceRow (DDL-shaped, frozen,
extra=forbid), ExecDriverSettings (env boundary for the V2 driver; ttl
override exists because the shared router clamps TTLs >= 0.5s),
ExecGateReport schema, beartype 'typed' decorator with
DOLPHIN_VIOLET_BEARTYPE=0 kill-switch.

divergence.py: rows now parse through DivergenceRow before the sink —
malformed rows die at the source with a rate-limited WARNING + counter,
never at the head of the CH spool.

Properties (hypothesis, derandomized): ExecutionRouter state machine
(fill/retry mutual exclusion via pop-semantics, R1 exit escalation same
trade_id, bounded retry chains, <=1 working ENTER), LatencyHistogram
percentile laws (member-of-samples, monotone, extremes), DivergenceRow
parse laws. 34 new tests; violet suite 64 green; router 77 green; zero
shared-file edits.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Codex
2026-06-13 00:08:18 +02:00
parent 94b5b552e5
commit ba01b914ce
4 changed files with 605 additions and 11 deletions

View File

@@ -25,7 +25,10 @@ import time
from dataclasses import dataclass
from typing import Any, Callable, Dict, List, Optional
from pydantic import ValidationError
from .clock import PlaneClock, mono_ns
from .domain import DivergenceRow
LOGGER = logging.getLogger("violet.divergence")
@@ -84,6 +87,7 @@ class FeedDivergenceMonitor:
self._stream_task: Optional[asyncio.Task] = None
self._rest_task: Optional[asyncio.Task] = None
self.rows_emitted = 0
self.rows_rejected = 0
# ── venue side ───────────────────────────────────────────────────────────
@@ -204,17 +208,29 @@ class FeedDivergenceMonitor:
if (now - vm.mono) > self.venue_clock.staleness_budget_ns:
continue
divergence_bps = (vm.mid - scan_price) / scan_price * 1e4
self.sink(self.table, {
"ts": int(time.time() * 1000), # DateTime64(3)
"session_id": self.session_id,
"asset": key,
"scan_price": scan_price,
"venue_mid": vm.mid,
"divergence_bps": divergence_bps,
"scan_seq": int(scan_seq),
"venue_seq": int(vm.seq),
"mono_ns": int(now),
})
# V-TYPES: parse, don't validate — a malformed row dies here,
# never at the head of the CH spool (the bars_held lesson).
try:
row = DivergenceRow(
ts=int(time.time() * 1000), # DateTime64(3)
session_id=self.session_id,
asset=key,
scan_price=scan_price,
venue_mid=vm.mid,
divergence_bps=divergence_bps,
scan_seq=int(scan_seq),
venue_seq=int(vm.seq),
mono_ns=int(now),
)
except ValidationError as exc:
self.rows_rejected += 1
if self.rows_rejected == 1 or self.rows_rejected % 1000 == 0:
self.logger.warning(
"divergence row REJECTED at source (#%d) asset=%s: %s",
self.rows_rejected, key, exc,
)
continue
self.sink(self.table, row.model_dump())
emitted += 1
self.rows_emitted += emitted
return emitted