"""Bridge: canonical IR mapper for BeVault. This module introduces a minimal, canonical representation mapping BeVault primitives into a single, serializable IR (Objects/Morphisms/PlanDelta). It's intentionally lightweight and is designed to support plug-and-play adapters without vendor lock-in. The goal is to enable reproducible cross-adapter interactions and deterministic backtests later in the MVP. """ from __future__ import annotations from dataclasses import asdict, dataclass from typing import Any, Dict, Optional from .core import AuditLog, HedgeDelta, LocalArbProblem, PrivacyBudget, SharedSignals @dataclass class CanonicalIR: """A tiny, serializable canonical IR representation. This is not a full-fledged IR. It captures the core primitives in a structured, versioned form suitable for signing, auditing, and replay. """ version: str objects: Dict[str, Any] morphisms: Dict[str, Any] plan_delta: Optional[Dict[str, Any]] = None audit_log: Optional[Dict[str, Any]] = None privacy_budget: Optional[Dict[str, Any]] = None def to_dict(self) -> dict: return { "version": self.version, "objects": self.objects, "morphisms": self.morphisms, "plan_delta": self.plan_delta, "audit_log": self.audit_log, "privacy_budget": self.privacy_budget, } def to_canonical_ir( local: LocalArbProblem, signals: SharedSignals, delta: Optional[HedgeDelta] = None, audit_log: Optional[AuditLog] = None, privacy_budget: Optional[PrivacyBudget] = None, ) -> CanonicalIR: """Map BeVault MVP primitives to a CanonicalIR structure. This function provides a tiny, deterministic bridge from the MVP domain objects to a stable representation that can be serialized, signed, and replayed by adapters. """ objects = { "LocalArbProblem": asdict_safe(local), } morphisms = { "SharedSignals": asdict_safe(signals), } plan_delta = asdict_safe(delta) if delta is not None else None audit_payload = asdict_safe(audit_log) if audit_log is not None else None privacy_payload = asdict_safe(privacy_budget) if privacy_budget is not None else None return CanonicalIR( version="1.0", objects=objects, morphisms=morphisms, plan_delta=plan_delta, audit_log=audit_payload, privacy_budget=privacy_payload, ) def asdict_safe(obj) -> dict: """Safely convert a dataclass instance to a dict, handling None gracefully.""" if obj is None: return {} # Use dataclasses.asdict for clean conversion; if input isn't a dataclass, # fall back to its string representation to avoid raising. try: return asdict(obj) except Exception: return {"value": obj} def canonical_payload(ir: CanonicalIR) -> str: """Serialize a CanonicalIR in a stable form for signing and replay.""" import json return json.dumps(ir.to_dict(), sort_keys=True, separators=(",", ":")) __all__ = ["CanonicalIR", "to_canonical_ir", "canonical_payload"]