"""ORCH-5 (M-7): webhook delivery de-duplication helper. Webhook providers (Gitea/Plane) retry deliveries on timeout, network reset, or manual replay. Without idempotency a retried delivery re-enters the pipeline and spawns a duplicate run (the ET-009 incident class: parallel conveyors on one repo). This module computes a stable per-delivery id so the webhook handlers can INSERT-OR-IGNORE into events and skip the dispatch on a repeat. delivery_id format: ``f"{source}:{raw_or_hash}"`` where source prefixes gitea/plane so their id-spaces never collide. ``raw`` is the provider's native delivery header (a GUID) when present; otherwise we fall back to a sha256 of the body (a retried identical body yields the same hash). """ import hashlib def _sha256_hex(*parts: str) -> str: h = hashlib.sha256() for p in parts: h.update(p.encode("utf-8", "replace")) return h.hexdigest() def gitea_delivery_id(headers, event_type: str, body: bytes) -> str: """Compute the delivery_id for a Gitea webhook. Prefers the ``X-Gitea-Delivery`` header (a per-delivery GUID). Falls back to sha256(source + event_type + body) so a retried identical body still maps to one id even if Gitea omitted the header. """ raw = (headers.get("X-Gitea-Delivery") or "").strip() if not raw: raw = _sha256_hex("gitea", event_type or "", body.decode("utf-8", "replace")) return f"gitea:{raw}" def plane_delivery_id(headers, body: bytes) -> str: """Compute the delivery_id for a Plane webhook. Plane does not reliably send a delivery header, so we try a couple of common names and otherwise fall back to sha256("plane" + body): a retried identical body yields the same id. """ raw = ( headers.get("X-Plane-Delivery") or headers.get("X-Hook-Delivery") or "" ).strip() if not raw: raw = _sha256_hex("plane", body.decode("utf-8", "replace")) return f"plane:{raw}"