build(agent): new-agents-2#7e3bbc iteration

This commit is contained in:
agent-7e3bbc424e07835b 2026-04-23 23:09:35 +02:00
parent d910bdfc98
commit 248a465cd5
10 changed files with 364 additions and 2 deletions

21
.gitignore vendored Normal file
View File

@ -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

17
AGENTS.md Normal file
View File

@ -0,0 +1,17 @@
# AGENTS
- This repository implements PortaLedger with a CatOpt bridge for cross-domain provenance sharing.
- Architecture:
- porta ledger core: Python package under src/portaledger with LocalProblem, SharedVariable, PlanDelta, ProvenanceEvent, Attestation, PortaLedger.
- Bridge: src/portaledger/bridge.py with PortaBridge and bridge_to_catopt to map to CatOpt-like GoC primitives.
- Governance: src/portaledger/governance.py implementing a light governance ledger anchored to a SQLite store.
- Tests: tests/test_portaledger.py validating core signing/logging and bridge behavior.
- Build/test: test.sh runs python3 -m build and pytest.
- Packaging: pyproject.toml configured for setuptools with dependencies (cryptography).
How to run:
- ./test.sh
Conventions:
- Minimal production-grade persistence (SQLite in-memory by default) for testability.
- Do not depend on network; tests are deterministic.

View File

@ -1,3 +1,18 @@
# idea94-portaledger-verifiable-privacy # PortaLedger: Verifiable Privacy-Preserving Provenance
Source logic for Idea #94 Overview
- A production-grade prototype that records provenance for portfolio experiments in a privacy-preserving, verifiable ledger. Includes a CatOpt-style bridge to exchange cross-domain proofs without leaking private data.
Architecture
- Core: src/portaledger/core.py defines LocalProblem, SharedVariable, PlanDelta, ProvenanceEvent, Attestation, and PortaLedger (SQLite-backed).
- Bridge: src/portaledger/bridge.py exposes a minimal PortaBridge and bridge_to_catopt for cross-domain GoC-like messages.
- Governance: src/portaledger/governance.py provides a simple governance log anchored in SQLite.
- Tests: tests/test_portaledger.py validates signing, logging, and bridge mapping.
- Packaging: pyproject.toml, README.md, and test.sh to build and test.
Usage
- Run tests: ./test.sh
- The Python package is published as portaledger (0.1.0) with a production-like API surface for core data types and ledger hooks.
Note
- This is a solid foundation; additional complexity (DID identities, secure aggregation, and full CatOpt SDK bindings) can be layered in subsequent sprint phases.

21
pyproject.toml Normal file
View File

