build(agent): weasel-1#856f80 iteration
This commit is contained in:
parent
e11963ffb2
commit
8e73b0ac32
|
|
@ -0,0 +1,88 @@
|
|||
"""Canonical IR utilities and lightweight CRDT-like reconciliation.
|
||||
|
||||
This module provides:
|
||||
- simple PlanDelta JSON (de)serialization helpers
|
||||
- a deterministic, op-based merge function for PlanDelta lists that
|
||||
applies deltas in timestamp/author order to produce a deterministic
|
||||
reconciliation result suitable for offline-first replay.
|
||||
|
||||
The implementations are intentionally minimal and dependency-free so
|
||||
they remain easy to test and extend.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import List, Dict, Any
|
||||
|
||||
|
||||
def plan_delta_to_json(plan_delta: dict) -> str:
|
||||
"""Serialize a PlanDelta-like dict to a compact JSON string.
|
||||
|
||||
Expects a mapping with keys: delta (dict), timestamp (float), author (str),
|
||||
contract_id (str), signature (str). Missing fields are tolerated.
|
||||
"""
|
||||
# Ensure a stable ordering for determinism
|
||||
return json.dumps(plan_delta, sort_keys=True, separators=(",", ":"))
|
||||
|
||||
|
||||
def plan_delta_from_json(s: str) -> dict:
|
||||
"""Deserialize a PlanDelta JSON string to a dict.
|
||||
|
||||
This is a thin wrapper around json.loads but keeps the IR surface
|
||||
explicit and easy to extend with schema checks later.
|
||||
"""
|
||||
return json.loads(s)
|
||||
|
||||
|
||||
def _sort_key_for_pd(pd: dict) -> Any:
|
||||
# Some PlanDeltas may have NaN timestamps; use 0 for missing/NaN to keep
|
||||
# sort stable. Then use author to break ties deterministically.
|
||||
ts = pd.get("timestamp")
|
||||
try:
|
||||
if ts is None or (isinstance(ts, float) and (ts != ts)):
|
||||
ts_val = 0.0
|
||||
else:
|
||||
ts_val = float(ts)
|
||||
except Exception:
|
||||
ts_val = 0.0
|
||||
author = pd.get("author") or ""
|
||||
return (ts_val, author)
|
||||
|
||||
|
||||
def merge_plan_deltas(deltas: List[dict]) -> dict:
|
||||
"""Deterministically merge a list of PlanDelta-like dicts.
|
||||
|
||||
Behavior:
|
||||
- Sort deltas by (timestamp ascending, author ascending) to obtain a
|
||||
deterministic application order.
|
||||
- Apply each delta's "delta" mapping sequentially; later writes override
|
||||
earlier ones for the same keys.
|
||||
- Return a new PlanDelta-like dict with merged delta and provenance
|
||||
fields taken from the last-applied delta where available.
|
||||
|
||||
This is intentionally simple: it's an op-based reconciliation suitable as
|
||||
a starting point for CRDT-style deterministic replay across reconnects.
|
||||
"""
|
||||
if not deltas:
|
||||
return {"delta": {}, "timestamp": None, "author": "", "contract_id": "", "signature": ""}
|
||||
|
||||
# Work on shallow copies and sort for deterministic application order
|
||||
ordered = sorted([dict(d) for d in deltas], key=_sort_key_for_pd)
|
||||
merged: Dict[str, Any] = {}
|
||||
last_prov = {}
|
||||
for pd in ordered:
|
||||
inner = pd.get("delta") or {}
|
||||
# Apply key set from this delta
|
||||
for k, v in inner.items():
|
||||
merged[k] = v
|
||||
# record provenance (take the provenance of the last applied delta)
|
||||
last_prov = {k: pd.get(k) for k in ("timestamp", "author", "contract_id", "signature")}
|
||||
|
||||
result = {
|
||||
"delta": merged,
|
||||
"timestamp": last_prov.get("timestamp"),
|
||||
"author": last_prov.get("author") or "",
|
||||
"contract_id": last_prov.get("contract_id") or "",
|
||||
"signature": last_prov.get("signature") or "",
|
||||
}
|
||||
return result
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
import random
|
||||
from interplanetary_edge_orchestrator_privacy.ir import (
|
||||
plan_delta_to_json,
|
||||
plan_delta_from_json,
|
||||
merge_plan_deltas,
|
||||
)
|
||||
|
||||
|
||||
def make_pd(delta: dict, timestamp: float, author: str):
|
||||
return {"delta": delta, "timestamp": timestamp, "author": author, "contract_id": "c1", "signature": f"sig-{author}"}
|
||||
|
||||
|
||||
def test_plan_delta_json_roundtrip():
|
||||
pd = make_pd({"x": 1, "y": 2}, timestamp=123.45, author="agent-A")
|
||||
s = plan_delta_to_json(pd)
|
||||
pd2 = plan_delta_from_json(s)
|
||||
assert pd2["delta"]["x"] == 1
|
||||
assert pd2["author"] == "agent-A"
|
||||
|
||||
|
||||
def test_merge_plan_deltas_is_deterministic():
|
||||
# Create three deltas with overlapping keys and different timestamps
|
||||
p1 = make_pd({"a": 1}, timestamp=1.0, author="A")
|
||||
p2 = make_pd({"b": 2}, timestamp=2.0, author="B")
|
||||
p3 = make_pd({"a": 3}, timestamp=3.0, author="C")
|
||||
|
||||
baseline = merge_plan_deltas([p1, p2, p3])
|
||||
# expected: a overwritten by p3, b from p2
|
||||
assert baseline["delta"]["a"] == 3
|
||||
assert baseline["delta"]["b"] == 2
|
||||
|
||||
# Shuffle inputs many times and assert merge result is identical
|
||||
for _ in range(10):
|
||||
arr = [p1, p2, p3][:]
|
||||
random.shuffle(arr)
|
||||
m = merge_plan_deltas(arr)
|
||||
assert m == baseline
|
||||
Loading…
Reference in New Issue