build(agent): semicolon#54de0b iteration

This commit is contained in:
agent-54de0bcc6a17828b 2026-04-24 18:09:42 +02:00
parent f3daff5ee0
commit 97f542d063
15 changed files with 981 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

37
AGENTS.md Normal file
View File

@ -0,0 +1,37 @@
# AGENTS.md
## Architecture
This repository implements `KappaBridge` as a Python package under `src/idea156_kappabridge_privacy_safe`.
Core modules:
- `contracts.py`: canonical contract models for `Object`, `SharedSignal`, `PlanDelta`, `DatasetSchema`, `AccessPolicy`, and audit/reproducibility records.
- `storage.py`: SQLite persistence with SQLAlchemy, including the contract table and tamper-evident governance ledger.
- `registry.py`: graph-of-contracts registry backed by `networkx`.
- `attestation.py`: Ed25519-based cryptographic attestations for contract use.
- `sync.py`: deterministic delta sync envelopes for offline-first reconnection.
- `adapters.py`: lab/data-format adapters for climate, epidemiology, genomics, ecology, and image datasets.
- `optimizer.py`: lightweight deterministic federated planner for joint experiment requests.
- `harness.py`: conformance harness for adapter validation.
## Tech Stack
- Python 3.11+
- Pydantic for contract validation
- SQLAlchemy + SQLite for durable persistence
- Cryptography for attestations
- NetworkX for the contract graph
- Pandas, Pillow, and NumPy for adapter support
## Testing
- Run `bash test.sh` to build the package and execute the full test suite.
- The script must continue to pass after any change to contracts, ledger hashing, or sync behavior.
## Rules
- Keep contract hashing canonical and deterministic.
- Do not mutate stored contracts; append new records instead.
- Preserve ledger ordering and hash chaining.
- When adding adapters, ensure they produce stable, validated manifests.
- Prefer the smallest correct change that keeps offline sync and governance auditable.

View File

@ -1,3 +1,51 @@
# idea156-kappabridge-privacy-safe # KappaBridge
Source logic for Idea #156 KappaBridge is a privacy-safe, offline-first scientific knowledge federation framework.
It provides a contract-based exchange layer for federating datasets and models across labs while preserving data sovereignty. The implementation in this repository focuses on the core control plane:
- Canonical contracts for datasets, access policy, objects, shared signals, plans, audit events, and reproducibility manifests.
- A Graph-of-Contracts registry backed by SQLite and SQLAlchemy.
- Tamper-evident governance ledger entries with hash chaining.
- Ed25519 attestations for contract use.
- Deterministic delta sync envelopes for intermittent connectivity.
- Adapters for climate tabular data, epidemiology CSVs, genomics FASTA, ecology JSONL, and image dataset manifests.
- A lightweight federated planner for joint experiment requests.
- A conformance harness and adapter marketplace for extension points.
## What It Solves
Scientific collaborations often break on data movement, trust, and connectivity. KappaBridge lets labs publish structured contracts instead of raw data, exchange signed governance records, and replay changes deterministically when connectivity returns.
## Package
- Name: `idea156-kappabridge-privacy-safe`
- Import path: `idea156_kappabridge_privacy_safe`
## Quick Start
```python
from idea156_kappabridge_privacy_safe import AccessPolicyContract, ContractRegistry, FederationStore
store = FederationStore("kappa.db")
registry = ContractRegistry(store)
policy = AccessPolicyContract(name="default-policy", allowed_actions=["read"])
registry.register(policy)
```
## Testing
Run the full package checks with:
```bash
bash test.sh
```
That script builds the distribution and runs the test suite.
## Design Rules
- Contract hashes must remain canonical and deterministic.
- Stored contracts are immutable; changes are appended as new records.
- Ledger ordering and hash chaining must be preserved.
- Adapter outputs must stay stable and validated.

32
pyproject.toml Normal file
View File

