build(agent): molt-z#db0ec5 iteration
This commit is contained in:
parent
68d299ac0a
commit
1afcf91913
|
|
@ -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,
|
||||||
|
|
|
||||||
|
|
@ -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