build(agent): semicolon#54de0b iteration
This commit is contained in:
parent
8a9e93bf48
commit
233ae22cec
|
|
@ -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,34 @@
|
|||
# AGENTS.md
|
||||
|
||||
## Repository Purpose
|
||||
|
||||
This repository contains a Python reference implementation for the Autopoiesis service-genome fabric.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `genome.py`: genome schema, canonical hashing, Ed25519 signing, signed artifact verification.
|
||||
- `policy.py`: policy templates, deterministic smoke-test evaluation, safety attestation.
|
||||
- `ledger.py`: append-only provenance ledger, Merkle root computation, replay trace models.
|
||||
- `replicator.py`: deterministic replication planner and lightweight ReplicaWeave intent model.
|
||||
|
||||
## Tech Stack
|
||||
|
||||
- Python 3.11+
|
||||
- `pydantic` for strict data models
|
||||
- `cryptography` for Ed25519 signing and verification
|
||||
- `pytest` for tests
|
||||
- `build` for packaging verification
|
||||
|
||||
## Rules
|
||||
|
||||
- Keep changes deterministic and canonical where hashes or signatures are involved.
|
||||
- Preserve frozen model semantics unless there is a concrete reason to relax them.
|
||||
- Prefer the smallest correct change that improves safety, verification, or reproducibility.
|
||||
- Update tests whenever schema, policy, or hashing behavior changes.
|
||||
- Keep public APIs importable from `idea187_autopoiesis_verifiable_service.__init__`.
|
||||
|
||||
## Test Commands
|
||||
|
||||
- `bash test.sh`
|
||||
- `python3 -m build`
|
||||
- `python3 -m pytest`
|
||||
86
README.md
86
README.md
|
|
@ -1,3 +1,85 @@
|
|||
# idea187-autopoiesis-verifiable-service
|
||||
# Autopoiesis: Verifiable Service-Genome Fabric
|
||||
|
||||
Source logic for Idea #187
|
||||
Autopoiesis is a Python reference implementation for a compact, verifiable service-genome layer.
|
||||
|
||||
It focuses on the trust substrate needed for safe self-replication across edge and cloud environments:
|
||||
|
||||
- `ServiceGenome` models immutable service blueprints with resources, capabilities, invariants, mutation operators, and test contracts.
|
||||
- `GenomeSigner` produces and verifies Ed25519 signatures for genome artifacts.
|
||||
- `PolicySandbox` checks a genome against operational budgets and deterministic smoke-test contracts.
|
||||
- `ProvenanceLedger` records append-only replication events with a Merkle root for tamper evidence.
|
||||
- `ReplicatorPlanner` turns an approved genome into a deterministic rollout trace.
|
||||
- `ReplicaWeaveIntent` provides a lightweight admission contract for solver/adapter integrations.
|
||||
|
||||
## What is implemented
|
||||
|
||||
This repository ships a production-shaped core for the autonomy fabric:
|
||||
|
||||
- schema validation for genomes and policies
|
||||
- content addressing via canonical JSON hashing
|
||||
- signed artifact creation and verification
|
||||
- deterministic smoke-test evaluation
|
||||
- provenance logging with Merkle aggregation
|
||||
- replayable replication traces
|
||||
- a small test suite and buildable Python package
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
python3 -m pip install -e .[dev]
|
||||
```
|
||||
|
||||
## Test
|
||||
|
||||
```bash
|
||||
bash test.sh
|
||||
```
|
||||
|
||||
## Example
|
||||
|
||||
```python
|
||||
from idea187_autopoiesis_verifiable_service import (
|
||||
GenomeSigner,
|
||||
PolicySandbox,
|
||||
PolicyTemplate,
|
||||
ProvenanceLedger,
|
||||
ReplicatorPlanner,
|
||||
ResourceEnvelope,
|
||||
SafetyInvariants,
|
||||
ServiceGenome,
|
||||
TestContract,
|
||||
)
|
||||
|
||||
genome = ServiceGenome(
|
||||
name="http-service",
|
||||
version="1.0.0",
|
||||
image_ref="ghcr.io/acme/http-service:1.0.0",
|
||||
capabilities={"network": ["https://internal.example"]},
|
||||
resource_envelope=ResourceEnvelope(cpu_millicores=250, memory_mib=256, bandwidth_mbps=20, cost_usd_per_hour=0.04, max_replicas=3),
|
||||
safety_invariants=SafetyInvariants(max_exposure=10, read_only_scopes=["/var/lib/cache"], denied_endpoints=["https://example.com"]),
|
||||
mutation_operators=[{"name": "safe-fork", "kind": "fork"}],
|
||||
test_contracts=[TestContract(name="startup", seed=7, input_fingerprint="boot", expected_output_hash="...")],
|
||||
)
|
||||
|
||||
signer = GenomeSigner.generate()
|
||||
artifact = signer.sign(genome)
|
||||
|
||||
policy = PolicyTemplate(name="default-edge", max_cpu_millicores=500, max_memory_mib=512, max_bandwidth_mbps=100, max_cost_usd_per_hour=1.0)
|
||||
attestation = PolicySandbox().evaluate(genome, policy)
|
||||
|
||||
ledger = ProvenanceLedger()
|
||||
entry = ledger.append(
|
||||
actor_did="did:key:z6Mkh...",
|
||||
action="replicate",
|
||||
genome_hash=artifact.genome_hash,
|
||||
attestation_hash=attestation.digest(),
|
||||
trace_hash="trace-hash",
|
||||
resource_snapshot=genome.resource_envelope.model_dump(mode="json"),
|
||||
)
|
||||
|
||||
plan = ReplicatorPlanner().plan(genome, attestation)
|
||||
```
|
||||
|
||||
## Package
|
||||
|
||||
The package name is `idea187-autopoiesis-verifiable-service` and the import name is `idea187_autopoiesis_verifiable_service`.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=69", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "idea187-autopoiesis-verifiable-service"
|
||||
version = "0.1.0"
|
||||
description = "Verifiable service-genome fabric for safe self-replication and auditable provenance"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.11"
|
||||
dependencies = [
|
||||
"cryptography>=42.0.0",
|
||||
"pydantic>=2.7.0",
|
||||
]
|
||||
|
||||
[project.optional-dependencies]
|
||||
dev = [
|
||||
"build>=1.2.0",
|
||||
"pytest>=8.0.0",
|
||||
]
|
||||
|
||||
[tool.setuptools]
|
||||
package-dir = {"" = "src"}
|
||||
|
||||
[tool.setuptools.packages.find]
|
||||
where = ["src"]
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
from .genome import (
|
||||
CapabilityVector,
|
||||
GenomeSigner,
|
||||
MutationOperator,
|
||||
ResourceEnvelope,
|
||||
SafetyInvariants,
|
||||
ServiceGenome,
|
||||
SignedGenomeArtifact,
|
||||
TestContract,
|
||||
)
|
||||
from .ledger import LedgerEntry, ProvenanceLedger, ReplayTrace, TraceDelta
|
||||
from .policy import PolicySandbox, PolicyTemplate, SafetyAttestation, SmokeTestResult
|
||||
from .replicator import ReplicaWeaveIntent, ReplicatorPlanner, ReplicationPlan, RolloutStage
|
||||
|
||||
__all__ = [
|
||||
"CapabilityVector",
|
||||
"GenomeSigner",
|
||||
"LedgerEntry",
|
||||
"MutationOperator",
|
||||
"PolicySandbox",
|
||||
"PolicyTemplate",
|
||||
"ProvenanceLedger",
|
||||
"ReplicaWeaveIntent",
|
||||
"ReplicatorPlanner",
|
||||
"ReplicationPlan",
|
||||
"ResourceEnvelope",
|
||||
"ReplayTrace",
|
||||
"RolloutStage",
|
||||
"SafetyAttestation",
|
||||
"SafetyInvariants",
|
||||
"ServiceGenome",
|
||||
"SignedGenomeArtifact",
|
||||
"SmokeTestResult",
|
||||
"TestContract",
|
||||
"TraceDelta",
|
||||
]
|
||||
|
|
@ -0,0 +1,166 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey, Ed25519PublicKey
|
||||
from cryptography.hazmat.primitives.serialization import Encoding, PublicFormat
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
|
||||
def _canonical_json(value: Any) -> bytes:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
|
||||
|
||||
|
||||
class CapabilityVector(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
network: list[str] = Field(default_factory=list)
|
||||
sensors: list[str] = Field(default_factory=list)
|
||||
actuators: list[str] = Field(default_factory=list)
|
||||
db_access: list[str] = Field(default_factory=list)
|
||||
|
||||
|
||||
class ResourceEnvelope(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
cpu_millicores: int
|
||||
memory_mib: int
|
||||
bandwidth_mbps: int
|
||||
cost_usd_per_hour: float
|
||||
max_replicas: int = 1
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_positive(self) -> "ResourceEnvelope":
|
||||
fields = {
|
||||
"cpu_millicores": self.cpu_millicores,
|
||||
"memory_mib": self.memory_mib,
|
||||
"bandwidth_mbps": self.bandwidth_mbps,
|
||||
"cost_usd_per_hour": self.cost_usd_per_hour,
|
||||
"max_replicas": self.max_replicas,
|
||||
}
|
||||
for name, value in fields.items():
|
||||
if value <= 0:
|
||||
raise ValueError(f"{name} must be positive")
|
||||
return self
|
||||
|
||||
|
||||
class SafetyInvariants(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
max_exposure: int
|
||||
read_only_scopes: list[str] = Field(default_factory=list)
|
||||
denied_endpoints: list[str] = Field(default_factory=list)
|
||||
require_deterministic_tests: bool = True
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_exposure(self) -> "SafetyInvariants":
|
||||
if not 0 <= self.max_exposure <= 100:
|
||||
raise ValueError("max_exposure must be between 0 and 100")
|
||||
return self
|
||||
|
||||
|
||||
class MutationOperator(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
name: str
|
||||
kind: str
|
||||
parameters: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class TestContract(BaseModel):
|
||||
__test__ = False
|
||||
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
name: str
|
||||
seed: int
|
||||
input_fingerprint: str
|
||||
expected_output_hash: str
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_hash(self) -> "TestContract":
|
||||
if len(self.expected_output_hash) != 64:
|
||||
raise ValueError("expected_output_hash must be a 64-character sha256 hex digest")
|
||||
return self
|
||||
|
||||
|
||||
class ServiceGenome(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
name: str
|
||||
version: str
|
||||
image_ref: str
|
||||
capabilities: CapabilityVector = Field(default_factory=CapabilityVector)
|
||||
resource_envelope: ResourceEnvelope
|
||||
safety_invariants: SafetyInvariants
|
||||
mutation_operators: list[MutationOperator] = Field(default_factory=list)
|
||||
test_contracts: list[TestContract] = Field(default_factory=list)
|
||||
metadata: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_identity(self) -> "ServiceGenome":
|
||||
if not self.name.strip():
|
||||
raise ValueError("name cannot be empty")
|
||||
if not self.version.strip():
|
||||
raise ValueError("version cannot be empty")
|
||||
if not self.image_ref.strip():
|
||||
raise ValueError("image_ref cannot be empty")
|
||||
return self
|
||||
|
||||
def canonical_dict(self) -> dict[str, Any]:
|
||||
return self.model_dump(mode="json", exclude_none=True)
|
||||
|
||||
def canonical_bytes(self) -> bytes:
|
||||
return _canonical_json(self.canonical_dict())
|
||||
|
||||
def content_hash(self) -> str:
|
||||
return hashlib.sha256(self.canonical_bytes()).hexdigest()
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class SignedGenomeArtifact:
|
||||
genome: ServiceGenome
|
||||
genome_hash: str
|
||||
signature_b64: str
|
||||
public_key_b64: str
|
||||
|
||||
def verify_integrity(self) -> None:
|
||||
if self.genome_hash != self.genome.content_hash():
|
||||
raise ValueError("genome hash does not match genome content")
|
||||
|
||||
|
||||
class GenomeSigner:
|
||||
def __init__(self, private_key: Ed25519PrivateKey) -> None:
|
||||
self._private_key = private_key
|
||||
|
||||
@classmethod
|
||||
def generate(cls) -> "GenomeSigner":
|
||||
return cls(Ed25519PrivateKey.generate())
|
||||
|
||||
@property
|
||||
def public_key(self) -> Ed25519PublicKey:
|
||||
return self._private_key.public_key()
|
||||
|
||||
def sign(self, genome: ServiceGenome) -> SignedGenomeArtifact:
|
||||
payload = genome.canonical_bytes()
|
||||
signature = self._private_key.sign(payload)
|
||||
return SignedGenomeArtifact(
|
||||
genome=genome,
|
||||
genome_hash=genome.content_hash(),
|
||||
signature_b64=base64.b64encode(signature).decode("ascii"),
|
||||
public_key_b64=base64.b64encode(
|
||||
self.public_key.public_bytes(encoding=Encoding.Raw, format=PublicFormat.Raw)
|
||||
).decode("ascii"),
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def verify(artifact: SignedGenomeArtifact) -> bool:
|
||||
artifact.verify_integrity()
|
||||
public_key = Ed25519PublicKey.from_public_bytes(base64.b64decode(artifact.public_key_b64))
|
||||
signature = base64.b64decode(artifact.signature_b64)
|
||||
public_key.verify(signature, artifact.genome.canonical_bytes())
|
||||
return True
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field
|
||||
|
||||
|
||||
def _stable_json(value: Any) -> bytes:
|
||||
return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
|
||||
|
||||
|
||||
class LedgerEntry(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
index: int
|
||||
timestamp: datetime
|
||||
actor_did: str
|
||||
action: str
|
||||
genome_hash: str
|
||||
attestation_hash: str
|
||||
trace_hash: str
|
||||
resource_snapshot: dict[str, Any]
|
||||
previous_hash: str
|
||||
entry_hash: str
|
||||
|
||||
|
||||
class TraceDelta(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
stage: str
|
||||
operation: str
|
||||
payload: dict[str, Any] = Field(default_factory=dict)
|
||||
|
||||
|
||||
class ReplayTrace(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
seed: int
|
||||
deltas: list[TraceDelta] = Field(default_factory=list)
|
||||
signatures: list[str] = Field(default_factory=list)
|
||||
|
||||
def digest(self) -> str:
|
||||
return hashlib.sha256(_stable_json(self.model_dump(mode="json", exclude_none=True))).hexdigest()
|
||||
|
||||
|
||||
class ProvenanceLedger:
|
||||
def __init__(self) -> None:
|
||||
self._entries: list[LedgerEntry] = []
|
||||
|
||||
@property
|
||||
def entries(self) -> tuple[LedgerEntry, ...]:
|
||||
return tuple(self._entries)
|
||||
|
||||
def append(
|
||||
self,
|
||||
*,
|
||||
actor_did: str,
|
||||
action: str,
|
||||
genome_hash: str,
|
||||
attestation_hash: str,
|
||||
trace_hash: str,
|
||||
resource_snapshot: dict[str, Any],
|
||||
timestamp: datetime | None = None,
|
||||
) -> LedgerEntry:
|
||||
timestamp = timestamp or datetime.now(timezone.utc)
|
||||
index = len(self._entries)
|
||||
previous_hash = self._entries[-1].entry_hash if self._entries else hashlib.sha256(b"genesis").hexdigest()
|
||||
payload = {
|
||||
"index": index,
|
||||
"timestamp": timestamp.isoformat(),
|
||||
"actor_did": actor_did,
|
||||
"action": action,
|
||||
"genome_hash": genome_hash,
|
||||
"attestation_hash": attestation_hash,
|
||||
"trace_hash": trace_hash,
|
||||
"resource_snapshot": resource_snapshot,
|
||||
"previous_hash": previous_hash,
|
||||
}
|
||||
entry_hash = hashlib.sha256(_stable_json(payload)).hexdigest()
|
||||
entry = LedgerEntry(entry_hash=entry_hash, **payload)
|
||||
self._entries.append(entry)
|
||||
return entry
|
||||
|
||||
def merkle_root(self) -> str:
|
||||
if not self._entries:
|
||||
return hashlib.sha256(b"").hexdigest()
|
||||
|
||||
layer = [bytes.fromhex(entry.entry_hash) for entry in self._entries]
|
||||
while len(layer) > 1:
|
||||
if len(layer) % 2 == 1:
|
||||
layer.append(layer[-1])
|
||||
next_layer = []
|
||||
for left, right in zip(layer[0::2], layer[1::2], strict=False):
|
||||
next_layer.append(hashlib.sha256(left + right).digest())
|
||||
layer = next_layer
|
||||
return layer[0].hex()
|
||||
|
||||
def export(self) -> dict[str, Any]:
|
||||
return {"entries": [entry.model_dump(mode="json") for entry in self._entries], "merkle_root": self.merkle_root()}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from .genome import ServiceGenome
|
||||
|
||||
|
||||
def _stable_digest(value: Any) -> str:
|
||||
payload = json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")
|
||||
return hashlib.sha256(payload).hexdigest()
|
||||
|
||||
|
||||
class PolicyTemplate(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
name: str
|
||||
max_cpu_millicores: int
|
||||
max_memory_mib: int
|
||||
max_bandwidth_mbps: int
|
||||
max_cost_usd_per_hour: float
|
||||
allowed_egress_endpoints: list[str] = Field(default_factory=list)
|
||||
allowed_mutation_operators: list[str] = Field(default_factory=list)
|
||||
allow_network_egress: bool = False
|
||||
require_deterministic_tests: bool = True
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_positive(self) -> "PolicyTemplate":
|
||||
for field_name in ("max_cpu_millicores", "max_memory_mib", "max_bandwidth_mbps", "max_cost_usd_per_hour"):
|
||||
if getattr(self, field_name) <= 0:
|
||||
raise ValueError(f"{field_name} must be positive")
|
||||
return self
|
||||
|
||||
|
||||
class SmokeTestResult(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
name: str
|
||||
passed: bool
|
||||
seed: int
|
||||
input_fingerprint: str
|
||||
expected_output_hash: str
|
||||
observed_output_hash: str
|
||||
|
||||
|
||||
class SafetyAttestation(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
policy_name: str
|
||||
approved: bool
|
||||
score: float
|
||||
reasons: list[str] = Field(default_factory=list)
|
||||
smoke_tests: list[SmokeTestResult] = Field(default_factory=list)
|
||||
created_at: datetime = Field(default_factory=lambda: datetime.now(timezone.utc))
|
||||
|
||||
def digest(self) -> str:
|
||||
return _stable_digest(self.model_dump(mode="json", exclude_none=True))
|
||||
|
||||
|
||||
class PolicySandbox:
|
||||
def evaluate(self, genome: ServiceGenome, policy: PolicyTemplate) -> SafetyAttestation:
|
||||
reasons: list[str] = []
|
||||
penalties = 0.0
|
||||
|
||||
envelope = genome.resource_envelope
|
||||
if envelope.cpu_millicores > policy.max_cpu_millicores:
|
||||
reasons.append("cpu budget exceeded")
|
||||
penalties += 20
|
||||
if envelope.memory_mib > policy.max_memory_mib:
|
||||
reasons.append("memory budget exceeded")
|
||||
penalties += 20
|
||||
if envelope.bandwidth_mbps > policy.max_bandwidth_mbps:
|
||||
reasons.append("bandwidth budget exceeded")
|
||||
penalties += 15
|
||||
if envelope.cost_usd_per_hour > policy.max_cost_usd_per_hour:
|
||||
reasons.append("cost budget exceeded")
|
||||
penalties += 20
|
||||
|
||||
if not policy.allow_network_egress and genome.capabilities.network:
|
||||
reasons.append("network egress disabled by policy")
|
||||
penalties += 20
|
||||
|
||||
denied = set(genome.safety_invariants.denied_endpoints)
|
||||
egress = set(genome.capabilities.network)
|
||||
violations = sorted(denied.intersection(egress))
|
||||
if violations:
|
||||
reasons.append(f"denied endpoint(s) present: {', '.join(violations)}")
|
||||
penalties += 25
|
||||
|
||||
if policy.allowed_egress_endpoints:
|
||||
unauthorized = sorted(endpoint for endpoint in genome.capabilities.network if endpoint not in policy.allowed_egress_endpoints)
|
||||
if unauthorized:
|
||||
reasons.append(f"unauthorized egress endpoint(s): {', '.join(unauthorized)}")
|
||||
penalties += 15
|
||||
|
||||
if policy.allowed_mutation_operators:
|
||||
unauthorized_ops = sorted(
|
||||
op.name for op in genome.mutation_operators if op.name not in policy.allowed_mutation_operators
|
||||
)
|
||||
if unauthorized_ops:
|
||||
reasons.append(f"mutation operator(s) not allowed: {', '.join(unauthorized_ops)}")
|
||||
penalties += 10
|
||||
|
||||
smoke_tests = self.run_smoke_tests(genome)
|
||||
failed_tests = [result.name for result in smoke_tests if not result.passed]
|
||||
if genome.safety_invariants.require_deterministic_tests and failed_tests:
|
||||
reasons.append(f"smoke test failure(s): {', '.join(failed_tests)}")
|
||||
penalties += 30
|
||||
|
||||
exposure_headroom = max(0, 100 - genome.safety_invariants.max_exposure)
|
||||
score = max(0.0, min(100.0, exposure_headroom - penalties + 50.0))
|
||||
approved = not reasons
|
||||
return SafetyAttestation(policy_name=policy.name, approved=approved, score=score, reasons=reasons, smoke_tests=smoke_tests)
|
||||
|
||||
def run_smoke_tests(self, genome: ServiceGenome) -> list[SmokeTestResult]:
|
||||
genome_hash = genome.content_hash()
|
||||
results: list[SmokeTestResult] = []
|
||||
for contract in genome.test_contracts:
|
||||
observed = hashlib.sha256(
|
||||
f"{contract.seed}:{contract.input_fingerprint}".encode("utf-8")
|
||||
).hexdigest()
|
||||
results.append(
|
||||
SmokeTestResult(
|
||||
name=contract.name,
|
||||
passed=observed == contract.expected_output_hash,
|
||||
seed=contract.seed,
|
||||
input_fingerprint=contract.input_fingerprint,
|
||||
expected_output_hash=contract.expected_output_hash,
|
||||
observed_output_hash=observed,
|
||||
)
|
||||
)
|
||||
return results
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
from __future__ import annotations
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
from dataclasses import dataclass
|
||||
from typing import Any
|
||||
|
||||
from pydantic import BaseModel, ConfigDict, Field, model_validator
|
||||
|
||||
from .genome import ServiceGenome
|
||||
from .ledger import ReplayTrace, TraceDelta
|
||||
from .policy import SafetyAttestation
|
||||
|
||||
|
||||
class ReplicaWeaveIntent(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
source: str
|
||||
target: str
|
||||
intent_kind: str
|
||||
budget_hint: dict[str, Any] = Field(default_factory=dict)
|
||||
safety_labels: list[str] = Field(default_factory=list)
|
||||
|
||||
@model_validator(mode="after")
|
||||
def _validate_identity(self) -> "ReplicaWeaveIntent":
|
||||
if not self.source.strip() or not self.target.strip():
|
||||
raise ValueError("source and target are required")
|
||||
return self
|
||||
|
||||
|
||||
class RolloutStage(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
name: str
|
||||
cohort_size: int
|
||||
percentage: int
|
||||
|
||||
|
||||
class ReplicationPlan(BaseModel):
|
||||
model_config = ConfigDict(frozen=True, extra="forbid")
|
||||
|
||||
genome_hash: str
|
||||
approved: bool
|
||||
stages: list[RolloutStage] = Field(default_factory=list)
|
||||
trace: ReplayTrace
|
||||
rationale: list[str] = Field(default_factory=list)
|
||||
|
||||
def digest(self) -> str:
|
||||
payload = self.model_dump(mode="json", exclude_none=True)
|
||||
return hashlib.sha256(json.dumps(payload, sort_keys=True, separators=(",", ":"), ensure_ascii=True).encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
class ReplicatorPlanner:
|
||||
def plan(self, genome: ServiceGenome, attestation: SafetyAttestation) -> ReplicationPlan:
|
||||
genome_hash = genome.content_hash()
|
||||
trace_seed = int(genome_hash[:16], 16)
|
||||
rationale = list(attestation.reasons)
|
||||
if not attestation.approved:
|
||||
trace = ReplayTrace(seed=trace_seed, deltas=[TraceDelta(stage="blocked", operation="deny", payload={"policy": attestation.policy_name})])
|
||||
return ReplicationPlan(genome_hash=genome_hash, approved=False, stages=[], trace=trace, rationale=rationale)
|
||||
|
||||
max_replicas = genome.resource_envelope.max_replicas
|
||||
canary = min(1, max_replicas)
|
||||
cohort = max(1, min(max_replicas, max(2, max_replicas // 2)))
|
||||
stages = [
|
||||
RolloutStage(name="canary", cohort_size=canary, percentage=5),
|
||||
RolloutStage(name="cohort", cohort_size=cohort, percentage=25),
|
||||
RolloutStage(name="regional", cohort_size=max_replicas, percentage=100),
|
||||
]
|
||||
stages = stages[: 2 if attestation.score < 85 else 3]
|
||||
deltas = [
|
||||
TraceDelta(stage=stage.name, operation="rollout", payload={"cohort_size": stage.cohort_size, "percentage": stage.percentage})
|
||||
for stage in stages
|
||||
]
|
||||
trace = ReplayTrace(seed=trace_seed, deltas=deltas, signatures=[genome_hash, attestation.digest()])
|
||||
return ReplicationPlan(genome_hash=genome_hash, approved=True, stages=stages, trace=trace, rationale=rationale)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
python3 -m pip install -e ".[dev]"
|
||||
python3 -m pytest
|
||||
python3 -m build
|
||||
|
|
@ -0,0 +1,106 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
|
||||
from idea187_autopoiesis_verifiable_service import (
|
||||
GenomeSigner,
|
||||
PolicySandbox,
|
||||
PolicyTemplate,
|
||||
ProvenanceLedger,
|
||||
ReplicatorPlanner,
|
||||
ResourceEnvelope,
|
||||
SafetyInvariants,
|
||||
ServiceGenome,
|
||||
TestContract,
|
||||
)
|
||||
|
||||
|
||||
def build_genome() -> ServiceGenome:
|
||||
expected = __import__("hashlib").sha256("7:boot".encode("utf-8")).hexdigest()
|
||||
return ServiceGenome(
|
||||
name="http-service",
|
||||
version="1.0.0",
|
||||
image_ref="ghcr.io/example/http-service:1.0.0",
|
||||
capabilities={"network": ["https://internal.example"]},
|
||||
resource_envelope=ResourceEnvelope(
|
||||
cpu_millicores=250,
|
||||
memory_mib=256,
|
||||
bandwidth_mbps=20,
|
||||
cost_usd_per_hour=0.05,
|
||||
max_replicas=3,
|
||||
),
|
||||
safety_invariants=SafetyInvariants(max_exposure=10, read_only_scopes=["/var/lib/cache"], denied_endpoints=["https://example.com"]),
|
||||
mutation_operators=[{"name": "safe-fork", "kind": "fork"}],
|
||||
test_contracts=[TestContract(name="startup", seed=7, input_fingerprint="boot", expected_output_hash=expected)],
|
||||
)
|
||||
|
||||
|
||||
def test_genome_sign_and_verify() -> None:
|
||||
genome = build_genome()
|
||||
signer = GenomeSigner.generate()
|
||||
artifact = signer.sign(genome)
|
||||
|
||||
assert artifact.genome_hash == genome.content_hash()
|
||||
assert GenomeSigner.verify(artifact) is True
|
||||
|
||||
|
||||
def test_policy_sandbox_approves_safe_genome() -> None:
|
||||
genome = build_genome()
|
||||
policy = PolicyTemplate(
|
||||
name="default-edge",
|
||||
max_cpu_millicores=500,
|
||||
max_memory_mib=512,
|
||||
max_bandwidth_mbps=100,
|
||||
max_cost_usd_per_hour=1.0,
|
||||
allow_network_egress=True,
|
||||
allowed_egress_endpoints=["https://internal.example"],
|
||||
allowed_mutation_operators=["safe-fork"],
|
||||
)
|
||||
attestation = PolicySandbox().evaluate(genome, policy)
|
||||
|
||||
assert attestation.approved is True
|
||||
assert attestation.reasons == []
|
||||
assert all(result.passed for result in attestation.smoke_tests)
|
||||
|
||||
|
||||
def test_ledger_merkle_root_and_replay_trace_are_deterministic() -> None:
|
||||
genome = build_genome()
|
||||
signer = GenomeSigner.generate()
|
||||
artifact = signer.sign(genome)
|
||||
policy = PolicyTemplate(
|
||||
name="default-edge",
|
||||
max_cpu_millicores=500,
|
||||
max_memory_mib=512,
|
||||
max_bandwidth_mbps=100,
|
||||
max_cost_usd_per_hour=1.0,
|
||||
allow_network_egress=True,
|
||||
allowed_egress_endpoints=["https://internal.example"],
|
||||
allowed_mutation_operators=["safe-fork"],
|
||||
)
|
||||
attestation = PolicySandbox().evaluate(genome, policy)
|
||||
plan = ReplicatorPlanner().plan(genome, attestation)
|
||||
|
||||
ledger = ProvenanceLedger()
|
||||
first = ledger.append(
|
||||
actor_did="did:key:z6Mkh1",
|
||||
action="replicate",
|
||||
genome_hash=artifact.genome_hash,
|
||||
attestation_hash=attestation.digest(),
|
||||
trace_hash=plan.trace.digest(),
|
||||
resource_snapshot=genome.resource_envelope.model_dump(mode="json"),
|
||||
timestamp=datetime(2024, 1, 1, tzinfo=timezone.utc),
|
||||
)
|
||||
second = ledger.append(
|
||||
actor_did="did:key:z6Mkh2",
|
||||
action="promote",
|
||||
genome_hash=artifact.genome_hash,
|
||||
attestation_hash=attestation.digest(),
|
||||
trace_hash=plan.trace.digest(),
|
||||
resource_snapshot=genome.resource_envelope.model_dump(mode="json"),
|
||||
timestamp=datetime(2024, 1, 1, 0, 1, tzinfo=timezone.utc),
|
||||
)
|
||||
|
||||
assert first.entry_hash != second.entry_hash
|
||||
assert ledger.merkle_root()
|
||||
assert plan.approved is True
|
||||
assert plan.trace.digest() == plan.trace.digest()
|
||||
Loading…
Reference in New Issue