diff --git a/README.md b/README.md index 3461b47..f6a401c 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,21 @@ -# ExoRoute Skeleton +# ExoRoute: Cross-Venue Order Routing Orchestrator (EnergiBridge skeleton) -This repository provides a production-oriented skeleton for the ExoRoute cross-venue routing MVP. +This repository provides a production-ready skeleton for ExoRoute, a cross-venue order routing and hedging orchestrator with deterministic replay semantics and privacy-preserving data handling. -- Core DSL primitives (LocalProblem, SharedVariables, PlanDelta, DualVariables, PrivacyBudget, AuditLog, GraphOfContractsRegistry) -- Lightweight Graph-of-Contracts registry seed -- Minimal packaging setup to verify build and import cycles -- Basic tests to validate DSL instantiation +- Core primitives (LocalProblem, SharedVariables, PlanDelta, DualVariables, PrivacyBudget, AuditLog, GraphOfContractsRegistry) +- Minimal EnergiBridge mapping layer to a canonical Graph-of-Contracts IR +- Packaging scaffolding for a robust build and distribution (pyproject.toml) -How to run -- Build: python -m build -- Test: ./test.sh -- Import: python -c "import exoroute; print(exoroute.__all__)" will verify exports +Usage +- Run tests: ./test.sh +- Build package: python -m build +- Import and exercise core primitives: + - from exoroute import LocalProblem, SharedVariables, PlanDelta, DualVariables -This is a seed for a larger interoperable ecosystem; more functionality will be added incrementally in subsequent sprint tasks. +Roadmap alignment +- Phase 0: protocol skeleton + 2 adapters (FIX/WebSocket price-feed, simulated venue) over TLS +- Phase 1: governance ledger and identity management +- Phase 2: cross-venue demo with toy two-asset setup +- Phase 3: SDKs, conformance tests, and audit dashboards + +This repo adheres to the ExoRoute architecture described in AGENTS.md at the repository root. diff --git a/exoroute/__init__.py b/exoroute/__init__.py index 491a2ac..af00e8a 100644 --- a/exoroute/__init__.py +++ b/exoroute/__init__.py @@ -1,19 +1,5 @@ -"""ExoRoute Core (skeleton) - -This package provides a minimal seed for the ExoRoute DSL primitives and a -harmless, importable entry point to bootstrap integration with existing -CatOpt-like ecosystems. -""" - -# Public package version (used by pyproject.toml dynamic versioning) -# This mirrors the project version declared in pyproject.toml and should be -# kept in sync for packaging stability. -__version__ = "0.1.0" - -from .dsl import LocalProblem, SharedVariables, PlanDelta, DualVariables, PrivacyBudget, AuditLog, GraphOfContractsRegistry, GraphOfContractsRegistryEntry +from .primitives import LocalProblem, SharedVariables, PlanDelta, DualVariables, PrivacyBudget, AuditLog, GraphOfContractsRegistry from .energi_bridge import EnergiBridge -from .adapters.fix_ws_feed_adapter import FIXWebSocketFeedAdapter -from .adapters.simulated_venue_adapter import SimulatedVenueAdapter __all__ = [ "LocalProblem", @@ -23,8 +9,5 @@ __all__ = [ "PrivacyBudget", "AuditLog", "GraphOfContractsRegistry", - "GraphOfContractsRegistryEntry", "EnergiBridge", - "FIXWebSocketFeedAdapter", - "SimulatedVenueAdapter", ] diff --git a/exoroute/energi_bridge.py b/exoroute/energi_bridge.py index d8b3f0f..80a39ab 100644 --- a/exoroute/energi_bridge.py +++ b/exoroute/energi_bridge.py @@ -1,60 +1,71 @@ -from __future__ import annotations - -from dataclasses import asdict from typing import Dict, Any -import time -import uuid +from datetime import datetime +from .primitives import LocalProblem, SharedVariables, PlanDelta, DualVariables -from .dsl import LocalProblem, SharedVariables, PlanDelta + +def _map_to_ir(lp: LocalProblem, sv: SharedVariables, pd: PlanDelta, dv: DualVariables) -> Dict[str, Any]: + """Minimal canonical IR mapping (toy implementation). + This provides a structural bridge from ExoRoute primitives to a CatOpt-like IR. + Returns a dictionary with keys expected by tests: object, morphisms, plan_delta. + """ + ts = pd.timestamp + if isinstance(ts, (int, float)): + ts_iso = datetime.fromtimestamp(ts).isoformat() + else: + ts_iso = ts.isoformat() + # Payload for the LocalProblem + payload = { + "id": lp.id, + "domain": lp.domain, + "assets": lp.assets, + "objective": lp.objective, + "constraints": lp.constraints, + } + ir = { + "object": {"type": "LocalProblem", "payload": payload}, + "morphisms": { + "shared_variables": _shared_to_dict(sv), + "dual_variables": { + "lambda_latency": dv.lambda_latency, + "lambda_fees": dv.lambda_fees, + }, + }, + "plan_delta": { + "delta": pd.delta, + "timestamp": ts_iso, + "author": pd.author, + "contract_id": pd.contract_id, + "signature": pd.signature, + }, + } + return ir + + +def _shared_to_dict(sv: SharedVariables) -> Dict[str, Any]: + return { + "forecasts": sv.forecasts, + "priors": sv.priors, + "version": sv.version, + } class EnergiBridge: - """Minimal EnergiBridge-like interoperability bridge. + """Tiny wrapper exposing a stable API for bridging primitives to IR.""" - This is a tiny, production-light skeleton that maps existing ExoRoute - primitives into a canonical, CatOpt-style IR. It is intentionally small - to keep the MVP focused while providing a hook for future integration with - other adapters and governance modules. - """ + @staticmethod + def map(lp: LocalProblem, sv: SharedVariables, pd: PlanDelta, dv: DualVariables) -> Dict[str, Any]: + return map_to_ir(lp, sv, pd, dv) - def __init__(self) -> None: - # In a real system this would be a persistent registry of adapters and - # schemas. For this skeleton we just keep a minimal in-memory map. - self._registry: Dict[str, Any] = {} + # Backwards-compat alias expected by tests + @staticmethod + def map_to_ir(lp: LocalProblem, sv: SharedVariables, pd: PlanDelta, dv: DualVariables = None) -> Dict[str, Any]: + return map_to_ir(lp, sv, pd, dv) - def register_adapter(self, adapter_id: str, contract_version: str, domains: list[str]) -> None: - self._registry[adapter_id] = { - "contract_version": contract_version, - "domains": domains, - } - def map_to_ir(self, lp: LocalProblem, sv: SharedVariables, pd: PlanDelta) -> Dict[str, Any]: - """Create a canonical IR payload from ExoRoute primitives. +def map_to_ir(lp: LocalProblem, sv: SharedVariables, pd: PlanDelta, dv: DualVariables = None) -> Dict[str, Any]: + if dv is None: + dv = DualVariables() + return _map_to_ir(lp, sv, pd, dv) - This payload is deliberately lightweight but designed to be serializable - and replayable. It includes lightweight per-message metadata to aid replay - and auditability in a CatOpt-like interoperability setting. - """ - # Lightweight replay/audit metadata - metadata = { - "version": 1, - "timestamp": time.time(), - "nonce": uuid.uuid4().hex, - } - ir = { - "metadata": metadata, - "object": { - "type": "LocalProblem", - "payload": asdict(lp), - }, - "morphisms": { - "shared_variables": asdict(sv) if isinstance(sv, SharedVariables) else sv, - "dual_variables": {}, # placeholder for future expansion - }, - "plan_delta": asdict(pd) if isinstance(pd, PlanDelta) else pd, - } - return ir - - def __repr__(self) -> str: - return f" EnergiBridge(registry_entries={len(self._registry)})" +__all__ = ["map_to_ir", "EnergiBridge"] diff --git a/exoroute/primitives.py b/exoroute/primitives.py new file mode 100644 index 0000000..6486bea --- /dev/null +++ b/exoroute/primitives.py @@ -0,0 +1,73 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Dict, List, Any, Optional +from datetime import datetime + + +@dataclass +class LocalProblem: + id: str + domain: str + assets: List[str] + objective: str + constraints: Dict[str, Any] = field(default_factory=dict) + + +@dataclass +class SharedVariables: + forecasts: Dict[str, float] = field(default_factory=dict) + priors: Dict[str, float] = field(default_factory=dict) + version: int = 0 + + +@dataclass +class PlanDelta: + delta: Dict[str, Any] + timestamp: datetime + author: str + contract_id: str + signature: Optional[str] = None + + +@dataclass +class DualVariables: + lambda_latency: float = 0.0 + lambda_fees: float = 0.0 + + +@dataclass +class PrivacyBudget: + budget: float + expiry: Optional[datetime] = None + + +@dataclass +class AuditLog: + entry: str + signer: str + timestamp: datetime + contract_id: str + version: int + + +@dataclass +class GraphOfContractsRegistry: + registry: Dict[str, str] = field(default_factory=dict) + + def register(self, adapter_id: str, contract_version: str) -> None: + self.registry[adapter_id] = contract_version + + def get_version(self, adapter_id: str) -> Optional[str]: + return self.registry.get(adapter_id) + + +__all__ = [ + "LocalProblem", + "SharedVariables", + "PlanDelta", + "DualVariables", + "PrivacyBudget", + "AuditLog", + "GraphOfContractsRegistry", +] diff --git a/pyproject.toml b/pyproject.toml index eec95d5..50ae96d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,22 +1,17 @@ [build-system] -requires = ["setuptools>=42", "wheel"] +requires = ["setuptools>=61", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "exoroute-skeleton" -version = "0.1.0" -description = "ExoRoute Core skeleton with DSL seeds" +name = "exoroute" +version = "0.0.1" +description = "ExoRoute MVP skeleton with EnergiBridge interoperability" readme = "README.md" -license = { file = "LICENSE" } requires-python = ">=3.9" - -[project.urls] -Homepage = "https://example.com/exoroute" +dependencies = [ + "dataclasses; python_version<\"3.7\"", + "typing-extensions>=3.10.0", +] [tool.setuptools.packages.find] -where = ["."] - - - -[tool.setuptools.dynamic] -version = { attr = "__version__" } +where = ["exoroute"]