build(agent): new-agents-3#dd492b iteration
This commit is contained in:
parent
8636fde180
commit
93b338f7b7
34
README.md
34
README.md
|
|
@ -1,20 +1,28 @@
|
||||||
# FeedTrust: Blockchain-backed Access Control & Provenance for Cross-Venue Market Data Feeds
|
FeedTrust: Blockchain-backed Access Control & Provenance for Cross-Venue Market Data Feeds
|
||||||
|
|
||||||
Overview
|
Overview
|
||||||
- FeedTrust provides a policy-driven access layer between data venues and trading pipelines. It enforces who can access which data signals (raw quotes, aggregates, latency metrics) under defined conditions and produces cryptographic proofs of access and data lineage anchored to a Merkle-based ledger.
|
- FeedTrust is a modular, open-source layer that sits between market data venues and algo trading pipelines to enforce fine-grained, policy-driven data access while preserving auditability and traceability across venues. It codifies access policies into a machine-checkable DSL, provides cryptographic provenance proofs, and maintains a tamper-evident ledger for data lineage.
|
||||||
|
|
||||||
Architecture (Python MVP)
|
Architecture (production-ready mindset)
|
||||||
- Policy DSL (policy.py): A lightweight DSL for declaring access rules.
|
- Policy DSL core and compiler (feedtrust/policy.py)
|
||||||
- Adapters (adapters/): Toy FIX and WebSocket adapters that convert feeds into a canonical signal format.
|
- Provenance ledger (Merkle-based) with proofs (feedtrust/ledger_merkle.py)
|
||||||
- Aggregator (aggregation.py): Cross-venue data mixing with provenance tagging.
|
- Adapters for feeds (FIX/WebSocket) to canonical signals (feedtrust/adapters/*)
|
||||||
- Provenance Ledger (ledger_merkle.py): Merkle-tree based proofs of data lineage.
|
- Lightweight aggregation layer for cross-venue signals (feedtrust/aggregation.py)
|
||||||
- Core (core.py): Orchestrates policy checks, ledger interactions, and end-to-end flow.
|
- Core orchestrator to enforce policies and log provenance (feedtrust/core.py)
|
||||||
- Tests (tests/): Unit and integration tests for policy enforcement, ledger proofs, and end-to-end flow.
|
|
||||||
|
|
||||||
Getting Started
|
MVP Scope (as implemented in this repo)
|
||||||
|
- Tiny policy DSL with allow and deny rules
|
||||||
|
- Two toy adapters bridging FIX and WebSocket feeds to canonical signals
|
||||||
|
- Merkle-based provenance ledger with a simple proof mechanism
|
||||||
|
- Simple cross-venue aggregator for price-like signals
|
||||||
|
- End-to-end flow: ingest adapter signals, log to ledger, generate aggregates, expose root proof
|
||||||
|
|
||||||
|
How to Run
|
||||||
|
- Prerequisites: Python 3.11+
|
||||||
|
- Install test dependencies: pytest
|
||||||
- Run tests: ./test.sh
|
- Run tests: ./test.sh
|
||||||
- Build package: python -m build
|
- Build package: python -m build
|
||||||
|
|
||||||
Contribution
|
Notes
|
||||||
- This project emphasizes minimal, well-scoped changes with strong test coverage.
|
- This project is designed as an MVP and a proof-of-concept toward a production-grade, contract-governed data-gateway with verifiable provenance.
|
||||||
- See AGENTS.md for contributor guidelines.
|
- See AGENTS.md for repository conventions and testing commands.
|
||||||
|
|
|
||||||
|
|
@ -3,39 +3,66 @@ from typing import List, Dict, Any
|
||||||
|
|
||||||
class PolicyEngine:
|
class PolicyEngine:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self._policies: List[Dict[str, Any]] = []
|
# Separate allow and deny policies for straightforward evaluation order
|
||||||
|
self._policies: List[Dict[str, Any]] = [] # allowed rules
|
||||||
|
self._denies: List[Dict[str, Any]] = [] # denied rules
|
||||||
|
|
||||||
def load_policies(self, dsl: str) -> None:
|
def load_policies(self, dsl: str) -> None:
|
||||||
# Very small DSL parser for a single-line policy per call.
|
# Very small DSL parser for a single-line policy per call.
|
||||||
|
# Supports two forms:
|
||||||
|
# allow ...
|
||||||
|
# deny ...
|
||||||
# Example:
|
# Example:
|
||||||
# allow subject="traderA" venue="venue1" signal="price" action="read" latency_ms=5 volume_cap=1000 regulatory="none"
|
# allow subject="traderA" venue="venue1" signal="price" action="read" latency_ms=5 volume_cap=1000 regulatory="none"
|
||||||
|
# deny subject="traderB" venue="venue1" signal="price" action="read" latency_ms=0
|
||||||
for line in [l.strip() for l in dsl.splitlines() if l.strip()]:
|
for line in [l.strip() for l in dsl.splitlines() if l.strip()]:
|
||||||
if not line:
|
if not line:
|
||||||
continue
|
continue
|
||||||
if not line.startswith("allow"):
|
# Determine policy type
|
||||||
continue
|
if line.startswith("allow"):
|
||||||
# Remove leading 'allow'
|
rest = line[len("allow"):].strip()
|
||||||
rest = line[len("allow"):].strip()
|
policy: Dict[str, Any] = {}
|
||||||
policy: Dict[str, Any] = {}
|
parts = rest.split()
|
||||||
# Split by spaces, then parse key=value parts
|
for part in parts:
|
||||||
parts = rest.split()
|
if "=" not in part:
|
||||||
for part in parts:
|
continue
|
||||||
if "=" not in part:
|
k, v = part.split("=", 1)
|
||||||
continue
|
v = v.strip().strip('"')
|
||||||
k, v = part.split("=", 1)
|
if v.isdigit():
|
||||||
v = v.strip().strip('"')
|
policy[k] = int(v)
|
||||||
# cast common types
|
else:
|
||||||
if v.isdigit():
|
policy[k] = v
|
||||||
policy[k] = int(v)
|
policy.setdefault("latency_ms", 0)
|
||||||
else:
|
policy.setdefault("volume_cap", None)
|
||||||
policy[k] = v
|
self._policies.append(policy)
|
||||||
# Normalize some keys
|
elif line.startswith("deny"):
|
||||||
policy.setdefault("latency_ms", 0)
|
rest = line[len("deny"):].strip()
|
||||||
policy.setdefault("volume_cap", None)
|
policy: Dict[str, Any] = {}
|
||||||
self._policies.append(policy)
|
parts = rest.split()
|
||||||
|
for part in parts:
|
||||||
|
if "=" not in part:
|
||||||
|
continue
|
||||||
|
k, v = part.split("=", 1)
|
||||||
|
v = v.strip().strip('"')
|
||||||
|
if v.isdigit():
|
||||||
|
policy[k] = int(v)
|
||||||
|
else:
|
||||||
|
policy[k] = v
|
||||||
|
policy.setdefault("latency_ms", 0)
|
||||||
|
policy.setdefault("volume_cap", None)
|
||||||
|
self._denies.append(policy)
|
||||||
|
|
||||||
def check_access(self, subject: str, venue: str, signal: str, action: str) -> bool:
|
def check_access(self, subject: str, venue: str, signal: str, action: str) -> bool:
|
||||||
# Simple matcher: any policy that matches all provided fields grants access
|
# Deny policies take precedence: if any deny matches, deny access
|
||||||
|
for p in self._denies:
|
||||||
|
if (
|
||||||
|
p.get("subject") in (subject, "any")
|
||||||
|
and p.get("venue") in (venue, "any")
|
||||||
|
and p.get("signal") == signal
|
||||||
|
and p.get("action") == action
|
||||||
|
):
|
||||||
|
return False
|
||||||
|
# Otherwise, evaluate allow policies
|
||||||
for p in self._policies:
|
for p in self._policies:
|
||||||
if (
|
if (
|
||||||
p.get("subject") in (subject, "any")
|
p.get("subject") in (subject, "any")
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue