"""Credential-preserving Approval Engine registry metadata update. No credential is created, returned, logged or written to a local file. The caller owns the attended custody envelope and persists only ``receipt``. """ from __future__ import annotations import copy import hashlib import json from typing import Any from audit_core.emission_cadence import parse_declaration from audit_core.senders import SenderRegistry CLASSES = {"issuance", "use", "supersession", "revocation"} def strict_json(raw: bytes | str) -> Any: def unique(pairs): result = {} for key, value in pairs: if key in result: raise ValueError("duplicate_json_key") result[key] = value return result return json.loads(raw, object_pairs_hook=unique) def prepare(body: dict, declaration_raw: bytes, proposal: dict) -> tuple[dict, dict]: """Return an in-memory KV data body and a nonsecret receipt. Refuse absent/ambiguous identity, wrong scope, credential absence, stale source pins, widened class inventory, or proposal/declaration disagreement. Preserve every sibling sender, credential, expiry, policy and KV property. """ digest = hashlib.sha256(declaration_raw).hexdigest() if proposal.get("emission_cadence_sha256") != digest: raise ValueError("declaration_digest_mismatch") if (proposal.get("sender"), proposal.get("source"), proposal.get("tenant"), proposal.get("may_read")) != ("approval-engine", "approval-engine", "tenant:platform", False): raise ValueError("unexpected_proposal_scope") if proposal.get("preserve_existing_credentials_and_other_registry_fields") is not True: raise ValueError("preservation_not_declared") frame = proposal["coordination_frame"] f = frame["frame_seconds"] if type(f) is not int or f <= 0: raise ValueError("invalid_frame") waits = proposal["heartbeat_classes"] if waits != dict.fromkeys(CLASSES, 2 * f): raise ValueError("unexpected_class_waits") if (proposal.get("receiver_grace_factor"), proposal.get("effective_deadline_seconds")) != (1.5, 3 * f): raise ValueError("unexpected_deadline") declaration = strict_json(declaration_raw) parsed = parse_declaration(declaration) if parsed.source != "approval-engine" or parsed.rates: raise ValueError("unexpected_declaration_source_or_rate") declared = {e["event_class"]: e for e in parsed.other_entries} if len(declared) != len(parsed.other_entries) or set(declared) != CLASSES | {"heartbeat"}: raise ValueError("unexpected_declaration_inventory") if declaration.get("extensions", {}).get("netkingdom", {}).get("coordination_frame") != frame: raise ValueError("frame_provenance_mismatch") for name in CLASSES: beat = declared[name].get("heartbeat", {}) ext = beat.get("extensions", {}).get("approval-engine", {}) if (beat.get("event_class"), beat.get("interval_seconds"), ext.get("asserted_class"), ext.get("requested_observer_max_gap_seconds"), ext.get("effective_deadline_seconds")) != ( "audit-core.heartbeat", f, name, 2 * f, 3 * f): raise ValueError("declaration_wait_mismatch") rows = strict_json(body["senders.json"]) if not isinstance(rows, list) or any(not isinstance(row, dict) for row in rows): raise ValueError("invalid_registry") indices = [i for i, row in enumerate(rows) if row.get("name") == "approval-engine"] if len(indices) != 1: raise ValueError("sender_absent_or_ambiguous") i = indices[0] before = rows[i] if (before.get("sources"), before.get("tenants"), before.get("may_write", True), before.get("may_read", False)) != (["approval-engine"], ["tenant:platform"], True, False): raise ValueError("existing_sender_scope_mismatch") if not (before.get("tokens") or before.get("token")): raise ValueError("existing_credentials_required") existing_classes = set(before.get("heartbeat_classes", {})) if existing_classes - CLASSES: raise ValueError("would_drop_existing_heartbeat_class") updated_rows = copy.deepcopy(rows) updated_rows[i]["heartbeat_classes"] = copy.deepcopy(waits) updated_rows[i]["emission_cadence"] = declaration # Validate the entire candidate against the actual receiver parser. No # private data appears in exceptions emitted by the attended wrapper. SenderRegistry.from_env({"AUDIT_CORE_SENDERS": json.dumps(updated_rows)}) updated = copy.deepcopy(body) changed = updated_rows != rows if changed: updated["senders.json"] = json.dumps(updated_rows, separators=(",", ":")) receipt = {"sender": "approval-engine", "source": "approval-engine", "tenant": "tenant:platform", "may_read": False, "emission_cadence_sha256": digest, "heartbeat_classes": waits, "effective_deadline_seconds": 3 * f, "changed": changed, "credentials_preserved": True, "other_registry_fields_preserved": True} return updated, receipt def qonto_metadata(declaration: dict, declaration_sha256: str) -> dict: """Build a nonsecret sender proposal; custody owner adds tokens in memory. The digest is of the owner's YAML bytes, which the custody caller verifies before parsing; JSON serialization cannot stand in for that source digest. """ parsed = parse_declaration(declaration) if parsed.source != "qonto-assistant" or parsed.stream_id != "qonto-assistant.audit" or parsed.rates: raise ValueError("unexpected_qonto_declaration") entries = {e["event_class"]: e for e in parsed.other_entries} kinds = {"audit.allow": "attributive", "audit.deny": "load-bearing", "audit.heartbeat": "load-bearing"} if set(entries) != set(kinds) or len(entries) != len(parsed.other_entries): raise ValueError("unexpected_qonto_inventory") frame = declaration.get("extensions", {}).get("netkingdom", {}).get("coordination_frame", {}) f = frame.get("frame_seconds") if type(f) is not int or f <= 0: raise ValueError("invalid_qonto_frame") if len(declaration_sha256) != 64 or any(c not in "0123456789abcdef" for c in declaration_sha256): raise ValueError("invalid_qonto_source_digest") from audit_core.emission_cadence import duration_seconds for name, kind in kinds.items(): entry = entries[name] if entry.get("extensions", {}).get("netkingdom", {}).get("evidence_class") != kind: raise ValueError("qonto_classification_mismatch") beat = entry.get("heartbeat", {}) if beat.get("event_class") != "audit.heartbeat" or duration_seconds(beat.get("interval")) != f: raise ValueError("qonto_cadence_mismatch") if beat.get("extensions", {}).get("qonto-assistant", {}).get("assertion_scope") != "no-deny-transitions-in-window": raise ValueError("qonto_assertion_scope_mismatch") return {"name": "rapp-qonto-collector", "sources": ["qonto-assistant"], "tenants": ["tenant:friendly:binky"], "may_write": True, "may_read": False, "secret_policy": "redact", "evidence_kind": "load-bearing", "heartbeat_classes": {"audit.deny": 2 * f}, "emission_cadence": copy.deepcopy(declaration)}