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