build(agent): molt-z#db0ec5 iteration

This commit is contained in:
agent-db0ec53c058f1326 2026-04-16 23:00:17 +02:00
parent 68d299ac0a
commit 1afcf91913
2 changed files with 83 additions and 0 deletions

View File

@ -16,10 +16,30 @@ class PlanDelta:
agent_id: str agent_id: str
delta: Dict[str, float] delta: Dict[str, float]
timestamp: float timestamp: float
# Optional fields to support CRDT-like, partition-tolerant merges
parent_version: int | None = None
sequence: int | None = None
def to_json(self) -> str: def to_json(self) -> str:
return json.dumps(asdict(self)) return json.dumps(asdict(self))
# Simple CRDT-style merge helper (shadow plan example, not a full CRDT)
def crdt_merge_deltas(d1: "PlanDelta", d2: "PlanDelta") -> "PlanDelta":
merged_delta = {**d1.delta, **d2.delta}
merged_agent = d2.agent_id if d2.agent_id else d1.agent_id
merged_ts = max(d1.timestamp, d2.timestamp)
merged_parent = None
if d1.parent_version is not None or d2.parent_version is not None:
v1 = d1.parent_version if d1.parent_version is not None else 0
v2 = d2.parent_version if d2.parent_version is not None else 0
merged_parent = max(v1, v2)
merged_seq = None
if d1.sequence is not None or d2.sequence is not None:
s1 = d1.sequence if d1.sequence is not None else 0
s2 = d2.sequence if d2.sequence is not None else 0
merged_seq = max(s1, s2)
return PlanDelta(agent_id=merged_agent, delta=merged_delta, timestamp=merged_ts, parent_version=merged_parent, sequence=merged_seq)
@dataclass @dataclass
class SharedSchedule: class SharedSchedule:
schedule: Dict[str, Any] schedule: Dict[str, Any]
@ -176,6 +196,40 @@ AdapterRegistry.register_schema(
"types": {"adapter_id": str, "status": dict}, "types": {"adapter_id": str, "status": dict},
}, },
) )
# ---------------- CaC (Contract-as-Code) primitives -----------------
@dataclass
class CaCContract:
contract_id: str
version: int
content: Dict[str, Any]
signature: str | None = None
def to_json(self) -> str:
return json.dumps({
"contract_id": self.contract_id,
"version": self.version,
"content": self.content,
"signature": self.signature,
})
def sign_ca_contract(contract: CaCContract, key: str) -> CaCContract:
import hashlib, json
payload = json.dumps(contract.content, sort_keys=True).encode()
contract.signature = hashlib.sha256((key).encode() + payload).hexdigest()
return contract
class CaCRegistry:
_contracts: Dict[str, CaCContract] = {}
@classmethod
def register(cls, contract: CaCContract) -> None:
cls._contracts[contract.contract_id] = contract
@classmethod
def get(cls, contract_id: str) -> CaCContract | None:
return cls._contracts.get(contract_id)
AdapterRegistry.register_schema( AdapterRegistry.register_schema(
name="HabitatAdapter", name="HabitatAdapter",
version=1, version=1,

View File

@ -0,0 +1,29 @@
import json
from nova_plan.contracts import CaCContract, CaCRegistry, sign_ca_contract, crdt_merge_deltas, PlanDelta
def test_cac_contract_signing_and_serialization():
c = CaCContract(contract_id="cac-001", version=1, content={"name": "NovaPlan", "purpose": "MVP"})
signed = sign_ca_contract(c, key="topsecret")
assert signed.signature is not None
s = signed.to_json()
# Ensure JSON can be parsed back without error
parsed = json.loads(s)
assert parsed.get("contract_id") == "cac-001"
def test_crdt_merge_deltas_basic():
d1 = PlanDelta(agent_id="a1", delta={"x": 1.0}, timestamp=1.0)
d2 = PlanDelta(agent_id="a2", delta={"y": 2.0}, timestamp=2.0)
merged = crdt_merge_deltas(d1, d2)
assert isinstance(merged, PlanDelta)
assert "x" in merged.delta and "y" in merged.delta
assert merged.timestamp == 2.0
def test_cac_registry_store_and_retrieve():
c = CaCContract(contract_id="cac-002", version=1, content={"foo": "bar"})
CaCRegistry.register(c)
retrieved = CaCRegistry.get("cac-002")
assert retrieved is c