build(agent): c3po#b883b4 iteration

This commit is contained in:
agent-b883b4bc188823a2 2026-04-25 21:29:13 +02:00
parent a892c114cd
commit 27203fd181
10 changed files with 743 additions and 65 deletions

View File

@ -2,15 +2,15 @@
Architectural rules and contribution guidelines for HopeMesh 2.0 bootstrap. Architectural rules and contribution guidelines for HopeMesh 2.0 bootstrap.
- Language: Python. Packaging via pyproject.toml with setuptools. - Language: Python. Packaging via `pyproject.toml` with setuptools.
- Key modules: - Core modules:
- hopemesh.contracts: Graph-of-Contracts registry skeleton and primitive types. - `hopemesh.core`: canonical primitives and fingerprinting helpers.
- hopemesh.sdk: Tiny DSL-like helpers for LocalProblem, SharedVariables, PlanDelta, Policy, AttestationHint, AuditLog, PrivacyBudget. - `hopemesh.contracts`: Graph-of-Contracts registry plus tamper-evident governance ledger.
- hopemesh.cli: Lightweight command-line interface stub for adapters and governance operations. - `hopemesh.sdk`: DSL-style constructors and delta merge helpers.
- `hopemesh.cli`: local registry and ledger CLI.
- Tests and build: - Testing:
- test.sh should run: python3 -m build and a quick import/instantiate smoke test. - `test.sh` runs `python3 -m build`, a smoke import/instantiate check, and `python3 -m unittest discover -s tests -p 'test_*.py'`.
- Publishing: - Publishing:
- Create READY_TO_PUBLISH when all checks pass. - Create `READY_TO_PUBLISH` only when the repo is fully green and the implementation scope matches the published description.
This document is a placeholder for the initial bootstrap rules. As the repo evolves, update with concrete testing commands and CI expectations. Keep changes small, deterministic, and standard-library only unless a new dependency is explicitly justified.

View File

@ -1,29 +1,40 @@
# HopeMesh 2.0 (Skeleton) # HopeMesh 2.0
This repository contains a production-ready skeleton for a federated, privacy-preserving humanitarian resource allocation platform as described in the HopeMesh 2.0 concept. The initial code focuses on: HopeMesh 2.0 is a Python package for federated humanitarian allocation workflows in disrupted networks.
- Defining canonical primitives: LocalProblem, SharedVariables, PlanDelta, Policy, AttestationHint, AuditLog, PrivacyBudget.
- A Graph-of-Contracts registry scaffold to manage data schemas and adapters.
- A minimal, deterministic SDK for building adapters that interoperate with the primitives.
- Packaging metadata and a test script that validates import/instantiation and packaging.
How to run It provides:
- Ensure Python 3.8+ is installed. - Canonical primitives for `LocalProblem`, `SharedVariables`, `PlanDelta`, `Policy`, `AttestationHint`, `AuditLog`, and `PrivacyBudget`.
- Install build tooling and build the package: - A Graph-of-Contracts registry for versioned schemas and adapters.
- python3 -m pip install --upgrade build - A tamper-evident governance ledger.
- python3 -m build - Small SDK helpers for deterministic construction and delta merging.
- Smoke test: - A CLI for local registry and ledger snapshots.
- python3 - << 'PY'
from hopemesh.sdk import LocalProblem, SharedVariables
lp = LocalProblem(name="RegionA Allocation")
sv = SharedVariables(aggregate_type="sum")
print(lp, sv)
PY
This repo is intentionally minimal to bootstrap the architecture and testing workflow. A more complete implementation will grow the registry, adapters, and cryptographic governance features in subsequent commits. ## Layout
Usage notes: - `hopemesh/core.py`: primitive dataclasses and fingerprinting helpers.
- Run tests via ./test.sh - `hopemesh/contracts.py`: Graph-of-Contracts and governance ledger.
- The test script builds the package and runs a smoke test importing and instantiating primitives. - `hopemesh/sdk.py`: DSL-style constructors and merge helpers.
- After successful tests, create a READY_TO_PUBLISH marker in the repo root. - `hopemesh/cli.py`: local CLI for registry and ledger operations.
- `tests/`: unit tests for primitives, registry, ledger, and helpers.
READY_TO_PUBLISH is created when all checks pass. ## Quick Start
```bash
python3 -m pip install --upgrade build
python3 -m build
./test.sh
```
## CLI
```bash
python -m hopemesh.cli --version
python -m hopemesh.cli registry init --store .hopemesh/state.json
python -m hopemesh.cli registry add-schema --name allocation --schema-version 1.0 --definition-json '{"fields":["region","qty"]}'
python -m hopemesh.cli ledger verify
```
## Notes
- The project is intentionally dependency-light and uses the Python standard library for validation, hashing, and snapshotting.
- `test.sh` runs `python3 -m build`, a smoke import/instantiate check, and the unit test suite.

