build(agent): semicolon#54de0b iteration
This commit is contained in:
parent
291373459b
commit
c264fd38ca
|
|
@ -0,0 +1,21 @@
|
|||
node_modules/
|
||||
.npmrc
|
||||
.env
|
||||
.env.*
|
||||
__tests__/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
dist/
|
||||
build/
|
||||
.cache/
|
||||
*.log
|
||||
.DS_Store
|
||||
tmp/
|
||||
.tmp/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
venv/
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
READY_TO_PUBLISH
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
# AGENTS.md
|
||||
|
||||
## Repository Shape
|
||||
|
||||
- `src/idea109_secureex_hardware_accelerated/`: package code.
|
||||
- `tests/`: unit tests.
|
||||
- `test.sh`: canonical verification entrypoint.
|
||||
- `README.md`: architecture and usage.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Python 3.10+
|
||||
- Standard library only for runtime code.
|
||||
- SQLite for the governance ledger.
|
||||
- Setuptools for packaging.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `primitives.py`: canonical trading and governance dataclasses.
|
||||
- `adapters.py`: FIX and WebSocket-to-canonical adapters.
|
||||
- `enclave.py`: deterministic enclave execution and attestation model.
|
||||
- `governance.py`: append-only, hash-chained SQLite ledger.
|
||||
- `delta_sync.py`: deterministic reconciliation helpers.
|
||||
- `replay.py`: offline deterministic replay harness and CLI.
|
||||
|
||||
## Rules
|
||||
|
||||
- Keep new logic deterministic unless explicit randomness is required and seeded.
|
||||
- Preserve the hash chain in the governance ledger.
|
||||
- Do not weaken validation in adapters or attestation verification.
|
||||
- Prefer the smallest correct change.
|
||||
- Update tests when changing canonical payload shapes or replay behavior.
|
||||
|
||||
## Verification
|
||||
|
||||
- `bash test.sh`
|
||||
- The script must run unit tests and `python3 -m build`.
|
||||
87
README.md
87
README.md
|
|
@ -1,3 +1,86 @@
|
|||
# idea109-secureex-hardware-accelerated
|
||||
# SecureEx
|
||||
|
||||
Source logic for Idea #109
|
||||
SecureEx is a deterministic compliance and replay core for cross-venue trading workflows.
|
||||
|
||||
It provides:
|
||||
|
||||
- Canonical market primitives for venue normalization.
|
||||
- FIX and WebSocket adapters.
|
||||
- A deterministic enclave execution model with attestation.
|
||||
- A SQLite-backed hash-chained governance ledger.
|
||||
- Delta reconciliation helpers for disconnect recovery.
|
||||
- An offline replay harness for regression testing.
|
||||
|
||||
## What This Repository Does
|
||||
|
||||
This codebase models the minimum trusted control plane needed for a SecureEx-style system:
|
||||
|
||||
- normalize market data into `LocalMarket`
|
||||
- aggregate it into `SharedSignals`
|
||||
- produce a deterministic `PlanDelta`
|
||||
- attest the enclave measurement and decision path
|
||||
- persist the decision trail into an auditable SQLite ledger
|
||||
- replay the same inputs deterministically after a disconnect
|
||||
|
||||
## Architecture
|
||||
|
||||
### Canonical primitives
|
||||
|
||||
- `LocalMarket`: venue-level bid/ask state.
|
||||
- `SharedSignals`: aggregated cross-venue signal output.
|
||||
- `PlanDelta`: the next hedge or quote action.
|
||||
- `PrivacyBudget`: guards signal leakage.
|
||||
- `AuditLogEntry`: the attested event record.
|
||||
|
||||
### Adapters
|
||||
|
||||
- `FixAdapter`: parses FIX-style tag/value payloads.
|
||||
- `WebSocketAdapter`: parses JSON market updates.
|
||||
|
||||
### Enclave and attestation
|
||||
|
||||
- `SecureEnclave.compute()` is deterministic for a given market set.
|
||||
- `SecureEnclave.attest()` returns a signed measurement report.
|
||||
- `verify_attestation()` checks the report signature and measurement.
|
||||
|
||||
### Governance ledger
|
||||
|
||||
- `GovernanceLedger` stores an append-only hash chain in SQLite.
|
||||
- `verify_chain()` recomputes the full chain for audit validation.
|
||||
|
||||
### Delta sync and replay
|
||||
|
||||
- `DeltaSync` tracks sequence progress per venue and asset.
|
||||
- `reconcile_markets()` computes the deterministic recovery delta.
|
||||
- `replay.py` can run a local replay from a JSON event file.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
python3 -m pip install -e .
|
||||
```
|
||||
|
||||
## Run Tests
|
||||
|
||||
```bash
|
||||
bash test.sh
|
||||
```
|
||||
|
||||
## Replay CLI
|
||||
|
||||
```bash
|
||||
secureex-replay --events events.json
|
||||
```
|
||||
|
||||
Example `events.json`:
|
||||
|
||||
```json
|
||||
[
|
||||
{"adapter": "fix", "venue": "VENUE_A", "message": "55=ABC|132=10.1|133=10.2|134=100|135=120|34=7|52=1.5"},
|
||||
{"adapter": "ws", "venue": "VENUE_B", "message": {"asset": "ABC", "bid": 10.0, "ask": 10.3, "bid_size": 80, "ask_size": 60, "sequence": 8, "timestamp": 1.7}}
|
||||
]
|
||||
```
|
||||
|
||||
## Packaging
|
||||
|
||||
The package name is `idea109-secureex-hardware-accelerated` and the long description is sourced from this `README.md`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,21 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=68", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "idea109-secureex-hardware-accelerated"
|
||||
version = "0.1.0"
|
||||
description = "SecureEx: a verifiable compliance and deterministic replay engine for cross-venue trading"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
license = "MIT"
|
||||
authors = [{name = "OpenCode"}]
|
||||
|
||||
[project.scripts]
|
||||
secureex-replay = "idea109_secureex_hardware_accelerated.replay:main"
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "src"}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
"""SecureEx core package."""
|
||||
|
||||
from .adapters import FixAdapter, WebSocketAdapter
|
||||
from .delta_sync import DeltaSync, reconcile_markets
|
||||
from .enclave import SecureEnclave, verify_attestation
|
||||
from .governance import GovernanceLedger
|
||||
from .primitives import (
|
||||
AuditLogEntry,
|
||||
LocalMarket,
|
||||
PlanDelta,
|
||||
PrivacyBudget,
|
||||
SharedSignals,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"AuditLogEntry",
|
||||
"DeltaSync",
|
||||
"FixAdapter",
|
||||
"GovernanceLedger",
|
||||
"LocalMarket",
|
||||
"PlanDelta",
|
||||
"PrivacyBudget",
|
||||
"SecureEnclave",
|
||||
"SharedSignals",
|
||||
"WebSocketAdapter",
|
||||
"reconcile_markets",
|
||||
"verify_attestation",
|
||||
]
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from collections.abc import Mapping
|
||||
|
||||
from .primitives import LocalMarket
|
||||
|
||||
|
||||
def _coerce_float(value: object) -> float:
|
||||
if isinstance(value, (str, int, float)):
|
||||
return float(value)
|
||||
raise TypeError(f"cannot coerce {type(value)!r} to float")
|
||||
|
||||
|
||||
def _coerce_int(value: object) -> int:
|
||||
if isinstance(value, (str, int, float)):
|
||||
return int(float(value))
|
||||
raise TypeError(f"cannot coerce {type(value)!r} to int")
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class FixAdapter:
|
||||
venue: str
|
||||
|
||||
def parse_market(self, message: str) -> LocalMarket:
|
||||
fields: dict[str, str] = {}
|
||||
for pair in message.split("|"):
|
||||
if not pair:
|
||||
continue
|
||||
if "=" not in pair:
|
||||
continue
|
||||
key, value = pair.split("=", 1)
|
||||
fields[key] = value
|
||||
|
||||
return LocalMarket(
|
||||
venue=self.venue,
|
||||
asset=fields.get("55", fields.get("symbol", "UNKNOWN")),
|
||||
bid=_coerce_float(fields.get("132", fields.get("bid", 0.0))),
|
||||
ask=_coerce_float(fields.get("133", fields.get("ask", 0.0))),
|
||||
bid_size=_coerce_float(fields.get("134", fields.get("bid_size", 0.0))),
|
||||
ask_size=_coerce_float(fields.get("135", fields.get("ask_size", 0.0))),
|
||||
sequence=_coerce_int(fields.get("34", fields.get("sequence", 0))),
|
||||
timestamp=_coerce_float(fields.get("52", fields.get("timestamp", 0.0))),
|
||||
)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class WebSocketAdapter:
|
||||
venue: str
|
||||
|
||||
def parse_market(self, message: str | Mapping[str, object]) -> LocalMarket:
|
||||
payload = json.loads(message) if isinstance(message, str) else dict(message)
|
||||
return LocalMarket(
|
||||
venue=self.venue,
|
||||
asset=str(payload.get("asset", payload.get("symbol", "UNKNOWN"))),
|
||||
bid=_coerce_float(payload.get("bid", 0.0)),
|
||||
ask=_coerce_float(payload.get("ask", 0.0)),
|
||||
bid_size=_coerce_float(payload.get("bid_size", payload.get("bidSize", 0.0))),
|
||||
ask_size=_coerce_float(payload.get("ask_size", payload.get("askSize", 0.0))),
|
||||
sequence=_coerce_int(payload.get("sequence", payload.get("seq", 0))),
|
||||
timestamp=_coerce_float(payload.get("timestamp", 0.0)),
|
||||
)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable, Sequence
|
||||
|
||||
from .primitives import LocalMarket
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class MarketDelta:
|
||||
asset: str
|
||||
venue: str
|
||||
previous_sequence: int
|
||||
current_sequence: int
|
||||
bid: float
|
||||
ask: float
|
||||
|
||||
|
||||
class DeltaSync:
|
||||
def __init__(self) -> None:
|
||||
self._last_sequences: dict[tuple[str, str], int] = {}
|
||||
|
||||
def snapshot(self, markets: Sequence[LocalMarket]) -> list[MarketDelta]:
|
||||
deltas: list[MarketDelta] = []
|
||||
for market in sorted(markets, key=lambda m: (m.asset, m.venue, m.sequence)):
|
||||
key = (market.asset, market.venue)
|
||||
previous_sequence = self._last_sequences.get(key, 0)
|
||||
if market.sequence != previous_sequence:
|
||||
deltas.append(
|
||||
MarketDelta(
|
||||
asset=market.asset,
|
||||
venue=market.venue,
|
||||
previous_sequence=previous_sequence,
|
||||
current_sequence=market.sequence,
|
||||
bid=market.bid,
|
||||
ask=market.ask,
|
||||
)
|
||||
)
|
||||
self._last_sequences[key] = market.sequence
|
||||
return deltas
|
||||
|
||||
|
||||
def reconcile_markets(local: Sequence[LocalMarket], remote: Sequence[LocalMarket]) -> list[MarketDelta]:
|
||||
remote_index = {(m.asset, m.venue): m for m in remote}
|
||||
deltas: list[MarketDelta] = []
|
||||
for market in sorted(local, key=lambda m: (m.asset, m.venue, m.sequence)):
|
||||
counterpart = remote_index.get((market.asset, market.venue))
|
||||
if counterpart is None or counterpart.sequence != market.sequence or counterpart.bid != market.bid or counterpart.ask != market.ask:
|
||||
deltas.append(
|
||||
MarketDelta(
|
||||
asset=market.asset,
|
||||
venue=market.venue,
|
||||
previous_sequence=counterpart.sequence if counterpart else 0,
|
||||
current_sequence=market.sequence,
|
||||
bid=market.bid,
|
||||
ask=market.ask,
|
||||
)
|
||||
)
|
||||
return deltas
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Iterable, Sequence
|
||||
|
||||
from .primitives import LocalMarket, PlanDelta, PrivacyBudget, SharedSignals, EnclaveDecision
|
||||
|
||||
|
||||
def _stable_json(value: object) -> str:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AttestationReport:
|
||||
measurement: str
|
||||
nonce: str
|
||||
signature: str
|
||||
|
||||
|
||||
class SecureEnclave:
|
||||
def __init__(self, enclave_id: str, secret: str, measurement: str | None = None) -> None:
|
||||
self.enclave_id = enclave_id
|
||||
self._secret = secret.encode("utf-8")
|
||||
self.measurement = measurement or self._measure()
|
||||
|
||||
def _measure(self) -> str:
|
||||
return hashlib.sha256(f"{self.enclave_id}:{self.__class__.__name__}".encode("utf-8")).hexdigest()
|
||||
|
||||
def attest(self, nonce: str) -> AttestationReport:
|
||||
message = f"{self.measurement}:{nonce}:{self.enclave_id}".encode("utf-8")
|
||||
signature = hmac.new(self._secret, message, hashlib.sha256).hexdigest()
|
||||
return AttestationReport(measurement=self.measurement, nonce=nonce, signature=signature)
|
||||
|
||||
def compute(self, markets: Sequence[LocalMarket], privacy_budget: PrivacyBudget | None = None) -> EnclaveDecision:
|
||||
if not markets:
|
||||
raise ValueError("markets are required")
|
||||
|
||||
grouped: dict[str, list[LocalMarket]] = {}
|
||||
for market in markets:
|
||||
grouped.setdefault(market.asset, []).append(market)
|
||||
|
||||
asset, asset_markets = sorted(grouped.items())[0]
|
||||
bids = sum(m.bid * m.bid_size for m in asset_markets)
|
||||
asks = sum(m.ask * m.ask_size for m in asset_markets)
|
||||
depth = sum(m.bid_size + m.ask_size for m in asset_markets)
|
||||
bid = min(m.bid for m in asset_markets)
|
||||
ask = max(m.ask for m in asset_markets)
|
||||
mid = sum(m.mid for m in asset_markets) / len(asset_markets)
|
||||
spread = ask - bid
|
||||
budget = privacy_budget or PrivacyBudget(total_units=8)
|
||||
budget = budget.consume(1)
|
||||
|
||||
signals = SharedSignals(
|
||||
asset=asset,
|
||||
mid=mid,
|
||||
spread=spread,
|
||||
venue_count=len({m.venue for m in asset_markets}),
|
||||
aggregate_depth=depth,
|
||||
privacy_budget_used=budget.used_units,
|
||||
)
|
||||
|
||||
venue = sorted(asset_markets, key=lambda market: (market.ask, market.sequence, market.venue))[0].venue
|
||||
side = "BUY" if bids < asks else "SELL"
|
||||
quantity = max(1.0, round(depth / max(len(asset_markets), 1), 4))
|
||||
limit_price = round(mid + (0.01 if side == "BUY" else -0.01), 4)
|
||||
plan_source = _stable_json({"asset": asset, "mid": mid, "spread": spread, "venue": venue, "side": side})
|
||||
plan_id = hashlib.sha256(plan_source.encode("utf-8")).hexdigest()[:16]
|
||||
plan = PlanDelta(
|
||||
plan_id=plan_id,
|
||||
asset=asset,
|
||||
venue=venue,
|
||||
side=side,
|
||||
quantity=quantity,
|
||||
limit_price=limit_price,
|
||||
rationale=f"cross-venue-deterministic-{side.lower()}",
|
||||
sequence=max(m.sequence for m in asset_markets),
|
||||
)
|
||||
return EnclaveDecision(signals=signals, plan=plan, privacy_budget=budget)
|
||||
|
||||
|
||||
def verify_attestation(report: AttestationReport, expected_measurement: str, secret: str, enclave_id: str) -> bool:
|
||||
message = f"{expected_measurement}:{report.nonce}:{enclave_id}".encode("utf-8")
|
||||
expected_signature = hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest()
|
||||
return report.measurement == expected_measurement and hmac.compare_digest(report.signature, expected_signature)
|
||||
|
|
@ -0,0 +1,140 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import sqlite3
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Iterable
|
||||
|
||||
from .enclave import AttestationReport
|
||||
from .primitives import AuditLogEntry
|
||||
|
||||
|
||||
def _stable_json(value: Any) -> str:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LedgerRecord:
|
||||
sequence: int
|
||||
entry_type: str
|
||||
payload: dict[str, Any]
|
||||
timestamp: float
|
||||
previous_hash: str
|
||||
entry_hash: str
|
||||
attestation_measurement: str
|
||||
attestation_nonce: str
|
||||
attestation_signature: str
|
||||
|
||||
|
||||
class GovernanceLedger:
|
||||
def __init__(self, path: str | Path = ":memory:") -> None:
|
||||
self.path = str(path)
|
||||
self._conn = sqlite3.connect(self.path)
|
||||
self._conn.row_factory = sqlite3.Row
|
||||
self._initialize()
|
||||
|
||||
def _initialize(self) -> None:
|
||||
self._conn.execute(
|
||||
"""
|
||||
CREATE TABLE IF NOT EXISTS governance_log (
|
||||
sequence INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
entry_type TEXT NOT NULL,
|
||||
payload TEXT NOT NULL,
|
||||
timestamp REAL NOT NULL,
|
||||
previous_hash TEXT NOT NULL,
|
||||
entry_hash TEXT NOT NULL,
|
||||
attestation_measurement TEXT NOT NULL,
|
||||
attestation_nonce TEXT NOT NULL,
|
||||
attestation_signature TEXT NOT NULL
|
||||
)
|
||||
"""
|
||||
)
|
||||
self._conn.commit()
|
||||
|
||||
def head_hash(self) -> str:
|
||||
row = self._conn.execute(
|
||||
"SELECT entry_hash FROM governance_log ORDER BY sequence DESC LIMIT 1"
|
||||
).fetchone()
|
||||
return row["entry_hash"] if row else "GENESIS"
|
||||
|
||||
def append(self, entry_type: str, payload: dict[str, Any], timestamp: float, attestation: AttestationReport) -> LedgerRecord:
|
||||
previous_hash = self.head_hash()
|
||||
body = _stable_json({
|
||||
"entry_type": entry_type,
|
||||
"payload": payload,
|
||||
"timestamp": timestamp,
|
||||
"previous_hash": previous_hash,
|
||||
"measurement": attestation.measurement,
|
||||
"nonce": attestation.nonce,
|
||||
"signature": attestation.signature,
|
||||
})
|
||||
entry_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
cursor = self._conn.execute(
|
||||
"""
|
||||
INSERT INTO governance_log (
|
||||
entry_type, payload, timestamp, previous_hash, entry_hash,
|
||||
attestation_measurement, attestation_nonce, attestation_signature
|
||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""",
|
||||
(
|
||||
entry_type,
|
||||
_stable_json(payload),
|
||||
timestamp,
|
||||
previous_hash,
|
||||
entry_hash,
|
||||
attestation.measurement,
|
||||
attestation.nonce,
|
||||
attestation.signature,
|
||||
),
|
||||
)
|
||||
self._conn.commit()
|
||||
return LedgerRecord(
|
||||
sequence=int(cursor.lastrowid or 0),
|
||||
entry_type=entry_type,
|
||||
payload=payload,
|
||||
timestamp=timestamp,
|
||||
previous_hash=previous_hash,
|
||||
entry_hash=entry_hash,
|
||||
attestation_measurement=attestation.measurement,
|
||||
attestation_nonce=attestation.nonce,
|
||||
attestation_signature=attestation.signature,
|
||||
)
|
||||
|
||||
def records(self) -> list[LedgerRecord]:
|
||||
rows = self._conn.execute(
|
||||
"SELECT * FROM governance_log ORDER BY sequence ASC"
|
||||
).fetchall()
|
||||
return [
|
||||
LedgerRecord(
|
||||
sequence=row["sequence"],
|
||||
entry_type=row["entry_type"],
|
||||
payload=json.loads(row["payload"]),
|
||||
timestamp=row["timestamp"],
|
||||
previous_hash=row["previous_hash"],
|
||||
entry_hash=row["entry_hash"],
|
||||
attestation_measurement=row["attestation_measurement"],
|
||||
attestation_nonce=row["attestation_nonce"],
|
||||
attestation_signature=row["attestation_signature"],
|
||||
)
|
||||
for row in rows
|
||||
]
|
||||
|
||||
def verify_chain(self) -> bool:
|
||||
previous_hash = "GENESIS"
|
||||
for record in self.records():
|
||||
body = _stable_json({
|
||||
"entry_type": record.entry_type,
|
||||
"payload": record.payload,
|
||||
"timestamp": record.timestamp,
|
||||
"previous_hash": record.previous_hash,
|
||||
"measurement": record.attestation_measurement,
|
||||
"nonce": record.attestation_nonce,
|
||||
"signature": record.attestation_signature,
|
||||
})
|
||||
entry_hash = hashlib.sha256(body.encode("utf-8")).hexdigest()
|
||||
if record.previous_hash != previous_hash or record.entry_hash != entry_hash:
|
||||
return False
|
||||
previous_hash = record.entry_hash
|
||||
return True
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import Any, Mapping
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class LocalMarket:
|
||||
venue: str
|
||||
asset: str
|
||||
bid: float
|
||||
ask: float
|
||||
bid_size: float = 0.0
|
||||
ask_size: float = 0.0
|
||||
sequence: int = 0
|
||||
timestamp: float = 0.0
|
||||
|
||||
@property
|
||||
def mid(self) -> float:
|
||||
return (self.bid + self.ask) / 2.0
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class SharedSignals:
|
||||
asset: str
|
||||
mid: float
|
||||
spread: float
|
||||
venue_count: int
|
||||
aggregate_depth: float
|
||||
privacy_budget_used: int = 0
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PlanDelta:
|
||||
plan_id: str
|
||||
asset: str
|
||||
venue: str
|
||||
side: str
|
||||
quantity: float
|
||||
limit_price: float
|
||||
rationale: str
|
||||
sequence: int
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PrivacyBudget:
|
||||
total_units: int
|
||||
used_units: int = 0
|
||||
|
||||
def consume(self, units: int = 1) -> "PrivacyBudget":
|
||||
new_used = self.used_units + units
|
||||
if new_used > self.total_units:
|
||||
raise ValueError("privacy budget exhausted")
|
||||
return PrivacyBudget(total_units=self.total_units, used_units=new_used)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class AuditLogEntry:
|
||||
entry_type: str
|
||||
payload: Mapping[str, Any]
|
||||
timestamp: float
|
||||
previous_hash: str
|
||||
entry_hash: str
|
||||
attestation_measurement: str
|
||||
attestation_nonce: str
|
||||
attestation_signature: str
|
||||
|
||||
def to_record(self) -> dict[str, Any]:
|
||||
return asdict(self)
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class EnclaveDecision:
|
||||
signals: SharedSignals
|
||||
plan: PlanDelta
|
||||
privacy_budget: PrivacyBudget = field(default_factory=lambda: PrivacyBudget(total_units=1))
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from dataclasses import asdict
|
||||
from pathlib import Path
|
||||
from collections.abc import Mapping
|
||||
from typing import Iterable, cast
|
||||
|
||||
from .adapters import FixAdapter, WebSocketAdapter
|
||||
from .enclave import SecureEnclave, verify_attestation
|
||||
from .governance import GovernanceLedger
|
||||
from .primitives import LocalMarket
|
||||
|
||||
|
||||
def replay_events(events: Iterable[dict[str, object]], *, enclave: SecureEnclave, ledger: GovernanceLedger) -> dict[str, object]:
|
||||
markets: list[LocalMarket] = []
|
||||
for event in events:
|
||||
adapter_type = str(event["adapter"])
|
||||
venue = str(event["venue"])
|
||||
message = event["message"]
|
||||
if adapter_type == "fix":
|
||||
market = FixAdapter(venue=venue).parse_market(str(message))
|
||||
elif adapter_type == "ws":
|
||||
if isinstance(message, (str, Mapping)):
|
||||
market = WebSocketAdapter(venue=venue).parse_market(
|
||||
cast(str | Mapping[str, object], message)
|
||||
)
|
||||
else:
|
||||
raise ValueError("ws message must be JSON string or mapping")
|
||||
else:
|
||||
raise ValueError(f"unknown adapter: {adapter_type}")
|
||||
markets.append(market)
|
||||
|
||||
decision = enclave.compute(markets)
|
||||
attestation = enclave.attest(nonce=f"replay-{decision.plan.plan_id}")
|
||||
ledger.append(
|
||||
"plan_delta",
|
||||
{"signals": asdict(decision.signals), "plan": asdict(decision.plan)},
|
||||
timestamp=float(len(markets)),
|
||||
attestation=attestation,
|
||||
)
|
||||
return {
|
||||
"signals": asdict(decision.signals),
|
||||
"plan": asdict(decision.plan),
|
||||
"attestation_verified": verify_attestation(attestation, enclave.measurement, enclave._secret.decode("utf-8"), enclave.enclave_id),
|
||||
"ledger_head": ledger.head_hash(),
|
||||
"ledger_consistent": ledger.verify_chain(),
|
||||
}
|
||||
|
||||
|
||||
def build_parser() -> argparse.ArgumentParser:
|
||||
parser = argparse.ArgumentParser(description="Run a deterministic SecureEx replay")
|
||||
parser.add_argument("--events", required=True, help="Path to JSON event file")
|
||||
parser.add_argument("--ledger", default=":memory:", help="SQLite ledger path")
|
||||
parser.add_argument("--enclave-id", default="edge-node-1")
|
||||
parser.add_argument("--secret", default="secureex-demo-secret")
|
||||
return parser
|
||||
|
||||
|
||||
def main(argv: list[str] | None = None) -> int:
|
||||
args = build_parser().parse_args(argv)
|
||||
payload = json.loads(Path(args.events).read_text())
|
||||
if not isinstance(payload, list):
|
||||
raise SystemExit("events file must contain a JSON array")
|
||||
enclave = SecureEnclave(enclave_id=args.enclave_id, secret=args.secret)
|
||||
ledger = GovernanceLedger(args.ledger)
|
||||
result = replay_events(payload, enclave=enclave, ledger=ledger)
|
||||
print(json.dumps(result, sort_keys=True, indent=2))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
export PYTHONPATH="src"
|
||||
python3 -m pip install --quiet --upgrade build
|
||||
python3 -m unittest discover -s tests -v
|
||||
python3 -m build
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
from idea109_secureex_hardware_accelerated.adapters import FixAdapter, WebSocketAdapter
|
||||
from idea109_secureex_hardware_accelerated.delta_sync import DeltaSync, reconcile_markets
|
||||
from idea109_secureex_hardware_accelerated.enclave import SecureEnclave, verify_attestation
|
||||
from idea109_secureex_hardware_accelerated.governance import GovernanceLedger
|
||||
|
||||
|
||||
class SecureExTests(unittest.TestCase):
|
||||
def test_adapters_parse_canonical_markets(self) -> None:
|
||||
fix_market = FixAdapter(venue="VENUE_A").parse_market("35=W|55=ABC|132=10.1|133=10.2|134=100|135=120|34=7|52=1.5")
|
||||
ws_market = WebSocketAdapter(venue="VENUE_B").parse_market(
|
||||
{"asset": "ABC", "bid": 10.0, "ask": 10.3, "bid_size": 80, "ask_size": 60, "sequence": 8, "timestamp": 1.7}
|
||||
)
|
||||
self.assertEqual(fix_market.asset, "ABC")
|
||||
self.assertEqual(ws_market.venue, "VENUE_B")
|
||||
self.assertAlmostEqual(fix_market.mid, 10.15)
|
||||
|
||||
def test_enclave_attestation_and_deterministic_decision(self) -> None:
|
||||
markets = [
|
||||
FixAdapter(venue="VENUE_A").parse_market("55=ABC|132=10.1|133=10.2|134=100|135=120|34=7|52=1.5"),
|
||||
WebSocketAdapter(venue="VENUE_B").parse_market("{\"asset\": \"ABC\", \"bid\": 10.0, \"ask\": 10.3, \"bid_size\": 80, \"ask_size\": 60, \"sequence\": 8, \"timestamp\": 1.7}")
|
||||
]
|
||||
enclave = SecureEnclave(enclave_id="edge-node-1", secret="demo-secret")
|
||||
decision1 = enclave.compute(markets)
|
||||
decision2 = enclave.compute(markets)
|
||||
self.assertEqual(decision1.plan, decision2.plan)
|
||||
report = enclave.attest("nonce-1")
|
||||
self.assertTrue(verify_attestation(report, enclave.measurement, "demo-secret", enclave.enclave_id))
|
||||
|
||||
def test_ledger_chain_and_replay(self) -> None:
|
||||
with tempfile.TemporaryDirectory() as tmp:
|
||||
ledger = GovernanceLedger(Path(tmp) / "ledger.sqlite3")
|
||||
enclave = SecureEnclave(enclave_id="edge-node-1", secret="demo-secret")
|
||||
report = enclave.attest("nonce-2")
|
||||
ledger.append("plan_delta", {"plan_id": "x"}, 1.0, report)
|
||||
self.assertTrue(ledger.verify_chain())
|
||||
|
||||
sync = DeltaSync()
|
||||
markets = [
|
||||
FixAdapter(venue="VENUE_A").parse_market("55=ABC|132=10.1|133=10.2|134=100|135=120|34=7|52=1.5"),
|
||||
WebSocketAdapter(venue="VENUE_B").parse_market({"asset": "ABC", "bid": 10.0, "ask": 10.3, "sequence": 8}),
|
||||
]
|
||||
first = sync.snapshot(markets)
|
||||
second = sync.snapshot(markets)
|
||||
self.assertEqual(len(first), 2)
|
||||
self.assertEqual(second, [])
|
||||
delta = reconcile_markets(markets, markets[:1])
|
||||
self.assertEqual(len(delta), 1)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Loading…
Reference in New Issue