build(agent): molt-d#cb502d iteration

This commit is contained in:
agent-cb502d7656738cf6 2026-04-17 09:30:40 +02:00
parent 6591349ef9
commit c202daaf95
7 changed files with 359 additions and 237 deletions

View File

@ -1,30 +1,16 @@
# CosmosMesh Privacy-Preserving Federated (CatOpt bridge MVP) CosmosMesh Privacy-Preserving Federated Mission Planning (CatOpt bridge MVP)
This repository contains a production-oriented MVP scaffold for CosmosMesh, focused on privacy-preserving federated mission planning in deep-space constellations. It provides a canonical EnergiBridge/CatOpt bridge that maps CosmosMesh primitives to a vendor-agnostic intermediate representation (IR) to enable cross-domain adapters with minimal rework. This repository provides a production-oriented MVP scaffold for privacy-preserving, federated planning across heterogeneous deep-space assets. The CatOpt bridge maps CosmosMesh primitives into a vendor-agnostic intermediate representation to enable cross-domain adapters with minimal rework.
Key concepts - Core concepts
- LocalProblem: per-asset planning task with objective, variables, and constraints. - CatOpt bridge primitives and a minimal Graph-of-Contracts (GoC) registry
- SharedVariables / DualVariables: versioned signals used for federated optimization. - Lightweight adapters (rover_planner, habitat_module) over TLS
- PlanDelta: incremental plan updates with cryptographic tags. - Minimal data contracts: LocalProblem, SharedVariables, DualVariables, PlanDelta, PrivacyBudget, AuditLog
- TimeMonoid and per-message metadata: timing, nonce, and versioning for replay protection. - End-to-end delta-sync sketch with deterministic offline replay
- Graph-of-Contracts registry: versioned data schemas and adapter conformance harness. - Basic security primitives (signatures, per-message metadata) suitable for MVP
MVP highlights
- A 23 asset testbed with a simple quadratic objective (e.g., task allocation + energy budgeting) and an ADMM-lite solver.
- Data contracts seeds: LocalProblem, SharedVariables, DualVariables, PlanDelta, PrivacyBudget, AuditLog.
- Deterministic delta-sync for intermittent connectivity with audit trails.
- DID/short-lived certs baseline for identity and security.
- Two reference adapters and a space-scenario simulator to validate convergence.
Usage Usage
- The core bridge lives under `src/cosmosmesh_privacy_preserving_federated/`. - Import modules under src/cosmosmesh_privacy_preserving_federated/
- CatOptBridge provides to_catopt/from_catopt helpers for LocalProblem objects. - Run tests via ./test.sh (pytest-based tests included)
- EnergiBridge is a minimal stub for cross-domain interoperability.
Build and tests This README intentionally keeps surface area small while documenting how to extend for a production-grade setup.
- The project uses a pyproject.toml build configuration. Run:
- python3 -m build
- Tests (if present) use pytest. Run:
- pytest -q
See CONTRIBUTING guidelines in AGENTS.md for how to contribute and extend the MVP.

View File

@ -1,16 +1,13 @@
"""CosmosMesh Privacy-Preserving Federated package. """CosmosMesh Privacy-Preserving Federated package init."""
Exposes MVP scaffolds for bridging CosmosMesh primitives to a CatOpt-style from .catopt_bridge import LocalProblem, SharedVariables, DualVariables, PlanDelta, PrivacyBudget, AuditLog, GraphOfContracts
representation via the catopt_bridge module and a lightweight EnergiBridge
for canonical interoperability with a CatOpt-like IR.
"""
from .catopt_bridge import CatOptBridge, ContractRegistry, LocalProblem __all__ = [
try: "LocalProblem",
from .energi_bridge import EnergiBridge, LocalProblemEP # type: ignore "SharedVariables",
__all__ = ["CatOptBridge", "ContractRegistry", "LocalProblem", "EnergiBridge", "LocalProblemEP"] "DualVariables",
except Exception: "PlanDelta",
# EnergiBridge not yet available; avoid import-time failure for MVPs that "PrivacyBudget",
# import this package before energibridge module is added in a follow-up "AuditLog",
# patch. "GraphOfContracts",
__all__ = ["CatOptBridge", "ContractRegistry", "LocalProblem"] ]

View File

@ -0,0 +1,3 @@
"""Adapters namespace for CosmosMesh MVP."""
__all__ = ["rover_planner", "habitat_module"]

View File

