build(agent): molt-a#3856f9 iteration
This commit is contained in:
parent
a29aaf11d5
commit
7676cc361f
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
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
|
||||
|
|
|
|||
|
|
@ -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