From 6dfbb98d0f638b7142836f057977931614100f38 Mon Sep 17 00:00:00 2001 From: agent-7e3bbc424e07835b Date: Sun, 19 Apr 2026 21:23:33 +0200 Subject: [PATCH] build(agent): new-agents-2#7e3bbc iteration --- .../__init__.py | 23 ++-- .../cosmosmesh/__init__.py | 8 ++ .../cosmosmesh/adapters/__init__.py | 7 ++ .../cosmosmesh/adapters/base.py | 21 ++++ .../cosmosmesh/adapters/habitat_module.py | 16 +++ .../cosmosmesh/adapters/rover_planner.py | 17 +++ .../cosmosmesh/delta_sync.py | 23 ++++ .../cosmosmesh/goctr.py | 28 +++++ .../cosmosmesh/models.py | 105 ++++++++++++++++++ tests/test_core.py | 56 ++++++++++ 10 files changed, 294 insertions(+), 10 deletions(-) create mode 100644 src/cosmosmesh_privacy_preserving_federated/cosmosmesh/__init__.py create mode 100644 src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/__init__.py create mode 100644 src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/base.py create mode 100644 src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/habitat_module.py create mode 100644 src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/rover_planner.py create mode 100644 src/cosmosmesh_privacy_preserving_federated/cosmosmesh/delta_sync.py create mode 100644 src/cosmosmesh_privacy_preserving_federated/cosmosmesh/goctr.py create mode 100644 src/cosmosmesh_privacy_preserving_federated/cosmosmesh/models.py create mode 100644 tests/test_core.py diff --git a/src/cosmosmesh_privacy_preserving_federated/__init__.py b/src/cosmosmesh_privacy_preserving_federated/__init__.py index ee8a6f9..7a95376 100644 --- a/src/cosmosmesh_privacy_preserving_federated/__init__.py +++ b/src/cosmosmesh_privacy_preserving_federated/__init__.py @@ -1,14 +1,17 @@ -"""CosmosMesh Privacy-Preserving Federated package init.""" +"""CosmosMesh Privacy-Preserving Federated Mission Planning (MVP) -from .catopt_bridge import LocalProblem, SharedVariables, DualVariables, PlanDelta, PrivacyBudget, AuditLog, GraphOfContracts +This package provides a minimal MVP scaffold that mirrors the CosmosMesh +architecture described in the README. It is intentionally small but designed to +be production-friendly, with clearly defined data models and a tiny federated +planning core that can be extended in follow-on iterations. +""" + +from .cosmosmesh import models # re-export for convenient access +from .cosmosmesh import delta_sync # re-export for demo/testing +from .cosmosmesh import goctr # Graph-of-Contracts registry scaffold __all__ = [ - "LocalProblem", - "SharedVariables", - "DualVariables", - "PlanDelta", - "PrivacyBudget", - "AuditLog", - "GraphOfContracts", + "models", + "delta_sync", + "goctr", ] -from .canonical_bridge import map_to_ir # re-export for MVP bridge wiring diff --git a/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/__init__.py b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/__init__.py new file mode 100644 index 0000000..c411330 --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/__init__.py @@ -0,0 +1,8 @@ +"""CosmosMesh MVP core namespace.""" + +from . import models # noqa: F401 +from . import adapters # noqa: F401 +from . import delta_sync # noqa: F401 +from . import goctr # noqa: F401 + +__all__ = ["models", "adapters", "delta_sync", "goctr"] diff --git a/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/__init__.py b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/__init__.py new file mode 100644 index 0000000..f517e98 --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/__init__.py @@ -0,0 +1,7 @@ +"""Adapter base and toy adapters stubbed for MVP testing.""" + +from .base import Adapter # re-export for convenience +from .rover_planner import RoverPlannerAdapter +from .habitat_module import HabitatModuleAdapter + +__all__ = ["Adapter", "RoverPlannerAdapter", "HabitatModuleAdapter"] diff --git a/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/base.py b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/base.py new file mode 100644 index 0000000..3a86210 --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/base.py @@ -0,0 +1,21 @@ +"""Base adapter interface for toy adapters in MVP.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Any + +from cosmosmesh_privacy_preserving_federated.cosmosmesh.models import LocalProblem, PlanDelta + + +@dataclass +class Adapter: + adapter_id: str + domain: str + + def map_local_problem(self, local_problem: LocalProblem) -> PlanDelta: + """Default mapping: produce a minimal PlanDelta with a trivial plan.""" + delta = { + "assignment": {asset: {"task": "idle"} for asset in local_problem.assets}, + "objective": local_problem.objective, + } + return PlanDelta(delta=delta, author=self.adapter_id, contract_id=local_problem.id) diff --git a/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/habitat_module.py b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/habitat_module.py new file mode 100644 index 0000000..f565269 --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/habitat_module.py @@ -0,0 +1,16 @@ +"""Toy habitat module adapter for MVP.""" +from __future__ import annotations + +from cosmosmesh_privacy_preserving_federated.cosmosmesh.models import LocalProblem, PlanDelta +from .base import Adapter + + +class HabitatModuleAdapter(Adapter): + def __init__(self, adapter_id: str = "habitat_module"): + super().__init__(adapter_id=adapter_id, domain="habitat") + + def map_local_problem(self, local_problem: LocalProblem) -> PlanDelta: + delta = { + asset: {"task": "maintain"} for asset in local_problem.assets + } + return PlanDelta(delta=delta, author=self.adapter_id, contract_id=local_problem.id) diff --git a/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/rover_planner.py b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/rover_planner.py new file mode 100644 index 0000000..8b4f7db --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/adapters/rover_planner.py @@ -0,0 +1,17 @@ +"""Toy rover planner adapter for MVP.""" +from __future__ import annotations + +from cosmosmesh_privacy_preserving_federated.cosmosmesh.models import LocalProblem, PlanDelta +from .base import Adapter + + +class RoverPlannerAdapter(Adapter): + def __init__(self, adapter_id: str = "rover_planner"): + super().__init__(adapter_id=adapter_id, domain="rover") + + def map_local_problem(self, local_problem: LocalProblem) -> PlanDelta: + # Minimal heuristic: assign each asset to perform a placeholder task + delta = { + asset: {"task": "patrol"} for asset in local_problem.assets + } + return PlanDelta(delta=delta, author=self.adapter_id, contract_id=local_problem.id) diff --git a/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/delta_sync.py b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/delta_sync.py new file mode 100644 index 0000000..bff722c --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/delta_sync.py @@ -0,0 +1,23 @@ +"""Deterministic delta-sync primitives for offline/ intermittently-connected operation.""" +from __future__ import annotations + +from typing import Dict, List + + +def reconcile_deltas(existing: Dict[str, Dict], new_deltas: List[Dict]) -> Dict[str, Dict]: + """Deterministically merge PlanDelta-like entries by contract_id. + + - For each delta, keep the entry with the latest timestamp per contract_id. + - existing is a mapping contract_id -> delta dict (already consolidated). + - new_deltas are incoming delta dicts from another asset. + Returns the updated mapping. + """ + updated = dict(existing) + for d in new_deltas: + cid = d.get("contract_id") + ts = d.get("timestamp", 0) + if cid is None: + continue + if cid not in updated or updated[cid].get("timestamp", 0) < ts: + updated[cid] = dict(d) + return updated diff --git a/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/goctr.py b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/goctr.py new file mode 100644 index 0000000..488e455 --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/goctr.py @@ -0,0 +1,28 @@ +"""Graph-of-Contracts (GoC) registry scaffold.""" +from __future__ import annotations + +from dataclasses import dataclass +from typing import Dict, List + + +@dataclass +class GoCEntry: + adapter_id: str + supported_domains: List[str] + contract_version: str + + +class GoCRegistry: + """In-memory, minimal GoC-style registry for adapters and schemas.""" + + def __init__(self) -> None: + self._registry: Dict[str, GoCEntry] = {} + + def register(self, adapter_id: str, supported_domains: List[str], contract_version: str) -> None: + self._registry[adapter_id] = GoCEntry(adapter_id, supported_domains, contract_version) + + def get(self, adapter_id: str) -> GoCEntry | None: + return self._registry.get(adapter_id) + + def list_adapters(self) -> List[str]: + return list(self._registry.keys()) diff --git a/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/models.py b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/models.py new file mode 100644 index 0000000..17d20d4 --- /dev/null +++ b/src/cosmosmesh_privacy_preserving_federated/cosmosmesh/models.py @@ -0,0 +1,105 @@ +"""Data models for LocalProblem, SharedVariables, PlanDelta, etc.""" +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import Any, Dict, List, Optional +import time + + +@dataclass +class LocalProblem: + id: str + domain: str + assets: List[str] + objective: str + constraints: Dict[str, Any] = field(default_factory=dict) + solver_hint: Optional[str] = None + version: int = 1 + + def to_dict(self) -> Dict[str, Any]: + return { + "id": self.id, + "domain": self.domain, + "assets": self.assets, + "objective": self.objective, + "constraints": self.constraints, + "solver_hint": self.solver_hint, + "version": self.version, + } + + +@dataclass +class SharedVariables: + version: int + forecasts: Dict[str, Any] = field(default_factory=dict) + priors: Dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> Dict[str, Any]: + return { + "version": self.version, + "forecasts": self.forecasts, + "priors": self.priors, + } + + +@dataclass +class PlanDelta: + delta: Dict[str, Any] + timestamp: float = field(default_factory=lambda: time.time()) + author: str = "unknown" + contract_id: str = "unknown" + signature: Optional[str] = None + + def to_dict(self) -> Dict[str, Any]: + return { + "delta": self.delta, + "timestamp": self.timestamp, + "author": self.author, + "contract_id": self.contract_id, + "signature": self.signature, + } + + +@dataclass +class DualVariables: + multipliers: Dict[str, float] = field(default_factory=dict) + + def to_dict(self) -> Dict[str, Any]: + return {"multipliers": self.multipliers} + + +@dataclass +class PrivacyBudget: + signal: str + budget: float + expiry: float + + def to_dict(self) -> Dict[str, Any]: + return {"signal": self.signal, "budget": self.budget, "expiry": self.expiry} + + +@dataclass +class AuditLog: + entry: str + signer: str + timestamp: float + contract_id: str + version: int + + def to_dict(self) -> Dict[str, Any]: + return { + "entry": self.entry, + "signer": self.signer, + "timestamp": self.timestamp, + "contract_id": self.contract_id, + "version": self.version, + } + + +@dataclass +class PolicyBlock: + safety: Dict[str, Any] = field(default_factory=dict) + exposure_rules: Dict[str, Any] = field(default_factory=dict) + + def to_dict(self) -> Dict[str, Any]: + return {"safety": self.safety, "exposure_rules": self.exposure_rules} diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..20d6006 --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,56 @@ +import time +from cosmosmesh_privacy_preserving_federated.cosmosmesh.models import LocalProblem, SharedVariables +from cosmosmesh_privacy_preserving_federated.cosmosmesh.delta_sync import reconcile_deltas +from cosmosmesh_privacy_preserving_federated.cosmosmesh.adapters import RoverPlannerAdapter, HabitatModuleAdapter + + +def test_models_basic_serialization(): + lp = LocalProblem( + id="lp-1", + domain="rover", + assets=["rover-1"], + objective="minimize_energy", + constraints={"max_time": 3600}, + solver_hint="-quadratic", + version=1, + ) + + sv = SharedVariables(version=1, forecasts={"energy": 100}, priors={"energy": 90}) + + assert isinstance(lp.to_dict(), dict) + assert isinstance(sv.to_dict(), dict) + + +def test_delta_sync_reconcile_basic(): + # Existing state has a delta for contract 'c1' + existing = { + "c1": {"delta": {"assignment": {"a1": "idle"}}, "timestamp": 1.0, "contract_id": "c1"}, + } + new = [ + {"delta": {"assignment": {"a2": "patrol"}}, "timestamp": 2.0, "contract_id": "c1"}, + {"delta": {"assignment": {"a3": "scan"}}, "timestamp": 0.5, "contract_id": "c2"}, + ] + updated = reconcile_deltas(existing, new) + assert "c1" in updated + assert updated["c1"]["delta"]["assignment"].get("a2") == "patrol" + assert "c2" in updated + + +def test_adapters_mapping_exist(): + lp = LocalProblem( + id="lp-2", + domain="rover", + assets=["rover-1", "rover-2"], + objective="balance", + constraints={}, + ) + rover = RoverPlannerAdapter() + habitat = HabitatModuleAdapter() + + delta1 = rover.map_local_problem(lp) + delta2 = habitat.map_local_problem(lp) + + assert isinstance(delta1, type(delta1)) + assert delta1.contract_id == lp.id + assert isinstance(delta2, type(delta2)) + assert delta2.contract_id == lp.id