@ -1,31 +1,41 @@
"""Toy habitat_module adapter for CosmosMesh CatOpt bridge."""
from __future__ import annotations from __future__ import annotations
from typing import Any, Dict
from cosmosmesh_privacy_preserving_federated.catopt_bridge import LocalProblem, SharedVariables, DualVariables from dataclasses import dataclass, asdict
from typing import Dict, Any
from datetime import datetime
class HabitatModuleAdapter: @dataclass
"""Minimal habitat module adapter scaffold. class HabitatState:
id: str
last_seen: datetime
plan: Dict[str, Any]
Provides a tiny interface consistent with the Rover adapter to demonstrate
end-to-end flow in the MVP. This is intentionally lightweight.
"""
def __init__(self) -> None: class HabitatModule:
pass def __init__(self, module_id: str) -> None:
self.module_id = module_id
def read_state(self) -> Dict[str, Any]: self.state = HabitatState(
return {"life_support": {"oxygen": 21.0, "co2": 0.04}, "status": "ok"} id=module_id,
last_seen=datetime.utcnow(),
def expose_local_problem_data(self) -> LocalProblem: plan={"tasks": []},
lp = LocalProblem(
id="lp_habitat_1",
objective="balance_life_support",
variables={"heater": 0.5},
constraints=["power<=100"],
version=1,
) )
return lp
def apply_command(self, command: Dict[str, Any]) -> Dict[str, Any]: def readState(self) -> Dict[str, Any]:
return {"ack": True, "command": command} self.state.last_seen = datetime.utcnow()
return asdict(self.state)
def exposeLocalProblemData(self) -> Dict[str, Any]:
return {"module_id": self.module_id, "plan": self.state.plan}
def applyCommand(self, command: Dict[str, Any]) -> bool:
if not isinstance(command, dict):
return False
update = command.get("update", {})
self.state.plan.update(update)
self.state.last_seen = datetime.utcnow()
return True
__all__ = ["HabitatModule"]

View File

@ -1,39 +1,44 @@
"""Toy rover planner adapter for CosmosMesh CatOpt bridge. """Toy rover_planner adapter for CosmosMesh CatOpt bridge.
This adapter implements a minimal interface compatible with the CatOpt bridge Implements a minimal interface: readState, exposeLocalProblemData, applyCommand.
and demonstrates how a device-specific model could be translated into a LocalProblem The real system would implement TLS transport and cryptographic signaling.
and how to expose local data as SharedVariables.
""" """
from __future__ import annotations from __future__ import annotations
from typing import Any, Dict, List from dataclasses import dataclass, asdict
from typing import Dict, Any
from datetime import datetime
from cosmosmesh_privacy_preserving_federated.catopt_bridge import LocalProblem, SharedVariable, PlanDelta, CatOptBridge @dataclass
class RoverState:
id: str
last_seen: datetime
local_problem: Dict[str, Any]
class RoverPlannerAdapter: class RoverPlanner:
def __init__(self, planner_id: str = "rover-1") -> None: def __init__(self, rover_id: str) -> None:
self.planner_id = planner_id self.rover_id = rover_id
# Use a loosely-typed state dict to handle diverse state shapes across adapters self.state = RoverState(
self._state: Dict[str, Any] = { id=rover_id,
"tasks": ["survey", "sample"] last_seen=datetime.utcnow(),
} local_problem={"objective": {"maximize": 1.0}, "assets": [rover_id]},
)
def readState(self) -> Dict[str, Any]: def readState(self) -> Dict[str, Any]:
# Return a small snapshot of rover state as a dict self.state.last_seen = datetime.utcnow()
return {"planner_id": self.planner_id, "state": self._state} return asdict(self.state)
def exposeLocalProblemData(self) -> LocalProblem: def exposeLocalProblemData(self) -> Dict[str, Any]:
# Expose a simple LocalProblem for the rover to solve return {"rover_id": self.rover_id, "local_problem": self.state.local_problem}
lp = LocalProblem(
problem_id=f"rover-{self.planner_id}-lp",
version=1,
variables={"task_weight": 1.0, "battery": 90.0},
objective=0.0,
constraints=[{"battery_min": 20.0}],
)
return lp
def applyCommand(self, command: Dict[str, Any]) -> None: def applyCommand(self, command: Dict[str, Any]) -> bool:
# Apply a command to the rover planner (no-op in this toy example) # Very lightweight: merge command into local_problem and update timestamp
self._state["last_command"] = command if not isinstance(command, dict):
return False
self.state.local_problem.update(command.get("update", {}))
self.state.last_seen = datetime.utcnow()
return True
__all__ = ["RoverPlanner"]

View File