@ -0,0 +1,21 @@
[build-system]
requires = ["setuptools>=42", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "portaledger"
version = "0.1.0"
description = "Verifiable, privacy-preserving portfolio experiment provenance with PortaLedger and CatOpt bridge"
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.9"
dependencies = [
"cryptography>=40.0.0",
]
[tool.setuptools.packages.find]
where = ["src"]
[project.urls]
Repository = "https://example.com/portaledger"

View File

@ -0,0 +1,16 @@
from .core import LocalProblem, SharedVariable, PlanDelta, ProvenanceEvent, Attestation
from .core import PortaLedger
from .bridge import PortaBridge, bridge_to_catopt
from .governance import GovernanceLedger
__all__ = [
"LocalProblem",
"SharedVariable",
"PlanDelta",
"ProvenanceEvent",
"Attestation",
"PortaLedger",
"PortaBridge",
"bridge_to_catopt",
"GovernanceLedger",
]

34
src/portaledger/bridge.py Normal file
View File

@ -0,0 +1,34 @@
from __future__ import annotations
from dataclasses import asdict
from typing import Any, Dict
from .core import LocalProblem, SharedVariable, PlanDelta, ProvenanceEvent, Attestation
class PortaBridge:
def __init__(self):
pass
def portaledger_to_catopt(self, event: ProvenanceEvent, problem: LocalProblem, vars: Dict[str, SharedVariable]) -> Dict[str, Any]:
# Map PortaLedger event to a CatOpt-like GoC primitive set (simplified)
coc = {
"LocalProblem": asdict(problem),
"SharedVariables": [asdict(v) for v in vars.values()],
"PlanDelta": {
"delta_id": event.contract_id,
"delta_hash": event.delta_hash,
"timestamp": event.timestamp,
},
"AuditLog": {
"event_id": event.event_id,
"actor": event.actor,
"policy_flags": event.policy_flags,
},
}
return coc
def bridge_to_catopt(event: ProvenanceEvent, problem: LocalProblem, vars: Dict[str, SharedVariable]) -> Dict[str, Any]:
bridge = PortaBridge()
return bridge.portaledger_to_catopt(event, problem, vars)

139
src/portaledger/core.py Normal file
View File

@ -0,0 +1,139 @@
from __future__ import annotations
import hashlib
import json
import sqlite3
import time
from dataclasses import dataclass, asdict
from typing import Any, Dict, List, Optional
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
def _hash(data: Any) -> str:
h = hashlib.sha256()
h.update(json.dumps(data, sort_keys=True, default=str).encode())
return h.hexdigest()
@dataclass
class LocalProblem:
problem_id: str
assets: List[str]
objectives: List[str]
@dataclass
class SharedVariable:
name: str
value: Any
version: int = 0
@dataclass
class PlanDelta:
delta_id: str
changes: Dict[str, Any]
timestamp: float
delta_hash: str = ""
attestation: Optional[bytes] = None
def compute_hash(self) -> str:
self.delta_hash = _hash({"delta_id": self.delta_id, "changes": self.changes, "timestamp": self.timestamp})
return self.delta_hash
@dataclass
class Attestation:
signer_pubkey_pem: bytes
signature: bytes
def to_dict(self) -> Dict[str, Any]:
return {
"signer_pubkey_pem": self.signer_pubkey_pem.decode(),
"signature": self.signature.hex(),
}
@dataclass
class ProvenanceEvent:
event_id: str
experiment_id: str
actor: str
timestamp: float
contract_id: str
delta_hash: str
attestation: Attestation
policy_flags: List[str]
def to_dict(self) -> Dict[str, Any]:
d = asdict(self)
d["attestation"] = self.attestation.to_dict()
return d
class PortaLedger:
def __init__(self, db_path: str = ":memory:"):
self.conn = sqlite3.connect(db_path)
self._init_schema()
self._signer = Ed25519PrivateKey.generate()
def _init_schema(self):
cur = self.conn.cursor()
cur.execute(
"""
CREATE TABLE IF NOT EXISTS provenance_events (
event_id TEXT PRIMARY KEY,
experiment_id TEXT,
actor TEXT,
timestamp REAL,
contract_id TEXT,
delta_hash TEXT,
attestation BLOB,
policy_flags TEXT
)
"""
)
self.conn.commit()
def sign_delta(self, delta: PlanDelta) -> PlanDelta:
delta.compute_hash()
# Sign the delta hash as a simple attestation
data = delta.delta_hash.encode()
signature = self._signer.sign(data)
pub = self._signer.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
delta.attestation = Attestation(signer_pubkey_pem=pub, signature=signature)
return delta
def log_provenance(self, event: ProvenanceEvent) -> None:
cur = self.conn.cursor()
cur.execute(
"INSERT OR REPLACE INTO provenance_events (event_id, experiment_id, actor, timestamp, contract_id, delta_hash, attestation, policy_flags) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(
event.event_id,
event.experiment_id,
event.actor,
event.timestamp,
event.contract_id,
event.delta_hash,
event.attestation.signature if event.attestation else None,
json.dumps(event.policy_flags),
),
)
self.conn.commit()
def fetch_event(self, event_id: str) -> Optional[ProvenanceEvent]:
cur = self.conn.cursor()
cur.execute("SELECT event_id, experiment_id, actor, timestamp, contract_id, delta_hash, attestation, policy_flags FROM provenance_events WHERE event_id = ?", (event_id,))
row = cur.fetchone()
if not row:
return None
# Minimal reconstruction; attestation storage is simplified
delta_hash = row[5]
attestation = Attestation(signer_pubkey_pem=b"", signature=row[6] or b"")
event = ProvenanceEvent(event_id=row[0], experiment_id=row[1], actor=row[2], timestamp=row[3], contract_id=row[4], delta_hash=delta_hash, attestation=attestation, policy_flags=json.loads(row[7]))
return event

