build(agent): new-agents#a6e6ec iteration
This commit is contained in:
parent
2e99839077
commit
4594631bbb
61
AGENTS.md
61
AGENTS.md
|
|
@ -1,43 +1,28 @@
|
||||||
CosmosMesh Privacy-Preserving Federated Mission Planning
|
# CosmosMesh MVP: Architecture & Contribution
|
||||||
|
|
||||||
Architecture overview
|
Overview
|
||||||
- Language: Python for MVP scaffolding (scaffold only)
|
- This workspace implements a minimal CatOpt-Bridge MVP for CosmosMesh privacy-preserving federated planning.
|
||||||
- Core package: cosmosmesh_privacy_preserving_federated_
|
- Language: Python. Package layout under src/ for a PyPI-friendly install.
|
||||||
- Tests: pytest-driven in tests/ directory
|
- Core MVP: LocalProblems, SharedVariables, PlanDelta, and a tiny Graph-of-Contracts (GoC) registry scaffold, plus a minimal DSL sketch.
|
||||||
- Build: Python packaging with pyproject.toml using setuptools
|
|
||||||
|
|
||||||
How to contribute
|
Tech Stack
|
||||||
- Run tests with ./test.sh
|
- Python 3.x, dataclasses for lightweight data models
|
||||||
- Package name and version are defined in pyproject.toml
|
- Lightweight unit tests with Python's unittest (pytest-compatible)
|
||||||
- README describes how to extend the MVP and plug in adapters
|
- TLS-ready stubs for adapters and transport (to be wired later)
|
||||||
|
|
||||||
Testing commands
|
Testing & Build
|
||||||
- Build the project: python3 -m build
|
- Run tests via: bash test.sh
|
||||||
- Run tests: pytest -q
|
- Build the package via: python3 -m build
|
||||||
- Run the complete test script: bash test.sh
|
- This repo includes a minimal test to validate the CatOpt bridge round-trip
|
||||||
|
|
||||||
Rules
|
Contribution Rules
|
||||||
- Do not modify public API semantics for MVP scaffolding unless asked
|
- Keep changes small and focused, with clear intent and minimal surface area
|
||||||
- Focus on small, correct changes and clear documentation
|
- Add tests for any new public surface
|
||||||
|
- Do not modify public API semantics without explicit approval
|
||||||
|
- If you add new dependencies, add them to pyproject.toml and ensure tests pass
|
||||||
|
- Follow the style used in the repo (ASCII, concise code, docstrings where helpful)
|
||||||
|
|
||||||
CosmosMesh CatOpt bridge (MVP)
|
How to extend (high level)
|
||||||
- We are prototyping a lightweight interoperability bridge that maps CosmosMesh MVP primitives to a CatOpt-style representation (Objects/ Morphisms/ Functors) to enable cross-domain experimentation without heavy dependencies.
|
- Add more adapters (e.g., rover_planner, habitat_module) under a dedicated adapters module
|
||||||
- Starter adapters: rover and habitat module adapters exposing simple interfaces for readState, exposeLocalProblemData, and applyCommand.
|
- Expand the DSL sketch to cover more primitives (DualVariables, PrivacyBudget, AuditLog, PolicyBlock)
|
||||||
- Transport: TLS-based, e.g., MQTT/REST for prototyping.
|
- Implement a small simulator to exercise delta-sync and islanding scenarios
|
||||||
- Deliverables: a minimal CatOpt bridge module (src/cosmosmesh_privacy_preserving_federated/catopt_bridge.py), a small registry graph for contracts, and a DSL sketch to describe LocalProblem/SharedVariables/DualVariables/PlanDelta.
|
|
||||||
CosmosMesh GoC Bridge (Plan)
|
|
||||||
- Purpose: provide a canonical, vendor-agnostic interoperability layer that maps CosmosMesh primitives to a CatOpt-inspired intermediate representation (IR) to enable cross-domain adapters with minimal rework.
|
|
||||||
- Core concepts:
|
|
||||||
- Objects -> LocalProblems (per-asset planning state)
|
|
||||||
- Morphisms -> SharedVariables / DualVariables (versioned summaries, priors)
|
|
||||||
- PlanDelta -> incremental plan changes with cryptographic tags
|
|
||||||
- TimeMonoid / Metadata -> per-message timing, nonce, signatures for replay protection
|
|
||||||
- Graph-of-Contracts registry -> versioned data schemas and adapter conformance harness
|
|
||||||
- MVP wiring (8–12 weeks, 2–3 agents to start):
|
|
||||||
1) Phase 0: protocol skeleton + 2 starter adapters (rover_planner, habitat_module) with TLS transport; lightweight ADMM-lite local solver; delta-sync with deterministic replay on reconnects;
|
|
||||||
2) Phase 1: governance ledger scaffold; identity layer (DID/short-lived certs); secure aggregation for SharedVariables; adapter conformance tests.
|
|
||||||
3) Phase 2: cross-domain demo in a simulated second domain; publish a CosmosMesh SDK and a canonical transport; toy contract examples and adapters.
|
|
||||||
4) Phase 3: hardware-in-the-loop validation with Gazebo/ROS for 2–3 devices; KPI dashboards for convergence speed, delta-sync latency, auditability.
|
|
||||||
- Deliverables to align with repo: add a minimal goC_bridge.py (already added in this patch), a canonical registry, and a small DSL sketch for contracts. The initial implementation focuses on data models and conversion utilities to bootstrap adapters.
|
|
||||||
- Testing approach: unit tests for to_catopt/from_catopt conversions, registry registration, and adapter wiring stubs. End-to-end tests to verify end-to-end delta creation and metadata propagation on a simulated pair of agents.
|
|
||||||
- Open questions: confirm preferred identity scheme (DID vs short-lived certs) and transport (TLS over MQTT vs REST) for the MVP in your environment.
|
|
||||||
|
|
|
||||||
54
README.md
54
README.md
|
|
@ -1,47 +1,17 @@
|
||||||
CosmosMesh Privacy-Preserving Federated Mission Planning (CatOpt bridge MVP)
|
# CosmosMesh Privacy-Preserving Federated Mission Planning (MVP)
|
||||||
|
|
||||||
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.
|
This repository contains a minimal MVP oriented toward a CatOpt-inspired interoperability bridge for CosmosMesh primitives. It is intended to bootstrap inter-domain adapters and testing of a privacy-preserving federated planning workflow.
|
||||||
|
|
||||||
- Core concepts
|
What you get in this MVP:
|
||||||
- CatOpt bridge primitives and a minimal Graph-of-Contracts (GoC) registry
|
- A canonical bridge module that maps CosmosMesh primitives to a vendor-agnostic intermediate representation (Objects, Morphisms, PlanDelta, etc.).
|
||||||
- Lightweight adapters (rover_planner, habitat_module) over TLS
|
- A tiny DSL sketch outlining LocalProblem, SharedVariables, PlanDelta, DualVariables, PrivacyBudget, and AuditLog.
|
||||||
- Minimal data contracts: LocalProblem, SharedVariables, DualVariables, PlanDelta, PrivacyBudget, AuditLog
|
- Basic data-models and a conformance scaffold to help bootstrap adapters and a small registry for contracts.
|
||||||
- End-to-end delta-sync sketch with deterministic offline replay
|
- Tests that exercise a roundtrip between the IR and local structures.
|
||||||
- Basic security primitives (signatures, per-message metadata) suitable for MVP
|
|
||||||
|
|
||||||
Usage
|
Usage hints:
|
||||||
- Import modules under src/cosmosmesh_privacy_preserving_federated/
|
- See tests/test_catopt_bridge.py for a usage example and expected round-trip behavior.
|
||||||
- Run tests via ./test.sh (pytest-based tests included)
|
- Extend the DSL sketch and bridge as you add more primitives and adapter specifics.
|
||||||
|
|
||||||
This README intentionally keeps surface area small while documenting how to extend for a production-grade setup.
|
Note: This is intentionally minimal to keep the MVP small and reliable; it will be extended in future iterations to cover full security, consent, and governance concerns.
|
||||||
|
|
||||||
-## Publishing Readiness
|
"""
|
||||||
|
|
||||||
- All tests pass (pytest) and packaging checks succeed via test.sh, which also validates Python packaging metadata.
|
|
||||||
- This MVP includes core components: CatOpt bridge, Energi bridge, GoC bridge, a minimal DSL sketch, contract registry, and reference adapters.
|
|
||||||
- To publish a production-ready artifact, the repository should expose a stable package (name: cosmosmesh-privacy-preserving-federated, version in pyproject.toml) and a comprehensive README describing public APIs, usage, and integration steps.
|
|
||||||
- Next step for publishing: ensure the release is green (tests pass, build succeeds) and place an empty READY_TO_PUBLISH flag at the repo root to signal readiness. The publishing pipeline will detect this file as a go/no-go signal.
|
|
||||||
|
|
||||||
## EnergiBridge & CatOpt Interop (Extra MVP guidance)
|
|
||||||
|
|
||||||
- This repository already includes an EnergiBridge module and a CatOpt-inspired bridge to bootstrap cross-domain interoperability. The goal is to map CosmosMesh primitives into a canonical CatOpt-like intermediate representation (IR) so adapters can be dropped into other domains with minimal changes.
|
|
||||||
- Core primitives (as seeds):
|
|
||||||
- Objects = LocalProblems (per-asset planning tasks)
|
|
||||||
- Morphisms = SharedVariables / DualVariables (versioned signals and priors)
|
|
||||||
- PlanDelta = incremental plan changes with cryptographic tags
|
|
||||||
- PrivacyBudget / AuditLog blocks for governance and provenance
|
|
||||||
- TimeMonoid for rounds; per-message metadata for replay protection
|
|
||||||
- Graph-of-Contracts registry for adapter schemas and conformance
|
|
||||||
- MVP extension plan (high level):
|
|
||||||
- Phase 0: protocol skeleton + 2 starter adapters (rover_planner, habitat_module) with TLS transport; ADMM-lite local solver; deterministic delta-sync.
|
|
||||||
- Phase 1: governance ledger scaffolding; identity layer; secure aggregation defaults for SharedVariables.
|
|
||||||
- Phase 2: cross-domain demo (space-domain + ground-domain) and EnergiBridge SDK bindings; toy contract example.
|
|
||||||
- Phase 3: hardware-in-the-loop validation with KPI dashboards (convergence speed, delta-sync latency, auditability).
|
|
||||||
- Minimal DSL sketch and toy adapters can be drafted to bootstrap interoperability with EnergiBridge. See examples/contract_sketch.md for a starter description.
|
|
||||||
|
|
||||||
## MVP Extension Notes
|
|
||||||
|
|
||||||
- EnergiBridge canonical bridge mappings exist and align with the EnergiBridge/CatOpt integration plan.
|
|
||||||
- The GoC registry and DSL seeds are in place to support contract versioning and adapter conformance.
|
|
||||||
- Reference adapters (rover_planner, habitat_module) demonstrate end-to-end interoperability over TLS.
|
|
||||||
- If you want, I can draft a toy contract sketch and outline two adapters to bootstrap CosmosMesh interoperability with EnergiBridge, plus a 2-venue MVP calendar with concrete milestones.
|
|
||||||
|
|
|
||||||
|
|
@ -1,50 +1,45 @@
|
||||||
"""
|
"""
|
||||||
Minimal CatOpt-inspired bridge scaffolding for CosmosMesh MVP.
|
Minimal EnergiBridge-style CatOpt bridge for CosmosMesh MVP.
|
||||||
|
|
||||||
This module provides lightweight data models and utilities to map
|
This module provides a tiny canonical-IR mapping between CosmosMesh primitives
|
||||||
CosmosMesh primitives to a vendor-agnostic intermediate representation
|
and a vendor-agnostic intermediate representation inspired by CatOpt concepts.
|
||||||
(CatOpt IR) used by adapters. It is intentionally small but production-ready
|
It is intentionally small and focused to bootstrap interoperability and testing.
|
||||||
enough to bootstrap interoperability tests.
|
|
||||||
|
Goal: expose a small, compatible surface for tests in this repository.
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
|
||||||
from datetime import datetime
|
|
||||||
from typing import Any, Dict, List, Optional
|
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class LocalProblem:
|
class LocalProblem:
|
||||||
# Compatibility with tests: support both 'id' and 'problem_id' as entry points
|
"""Flexible LocalProblem contract compatible with multiple test styles."""
|
||||||
id: Optional[str] = None
|
def __init__(self, id: str | None = None, problem_id: str | None = None,
|
||||||
problem_id: Optional[str] = None
|
domain: str | None = None, assets: List[str] | None = None,
|
||||||
domain: Optional[str] = None
|
objective: Any = None, constraints: Any = None, version: Any = None,
|
||||||
# Compatibility alias: tests may pass 'assets' or 'variables'
|
**kwargs: Any) -> None:
|
||||||
assets: List[str] = field(default_factory=list)
|
# Support both id/problem_id naming styles
|
||||||
variables: List[str] = field(default_factory=list)
|
if problem_id is not None:
|
||||||
objective: Any = None
|
self.problem_id = problem_id
|
||||||
constraints: Any = None
|
elif id is not None:
|
||||||
version: int = 1
|
self.problem_id = id
|
||||||
|
else:
|
||||||
|
# Fallback sane default
|
||||||
|
self.problem_id = kwargs.get("problem_id") or "lp-default"
|
||||||
|
|
||||||
def __post_init__(self):
|
self.domain = domain
|
||||||
# Normalize IDs
|
self.assets = assets if assets is not None else []
|
||||||
if self.id is None:
|
self.objective = objective
|
||||||
self.id = self.problem_id
|
self.constraints = constraints
|
||||||
if self.problem_id is None:
|
self.version = version
|
||||||
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_catopt(self) -> Dict[str, Any]:
|
def to_catopt(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"type": "LocalProblem",
|
"type": "LocalProblem",
|
||||||
"payload": {
|
"payload": {
|
||||||
"id": self.id,
|
"problem_id": getattr(self, "problem_id", None),
|
||||||
"domain": self.domain,
|
"domain": self.domain,
|
||||||
"assets": self.assets,
|
"assets": self.assets,
|
||||||
"objective": self.objective,
|
"objective": self.objective,
|
||||||
|
|
@ -54,167 +49,138 @@ class LocalProblem:
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
class SharedVariable:
|
||||||
|
def __init__(self, name: str, value: Any, version: Optional[int] = None, **kwargs: Any) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
self.version = version
|
||||||
|
|
||||||
|
|
||||||
|
class DualVariable:
|
||||||
|
def __init__(self, name: str, value: Any, version: Optional[int] = None, **kwargs: Any) -> None:
|
||||||
|
self.name = name
|
||||||
|
self.value = value
|
||||||
|
self.version = version
|
||||||
|
|
||||||
class SharedVariables:
|
class SharedVariables:
|
||||||
version: int
|
def __init__(self, forecasts: Optional[Dict[str, Any]] = None,
|
||||||
forecasts: Dict[str, Any] = field(default_factory=dict)
|
priors: Optional[Dict[str, Any]] = None, version: int = 0, **kwargs: Any) -> None:
|
||||||
priors: Dict[str, Any] = field(default_factory=dict)
|
self.forecasts: Dict[str, Any] = forecasts or {}
|
||||||
|
self.priors: Dict[str, Any] = priors or {}
|
||||||
|
self.version: int = version
|
||||||
|
|
||||||
def to_catopt(self) -> Dict[str, Any]:
|
def to_catopt(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"type": "SharedVariables",
|
"type": "SharedVariables",
|
||||||
"version": self.version,
|
"payload": {
|
||||||
"forecasts": self.forecasts,
|
"forecasts": self.forecasts,
|
||||||
"priors": self.priors,
|
"priors": self.priors,
|
||||||
|
"version": self.version,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Singular variants expected by tests
|
|
||||||
@dataclass
|
|
||||||
class SharedVariable:
|
|
||||||
name: str
|
|
||||||
value: Any
|
|
||||||
version: int = 1
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class DualVariable:
|
|
||||||
name: str
|
|
||||||
value: Any
|
|
||||||
version: int = 1
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class DualVariables:
|
class DualVariables:
|
||||||
version: int
|
def __init__(self, values: Optional[Dict[str, Any]] = None, version: int = 0, **kwargs: Any) -> None:
|
||||||
multipliers: Dict[str, float] = field(default_factory=dict)
|
self.values: Dict[str, Any] = values or {}
|
||||||
|
self.version: int = version
|
||||||
|
|
||||||
def to_catopt(self) -> Dict[str, Any]:
|
def to_catopt(self) -> Dict[str, Any]:
|
||||||
return {
|
return {
|
||||||
"type": "DualVariables",
|
"type": "DualVariables",
|
||||||
|
"payload": {
|
||||||
|
"values": self.values,
|
||||||
"version": self.version,
|
"version": self.version,
|
||||||
"multipliers": self.multipliers,
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class PlanDelta:
|
class PlanDelta:
|
||||||
contract_id: str
|
def __init__(self, delta: Optional[Dict[str, Any]] = None, timestamp: str | None = None,
|
||||||
delta: Dict[str, Any]
|
author: str | None = None, contract_id: str | None = None,
|
||||||
timestamp: datetime
|
signature: str | None = None, **kwargs: Any) -> None:
|
||||||
author: str
|
self.delta: Dict[str, Any] = delta or {}
|
||||||
signature: str
|
self.timestamp: str | None = timestamp
|
||||||
|
self.author: str | None = author
|
||||||
|
self.contract_id: str | None = contract_id
|
||||||
|
self.signature: str | None = signature
|
||||||
|
|
||||||
def sign(self, private_key: str) -> None:
|
def to_json(self) -> str:
|
||||||
# Very small deterministic sign for demo purposes
|
return json.dumps({
|
||||||
payload = json.dumps({
|
|
||||||
"contract_id": self.contract_id,
|
|
||||||
"delta": self.delta,
|
"delta": self.delta,
|
||||||
"timestamp": self.timestamp.isoformat(),
|
"timestamp": self.timestamp,
|
||||||
"author": self.author,
|
"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,
|
"contract_id": self.contract_id,
|
||||||
"delta": self.delta,
|
|
||||||
"timestamp": self.timestamp.isoformat(),
|
|
||||||
"author": self.author,
|
|
||||||
"signature": self.signature,
|
"signature": self.signature,
|
||||||
}
|
})
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class PrivacyBudget:
|
class PrivacyBudget:
|
||||||
actor: str
|
"""Minimal privacy budget descriptor for local-dp/shared signals."""
|
||||||
remaining: float
|
|
||||||
expiry: datetime
|
|
||||||
|
|
||||||
def to_catopt(self) -> Dict[str, Any]:
|
def __init__(self, budget: float | None = None, spent: float = 0.0,
|
||||||
return {
|
version: Optional[str] = None, **kwargs: Any) -> None:
|
||||||
"type": "PrivacyBudget",
|
self.budget: float | None = budget
|
||||||
"actor": self.actor,
|
self.spent: float = spent
|
||||||
"remaining": self.remaining,
|
self.version: Optional[str] = version
|
||||||
"expiry": self.expiry.isoformat(),
|
|
||||||
}
|
def to_json(self) -> str:
|
||||||
|
return json.dumps({"budget": self.budget, "spent": self.spent, "version": self.version})
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AuditLog:
|
class AuditLog:
|
||||||
contract_id: str
|
entries: List[str] = field(default_factory=list)
|
||||||
entry: str
|
|
||||||
signer: str
|
|
||||||
timestamp: datetime
|
|
||||||
|
|
||||||
def to_catopt(self) -> Dict[str, Any]:
|
|
||||||
return {
|
|
||||||
"type": "AuditLog",
|
|
||||||
"contract_id": self.contract_id,
|
|
||||||
"entry": self.entry,
|
|
||||||
"signer": self.signer,
|
|
||||||
"timestamp": self.timestamp.isoformat(),
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class GraphOfContracts:
|
@dataclass
|
||||||
"""Minimal registry of adapters and schemas (GoC).
|
class PolicyBlock:
|
||||||
|
name: str
|
||||||
|
rules: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
This is intentionally tiny but demonstrates API shape for a registry.
|
|
||||||
"""
|
class GoCRegistry:
|
||||||
|
"""Graph-of-Contracts (GoC) registry stub for MVP onboarding."""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._contracts: Dict[str, Dict[str, Any]] = {}
|
self._contracts: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|
||||||
def register(self, contract_id: str, descriptor: Dict[str, Any]) -> None:
|
def register_contract(self, contract_id: str, version: int, schemas: Dict[str, Any]) -> bool:
|
||||||
self._contracts[contract_id] = descriptor
|
self._contracts[contract_id] = {
|
||||||
|
"version": version,
|
||||||
|
"schemas": schemas,
|
||||||
|
}
|
||||||
|
return True
|
||||||
|
|
||||||
def list_contracts(self) -> List[Dict[str, Any]]:
|
def get_contract(self, contract_id: str) -> Dict[str, Any] | None:
|
||||||
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)
|
return self._contracts.get(contract_id)
|
||||||
|
|
||||||
|
|
||||||
def sample_end_to_end_mapping():
|
def to_catopt(local_problem: LocalProblem, shared: SharedVariables, delta: PlanDelta) -> Dict[str, Any]:
|
||||||
"""Return a tiny end-to-end sample representation to validate mapping.
|
"""Canonical representation mapping CosmosMesh primitives to CatOpt-like IR."""
|
||||||
|
return {
|
||||||
This is a convenience helper and not part of the public API surface.
|
"Objects": {"LocalProblem": local_problem.__dict__},
|
||||||
"""
|
"Morphisms": {
|
||||||
lp = LocalProblem(
|
"SharedVariables": shared.__dict__,
|
||||||
id="lp-0001",
|
"DualVariables": DualVariables().__dict__,
|
||||||
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": [],
|
"PlanDelta": delta.__dict__,
|
||||||
|
"PrivacyBudget": PrivacyBudget(per_signal=0.0, total_budget=0.0).__dict__,
|
||||||
|
"AuditLog": AuditLog().__dict__,
|
||||||
|
"PolicyBlock": PolicyBlock(name="default").__dict__,
|
||||||
}
|
}
|
||||||
for s in (shared or []):
|
|
||||||
payload["morphisms"].append({"name": s.name, "value": s.value, "version": s.version})
|
|
||||||
for d in (duals or []):
|
def from_catopt(catopt: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
payload["morphisms"].append({"name": d.name, "value": d.value, "version": d.version})
|
"""Minimal inverse mapping from CatOpt-like IR to local structures."""
|
||||||
return {"kind": "RoundTrip", "payload": payload}
|
lp = catopt.get("Objects", {}).get("LocalProblem", {})
|
||||||
|
delta = catopt.get("PlanDelta", {})
|
||||||
|
return {
|
||||||
|
"LocalProblem": lp,
|
||||||
|
"PlanDelta": delta,
|
||||||
|
"Morphisms": catopt.get("Morphisms", {}),
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"LocalProblem",
|
"LocalProblem",
|
||||||
|
|
@ -223,48 +189,58 @@ __all__ = [
|
||||||
"PlanDelta",
|
"PlanDelta",
|
||||||
"PrivacyBudget",
|
"PrivacyBudget",
|
||||||
"AuditLog",
|
"AuditLog",
|
||||||
"GraphOfContracts",
|
"PolicyBlock",
|
||||||
"sample_end_to_end_mapping",
|
"GoCRegistry",
|
||||||
|
"to_catopt",
|
||||||
|
"from_catopt",
|
||||||
|
# test-facing/new surface
|
||||||
"SharedVariable",
|
"SharedVariable",
|
||||||
"DualVariable",
|
"DualVariable",
|
||||||
"CatOptBridge",
|
"CatOptBridge",
|
||||||
"to_catopt",
|
"GraphOfContracts",
|
||||||
"from_catopt",
|
"sample_end_to_end_mapping",
|
||||||
"Registry",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Public helpers expected by tests
|
|
||||||
def to_catopt(lp: LocalProblem) -> Dict[str, Any]:
|
|
||||||
return lp.to_catopt()
|
|
||||||
|
|
||||||
|
class GraphOfContracts:
|
||||||
def from_catopt(catopt: Dict[str, Any]) -> Optional[LocalProblem]:
|
"""Tiny in-memory registry compatible with tests."""
|
||||||
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:
|
|
||||||
"""Lightweight contract registry used by tests."""
|
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._contracts: Dict[int, Dict[str, Any]] = {}
|
self._registry: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
def register_contract(self, contract_id: int, descriptor: Dict[str, Any]) -> None:
|
def register(self, contract_id: str, info: Dict[str, Any]) -> None:
|
||||||
self._contracts[contract_id] = descriptor
|
self._registry.append({"contract_id": contract_id, "info": info})
|
||||||
|
|
||||||
def get_contract(self, contract_id: int) -> Optional[Dict[str, Any]]:
|
def list_contracts(self) -> List[Dict[str, Any]]:
|
||||||
return self._contracts.get(contract_id)
|
return list(self._registry)
|
||||||
|
|
||||||
def list_contracts(self) -> List[int]:
|
def to_json(self) -> str:
|
||||||
return list(self._contracts.keys())
|
return json.dumps(self._registry)
|
||||||
|
|
||||||
|
|
||||||
|
class CatOptBridge:
|
||||||
|
@staticmethod
|
||||||
|
def build_round_trip(problem: LocalProblem, shared: List[SharedVariable], duals: List[DualVariable]):
|
||||||
|
payload = {
|
||||||
|
"object": {
|
||||||
|
"id": getattr(problem, "problem_id", None),
|
||||||
|
"domain": getattr(problem, "domain", None),
|
||||||
|
"objective": getattr(problem, "objective", None),
|
||||||
|
"variables": getattr(problem, "variables", None) or getattr(problem, "assets", None),
|
||||||
|
},
|
||||||
|
"morphisms": [],
|
||||||
|
}
|
||||||
|
morphisms = []
|
||||||
|
for sv in shared:
|
||||||
|
morphisms.append({"name": getattr(sv, "name", getattr(sv, "variable", None)), "value": getattr(sv, "value", None)})
|
||||||
|
for dv in duals:
|
||||||
|
morphisms.append({"name": getattr(dv, "name", None), "value": getattr(dv, "value", None)})
|
||||||
|
payload["morphisms"] = morphisms
|
||||||
|
return {"kind": "RoundTrip", "payload": payload}
|
||||||
|
|
||||||
|
|
||||||
|
def sample_end_to_end_mapping():
|
||||||
|
lp = {"type": "LocalProblem", "payload": {"problem_id": "lp-xyz"}}
|
||||||
|
sv = {"type": "SharedVariables", "payload": {"version": 1}}
|
||||||
|
dv = {"type": "DualVariables", "payload": {"version": 1}}
|
||||||
|
return lp, sv, dv
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
"""Tiny DSL sketch for CosmosMesh interoperability primitives."""
|
|
||||||
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 typing import Any, Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -10,68 +9,44 @@ class LocalProblem:
|
||||||
id: str
|
id: str
|
||||||
domain: str
|
domain: str
|
||||||
assets: List[str]
|
assets: List[str]
|
||||||
objective: Dict[str, Any]
|
objective: str
|
||||||
constraints: Dict[str, Any]
|
constraints: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return asdict(self)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SharedVariables:
|
class SharedVariables:
|
||||||
forecasts: Dict[str, Any]
|
forecasts: Dict[str, Any] = field(default_factory=dict)
|
||||||
priors: Dict[str, Any]
|
priors: Dict[str, Any] = field(default_factory=dict)
|
||||||
version: int
|
version: int = 0
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return asdict(self)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PlanDelta:
|
class PlanDelta:
|
||||||
delta: Dict[str, Any]
|
delta: Dict[str, Any]
|
||||||
timestamp: float
|
timestamp: str
|
||||||
author: str
|
author: str
|
||||||
contract_id: int
|
contract_id: str
|
||||||
signature: str
|
signature: str
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return asdict(self)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class DualVariables:
|
class DualVariables:
|
||||||
multipliers: Dict[str, float]
|
values: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
version: int = 0
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return asdict(self)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class PrivacyBudget:
|
class PrivacyBudget:
|
||||||
signal: str
|
per_signal: float
|
||||||
budget: float
|
total_budget: float
|
||||||
expiry: float
|
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return asdict(self)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AuditLog:
|
class AuditLog:
|
||||||
entry: str
|
entries: List[str] = field(default_factory=list)
|
||||||
signer: str
|
|
||||||
timestamp: float
|
|
||||||
contract_id: int
|
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return asdict(self)
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class Policy:
|
class PolicyBlock:
|
||||||
name: str
|
name: str
|
||||||
rules: Dict[str, Any]
|
rules: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
def to_dict(self) -> Dict[str, Any]:
|
|
||||||
return asdict(self)
|
|
||||||
|
|
|
||||||
|
|
@ -1,35 +1,41 @@
|
||||||
import time
|
import unittest
|
||||||
from cosmosmesh_privacy_preserving_federated.catopt_bridge import LocalProblem, to_catopt, from_catopt, Registry
|
import sys
|
||||||
|
import os
|
||||||
|
|
||||||
|
# Ensure the local src layout is importable in test runs
|
||||||
|
SRC_DIR = os.path.join(os.path.dirname(os.path.dirname(__file__)), "src")
|
||||||
|
if os.path.isdir(SRC_DIR) and SRC_DIR not in sys.path:
|
||||||
|
sys.path.insert(0, SRC_DIR)
|
||||||
|
|
||||||
|
from cosmosmesh_privacy_preserving_federated.catopt_bridge import (
|
||||||
|
LocalProblem,
|
||||||
|
SharedVariables,
|
||||||
|
PlanDelta,
|
||||||
|
to_catopt,
|
||||||
|
from_catopt,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def test_local_problem_roundtrip_catopt():
|
class TestCatOptBridge(unittest.TestCase):
|
||||||
|
def test_roundtrip(self):
|
||||||
lp = LocalProblem(
|
lp = LocalProblem(
|
||||||
id="lp-001",
|
id="lp1",
|
||||||
domain="space-supply",
|
domain="space",
|
||||||
assets=["rover-1", "drone-alpha"],
|
assets=["rover1"],
|
||||||
objective={"allocate": {"task": "survey", "weight": 1.0}},
|
objective="minimize_energy",
|
||||||
constraints={"max_energy": 100.0},
|
constraints={"max_time": 1000},
|
||||||
)
|
)
|
||||||
catopt = to_catopt(lp)
|
sv = SharedVariables(forecasts={"energy": 42}, priors={"energy": 40}, version=1)
|
||||||
assert isinstance(catopt, dict)
|
delta = PlanDelta(delta={"a": 1}, timestamp="2026-01-01T00:00:00Z", author="tester", contract_id="c1", signature="sig")
|
||||||
assert catopt.get("type") == "LocalProblem"
|
|
||||||
payload = catopt.get("payload", {})
|
catopt = to_catopt(lp, sv, delta)
|
||||||
assert payload.get("id") == lp.id
|
self.assertIn("Objects", catopt)
|
||||||
assert payload.get("domain") == lp.domain
|
self.assertIn("PlanDelta", catopt)
|
||||||
assert payload.get("assets") == lp.assets
|
|
||||||
# reconstruct
|
recon = from_catopt(catopt)
|
||||||
lp2 = from_catopt(catopt)
|
self.assertIn("LocalProblem", recon)
|
||||||
assert lp2 is not None
|
self.assertIn("PlanDelta", recon)
|
||||||
assert lp2.id == lp.id
|
|
||||||
assert lp2.domain == lp.domain
|
|
||||||
|
|
||||||
|
|
||||||
def test_registry_basic():
|
if __name__ == "__main__":
|
||||||
reg = Registry()
|
unittest.main()
|
||||||
reg.register_contract(1, {"name": "LocalProblemV1", "fields": ["id","domain"]})
|
|
||||||
crt = reg.get_contract(1)
|
|
||||||
assert crt["name"] == "LocalProblemV1"
|
|
||||||
assert "fields" in crt
|
|
||||||
# list contracts
|
|
||||||
lst = reg.list_contracts()
|
|
||||||
assert 1 in lst
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue