"""Chronicle CloudEvents client — matches fastapi-gsap chronicle pattern.""" import hashlib import json import logging from datetime import datetime, UTC import httpx from llm_broker.settings import settings logger = logging.getLogger(__name__) async def emit(kind: str, payload: dict) -> str: """Post a delegation event to Chronicle. Returns CID or empty string.""" url = settings.chronicle_webhook_url if not url: return "" try: event_json = json.dumps({"kind": kind, **payload}, sort_keys=True, default=str) cid = "sha256:" + hashlib.sha256(event_json.encode()).hexdigest() async with httpx.AsyncClient(timeout=5.0) as client: await client.post(url, json={ "pusher": {"login": payload.get("delegator_did", settings.broker_did)}, "ref": f"refs/llm-broker/{kind}", "repository": {"full_name": "llm-broker/delegation"}, "commits": [{"message": f"{kind}: {json.dumps(payload, default=str)}"}], }, headers={"X-Forgejo-Event": "push"}) return cid except Exception as e: logger.warning("Chronicle emit failed: %s: %s", kind, e) return "" async def delegation_created( delegation_id: str, delegator_did: str, agent_did: str, agent_type: str, scope: dict, ) -> str: return await emit("DELEGATION_CREATED", { "event_code": "0x3001", "delegation_id": delegation_id, "delegator_did": delegator_did, "agent_did": agent_did, "agent_type": agent_type, "scope": scope, "timestamp": datetime.now(UTC).isoformat(), }) async def delegation_revoked(delegation_id: str, reason: str) -> str: return await emit("DELEGATION_REVOKED", { "event_code": "0x3003", "delegation_id": delegation_id, "reason": reason, "timestamp": datetime.now(UTC).isoformat(), }) async def delegation_expired(delegation_id: str) -> str: return await emit("DELEGATION_EXPIRED", { "event_code": "0x3004", "delegation_id": delegation_id, "timestamp": datetime.now(UTC).isoformat(), })