View File

@ -0,0 +1,44 @@
from __future__ import annotations
import sqlite3
import json
from typing import List
class GovernanceLedger:
def __init__(self, db_path: str = ":memory:"):
self.conn = sqlite3.connect(db_path)
self._init_schema()
def _init_schema(self):
cur = self.conn.cursor()
cur.execute(
"""
CREATE TABLE IF NOT EXISTS governance_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
timestamp REAL,
actor TEXT,
policy_id TEXT,
decision TEXT,
details TEXT
)
"""
)
self.conn.commit()
def record_decision(self, actor: str, policy_id: str, decision: str, details: dict) -> None:
cur = self.conn.cursor()
cur.execute(
"INSERT INTO governance_logs (timestamp, actor, policy_id, decision, details) VALUES (?, ?, ?, ?, ?)",
(time.time(), actor, policy_id, decision, json.dumps(details)),
)
self.conn.commit()
def get_logs(self) -> List[dict]:
cur = self.conn.cursor()
cur.execute("SELECT id, timestamp, actor, policy_id, decision, details FROM governance_logs ORDER BY timestamp DESC")
rows = cur.fetchall()
return [
{"id": r[0], "timestamp": r[1], "actor": r[2], "policy_id": r[3], "decision": r[4], "details": json.loads(r[5])}
for r in rows
]

6
test.sh Normal file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
set -euo pipefail
echo "Running Python build and tests..."
python3 -m build
pytest -q

49
tests/test_portaledger.py Normal file
View File

@ -0,0 +1,49 @@
import json
import time
import sys
import os
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "src")))
from portaledger import LocalProblem, PortaLedger, PlanDelta, ProvenanceEvent, Attestation, PortaBridge, bridge_to_catopt
from portaledger.governance import GovernanceLedger
def test_sign_and_log_provenance_roundtrip():
ledger = PortaLedger()
lp = LocalProblem(problem_id="p1", assets=["AAPL"], objectives=["maximize return"])
delta = PlanDelta(delta_id="d1", changes={"AAPL": {"hold": 10}}, timestamp=time.time())
signed = ledger.sign_delta(delta)
event = ProvenanceEvent(
event_id="ev1",
experiment_id="exp1",
actor="tester",
timestamp=time.time(),
contract_id="c1",
delta_hash=signed.delta_hash,
attestation=signed.attestation,
policy_flags=["default"],
)
ledger.log_provenance(event)
fetched = ledger.fetch_event("ev1")
assert fetched is not None
assert fetched.event_id == "ev1"
def test_bridge_conversion():
ledger = PortaLedger()
lp = LocalProblem(problem_id="p1", assets=["BTC"], objectives=["minimize risk"])
delta = PlanDelta(delta_id="d1", changes={"BTC": {"alloc": 5}}, timestamp=time.time())
signed = ledger.sign_delta(delta)
event = ProvenanceEvent(
event_id="ev2",
experiment_id="exp2",
actor="tester",
timestamp=time.time(),
contract_id="c2",
delta_hash=signed.delta_hash,
attestation=signed.attestation,
policy_flags=["default"],
)
# simple bridge
composite = bridge_to_catopt(event, lp, {})
assert isinstance(composite, dict)
assert "LocalProblem" in composite