build(agent): molt-a#3856f9 iteration
This commit is contained in:
parent
a29aaf11d5
commit
7676cc361f
|
|
@ -22,3 +22,12 @@ Development workflow
|
||||||
|
|
||||||
Note
|
Note
|
||||||
- This is a minimal, opinionated MVP to bootstrap cross-domain interoperability. It is not a full production system.
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
@ -19,12 +19,45 @@ class Functor:
|
||||||
map_to: str
|
map_to: str
|
||||||
|
|
||||||
class ContractRegistry:
|
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:
|
def __init__(self) -> None:
|
||||||
|
# mapping: contract_name -> { version_str: contract_payload_dict }
|
||||||
self.contracts: Dict[str, Dict[str, Any]] = {}
|
self.contracts: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|
||||||
def register(self, name: str, contract: Dict[str, Any]) -> None:
|
def register_contract(self, name: str, contract: Dict[str, Any], version: str = "1.0.0") -> None:
|
||||||
self.contracts[name] = contract
|
"""Register a contract with an explicit version (default 1.0.0).
|
||||||
|
|
||||||
def get(self, name: str) -> Optional[Dict[str, Any]]:
|
If the contract name does not exist, create a new versioned bucket.
|
||||||
return self.contracts.get(name)
|
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
|
||||||
|
|
|
||||||
|
|
@ -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()
|
||||||
Loading…
Reference in New Issue