View File

@ -1,8 +1,18 @@
"""hopemesh package root (skeleton). """HopeMesh 2.0 primitives and registry scaffolding."""
Minimal primitives for HopeMesh 2.0 prototype.
"""
from .core import LocalProblem, SharedVariables, PlanDelta, Policy, AttestationHint, AuditLog, PrivacyBudget from .core import LocalProblem, SharedVariables, PlanDelta, Policy, AttestationHint, AuditLog, PrivacyBudget
from .contracts import ContractRecord, ContractLink, GraphOfContracts, GovernanceLedger, LedgerEntry
from .sdk import (
make_local_problem,
make_shared_variables,
make_plan_delta,
make_policy,
make_attestation_hint,
make_audit_log,
make_privacy_budget,
merge_plan_deltas,
validate_deltas,
)
__all__ = [ __all__ = [
"LocalProblem", "LocalProblem",
@ -12,4 +22,18 @@ __all__ = [
"AttestationHint", "AttestationHint",
"AuditLog", "AuditLog",
"PrivacyBudget", "PrivacyBudget",
"ContractRecord",
"ContractLink",
"GraphOfContracts",
"GovernanceLedger",
"LedgerEntry",
"make_local_problem",
"make_shared_variables",
"make_plan_delta",
"make_policy",
"make_attestation_hint",
"make_audit_log",
"make_privacy_budget",
"merge_plan_deltas",
"validate_deltas",
] ]

View File

@ -1,17 +1,144 @@
"""Lightweight CLI stub for adapters and governance ops.""" """CLI for HopeMesh registry and governance snapshots."""
from __future__ import annotations
import argparse import argparse
import json
from pathlib import Path
from typing import Any, Dict
def main(): from .contracts import GraphOfContracts, GovernanceLedger
parser = argparse.ArgumentParser(prog="hopemesh", description="HopeMesh 2.0 skeleton CLI")
subparsers = parser.add_subparsers(dest="cmd", required=False)
parser.add_argument("--version", action="store_true", help="Show version")
args = parser.parse_args() DEFAULT_STORE = Path(".hopemesh/state.json")
if args.version:
def _load_state(store: Path) -> Dict[str, Any]:
if not store.exists():
return {"registry": {"records": [], "links": []}, "ledger": {"entries": []}}
return json.loads(store.read_text(encoding="utf-8"))
def _save_state(store: Path, state: Dict[str, Any]) -> None:
store.parent.mkdir(parents=True, exist_ok=True)
store.write_text(json.dumps(state, indent=2, sort_keys=True), encoding="utf-8")
def _graph_from_state(state: Dict[str, Any]) -> GraphOfContracts:
return GraphOfContracts.from_snapshot(state.get("registry", {}))
def _ledger_from_state(state: Dict[str, Any]) -> GovernanceLedger:
return GovernanceLedger.from_snapshot(state.get("ledger", {}))
def _store_snapshot(store: Path, graph: GraphOfContracts, ledger: GovernanceLedger) -> None:
_save_state(store, {"registry": graph.snapshot(), "ledger": ledger.snapshot()})
def build_parser() -> argparse.ArgumentParser:
parser = argparse.ArgumentParser(prog="hopemesh", description="HopeMesh 2.0 registry and governance CLI")
parser.add_argument("--version", dest="show_version", action="store_true", help="Show version")
subparsers = parser.add_subparsers(dest="cmd")
registry = subparsers.add_parser("registry", help="Manage the Graph-of-Contracts")
registry_sub = registry.add_subparsers(dest="registry_cmd", required=True)
registry_init = registry_sub.add_parser("init", help="Create an empty registry store")
registry_init.add_argument("--store", type=Path, default=DEFAULT_STORE)
registry_schema = registry_sub.add_parser("add-schema", help="Register a schema contract")
registry_schema.add_argument("--store", type=Path, default=DEFAULT_STORE)
registry_schema.add_argument("--name", required=True)
registry_schema.add_argument("--schema-version", required=True)
registry_schema.add_argument("--definition-json", required=True)
registry_adapter = registry_sub.add_parser("add-adapter", help="Register an adapter contract")
registry_adapter.add_argument("--store", type=Path, default=DEFAULT_STORE)
registry_adapter.add_argument("--name", required=True)
registry_adapter.add_argument("--adapter-version", required=True)
registry_adapter.add_argument("--definition-json", required=True)
registry_link = registry_sub.add_parser("link", help="Link two contracts")
registry_link.add_argument("--store", type=Path, default=DEFAULT_STORE)
registry_link.add_argument("--source-id", required=True)
registry_link.add_argument("--target-id", required=True)
registry_link.add_argument("--relation", default="implements")
registry_show = registry_sub.add_parser("snapshot", help="Print the registry snapshot")
registry_show.add_argument("--store", type=Path, default=DEFAULT_STORE)
ledger = subparsers.add_parser("ledger", help="Manage the governance ledger")
ledger_sub = ledger.add_subparsers(dest="ledger_cmd", required=True)
ledger_append = ledger_sub.add_parser("append", help="Append a governance event")
ledger_append.add_argument("--store", type=Path, default=DEFAULT_STORE)
ledger_append.add_argument("--event-type", required=True)
ledger_append.add_argument("--payload-json", required=True)
ledger_append.add_argument("--actor", default="system")
ledger_verify = ledger_sub.add_parser("verify", help="Verify ledger integrity")
ledger_verify.add_argument("--store", type=Path, default=DEFAULT_STORE)
return parser
def main(argv: list[str] | None = None) -> int:
parser = build_parser()
args = parser.parse_args(argv)
if args.show_version:
print("hopemesh-sdk 0.1.0") print("hopemesh-sdk 0.1.0")
elif not args.cmd: return 0
if not args.cmd:
parser.print_help() parser.print_help()
return 0
state = _load_state(args.store)
graph = _graph_from_state(state)
ledger = _ledger_from_state(state)
if args.cmd == "registry":
if args.registry_cmd == "init":
_store_snapshot(args.store, graph, ledger)
print(str(args.store))
return 0
if args.registry_cmd in {"add-schema", "add-adapter"}:
definition = json.loads(args.definition_json)
if args.registry_cmd == "add-schema":
record = graph.register_schema(args.name, args.schema_version, definition)
else:
record = graph.register_adapter(args.name, args.adapter_version, definition)
ledger.append("registry.register", {"contract_id": record.contract_id, "kind": record.kind})
_store_snapshot(args.store, graph, ledger)
print(record.contract_id)
return 0
if args.registry_cmd == "link":
link = graph.link(args.source_id, args.target_id, args.relation)
ledger.append("registry.link", link.to_dict())
_store_snapshot(args.store, graph, ledger)
print(json.dumps(link.to_dict(), sort_keys=True))
return 0
if args.registry_cmd == "snapshot":
print(json.dumps(graph.snapshot(), indent=2, sort_keys=True))
return 0
if args.cmd == "ledger":
if args.ledger_cmd == "append":
payload = json.loads(args.payload_json)
entry = ledger.append(args.event_type, payload, actor=args.actor)
_store_snapshot(args.store, graph, ledger)
print(json.dumps(entry.to_dict(), indent=2, sort_keys=True))
return 0
if args.ledger_cmd == "verify":
issues = ledger.verify()
print(json.dumps({"ok": not issues, "issues": issues}, indent=2, sort_keys=True))
return 0
parser.error("unknown command")
return 2
if __name__ == "__main__": if __name__ == "__main__":
main() raise SystemExit(main())

273
hopemesh/contracts.py Normal file
View File

@ -0,0 +1,273 @@
from __future__ import annotations
from dataclasses import dataclass, asdict, field
from typing import Any, Dict, Iterable, List, Optional
import hashlib
import json
import time
from .core import AttestationHint, AuditLog, PlanDelta, Policy, _canonical_json
def _hash(payload: Any) -> str:
return hashlib.sha256(_canonical_json(payload).encode("utf-8")).hexdigest()
@dataclass
class ContractRecord:
contract_id: str
kind: str
name: str
version: str
definition: Dict[str, Any] = field(default_factory=dict)
attestation: Optional[AttestationHint] = None
created_at: float = field(default_factory=lambda: time.time())
def to_dict(self) -> Dict[str, Any]:
data = asdict(self)
if self.attestation is not None:
data["attestation"] = self.attestation.to_dict()
return data
@dataclass
class ContractLink:
source_id: str
target_id: str
relation: str
created_at: float = field(default_factory=lambda: time.time())
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
class GraphOfContracts:
def __init__(self) -> None:
self._records: Dict[str, ContractRecord] = {}
self._links: List[ContractLink] = []
def register_schema(
self,
name: str,
version: str,
definition: Dict[str, Any],
attestation: Optional[AttestationHint] = None,
) -> ContractRecord:
contract_id = f"schema:{name}:{version}"
record = ContractRecord(
contract_id=contract_id,
kind="schema",
name=name,
version=version,
definition=dict(definition),
attestation=attestation,
)
self._records[contract_id] = record
return record
def register_adapter(
self,
name: str,
version: str,
definition: Dict[str, Any],
attestation: Optional[AttestationHint] = None,
) -> ContractRecord:
contract_id = f"adapter:{name}:{version}"
record = ContractRecord(
contract_id=contract_id,
kind="adapter",
name=name,
version=version,
definition=dict(definition),
attestation=attestation,
)
self._records[contract_id] = record
return record
def link(self, source_id: str, target_id: str, relation: str = "implements") -> ContractLink:
if source_id not in self._records:
raise KeyError(f"Unknown source contract: {source_id}")
if target_id not in self._records:
raise KeyError(f"Unknown target contract: {target_id}")
link = ContractLink(source_id=source_id, target_id=target_id, relation=relation)
self._links.append(link)
return link
def resolve(self, contract_id: str) -> ContractRecord:
return self._records[contract_id]
def records(self) -> List[ContractRecord]:
return list(self._records.values())
def links(self) -> List[ContractLink]:
return list(self._links)
def validate(self) -> List[str]:
issues: List[str] = []
for link in self._links:
if link.source_id not in self._records:
issues.append(f"missing source {link.source_id}")
if link.target_id not in self._records:
issues.append(f"missing target {link.target_id}")
return issues
def snapshot(self) -> Dict[str, Any]:
return {
"records": [record.to_dict() for record in self.records()],
"links": [link.to_dict() for link in self.links()],
"fingerprint": self.fingerprint(),
}
@classmethod
def from_snapshot(cls, snapshot: Dict[str, Any]) -> "GraphOfContracts":
graph = cls()
for record_data in snapshot.get("records", []):
attestation = record_data.get("attestation")
hint = AttestationHint(**attestation) if attestation else None
record = ContractRecord(
contract_id=record_data["contract_id"],
kind=record_data["kind"],
name=record_data["name"],
version=record_data["version"],
definition=dict(record_data.get("definition", {})),
attestation=hint,
created_at=record_data.get("created_at", time.time()),
)
graph._records[record.contract_id] = record
for link_data in snapshot.get("links", []):
graph._links.append(
ContractLink(
source_id=link_data["source_id"],
target_id=link_data["target_id"],
relation=link_data["relation"],
created_at=link_data.get("created_at", time.time()),
)
)
return graph
def fingerprint(self) -> str:
payload = {
"records": [record.to_dict() for record in sorted(self._records.values(), key=lambda item: item.contract_id)],
"links": [link.to_dict() for link in self._links],
}
return _hash(payload)
@dataclass
class LedgerEntry:
entry_id: str
event_type: str
payload: Dict[str, Any]
actor: str
timestamp: float
previous_hash: str
entry_hash: str
signature: Optional[str] = None
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
class GovernanceLedger:
def __init__(self) -> None:
self._entries: List[LedgerEntry] = []
def append(
self,
event_type: str,
payload: Dict[str, Any],
actor: str = "system",
signature: Optional[str] = None,
timestamp: Optional[float] = None,
) -> LedgerEntry:
ts = time.time() if timestamp is None else timestamp
previous_hash = self._entries[-1].entry_hash if self._entries else "genesis"
entry_id = payload.get("entry_id") or f"{event_type}-{len(self._entries) + 1}"
basis = {
"entry_id": entry_id,
"event_type": event_type,
"payload": payload,
"actor": actor,
"timestamp": ts,
"previous_hash": previous_hash,
}
entry_hash = _hash(basis)
entry = LedgerEntry(
entry_id=entry_id,
event_type=event_type,
payload=dict(payload),
actor=actor,
timestamp=ts,
previous_hash=previous_hash,
entry_hash=entry_hash,
signature=signature or entry_hash,
)
self._entries.append(entry)
return entry
def entries(self) -> List[LedgerEntry]:
return list(self._entries)
def verify(self) -> List[str]:
issues: List[str] = []
previous_hash = "genesis"
for entry in self._entries:
basis = {
"entry_id": entry.entry_id,
"event_type": entry.event_type,
"payload": entry.payload,
"actor": entry.actor,
"timestamp": entry.timestamp,
"previous_hash": previous_hash,
}
expected_hash = _hash(basis)
if expected_hash != entry.entry_hash:
issues.append(f"entry {entry.entry_id} hash mismatch")
if entry.previous_hash != previous_hash:
issues.append(f"entry {entry.entry_id} chain mismatch")
previous_hash = entry.entry_hash
return issues
def snapshot(self) -> Dict[str, Any]:
return {"entries": [entry.to_dict() for entry in self._entries], "fingerprint": self.fingerprint()}
@classmethod
def from_snapshot(cls, snapshot: Dict[str, Any]) -> "GovernanceLedger":
ledger = cls()
for entry_data in snapshot.get("entries", []):
ledger._entries.append(
LedgerEntry(
entry_id=entry_data["entry_id"],
event_type=entry_data["event_type"],
payload=dict(entry_data.get("payload", {})),
actor=entry_data["actor"],
timestamp=entry_data["timestamp"],
previous_hash=entry_data["previous_hash"],
entry_hash=entry_data["entry_hash"],
signature=entry_data.get("signature"),
)
)
return ledger
def fingerprint(self) -> str:
return _hash({"entries": [entry.to_dict() for entry in self._entries]})
def validate_plan_deltas(deltas: Iterable[PlanDelta]) -> List[str]:
issues: List[str] = []
for delta in deltas:
if not delta.actions:
issues.append(f"{delta.delta_id}: empty actions")
if delta.signature is not None and not delta.signature.strip():
issues.append(f"{delta.delta_id}: blank signature")
return issues
def policy_summary(policy: Policy) -> str:
return json.dumps(policy.to_dict(), sort_keys=True)
def audit_plan_delta(delta: PlanDelta, actor: str, message: str) -> AuditLog:
payload = {"delta_id": delta.delta_id, "actor": actor, "message": message, "fingerprint": delta.fingerprint()}
signature = _hash(payload)
return AuditLog(entry_id=f"audit-{delta.delta_id}", message=message, signature=signature, anchor=payload["fingerprint"])

View File

@ -1,29 +1,60 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field
from dataclasses import dataclass, field, asdict
from typing import Any, Dict, List, Optional from typing import Any, Dict, List, Optional
import hashlib
import json
import time import time
def _canonical_json(payload: Any) -> str:
return json.dumps(payload, sort_keys=True, separators=(",", ":"), default=str)
def _fingerprint(payload: Any) -> str:
return hashlib.sha256(_canonical_json(payload).encode("utf-8")).hexdigest()
@dataclass @dataclass
class LocalProblem: class LocalProblem:
name: str name: str
region: Optional[str] = None region: Optional[str] = None
objective: str = ""
constraints: Dict[str, Any] = field(default_factory=dict)
metadata: Dict[str, Any] = field(default_factory=dict) metadata: Dict[str, Any] = field(default_factory=dict)
created_at: float = field(default_factory=lambda: time.time()) created_at: float = field(default_factory=lambda: time.time())
def __post_init__(self) -> None:
if not self.name.strip():
raise ValueError("LocalProblem.name must not be empty")
def __repr__(self) -> str: def __repr__(self) -> str:
return f"LocalProblem(name={self.name!r}, region={self.region!r})" return f"LocalProblem(name={self.name!r}, region={self.region!r}, objective={self.objective!r})"
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
def fingerprint(self) -> str:
return _fingerprint(self.to_dict())
@dataclass @dataclass
class SharedVariables: class SharedVariables:
description: str = "" description: str = ""
data: Dict[str, Any] = field(default_factory=dict) data: Dict[str, Any] = field(default_factory=dict)
aggregate_type: str = "sum" # placeholder for privacy-preserving aggregation aggregate_type: str = "sum"
privacy_budget_tag: Optional[str] = None
source_regions: List[str] = field(default_factory=list)
created_at: float = field(default_factory=lambda: time.time()) created_at: float = field(default_factory=lambda: time.time())
def __repr__(self) -> str: def __repr__(self) -> str:
return f"SharedVariables(desc={self.description!r}, keys={list(self.data.keys())})" return f"SharedVariables(desc={self.description!r}, keys={list(self.data.keys())}, aggregate={self.aggregate_type!r})"
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
def fingerprint(self) -> str:
return _fingerprint(self.to_dict())
@dataclass @dataclass
@ -31,10 +62,27 @@ class PlanDelta:
delta_id: str delta_id: str
actions: List[Dict[str, Any]] = field(default_factory=list) actions: List[Dict[str, Any]] = field(default_factory=list)
timestamp: float = field(default_factory=lambda: time.time()) timestamp: float = field(default_factory=lambda: time.time())
actor_did: Optional[str] = None
causality: Dict[str, int] = field(default_factory=dict)
policy_tags: List[str] = field(default_factory=list)
privacy_budget_tag: Optional[str] = None
annotations: Dict[str, Any] = field(default_factory=dict) annotations: Dict[str, Any] = field(default_factory=dict)
signature: Optional[str] = None
def __post_init__(self) -> None:
if not self.delta_id.strip():
raise ValueError("PlanDelta.delta_id must not be empty")
if not isinstance(self.actions, list):
raise TypeError("PlanDelta.actions must be a list")
def __repr__(self) -> str: def __repr__(self) -> str:
return f"PlanDelta(delta_id={self.delta_id!r}, actions={len(self.actions)} actions)" return f"PlanDelta(delta_id={self.delta_id!r}, actions={len(self.actions)} actions, actor={self.actor_did!r})"
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
def fingerprint(self) -> str:
return _fingerprint(self.to_dict())
@dataclass @dataclass
@ -42,9 +90,14 @@ class Policy:
policy_id: str policy_id: str
rules: Dict[str, Any] = field(default_factory=dict) rules: Dict[str, Any] = field(default_factory=dict)
effective_at: float = field(default_factory=lambda: time.time()) effective_at: float = field(default_factory=lambda: time.time())
scope: str = "global"
approved_by: Optional[str] = None
def __repr__(self) -> str: def __repr__(self) -> str:
return f"Policy(id={self.policy_id!r}, rules={len(self.rules)})" return f"Policy(id={self.policy_id!r}, rules={len(self.rules)}, scope={self.scope!r})"
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@dataclass @dataclass
@ -52,10 +105,14 @@ class AttestationHint:
hint_id: str hint_id: str
scheme: str = "dummy" scheme: str = "dummy"
payload: Dict[str, Any] = field(default_factory=dict) payload: Dict[str, Any] = field(default_factory=dict)
expires_at: Optional[float] = None
def __repr__(self) -> str: def __repr__(self) -> str:
return f"AttestationHint(id={self.hint_id!r}, scheme={self.scheme!r})" return f"AttestationHint(id={self.hint_id!r}, scheme={self.scheme!r})"
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@dataclass @dataclass
class AuditLog: class AuditLog:
@ -63,21 +120,37 @@ class AuditLog:
message: str message: str
timestamp: float = field(default_factory=lambda: time.time()) timestamp: float = field(default_factory=lambda: time.time())
signature: Optional[str] = None signature: Optional[str] = None
anchor: Optional[str] = None
def __repr__(self) -> str: def __repr__(self) -> str:
return f"AuditLog(id={self.entry_id!r}, ts={self.timestamp})" return f"AuditLog(id={self.entry_id!r}, ts={self.timestamp})"
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
@dataclass @dataclass
class PrivacyBudget: class PrivacyBudget:
budget_id: str budget_id: str
limit: float limit: float
used: float = 0.0 used: float = 0.0
def consume(self, amount: float) -> None:
self.used = min(self.used + amount, self.limit) def consume(self, amount: float) -> bool:
if amount < 0:
raise ValueError("PrivacyBudget.consume requires a non-negative amount")
if self.used + amount > self.limit:
return False
self.used += amount
return True
def can_consume(self, amount: float) -> bool:
return amount >= 0 and self.used + amount <= self.limit
def remaining(self) -> float: def remaining(self) -> float:
return self.limit - self.used return self.limit - self.used
def to_dict(self) -> Dict[str, Any]:
return asdict(self)
def __repr__(self) -> str: def __repr__(self) -> str:
return f"PrivacyBudget(id={self.budget_id!r}, remaining={self.remaining()})" return f"PrivacyBudget(id={self.budget_id!r}, remaining={self.remaining()})"

View File

@ -1,18 +1,55 @@
from __future__ import annotations from __future__ import annotations
"""Tiny DSL-like helpers for HopeMesh primitives and contract tooling."""
from typing import Iterable
from .core import LocalProblem, SharedVariables, PlanDelta, Policy, AttestationHint, AuditLog, PrivacyBudget from .core import LocalProblem, SharedVariables, PlanDelta, Policy, AttestationHint, AuditLog, PrivacyBudget
from .contracts import validate_plan_deltas
"""Tiny DSL-like helpers for HopeMesh primitives. def make_local_problem(
This module provides lightweight constructors to enable adapters to plug into the canonical primitives. name: str,
""" region: str | None = None,
objective: str = "",
constraints: dict | None = None,
**metadata,
) -> LocalProblem:
return LocalProblem(name=name, region=region, objective=objective, constraints=constraints or {}, metadata=metadata)
def make_local_problem(name: str, region: str | None = None, **kwargs) -> LocalProblem: def make_shared_variables(
return LocalProblem(name=name, region=region, metadata=kwargs) description: str = "",
data: dict | None = None,
aggregate_type: str = "sum",
privacy_budget_tag: str | None = None,
source_regions: list[str] | None = None,
) -> SharedVariables:
return SharedVariables(
description=description,
data=data or {},
aggregate_type=aggregate_type,
privacy_budget_tag=privacy_budget_tag,
source_regions=source_regions or [],
)
def make_shared_variables(description: str = "", data: dict | None = None, aggregate_type: str = "sum") -> SharedVariables: def make_plan_delta(
return SharedVariables(description=description, data=data or {}, aggregate_type=aggregate_type) delta_id: str,
actions: list[dict],
def make_plan_delta(delta_id: str, actions: list[dict], **kwargs) -> PlanDelta: actor_did: str | None = None,
return PlanDelta(delta_id=delta_id, actions=actions, annotations=kwargs) causality: dict | None = None,
policy_tags: list[str] | None = None,
privacy_budget_tag: str | None = None,
signature: str | None = None,
**annotations,
) -> PlanDelta:
return PlanDelta(
delta_id=delta_id,
actions=actions,
actor_did=actor_did,
causality=causality or {},
policy_tags=policy_tags or [],
privacy_budget_tag=privacy_budget_tag,
signature=signature,
annotations=annotations,
)
def make_policy(policy_id: str, **rules) -> Policy: def make_policy(policy_id: str, **rules) -> Policy:
return Policy(policy_id=policy_id, rules=rules) return Policy(policy_id=policy_id, rules=rules)
@ -25,3 +62,23 @@ def make_audit_log(entry_id: str, message: str, signature: str | None = None) ->
def make_privacy_budget(budget_id: str, limit: float) -> PrivacyBudget: def make_privacy_budget(budget_id: str, limit: float) -> PrivacyBudget:
return PrivacyBudget(budget_id=budget_id, limit=limit) return PrivacyBudget(budget_id=budget_id, limit=limit)
def merge_plan_deltas(deltas: Iterable[PlanDelta]) -> PlanDelta:
ordered = sorted(deltas, key=lambda delta: (delta.timestamp, delta.delta_id))
actions: list[dict] = []
contributors: list[str] = []
for delta in ordered:
contributors.append(delta.delta_id)
actions.extend(delta.actions)
delta_id = "merged-" + "-".join(contributors) if contributors else "merged-empty"
return PlanDelta(
delta_id=delta_id,
actions=actions,
causality={"merged_from": len(contributors)},
annotations={"contributors": contributors},
)
def validate_deltas(deltas: Iterable[PlanDelta]) -> list[str]:
return validate_plan_deltas(deltas)

