From c7bb48f0b4dc1790ba26d4f6138dba88ee1df3b8 Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sun, 4 Oct 2026 08:44:50 +0100 Subject: [PATCH] Clarify the existing webhook correlation contract Keep canonical guidance aligned with the actual unchanged header and copied member-aware templates. Pin the correction in the same safety guard so a later maintainer does not restore permanent header-only deduplication. Contract-Neutral: Documentation clarification and its verification only; webhook wire format, retry stability and runtime behaviour are unchanged. Change-source: pulse-maintainer --- .../v6/internal/subsystems/notifications.md | 15 +++++++++++++-- scripts/tests/test_webhook_verification_docs.py | 3 +++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/release-control/v6/internal/subsystems/notifications.md b/docs/release-control/v6/internal/subsystems/notifications.md index 1e63866860..9081f935bf 100644 --- a/docs/release-control/v6/internal/subsystems/notifications.md +++ b/docs/release-control/v6/internal/subsystems/notifications.md @@ -493,9 +493,20 @@ through the canonical webhook transport must send `X-Pulse-Timestamp` and `X-Pulse-Signature` (`v1=` + hex HMAC-SHA256 over `timestamp + "." + body`), computed at the single request-construction choke point and set after custom headers so user-provided header maps cannot shadow them. Alert deliveries also -send `X-Pulse-Event-ID` (`alertID:event`) as the idempotency token; it must be +send `X-Pulse-Event-ID` (`alertID:event`) as a legacy correlation token; it must be stable across both transport-layer and queue-layer retries of the same alert -occurrence. The management API must mask a configured signing secret on read +occurrence. It omits occurrence start, severity and grouped membership and is +not a unique incident or delivery identity; it is not covered by the HMAC. +Receiver guidance must not prescribe permanent header-only deduplication. +Normal queued templates expose every member through `.Alerts`, whose full- +precision `StartTime` can distinguish later occurrences even when the primary +RFC3339 start string has the same whole second. Correlate recovery with that +occurrence and preserve severity updates and changed members. The existing +wire format and retry stability remain unchanged. `webhook_guide_test.go` +exercises copied templates through the actual signed sender over loopback for +retry, escalation, recovery, recurrence, group changes and JSON escaping; it +does not establish durable ticket actions or installed notification delivery. +The management API must mask a configured signing secret on read and preserve the stored secret when an update echoes the masked placeholder, the same ownership rule it applies to header and custom-field secrets. That same transport boundary also owns webhook request normalization. Rendered diff --git a/scripts/tests/test_webhook_verification_docs.py b/scripts/tests/test_webhook_verification_docs.py index 78fee09927..32b366e264 100644 --- a/scripts/tests/test_webhook_verification_docs.py +++ b/scripts/tests/test_webhook_verification_docs.py @@ -161,6 +161,9 @@ class WebhookVerificationDocsTest(unittest.TestCase): self.assertNotIn("two-priority PSA mapping covers the full range", text) self.assertIn('"info"', text) self.assertIn("successful firing-delivery receipt", text) + contract = (ROOT / "docs/release-control/v6/internal/subsystems/notifications.md").read_text() + self.assertIn("as a legacy correlation token", contract) + self.assertIn("must not prescribe permanent header-only deduplication", contract) if __name__ == "__main__":