@ -1,200 +1,270 @@
""" """
Minimal EnergiBridge canonical bridge (CatOpt-like IR) for CosmosMesh MVP. Minimal CatOpt-inspired bridge scaffolding for CosmosMesh MVP.
This module provides lightweight data models and conversion utilities that map This module provides lightweight data models and utilities to map
CosmosMesh MVP primitives to a vendor-agnostic intermediate representation CosmosMesh primitives to a vendor-agnostic intermediate representation
(CatOpt-inspired). It is intentionally small and testable to bootstrap (CatOpt IR) used by adapters. It is intentionally small but production-ready
interoperability with simple adapters. enough to bootstrap interoperability tests.
""" """
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, asdict from dataclasses import dataclass, field
from typing import Any, Dict, List from datetime import datetime
from typing import Any, Dict, List, Optional
import hashlib
import json
@dataclass
class LocalProblem: class LocalProblem:
"""A minimal local optimization problem instance with broad constructor support. # Compatibility with tests: support both 'id' and 'problem_id' as entry points
id: Optional[str] = None
problem_id: Optional[str] = None
domain: Optional[str] = None
# Compatibility alias: tests may pass 'assets' or 'variables'
assets: List[str] = field(default_factory=list)
variables: List[str] = field(default_factory=list)
objective: Any = None
constraints: Any = None
version: int = 1
This class aims to be flexible to accommodate multiple naming styles used def __post_init__(self):
across tests and codebases: # Normalize IDs
- id / problem_id if self.id is None:
- domain / space/domain self.id = self.problem_id
- assets / variables if self.problem_id is None:
- objective / constraints
"""
def __init__(self, id: str | None = None, problem_id: str | None = None,
domain: str | None = None, assets: List[str] | None = None,
objective: Dict[str, Any] | None = None,
constraints: Dict[str, Any] | None = None,
variables: List[str] | None = None,
version: int = 1, **kwargs):
self.id = id or problem_id or kwargs.get("problem_id") or "lp-unnamed"
self.domain = domain or kwargs.get("domain", "")
# Support both assets and variables naming
self.assets = assets if assets is not None else (variables or [])
self.objective = objective if objective is not None else kwargs.get("objective", {})
self.constraints = constraints if constraints is not None else kwargs.get("constraints", {})
self.version = version
# Backwards-compat alias so tests can access problem_id
self.problem_id = self.id self.problem_id = self.id
# Normalize assets/variables aliasing
if not self.assets:
self.assets = list(self.variables) if self.variables else []
if not self.variables:
self.variables = list(self.assets) if self.assets else []
def to_dict(self) -> Dict[str, Any]: def to_catopt(self) -> Dict[str, Any]:
return { return {
"type": "LocalProblem",
"payload": {
"id": self.id, "id": self.id,
"domain": self.domain, "domain": self.domain,
"assets": self.assets, "assets": self.assets,
"objective": self.objective, "objective": self.objective,
"constraints": self.constraints, "constraints": self.constraints,
"version": self.version, "version": self.version,
},
} }
@dataclass
class SharedVariables: class SharedVariables:
"""Backward-compatible container for forecasts/priors (legacy form).""" version: int
def __init__(self, forecasts: Dict[str, Any], priors: Dict[str, Any], version: int): forecasts: Dict[str, Any] = field(default_factory=dict)
self.forecasts = forecasts priors: Dict[str, Any] = field(default_factory=dict)
self.priors = priors
self.version = version
def to_dict(self) -> Dict[str, Any]:
return {"forecasts": self.forecasts, "priors": self.priors, "version": self.version}
def to_catopt(self) -> Dict[str, Any]:
return {
"type": "SharedVariables",
"version": self.version,
"forecasts": self.forecasts,
"priors": self.priors,
}
# Singular variants expected by tests
@dataclass
class SharedVariable: class SharedVariable:
"""Single signal representation (name/value) used by tests.""" name: str
def __init__(self, name: str, value: Any, version: int): value: Any
self.name = name version: int = 1
self.value = value
self.version = version
def to_dict(self) -> Dict[str, Any]: @dataclass
return {"name": self.name, "value": self.value, "version": self.version} class DualVariable:
name: str
value: Any
version: int = 1
@dataclass @dataclass
class DualVariables: class DualVariables:
"""Lagrange multipliers or dual signals.""" version: int
multipliers: Dict[str, float] multipliers: Dict[str, float] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]: def to_catopt(self) -> Dict[str, Any]:
return asdict(self) return {
"type": "DualVariables",
"version": self.version,
class DualVariable: "multipliers": self.multipliers,
"""Single dual-variable signal for compatibility with tests.""" }
def __init__(self, name: str, value: float, version: int):
self.name = name
self.value = value
self.version = version
def to_dict(self) -> Dict[str, Any]:
return {"name": self.name, "value": self.value, "version": self.version}
@dataclass @dataclass
class PlanDelta: class PlanDelta:
"""Incremental plan changes with crypto-like tags for auditability.""" contract_id: str
delta: Dict[str, Any] delta: Dict[str, Any]
timestamp: float timestamp: datetime
author: str author: str
contract_id: int signature: str
signature: str # placeholder for cryptographic tag
def to_dict(self) -> Dict[str, Any]: def sign(self, private_key: str) -> None:
return asdict(self) # Very small deterministic sign for demo purposes
payload = json.dumps({
"contract_id": self.contract_id,
"delta": self.delta,
"timestamp": self.timestamp.isoformat(),
"author": self.author,
}, sort_keys=True)
# naive sign: hash of payload + key
self.signature = hashlib.sha256((payload + private_key).encode()).hexdigest()
def to_catopt(self) -> Dict[str, Any]:
return {
"type": "PlanDelta",
"contract_id": self.contract_id,
"delta": self.delta,
"timestamp": self.timestamp.isoformat(),
"author": self.author,
"signature": self.signature,
}
@dataclass @dataclass
class PrivacyBudget: class PrivacyBudget:
"""Simple privacy budget block per signal.""" actor: str
signal: str remaining: float
budget: float expiry: datetime
expiry: float
def to_dict(self) -> Dict[str, Any]: def to_catopt(self) -> Dict[str, Any]:
return asdict(self) return {
"type": "PrivacyBudget",
"actor": self.actor,
"remaining": self.remaining,
"expiry": self.expiry.isoformat(),
}
@dataclass @dataclass
class AuditLog: class AuditLog:
"""Tamper-evident log entry for governance/audit.""" contract_id: str
entry: str entry: str
signer: str signer: str
timestamp: float timestamp: datetime
contract_id: int
def to_dict(self) -> Dict[str, Any]: def to_catopt(self) -> Dict[str, Any]:
return asdict(self) return {
"type": "AuditLog",
"contract_id": self.contract_id,
"entry": self.entry,
"signer": self.signer,
"timestamp": self.timestamp.isoformat(),
}
class GraphOfContracts:
"""Minimal registry of adapters and schemas (GoC).
This is intentionally tiny but demonstrates API shape for a registry.
"""
def __init__(self) -> None:
self._contracts: Dict[str, Dict[str, Any]] = {}
def register(self, contract_id: str, descriptor: Dict[str, Any]) -> None:
self._contracts[contract_id] = descriptor
def list_contracts(self) -> List[Dict[str, Any]]:
return [{"contract_id": cid, **desc} for cid, desc in self._contracts.items()]
def get(self, contract_id: str) -> Optional[Dict[str, Any]]:
return self._contracts.get(contract_id)
def sample_end_to_end_mapping():
"""Return a tiny end-to-end sample representation to validate mapping.
This is a convenience helper and not part of the public API surface.
"""
lp = LocalProblem(
id="lp-0001",
domain="space-ops",
assets=["rover-1", "drone-a"],
objective={"maximize": {"util": 1.0}},
constraints=[{"power": {"<=": 100.0}}],
)
sv = SharedVariables(version=1, forecasts={"deadline": 1234}, priors={"p": 0.5})
dv = DualVariables(version=1, multipliers={"lambda": 0.1})
return lp.to_catopt(), sv.to_catopt(), dv.to_catopt()
class CatOptBridge:
"""Minimal bridge facade for test interoperability."""
@staticmethod
def build_round_trip(problem: LocalProblem, shared: List[SharedVariable], duals: List[DualVariable]) -> Dict[str, Any]:
obj_id = problem.id or problem.problem_id
payload = {
"object": {
"id": obj_id,
"domain": problem.domain,
"assets": problem.assets or problem.variables,
"objective": problem.objective,
"constraints": problem.constraints,
"version": problem.version,
},
"morphisms": [],
}
for s in (shared or []):
payload["morphisms"].append({"name": s.name, "value": s.value, "version": s.version})
for d in (duals or []):
payload["morphisms"].append({"name": d.name, "value": d.value, "version": d.version})
return {"kind": "RoundTrip", "payload": payload}
__all__ = [
"LocalProblem",
"SharedVariables",
"DualVariables",
"PlanDelta",
"PrivacyBudget",
"AuditLog",
"GraphOfContracts",
"sample_end_to_end_mapping",
"SharedVariable",
"DualVariable",
"CatOptBridge",
"to_catopt",
"from_catopt",
"Registry",
]
# Public helpers expected by tests
def to_catopt(lp: LocalProblem) -> Dict[str, Any]:
return lp.to_catopt()
def from_catopt(catopt: Dict[str, Any]) -> Optional[LocalProblem]:
payload = catopt.get("payload") or {}
if not payload:
return None
lp = LocalProblem(
id=payload.get("id"),
problem_id=payload.get("id"),
domain=payload.get("domain"),
assets=payload.get("assets") or payload.get("variables") or [],
objective=payload.get("objective"),
constraints=payload.get("constraints"),
version=payload.get("version", 1),
)
return lp
class Registry: class Registry:
"""Tiny Graph-of-Contracts registry for adapters and data schemas.""" """Lightweight contract registry used by tests."""
def __init__(self) -> None: def __init__(self) -> None:
self._contracts: Dict[int, Dict[str, Any]] = {} self._contracts: Dict[int, Dict[str, Any]] = {}
def register_contract(self, contract_id: int, schema: Dict[str, Any]) -> None: def register_contract(self, contract_id: int, descriptor: Dict[str, Any]) -> None:
self._contracts[contract_id] = dict(schema) self._contracts[contract_id] = descriptor
def get_contract(self, contract_id: int) -> Dict[str, Any]: def get_contract(self, contract_id: int) -> Optional[Dict[str, Any]]:
return self._contracts.get(contract_id, {}) return self._contracts.get(contract_id)
def list_contracts(self) -> List[int]: def list_contracts(self) -> List[int]:
return sorted(self._contracts.keys()) return list(self._contracts.keys())
def to_catopt(local_problem: LocalProblem) -> Dict[str, Any]:
"""Convert a LocalProblem into a CatOpt-like IR payload."""
payload = local_problem.to_dict()
return {"type": "LocalProblem", "payload": payload}
def from_catopt(catopt: Dict[str, Any]) -> LocalProblem | None:
"""Convert a CatOpt IR payload back into a LocalProblem if type matches."""
if not isinstance(catopt, dict) or catopt.get("type") != "LocalProblem":
return None
payload = catopt.get("payload", {})
try:
return LocalProblem(
id=payload["id"],
domain=payload["domain"],
assets=payload["assets"],
objective=payload["objective"],
constraints=payload["constraints"],
)
except KeyError:
return None
# Compatibility aliases for existing tests and __init__ expectations
class CatOptBridge(Registry):
"""Backward-compatible bridge facade exposing Registry-like API."""
pass
@classmethod
def build_round_trip(cls, problem: LocalProblem, shared: list, duals: list) -> dict:
"""Construct a RoundTrip envelope containing the problem and signals.
This tiny helper is designed for tests to validate end-to-end
interoperability without requiring a full protocol stack.
"""
payload = {
"object": {"id": getattr(problem, "problem_id", getattr(problem, "id", None))},
"morphisms": [] ,
}
for sv in (shared or []):
if hasattr(sv, "to_dict"):
payload["morphisms"].append(sv.to_dict())
else:
# fallback if plain dict
payload["morphisms"].append(dict(sv))
for dv in (duals or []):
if hasattr(dv, "to_dict"):
payload["morphisms"].append(dv.to_dict())
else:
payload["morphisms"].append(dict(dv))
return {"kind": "RoundTrip", "payload": payload}
# Public aliases expected by tests / API
ContractRegistry = Registry

View File

@ -0,0 +1,51 @@
import datetime
import pytest
import os
import sys
# 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)
from cosmosmesh_privacy_preserving_federated.catopt_bridge import (
LocalProblem,
SharedVariables,
DualVariables,
PlanDelta,
PrivacyBudget,
AuditLog,
GraphOfContracts,
sample_end_to_end_mapping,
)
def test_catopt_basic_dataclasses_roundtrip():
lp = LocalProblem(
id="lp-xyz",
domain="test",
assets=["a1"],
objective={"maximize": 1.0},
constraints=[{"c": 1}],
)
sv = SharedVariables(version=1)
dv = DualVariables(version=1)
_ = lp.to_catopt()
_ = sv.to_catopt()
_ = dv.to_catopt()
def test_sample_end_to_end_mapping_returns_three_catopt_dicts():
lp_dict, sv_dict, dv_dict = sample_end_to_end_mapping()
assert isinstance(lp_dict, dict) and lp_dict.get("type") == "LocalProblem"
assert isinstance(sv_dict, dict) and sv_dict.get("type") == "SharedVariables"
assert isinstance(dv_dict, dict) and dv_dict.get("type") == "DualVariables"
def test_graph_of_contracts_basic():
g = GraphOfContracts()
g.register("contract-A", {"name": "TestAdapter", "version": "0.0.1"})
items = g.list_contracts()
assert isinstance(items, list) and items[0]["contract_id"] == "contract-A"