build(agent): melter#14fd4b iteration

This commit is contained in:
agent-14fd4b738639d573 2026-04-25 20:42:12 +02:00
parent d7824a3d63
commit e1ac379a47
12 changed files with 386 additions and 2 deletions

21
.gitignore vendored Normal file
View File

@ -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

30
AGENTS.md Normal file
View File

@ -0,0 +1,30 @@
Repository Agent Guide — BlockEnergy Mesh (idea85-blockenergy-mesh-verifiable)
=============================================================
Purpose
- This repository holds a protocol skeleton and a focused Phase-0 implementation for the BlockEnergy Mesh idea. Agents must preserve the registry, primitives, adapters, and the toy proof mechanism. Future agents will extend with solvers, ZK proofs, and ledger integrations.
Tech stack
- Python 3.8+
- pydantic for canonical models and schema validation
- cryptography for signature-based attestations
- numpy for small numerical helpers
- pytest for tests
Key paths
- src/idea85_blockenergy_mesh_verifiable/: package root
- primitives.py: LocalProblem, SharedVariables, PlanDelta, AuditLog
- registry.py: GraphOfContracts (in-memory registry)
- adapters.py: sample adapters and adapter base class
- proofs.py: toy proof generation and verification
Testing
- Run `bash test.sh`. The script executes `pytest` and `python3 -m build` to validate packaging.
Git / CI notes for agents
- Do not run git commands from the agent unless the user explicitly requests commits or pushes.
- When extending interfaces or changing persisted schema, update registry tests and include a migration plan.
Contribution rules
- Keep changes minimal and focused. Follow the repo's dataclass/pydantic patterns.
- Add unit tests for any new behavior. Ensure `pytest` remains green.

View File

@ -1,3 +1,21 @@
# idea85-blockenergy-mesh-verifiable
BlockEnergy Mesh — idea85-blockenergy-mesh-verifiable
=====================================================
Source logic for Idea #85
This repository contains a focused, production-oriented chunk of the BlockEnergy Mesh project: a protocol skeleton, canonical primitives, a Graph-of-Contracts registry, two sample adapters, and a toy on-chain proof mechanism (signature-based attestation of constraint satisfaction).
What is included (MVP slice)
- Canonical primitives: LocalProblem, SharedVariables, PlanDelta, AuditLog (pydantic models)
- Graph-of-Contracts: simple registry to version contract schemas and adapters
- Two starter adapters: SubstationMeterAdapter and DerAggregatorAdapter that map device data to LocalProblem and produce PlanDelta
- Toy proof mechanism: cryptographic commitment (SHA-256) + ECDSA signature (via cryptography) as a verifiable attestation
- Tests and packaging: pytest tests and pyproject.toml for packaging
Run tests
1. Create a virtualenv (recommended)
2. Install test deps (optional): `pip install -r requirements.txt` (not required)
3. Run the test script: `bash test.sh`
This will run `pytest` and `python3 -m build` to validate packaging metadata.
Roadmap
- This patch implements Phase 0 skeleton and a toy proof scheme. Next steps by the swarm: integrate ZK proofs (snarkjs/circom or zksnark bindings), off-chain solver (ADMM-lite), delta-sync reconciliation, governance ledger integration, and adapter conformance suite.

15
pyproject.toml Normal file
View File

@ -0,0 +1,15 @@
[build-system]
requires = ["setuptools>=61.0", "wheel"]
build-backend = "setuptools.build_meta"
[project]
name = "idea85-blockenergy-mesh-verifiable"
version = "0.1.0"
description = "BlockEnergy Mesh: Verifiable Cross-Utility Microgrid Data Exchange (toy MVP)"
readme = "README.md"
requires-python = ">=3.8"
dependencies = [
"cryptography>=3.4",
"pydantic>=1.8",
"numpy>=1.21",
]

4
requirements.txt Normal file
View File

@ -0,0 +1,4 @@
cryptography>=3.4
pydantic>=1.8
numpy>=1.21
pytest

View File

@ -0,0 +1,7 @@
"""idea85_blockenergy_mesh_verifiable package."""
__all__ = [
"primitives",
"registry",
"adapters",
"proofs",
]

View File

