build(agent): c3po#b883b4 iteration

This commit is contained in:
agent-b883b4bc188823a2 2026-04-25 21:14:13 +02:00
parent 703e3ecbfe
commit 28ae65a4e7
16 changed files with 807 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

27
AGENTS.md Normal file
View File

@ -0,0 +1,27 @@
# MediMesh Contributor Guide
## Architecture
- `src/medimesh/contracts.py`: Graph-of-Contracts registry and schema conformance checks.
- `src/medimesh/solver.py`: Distributed allocator and policy weighting for scarce medical resources.
- `src/medimesh/governance.py`: DID-style identities, attestations, and tamper-evident SQLite ledger.
- `src/medimesh/sync.py`: Offline-first delta storage and deterministic replay.
- `src/medimesh/adapters.py`: Starter hospital inventory and EMS/logistics adapters.
- `src/medimesh/scenario.py`: End-to-end toy scenario for validation and demos.
## Tech Stack
- Python 3.10+
- `networkx` for contract graph modeling
- `cryptography` for Ed25519 signing and verification
- SQLite via the standard library for local durable state
- `pytest` for tests
## Rules
- Keep changes small and deterministic.
- Preserve privacy boundaries: patient data stays local and out of global coordination objects.
- Prefer aggregated signals over site-level reconstruction.
- Maintain hash-chain integrity in sync and governance stores.
- Update tests alongside behavior changes.
## Commands
- Install and test: `bash test.sh`
- Run the toy scenario: `python -m medimesh`

View File

@ -1,3 +1,34 @@
# idea150-medimesh-federated-privacy # MediMesh
Source logic for Idea #150 MediMesh is a federated coordination platform for disaster-affected health systems.
It keeps site-level data local, exchanges only aggregated signals, and uses a canonical contract registry to coordinate hospitals, clinics, EMS, pharmacies, and supply caches under privacy and governance constraints.
## What It Provides
- Graph-of-Contracts registry for schema and adapter governance
- Distributed allocator for scarce resources using policy-weighted coordination
- SQLite-backed tamper-evident governance log with cryptographic attestations
- Offline-first delta storage with deterministic replay for audits
- Starter hospital inventory and EMS/logistics adapters
- End-to-end toy scenario with a shortage shock
## Package Layout
- `medimesh.contracts`: canonical contracts and registry
- `medimesh.solver`: local and distributed allocation logic
- `medimesh.governance`: identities, credentials, and ledger
- `medimesh.sync`: offline delta sync and replay
- `medimesh.adapters`: reference controllers
- `medimesh.scenario`: demo orchestration
## Run
```bash
bash test.sh
python -m medimesh
```
## Packaging
- Python distribution name: `idea150-medimesh-federated-privacy`
- `readme = "README.md"` is wired into `pyproject.toml`

23
pyproject.toml Normal file
View File

