build(agent): molt-d#cb502d iteration
This commit is contained in:
parent
743f3a278c
commit
2b32d1dcc8
|
|
@ -0,0 +1,155 @@
|
|||
"""Contracts and registry scaffolding for CosmosMesh MVP.
|
||||
|
||||
This module provides lightweight data-contract primitives used by the
|
||||
privacy-preserving federated planning MVP. The goal is to offer a minimal,
|
||||
well-typed surface to express LocalProblem data, shared/delta signals, and
|
||||
auditability metadata that can be exchanged between agents.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, asdict
|
||||
from typing import Any, Dict, Optional
|
||||
import json
|
||||
|
||||
|
||||
@dataclass
|
||||
class LocalProblem:
|
||||
"""Represents a per-agent optimization problem contract.
|
||||
|
||||
- agent_id: unique identifier for the agent
|
||||
- variables: mapping of variable names to current values (e.g., decision vars)
|
||||
- objective: a simple representation of the objective (coefficients or metadata)
|
||||
- constraints: opaque dict describing constraints (kept generic for MVP)
|
||||
- version: optional version of the contract for auditing
|
||||
"""
|
||||
|
||||
agent_id: str
|
||||
variables: Dict[str, float]
|
||||
objective: Dict[str, float]
|
||||
constraints: Dict[str, Any]
|
||||
version: Optional[str] = None
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps(asdict(self))
|
||||
|
||||
@staticmethod
|
||||
def from_json(payload: str) -> "LocalProblem":
|
||||
data = json.loads(payload)
|
||||
return LocalProblem(**data) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@dataclass
|
||||
class SharedVariables:
|
||||
"""Container for signals shared across agents (e.g., primal variables)."""
|
||||
|
||||
variables: Dict[str, float]
|
||||
version: Optional[str] = None
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps(asdict(self))
|
||||
|
||||
@staticmethod
|
||||
def from_json(payload: str) -> "SharedVariables":
|
||||
data = json.loads(payload)
|
||||
return SharedVariables(**data) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@dataclass
|
||||
class DualVariables:
|
||||
"""Dual variables carried alongside shared variables."""
|
||||
|
||||
values: Dict[str, float]
|
||||
version: Optional[str] = None
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps(asdict(self))
|
||||
|
||||
@staticmethod
|
||||
def from_json(payload: str) -> "DualVariables":
|
||||
data = json.loads(payload)
|
||||
return DualVariables(**data) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PlanDelta:
|
||||
"""Represents a delta/patch to a previously computed plan."""
|
||||
|
||||
agent_id: str
|
||||
delta: Dict[str, Any]
|
||||
version: Optional[str] = None
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps(asdict(self))
|
||||
|
||||
@staticmethod
|
||||
def from_json(payload: str) -> "PlanDelta":
|
||||
data = json.loads(payload)
|
||||
return PlanDelta(**data) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@dataclass
|
||||
class PrivacyBudget:
|
||||
"""Minimal privacy budget descriptor for local-dp/shared signals."""
|
||||
|
||||
budget: float
|
||||
spent: float = 0.0
|
||||
version: Optional[str] = None
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps(asdict(self))
|
||||
|
||||
@staticmethod
|
||||
def from_json(payload: str) -> "PrivacyBudget":
|
||||
data = json.loads(payload)
|
||||
return PrivacyBudget(**data) # type: ignore[arg-type]
|
||||
|
||||
|
||||
@dataclass
|
||||
class AuditLog:
|
||||
"""Auditable event payload used for replay protection and tracing."""
|
||||
|
||||
event: str
|
||||
metadata: Dict[str, Any]
|
||||
timestamp: float
|
||||
version: Optional[str] = None
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps(asdict(self))
|
||||
|
||||
@staticmethod
|
||||
def from_json(payload: str) -> "AuditLog":
|
||||
data = json.loads(payload)
|
||||
return AuditLog(**data) # type: ignore[arg-type]
|
||||
|
||||
|
||||
class GraphOfContracts:
|
||||
"""A tiny registry that maps contract names to versioned schemas.
|
||||
|
||||
This is intentionally lightweight and intended for MVP scaffolding. It
|
||||
allows registering known contract names with a version string and
|
||||
retrieving their canonical representation. The actual schema registry would
|
||||
be a separate service in a full implementation.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._registry: Dict[str, str] = {}
|
||||
|
||||
def register(self, name: str, version: str) -> None:
|
||||
self._registry[str(name)] = str(version)
|
||||
|
||||
def get_version(self, name: str) -> Optional[str]:
|
||||
return self._registry.get(name)
|
||||
|
||||
def to_json(self) -> str:
|
||||
return json.dumps(self._registry)
|
||||
|
||||
|
||||
__all__ = [
|
||||
"LocalProblem",
|
||||
"SharedVariables",
|
||||
"DualVariables",
|
||||
"PlanDelta",
|
||||
"PrivacyBudget",
|
||||
"AuditLog",
|
||||
"GraphOfContracts",
|
||||
]
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
import json
|
||||
import time
|
||||
|
||||
from cosmosmesh_privacy_preserving_federated.contracts import LocalProblem, GraphOfContracts
|
||||
|
||||
|
||||
def test_local_problem_json_roundtrip():
|
||||
lp = LocalProblem(
|
||||
agent_id="rover-1",
|
||||
variables={"x": 1.0, "y": 2.0},
|
||||
objective={"coef": 0.5},
|
||||
constraints={"type": "ineq", "limit": 10.0},
|
||||
version="v0.1",
|
||||
)
|
||||
payload = lp.to_json()
|
||||
lp2 = LocalProblem.from_json(payload)
|
||||
assert lp2.agent_id == lp.agent_id
|
||||
assert lp2.variables == lp.variables
|
||||
assert lp2.version == lp.version
|
||||
|
||||
|
||||
def test_graph_of_contracts_registry():
|
||||
g = GraphOfContracts()
|
||||
g.register("LocalProblem", "v0.1")
|
||||
g.register("PlanDelta", "v0.2")
|
||||
assert g.get_version("LocalProblem") == "v0.1"
|
||||
assert g.get_version("PlanDelta") == "v0.2"
|
||||
# ensure JSON export works and is parseable
|
||||
data = g.to_json()
|
||||
assert isinstance(data, str)
|
||||
parsed = json.loads(data)
|
||||
assert "LocalProblem" in parsed
|
||||
Loading…
Reference in New Issue