diff --git a/src/algograph_algebraic_portfolio_compiler_f/registry.py b/src/algograph_algebraic_portfolio_compiler_f/registry.py index bdb98ff..208430d 100644 --- a/src/algograph_algebraic_portfolio_compiler_f/registry.py +++ b/src/algograph_algebraic_portfolio_compiler_f/registry.py @@ -3,6 +3,8 @@ from __future__ import annotations from dataclasses import dataclass, field from typing import Dict, List, Optional, Any import time +import json +import hashlib @dataclass @@ -58,5 +60,30 @@ class GraphOfContractsRegistry: raise KeyError(f"Contract {contract_id} not found") rec.attestations.append(attestation) + def generate_attestation(self, contract_id: str) -> str: + """Create a deterministic SHA-256 attestation for a registered contract. + + The attestation is computed over a canonical JSON serialization of the + contract metadata (name, version, contract_id, schema, signer, timestamp) + to ensure repeatability for audits and replay checks. + """ + rec = self.contracts.get(contract_id) + if rec is None: + raise KeyError(f"Contract {contract_id} not found") + # Canonical JSON serialization for deterministic hashing + payload = { + "name": rec.name, + "version": rec.version, + "contract_id": rec.contract_id, + "schema": rec.schema, + "signer": rec.signer, + "timestamp": rec.timestamp, + } + data = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8") + h = hashlib.sha256(data).hexdigest() + att = f"sha256:{h}" + rec.attestations.append(att) + return att + __all__ = ["ContractRecord", "GraphOfContractsRegistry"] diff --git a/tests/test_registry.py b/tests/test_registry.py index 226c26f..6e2e099 100644 --- a/tests/test_registry.py +++ b/tests/test_registry.py @@ -18,3 +18,8 @@ def test_registry_basic_operations(): assert c.name == "toyAdapter" reg.add_attestation("idea-1", "attestation-123") assert reg.get_contract("idea-1").attestations[-1] == "attestation-123" + # generate deterministic sha256 attestation and ensure it's appended + att = reg.generate_attestation("idea-1") + assert isinstance(att, str) + assert att.startswith("sha256:") + assert reg.get_contract("idea-1").attestations[-1] == att