@ -0,0 +1,32 @@
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "idea156-kappabridge-privacy-safe"
version = "0.1.0"
description = "Privacy-safe offline-first scientific knowledge federation with contract-based sync and governance."
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"pydantic>=2.0",
"sqlalchemy>=2.0",
"cryptography>=42.0",
"networkx>=3.0",
"pandas>=2.0",
"pillow>=10.0",
"numpy>=1.26",
]
[project.optional-dependencies]
test = ["pytest>=8.0", "build>=1.0"]
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]
[tool.pytest.ini_options]
testpaths = ["tests"]
pythonpath = ["src"]

View File

@ -0,0 +1,48 @@
"""KappaBridge: privacy-safe offline-first scientific knowledge federation."""
from .adapters import (
AdapterMarketplace,
ClimateCSVAdapter,
EcologyJSONLAdapter,
EpidemiologyCSVAdapter,
GenomicsFASTAAdapter,
ImageDatasetAdapter,
)
from .attestation import AttestationAuthority, AttestationRecord
from .contracts import (
AccessPolicyContract,
AuditLogContract,
DatasetSchemaContract,
ObjectContract,
PlanDeltaContract,
ReproducibilityManifestContract,
SharedSignalContract,
)
from .optimizer import FederatedPlanner, LabProfile
from .registry import ContractRegistry
from .storage import FederationStore
from .sync import DeltaSyncEngine, SyncEnvelope
__all__ = [
"AccessPolicyContract",
"AdapterMarketplace",
"AttestationAuthority",
"AttestationRecord",
"AuditLogContract",
"ClimateCSVAdapter",
"ContractRegistry",
"DatasetSchemaContract",
"DeltaSyncEngine",
"EcologyJSONLAdapter",
"EpidemiologyCSVAdapter",
"FederatedPlanner",
"FederationStore",
"GenomicsFASTAAdapter",
"ImageDatasetAdapter",
"LabProfile",
"ObjectContract",
"PlanDeltaContract",
"ReproducibilityManifestContract",
"SharedSignalContract",
"SyncEnvelope",
]

View File

