"""Chronicle event emission via stdout (Phase A) or HFL direct binding (Phase B). Phase A (current): Writes substrate_event JSON lines to stdout. bascule-filter-python reads and translates to ChronicleEntry records. Phase B (planned): When substrate-hfl-python (PyO3) is installed, routes events through HFL kernel module directly: chronicle_write(), accord_check(), session_get() Tier 4 → Tier 1/2 upgrade with zero application code changes. Detection: try import substrate_hfl; if available, use HFL path. All writes are non-blocking. All failures are silently logged. Chronicle emission MUST NOT affect application behavior. """ import json import logging import sys import time from typing import Optional logger = logging.getLogger(__name__) # TODO Phase B: HFL direct binding detection # When substrate-hfl-python (PyO3) is installed, use HFL path: # try: # import substrate_hfl # _hfl_available = True # except ImportError: # _hfl_available = False # Then in emit_event(): if _hfl_available, call substrate_hfl.chronicle_write() # instead of stdout print(). Zero application code changes required. _hfl_available = False LAYER_TCB = 0 LAYER_IDENTITY = 1 LAYER_GOVERNANCE = 2 LAYER_APPLICATION = 3 def emit_event( kind: str, layer: int, payload: dict, actor_did: str = "", accord_hash: str = "", shard_id: str = "", ) -> bool: """Emit a Chronicle event to stdout. Returns True if written, False on error (non-fatal). The substrate_event marker tells bascule-filter this is a Chronicle event. """ event = { "substrate_event": True, "kind": kind, "layer": layer, "actor_did": actor_did, "accord_hash": accord_hash, "shard_id": shard_id, "occurred_at": time.time_ns(), "payload": payload, } try: line = json.dumps(event, separators=(",", ":"), sort_keys=True) print(line, flush=True) return True except Exception as e: logger.warning("Chronicle emit failed (non-fatal): %s", e) return False class ShardEmitter: """Chronicle emitter scoped to a shard. Carries shard_id, actor_did, accord_hash.""" def __init__(self, context): self._ctx = context def emit(self, kind: str, layer: int, payload: dict, actor_did: Optional[str] = None) -> bool: return emit_event( kind=kind, layer=layer, payload=payload, actor_did=actor_did or self._ctx.actor_did, accord_hash=self._ctx.accord_hash, shard_id=self._ctx.shard_id, ) def request( self, method: str, path: str, status: int, duration_ms: float, actor_did: Optional[str] = None, view_name: Optional[str] = None, ) -> bool: return self.emit( "APP_DJANGO_REQUEST", LAYER_APPLICATION, {"method": method, "path": path, "status": status, "duration_ms": duration_ms, "view_name": view_name}, actor_did=actor_did, ) def signal(self, signal_name: str, model: str, instance_id: Optional[str] = None) -> bool: return self.emit( "APP_DJANGO_SIGNAL", LAYER_APPLICATION, {"signal": signal_name, "model": model, "instance_id": instance_id}, )