16
test.sh
View File

@ -7,14 +7,28 @@ python3 -m build >/tmp/build.log 2>&1 || {
echo "Smoke test: import modules and create objects..." echo "Smoke test: import modules and create objects..."
python3 - << 'PY' python3 - << 'PY'
from hopemesh.sdk import make_local_problem, make_shared_variables, make_plan_delta from hopemesh import GraphOfContracts, GovernanceLedger
from hopemesh.sdk import make_local_problem, make_shared_variables, make_plan_delta, merge_plan_deltas
lp = make_local_problem("RegionA Allocation", region="RegionA") lp = make_local_problem("RegionA Allocation", region="RegionA")
sv = make_shared_variables("example", {"stock": 100}) sv = make_shared_variables("example", {"stock": 100})
pd = make_plan_delta("pd-001", [{"allocate": {"region": "RegionA", "qty": 10}}]) pd = make_plan_delta("pd-001", [{"allocate": {"region": "RegionA", "qty": 10}}])
graph = GraphOfContracts()
schema = graph.register_schema("allocation", "1.0", {"fields": ["region", "qty"]})
adapter = graph.register_adapter("regional-hub", "1.0", {"transport": "tls"})
graph.link(adapter.contract_id, schema.contract_id)
ledger = GovernanceLedger()
ledger.append("demo", {"delta": pd.delta_id})
merged = merge_plan_deltas([pd])
print(lp) print(lp)
print(sv) print(sv)
print(pd) print(pd)
print(graph.fingerprint())
print(ledger.verify())
print(merged)
print("OK") print("OK")
PY PY
echo "Running unit tests..."
python3 -m unittest discover -s tests -p 'test_*.py'
echo "All tests passed." echo "All tests passed."

34
tests/test_cli.py Normal file
View File

@ -0,0 +1,34 @@
import tempfile
import unittest
from pathlib import Path
from hopemesh.cli import main
class HopeMeshCLITests(unittest.TestCase):
def test_registry_and_ledger_commands(self) -> None:
with tempfile.TemporaryDirectory() as tmpdir:
store = Path(tmpdir) / "state.json"
self.assertEqual(main(["registry", "init", "--store", str(store)]), 0)
self.assertEqual(
main(
[
"registry",
"add-schema",
"--store",
str(store),
"--name",
"allocation",
"--schema-version",
"1.0",
"--definition-json",
'{"fields":["region","qty"]}',
]
),
0,
)
self.assertEqual(main(["ledger", "verify", "--store", str(store)]), 0)
if __name__ == "__main__":
unittest.main()

65
tests/test_core.py Normal file
View File

@ -0,0 +1,65 @@
import unittest
from hopemesh import (
GraphOfContracts,
GovernanceLedger,
LocalProblem,
PrivacyBudget,
make_local_problem,
make_plan_delta,
make_shared_variables,
merge_plan_deltas,
validate_deltas,
)
class HopeMeshCoreTests(unittest.TestCase):
def test_primitives_and_budget(self) -> None:
problem = LocalProblem(name="Water allocation", region="North", objective="minimize unmet demand")
shared = make_shared_variables("stock", {"water": 10}, source_regions=["North"])
budget = PrivacyBudget("pb-1", 3.0)
self.assertTrue(problem.fingerprint())
self.assertTrue(shared.fingerprint())
self.assertTrue(budget.consume(1.5))
self.assertFalse(budget.consume(2.0))
self.assertEqual(round(budget.remaining(), 2), 1.5)
def test_graph_of_contracts(self) -> None:
graph = GraphOfContracts()
schema = graph.register_schema("resource-plan", "1.0", {"fields": ["id", "qty"]})
adapter = graph.register_adapter("field-hub", "1.0", {"transport": "tls"})
link = graph.link(adapter.contract_id, schema.contract_id)
self.assertEqual(link.relation, "implements")
self.assertEqual(graph.validate(), [])
snapshot = graph.snapshot()
restored = GraphOfContracts.from_snapshot(snapshot)
self.assertEqual(restored.fingerprint(), graph.fingerprint())
def test_ledger_chain(self) -> None:
ledger = GovernanceLedger()
first = ledger.append("audit", {"message": "created"})
second = ledger.append("audit", {"message": "updated"}, actor="operator")
self.assertNotEqual(first.entry_hash, second.entry_hash)
self.assertEqual(ledger.verify(), [])
restored = GovernanceLedger.from_snapshot(ledger.snapshot())
self.assertEqual(restored.verify(), [])
def test_delta_helpers(self) -> None:
delta_a = make_plan_delta("a", [{"allocate": {"region": "North", "qty": 2}}])
delta_b = make_plan_delta("b", [{"allocate": {"region": "South", "qty": 3}}])
merged = merge_plan_deltas([delta_b, delta_a])
self.assertEqual(len(merged.actions), 2)
self.assertEqual(validate_deltas([delta_a, delta_b]), [])
def test_factory_alias(self) -> None:
problem = make_local_problem("Hospital supply", region="East", urgent=True)
self.assertEqual(problem.region, "East")
self.assertEqual(problem.metadata["urgent"], True)
if __name__ == "__main__":
unittest.main()