@ -0,0 +1,196 @@
from __future__ import annotations
from dataclasses import dataclass
from io import BytesIO, StringIO
import json
from pathlib import Path
from typing import Any, Protocol
import pandas as pd
from PIL import Image
from .contracts import (
AccessPolicyContract,
DatasetSchemaContract,
ObjectContract,
SchemaField,
SharedSignalContract,
)
@dataclass(frozen=True)
class AdaptedBundle:
dataset_schema: DatasetSchemaContract
object_contract: ObjectContract
shared_signals: list[SharedSignalContract]
metadata: dict[str, Any]
class DataAdapter(Protocol):
name: str
def adapt(self, source: Any, *, dataset_name: str, discipline: str) -> AdaptedBundle: ...
class ClimateCSVAdapter:
name = "climate-csv"
def adapt(self, source: str | Path, *, dataset_name: str, discipline: str = "climate") -> AdaptedBundle:
frame = pd.read_csv(source)
fields = [SchemaField(name=str(column), dtype=str(frame[column].dtype), required=True) for column in frame.columns]
payload = frame.to_csv(index=False).encode("utf-8")
schema = DatasetSchemaContract(
name=f"{dataset_name}-schema",
modality="tabular",
discipline=discipline,
fields=fields,
provenance={"rows": int(len(frame)), "columns": list(frame.columns)},
)
object_contract = ObjectContract(
name=f"{dataset_name}-object",
media_type="text/csv",
content_hash=_sha256(payload),
byte_size=len(payload),
source_uri=str(source),
)
signals = [
SharedSignalContract(
name=f"{dataset_name}-signal",
signal_name="tabular_summary",
source_object_id=object_contract.contract_id,
payload_hash=schema.digest(),
privacy_budget_spent=0.0,
)
]
return AdaptedBundle(schema, object_contract, signals, {"adapter": self.name, "rows": len(frame)})
class EpidemiologyCSVAdapter(ClimateCSVAdapter):
name = "epidemiology-csv"
def adapt(self, source: str | Path, *, dataset_name: str, discipline: str = "epidemiology") -> AdaptedBundle:
return super().adapt(source, dataset_name=dataset_name, discipline=discipline)
class GenomicsFASTAAdapter:
name = "genomics-fasta"
def adapt(self, source: str | Path, *, dataset_name: str, discipline: str = "genomics") -> AdaptedBundle:
text = Path(source).read_text(encoding="utf-8")
records = _parse_fasta(text)
fields = [SchemaField(name="sequence_id", dtype="string"), SchemaField(name="sequence", dtype="string")]
schema = DatasetSchemaContract(
name=f"{dataset_name}-schema",
modality="sequence",
discipline=discipline,
fields=fields,
provenance={"records": len(records)},
)
object_contract = ObjectContract(
name=f"{dataset_name}-object",
media_type="chemical/seq-na",
content_hash=_sha256(text.encode("utf-8")),
byte_size=len(text.encode("utf-8")),
source_uri=str(source),
)
signals = [
SharedSignalContract(
name=f"{dataset_name}-signal",
signal_name="sequence_inventory",
source_object_id=object_contract.contract_id,
payload_hash=schema.digest(),
)
]
return AdaptedBundle(schema, object_contract, signals, {"adapter": self.name, "records": len(records)})
class EcologyJSONLAdapter:
name = "ecology-jsonl"
def adapt(self, source: str | Path, *, dataset_name: str, discipline: str = "ecology") -> AdaptedBundle:
text = Path(source).read_text(encoding="utf-8")
rows = [json.loads(line) for line in text.splitlines() if line.strip()]
columns = sorted({key for row in rows for key in row.keys()})
fields = [SchemaField(name=column, dtype="string", required=False) for column in columns]
schema = DatasetSchemaContract(
name=f"{dataset_name}-schema",
modality="tabular",
discipline=discipline,
fields=fields,
provenance={"observations": len(rows)},
)
object_contract = ObjectContract(
name=f"{dataset_name}-object",
media_type="application/jsonl",
content_hash=_sha256(text.encode("utf-8")),
byte_size=len(text.encode("utf-8")),
source_uri=str(source),
)
return AdaptedBundle(schema, object_contract, [], {"adapter": self.name, "observations": len(rows)})
class ImageDatasetAdapter:
name = "image-dataset"
def adapt(self, source: str | Path, *, dataset_name: str, discipline: str = "cross-domain") -> AdaptedBundle:
path = Path(source)
images = sorted([p for p in path.iterdir() if p.is_file()])
rows = []
for image_path in images:
with Image.open(image_path) as img:
rows.append({"filename": image_path.name, "width": img.width, "height": img.height, "format": img.format or "unknown"})
frame = pd.DataFrame(rows)
fields = [SchemaField(name="filename", dtype="string"), SchemaField(name="width", dtype="integer"), SchemaField(name="height", dtype="integer"), SchemaField(name="format", dtype="string")]
schema = DatasetSchemaContract(
name=f"{dataset_name}-schema",
modality="image",
discipline=discipline,
fields=fields,
provenance={"images": len(images)},
)
manifest_bytes = frame.to_json(orient="records", indent=None).encode("utf-8")
object_contract = ObjectContract(
name=f"{dataset_name}-object",
media_type="application/vnd.kappabridge.image-manifest+json",
content_hash=_sha256(manifest_bytes),
byte_size=len(manifest_bytes),
source_uri=str(source),
)
return AdaptedBundle(schema, object_contract, [], {"adapter": self.name, "images": len(images)})
class AdapterMarketplace:
def __init__(self) -> None:
self._adapters: dict[str, DataAdapter] = {}
def register(self, adapter: DataAdapter) -> None:
self._adapters[adapter.name] = adapter
def get(self, name: str) -> DataAdapter:
return self._adapters[name]
def names(self) -> list[str]:
return sorted(self._adapters)
def _parse_fasta(text: str) -> list[tuple[str, str]]:
records: list[tuple[str, str]] = []
current_id: str | None = None
chunks: list[str] = []
for line in text.splitlines():
if line.startswith(">"):
if current_id is not None:
records.append((current_id, "".join(chunks)))
current_id = line[1:].strip()
chunks = []
elif line.strip():
chunks.append(line.strip())
if current_id is not None:
records.append((current_id, "".join(chunks)))
return records
def _sha256(data: bytes) -> str:
import hashlib
return hashlib.sha256(data).hexdigest()

