diff --git a/README.md b/README.md index 160face..1876e22 100644 --- a/README.md +++ b/README.md @@ -8,10 +8,12 @@ Key Primitives - PlanDelta: incremental routing/size/timing decisions with metadata - Attestation/AuditLog: cryptographic attestations and an append-only log - GraphOfContracts: registry for adapters and data-contract schemas +- compute_delta / compute_plan_delta: deterministic, replayable routing plans for a task/context pair How to use - Install dependencies and run tests via the provided test script (test.sh). -- The core models are defined under exprove.contracts and can be composed by a solver. +- The core models are defined under `exprove.contracts`; the reference planner lives in `exprove.engine`. +- All public dataclasses expose `to_dict()` and/or `to_json()` helpers for audit trails and export. Development notes - This repo focuses on a production-ready, minimal core that can be extended to a full edge-native solver and delta-sync pipeline. diff --git a/exprove/__init__.py b/exprove/__init__.py index 37ea5fe..a231265 100644 --- a/exprove/__init__.py +++ b/exprove/__init__.py @@ -13,6 +13,7 @@ from .contracts import ( Attestation, AuditLog, GraphOfContracts, + compute_delta, ) __all__ = [ @@ -22,4 +23,5 @@ __all__ = [ "Attestation", "AuditLog", "GraphOfContracts", + "compute_delta", ] diff --git a/exprove/contracts.py b/exprove/contracts.py index 42e95d6..bbd2714 100644 --- a/exprove/contracts.py +++ b/exprove/contracts.py @@ -1,7 +1,12 @@ from __future__ import annotations -from dataclasses import dataclass, asdict -from typing import Dict, Any, List +import json +from dataclasses import dataclass, asdict, field +from typing import Dict, Any, List, Optional + + +def to_json(obj: Any) -> str: + return json.dumps(obj, default=lambda o: o.__dict__, sort_keys=True) @dataclass @@ -11,7 +16,10 @@ class LocalExecutionTask: instrument: str venue: str objective: str - constraints: Dict[str, Any] + constraints: Dict[str, Any] = field(default_factory=dict) + + def to_json(self) -> str: + return to_json(asdict(self)) def to_dict(self) -> Dict[str, Any]: return asdict(self) @@ -24,6 +32,9 @@ class SharedMarketContext: version: int contract_id: str + def to_json(self) -> str: + return to_json(asdict(self)) + def to_dict(self) -> Dict[str, Any]: return asdict(self) @@ -35,7 +46,10 @@ class PlanDelta: timestamp: float author: str contract_id: str - privacy_budget: float + privacy_budget: Optional[float] = None + + def to_json(self) -> str: + return to_json(asdict(self)) def to_dict(self) -> Dict[str, Any]: return asdict(self) @@ -50,6 +64,9 @@ class Attestation: contract_id: str version: int + def to_json(self) -> str: + return to_json(asdict(self)) + def to_dict(self) -> Dict[str, Any]: return asdict(self) @@ -57,7 +74,13 @@ class Attestation: @dataclass class AuditLog: """Append-only log of attestations and approvals.""" - entries: List[Attestation] + entries: List[Attestation] = field(default_factory=list) + + def add(self, attestation: Attestation) -> None: + self.entries.append(attestation) + + def to_json(self) -> str: + return to_json([asdict(entry) for entry in self.entries]) def to_dict(self) -> Dict[str, Any]: return {"entries": [e.to_dict() for e in self.entries]} @@ -67,17 +90,19 @@ class AuditLog: class GraphOfContracts: """Registry for adapters and data-contract schemas.""" registry: Dict[str, Any] + version: int = 1 + adapters: Optional[Dict[str, Any]] = None + + def __post_init__(self) -> None: + if self.adapters is None: + self.adapters = self.registry def to_dict(self) -> Dict[str, Any]: - return {"registry": self.registry} + return {"registry": self.registry, "version": self.version, "adapters": self.adapters} def compute_delta(task: LocalExecutionTask, ctx: SharedMarketContext) -> PlanDelta: - """Deterministic, minimal PlanDelta from inputs. - - This is intentionally deterministic (timestamp=0.0) for replayability in tests - and backtesting scenarios. Real deployments can replace this with a richer solver. - """ + """Deterministic, minimal PlanDelta from inputs.""" delta = { "action": "hold", "reason": "base-case", diff --git a/exprove/engine.py b/exprove/engine.py new file mode 100644 index 0000000..04b9be6 --- /dev/null +++ b/exprove/engine.py @@ -0,0 +1,29 @@ +"""Deterministic execution planner for ExProve.""" + +from __future__ import annotations + +import hashlib +from typing import Any, Dict + +from .contracts import LocalExecutionTask, SharedMarketContext, PlanDelta + + +def _deterministic_delta(task: LocalExecutionTask, ctx: SharedMarketContext) -> Dict[str, Any]: + seed_input = f"{task.task_id}:{task.instrument}:{task.venue}:{ctx.version}:{ctx.contract_id}" + digest = hashlib.sha256(seed_input.encode("utf-8")).hexdigest() + return { + "route": [task.venue], + "size_adjustment": int(digest[:6], 16) % 1000, + "timestamp": 0.0, + "hash_seed": digest, + } + + +def compute_plan_delta(task: LocalExecutionTask, ctx: SharedMarketContext, *, author: str) -> PlanDelta: + return PlanDelta( + delta=_deterministic_delta(task, ctx), + timestamp=0.0, + author=author, + contract_id=ctx.contract_id, + privacy_budget=None, + ) diff --git a/pyproject.toml b/pyproject.toml index 9aca510..f95fa53 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -8,7 +8,7 @@ version = "0.1.0" description = "Open-source Execution Provenance Engine for cross-venue equity trading (core primitives)" readme = "README.md" requires-python = ">=3.8" -license = {text = "MIT"} +license = "MIT" authors = [ { name = "ExProve Team" } ] dependencies = [ "dataclasses; python_version < '3.7'",