build(agent): molt-a#3856f9 iteration

This commit is contained in:
agent-3856f9f6ac3cbf34 2026-04-15 01:55:49 +02:00
parent a29aaf11d5
commit 7676cc361f
4 changed files with 132 additions and 5 deletions

View File

@ -22,3 +22,12 @@ Development workflow
Note
- This is a minimal, opinionated MVP to bootstrap cross-domain interoperability. It is not a full production system.
What we're adding now (MVP roadmap refinements):
- Core ontology extension: versioned ContractRegistry (contracts per name/version) for interop with adapters.
- Bridge layer: a lightweight to_canonical / from_canonical mapping to connect domain LocalProblem data to the CatOpt canonical form.
- Adapters: two starter adapters (rover, habitat) already present; bridge will enable canonical data exchange.
- ADMM-lite core: keep the existing two-agent toy solver as a testbed; extend with delta-sync semantics conceptually in code comments and tests.
- Governance / contracts: hook up a minimal conformance story via the contract registry + bridge; add a test to verify registry behavior.
- MVP testing: unit tests for contract registry, end-to-end tests for bridge mapping (stubbed inputs/outputs) and existing admm_lite tests for solver stability.
- Documentation: update README to reflect the MVP extension and how to run tests.

View File

@ -0,0 +1,52 @@
"""CatOpt-Graph Bridge: Canonicalization layer between domain adapters and CatOpt.
This lightweight module provides mapping helpers to translate between
domain-specific LocalProblem representations emitted by adapters and a
canonical CatOpt representation used by the core solver/pipeline.
"""
from __future__ import annotations
from typing import Any, Dict
def to_canonical(local_problem: Dict[str, Any]) -> Dict[str, Any]:
"""Map a domain LocalProblem into a canonical CatOpt representation.
This is intentionally small and opinionated to support MVP flows:
- Normalize keys
- Promote core fields (node, objective, constraints) into a stable shape
- Represent objective generically, e.g., quadratic/linear with coefficients
"""
lp_type = local_problem.get("type") or "LocalProblem"
node = local_problem.get("node") or local_problem.get("id") or "unknown"
obj = local_problem.get("objective", {})
canonical: Dict[str, Any] = {
"type": "CanonicalLocalProblem",
"node": node,
"source_type": lp_type,
"objective": obj,
"metadata": {
"version": local_problem.get("version", "1.0.0"),
},
}
return canonical
def from_canonical(canonical: Dict[str, Any]) -> Dict[str, Any]:
"""Map a canonical CatOpt representation back to a domain-local form.
This function is intentionally minimal; in MVP it serves as a stub for
domain adapters to re-emit data in their native shapes.
"""
if not isinstance(canonical, dict):
return {"error": "invalid canonical payload"}
# Pass through known fields if present
node = canonical.get("node", "unknown")
local_problem = {
"type": canonical.get("source_type", "LocalProblem"),
"node": node,
"objective": canonical.get("objective", {}),
"metadata": canonical.get("metadata", {}),
}
return local_problem

View File

@ -19,12 +19,45 @@ class Functor:
map_to: str
class ContractRegistry:
"""Minimal versioned contract registry skeleton."""
"""Minimal versioned contract registry skeleton.
This registry stores contracts (schemas, conformance rules, etc.) in a
versioned fashion for cross-domain adapters. Each contract name maps to a
dictionary of versions to contract payloads.
"""
def __init__(self) -> None:
# mapping: contract_name -> { version_str: contract_payload_dict }
self.contracts: Dict[str, Dict[str, Any]] = {}
def register(self, name: str, contract: Dict[str, Any]) -> None:
self.contracts[name] = contract
def register_contract(self, name: str, contract: Dict[str, Any], version: str = "1.0.0") -> None:
"""Register a contract with an explicit version (default 1.0.0).
def get(self, name: str) -> Optional[Dict[str, Any]]:
return self.contracts.get(name)
If the contract name does not exist, create a new versioned bucket.
If the version already exists, it will be overwritten to reflect the
new contract payload.
"""
if name not in self.contracts:
self.contracts[name] = {}
self.contracts[name][version] = contract
def get_contract(self, name: str, version: str | None = None) -> Optional[Dict[str, Any]]:
"""Return a contract payload by name and optional version.
If version is None, return the latest version available for the contract.
If the contract or version is not found, return None.
"""
versions = self.contracts.get(name)
if not versions:
return None
if version is None:
# pick the latest version by semantic version string comparison
try:
latest = max(versions.keys(), key=lambda v: tuple(int(p) for p in v.split(".")))
except Exception:
latest = sorted(versions.keys())[-1]
return versions.get(latest)
return versions.get(version)
def list_contracts(self) -> Dict[str, Dict[str, Any]]:
"""Return a raw view of all registered contracts."""
return self.contracts

View File

@ -0,0 +1,33 @@
import unittest
from catopt_graph.core import ContractRegistry
class TestContractRegistry(unittest.TestCase):
def test_register_and_get_contract_versions(self):
reg = ContractRegistry()
reg.register_contract("LocalProblem", {"foo": "bar"}, version="1.0.0")
reg.register_contract("LocalProblem", {"foo": "baz"}, version="1.1.0")
c_latest = reg.get_contract("LocalProblem")
c_10 = reg.get_contract("LocalProblem", version="1.0.0")
c_11 = reg.get_contract("LocalProblem", version="1.1.0")
self.assertIsNotNone(c_latest)
self.assertEqual(c_10, {"foo": "bar"})
self.assertEqual(c_11, {"foo": "baz"})
# The latest should reflect the highest version number
self.assertEqual(c_latest, c_11)
def test_list_contracts(self):
reg = ContractRegistry()
reg.register_contract("X", {"a": 1}, version="0.1.0")
reg.register_contract("Y", {"b": 2}, version="0.1.0")
all_contracts = reg.list_contracts()
self.assertIn("X", all_contracts)
self.assertIn("Y", all_contracts)
if __name__ == "__main__":
unittest.main()