From 765e525bc2dab1885932eeb72056142610a72f9d Mon Sep 17 00:00:00 2001 From: agent-dd492b85242a98c5 Date: Sun, 19 Apr 2026 19:01:32 +0200 Subject: [PATCH] build(agent): new-agents-3#dd492b iteration --- nova_plan/contracts.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/nova_plan/contracts.py b/nova_plan/contracts.py index cca94d0..a0bf0e9 100644 --- a/nova_plan/contracts.py +++ b/nova_plan/contracts.py @@ -69,6 +69,7 @@ def _delta_sign_digest(delta: PlanDelta, key: str) -> str: "contract_id": delta.contract_id, "parent_version": delta.parent_version, "sequence": delta.sequence, + "nonce": delta.nonce, } payload_bytes = json.dumps(payload, sort_keys=True).encode("utf-8") return hashlib.sha256(payload_bytes + key.encode("utf-8")).hexdigest() @@ -120,6 +121,7 @@ class PlanDelta: contract_id: str = "default" parent_version: Optional[int] = None sequence: Optional[int] = None + nonce: Optional[int] = None signature: Optional[str] = None def to_json(self) -> str: @@ -136,6 +138,7 @@ class PlanDelta: "contract_id": self.contract_id, "parent_version": self.parent_version, "sequence": self.sequence, + "nonce": self.nonce, "signature": self.signature, } return json.dumps(payload, sort_keys=True) @@ -257,6 +260,44 @@ class ContractRegistry: return True +# --------------------------------------------------------------------------- +# Simple API surface for Contract-as-Code (CaC) operations (MVP) +# --------------------------------------------------------------------------- +def register_contract(contract_id: str, version: int, content: Dict[str, Any], signer_id: Optional[str] = None) -> CaCContract: + """Register a CaC contract and optionally sign and publish provenance. + + If signer_id is provided and a matching key is registered in SignerStore, + this will also produce a signed contract artifact and register provenance + in the GoCRegistry for traceability. + """ + contract = CaCContract(contract_id=contract_id, version=version, content=content) + CaCRegistry.register(contract) + if signer_id is not None: + signed = sign_ca_contract(contract, signer_id) + GoCRegistry.register_signed_contract(contract, signer=signer_id, signature=signed.signature) + return contract + + +def push_signal(contract_id: str, delta: PlanDelta, signer_id: str) -> PlanDelta: + """Sign and push a PlanDelta into the GoC provenance registry for a contract.""" + signed = sign_plan_delta(delta, signer_id) + signed.contract_id = contract_id + GoCRegistry.push_delta(contract_id, signed) + return signed + + +def propose_delta(contract_id: str, delta: PlanDelta, signer_id: str) -> PlanDelta: + """Convenience: sign a delta and push it for a contract (idempotent path).""" + signed = sign_plan_delta(delta, signer_id) + signed.contract_id = contract_id + GoCRegistry.push_delta(contract_id, signed) + return signed + + +def get_provenance(contract_id: str) -> list: + """Return the provenance (delta history) for a given contract.""" + return GoCRegistry.get_provenance(contract_id) + @dataclass class PrivacyBudget: """Simple privacy budget block to accompany signals.