build(agent): weasel-1#856f80 iteration
This commit is contained in:
parent
0c168c00fb
commit
629cb1df70
|
|
@ -0,0 +1,21 @@
|
||||||
|
node_modules/
|
||||||
|
.npmrc
|
||||||
|
.env
|
||||||
|
.env.*
|
||||||
|
__tests__/
|
||||||
|
coverage/
|
||||||
|
.nyc_output/
|
||||||
|
dist/
|
||||||
|
build/
|
||||||
|
.cache/
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
tmp/
|
||||||
|
.tmp/
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
.venv/
|
||||||
|
venv/
|
||||||
|
*.egg-info/
|
||||||
|
.pytest_cache/
|
||||||
|
READY_TO_PUBLISH
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
GravityWeave - Agent Guidelines and Architecture
|
||||||
|
|
||||||
|
Architecture
|
||||||
|
- Language: Python 3.8+
|
||||||
|
- Minimal package: gravityweave
|
||||||
|
- Core modules: registry, plan_delta (CRDT), ledger, adapters, solver
|
||||||
|
|
||||||
|
Tech stack
|
||||||
|
- Standard library (hashlib, hmac, json, time)
|
||||||
|
- pytest for tests
|
||||||
|
|
||||||
|
Testing and CI
|
||||||
|
- Run tests and build with `bash test.sh`.
|
||||||
|
- `test.sh` builds the package (`python3 -m build`) and runs `pytest -q`.
|
||||||
|
|
||||||
|
Contribution rules
|
||||||
|
- Small incremental changes preferred.
|
||||||
|
- Follow existing patterns; keep public API names stable.
|
||||||
|
- Add unit tests for any new behavior.
|
||||||
|
|
||||||
|
Packaging
|
||||||
|
- pyproject.toml defines build metadata and is required for packaging.
|
||||||
16
README.md
16
README.md
|
|
@ -1,3 +1,15 @@
|
||||||
# idea182-implementation
|
# GravityWeave (idea182-implementation)
|
||||||
|
|
||||||
Implementation for Idea #182
|
Minimal GravityWeave skeleton implementing core primitives for a federated,
|
||||||
|
delay-tolerant mission optimization stack. This repo provides:
|
||||||
|
|
||||||
|
- A Graph-of-Contracts registry (gravityweave.registry)
|
||||||
|
- A small CRDT-style PlanDelta implementation (ORSet, LWWRegister)
|
||||||
|
- Toy adapters: sat_planner and relay_module
|
||||||
|
- A minimal governance ledger with tamper-evident chaining (HMAC-signed)
|
||||||
|
- An ADMM-lite solver stub for local updates
|
||||||
|
|
||||||
|
This is intentionally a small, well-tested starting point for the full
|
||||||
|
GravityWeave architecture described in the original idea.
|
||||||
|
|
||||||
|
Run tests: `bash test.sh`
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
"""GravityWeave package - lightweight federation primitives for orbital resource optimization.
|
||||||
|
|
||||||
|
This package contains minimal, well-tested building blocks intended as a foundation
|
||||||
|
for the GravityWeave ecosystem: a Graph-of-Contracts registry, a small CRDT-style
|
||||||
|
PlanDelta, a governance ledger (tamper-evident append-only log), two toy adapters,
|
||||||
|
and an ADMM-lite solver stub.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .registry import Registry
|
||||||
|
from .plan_delta import PlanDelta, ORSet, LWWRegister
|
||||||
|
from .ledger import GovernanceLedger
|
||||||
|
from .adapters import sat_planner, relay_module
|
||||||
|
from .solver import ADMMNode
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Registry",
|
||||||
|
"PlanDelta",
|
||||||
|
"ORSet",
|
||||||
|
"LWWRegister",
|
||||||
|
"GovernanceLedger",
|
||||||
|
"sat_planner",
|
||||||
|
"relay_module",
|
||||||
|
"ADMMNode",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
from . import sat_planner, relay_module
|
||||||
|
|
||||||
|
__all__ = ["sat_planner", "relay_module"]
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
"""Toy relay module adapter.
|
||||||
|
|
||||||
|
This adapter represents a relay node that accepts downlink reservations and
|
||||||
|
returns feasibility signals. It's intentionally minimal for unit testing.
|
||||||
|
"""
|
||||||
|
from typing import Dict, Any
|
||||||
|
from ..plan_delta import PlanDelta
|
||||||
|
|
||||||
|
|
||||||
|
class RelayModuleAdapter:
|
||||||
|
def __init__(self, node_id: str):
|
||||||
|
self.node_id = node_id
|
||||||
|
self.schedule = [] # simplistic list of reserved windows
|
||||||
|
|
||||||
|
def describe_local_problem(self) -> Dict[str, Any]:
|
||||||
|
return {"node_id": self.node_id, "capabilities": ["downlink"]}
|
||||||
|
|
||||||
|
def apply_delta(self, delta: PlanDelta) -> bool:
|
||||||
|
# accept 'downlink' commitments and add to schedule
|
||||||
|
for c in delta.commitments.value():
|
||||||
|
if isinstance(c, dict) and c.get("type") == "downlink":
|
||||||
|
self.schedule.append(c)
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def create(node_id: str):
|
||||||
|
return RelayModuleAdapter(node_id)
|
||||||
|
|
@ -0,0 +1,32 @@
|
||||||
|
"""Toy satellite planner adapter.
|
||||||
|
|
||||||
|
Exposes a simple interface to produce local constraints and accept PlanDelta commits.
|
||||||
|
This is a stub implementation intended for integration tests and adapter conformance.
|
||||||
|
"""
|
||||||
|
from typing import Dict, Any
|
||||||
|
from ..plan_delta import PlanDelta
|
||||||
|
|
||||||
|
|
||||||
|
class SatPlannerAdapter:
|
||||||
|
def __init__(self, node_id: str):
|
||||||
|
self.node_id = node_id
|
||||||
|
self.local_state: Dict[str, Any] = {"fuel": 100.0, "power": 100.0}
|
||||||
|
|
||||||
|
def describe_local_problem(self) -> Dict[str, Any]:
|
||||||
|
# returns a serializable local problem description
|
||||||
|
return {"node_id": self.node_id, "state": self.local_state}
|
||||||
|
|
||||||
|
def apply_delta(self, delta: PlanDelta) -> bool:
|
||||||
|
# naive application: if commitment contains a "burn" element, deduct fuel
|
||||||
|
for c in delta.commitments.value():
|
||||||
|
if isinstance(c, dict) and c.get("type") == "burn":
|
||||||
|
amount = c.get("fuel", 0.0)
|
||||||
|
if self.local_state["fuel"] >= amount:
|
||||||
|
self.local_state["fuel"] -= amount
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
def create(node_id: str):
|
||||||
|
return SatPlannerAdapter(node_id)
|
||||||
|
|
@ -0,0 +1,54 @@
|
||||||
|
"""Governance ledger: append-only tamper-evident log with HMAC signatures.
|
||||||
|
|
||||||
|
This ledger implements a minimal tamper-evident append-only log. Each entry
|
||||||
|
carries the previous hash and an HMAC using a local key to simulate per-node
|
||||||
|
signatures. The ledger supports append and verification.
|
||||||
|
"""
|
||||||
|
import hashlib
|
||||||
|
import hmac
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from typing import List, Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
class GovernanceLedger:
|
||||||
|
def __init__(self, node_key: bytes):
|
||||||
|
self.node_key = node_key
|
||||||
|
self._chain: List[Dict[str, Any]] = []
|
||||||
|
|
||||||
|
def _entry_hash(self, entry: Dict[str, Any]) -> str:
|
||||||
|
raw = json.dumps(entry, sort_keys=True, separators=(",", ":"))
|
||||||
|
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
|
||||||
|
|
||||||
|
def _sign(self, payload: str) -> str:
|
||||||
|
return hmac.new(self.node_key, payload.encode("utf-8"), hashlib.sha256).hexdigest()
|
||||||
|
|
||||||
|
def append(self, author: str, payload: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
prev_hash = self._chain[-1]["hash"] if self._chain else ""
|
||||||
|
entry = {
|
||||||
|
"author": author,
|
||||||
|
"payload": payload,
|
||||||
|
"prev_hash": prev_hash,
|
||||||
|
"ts": time.time(),
|
||||||
|
}
|
||||||
|
entry_hash = self._entry_hash(entry)
|
||||||
|
signature = self._sign(entry_hash)
|
||||||
|
entry["hash"] = entry_hash
|
||||||
|
entry["sig"] = signature
|
||||||
|
self._chain.append(entry)
|
||||||
|
return entry
|
||||||
|
|
||||||
|
def verify_chain(self) -> bool:
|
||||||
|
prev = ""
|
||||||
|
for entry in self._chain:
|
||||||
|
if entry.get("prev_hash", "") != prev:
|
||||||
|
return False
|
||||||
|
# verify signature
|
||||||
|
expected_sig = self._sign(entry["hash"])
|
||||||
|
if not hmac.compare_digest(expected_sig, entry.get("sig", "")):
|
||||||
|
return False
|
||||||
|
prev = entry["hash"]
|
||||||
|
return True
|
||||||
|
|
||||||
|
def entries(self) -> List[Dict[str, Any]]:
|
||||||
|
return list(self._chain)
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
"""PlanDelta CRDT primitives: OR-Set and LWW registers for deterministic merging.
|
||||||
|
|
||||||
|
This file implements a minimal set of CRDT-like types used to make PlanDelta
|
||||||
|
merges deterministic on reconnection. They are intentionally small but include
|
||||||
|
merge operations and vector-clock-like timestamps.
|
||||||
|
"""
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Any, Dict, List
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ORSet:
|
||||||
|
# store JSON-keyed entries so unhashable objects (dicts) can be used
|
||||||
|
add_set: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
||||||
|
rem_set: Dict[str, Dict[str, Any]] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def _key(self, element: Any) -> str:
|
||||||
|
import json as _json
|
||||||
|
|
||||||
|
return _json.dumps(element, sort_keys=True, separators=(',', ':'))
|
||||||
|
|
||||||
|
def add(self, element: Any):
|
||||||
|
k = self._key(element)
|
||||||
|
self.add_set[k] = {"val": element, "ts": time.time()}
|
||||||
|
|
||||||
|
def remove(self, element: Any):
|
||||||
|
k = self._key(element)
|
||||||
|
self.rem_set[k] = {"val": element, "ts": time.time()}
|
||||||
|
|
||||||
|
def value(self) -> List[Any]:
|
||||||
|
res: List[Any] = []
|
||||||
|
for k, info in self.add_set.items():
|
||||||
|
add_ts = info["ts"]
|
||||||
|
rem_ts = self.rem_set.get(k, {}).get("ts", -1)
|
||||||
|
if add_ts > rem_ts:
|
||||||
|
# original object
|
||||||
|
res.append(self.add_set[k]["val"])
|
||||||
|
return res
|
||||||
|
|
||||||
|
def merge(self, other: "ORSet") -> "ORSet":
|
||||||
|
res = ORSet()
|
||||||
|
# merge add_set
|
||||||
|
for k, info in {**self.add_set, **other.add_set}.items():
|
||||||
|
# pick newest
|
||||||
|
left = self.add_set.get(k)
|
||||||
|
right = other.add_set.get(k)
|
||||||
|
if left and right:
|
||||||
|
res.add_set[k] = left if left["ts"] >= right["ts"] else right
|
||||||
|
else:
|
||||||
|
res.add_set[k] = info
|
||||||
|
# merge rem_set similarly
|
||||||
|
for k, info in {**self.rem_set, **other.rem_set}.items():
|
||||||
|
left = self.rem_set.get(k)
|
||||||
|
right = other.rem_set.get(k)
|
||||||
|
if left and right:
|
||||||
|
res.rem_set[k] = left if left["ts"] >= right["ts"] else right
|
||||||
|
else:
|
||||||
|
res.rem_set[k] = info
|
||||||
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LWWRegister:
|
||||||
|
value: Any = None
|
||||||
|
ts: float = 0.0
|
||||||
|
|
||||||
|
def write(self, value: Any):
|
||||||
|
self.value = value
|
||||||
|
self.ts = time.time()
|
||||||
|
|
||||||
|
def read(self):
|
||||||
|
return self.value
|
||||||
|
|
||||||
|
def merge(self, other: "LWWRegister") -> "LWWRegister":
|
||||||
|
if other.ts >= self.ts:
|
||||||
|
return LWWRegister(value=other.value, ts=other.ts)
|
||||||
|
return LWWRegister(value=self.value, ts=self.ts)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlanDelta:
|
||||||
|
# a minimal plan delta carrying a set of commitments and a priority register
|
||||||
|
commitments: ORSet = field(default_factory=ORSet)
|
||||||
|
priority: LWWRegister = field(default_factory=LWWRegister)
|
||||||
|
origin: str = ""
|
||||||
|
|
||||||
|
def merge(self, other: "PlanDelta") -> "PlanDelta":
|
||||||
|
p = PlanDelta()
|
||||||
|
p.commitments = self.commitments.merge(other.commitments)
|
||||||
|
p.priority = self.priority.merge(other.priority)
|
||||||
|
# origin is not merged; keep latest priority's origin heuristically
|
||||||
|
p.origin = other.origin if other.priority.ts >= self.priority.ts else self.origin
|
||||||
|
return p
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
"""Graph-of-Contracts registry: minimal schema and adapter registration.
|
||||||
|
|
||||||
|
This registry stores contract descriptors and adapter metadata. It's intentionally
|
||||||
|
lightweight and uses JSON-serializable structures so adapters can be validated.
|
||||||
|
"""
|
||||||
|
from typing import Dict, Any
|
||||||
|
import time
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
|
||||||
|
class Registry:
|
||||||
|
def __init__(self):
|
||||||
|
# contracts keyed by name -> metadata
|
||||||
|
self._contracts: Dict[str, Dict[str, Any]] = {}
|
||||||
|
self._adapters: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|
||||||
|
def register_contract(self, name: str, schema: Dict[str, Any], version: str = "0.1") -> str:
|
||||||
|
contract_id = f"{name}:{version}"
|
||||||
|
self._contracts[contract_id] = {
|
||||||
|
"name": name,
|
||||||
|
"version": version,
|
||||||
|
"schema": schema,
|
||||||
|
"registered_at": time.time(),
|
||||||
|
"id": contract_id,
|
||||||
|
}
|
||||||
|
return contract_id
|
||||||
|
|
||||||
|
def get_contract(self, contract_id: str) -> Dict[str, Any]:
|
||||||
|
return self._contracts[contract_id]
|
||||||
|
|
||||||
|
def register_adapter(self, adapter_name: str, contract_id: str, meta: Dict[str, Any]) -> str:
|
||||||
|
adapter_id = f"{adapter_name}:{str(uuid.uuid4())[:8]}"
|
||||||
|
self._adapters[adapter_id] = {
|
||||||
|
"name": adapter_name,
|
||||||
|
"contract_id": contract_id,
|
||||||
|
"meta": meta,
|
||||||
|
"registered_at": time.time(),
|
||||||
|
"id": adapter_id,
|
||||||
|
}
|
||||||
|
return adapter_id
|
||||||
|
|
||||||
|
def list_adapters(self):
|
||||||
|
return list(self._adapters.values())
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
"""ADMM-lite node stub.
|
||||||
|
|
||||||
|
This file contains a very small ADMM-like iterative collaborator that exchanges
|
||||||
|
dual updates and tries to improve a local objective. The real system would
|
||||||
|
use network transport and privacy-preserving aggregation; this stub focuses on
|
||||||
|
the local update loop and simple utility computation for tests.
|
||||||
|
"""
|
||||||
|
from typing import Any, Dict
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ADMMNode:
|
||||||
|
node_id: str
|
||||||
|
local_x: float = 0.0
|
||||||
|
dual: float = 0.0
|
||||||
|
rho: float = 1.0
|
||||||
|
|
||||||
|
def local_objective(self, x: float) -> float:
|
||||||
|
# simple quadratic objective with a local preferred point
|
||||||
|
preferred = 10.0
|
||||||
|
return (x - preferred) ** 2
|
||||||
|
|
||||||
|
def step(self, global_avg: float) -> float:
|
||||||
|
# ADMM-style proximal step: minimize 0.5*(x - preferred)^2 + (rho/2)*(x - z + u)^2
|
||||||
|
# here we do a single closed-form proximal update for a quadratic
|
||||||
|
preferred = 10.0
|
||||||
|
z = global_avg
|
||||||
|
u = self.dual
|
||||||
|
# minimize (x-pref)^2 + rho*(x - z + u)^2
|
||||||
|
denom = 1.0 + self.rho
|
||||||
|
num = 2 * preferred + 2 * self.rho * (z - u)
|
||||||
|
x_new = num / (2 * denom)
|
||||||
|
self.local_x = x_new
|
||||||
|
# update dual as simple gradient step
|
||||||
|
self.dual = u + (x_new - z)
|
||||||
|
return x_new
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "gravityweave"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "GravityWeave: Federated orbital resource optimization skeleton"
|
||||||
|
readme = "README.md"
|
||||||
|
requires-python = ">=3.8"
|
||||||
|
authors = [ { name = "GravityWeave Contributors" } ]
|
||||||
|
dependencies = []
|
||||||
9
test.sh
9
test.sh
|
|
@ -1,3 +1,10 @@
|
||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
echo 'Tests passed!'
|
set -euo pipefail
|
||||||
|
echo "Building package..."
|
||||||
|
python3 -m build
|
||||||
|
echo "Installing package into environment..."
|
||||||
|
pip3 install --upgrade .
|
||||||
|
echo "Running tests..."
|
||||||
|
pytest -q
|
||||||
|
echo 'All tests passed!'
|
||||||
exit 0
|
exit 0
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
import os
|
||||||
|
import secrets
|
||||||
|
from gravityweave.registry import Registry
|
||||||
|
from gravityweave.plan_delta import PlanDelta, ORSet, LWWRegister
|
||||||
|
from gravityweave.ledger import GovernanceLedger
|
||||||
|
from gravityweave.adapters.sat_planner import create as sat_create
|
||||||
|
from gravityweave.adapters.relay_module import create as relay_create
|
||||||
|
from gravityweave.solver import ADMMNode
|
||||||
|
|
||||||
|
|
||||||
|
def test_registry_and_adapters():
|
||||||
|
reg = Registry()
|
||||||
|
cid = reg.register_contract("sat_problem", {"fuel": "float"})
|
||||||
|
aid = reg.register_adapter("sat_planner", cid, {"version": "0.1"})
|
||||||
|
adapters = reg.list_adapters()
|
||||||
|
assert any(a["name"] == "sat_planner" for a in adapters)
|
||||||
|
|
||||||
|
|
||||||
|
def test_plan_delta_merge():
|
||||||
|
p1 = PlanDelta(origin="nodeA")
|
||||||
|
p1.commitments.add({"type": "burn", "fuel": 1.0})
|
||||||
|
p1.priority.write(5)
|
||||||
|
|
||||||
|
p2 = PlanDelta(origin="nodeB")
|
||||||
|
p2.commitments.add({"type": "downlink", "window": 42})
|
||||||
|
p2.priority.write(10)
|
||||||
|
|
||||||
|
merged = p1.merge(p2)
|
||||||
|
vals = merged.commitments.value()
|
||||||
|
assert any(isinstance(v, dict) for v in vals)
|
||||||
|
assert merged.priority.read() == 10
|
||||||
|
|
||||||
|
|
||||||
|
def test_ledger_append_and_verify():
|
||||||
|
key = secrets.token_bytes(32)
|
||||||
|
ledger = GovernanceLedger(key)
|
||||||
|
e1 = ledger.append("nodeA", {"action": "seed"})
|
||||||
|
e2 = ledger.append("nodeB", {"action": "update"})
|
||||||
|
assert ledger.verify_chain()
|
||||||
|
entries = ledger.entries()
|
||||||
|
assert entries[0]["author"] == "nodeA"
|
||||||
|
|
||||||
|
|
||||||
|
def test_adapters_apply_delta():
|
||||||
|
sat = sat_create("sat1")
|
||||||
|
relay = relay_create("relay1")
|
||||||
|
delta = PlanDelta(origin="test")
|
||||||
|
delta.commitments.add({"type": "burn", "fuel": 1.0})
|
||||||
|
delta.commitments.add({"type": "downlink", "window": 99})
|
||||||
|
|
||||||
|
assert sat.apply_delta(delta)
|
||||||
|
assert relay.apply_delta(delta)
|
||||||
|
|
||||||
|
|
||||||
|
def test_admm_node_step():
|
||||||
|
node = ADMMNode("n1")
|
||||||
|
z = 8.0
|
||||||
|
x_new = node.step(z)
|
||||||
|
# numeric sanity
|
||||||
|
assert isinstance(x_new, float)
|
||||||
Loading…
Reference in New Issue