View File

@ -0,0 +1,61 @@
from __future__ import annotations
from base64 import b64decode, b64encode
from datetime import datetime, timezone
from dataclasses import dataclass
from typing import Any
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from pydantic import BaseModel, ConfigDict, Field
from .contracts import BaseContract
class AttestationRecord(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid")
attestation_id: str
subject_contract_id: str
subject_hash: str
signer: str
signature_b64: str
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
claims: dict[str, Any] = Field(default_factory=dict)
@dataclass
class AttestationAuthority:
signer_id: str
private_key: Ed25519PrivateKey | None = None
def __post_init__(self) -> None:
if self.private_key is None:
self.private_key = Ed25519PrivateKey.generate()
@property
def public_key(self) -> Ed25519PublicKey:
assert self.private_key is not None
return self.private_key.public_key()
def attest(self, contract: BaseContract, **claims: Any) -> AttestationRecord:
payload = f"{contract.contract_id}|{contract.digest()}|{self.signer_id}".encode("utf-8")
assert self.private_key is not None
signature = self.private_key.sign(payload)
return AttestationRecord(
attestation_id=f"att-{contract.contract_id}",
subject_contract_id=contract.contract_id,
subject_hash=contract.digest(),
signer=self.signer_id,
signature_b64=b64encode(signature).decode("ascii"),
claims=claims,
)
def verify(self, attestation: AttestationRecord, contract: BaseContract, public_key: Ed25519PublicKey | None = None) -> bool:
key = public_key or self.public_key
payload = f"{contract.contract_id}|{contract.digest()}|{attestation.signer}".encode("utf-8")
try:
key.verify(b64decode(attestation.signature_b64), payload)
except InvalidSignature:
return False
return attestation.subject_contract_id == contract.contract_id and attestation.subject_hash == contract.digest()

View File

@ -0,0 +1,111 @@
from __future__ import annotations
from datetime import datetime, timezone
from enum import Enum
from hashlib import sha256
import json
from typing import Any, Literal
from uuid import uuid4
from pydantic import BaseModel, ConfigDict, Field
def _utcnow() -> datetime:
return datetime.now(timezone.utc)
def _canonical_payload(value: BaseModel | dict[str, Any]) -> str:
if isinstance(value, BaseModel):
payload = value.model_dump(mode="json", exclude={"contract_id", "created_at"})
else:
payload = value
return json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
class ContractKind(str, Enum):
object = "Object"
shared_signal = "SharedSignal"
plan_delta = "PlanDelta"
dataset_schema = "DatasetSchema"
access_policy = "AccessPolicy"
audit_log = "AuditLog"
reproducibility_manifest = "ReproducibilityManifest"
class BaseContract(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid")
contract_id: str = Field(default_factory=lambda: uuid4().hex)
kind: str
name: str
version: str = "1.0.0"
created_at: datetime = Field(default_factory=_utcnow)
parents: list[str] = Field(default_factory=list)
def digest(self) -> str:
return sha256(_canonical_payload(self).encode("utf-8")).hexdigest()
class SchemaField(BaseModel):
model_config = ConfigDict(frozen=True, extra="forbid")
name: str
dtype: str
required: bool = True
description: str | None = None
class DatasetSchemaContract(BaseContract):
kind: Literal["DatasetSchema"] = ContractKind.dataset_schema.value
modality: str
discipline: str
fields: list[SchemaField]
provenance: dict[str, Any] = Field(default_factory=dict)
class AccessPolicyContract(BaseContract):
kind: Literal["AccessPolicy"] = ContractKind.access_policy.value
allowed_actions: list[str]
privacy_budget_epsilon: float | None = None
requires_attestation: bool = True
data_residency: list[str] = Field(default_factory=list)
class ObjectContract(BaseContract):
kind: Literal["Object"] = ContractKind.object.value
media_type: str
content_hash: str
byte_size: int
source_uri: str | None = None
class SharedSignalContract(BaseContract):
kind: Literal["SharedSignal"] = ContractKind.shared_signal.value
signal_name: str
source_object_id: str
payload_hash: str
privacy_budget_spent: float = 0.0
class PlanDeltaContract(BaseContract):
kind: Literal["PlanDelta"] = ContractKind.plan_delta.value
objective: str
requested_contracts: list[str]
steps: list[str]
deterministic_key: str
class AuditLogContract(BaseContract):
kind: Literal["AuditLog"] = ContractKind.audit_log.value
actor: str
action: str
target_contract_id: str
details: dict[str, Any] = Field(default_factory=dict)
class ReproducibilityManifestContract(BaseContract):
kind: Literal["ReproducibilityManifest"] = ContractKind.reproducibility_manifest.value
execution_id: str
code_hash: str
data_hashes: list[str]
environment: dict[str, Any] = Field(default_factory=dict)

View File

@ -0,0 +1,29 @@
from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from typing import Any
from .adapters import AdaptedBundle, AdapterMarketplace, DataAdapter
@dataclass(frozen=True)
class ConformanceResult:
adapter_name: str
passed: bool
checks: list[str]
class ConformanceHarness:
def __init__(self, marketplace: AdapterMarketplace) -> None:
self.marketplace = marketplace
def validate(self, adapter_name: str, source: Any, *, dataset_name: str, discipline: str) -> ConformanceResult:
adapter: DataAdapter = self.marketplace.get(adapter_name)
bundle = adapter.adapt(source, dataset_name=dataset_name, discipline=discipline)
checks = [
f"schema:{bundle.dataset_schema.digest()}",
f"object:{bundle.object_contract.digest()}",
f"signals:{len(bundle.shared_signals)}",
]
return ConformanceResult(adapter_name=adapter_name, passed=bool(bundle.dataset_schema.fields and bundle.object_contract.content_hash), checks=checks)

View File

@ -0,0 +1,34 @@
from __future__ import annotations
from dataclasses import dataclass
from hashlib import sha256
from .contracts import PlanDeltaContract
@dataclass(frozen=True)
class LabProfile:
lab_name: str
discipline: str
modalities: list[str]
offline_capable: bool = True
privacy_budget_epsilon: float | None = None
class FederatedPlanner:
def plan_joint_objective(self, objective: str, labs: list[LabProfile], requested_contracts: list[str]) -> PlanDeltaContract:
ordered_labs = sorted(labs, key=lambda lab: (lab.discipline, lab.lab_name))
steps = [
f"request:{lab.lab_name}:{','.join(sorted(lab.modalities))}"
for lab in ordered_labs
if lab.offline_capable
]
steps.append(f"objective:{objective}")
deterministic_key = sha256("|".join([objective, *sorted(requested_contracts), *steps]).encode("utf-8")).hexdigest()
return PlanDeltaContract(
name="joint-plan",
objective=objective,
requested_contracts=sorted(requested_contracts),
steps=steps,
deterministic_key=deterministic_key,
)

View File

@ -0,0 +1,61 @@
from __future__ import annotations
from dataclasses import dataclass
import networkx as nx
from .contracts import BaseContract
from .storage import FederationStore
@dataclass(frozen=True)
class RegistryLookup:
contract_id: str
ancestors: list[str]
descendants: list[str]
class ContractRegistry:
def __init__(self, store: FederationStore) -> None:
self.store = store
self.graph = nx.DiGraph()
self._load_from_store()
def _load_from_store(self) -> None:
for record in self.store.contracts():
self.graph.add_node(record["contract_id"], **record)
for parent in record["parents"]:
self.graph.add_edge(parent, record["contract_id"])
def register(self, contract: BaseContract) -> str:
self.store.upsert_contract(contract)
self.graph.add_node(contract.contract_id, contract=contract, kind=contract.kind, name=contract.name)
for parent in contract.parents:
self.graph.add_edge(parent, contract.contract_id)
self.store.record_event(
"contract_added",
{
"contract": contract.model_dump(mode="json"),
"content_hash": contract.digest(),
"active": True,
},
)
return contract.contract_id
def get(self, contract_id: str) -> dict[str, object] | None:
return self.store.get_contract_record(contract_id)
def ancestry(self, contract_id: str) -> list[str]:
return sorted(nx.ancestors(self.graph, contract_id))
def descendants(self, contract_id: str) -> list[str]:
return sorted(nx.descendants(self.graph, contract_id))
def lookup(self, contract_id: str) -> RegistryLookup:
return RegistryLookup(contract_id=contract_id, ancestors=self.ancestry(contract_id), descendants=self.descendants(contract_id))
def active_contracts(self, kind: str | None = None) -> list[dict[str, object]]:
contracts = [record for record in self.store.contracts() if record["active"]]
if kind is not None:
contracts = [record for record in contracts if record["kind"] == kind]
return contracts

View File

@ -0,0 +1,188 @@
from __future__ import annotations
from datetime import datetime, timezone
import json
from pathlib import Path
from typing import Any, Iterable
from sqlalchemy import Boolean, DateTime, Integer, String, Text, create_engine, select
from sqlalchemy.orm import DeclarativeBase, Mapped, Session, mapped_column, sessionmaker
from .contracts import BaseContract
class Base(DeclarativeBase):
pass
class ContractRow(Base):
__tablename__ = "contracts"
contract_id: Mapped[str] = mapped_column(String(64), primary_key=True)
kind: Mapped[str] = mapped_column(String(64), index=True)
name: Mapped[str] = mapped_column(String(255), index=True)
version: Mapped[str] = mapped_column(String(32))
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
parents_json: Mapped[str] = mapped_column(Text)
payload_json: Mapped[str] = mapped_column(Text)
content_hash: Mapped[str] = mapped_column(String(64), index=True)
active: Mapped[bool] = mapped_column(Boolean, default=True, index=True)
class LedgerRow(Base):
__tablename__ = "governance_ledger"
seq: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True)
event_type: Mapped[str] = mapped_column(String(64), index=True)
event_json: Mapped[str] = mapped_column(Text)
prev_hash: Mapped[str] = mapped_column(String(64))
entry_hash: Mapped[str] = mapped_column(String(64), unique=True, index=True)
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), index=True)
class SyncStateRow(Base):
__tablename__ = "sync_state"
peer_id: Mapped[str] = mapped_column(String(128), primary_key=True)
last_seq: Mapped[int] = mapped_column(Integer, default=0)
def _utcnow() -> datetime:
return datetime.now(timezone.utc)
class FederationStore:
def __init__(self, path: str | Path = ":memory:") -> None:
self.path = str(path)
self.engine = create_engine(
f"sqlite:///{self.path}" if self.path != ":memory:" else "sqlite+pysqlite:///:memory:",
future=True,
)
Base.metadata.create_all(self.engine)
self.session_factory = sessionmaker(bind=self.engine, future=True)
def session(self) -> Session:
return self.session_factory()
def upsert_contract(self, contract: BaseContract, *, active: bool = True) -> None:
with self.session() as session:
row = ContractRow(
contract_id=contract.contract_id,
kind=contract.kind,
name=contract.name,
version=contract.version,
created_at=contract.created_at,
parents_json=json.dumps(contract.parents, sort_keys=True),
payload_json=json.dumps(contract.model_dump(mode="json"), sort_keys=True, separators=(",", ":"), ensure_ascii=True),
content_hash=contract.digest(),
active=active,
)
session.merge(row)
session.commit()
def contracts(self) -> list[dict[str, Any]]:
with self.session() as session:
rows = session.execute(select(ContractRow).order_by(ContractRow.created_at, ContractRow.contract_id)).scalars().all()
return [self._row_to_contract_dict(row) for row in rows]
def get_contract_record(self, contract_id: str) -> dict[str, Any] | None:
with self.session() as session:
row = session.get(ContractRow, contract_id)
return None if row is None else self._row_to_contract_dict(row)
def record_event(self, event_type: str, payload: dict[str, Any]) -> dict[str, Any]:
with self.session() as session:
last = session.execute(select(LedgerRow).order_by(LedgerRow.seq.desc())).scalars().first()
prev_hash = last.entry_hash if last else "0" * 64
seq = (last.seq if last else 0) + 1
event_json = json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
entry_hash = _hash_ledger(seq, event_type, event_json, prev_hash)
row = LedgerRow(
seq=seq,
event_type=event_type,
event_json=event_json,
prev_hash=prev_hash,
entry_hash=entry_hash,
created_at=_utcnow(),
)
session.add(row)
session.commit()
return self._ledger_row_to_dict(row)
def import_ledger_event(self, record: dict[str, Any]) -> dict[str, Any]:
with self.session() as session:
if session.execute(select(LedgerRow).where(LedgerRow.entry_hash == record["entry_hash"])).scalar_one_or_none():
return record
row = LedgerRow(
seq=record["seq"],
event_type=record["event_type"],
event_json=record["event_json"],
prev_hash=record["prev_hash"],
entry_hash=record["entry_hash"],
created_at=datetime.fromisoformat(record["created_at"]),
)
session.add(row)
session.commit()
payload = json.loads(row.event_json)
if row.event_type == "contract_added":
self.import_contract_payload(payload)
return self._ledger_row_to_dict(row)
def import_contract_payload(self, payload: dict[str, Any]) -> None:
contract = payload["contract"]
with self.session() as session:
row = ContractRow(
contract_id=contract["contract_id"],
kind=contract["kind"],
name=contract["name"],
version=contract["version"],
created_at=datetime.fromisoformat(contract["created_at"]),
parents_json=json.dumps(contract.get("parents", []), sort_keys=True),
payload_json=json.dumps(contract, sort_keys=True, separators=(",", ":"), ensure_ascii=True),
content_hash=payload["content_hash"],
active=payload.get("active", True),
)
session.merge(row)
session.commit()
def ledger_since(self, seq: int) -> list[dict[str, Any]]:
with self.session() as session:
rows = session.execute(select(LedgerRow).where(LedgerRow.seq > seq).order_by(LedgerRow.seq)).scalars().all()
return [self._ledger_row_to_dict(row) for row in rows]
def latest_seq(self) -> int:
with self.session() as session:
row = session.execute(select(LedgerRow).order_by(LedgerRow.seq.desc())).scalars().first()
return 0 if row is None else row.seq
@staticmethod
def _row_to_contract_dict(row: ContractRow) -> dict[str, Any]:
return {
"contract_id": row.contract_id,
"kind": row.kind,
"name": row.name,
"version": row.version,
"created_at": row.created_at.isoformat(),
"parents": json.loads(row.parents_json),
"payload_json": row.payload_json,
"content_hash": row.content_hash,
"active": row.active,
}
@staticmethod
def _ledger_row_to_dict(row: LedgerRow) -> dict[str, Any]:
return {
"seq": row.seq,
"event_type": row.event_type,
"event_json": row.event_json,
"prev_hash": row.prev_hash,
"entry_hash": row.entry_hash,
"created_at": row.created_at.isoformat(),
}
def _hash_ledger(seq: int, event_type: str, event_json: str, prev_hash: str) -> str:
import hashlib
data = f"{seq}|{event_type}|{event_json}|{prev_hash}".encode("utf-8")
return hashlib.sha256(data).hexdigest()

