build(agent): r2d2#deee02 iteration
This commit is contained in:
parent
e69c12108b
commit
b8ade7f44a
|
|
@ -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,31 @@
|
||||||
|
**ReplicaWeave Prototype — AGENTS.md**
|
||||||
|
|
||||||
|
Purpose
|
||||||
|
- Explain architecture, tech stack, tests, and rules so agents can contribute safely.
|
||||||
|
|
||||||
|
Repository layout
|
||||||
|
- pyproject.toml / setup.cfg: build metadata
|
||||||
|
- src/idea189_replicaweave_safe_mutation: package source
|
||||||
|
- tests/: pytest tests
|
||||||
|
- test.sh: runs tests and builds the package
|
||||||
|
|
||||||
|
Tech stack
|
||||||
|
- Language: Python 3.9+
|
||||||
|
- Dependencies: cryptography
|
||||||
|
- Build system: setuptools (declared in pyproject.toml)
|
||||||
|
|
||||||
|
Testing commands
|
||||||
|
- Run tests: `pytest`
|
||||||
|
- Build package: `python3 -m build`
|
||||||
|
- Full verification: `bash test.sh` (installs package, runs tests, builds)
|
||||||
|
|
||||||
|
Rules for contributors / agents
|
||||||
|
1. Make small, focused edits. Prefer minimal correct changes.
|
||||||
|
2. Run tests locally before pushing changes.
|
||||||
|
3. If you add a new dependency, add it to setup.cfg under `install_requires` and ensure tests still pass.
|
||||||
|
4. Do not modify tests to silence failures. Fix root causes.
|
||||||
|
5. If you add files, update README.md and tests accordingly.
|
||||||
|
6. Do not create READY_TO_PUBLISH unless the entire Original Idea Description is fully implemented and all tests pass.
|
||||||
|
|
||||||
|
Contact
|
||||||
|
- This repo is maintained by the ReplicaWeave agent swarm. Leave clear comments in PRs explaining the intent of changes.
|
||||||
38
README.md
38
README.md
|
|
@ -1,3 +1,37 @@
|
||||||
# idea189-replicaweave-safe-mutation
|
# ReplicaWeave — op-CRDT schema & TestCartridge spec (prototype)
|
||||||
|
|
||||||
Source logic for Idea #189
|
This repository provides a focused, well-tested Python prototype for two core pieces of the ReplicaWeave idea:
|
||||||
|
|
||||||
|
- An op-based CRDT delta schema (op types, deterministic merge, compact merkle-style ops root).
|
||||||
|
- A deterministic TestCartridge spec and small harness that runs cartridges with seeded inputs to produce reproducible TestReports and Scorecards.
|
||||||
|
|
||||||
|
This prototype is intended as a solid chunk for the larger ReplicaWeave system (Genome Lab test cartridges, Scorecards, signed Manifests). It is intentionally minimal but production-minded: it includes a Python package layout, signing utilities, deterministic behavior, and tests.
|
||||||
|
|
||||||
|
What you'll find here
|
||||||
|
- src/idea189_replicaweave_safe_mutation/: core library
|
||||||
|
- tests/: pytest test suite
|
||||||
|
- test.sh: runs tests and builds the package
|
||||||
|
- AGENTS.md: contributor instructions
|
||||||
|
|
||||||
|
Quick usage (from repo root):
|
||||||
|
|
||||||
|
1. Install package in editable mode:
|
||||||
|
|
||||||
|
python3 -m pip install -e .
|
||||||
|
|
||||||
|
2. Run tests:
|
||||||
|
|
||||||
|
pytest
|
||||||
|
|
||||||
|
3. Build the package metadata and sdist/wheel:
|
||||||
|
|
||||||
|
python3 -m build
|
||||||
|
|
||||||
|
Core concepts implemented
|
||||||
|
- Op-CRDT: op records, Delta merging with deterministic ordering and an ops_root function.
|
||||||
|
- TestCartridge: small, composable cartridges that run deterministically from a seed and return TestReports (score, logs, counterexample seed).
|
||||||
|
- Harness: deterministic runner that runs cartridges against deltas and emits signed Manifests.
|
||||||
|
- Scorecard: JSON schema describing safety_score, resource impact, reproducible failures.
|
||||||
|
- Simple signing (Ed25519) for Manifests / TestReports.
|
||||||
|
|
||||||
|
This is a foundation — next agents can add a WASM runner, richer policy checks, marketplace UI, and certification pipeline.
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools>=61.0", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
[metadata]
|
||||||
|
name = idea189_replicaweave_safe_mutation
|
||||||
|
version = 0.1.0
|
||||||
|
description = ReplicaWeave op-CRDT schema and TestCartridge spec
|
||||||
|
long_description = file: README.md
|
||||||
|
long_description_content_type = text/markdown
|
||||||
|
author = OpenCode
|
||||||
|
license = MIT
|
||||||
|
readme = README.md
|
||||||
|
|
||||||
|
[options]
|
||||||
|
package_dir =
|
||||||
|
= src
|
||||||
|
packages = find:
|
||||||
|
install_requires =
|
||||||
|
cryptography
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
"""idea189_replicaweave_safe_mutation
|
||||||
|
|
||||||
|
Small library implementing an op-CRDT schema, deterministic TestCartridge harness,
|
||||||
|
scorecard schema, and minimal signing/manifest utilities.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .crdt import Op, Delta
|
||||||
|
from .test_cartridge import TestCartridge, built_in_cartridges
|
||||||
|
from .harness import run_cartridge
|
||||||
|
from .scorecard import make_scorecard
|
||||||
|
from .signer import generate_keypair, sign_bytes, verify_signature
|
||||||
|
from .manifest import make_manifest
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"Op",
|
||||||
|
"Delta",
|
||||||
|
"TestCartridge",
|
||||||
|
"built_in_cartridges",
|
||||||
|
"run_cartridge",
|
||||||
|
"make_scorecard",
|
||||||
|
"generate_keypair",
|
||||||
|
"sign_bytes",
|
||||||
|
"verify_signature",
|
||||||
|
"make_manifest",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
"""Simple op-based CRDT delta schema and deterministic merge."""
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import List, Any
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(order=True)
|
||||||
|
class Op:
|
||||||
|
"""A single op in the op-CRDT model.
|
||||||
|
|
||||||
|
Fields:
|
||||||
|
- op_type: str (graft, prune, param-set, pin)
|
||||||
|
- target: str (path or id)
|
||||||
|
- value: Any
|
||||||
|
- timestamp: int (logical time)
|
||||||
|
- actor: str (actor id)
|
||||||
|
- idx: int (to preserve op ordering from same actor)
|
||||||
|
"""
|
||||||
|
timestamp: int
|
||||||
|
actor: str
|
||||||
|
idx: int
|
||||||
|
op_type: str = field(compare=False)
|
||||||
|
target: str = field(compare=False)
|
||||||
|
value: Any = field(compare=False)
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
return {
|
||||||
|
"timestamp": self.timestamp,
|
||||||
|
"actor": self.actor,
|
||||||
|
"idx": self.idx,
|
||||||
|
"op_type": self.op_type,
|
||||||
|
"target": self.target,
|
||||||
|
"value": self.value,
|
||||||
|
}
|
||||||
|
|
||||||
|
def digest(self) -> bytes:
|
||||||
|
j = json.dumps(self.to_dict(), sort_keys=True, separators=(",", ":"))
|
||||||
|
return hashlib.sha256(j.encode("utf-8")).digest()
|
||||||
|
|
||||||
|
|
||||||
|
class Delta:
|
||||||
|
"""A collection of ops with deterministic merge semantics."""
|
||||||
|
|
||||||
|
def __init__(self, ops: List[Op] = None):
|
||||||
|
self.ops = ops[:] if ops else []
|
||||||
|
|
||||||
|
def add_op(self, op: Op):
|
||||||
|
self.ops.append(op)
|
||||||
|
|
||||||
|
def merge(self, other: "Delta") -> "Delta":
|
||||||
|
# op-based CRDT deterministic merge: union of ops with stable ordering
|
||||||
|
combined = list(self.ops)
|
||||||
|
combined.extend(other.ops)
|
||||||
|
# sort by (timestamp, actor, idx) - dataclass order supports timestamp, actor, idx
|
||||||
|
combined.sort()
|
||||||
|
# deduplicate by op digest to avoid double-apply
|
||||||
|
seen = set()
|
||||||
|
dedup = []
|
||||||
|
for op in combined:
|
||||||
|
d = op.digest()
|
||||||
|
if d not in seen:
|
||||||
|
dedup.append(op)
|
||||||
|
seen.add(d)
|
||||||
|
return Delta(dedup)
|
||||||
|
|
||||||
|
def ops_root(self) -> str:
|
||||||
|
"""Compact Merkle-like root over op digests.
|
||||||
|
|
||||||
|
We compute a rolling SHA-256 by consuming op digests in deterministic order.
|
||||||
|
This keeps the root small and deterministic for auditors.
|
||||||
|
"""
|
||||||
|
h = hashlib.sha256()
|
||||||
|
for op in sorted(self.ops):
|
||||||
|
h.update(op.digest())
|
||||||
|
return h.hexdigest()
|
||||||
|
|
||||||
|
def to_list(self):
|
||||||
|
return [op.to_dict() for op in self.ops]
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
"""Deterministic harness to run cartridges against a delta and produce TestReports and Manifests."""
|
||||||
|
from .test_cartridge import TestCartridge, TestReport
|
||||||
|
from .manifest import make_manifest
|
||||||
|
from .signer import sign_bytes
|
||||||
|
from typing import Dict, Any, List, Tuple
|
||||||
|
|
||||||
|
|
||||||
|
def run_cartridge(cartridge: TestCartridge, delta: Dict[str, Any], seed: int, signer_key: bytes = None) -> Tuple[TestReport, Dict[str, Any]]:
|
||||||
|
"""Run a cartridge deterministically and return the TestReport and Manifest.
|
||||||
|
|
||||||
|
signer_key: optional private key to sign the manifest (Ed25519)
|
||||||
|
"""
|
||||||
|
report = cartridge.run(delta, seed)
|
||||||
|
# build a minimal manifest and sign it if key provided
|
||||||
|
manifest = make_manifest(parent_hash=delta.get("parent_hash"), delta_ops_root=delta.get("ops_root"), test_report_root=report.score if isinstance(report.score, int) else 0)
|
||||||
|
if signer_key:
|
||||||
|
# create a canonical payload for signing and attach it so verifiers can reproduce the same bytes
|
||||||
|
canonical = str(manifest).encode("utf-8")
|
||||||
|
sig = sign_bytes(signer_key, canonical)
|
||||||
|
manifest["signed_payload"] = canonical.decode("utf-8")
|
||||||
|
manifest["signature"] = sig.hex()
|
||||||
|
return report, manifest
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
"""Create minimal manifests for test runs."""
|
||||||
|
|
||||||
|
def make_manifest(parent_hash: str, delta_ops_root: str, test_report_root: int) -> dict:
|
||||||
|
return {
|
||||||
|
"parent_hash": parent_hash,
|
||||||
|
"delta_ops_root": delta_ops_root,
|
||||||
|
"test_report_root": test_report_root,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
"""Small scorecard generator for TestReports.
|
||||||
|
Produces a JSON-serializable dict with safety_score, resource_impact_est, reproducible_failures, recommended_limits
|
||||||
|
"""
|
||||||
|
from .test_cartridge import TestReport
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
def make_scorecard(report: TestReport, delta: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
reproducible = []
|
||||||
|
if report.counterexample_seed is not None:
|
||||||
|
reproducible.append(report.counterexample_seed)
|
||||||
|
resource_est = len(delta.get("ops", [])) * 10 # arbitrary units
|
||||||
|
recommended_limits = {"cpu_ms": max(50, resource_est * 10), "memory_mb": max(16, resource_est * 2)}
|
||||||
|
return {
|
||||||
|
"safety_score": report.score,
|
||||||
|
"resource_impact_est": resource_est,
|
||||||
|
"reproducible_failures": reproducible,
|
||||||
|
"recommended_limits": recommended_limits,
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
"""Simple Ed25519 signer/verifier helpers.
|
||||||
|
Requires cryptography.
|
||||||
|
"""
|
||||||
|
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
||||||
|
from cryptography.hazmat.primitives import serialization
|
||||||
|
|
||||||
|
|
||||||
|
def generate_keypair() -> (bytes, bytes):
|
||||||
|
"""Generate a private/public key pair and return raw bytes (private, public).
|
||||||
|
|
||||||
|
Private bytes are serialized in private bytes (PEM).
|
||||||
|
"""
|
||||||
|
priv = Ed25519PrivateKey.generate()
|
||||||
|
pub = priv.public_key()
|
||||||
|
priv_bytes = priv.private_bytes(encoding=serialization.Encoding.Raw, format=serialization.PrivateFormat.Raw, encryption_algorithm=serialization.NoEncryption())
|
||||||
|
pub_bytes = pub.public_bytes(encoding=serialization.Encoding.Raw, format=serialization.PublicFormat.Raw)
|
||||||
|
return priv_bytes, pub_bytes
|
||||||
|
|
||||||
|
|
||||||
|
def sign_bytes(private_key_raw: bytes, message: bytes) -> bytes:
|
||||||
|
priv = Ed25519PrivateKey.from_private_bytes(private_key_raw)
|
||||||
|
return priv.sign(message)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_signature(public_key_raw: bytes, message: bytes, signature: bytes) -> bool:
|
||||||
|
try:
|
||||||
|
pub = Ed25519PublicKey.from_public_bytes(public_key_raw)
|
||||||
|
pub.verify(signature, message)
|
||||||
|
return True
|
||||||
|
except Exception:
|
||||||
|
return False
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
"""TestCartridge spec and a few built-in deterministic cartridges.
|
||||||
|
|
||||||
|
Each cartridge is a small function that, given a delta and a seed, returns a TestReport.
|
||||||
|
TestReport is a dict with keys: passed (bool), score (0-100), logs (list), counterexample_seed (optional int)
|
||||||
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Callable, Dict, Any, List
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TestReport:
|
||||||
|
passed: bool
|
||||||
|
score: int
|
||||||
|
logs: List[str]
|
||||||
|
counterexample_seed: int = None
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class TestCartridge:
|
||||||
|
name: str
|
||||||
|
version: str
|
||||||
|
run: Callable[[Dict[str, Any], int], TestReport]
|
||||||
|
|
||||||
|
|
||||||
|
def _deterministic_outcome(seed: int, fail_rate: float = 0.2):
|
||||||
|
r = random.Random(seed)
|
||||||
|
return r.random() >= fail_rate
|
||||||
|
|
||||||
|
|
||||||
|
def network_partition_cartridge(delta, seed: int) -> TestReport:
|
||||||
|
# deterministic check: failure occurs when random < 0.15
|
||||||
|
ok = _deterministic_outcome(seed, fail_rate=0.15)
|
||||||
|
logs = [f"network_partition(seed={seed}) -> {'ok' if ok else 'fail'}"]
|
||||||
|
return TestReport(passed=ok, score=(90 if ok else 20), logs=logs, counterexample_seed=(None if ok else seed))
|
||||||
|
|
||||||
|
|
||||||
|
def resource_exhaustion_cartridge(delta, seed: int) -> TestReport:
|
||||||
|
# estimate resource impact from delta size
|
||||||
|
size = len(delta.get("ops", []))
|
||||||
|
r = random.Random(seed + size)
|
||||||
|
ok = r.random() >= 0.25
|
||||||
|
logs = [f"resource_exhaustion(seed={seed},ops={size}) -> {'ok' if ok else 'fail'}"]
|
||||||
|
score = max(0, 100 - size * 5) if ok else 10
|
||||||
|
return TestReport(passed=ok, score=score, logs=logs, counterexample_seed=(None if ok else seed))
|
||||||
|
|
||||||
|
|
||||||
|
def equivocation_cartridge(delta, seed: int) -> TestReport:
|
||||||
|
# simple heuristic: fail if two ops from different actors target same path
|
||||||
|
ops = delta.get("ops", [])
|
||||||
|
targets = {}
|
||||||
|
for op in ops:
|
||||||
|
t = op.get("target")
|
||||||
|
targets.setdefault(t, set()).add(op.get("actor"))
|
||||||
|
equiv = any(len(s) > 1 for s in targets.values())
|
||||||
|
logs = [f"equivocation check -> {'equivocated' if equiv else 'clean'}"]
|
||||||
|
return TestReport(passed=not equiv, score=(50 if not equiv else 0), logs=logs, counterexample_seed=(seed if equiv else None))
|
||||||
|
|
||||||
|
|
||||||
|
def actuator_mock_cartridge(delta, seed: int) -> TestReport:
|
||||||
|
# simulate actuator invocation risk; use seed to decide
|
||||||
|
ok = _deterministic_outcome(seed, fail_rate=0.1)
|
||||||
|
logs = [f"actuator_mock(seed={seed}) -> {'ok' if ok else 'danger'}"]
|
||||||
|
return TestReport(passed=ok, score=(95 if ok else 5), logs=logs, counterexample_seed=(None if ok else seed))
|
||||||
|
|
||||||
|
|
||||||
|
built_in_cartridges = [
|
||||||
|
TestCartridge("network_partition", "0.1", network_partition_cartridge),
|
||||||
|
TestCartridge("resource_exhaustion", "0.1", resource_exhaustion_cartridge),
|
||||||
|
TestCartridge("equivocation", "0.1", equivocation_cartridge),
|
||||||
|
TestCartridge("actuator_mock", "0.1", actuator_mock_cartridge),
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
python3 -m pip install --upgrade pip
|
||||||
|
python3 -m pip install -e .
|
||||||
|
pytest -q
|
||||||
|
python3 -m build
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
import pytest
|
||||||
|
from idea189_replicaweave_safe_mutation.crdt import Op, Delta
|
||||||
|
from idea189_replicaweave_safe_mutation.test_cartridge import built_in_cartridges
|
||||||
|
from idea189_replicaweave_safe_mutation.harness import run_cartridge
|
||||||
|
from idea189_replicaweave_safe_mutation.signer import generate_keypair, sign_bytes, verify_signature
|
||||||
|
|
||||||
|
|
||||||
|
def test_delta_merge_and_root():
|
||||||
|
a1 = Op(timestamp=1, actor="A", idx=0, op_type="param-set", target="/svc/replicas", value=3)
|
||||||
|
a2 = Op(timestamp=2, actor="A", idx=1, op_type="graft", target="/svc/new", value={"image": "v1"})
|
||||||
|
b1 = Op(timestamp=2, actor="B", idx=0, op_type="param-set", target="/svc/replicas", value=4)
|
||||||
|
|
||||||
|
d1 = Delta([a1, a2])
|
||||||
|
d2 = Delta([b1])
|
||||||
|
merged = d1.merge(d2)
|
||||||
|
# merged should contain 3 ops
|
||||||
|
assert len(merged.ops) == 3
|
||||||
|
root = merged.ops_root()
|
||||||
|
assert isinstance(root, str) and len(root) == 64
|
||||||
|
|
||||||
|
|
||||||
|
def test_harness_and_signing():
|
||||||
|
# prepare a simple delta
|
||||||
|
d = Delta()
|
||||||
|
d.add_op(Op(timestamp=1, actor="A", idx=0, op_type="graft", target="/x", value=1))
|
||||||
|
delta_payload = {"ops": d.to_list(), "ops_root": d.ops_root(), "parent_hash": None}
|
||||||
|
|
||||||
|
cartridge = built_in_cartridges[0] # network_partition
|
||||||
|
priv, pub = generate_keypair()
|
||||||
|
report, manifest = run_cartridge(cartridge, delta_payload, seed=42, signer_key=priv)
|
||||||
|
assert hasattr(report, "passed")
|
||||||
|
# signature present
|
||||||
|
assert "signature" in manifest
|
||||||
|
sig = bytes.fromhex(manifest["signature"])
|
||||||
|
signed_payload = manifest.get("signed_payload")
|
||||||
|
assert signed_payload is not None
|
||||||
|
ok = verify_signature(pub, signed_payload.encode("utf-8"), sig)
|
||||||
|
assert ok
|
||||||
Loading…
Reference in New Issue