@ -0,0 +1,23 @@
[build-system]
requires = ["setuptools>=68", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "idea150-medimesh-federated-privacy"
version = "0.1.0"
description = "Federated, privacy-preserving coordination for medical resource allocation in low-connectivity environments"
readme = "README.md"
requires-python = ">=3.10"
dependencies = [
"networkx>=3.2",
"cryptography>=42.0",
]
[project.optional-dependencies]
test = ["pytest>=8.0"]
[tool.setuptools]
package-dir = {"" = "src"}
[tool.setuptools.packages.find]
where = ["src"]

25
src/medimesh/__init__.py Normal file
View File

@ -0,0 +1,25 @@
"""MediMesh federated coordination toolkit."""
from .adapters import EMSDispatchLogisticsController, HospitalInventoryController
from .contracts import ContractRegistry, DataContract, GovernancePolicy
from .governance import Credential, GovernanceLedger, Identity, IdentityAuthority
from .scenario import run_toy_scenario
from .solver import DistributedAllocator, HumanitarianPolicy, SiteState
from .sync import OfflineDeltaStore
__all__ = [
"ContractRegistry",
"DataContract",
"GovernancePolicy",
"Credential",
"GovernanceLedger",
"Identity",
"IdentityAuthority",
"DistributedAllocator",
"HumanitarianPolicy",
"SiteState",
"OfflineDeltaStore",
"HospitalInventoryController",
"EMSDispatchLogisticsController",
"run_toy_scenario",
]

13
src/medimesh/__main__.py Normal file
View File

@ -0,0 +1,13 @@
from __future__ import annotations
import json
from .scenario import run_toy_scenario
def main() -> None:
print(json.dumps(run_toy_scenario(), indent=2, sort_keys=True))
if __name__ == "__main__":
main()

39
src/medimesh/adapters.py Normal file
View File

@ -0,0 +1,39 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Mapping
from .solver import AllocationResult, DistributedAllocator, HumanitarianPolicy, SiteState
@dataclass(frozen=True)
class HospitalInventoryController:
site_id: str
region: str
on_hand: float
forecast_demand: float
minimum_service: float = 0.0
def to_site_state(self) -> SiteState:
return SiteState(
site_id=self.site_id,
region=self.region,
on_hand=self.on_hand,
forecast_demand=self.forecast_demand,
minimum_service=self.minimum_service,
)
def recommend(self, allocator: DistributedAllocator, total_available: float, policy: HumanitarianPolicy) -> AllocationResult:
return allocator.allocate([self.to_site_state()], total_available=total_available, policy=policy)
@dataclass(frozen=True)
class EMSDispatchLogisticsController:
dispatch_base: str
region: str
def prioritize_transfers(self, allocations: Mapping[str, float], shortage_signal: float) -> dict[str, float]:
if shortage_signal <= 0:
return {site_id: 0.0 for site_id in allocations}
total = sum(max(0.0, amount) for amount in allocations.values()) or 1.0
return {site_id: round(shortage_signal * amount / total, 4) for site_id, amount in allocations.items()}

102
src/medimesh/contracts.py Normal file
View File

@ -0,0 +1,102 @@
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any, Dict, Iterable, List, Mapping, Optional
import networkx as nx
@dataclass(frozen=True)
class DataContract:
name: str
version: str
required_fields: tuple[str, ...]
optional_fields: tuple[str, ...] = ()
owner: str = ""
classification: str = "internal"
@property
def node_id(self) -> str:
return f"{self.name}@{self.version}"
def validate(self, payload: Mapping[str, Any]) -> list[str]:
missing = [field for field in self.required_fields if field not in payload]
if missing:
raise ValueError(f"payload missing required fields: {', '.join(missing)}")
return sorted(set(payload.keys()) - set(self.required_fields) - set(self.optional_fields))
@dataclass(frozen=True)
class GovernancePolicy:
name: str
version: str
triage_priority: float = 1.0
equity_weight: float = 1.0
continuity_weight: float = 1.0
regional_floor: float = 0.0
@property
def node_id(self) -> str:
return f"policy:{self.name}@{self.version}"
@dataclass(frozen=True)
class AdapterContractBinding:
adapter_name: str
reads: tuple[str, ...]
writes: tuple[str, ...]
governed_by: tuple[str, ...] = ()
class ContractRegistry:
def __init__(self) -> None:
self.graph: nx.DiGraph = nx.DiGraph()
def register_contract(self, contract: DataContract) -> None:
self.graph.add_node(
contract.node_id,
kind="contract",
contract=contract,
)
def register_policy(self, policy: GovernancePolicy) -> None:
self.graph.add_node(
policy.node_id,
kind="policy",
policy=policy,
)
def register_adapter(self, binding: AdapterContractBinding) -> None:
self.graph.add_node(
binding.adapter_name,
kind="adapter",
binding=binding,
)
for contract_id in binding.reads:
self.graph.add_edge(contract_id, binding.adapter_name, relation="feeds")
for contract_id in binding.writes:
self.graph.add_edge(binding.adapter_name, contract_id, relation="produces")
for policy_id in binding.governed_by:
self.graph.add_edge(policy_id, binding.adapter_name, relation="governs")
def register_flow(self, upstream: str, downstream: str, relation: str = "depends_on") -> None:
self.graph.add_edge(upstream, downstream, relation=relation)
def conformance_check(self, contract_id: str, payload: Mapping[str, Any]) -> list[str]:
contract = self.resolve_contract(contract_id)
return contract.validate(payload)
def resolve_contract(self, contract_id: str) -> DataContract:
node = self.graph.nodes[contract_id]
if node.get("kind") != "contract":
raise KeyError(f"{contract_id} is not a registered contract")
return node["contract"]
def lineage(self, node_id: str) -> list[str]:
return list(nx.ancestors(self.graph, node_id))
def topology(self) -> dict[str, list[tuple[str, str]]]:
return {
node: sorted((neighbor, data.get("relation", "")) for _, neighbor, data in self.graph.out_edges(node, data=True))
for node in self.graph.nodes
}

157
src/medimesh/governance.py Normal file
View File

@ -0,0 +1,157 @@
from __future__ import annotations
import base64
import hashlib
import json
import sqlite3
from dataclasses import dataclass
from datetime import datetime, timezone
from pathlib import Path
from typing import Any, Iterable, Mapping
from cryptography.exceptions import InvalidSignature
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
def _canonical_json(value: Any) -> str:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
def _hash_bytes(*parts: bytes) -> str:
digest = hashlib.sha256()
for part in parts:
digest.update(part)
return digest.hexdigest()
@dataclass(frozen=True)
class Identity:
did: str
public_key: bytes
private_key: Ed25519PrivateKey
def sign(self, payload: bytes) -> bytes:
return self.private_key.sign(payload)
def verify(self, payload: bytes, signature: bytes) -> None:
Ed25519PublicKey.from_public_bytes(self.public_key).verify(signature, payload)
class IdentityAuthority:
@staticmethod
def create(label: str) -> Identity:
private_key = Ed25519PrivateKey.generate()
public_key = private_key.public_key().public_bytes(Encoding.Raw, PublicFormat.Raw)
fingerprint = hashlib.sha256(public_key).hexdigest()[:24]
did = f"did:medimesh:{label}:{fingerprint}"
return Identity(did=did, public_key=public_key, private_key=private_key)
@dataclass(frozen=True)
class Credential:
subject_did: str
issuer_did: str
scopes: tuple[str, ...]
issued_at: str
expires_at: str
signature: str
def is_valid(self, now: datetime | None = None) -> bool:
now = now or datetime.now(timezone.utc)
expires = datetime.fromisoformat(self.expires_at)
return now <= expires
class GovernanceLedger:
def __init__(self, db_path: str | Path = ":memory:") -> None:
self.db_path = str(db_path)
self.conn = sqlite3.connect(self.db_path)
self.conn.row_factory = sqlite3.Row
self._ensure_schema()
def _ensure_schema(self) -> None:
self.conn.execute(
"""
CREATE TABLE IF NOT EXISTS ledger_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ts TEXT NOT NULL,
actor_did TEXT NOT NULL,
event_type TEXT NOT NULL,
payload TEXT NOT NULL,
prev_hash TEXT NOT NULL,
entry_hash TEXT NOT NULL,
signature TEXT NOT NULL
)
"""
)
self.conn.commit()
def latest_hash(self) -> str:
row = self.conn.execute("SELECT entry_hash FROM ledger_entries ORDER BY id DESC LIMIT 1").fetchone()
return row["entry_hash"] if row else "GENESIS"
def append(self, actor: Identity, event_type: str, payload: Mapping[str, Any]) -> dict[str, Any]:
ts = datetime.now(timezone.utc).isoformat()
prev_hash = self.latest_hash()
payload_json = _canonical_json(payload)
entry_hash = _hash_bytes(prev_hash.encode(), ts.encode(), actor.did.encode(), event_type.encode(), payload_json.encode())
signature = actor.sign(entry_hash.encode())
record = {
"ts": ts,
"actor_did": actor.did,
"event_type": event_type,
"payload": payload_json,
"prev_hash": prev_hash,
"entry_hash": entry_hash,
"signature": base64.b64encode(signature).decode(),
}
self.conn.execute(
"""
INSERT INTO ledger_entries (ts, actor_did, event_type, payload, prev_hash, entry_hash, signature)
VALUES (:ts, :actor_did, :event_type, :payload, :prev_hash, :entry_hash, :signature)
""",
record,
)
self.conn.commit()
return record
def entries(self) -> list[dict[str, Any]]:
rows = self.conn.execute("SELECT * FROM ledger_entries ORDER BY id ASC").fetchall()
return [dict(row) for row in rows]
def verify_chain(self, identity_lookup: Mapping[str, Identity]) -> bool:
previous = "GENESIS"
for entry in self.entries():
payload = entry["payload"]
recalculated = _hash_bytes(
previous.encode(),
entry["ts"].encode(),
entry["actor_did"].encode(),
entry["event_type"].encode(),
payload.encode(),
)
if recalculated != entry["entry_hash"]:
return False
actor = identity_lookup.get(entry["actor_did"])
if actor is None:
return False
try:
actor.verify(entry["entry_hash"].encode(), base64.b64decode(entry["signature"]))
except InvalidSignature:
return False
previous = entry["entry_hash"]
return True
def issue_credential(self, issuer: Identity, subject_did: str, scopes: Iterable[str], ttl_seconds: int = 900) -> Credential:
now = datetime.now(timezone.utc)
expires = now.timestamp() + ttl_seconds
body = {
"subject_did": subject_did,
"issuer_did": issuer.did,
"scopes": tuple(scopes),
"issued_at": now.isoformat(),
"expires_at": datetime.fromtimestamp(expires, tz=timezone.utc).isoformat(),
}
signature = base64.b64encode(issuer.sign(_canonical_json(body).encode())).decode()
return Credential(signature=signature, **body)

77
src/medimesh/scenario.py Normal file
View File

@ -0,0 +1,77 @@
from __future__ import annotations
from dataclasses import asdict
from .adapters import EMSDispatchLogisticsController, HospitalInventoryController
from .contracts import AdapterContractBinding, ContractRegistry, DataContract, GovernancePolicy
from .governance import GovernanceLedger, IdentityAuthority
from .solver import DistributedAllocator, HumanitarianPolicy, SiteState
from .sync import OfflineDeltaStore
def run_toy_scenario() -> dict:
registry = ContractRegistry()
inventory_contract = DataContract(
name="inventory_snapshot",
version="1.0",
required_fields=("site_id", "resource", "on_hand", "forecast_demand"),
optional_fields=("minimum_service",),
owner="hospital",
)
dispatch_contract = DataContract(
name="dispatch_plan",
version="1.0",
required_fields=("site_id", "transfer_priority", "shortage_signal"),
owner="ems",
)
policy = GovernancePolicy(name="humanitarian", version="1.0", triage_priority=1.5, equity_weight=1.2, continuity_weight=1.1)
registry.register_contract(inventory_contract)
registry.register_contract(dispatch_contract)
registry.register_policy(policy)
registry.register_adapter(
AdapterContractBinding(
adapter_name="hospital-inventory-controller",
reads=(inventory_contract.node_id,),
writes=(dispatch_contract.node_id,),
governed_by=(policy.node_id,),
)
)
authority = IdentityAuthority.create("medimesh")
ledger = GovernanceLedger()
delta_store = OfflineDeltaStore()
allocator = DistributedAllocator(iterations=10)
policy_runtime = HumanitarianPolicy(equity_weight=1.2, continuity_weight=1.1, triage_weight=1.5)
sites = [
SiteState("hospital-a", "north", 32, 46, equity_weight=1.4, continuity_weight=1.2, triage_priority=1.5, minimum_service=10),
SiteState("hospital-b", "central", 18, 40, equity_weight=1.1, continuity_weight=1.5, triage_priority=1.2, minimum_service=8),
SiteState("clinic-c", "south", 7, 21, equity_weight=1.8, continuity_weight=0.9, triage_priority=1.4, minimum_service=4),
SiteState("pharmacy-d", "east", 15, 12, equity_weight=0.9, continuity_weight=1.0, triage_priority=0.8, minimum_service=2),
]
ledger.append(authority, "scenario.initialized", {"sites": [site.site_id for site in sites]})
for site in sites:
delta_store.append(site.site_id, "set", {"on_hand": site.on_hand, "forecast_demand": site.forecast_demand}, authority.did)
shortage_shock = 28.0
result = allocator.allocate(sites, total_available=52.0 - shortage_shock, policy=policy_runtime)
ems = EMSDispatchLogisticsController(dispatch_base="ems-hub-1", region="central")
transfers = ems.prioritize_transfers(result.allocations, shortage_signal=shortage_shock)
ledger.append(authority, "allocation.issued", {"allocations": result.allocations, "shortage_shock": shortage_shock})
for site_id, allocation in result.allocations.items():
delta_store.append(site_id, "increment", {"allocated": allocation}, authority.did)
hospital = HospitalInventoryController("hospital-a", "north", 32, 46, minimum_service=10)
hospital_result = hospital.recommend(allocator, total_available=24.0, policy=policy_runtime)
return {
"registry_nodes": sorted(registry.graph.nodes),
"ledger_verified": ledger.verify_chain({authority.did: authority}),
"delta_replay": {site.site_id: delta_store.replay(site.site_id) for site in sites},
"allocation": result.allocations,
"unmet": result.unmet_demand,
"transfers": transfers,
"hospital_recommendation": hospital_result.allocations,
}

91
src/medimesh/solver.py Normal file
View File

@ -0,0 +1,91 @@
from __future__ import annotations
from dataclasses import dataclass
from typing import Iterable, Mapping
@dataclass(frozen=True)
class SiteState:
site_id: str
region: str
on_hand: float
forecast_demand: float
equity_weight: float = 1.0
continuity_weight: float = 1.0
triage_priority: float = 1.0
minimum_service: float = 0.0
@dataclass(frozen=True)
class HumanitarianPolicy:
equity_weight: float = 1.0
continuity_weight: float = 1.0
triage_weight: float = 1.0
regional_floor: float = 0.0
def site_prior(self, site: SiteState) -> float:
return (
site.equity_weight * self.equity_weight
+ site.continuity_weight * self.continuity_weight
+ site.triage_priority * self.triage_weight
)
@dataclass(frozen=True)
class AllocationResult:
allocations: dict[str, float]
unmet_demand: dict[str, float]
total_allocated: float
iterations: int
class DistributedAllocator:
def __init__(self, iterations: int = 8, step_size: float = 0.5) -> None:
self.iterations = iterations
self.step_size = step_size
@staticmethod
def _project_to_sum(values: list[float], total: float, lowers: list[float], uppers: list[float]) -> list[float]:
if not values:
return []
low = min(v - u for v, u in zip(values, uppers)) - abs(total)
high = max(v - l for v, l in zip(values, lowers)) + abs(total)
for _ in range(80):
shift = (low + high) / 2.0
projected = [min(upper, max(lower, value - shift)) for value, lower, upper in zip(values, lowers, uppers)]
current = sum(projected)
if abs(current - total) < 1e-8:
return projected
if current > total:
low = shift
else:
high = shift
shift = (low + high) / 2.0
return [min(upper, max(lower, value - shift)) for value, lower, upper in zip(values, lowers, uppers)]
def allocate(self, sites: Iterable[SiteState], total_available: float, policy: HumanitarianPolicy | None = None) -> AllocationResult:
policy = policy or HumanitarianPolicy()
sites = list(sites)
if not sites:
return AllocationResult({}, {}, 0.0, 0)
lowers = [max(0.0, site.minimum_service) for site in sites]
uppers = [max(lower, min(site.forecast_demand, site.on_hand + total_available)) for site, lower in zip(sites, lowers)]
weights = [max(0.1, policy.site_prior(site)) for site in sites]
total = min(total_available, sum(uppers))
x = [min(upper, max(lower, total * weight / sum(weights))) for weight, lower, upper in zip(weights, lowers, uppers)]
duals = [0.0 for _ in sites]
for _ in range(self.iterations):
proposals = []
for idx, site in enumerate(sites):
target = min(site.forecast_demand, site.on_hand + total_available)
prior = policy.site_prior(site)
local_update = x[idx] + self.step_size * ((target - x[idx]) / max(1.0, target) + prior - duals[idx])
proposals.append(local_update)
x = self._project_to_sum(proposals, total, lowers, uppers)
duals = [dual + proposal - alloc for dual, proposal, alloc in zip(duals, proposals, x)]
allocations = {site.site_id: round(value, 4) for site, value in zip(sites, x)}
unmet = {site.site_id: round(max(0.0, site.forecast_demand - allocations[site.site_id]), 4) for site in sites}
return AllocationResult(allocations=allocations, unmet_demand=unmet, total_allocated=round(sum(x), 4), iterations=self.iterations)

133
src/medimesh/sync.py Normal file
View File

@ -0,0 +1,133 @@
from __future__ import annotations
import hashlib
import json
import sqlite3
from dataclasses import dataclass
from pathlib import Path
from typing import Any, Iterable, Mapping
def _canonical_json(value: Any) -> str:
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True)
def _hash_chain(*parts: str) -> str:
digest = hashlib.sha256()
for part in parts:
digest.update(part.encode())
return digest.hexdigest()
@dataclass(frozen=True)
class DeltaRecord:
site_id: str
sequence: int
kind: str
payload: dict[str, Any]
author_did: str
prev_hash: str
hash: str
class OfflineDeltaStore:
def __init__(self, db_path: str | Path = ":memory:") -> None:
self.db_path = str(db_path)
self.conn = sqlite3.connect(self.db_path)
self.conn.row_factory = sqlite3.Row
self._ensure_schema()
def _ensure_schema(self) -> None:
self.conn.execute(
"""
CREATE TABLE IF NOT EXISTS deltas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
site_id TEXT NOT NULL,
sequence INTEGER NOT NULL,
kind TEXT NOT NULL,
payload TEXT NOT NULL,
author_did TEXT NOT NULL,
prev_hash TEXT NOT NULL,
hash TEXT NOT NULL
)
"""
)
self.conn.execute("CREATE UNIQUE INDEX IF NOT EXISTS idx_deltas_site_sequence ON deltas(site_id, sequence)")
self.conn.commit()
def latest_sequence(self, site_id: str) -> int:
row = self.conn.execute("SELECT MAX(sequence) AS sequence FROM deltas WHERE site_id = ?", (site_id,)).fetchone()
return int(row["sequence"] or 0)
def latest_hash(self, site_id: str) -> str:
row = self.conn.execute("SELECT hash FROM deltas WHERE site_id = ? ORDER BY sequence DESC LIMIT 1", (site_id,)).fetchone()
return row["hash"] if row else "GENESIS"
def append(self, site_id: str, kind: str, payload: Mapping[str, Any], author_did: str) -> DeltaRecord:
sequence = self.latest_sequence(site_id) + 1
prev_hash = self.latest_hash(site_id)
payload_json = _canonical_json(payload)
hash_value = _hash_chain(site_id, str(sequence), kind, payload_json, author_did, prev_hash)
self.conn.execute(
"""
INSERT INTO deltas (site_id, sequence, kind, payload, author_did, prev_hash, hash)
VALUES (?, ?, ?, ?, ?, ?, ?)
""",
(site_id, sequence, kind, payload_json, author_did, prev_hash, hash_value),
)
self.conn.commit()
return DeltaRecord(site_id, sequence, kind, dict(payload), author_did, prev_hash, hash_value)
def records(self, site_id: str) -> list[DeltaRecord]:
rows = self.conn.execute("SELECT * FROM deltas WHERE site_id = ? ORDER BY sequence ASC", (site_id,)).fetchall()
return [
DeltaRecord(
site_id=row["site_id"],
sequence=row["sequence"],
kind=row["kind"],
payload=json.loads(row["payload"]),
author_did=row["author_did"],
prev_hash=row["prev_hash"],
hash=row["hash"],
)
for row in rows
]
def delta_since(self, site_id: str, sequence: int) -> list[DeltaRecord]:
rows = self.conn.execute(
"SELECT * FROM deltas WHERE site_id = ? AND sequence > ? ORDER BY sequence ASC",
(site_id, sequence),
).fetchall()
return [
DeltaRecord(
site_id=row["site_id"],
sequence=row["sequence"],
kind=row["kind"],
payload=json.loads(row["payload"]),
author_did=row["author_did"],
prev_hash=row["prev_hash"],
hash=row["hash"],
)
for row in rows
]
def replay(self, site_id: str) -> dict[str, Any]:
state: dict[str, Any] = {}
for record in self.records(site_id):
if record.kind == "set":
state.update(record.payload)
elif record.kind == "increment":
for key, value in record.payload.items():
state[key] = state.get(key, 0) + value
else:
state[record.kind] = record.payload
return state
def verify_chain(self, site_id: str) -> bool:
previous = "GENESIS"
for record in self.records(site_id):
recalculated = _hash_chain(record.site_id, str(record.sequence), record.kind, _canonical_json(record.payload), record.author_did, previous)
if recalculated != record.hash:
return False
previous = record.hash
return True

