From 012a70f60f545a0b510f9dab782db9bfa3aa7552 Mon Sep 17 00:00:00 2001 From: agent-54de0bcc6a17828b Date: Fri, 24 Apr 2026 18:57:12 +0200 Subject: [PATCH] build(agent): semicolon#54de0b iteration --- AGENTS.md | 10 +- README.md | 33 ++++++- idea172_bevault_verifiable_best/__init__.py | 18 +++- .../adapters/__init__.py | 2 +- .../adapters/broker.py | 19 +++- .../adapters/price_feed.py | 42 ++++++-- idea172_bevault_verifiable_best/bridge.py | 45 +++++++-- idea172_bevault_verifiable_best/core.py | 66 +++++++++++-- idea172_bevault_verifiable_best/ledger.py | 87 +++++++++++++++++ idea172_bevault_verifiable_best/registry.py | 57 ++++++++--- idea172_bevault_verifiable_best/replay.py | 83 ++++++++++++++++ idea172_bevault_verifiable_best/signing.py | 22 +++-- pyproject.toml | 2 +- test.sh | 2 +- tests/test_basic.py | 23 ----- tests/test_bevault.py | 95 +++++++++++++++++++ 16 files changed, 521 insertions(+), 85 deletions(-) create mode 100644 idea172_bevault_verifiable_best/ledger.py create mode 100644 idea172_bevault_verifiable_best/replay.py delete mode 100644 tests/test_basic.py create mode 100644 tests/test_bevault.py diff --git a/AGENTS.md b/AGENTS.md index ea1ceb6..b5a967b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,11 +2,14 @@ Overview - BeVault is a modular backbone enforcing and proving Best Execution across multiple equity venues. -- The MVP focuses on a Graph-of-Contracts (GoC) registry, two starter adapters, and deterministic delta-sync with signed deltas. +- The current package includes a Graph-of-Contracts (GoC) registry, two starter adapters, a canonical IR bridge, a tamper-evident governance ledger, and deterministic delta-sync with signed deltas. Tech Stack (Production-Ready) - Language: Python 3.8+ - Core: lightweight dataclasses for LocalArbProblem, SharedSignals, HedgeDelta, AuditLog, PrivacyBudget +- Bridge: CanonicalIR and stable payload serialization for replay and signing +- Ledger: GovernanceLedger with hash chaining for auditability +- Replay: DeltaSyncEngine with version vectors and signed envelopes - Registry: GoCRegistry and GraphOfContracts with versioned adapters - Adapters: PriceFeedAdapter, BrokerAdapter (starter) - Crypto: HMAC-SHA256 based delta signing (pluggable key material) @@ -16,7 +19,10 @@ Tech Stack (Production-Ready) Project Structure - idea172_bevault_verifiable_best/ - __init__.py + - bridge.py - core.py + - ledger.py + - replay.py - signing.py - registry.py - adapters/ @@ -32,6 +38,6 @@ Project Structure - READY_TO_PUBLISH (empty when ready) How to contribute -- Start with the MVP: wire two adapters and ensure delta-signing and delta-sync are deterministic. +- Start by preserving determinism: keep signing, replay, and ledger hashes stable for identical inputs. - Expand the GoC registry with new contracts/adapters following the toy DSL seeds. - Ensure tests cover core contracts, registry operations, and adapter conformance checks. diff --git a/README.md b/README.md index ef3c7df..7512acd 100644 --- a/README.md +++ b/README.md @@ -1 +1,32 @@ -# BeVault: Verifiable Best-Execution Backbone (MVP) +# BeVault: Verifiable Best-Execution Backbone + +BeVault is a Python backbone for best-execution workflows across equity venues. It models local optimization problems, privacy-bounded market signals, signed hedge deltas, and tamper-evident governance logs in a small, testable package. + +## What It Provides + +- Canonical data contracts for `LocalArbProblem`, `SharedSignals`, `HedgeDelta`, `AuditLog`, and `PrivacyBudget` +- HMAC-SHA256 delta signing and verification +- A canonical IR bridge for replay and auditing +- A hash-chained governance ledger +- Deterministic delta-sync with version vectors +- A GoC registry plus starter price-feed and broker adapters + +## Modules + +- `core.py`: data contracts and toy DSL helpers +- `signing.py`: signing and verification +- `bridge.py`: canonical IR serialization +- `ledger.py`: append-only governance ledger +- `replay.py`: signed delta sync and replay +- `registry.py`: GoC registry and contract catalog +- `adapters/`: starter price-feed and broker adapters + +## Verification + +Run: + +```bash +bash test.sh +``` + +This installs the package, runs pytest, and builds a source distribution and wheel with `python3 -m build`. diff --git a/idea172_bevault_verifiable_best/__init__.py b/idea172_bevault_verifiable_best/__init__.py index 1cce80b..11a81d4 100644 --- a/idea172_bevault_verifiable_best/__init__.py +++ b/idea172_bevault_verifiable_best/__init__.py @@ -1,8 +1,12 @@ -"""BeVault Verifiable Best-Execution Core (package init).""" +"""BeVault verifiable best-execution backbone.""" from .core import LocalArbProblem, SharedSignals, HedgeDelta, AuditLog, PrivacyBudget +from .bridge import CanonicalIR, canonical_payload, to_canonical_ir +from .ledger import GovernanceLedger +from .adapters import PriceFeedAdapter, BrokerAdapter from .registry import GoCRegistry, GraphOfContracts -from .signing import sign_delta +from .replay import DeltaSyncEngine, SignedDeltaEnvelope, VersionVector +from .signing import sign_delta, verify_delta __all__ = [ "LocalArbProblem", @@ -10,7 +14,17 @@ __all__ = [ "HedgeDelta", "AuditLog", "PrivacyBudget", + "CanonicalIR", + "canonical_payload", + "to_canonical_ir", + "GovernanceLedger", + "PriceFeedAdapter", + "BrokerAdapter", "GoCRegistry", "GraphOfContracts", + "DeltaSyncEngine", + "SignedDeltaEnvelope", + "VersionVector", "sign_delta", + "verify_delta", ] diff --git a/idea172_bevault_verifiable_best/adapters/__init__.py b/idea172_bevault_verifiable_best/adapters/__init__.py index 42c5b2d..4de31cd 100644 --- a/idea172_bevault_verifiable_best/adapters/__init__.py +++ b/idea172_bevault_verifiable_best/adapters/__init__.py @@ -1,4 +1,4 @@ -"""Adapter base package for BeVault MVP (price feed, broker).""" +"""Adapter package for BeVault.""" from .price_feed import PriceFeedAdapter from .broker import BrokerAdapter diff --git a/idea172_bevault_verifiable_best/adapters/broker.py b/idea172_bevault_verifiable_best/adapters/broker.py index 1166a46..8294726 100644 --- a/idea172_bevault_verifiable_best/adapters/broker.py +++ b/idea172_bevault_verifiable_best/adapters/broker.py @@ -1,6 +1,8 @@ -"""Starter BrokerAdapter for BeVault MVP.""" +"""Broker adapter for BeVault.""" from __future__ import annotations -from dataclasses import dataclass + +from dataclasses import dataclass, field +from typing import List from ..core import HedgeDelta @@ -8,12 +10,19 @@ from ..core import HedgeDelta @dataclass class BrokerAdapter: name: str = "broker-v1" - version: str = "0.1" + version: str = "1.0" + sent_deltas: List[HedgeDelta] = field(default_factory=list) def send_delta(self, delta: HedgeDelta) -> bool: - # Placeholder: in real life this would push to venue/execution system - # Here we simply return True to indicate success + self.sent_deltas.append(delta) return True + def conformance_contract(self) -> dict: + return { + "name": self.name, + "version": self.version, + "accepts": ["HedgeDelta"], + } + __all__ = ["BrokerAdapter"] diff --git a/idea172_bevault_verifiable_best/adapters/price_feed.py b/idea172_bevault_verifiable_best/adapters/price_feed.py index c943158..b0c83ed 100644 --- a/idea172_bevault_verifiable_best/adapters/price_feed.py +++ b/idea172_bevault_verifiable_best/adapters/price_feed.py @@ -1,19 +1,45 @@ -"""Starter PriceFeedAdapter for BeVault MVP.""" +"""Price feed adapter for BeVault.""" from __future__ import annotations -from dataclasses import dataclass -from ..core import HedgeDelta +import hashlib +from dataclasses import dataclass +from typing import Optional + +from ..bridge import canonical_payload, to_canonical_ir +from ..core import HedgeDelta, LocalArbProblem, SharedSignals @dataclass class PriceFeedAdapter: name: str = "price-feed-v1" - version: str = "0.1" + version: str = "1.0" - def generate_signal(self, asset: str, price: float, latency_ms: float) -> HedgeDelta: - # Produce a minimal HedgeDelta as a placeholder signal payload - # In a real system this would produce SharedSignals -> local arb -> delta - return HedgeDelta(asset=asset, hedge_size=price, timestamp=latency_ms, policy_tag="price-feed") + def generate_signal(self, asset: str, price: float, latency_ms: float) -> SharedSignals: + return SharedSignals(asset=asset, price=price, latency_ms=latency_ms) + + def build_delta( + self, + local: LocalArbProblem, + signals: SharedSignals, + hedge_size: Optional[float] = None, + ) -> HedgeDelta: + hedge_size = signals.price if hedge_size is None else hedge_size + ir = to_canonical_ir(local, signals) + audit_ref = hashlib.sha256(canonical_payload(ir).encode("utf-8")).hexdigest() + return HedgeDelta( + asset=local.asset, + hedge_size=hedge_size, + timestamp=signals.timestamp, + policy_tag="price-feed", + audit_ref=audit_ref, + ) + + def conformance_contract(self) -> dict: + return { + "name": self.name, + "version": self.version, + "produces": ["SharedSignals", "HedgeDelta"], + } __all__ = ["PriceFeedAdapter"] diff --git a/idea172_bevault_verifiable_best/bridge.py b/idea172_bevault_verifiable_best/bridge.py index e717465..62dea6f 100644 --- a/idea172_bevault_verifiable_best/bridge.py +++ b/idea172_bevault_verifiable_best/bridge.py @@ -1,4 +1,4 @@ -"""Bridge: EnergiBridge-inspired IR mapper for BeVault MVP. +"""Bridge: canonical IR mapper for BeVault. This module introduces a minimal, canonical representation mapping BeVault primitives into a single, serializable IR (Objects/Morphisms/PlanDelta). @@ -9,9 +9,9 @@ interactions and deterministic backtests later in the MVP. from __future__ import annotations from dataclasses import asdict, dataclass -from typing import Optional +from typing import Any, Dict, Optional -from .core import HedgeDelta, LocalArbProblem, SharedSignals +from .core import AuditLog, HedgeDelta, LocalArbProblem, PrivacyBudget, SharedSignals @dataclass @@ -23,9 +23,11 @@ class CanonicalIR: """ version: str - objects: dict - morphisms: dict - plan_delta: Optional[dict] = None + 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 { @@ -33,10 +35,18 @@ class CanonicalIR: "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) -> CanonicalIR: +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 @@ -50,8 +60,17 @@ def to_canonical_ir(local: LocalArbProblem, signals: SharedSignals, delta: Optio "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="0.1", objects=objects, morphisms=morphisms, plan_delta=plan_delta) + 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: @@ -66,4 +85,12 @@ def asdict_safe(obj) -> dict: return {"value": obj} -__all__ = ["CanonicalIR", "to_canonical_ir"] +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"] diff --git a/idea172_bevault_verifiable_best/core.py b/idea172_bevault_verifiable_best/core.py index cfc4d1d..79214ff 100644 --- a/idea172_bevault_verifiable_best/core.py +++ b/idea172_bevault_verifiable_best/core.py @@ -1,8 +1,10 @@ -"""Core data contracts for BeVault MVP (toy DSL seeds).""" +"""Core data contracts for BeVault.""" from __future__ import annotations -from dataclasses import dataclass, field -from typing import List, Optional + +import json import time +from dataclasses import asdict, dataclass, field +from typing import Any, Dict, List, Optional @dataclass @@ -10,7 +12,16 @@ class LocalArbProblem: asset: str objective: str # e.g., 'min_cost', 'maximize_profit' risk_budget: float - constraints: dict = field(default_factory=dict) + constraints: Dict[str, Any] = field(default_factory=dict) + + def validate(self) -> None: + if not self.asset: + raise ValueError("asset is required") + if self.risk_budget < 0: + raise ValueError("risk_budget must be non-negative") + + def to_dict(self) -> Dict[str, Any]: + return asdict(self) @dataclass @@ -19,7 +30,14 @@ class SharedSignals: price: float latency_ms: float timestamp: float = field(default_factory=lambda: time.time()) - metadata: dict = field(default_factory=dict) + metadata: Dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> Dict[str, Any]: + return asdict(self) + + def is_fresh(self, max_age_seconds: float, now: Optional[float] = None) -> bool: + now = time.time() if now is None else now + return (now - self.timestamp) <= max_age_seconds @dataclass @@ -29,13 +47,37 @@ class HedgeDelta: timestamp: float = field(default_factory=lambda: time.time()) policy_tag: str = "default" signature: Optional[str] = None + version_vector: Dict[str, int] = field(default_factory=dict) + audit_ref: Optional[str] = None + + def to_signable_dict(self) -> Dict[str, Any]: + return { + "asset": self.asset, + "hedge_size": self.hedge_size, + "timestamp": self.timestamp, + "policy_tag": self.policy_tag, + "version_vector": self.version_vector, + "audit_ref": self.audit_ref, + } + + def to_json(self) -> str: + return json.dumps(self.to_signable_dict(), sort_keys=True, separators=(",", ":")) @dataclass class AuditLog: - events: List[str] = field(default_factory=list) + events: List[Dict[str, Any]] = field(default_factory=list) entry_ts: float = field(default_factory=lambda: time.time()) + def record(self, event_type: str, payload: Optional[Dict[str, Any]] = None) -> None: + self.events.append( + { + "event_type": event_type, + "payload": payload or {}, + "timestamp": time.time(), + } + ) + @dataclass class PrivacyBudget: @@ -43,6 +85,14 @@ class PrivacyBudget: remaining: float reset_epoch: float = field(default_factory=lambda: time.time()) + def consume(self, amount: float) -> bool: + if amount < 0: + raise ValueError("amount must be non-negative") + if amount > self.remaining: + return False + self.remaining -= amount + return True + __all__ = [ "LocalArbProblem", @@ -65,8 +115,6 @@ def generate_toy_seed(asset: str = "AAPL", price: float = 100.0): This helper is intentionally lightweight and is meant for local testing and documentation purposes within the MVP boundaries. """ - from time import time - local = LocalArbProblem( asset=asset, objective="min_cost", @@ -78,7 +126,7 @@ def generate_toy_seed(asset: str = "AAPL", price: float = 100.0): asset=asset, price=price, latency_ms=0.5, - timestamp=time(), + timestamp=time.time(), metadata={"toy_seed": True}, ) diff --git a/idea172_bevault_verifiable_best/ledger.py b/idea172_bevault_verifiable_best/ledger.py new file mode 100644 index 0000000..0c16c9a --- /dev/null +++ b/idea172_bevault_verifiable_best/ledger.py @@ -0,0 +1,87 @@ +"""Append-only governance ledger for BeVault.""" +from __future__ import annotations + +import hashlib +import json +import time +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional + + +def _stable_hash(payload: Dict[str, Any]) -> str: + encoded = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + return hashlib.sha256(encoded).hexdigest() + + +@dataclass +class LedgerEntry: + index: int + event_type: str + payload: Dict[str, Any] + timestamp: float + previous_hash: str + entry_hash: str + + +@dataclass +class GovernanceLedger: + """Tamper-evident event ledger with hash chaining.""" + + entries: List[LedgerEntry] = field(default_factory=list) + + def append(self, event_type: str, payload: Optional[Dict[str, Any]] = None, timestamp: Optional[float] = None) -> LedgerEntry: + timestamp = time.time() if timestamp is None else timestamp + payload = payload or {} + previous_hash = self.entries[-1].entry_hash if self.entries else "" + entry = LedgerEntry( + index=len(self.entries), + event_type=event_type, + payload=payload, + timestamp=timestamp, + previous_hash=previous_hash, + entry_hash="", + ) + entry.entry_hash = _stable_hash( + { + "index": entry.index, + "event_type": entry.event_type, + "payload": entry.payload, + "timestamp": entry.timestamp, + "previous_hash": entry.previous_hash, + } + ) + self.entries.append(entry) + return entry + + def verify(self) -> bool: + previous_hash = "" + for index, entry in enumerate(self.entries): + if entry.index != index: + return False + if entry.previous_hash != previous_hash: + return False + expected_hash = _stable_hash( + { + "index": entry.index, + "event_type": entry.event_type, + "payload": entry.payload, + "timestamp": entry.timestamp, + "previous_hash": entry.previous_hash, + } + ) + if entry.entry_hash != expected_hash: + return False + previous_hash = entry.entry_hash + return True + + def anchor(self) -> str: + return self.entries[-1].entry_hash if self.entries else "" + + def to_dict(self) -> Dict[str, Any]: + return { + "entries": [entry.__dict__ for entry in self.entries], + "anchor": self.anchor(), + } + + +__all__ = ["GovernanceLedger", "LedgerEntry"] diff --git a/idea172_bevault_verifiable_best/registry.py b/idea172_bevault_verifiable_best/registry.py index f891b78..e4809de 100644 --- a/idea172_bevault_verifiable_best/registry.py +++ b/idea172_bevault_verifiable_best/registry.py @@ -1,21 +1,19 @@ -"""Graph-of-Contracts (GoC) registry and minimal adapters registry. - -This is a lightweight, production-oriented scaffold for the MVP. -""" +"""Graph-of-Contracts (GoC) registry.""" from __future__ import annotations from dataclasses import dataclass, field -from typing import Dict, Optional +from typing import Any, Dict, List, Optional -from .core import HedgeDelta -# Import adapters to ensure their classes are discoverable when registry loads -from .adapters.price_feed import PriceFeedAdapter from .adapters.broker import BrokerAdapter +from .adapters.price_feed import PriceFeedAdapter +from .core import HedgeDelta @dataclass class GoCRegistry: - """Registry for adapters and data contracts (toy GoC).""" + """Registry for adapters and data contracts.""" adapters: Dict[str, object] = field(default_factory=dict) + contracts: Dict[str, str] = field(default_factory=dict) + metadata: Dict[str, Any] = field(default_factory=dict) def __post_init__(self): # Initialize to an empty mapping if not provided @@ -28,20 +26,49 @@ class GoCRegistry: def get_adapter(self, name: str) -> Optional[object]: return self.adapters.get(name) - -class GraphOfContracts: - """Lightweight catalog for contract versions and replay metadata.""" - def __init__(self) -> None: - self.contracts: Dict[str, str] = {} - def register_contract(self, name: str, version: str) -> None: self.contracts[name] = version def get_contract(self, name: str) -> Optional[str]: return self.contracts.get(name) + def conformance_report(self, adapter_name: str) -> Dict[str, Any]: + adapter = self.get_adapter(adapter_name) + if adapter is None: + return {"adapter": adapter_name, "registered": False, "conforms": False} + + required_methods: List[str] = [] + if isinstance(adapter, PriceFeedAdapter): + required_methods = ["generate_signal", "conformance_contract"] + elif isinstance(adapter, BrokerAdapter): + required_methods = ["send_delta", "conformance_contract"] + + missing = [method for method in required_methods if not hasattr(adapter, method)] + return { + "adapter": adapter_name, + "registered": True, + "conforms": not missing, + "missing": missing, + "contract": getattr(adapter, "conformance_contract", lambda: None)(), + } + + +class GraphOfContracts: + """Catalog for contract versions and replay metadata.""" + def __init__(self) -> None: + self.contracts: Dict[str, Dict[str, Any]] = {} + + def register_contract(self, name: str, version: str, schema: Optional[Dict[str, Any]] = None) -> None: + self.contracts[name] = {"version": version, "schema": schema or {}} + + def get_contract(self, name: str) -> Optional[Dict[str, Any]]: + return self.contracts.get(name) + def list_contracts(self): return list(self.contracts.items()) + def to_dict(self) -> Dict[str, Dict[str, Any]]: + return dict(self.contracts) + __all__ = ["GoCRegistry", "GraphOfContracts"] diff --git a/idea172_bevault_verifiable_best/replay.py b/idea172_bevault_verifiable_best/replay.py new file mode 100644 index 0000000..450c86c --- /dev/null +++ b/idea172_bevault_verifiable_best/replay.py @@ -0,0 +1,83 @@ +"""Deterministic delta-sync and offline replay utilities.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, Iterable, List, Optional, Tuple + +from .core import HedgeDelta +from .signing import sign_delta, verify_delta + + +@dataclass +class VersionVector: + clocks: Dict[str, int] = field(default_factory=dict) + + def bump(self, actor: str) -> None: + self.clocks[actor] = self.clocks.get(actor, 0) + 1 + + def merge(self, other: "VersionVector") -> None: + for actor, counter in other.clocks.items(): + self.clocks[actor] = max(self.clocks.get(actor, 0), counter) + + def copy(self) -> "VersionVector": + return VersionVector(clocks=dict(self.clocks)) + + def to_dict(self) -> Dict[str, int]: + return dict(self.clocks) + + +@dataclass +class SignedDeltaEnvelope: + delta: HedgeDelta + actor: str + signature: str + version_vector: VersionVector + + def to_dict(self) -> Dict[str, Any]: + return { + "delta": self.delta.to_signable_dict(), + "actor": self.actor, + "signature": self.signature, + "version_vector": self.version_vector.to_dict(), + } + + +class DeltaSyncEngine: + """Records and replays signed deltas deterministically.""" + + def __init__(self, signing_key: bytes) -> None: + self.signing_key = signing_key + self.version_vector = VersionVector() + self.envelopes: List[SignedDeltaEnvelope] = [] + + def record(self, delta: HedgeDelta, actor: str) -> SignedDeltaEnvelope: + self.version_vector.bump(actor) + delta.version_vector = self.version_vector.to_dict() + delta.signature = sign_delta(delta, self.signing_key) + envelope = SignedDeltaEnvelope(delta=delta, actor=actor, signature=delta.signature, version_vector=self.version_vector.copy()) + self.envelopes.append(envelope) + return envelope + + def ingest(self, envelope: SignedDeltaEnvelope) -> bool: + if not verify_delta(envelope.delta, self.signing_key, envelope.signature): + return False + self.version_vector.merge(envelope.version_vector) + self.envelopes.append(envelope) + return True + + def replay(self) -> List[HedgeDelta]: + ordered = sorted( + self.envelopes, + key=lambda item: ( + item.delta.timestamp, + item.actor, + item.signature, + ), + ) + return [envelope.delta for envelope in ordered] + + def history(self) -> List[Dict[str, Any]]: + return [envelope.to_dict() for envelope in self.envelopes] + + +__all__ = ["DeltaSyncEngine", "SignedDeltaEnvelope", "VersionVector"] diff --git a/idea172_bevault_verifiable_best/signing.py b/idea172_bevault_verifiable_best/signing.py index 64209bf..6cc8f4c 100644 --- a/idea172_bevault_verifiable_best/signing.py +++ b/idea172_bevault_verifiable_best/signing.py @@ -1,18 +1,24 @@ -"""Delta signing utilities for BeVault MVP.""" +"""Delta signing utilities for BeVault.""" from __future__ import annotations -import hmac + import hashlib +import hmac +import json + from .core import HedgeDelta def sign_delta(delta: HedgeDelta, key: bytes) -> str: - """Sign a HedgeDelta deterministically using HMAC-SHA256. + """Sign a HedgeDelta deterministically using HMAC-SHA256.""" - The signature is derived from the delta contents (asset, hedge_size, timestamp, policy_tag). - The resulting hex digest is stable for identical inputs and keys. - """ - payload = f"{delta.asset}:{delta.hedge_size}:{delta.timestamp}:{delta.policy_tag}" + payload = json.dumps(delta.to_signable_dict(), sort_keys=True, separators=(",", ":")) return hmac.new(key, payload.encode("utf-8"), hashlib.sha256).hexdigest() -__all__ = ["sign_delta"] +def verify_delta(delta: HedgeDelta, key: bytes, signature: str) -> bool: + """Verify a delta signature in a deterministic way.""" + + return hmac.compare_digest(sign_delta(delta, key), signature) + + +__all__ = ["sign_delta", "verify_delta"] diff --git a/pyproject.toml b/pyproject.toml index d73a492..1332395 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "0.1.0" description = "BeVault: verifiable best-execution backbone prototype with Cross-Venue GoC adapters" readme = "README.md" requires-python = ">=3.8" -license = {text = "MIT"} +license = "MIT" authors = [ { name = "OpenCode Team" } ] [tool.setuptools.packages.find] diff --git a/test.sh b/test.sh index 3f6fbc5..7bc34ce 100644 --- a/test.sh +++ b/test.sh @@ -4,7 +4,7 @@ set -euo pipefail echo "[BeVault] Running tests and packaging sanity checks..." # Ensure pip tooling is up to date -python3 -m pip install --upgrade pip setuptools wheel +python3 -m pip install --upgrade pip setuptools wheel build # Install the package in editable mode for testing imports python3 -m pip install -e . diff --git a/tests/test_basic.py b/tests/test_basic.py deleted file mode 100644 index c37b2f4..0000000 --- a/tests/test_basic.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Basic tests for BeVault MVP components.""" -import pytest - -from idea172_bevault_verifiable_best.core import HedgeDelta -from idea172_bevault_verifiable_best.signing import sign_delta -from idea172_bevault_verifiable_best.registry import GoCRegistry -from idea172_bevault_verifiable_best.adapters.price_feed import PriceFeedAdapter - - -def test_sign_delta_deterministic(): - delta = HedgeDelta(asset="AAPL", hedge_size=1.0, timestamp=1.0, policy_tag="test") - key = b"secret-key" - sig1 = sign_delta(delta, key) - sig2 = sign_delta(delta, key) - assert isinstance(sig1, str) and isinstance(sig2, str) - assert sig1 == sig2 - - -def test_registry_adapters_can_register_and_retrieve(): - reg = GoCRegistry() - adapter = PriceFeedAdapter() - reg.register_adapter(adapter) - assert reg.get_adapter(adapter.name) is adapter diff --git a/tests/test_bevault.py b/tests/test_bevault.py new file mode 100644 index 0000000..53ee126 --- /dev/null +++ b/tests/test_bevault.py @@ -0,0 +1,95 @@ +"""Tests for BeVault core behavior.""" + +from idea172_bevault_verifiable_best import ( + AuditLog, + DeltaSyncEngine, + GovernanceLedger, + GoCRegistry, + HedgeDelta, + LocalArbProblem, + PrivacyBudget, + PriceFeedAdapter, + BrokerAdapter, + SharedSignals, + canonical_payload, + sign_delta, + to_canonical_ir, + verify_delta, +) + + +def test_sign_and_verify_delta_deterministically(): + delta = HedgeDelta(asset="AAPL", hedge_size=1.0, timestamp=1.0, policy_tag="test") + key = b"secret-key" + sig1 = sign_delta(delta, key) + sig2 = sign_delta(delta, key) + assert sig1 == sig2 + assert verify_delta(delta, key, sig1) + + +def test_registry_reports_adapter_conformance(): + reg = GoCRegistry() + price = PriceFeedAdapter() + broker = BrokerAdapter() + reg.register_adapter(price) + reg.register_adapter(broker) + reg.register_contract("LocalArbProblem", "1.0") + + report = reg.conformance_report(price.name) + assert report["conforms"] is True + assert report["registered"] is True + assert reg.get_adapter(broker.name) is broker + assert reg.get_contract("LocalArbProblem") == "1.0" + + +def test_canonical_ir_includes_governance_metadata(): + local = LocalArbProblem(asset="MSFT", objective="min_cost", risk_budget=10.0) + signals = SharedSignals(asset="MSFT", price=250.0, latency_ms=2.0, timestamp=1.0) + audit = AuditLog() + audit.record("approve", {"desk": "equities"}) + budget = PrivacyBudget(asset="MSFT", remaining=5.0) + + ir = to_canonical_ir(local, signals, audit_log=audit, privacy_budget=budget) + payload = canonical_payload(ir) + + assert ir.version == "1.0" + assert "audit_log" in payload + assert "privacy_budget" in payload + + +def test_governance_ledger_hash_chain_verifies(): + ledger = GovernanceLedger() + first = ledger.append("approve", {"ticket": "abc"}, timestamp=1.0) + second = ledger.append("execute", {"ticket": "abc"}, timestamp=2.0) + + assert ledger.verify() is True + assert ledger.anchor() == second.entry_hash + assert first.previous_hash == "" + + ledger.entries[0].payload["ticket"] = "mutated" + assert ledger.verify() is False + + +def test_delta_sync_replays_deterministically(): + engine = DeltaSyncEngine(signing_key=b"sync-key") + delta_one = HedgeDelta(asset="AAPL", hedge_size=1.0, timestamp=1.0, policy_tag="a") + delta_two = HedgeDelta(asset="AAPL", hedge_size=2.0, timestamp=0.5, policy_tag="b") + + engine.record(delta_one, actor="desk-a") + engine.record(delta_two, actor="desk-b") + + replayed = engine.replay() + assert [delta.policy_tag for delta in replayed] == ["b", "a"] + assert replayed[0].signature is not None + + +def test_price_feed_builds_delta_and_broker_sends_it(): + adapter = PriceFeedAdapter() + local = LocalArbProblem(asset="AAPL", objective="min_cost", risk_budget=10.0) + signals = adapter.generate_signal(asset="AAPL", price=101.5, latency_ms=1.2) + delta = adapter.build_delta(local, signals) + + broker = BrokerAdapter() + assert broker.send_delta(delta) is True + assert broker.sent_deltas[-1] is delta + assert delta.audit_ref is not None