@ -0,0 +1,79 @@
from typing import Dict, Any
from datetime import datetime
from .primitives import LocalProblem, PlanDelta
from .proofs import generate_proof, generate_ec_keypair, ToyProof
class AdapterBase:
def __init__(self, name: str):
self.name = name
# each adapter owns a keypair for signing proofs in this MVP
self._priv = generate_ec_keypair()
def private_key(self):
return self._priv
class SubstationMeterAdapter(AdapterBase):
"""Maps a substation meter reading to a LocalProblem."""
def to_local_problem(self, meter_payload: Dict[str, Any]) -> LocalProblem:
# Expect payload with node_id, timestamp, loads list, capacity
return LocalProblem(
schema_version="localproblem-v1",
node_id=meter_payload["node_id"],
timestamp=datetime.fromisoformat(meter_payload["timestamp"]),
horizon_minutes=int(meter_payload.get("horizon_minutes", 60)),
net_load_kw=meter_payload["net_load_kw"],
capacity_kw=float(meter_payload["capacity_kw"]),
metadata=meter_payload.get("metadata"),
)
def produce_plan_and_proof(self, meter_payload: Dict[str, Any]) -> Dict[str, Any]:
lp = self.to_local_problem(meter_payload)
# naive deterministic plan: zero-out anything above capacity by scaling
avg = sum(lp.net_load_kw) / max(1, len(lp.net_load_kw))
scale = min(1.0, lp.capacity_kw / max(1e-6, avg))
plan = [v * scale for v in lp.net_load_kw]
delta = [p - v for p, v in zip(plan, lp.net_load_kw)]
pd = PlanDelta(schema_version="plandelta-v1", node_id=lp.node_id, delta_schedule_kw=delta, reason="scale_to_capacity")
# Create claims (toy): capacity respected if max(plan) <= capacity
claims = {"capacity_ok": max(plan) <= lp.capacity_kw}
proof = generate_proof(lp.dict(), claims, self._priv)
return {"plan_delta": pd, "proof": proof}
class DerAggregatorAdapter(AdapterBase):
"""Maps an aggregator's API to LocalProblem; slightly different heuristic."""
def to_local_problem(self, agg_payload: Dict[str, Any]) -> LocalProblem:
return LocalProblem(
schema_version="localproblem-v1",
node_id=agg_payload["id"],
timestamp=datetime.fromisoformat(agg_payload.get("ts")),
horizon_minutes=int(agg_payload.get("horizon", 60)),
net_load_kw=agg_payload["profile"],
capacity_kw=float(agg_payload.get("cap", 0.0)),
metadata=agg_payload.get("meta"),
)
def produce_plan_and_proof(self, agg_payload: Dict[str, Any]) -> Dict[str, Any]:
lp = self.to_local_problem(agg_payload)
# simple heuristic: shift down high peaks by fixed margin 10%
plan = [v * 0.9 if v > 0.8 * lp.capacity_kw else v for v in lp.net_load_kw]
delta = [p - v for p, v in zip(plan, lp.net_load_kw)]
pd = PlanDelta(schema_version="plandelta-v1", node_id=lp.node_id, delta_schedule_kw=delta, reason="peak_shave_10pct")
claims = {"capacity_ok": max(plan) <= lp.capacity_kw}
proof = generate_proof(lp.dict(), claims, self._priv)
return {"plan_delta": pd, "proof": proof}

View File

@ -0,0 +1,41 @@
from typing import List, Dict, Optional
from pydantic import BaseModel, Field
from datetime import datetime
class VersionedModel(BaseModel):
schema_version: str = Field(..., description="Schema version identifier")
class LocalProblem(VersionedModel):
"""Canonical representation of a local optimization problem for a DER domain."""
node_id: str
timestamp: datetime
horizon_minutes: int
net_load_kw: List[float]
capacity_kw: float
metadata: Optional[Dict] = None
class SharedVariables(VersionedModel):
"""Variables shared across domains (morphisms) for coordinated optimization."""
node_id: str
schedule_kw: List[float]
price_signal: Optional[List[float]] = None
class PlanDelta(VersionedModel):
"""Incremental plan updates produced by a local solver or adapter."""
node_id: str
delta_schedule_kw: List[float]
reason: Optional[str] = None
class AuditLog(VersionedModel):
"""Audit entry tying objects and proofs together for long-term verifiability."""
entry_id: str
created_at: datetime
author: str
object_hash: str
proof_ref: Optional[str] = None
metadata: Optional[Dict] = None

View File

