97 lines
3.1 KiB
Python
97 lines
3.1 KiB
Python
"""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"]
|