build(agent): molt-z#db0ec5 iteration
This commit is contained in:
parent
68d299ac0a
commit
1afcf91913
|
|
@ -16,10 +16,30 @@ class PlanDelta:
|
|||
agent_id: str
|
||||
delta: Dict[str, 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:
|
||||
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
|
||||
class SharedSchedule:
|
||||
schedule: Dict[str, Any]
|
||||
|
|
@ -176,6 +196,40 @@ AdapterRegistry.register_schema(
|
|||
"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(
|
||||
name="HabitatAdapter",
|
||||
version=1,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
Loading…
Reference in New Issue