@ -0,0 +1,79 @@
import json
from hashlib import sha256
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import ec
from cryptography.hazmat.primitives.asymmetric.utils import encode_dss_signature, decode_dss_signature
from cryptography.hazmat.primitives import serialization
from cryptography.exceptions import InvalidSignature
def _canonical_json(obj) -> bytes:
# Use json.dumps with a default serializer for datetimes and other non-serializable
# types. Keep keys sorted for deterministic commitments.
return json.dumps(
obj,
sort_keys=True,
separators=(",", ":"),
default=lambda o: o.isoformat() if hasattr(o, "isoformat") else str(o),
).encode("utf-8")
class ToyProof:
"""Toy proof: commitment (sha256 of object) + ECDSA signature by prover key.
The proof object carries a minimal claim set (e.g., capacity_ok) and a signature
over (commitment || claims) so verifiers can check the attestation without raw data.
"""
def __init__(self, commitment_hex: str, claims: dict, signature: bytes, pubkey_bytes: bytes):
self.commitment = commitment_hex
self.claims = claims
self.signature = signature
self.pubkey = pubkey_bytes
def to_dict(self):
return {
"commitment": self.commitment,
"claims": self.claims,
"signature": self.signature.hex(),
"pubkey": self.pubkey.hex(),
}
def generate_proof(obj: dict, claims: dict, private_key: ec.EllipticCurvePrivateKey) -> ToyProof:
# Commitment: hash of canonical JSON of the object
commitment = sha256(_canonical_json(obj)).hexdigest()
payload = {"commitment": commitment, "claims": claims}
payload_b = _canonical_json(payload)
signature = private_key.sign(payload_b, ec.ECDSA(hashes.SHA256()))
pubkey = private_key.public_key().public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo,
)
return ToyProof(commitment, claims, signature, pubkey)
def verify_proof(obj: dict, proof: ToyProof) -> bool:
# Recompute commitment
commitment = sha256(_canonical_json(obj)).hexdigest()
if commitment != proof.commitment:
return False
payload = {"commitment": proof.commitment, "claims": proof.claims}
payload_b = _canonical_json(payload)
# load pubkey
pubkey = serialization.load_der_public_key(proof.pubkey)
try:
pubkey.verify(proof.signature, payload_b, ec.ECDSA(hashes.SHA256()))
return True
except InvalidSignature:
return False
def generate_ec_keypair() -> ec.EllipticCurvePrivateKey:
return ec.generate_private_key(ec.SECP256R1())

View File

@ -0,0 +1,30 @@
from typing import Dict, Any, List
from copy import deepcopy
class GraphOfContracts:
"""Simple in-memory registry mapping contract names to versioned schemas and adapters.
This is intentionally lightweight: it stores schema metadata and adapter references.
"""
def __init__(self):
# structure: { contract_name: { version: {"schema": ..., "adapters": [names] } } }
self._store = {}
def register_schema(self, contract_name: str, version: str, schema: Any):
self._store.setdefault(contract_name, {})
self._store[contract_name][version] = {"schema": deepcopy(schema), "adapters": []}
def list_versions(self, contract_name: str) -> List[str]:
return list(self._store.get(contract_name, {}).keys())
def add_adapter(self, contract_name: str, version: str, adapter_name: str):
if contract_name not in self._store or version not in self._store[contract_name]:
raise KeyError("Schema version not found")
self._store[contract_name][version]["adapters"].append(adapter_name)
def get_schema(self, contract_name: str, version: str):
return self._store[contract_name][version]["schema"]
def adapters_for(self, contract_name: str, version: str):
return list(self._store[contract_name][version]["adapters"])

14
test.sh Normal file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
set -euo pipefail
echo "Installing package in editable mode..."
pip install -e .
echo "Running pytest..."
pytest -q
echo "Building package (python3 -m build)..."
python3 -m build
echo "All tests and build completed successfully."

46
tests/test_primitives.py Normal file
View File

@ -0,0 +1,46 @@
import pytest
from idea85_blockenergy_mesh_verifiable.primitives import LocalProblem
from idea85_blockenergy_mesh_verifiable.adapters import SubstationMeterAdapter, DerAggregatorAdapter
from idea85_blockenergy_mesh_verifiable.proofs import verify_proof
from datetime import datetime
def sample_meter_payload():
return {
"node_id": "substation-1",
"timestamp": datetime.utcnow().isoformat(),
"horizon_minutes": 60,
"net_load_kw": [10.0, 12.0, 11.0, 9.0],
"capacity_kw": 12.0,
}
def test_substation_adapter_plan_and_proof():
adapter = SubstationMeterAdapter("submeter")
payload = sample_meter_payload()
out = adapter.produce_plan_and_proof(payload)
assert "plan_delta" in out and "proof" in out
plan_delta = out["plan_delta"]
proof = out["proof"]
# Plan delta is a PlanDelta model
assert plan_delta.node_id == payload["node_id"]
# Proof verifies against original LocalProblem
lp = adapter.to_local_problem(payload)
assert verify_proof(lp.dict(), proof)
def test_der_adapter_plan_and_proof():
adapter = DerAggregatorAdapter("deragg")
payload = {
"id": "der-1",
"ts": datetime.utcnow().isoformat(),
"horizon": 60,
"profile": [2.0, 3.0, 5.0, 1.0],
"cap": 6.0,
}
out = adapter.produce_plan_and_proof(payload)
assert "plan_delta" in out and "proof" in out
lp = adapter.to_local_problem(payload)
assert verify_proof(lp.dict(), out["proof"]) is True