First commit of the previously-untracked PINK-on-DITAv2 migration system (execution moves to the Rust kernel; policy stays on legacy DITA, so Alpha Engine algorithmic integrity is preserved). BLUE is untouched. Sprint 0 (safety snapshot + flaw-fix verification, MARKET single-leg scope): - Verified Rust FSM fixes (flaws 2,4,10,11,13) by source read of lib.rs. - Hardened 5 vacuous/guarded assertions in test_flaws.py so each flaw test genuinely exercises its fix. Most important: Flaw 5 now asserts capital moves by EXACTLY realized PnL (was entering/exiting at the same price). - Offline suites: 533 passed, 0 failed (35 flaws + 402 kernel/accounting/ bridge + 96 runtime/persistence/multi-exit/restart/seams). - GATE PASS: MARKET-path-critical flaws 1,2,5 confirmed fixed + green. - Added SPRINT0_FLAW_VERIFICATION.md report and _rust_kernel/.gitignore (excludes Rust target/ build artifacts). LIMIT/partial-fill remain explicitly out of scope (MARKET-only bring-up). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
103 lines
3.1 KiB
Python
103 lines
3.1 KiB
Python
"""Debug journaling surfaces for DITAv2."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from dataclasses import dataclass, field
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Callable, Dict, List, Optional, Protocol
|
|
|
|
from .contracts import KernelTransition, TradeSlot, TradeStage, VenueEvent
|
|
from .control import KernelControlSnapshot
|
|
from .utils import json_safe, json_text
|
|
|
|
JournalSink = Callable[[str, Dict[str, Any]], None]
|
|
|
|
|
|
class KernelJournal(Protocol):
|
|
"""Append-only debug journal interface."""
|
|
|
|
def record(self, row: Dict[str, Any]) -> None:
|
|
...
|
|
|
|
def record_transition(
|
|
self,
|
|
*,
|
|
transition: KernelTransition,
|
|
slot: TradeSlot,
|
|
event: Optional[VenueEvent] = None,
|
|
control: Optional[KernelControlSnapshot] = None,
|
|
) -> None:
|
|
...
|
|
|
|
|
|
@dataclass
|
|
class MemoryKernelJournal:
|
|
"""In-memory journal used in tests."""
|
|
|
|
rows: List[Dict[str, Any]] = field(default_factory=list)
|
|
capture_limit: int = 10_000
|
|
|
|
def record(self, row: Dict[str, Any]) -> None:
|
|
if len(self.rows) < self.capture_limit:
|
|
self.rows.append(dict(row))
|
|
|
|
def record_transition(
|
|
self,
|
|
*,
|
|
transition: KernelTransition,
|
|
slot: TradeSlot,
|
|
event: Optional[VenueEvent] = None,
|
|
control: Optional[KernelControlSnapshot] = None,
|
|
) -> None:
|
|
row = _transition_row(transition=transition, slot=slot, event=event, control=control)
|
|
self.record(row)
|
|
|
|
|
|
class ClickHouseKernelJournal:
|
|
"""Fire-and-forget ClickHouse journal.
|
|
|
|
The sink is a small callable of the form ``sink(table_name, row_dict)``.
|
|
"""
|
|
|
|
def __init__(self, sink: Optional[JournalSink] = None):
|
|
self.sink = sink
|
|
|
|
def record(self, row: Dict[str, Any]) -> None:
|
|
if self.sink is not None:
|
|
self.sink("dita_kernel_debug", row)
|
|
|
|
def record_transition(
|
|
self,
|
|
*,
|
|
transition: KernelTransition,
|
|
slot: TradeSlot,
|
|
event: Optional[VenueEvent] = None,
|
|
control: Optional[KernelControlSnapshot] = None,
|
|
) -> None:
|
|
self.record(_transition_row(transition=transition, slot=slot, event=event, control=control))
|
|
|
|
|
|
def _transition_row(
|
|
*,
|
|
transition: KernelTransition,
|
|
slot: TradeSlot,
|
|
event: Optional[VenueEvent],
|
|
control: Optional[KernelControlSnapshot],
|
|
) -> Dict[str, Any]:
|
|
return {
|
|
"ts": transition.timestamp.isoformat() if hasattr(transition.timestamp, "isoformat") else str(transition.timestamp),
|
|
"trade_id": transition.trade_id,
|
|
"slot_id": transition.slot_id,
|
|
"prev_state": transition.prev_state.value,
|
|
"next_state": transition.next_state.value,
|
|
"trigger": transition.trigger,
|
|
"intent_id": transition.intent_id,
|
|
"event_id": transition.event_id,
|
|
"control_mode": transition.control_mode,
|
|
"control_verbosity": transition.control_verbosity,
|
|
"slot_state": slot.to_dict(),
|
|
"event_payload": json_safe(event) if event is not None else {},
|
|
"control_snapshot": control.as_dict() if control is not None else {},
|
|
"slot_state_json": json_text(slot.to_dict()),
|
|
}
|