7
test.sh Executable file
View File

@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
python3 -m pip install -e .[test]
python3 -m pip install build
pytest
python3 -m build

14
tests/test_contracts.py Normal file
View File

@ -0,0 +1,14 @@
from medimesh.contracts import AdapterContractBinding, ContractRegistry, DataContract, GovernancePolicy
def test_contract_registry_conformance_and_lineage() -> None:
registry = ContractRegistry()
contract = DataContract("inventory_snapshot", "1.0", ("site_id", "on_hand"), ("forecast_demand",))
policy = GovernancePolicy("humanitarian", "1.0")
registry.register_contract(contract)
registry.register_policy(policy)
registry.register_adapter(AdapterContractBinding("adapter", (contract.node_id,), (), (policy.node_id,)))
assert registry.conformance_check(contract.node_id, {"site_id": "a", "on_hand": 10, "forecast_demand": 8}) == []
assert contract.node_id in registry.lineage("adapter")
assert policy.node_id in registry.lineage("adapter")

View File

@ -0,0 +1,21 @@
from medimesh.governance import GovernanceLedger, IdentityAuthority
from medimesh.sync import OfflineDeltaStore
def test_governance_chain_verifies_and_credentials_are_short_lived() -> None:
authority = IdentityAuthority.create("tester")
ledger = GovernanceLedger()
ledger.append(authority, "site.registered", {"site_id": "hospital-a"})
cred = ledger.issue_credential(authority, "did:medimesh:subject", ["read:inventory"], ttl_seconds=60)
assert cred.is_valid()
assert ledger.verify_chain({authority.did: authority})
def test_delta_sync_replay_is_deterministic() -> None:
store = OfflineDeltaStore()
store.append("hospital-a", "set", {"on_hand": 10}, "did:medimesh:test")
store.append("hospital-a", "increment", {"on_hand": -3}, "did:medimesh:test")
assert store.verify_chain("hospital-a")
assert store.replay("hospital-a")["on_hand"] == 7

View File

@ -0,0 +1,24 @@
from medimesh.scenario import run_toy_scenario
from medimesh.solver import DistributedAllocator, HumanitarianPolicy, SiteState
def test_allocator_respects_supply_and_bounds() -> None:
allocator = DistributedAllocator(iterations=6)
sites = [
SiteState("a", "north", 10, 12, minimum_service=3),
SiteState("b", "south", 5, 8, minimum_service=2),
]
result = allocator.allocate(sites, total_available=9.0, policy=HumanitarianPolicy())
assert round(result.total_allocated, 4) <= 9.0
assert result.allocations["a"] >= 3
assert result.allocations["b"] >= 2
def test_toy_scenario_runs_end_to_end() -> None:
outcome = run_toy_scenario()
assert outcome["ledger_verified"] is True
assert "hospital-a" in outcome["allocation"]
assert outcome["registry_nodes"]