View File

@ -0,0 +1,31 @@
from __future__ import annotations
from dataclasses import dataclass
from .storage import FederationStore
@dataclass(frozen=True)
class SyncEnvelope:
anchor_seq: int
source: str
records: list[dict[str, object]]
class DeltaSyncEngine:
def __init__(self, store: FederationStore, peer_id: str) -> None:
self.store = store
self.peer_id = peer_id
def build_envelope(self, anchor_seq: int = 0) -> SyncEnvelope:
return SyncEnvelope(anchor_seq=anchor_seq, source=self.peer_id, records=self.store.ledger_since(anchor_seq))
def ingest_envelope(self, envelope: SyncEnvelope) -> int:
applied = 0
for record in envelope.records:
self.store.import_ledger_event(record)
applied += 1
return applied
def checkpoint(self) -> int:
return self.store.latest_seq()

4
test.sh Executable file
View File

@ -0,0 +1,4 @@
#!/usr/bin/env bash
set -euo pipefail
python3 -m build
pytest -q

78
tests/test_framework.py Normal file
View File

@ -0,0 +1,78 @@
from __future__ import annotations
from pathlib import Path
from idea156_kappabridge_privacy_safe import (
AccessPolicyContract,
AdapterMarketplace,
AttestationAuthority,
ClimateCSVAdapter,
ContractRegistry,
DeltaSyncEngine,
FederationStore,
FederatedPlanner,
GenomicsFASTAAdapter,
LabProfile,
)
from idea156_kappabridge_privacy_safe.harness import ConformanceHarness
def test_registry_ledger_and_attestation(tmp_path: Path) -> None:
store = FederationStore(tmp_path / "kappa.db")
registry = ContractRegistry(store)
policy = AccessPolicyContract(name="open-policy", allowed_actions=["read", "request"], privacy_budget_epsilon=1.0)
registry.register(policy)
authority = AttestationAuthority("lab-a")
attestation = authority.attest(policy, purpose="governance")
assert authority.verify(attestation, policy)
assert registry.active_contracts(kind="AccessPolicy")[0]["name"] == "open-policy"
assert store.latest_seq() == 1
def test_delta_sync_replays_ledger(tmp_path: Path) -> None:
left = FederationStore(tmp_path / "left.db")
right = FederationStore(tmp_path / "right.db")
left_registry = ContractRegistry(left)
right_registry = ContractRegistry(right)
policy = AccessPolicyContract(name="sync-policy", allowed_actions=["read"])
left_registry.register(policy)
engine = DeltaSyncEngine(left, peer_id="lab-left")
envelope = engine.build_envelope(anchor_seq=0)
assert right_registry.store.latest_seq() == 0
applied = DeltaSyncEngine(right, peer_id="lab-right").ingest_envelope(envelope)
assert applied == 1
assert right_registry.store.latest_seq() == 1
assert right_registry.get(policy.contract_id)["name"] == "sync-policy"
def test_adapters_and_planner_conformance(tmp_path: Path) -> None:
climate_csv = tmp_path / "climate.csv"
climate_csv.write_text("station,temp,humidity\nA,12,40\nB,15,55\n", encoding="utf-8")
fasta = tmp_path / "genome.fasta"
fasta.write_text(
">seq1\nACGT\n>seq2\nGGTT\n",
encoding="utf-8",
)
marketplace = AdapterMarketplace()
marketplace.register(ClimateCSVAdapter())
marketplace.register(GenomicsFASTAAdapter())
harness = ConformanceHarness(marketplace)
assert harness.validate("climate-csv", climate_csv, dataset_name="climate", discipline="climate").passed
assert harness.validate("genomics-fasta", fasta, dataset_name="genome", discipline="genomics").passed
planner = FederatedPlanner()
plan = planner.plan_joint_objective(
"joint climate-genomics study",
[LabProfile("lab-a", "climate", ["tabular"]), LabProfile("lab-b", "genomics", ["sequence"])],
["contract-a", "contract-b"],
)
assert plan.kind == "PlanDelta"
assert plan.steps