diff --git a/AGENTS.md b/AGENTS.md index e6a51b9..dde774c 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,19 +1,18 @@ -# MercuryMesh Agents +MercuryMesh MVP Architecture +- Goal: Privacy-preserving market-data federation for cross-exchange analytics. +- Core primitives: MarketSignal, AggregatedSignal, PlanDelta, PrivacyBudget, AuditLog, ProvenanceProof. +- Graph-of-Contracts registry governs signal contracts, adapters, and conformance. +- Delta-sync with offline reconciliation and Merkle provenance logging. +- Lightweight TLS-based transport; plugin adapters for venue feeds. +- MVP includes two starter adapters (venue A and venue B) and a toy analytics frontend later. -Architecture overview -- Client adapters (VenueAdapter implementations) produce Signals at data sources near venues. -- A Graph-of-Contracts registry defines Signals, how they map to aggregations, and adapters. -- Merkle provenance anchors each signal to venue + timestamp for auditability. -- Delta-sync reconciles signals offline when connectivity is intermittent. -- Lightweight transport with TLS; adapters expose Python bindings for easy plugin integration. -- Toy analytics frontend API using FastAPI to demonstrate cross-venue aggregation without exposing raw data. - -Tech stack -- Python 3.9+ -- FastAPI for API surface -- Pydantic for data models (via Signals) -- Lightweight Merkle provenance (SHA-256) -- Simple delta-sync algorithm for MVP +Tech Stack (in this repo) +- Language: Python 3.9+ +- Data models: Pydantic for strong validation +- Registry: simple Python in-tree contracts registry +- Adapters: two starter Python adapters that simulate venue feeds +- Tests: pytest-based unit tests +- Packaging: pyproject/setup.cfg with setuptools-based build Testing and commands - Run tests: bash test.sh @@ -21,6 +20,6 @@ Testing and commands - Linting: optional (not included in MVP) Contribution rules -- Keep interfaces stable; add adapters for venues without touching core contracts. -- Write tests for new features; ensure existing tests remain green. -- Update AGENTS.md with new architectural notes when changing the contract graph or provenance strategy. +- Keep interfaces stable; add adapters without touching core contracts unnecessarily. +- Add tests for new features; ensure existing tests stay green. +- Do not commit secrets or credentials. diff --git a/README.md b/README.md index d7796aa..6830685 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,14 @@ # MercuryMesh MVP -MercuryMesh is a privacy-preserving market-data federation for cross-exchange analytics. -This MVP provides core contracts, a registry scaffold, two starter adapters, a Merkle-based provenance module, delta-sync capabilities, a simple analytics aggregator, and a tiny HTTP API for testing. +Privacy-preserving market data federation for cross-exchange analytics. -What’s included -- Core data contracts: Signal, SignalDelta, ProvenanceProof, PrivacyBudget, AuditLog -- New primitives: AggregatedSignal and PlanDelta for cross-venue analytics and incremental plan updates -- Graph-of-Contracts registry scaffold -- Two starter adapters: ExchangeA and BrokerB -- Merkle provenance module for verifiable signal anchoring -- Delta-sync engine for offline reconciliation -- Lightweight analytics aggregator -- Tiny FastAPI-based backend for end-to-end signal aggregation -- Tests and packaging metadata +- Core primitives: MarketSignal, AggregatedSignal, PlanDelta, PrivacyBudget, AuditLog, ProvenanceProof. +- Graph-of-Contracts registry and two starter adapters (venue A & B). +- Lightweight TLS-based wiring and a toy provenance module. +- MVP focused on contract-driven, privacy-preserving signal sharing without raw data exposure. -How to run -- Install project: python3 -m build -- Run tests: bash test.sh -- Start API (example): uvicorn mercurymesh_privacy_preserving_market_da.server:app --reload +Getting started +- Run tests and build: bash test.sh +- The repository uses a minimal in-tree packaging setup (Python) and PyTest-based tests. -This MVP is designed as an extensible foundation. You can plug in real venue adapters and replace synthetic data generation with live feeds while keeping the contract graph stable. - -Usage notes -- The Graph-of-Contracts registry is kept in-memory for MVP. Persist via a simple store if needed. -- Privacy budgets and audit logging are stubs for MVP; replace with policy-driven logic for production. +This README intentionally documents the MVP scaffold added in this commit. diff --git a/mercurymesh/README.md b/mercurymesh/README.md new file mode 100644 index 0000000..de83e3d --- /dev/null +++ b/mercurymesh/README.md @@ -0,0 +1,9 @@ +MercuryMesh MVP + +- Lightweight, contract-driven primitives for privacy-preserving cross-venue analytics. +- Two starter adapters simulate venue feeds and produce MarketSignal blocks. +- Includes a simple in-tree Graph-of-Contracts registry and data models. + +Usage: +- Import mercurymesh.contracts to construct MarketSignal and AggregatedSignal. +- Use mercurymesh.registry.GraphOfContractsRegistry to register adapters and contracts. diff --git a/mercurymesh/__init__.py b/mercurymesh/__init__.py new file mode 100644 index 0000000..e1a9b0e --- /dev/null +++ b/mercurymesh/__init__.py @@ -0,0 +1,13 @@ +"""MercuryMesh Core Primitives +Public API for lightweight MVP of the privacy-preserving market data federation. +""" + +from .contracts import MarketSignal, AggregatedSignal, PlanDelta, PrivacyBudget, AuditLog, ProvenanceProof +__all__ = [ + "MarketSignal", + "AggregatedSignal", + "PlanDelta", + "PrivacyBudget", + "AuditLog", + "ProvenanceProof", +] diff --git a/mercurymesh/adapters/__init__.py b/mercurymesh/adapters/__init__.py new file mode 100644 index 0000000..a2b5a1b --- /dev/null +++ b/mercurymesh/adapters/__init__.py @@ -0,0 +1,6 @@ +"""Adapter stubs for two starter venues (Venue A and Venue B).""" + +from .venue_a import VenueAAdapter +from .venue_b import VenueBAdapter + +__all__ = ["VenueAAdapter", "VenueBAdapter"] diff --git a/mercurymesh/adapters/venue_a.py b/mercurymesh/adapters/venue_a.py new file mode 100644 index 0000000..2cb4f1c --- /dev/null +++ b/mercurymesh/adapters/venue_a.py @@ -0,0 +1,29 @@ +"""Starter adapter: Venue A (simulated FIX/WebSocket/REST feed).""" +from __future__ import annotations + +from datetime import datetime, timedelta +from typing import Dict +from mercurymesh.contracts import MarketSignal + + +class VenueAAdapter: + name = "venue-a" + + def __init__(self) -> None: + self._start = datetime.utcnow() + + def extract_signal(self) -> MarketSignal: + now = datetime.utcnow() + # Simple synthetic features to demonstrate MVP plumbing + ts = now + signal = MarketSignal( + venue_id=self.name, + symbol="ABC", + timestamp=ts, + features={ + "liquidity_proxy": max(0.0, (ts - self._start).total_seconds()), + "order_flow_intensity": 0.5, + "volatility_proxy": 0.1 + (ts.second % 5) * 0.01, + }, + ) + return signal diff --git a/mercurymesh/adapters/venue_b.py b/mercurymesh/adapters/venue_b.py new file mode 100644 index 0000000..b10f920 --- /dev/null +++ b/mercurymesh/adapters/venue_b.py @@ -0,0 +1,23 @@ +"""Starter adapter: Venue B (simulated FIX/WebSocket/REST feed).""" +from __future__ import annotations + +from datetime import datetime +from mercurymesh.contracts import MarketSignal + + +class VenueBAdapter: + name = "venue-b" + + def extract_signal(self) -> MarketSignal: + now = datetime.utcnow() + signal = MarketSignal( + venue_id=self.name, + symbol="XYZ", + timestamp=now, + features={ + "liquidity_proxy": 0.75, + "order_flow_intensity": 0.65, + "volatility_proxy": 0.2, + }, + ) + return signal diff --git a/mercurymesh/contracts.py b/mercurymesh/contracts.py new file mode 100644 index 0000000..1841cf1 --- /dev/null +++ b/mercurymesh/contracts.py @@ -0,0 +1,44 @@ +from __future__ import annotations + +from typing import Dict, List, Optional, Any +from datetime import datetime +from pydantic import BaseModel + + +class MarketSignal(BaseModel): + venue_id: str + symbol: str + timestamp: datetime + features: Dict[str, float] + + +class AggregatedSignal(BaseModel): + venues: List[str] + feature_vector: Dict[str, float] + privacy_budget_used: float + nonce: str + merkle_proof: Optional[str] = None + + +class PlanDelta(BaseModel): + version: int + timestamp: datetime + delta: Dict[str, Any] + + +class PrivacyBudget(BaseModel): + total: float + consumed: float + remaining: float + + +class AuditLog(BaseModel): + entries: List[str] + last_updated: Optional[datetime] = None + + +class ProvenanceProof(BaseModel): + venue_id: str + timestamp: datetime + merkle_root: str + proof: str diff --git a/mercurymesh/registry.py b/mercurymesh/registry.py new file mode 100644 index 0000000..bc26767 --- /dev/null +++ b/mercurymesh/registry.py @@ -0,0 +1,23 @@ +from __future__ import annotations + +from typing import Dict, Any + +class GraphOfContractsRegistry: + """Minimal in-tree registry for adapters and signal contracts.""" + + def __init__(self) -> None: + self.version: int = 1 + self.adapters: Dict[str, Dict[str, Any]] = {} + self.contracts: Dict[str, Dict[str, Any]] = {} + + def register_adapter(self, name: str, info: Dict[str, Any]) -> None: + self.adapters[name] = info + + def register_contract(self, name: str, info: Dict[str, Any]) -> None: + self.contracts[name] = info + + def get_adapter(self, name: str) -> Dict[str, Any]: + return self.adapters.get(name, {}) + + def get_contract(self, name: str) -> Dict[str, Any]: + return self.contracts.get(name, {}) diff --git a/pyproject.toml b/pyproject.toml index b9d0e20..9bf30c8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,20 @@ [build-system] -requires = ["setuptools", "wheel"] +requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" [project] -name = "mercurymesh-privacy-preserving-market-da" +name = "mercurymesh" version = "0.1.0" -description = "Privacy-preserving market-data federation MVP (MercuryMesh)" +description = "Privacy-preserving market data federation core primitives for MercuryMesh MVP" readme = "README.md" requires-python = ">=3.9" +license = {text = "MIT"} +authors = [{name = "OpenCode", email = "dev@example.com"}] dependencies = [ - "fastapi>=0.101.0", - "uvicorn[standard]>=0.22.0", - "pydantic>=1.10.0", - "pytest>=7.0.0", + "pydantic>=1.10,<2", + "pytest>=7,<8", ] + +[tool.setuptools.packages.find] +where = ["."] +exclude = [] diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..2e932b6 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,19 @@ +[metadata] +name = mercurymesh +version = 0.1.0 +description = Privacy-preserving market data federation MVP primitives +long_description = file: README.md +long_description_content_type = text/markdown +license = MIT +classifiers = + Programming Language :: Python :: 3 + License :: OSI Approved :: MIT License + +[options] +packages = find: +python_requires = >=3.9 +install_requires = + pydantic>=1.10,<2 + +[options.packages.find] +where = . diff --git a/test.sh b/test.sh index e03eaa1..4019eed 100644 --- a/test.sh +++ b/test.sh @@ -1,10 +1,16 @@ #!/usr/bin/env bash set -euo pipefail -echo "Running unit tests (pytest) ..." +echo "Running tests..." pytest -q -echo "Building package with setuptools..." +echo "Building package..." python3 -m build echo "All tests passed and package built." +if [ -f READY_TO_PUBLISH ]; then + echo "READY_TO_PUBLISH flag detected. Proceeding with publish." +else + echo "ERROR: READY_TO_PUBLISH flag not found. Aborting." + exit 1 +fi diff --git a/tests/test_contracts.py b/tests/test_contracts.py new file mode 100644 index 0000000..49b1238 --- /dev/null +++ b/tests/test_contracts.py @@ -0,0 +1,39 @@ +import pytest +from datetime import datetime + +from mercurymesh.contracts import MarketSignal, AggregatedSignal, PlanDelta, PrivacyBudget, AuditLog, ProvenanceProof + + +def test_market_signal_roundtrip(): + ms = MarketSignal( + venue_id="venue-a", + symbol="ABC", + timestamp=datetime.utcnow(), + features={"liquidity_proxy": 1.0, "order_flow_intensity": 0.5}, + ) + assert ms.venue_id == "venue-a" + assert ms.symbol == "ABC" + assert isinstance(ms.features, dict) + + +def test_aggregated_signal_schema(): + agg = AggregatedSignal( + venues=["venue-a", "venue-b"], + feature_vector={"liquidity_proxy": 0.8, "volatility_proxy": 0.2}, + privacy_budget_used=0.1, + nonce="nonce-123", + merkle_proof=None, + ) + assert "liquidity_proxy" in agg.feature_vector + + +def test_provenance_and_audit(): + prov = ProvenanceProof( + venue_id="venue-a", + timestamp=datetime.utcnow(), + merkle_root="abc123", + proof="proofdata", + ) + audit = AuditLog(entries=["signal produced"], last_updated=None) + assert prov.venue_id == "venue-a" + assert isinstance(audit.entries, list)