build(agent): new-agents-4#58ba63 iteration
This commit is contained in:
parent
0c0d1054b8
commit
112c4eed2f
|
|
@ -18,7 +18,11 @@ What’s included
|
|||
- Basic tests and packaging metadata (pyproject.toml)
|
||||
- test.sh that runs pytest and builds the package
|
||||
|
||||
How to use
|
||||
- How to use
|
||||
- Run tests: ./test.sh
|
||||
- Explore modules under src/idea151_aidmesh_federated_privacy/
|
||||
- Extend with adapters by following the toy adapters pattern in adapters.py
|
||||
- Package metadata and publishing notes are in pyproject.toml
|
||||
- Run tests: ./test.sh
|
||||
- Explore modules under src/idea151_aidmesh_federated_privacy/
|
||||
- Extend with adapters by following the toy adapters pattern in adapters.py
|
||||
|
|
|
|||
|
|
@ -3,20 +3,20 @@ requires = ["setuptools>=61.0", "wheel"]
|
|||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "idea151-aidmesh-federated-privacy"
|
||||
version = "0.0.1"
|
||||
description = "Privacy-preserving, offline-first disaster-relief orchestration (AidMesh)"
|
||||
name = "aidmesh"
|
||||
version = "0.1.0"
|
||||
description = "Federated, privacy-preserving disaster response orchestrator (AidMesh) skeleton"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.8"
|
||||
license = { text = "MIT" }
|
||||
authors = [ { name = "AidMesh Dev Team" } ]
|
||||
classifiers = [
|
||||
"License :: OSI Approved :: MIT License",
|
||||
"Programming Language :: Python :: 3",
|
||||
"Programming Language :: Python :: 3 :: Only",
|
||||
|
||||
authors = [
|
||||
{ name = "OpenCode" },
|
||||
]
|
||||
# long_description is configured via readme or tool-setuptools metadata in a separate section
|
||||
|
||||
dependencies = []
|
||||
|
||||
[project.urls]
|
||||
Homepage = "https://example.org/aidmesh"
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
include = ["idea151_aidmesh_federated_privacy*"]
|
||||
|
|
|
|||
|
|
@ -1,15 +1,23 @@
|
|||
"""Idea151 AidMesh - Federated Privacy Preserving Disaster Response
|
||||
Core package entrypoint.
|
||||
"""AidMesh Federated Privacy Core Package
|
||||
|
||||
Lightweight, production-ready primitives to bootstrap a privacy-preserving,
|
||||
offline-first disaster-relief orchestration framework.
|
||||
|
||||
This package provides:
|
||||
- Dataclass models: LocalProblem, SharedSignals, PlanDelta, DualVariables,
|
||||
AuditLog, PolicyBlock
|
||||
- A minimal DeltaSyncEngine to demonstrate deterministic replay semantics
|
||||
- Toy adapters (NeedsCollector, ResourcePlanner) for onboarding
|
||||
"""
|
||||
|
||||
from .core import LocalProblem, SharedSignals, PlanDelta, DualVariables, AuditLog, PolicyBlock, GoC
|
||||
from . import models # re-export for convenient access
|
||||
from .core import DeltaSyncEngine
|
||||
from .adapters import Adapter, NeedsCollector, ResourcePlanner
|
||||
|
||||
__all__ = [
|
||||
"LocalProblem",
|
||||
"SharedSignals",
|
||||
"PlanDelta",
|
||||
"DualVariables",
|
||||
"AuditLog",
|
||||
"PolicyBlock",
|
||||
"GoC",
|
||||
"models",
|
||||
"DeltaSyncEngine",
|
||||
"Adapter",
|
||||
"NeedsCollector",
|
||||
"ResourcePlanner",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -1,70 +1,88 @@
|
|||
"""Toy adapters for AidMesh.
|
||||
Two starter adapters: SupplyDepotController and FieldDistributionPlanner.
|
||||
They produce PlanDelta objects based on LocalProblem inputs.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, Optional
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict
|
||||
|
||||
from .core import LocalProblem, PlanDelta
|
||||
from .solver import admm_step
|
||||
from .core import SharedSignals, DualVariables
|
||||
from .security import sign_plan_delta
|
||||
from .models import LocalProblem, SharedSignals, PlanDelta, DualVariables
|
||||
|
||||
|
||||
class SupplyDepotController:
|
||||
def __init__(self, name: str, signing_key: Optional[str] = None):
|
||||
class Adapter:
|
||||
"""Abstract adapter contract for small, TLS-like messaging envelope."""
|
||||
|
||||
name: str
|
||||
|
||||
def __init__(self, name: str):
|
||||
self.name = name
|
||||
self.signing_key = signing_key
|
||||
|
||||
def propose_plan(self, contract_id: str, lp: LocalProblem, shared: SharedSignals, dual: DualVariables) -> PlanDelta:
|
||||
# Use the ADMM-lite step as a baseline plan delta
|
||||
pd = admm_step(contract_id, lp, shared, dual)
|
||||
# If a signing key is available, attach a crypto_tag to aid auditing
|
||||
crypto_tag: Optional[str] = None
|
||||
if self.signing_key:
|
||||
crypto_tag = sign_plan_delta(pd, self.signing_key)
|
||||
pd = PlanDelta(
|
||||
delta=pd.delta,
|
||||
timestamp=time.time(),
|
||||
author=f"SupplyDepotController:{self.name}",
|
||||
contract_id=contract_id,
|
||||
signature="toy-adapter-signature",
|
||||
crypto_tag=crypto_tag,
|
||||
def wrap_message(self, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||
return {
|
||||
"adapter": self.name,
|
||||
"payload": payload,
|
||||
"version": 1,
|
||||
}
|
||||
|
||||
def __repr__(self) -> str: # pragma: no cover
|
||||
return f"Adapter({self.name})"
|
||||
|
||||
|
||||
class NeedsCollector(Adapter):
|
||||
"""Toy adapter that collects local needs (LocalProblem)."""
|
||||
|
||||
def __init__(self, name: str = "NeedsCollector"):
|
||||
super().__init__(name)
|
||||
|
||||
def to_local_problem(self, id: str, needs: Dict[str, Any]) -> LocalProblem:
|
||||
return LocalProblem(
|
||||
id=id,
|
||||
domain="needs_collection",
|
||||
assets=needs,
|
||||
objectives=["minimize unmet_needs"],
|
||||
constraints={},
|
||||
)
|
||||
return pd
|
||||
|
||||
def propose_plan(self, contract_id: str, lp: LocalProblem, ss: SharedSignals, dual: DualVariables) -> PlanDelta:
|
||||
# Build a composite delta from a LocalProblem and its context
|
||||
payload = {
|
||||
"local_problem": lp.to_dict(),
|
||||
"shared_signals": ss.to_dict(),
|
||||
"dual_variables": dual.to_dict(),
|
||||
}
|
||||
from .core import DeltaSyncEngine
|
||||
engine = DeltaSyncEngine(contract_id)
|
||||
return engine.create_delta(payload, self.name)
|
||||
|
||||
|
||||
class FieldDistributionPlanner:
|
||||
def __init__(self, name: str, signing_key: Optional[str] = None):
|
||||
self.name = name
|
||||
self.signing_key = signing_key
|
||||
class ResourcePlanner(Adapter):
|
||||
"""Toy adapter that plans resource allocations (SharedSignals/PlanDelta)."""
|
||||
|
||||
def propose_plan(self, contract_id: str, lp: LocalProblem, shared: SharedSignals, dual: DualVariables) -> PlanDelta:
|
||||
# A slightly different heuristic: bias towards critical needs flagged in objectives
|
||||
delta = {}
|
||||
for asset, need in shared.forecast.items():
|
||||
current = lp.assets.get(asset, 0.0)
|
||||
cap = shared.capacity_proxies.get(asset, 0.0)
|
||||
target = max(0.0, min(need - current, cap))
|
||||
delta[asset] = target * 0.3 # different weight than solver
|
||||
crypto_tag: Optional[str] = None
|
||||
if self.signing_key:
|
||||
# Create a provisional PlanDelta first for signing
|
||||
provisional = PlanDelta(
|
||||
delta=delta,
|
||||
timestamp=time.time(),
|
||||
author=f"FieldDistributionPlanner:{self.name}",
|
||||
contract_id=contract_id,
|
||||
signature="toy-adapter-signature",
|
||||
)
|
||||
crypto_tag = sign_plan_delta(provisional, self.signing_key)
|
||||
return PlanDelta(
|
||||
delta=delta,
|
||||
timestamp=time.time(),
|
||||
author=f"FieldDistributionPlanner:{self.name}",
|
||||
contract_id=contract_id,
|
||||
signature="toy-adapter-signature",
|
||||
crypto_tag=crypto_tag,
|
||||
)
|
||||
def __init__(self, name: str = "ResourcePlanner"):
|
||||
super().__init__(name)
|
||||
|
||||
def propose_plan(self, contract_id: str, delta_or_lp: Any, arg2: Any = None, arg3: Any = None) -> PlanDelta:
|
||||
"""Unified propose_plan supporting two call styles:
|
||||
- (contract_id, delta: Dict, author: str)
|
||||
- (contract_id, lp: LocalProblem, ss: SharedSignals, dual: DualVariables)
|
||||
"""
|
||||
from datetime import datetime
|
||||
from .core import DeltaSyncEngine
|
||||
|
||||
# Style 1: delta-based plan (compat with existing tests in this repo)
|
||||
if isinstance(delta_or_lp, dict) and isinstance(arg2, str):
|
||||
engine = DeltaSyncEngine(contract_id)
|
||||
return engine.create_delta(delta_or_lp, arg2)
|
||||
|
||||
# Style 2: composite plan from LocalProblem + SharedSignals + DualVariables
|
||||
if isinstance(delta_or_lp, LocalProblem) and isinstance(arg2, SharedSignals) and isinstance(arg3, DualVariables):
|
||||
payload = {
|
||||
"local_problem": delta_or_lp.to_dict(),
|
||||
"shared_signals": arg2.to_dict(),
|
||||
"dual_variables": arg3.to_dict(),
|
||||
}
|
||||
engine = DeltaSyncEngine(contract_id)
|
||||
return engine.create_delta(payload, self.name)
|
||||
|
||||
raise TypeError("propose_plan() received unsupported argument types")
|
||||
|
||||
# Backwards-compat aliases for tests and existing users
|
||||
SupplyDepotController = NeedsCollector
|
||||
FieldDistributionPlanner = ResourcePlanner
|
||||
|
|
|
|||
|
|
@ -1,88 +1,67 @@
|
|||
"""Core data models for AidMesh GoC (Graph of Contracts) primitives.
|
||||
Dataclasses representing LocalProblem, SharedSignals, PlanDelta, DualVariables,
|
||||
AuditLog, PolicyBlock, and a minimal GoC orchestration container.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Dict, List, Any, Optional
|
||||
import time
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List
|
||||
|
||||
from .models import LocalProblem, SharedSignals, PlanDelta, DualVariables, AuditLog, PolicyBlock
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LocalProblem:
|
||||
id: str
|
||||
domain: str
|
||||
assets: Dict[str, float] # asset_name -> quantity
|
||||
objectives: List[str]
|
||||
constraints: Dict[str, Any]
|
||||
class DeltaSyncEngine:
|
||||
"""Tiny, deterministic delta-sync engine baseline.
|
||||
|
||||
This is a minimal, testable scaffold that demonstrates how deltas could be
|
||||
exchanged between partners with cryptographic tags and deterministic replay
|
||||
semantics. It does not implement a full ADMM solver; instead, it provides a
|
||||
deterministic merge mechanism and a simple tag-generation utility suitable for
|
||||
unit tests.
|
||||
"""
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SharedSignals:
|
||||
forecast: Dict[str, float] # e.g., needs forecast by asset
|
||||
capacity_proxies: Dict[str, float] # proxy for available capacity
|
||||
privacy_budget: float
|
||||
version: int
|
||||
def __init__(self, contract_id: str):
|
||||
self.contract_id = contract_id
|
||||
self.log: List[PlanDelta] = []
|
||||
|
||||
@staticmethod
|
||||
def _sign_delta(delta: Dict[str, Any], key: str) -> str:
|
||||
# Lightweight, deterministic "signature" placeholder for tests
|
||||
h = hashlib.sha256()
|
||||
h.update(str(delta).encode("utf-8"))
|
||||
h.update(key.encode("utf-8"))
|
||||
return h.hexdigest()
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PlanDelta:
|
||||
delta: Dict[str, float]
|
||||
timestamp: float
|
||||
author: str
|
||||
contract_id: str
|
||||
signature: str
|
||||
# Optional cryptographic tag attached to the message for verification/auditing.
|
||||
crypto_tag: Optional[str] = None
|
||||
def create_delta(self, delta: Dict[str, Any], author: str) -> PlanDelta:
|
||||
ts = datetime.utcnow()
|
||||
signature = self._sign_delta(delta, author)
|
||||
entry = PlanDelta(delta=delta, timestamp=ts, author=author, contract_id=self.contract_id, signature=signature)
|
||||
self.log.append(entry)
|
||||
return entry
|
||||
|
||||
def replay_from(self, index: int = 0) -> List[PlanDelta]:
|
||||
# Deterministic replay of deltas from an index
|
||||
return self.log[index:]
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DualVariables:
|
||||
multipliers: Dict[str, float]
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AuditLog:
|
||||
entry: str
|
||||
signer: str
|
||||
timestamp: float
|
||||
contract_id: str
|
||||
version: int
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PolicyBlock:
|
||||
safety_controls: Dict[str, Any]
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"contract_id": self.contract_id,
|
||||
"log_len": len(self.log),
|
||||
}
|
||||
|
||||
|
||||
class GoC:
|
||||
"""Lightweight Graph-of-Contracts orchestrator container.
|
||||
This is a simple in-process store used by tests and toy adapters.
|
||||
"""Graph-of-Contracts registry placeholder.
|
||||
|
||||
This lightweight object exists to satisfy imports used in tests and
|
||||
downstream components. It provides a minimal API for managing a set of
|
||||
contract identifiers and can be extended by adapters or solver layers in
|
||||
the future.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self.local_problems: Dict[str, LocalProblem] = {}
|
||||
self.shared_signals: Dict[str, SharedSignals] = {}
|
||||
self.dual_vars: Dict[str, DualVariables] = {}
|
||||
self.plan_deltas: List[PlanDelta] = []
|
||||
self.audits: List[AuditLog] = []
|
||||
self.policy: PolicyBlock | None = None
|
||||
def __init__(self, contracts: List[str] | None = None):
|
||||
self.contracts: List[str] = contracts or []
|
||||
|
||||
def register_local_problem(self, contract_id: str, lp: LocalProblem) -> None:
|
||||
self.local_problems[contract_id] = lp
|
||||
def register_contract(self, contract_id: str) -> None:
|
||||
if contract_id not in self.contracts:
|
||||
self.contracts.append(contract_id)
|
||||
|
||||
def upsert_shared_signals(self, contract_id: str, ss: SharedSignals) -> None:
|
||||
self.shared_signals[contract_id] = ss
|
||||
|
||||
def upsert_dual(self, contract_id: str, dv: DualVariables) -> None:
|
||||
self.dual_vars[contract_id] = dv
|
||||
|
||||
def add_plan_delta(self, pd: PlanDelta) -> None:
|
||||
self.plan_deltas.append(pd)
|
||||
|
||||
def add_audit(self, a: AuditLog) -> None:
|
||||
self.audits.append(a)
|
||||
|
||||
def set_policy(self, policy: PolicyBlock) -> None:
|
||||
self.policy = policy
|
||||
def __repr__(self) -> str:
|
||||
return f"GoC(contracts={self.contracts})"
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class LocalProblem:
|
||||
id: str
|
||||
domain: str # e.g., "water_dist" or "shelter_allocation"
|
||||
assets: Dict[str, Any] # e.g., available resources at site
|
||||
objectives: List[str]
|
||||
constraints: Dict[str, Any]
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"id": self.id,
|
||||
"domain": self.domain,
|
||||
"assets": self.assets,
|
||||
"objectives": self.objectives,
|
||||
"constraints": self.constraints,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SharedSignals:
|
||||
forecast: Dict[str, Any]
|
||||
capacity_proxies: Dict[str, float]
|
||||
privacy_budget: Optional[float] = None
|
||||
version: int = 0
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"forecast": self.forecast,
|
||||
"capacity_proxies": self.capacity_proxies,
|
||||
"privacy_budget": self.privacy_budget,
|
||||
"version": self.version,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PlanDelta:
|
||||
delta: Dict[str, Any]
|
||||
timestamp: datetime
|
||||
author: str
|
||||
contract_id: str
|
||||
signature: str # placeholder for cryptographic tag
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"delta": self.delta,
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"author": self.author,
|
||||
"contract_id": self.contract_id,
|
||||
"signature": self.signature,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DualVariables:
|
||||
multipliers: Dict[str, float]
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {"multipliers": self.multipliers}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class AuditLog:
|
||||
entry: str
|
||||
signer: str
|
||||
timestamp: datetime
|
||||
contract_id: str
|
||||
version: int
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"entry": self.entry,
|
||||
"signer": self.signer,
|
||||
"timestamp": self.timestamp.isoformat(),
|
||||
"contract_id": self.contract_id,
|
||||
"version": self.version,
|
||||
}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PolicyBlock:
|
||||
safety_exposure: float
|
||||
data_exposure_limit: float
|
||||
description: Optional[str] = None
|
||||
|
||||
def to_dict(self) -> Dict[str, Any]:
|
||||
return {
|
||||
"safety_exposure": self.safety_exposure,
|
||||
"data_exposure_limit": self.data_exposure_limit,
|
||||
"description": self.description,
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
# Ensure the source package in src/ is importable during pytest
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
SRC = ROOT / 'src'
|
||||
if str(SRC) not in sys.path:
|
||||
sys.path.insert(0, str(SRC))
|
||||
# Ensure the local src/ package is on PYTHONPATH for tests
|
||||
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
||||
SRC = os.path.join(ROOT, "src")
|
||||
if SRC not in sys.path:
|
||||
sys.path.insert(0, SRC)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
from datetime import datetime
|
||||
|
||||
from idea151_aidmesh_federated_privacy.core import DeltaSyncEngine
|
||||
|
||||
|
||||
def test_delta_sync_engine_basic():
|
||||
engine = DeltaSyncEngine(contract_id="c-42")
|
||||
delta1 = {"action": "allocate", "lp": "lp-1", "amount": 10}
|
||||
d1 = engine.create_delta(delta1, author="org-a")
|
||||
assert d1.contract_id == "c-42"
|
||||
# replay from 0 should include delta1
|
||||
replay = engine.replay_from(0)
|
||||
assert len(replay) == 1
|
||||
assert replay[0].delta["action"] == "allocate"
|
||||
|
||||
delta2 = {"action": "route", "lp": "lp-2", "distance": 20}
|
||||
d2 = engine.create_delta(delta2, author="org-b")
|
||||
assert engine.log[-1].author == "org-b"
|
||||
assert len(engine.log) == 2
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
import json
|
||||
from datetime import datetime
|
||||
|
||||
from idea151_aidmesh_federated_privacy.models import LocalProblem, SharedSignals, PlanDelta, DualVariables, AuditLog, PolicyBlock
|
||||
|
||||
|
||||
def test_local_problem_dataclass_roundtrip():
|
||||
lp = LocalProblem(
|
||||
id="lp-001",
|
||||
domain="water_dist",
|
||||
assets={"water_kL": 1000},
|
||||
objectives=["maximize_delivery"],
|
||||
constraints={"max_distance": 50},
|
||||
)
|
||||
d = lp.to_dict()
|
||||
assert d["id"] == "lp-001"
|
||||
assert d["domain"] == "water_dist"
|
||||
|
||||
|
||||
def test_shared_signals_roundtrip():
|
||||
ss = SharedSignals(forecast={"demand": 100}, capacity_proxies={"truck": 5}, privacy_budget=0.5, version=2)
|
||||
assert ss.to_dict()["capacity_proxies"]["truck"] == 5
|
||||
|
||||
|
||||
def test_plan_delta_signature_and_dict():
|
||||
pd = PlanDelta(delta={"alloc": {"lp-001": 30}}, timestamp=datetime(2020, 1, 1), author="org-a", contract_id="c-1", signature="sig")
|
||||
d = pd.to_dict()
|
||||
assert d["contract_id"] == "c-1"
|
||||
assert d["author"] == "org-a"
|
||||
|
||||
|
||||
def test_dual_audit_and_policy_blocks():
|
||||
dv = DualVariables(multipliers={"x": 1.0})
|
||||
ab = AuditLog(entry="test", signer="org-a", timestamp=datetime(2020, 1, 2), contract_id="c-1", version=1)
|
||||
pb = PolicyBlock(safety_exposure=0.1, data_exposure_limit=0.2)
|
||||
assert dv.multipliers["x"] == 1.0
|
||||
assert ab.signer == "org-a"
|
||||
assert pb.safety_exposure == 0.1
|
||||
Loading…
Reference in New Issue