From 94a50e75b017901ac31e15e27881aac46abce32c Mon Sep 17 00:00:00 2001 From: "pulse-triage[bot]" <249995291+pulse-triage[bot]@users.noreply.github.com> Date: Sat, 24 Oct 2026 16:21:39 +0100 Subject: [PATCH] Acknowledge persisted webhook deletion to the destination editor Return the explicit success boolean the existing editor requires after durable and live removal, while retaining the legacy status field. Pin the response contract so a successful deletion is not displayed as failed and does not encourage blind retries. Change-source: pulse-maintainer --- .../v6/internal/subsystems/agent-lifecycle.md | 5 +++++ .../v6/internal/subsystems/api-contracts.md | 12 ++++++++++++ .../v6/internal/subsystems/notifications.md | 8 ++++++++ .../v6/internal/subsystems/storage-recovery.md | 5 +++++ internal/api/alerting/notifications.go | 4 +++- internal/api/alerting/notifications_test.go | 7 +++++++ 6 files changed, 40 insertions(+), 1 deletion(-) diff --git a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md index 4c9051a0f3..733d6fa3e4 100644 --- a/docs/release-control/v6/internal/subsystems/agent-lifecycle.md +++ b/docs/release-control/v6/internal/subsystems/agent-lifecycle.md @@ -1687,6 +1687,11 @@ token-free command. No copy receipt establishes installed removal or a release. ## Shared Boundaries +Webhook DELETE acknowledges committed destination removal with `success: true` +and the retained legacy status. It does not acknowledge agent removal, revoke an +agent credential or change an already admitted agent/update job. Notification +queue destination-ID revocation remains with the notification owner. + Proxmox helper operation v2 carries the shared `ProxmoxLXCInventory` completeness and bounded omitted-VMID contract. V1 stays strict complete-only. The collector tries v1 only after an explicit unsupported-v2 response, under one unchanged operation deadline; transport, authorization and provider failures never select a broader fallback. The shared monitor constructor also composes notification bootstrap: it loads diff --git a/docs/release-control/v6/internal/subsystems/api-contracts.md b/docs/release-control/v6/internal/subsystems/api-contracts.md index d699383fa8..6ce20ef1c3 100644 --- a/docs/release-control/v6/internal/subsystems/api-contracts.md +++ b/docs/release-control/v6/internal/subsystems/api-contracts.md @@ -21,6 +21,18 @@ ## Purpose +### Webhook deletion has an explicit committed acknowledgement + +`DELETE /api/notifications/webhooks/{id}` returns `success: true` after saved and +live inventories both remove that ID. The legacy `status: "success"` field remains +for existing clients. The destination editor already requires the boolean before +accepting deletion; the acknowledgement must not leave a successfully removed +row shown as failed. Missing destinations, failed persistence and failed live +publication retain their existing error and rollback paths, not a success body. +The handler's deletion assertion pins both fields; persistence-failure controls +continue to require no live removal. This changes no route, scope, credential, +retention or admitted-delivery snapshot contract. + ### Typed MCP fleet filters survive HTTP projection GET/DELETE capability projection preserves public string and boolean query diff --git a/docs/release-control/v6/internal/subsystems/notifications.md b/docs/release-control/v6/internal/subsystems/notifications.md index a3f996d4d1..464354aa40 100644 --- a/docs/release-control/v6/internal/subsystems/notifications.md +++ b/docs/release-control/v6/internal/subsystems/notifications.md @@ -39,6 +39,14 @@ operator retry, cancellation, audit/receipt readback and new-alert delivery. This is synthetic source proof, not evidence of prior exposure, installed-provider recovery or resolution of a reported notification outage. +The DELETE acknowledgement includes `success: true` only after durable and live +removal complete, retaining the legacy `status: "success"` field. The existing +editor requires that explicit boolean before removing the visible destination; +a successful mutation must not be displayed as failed or encourage a blind retry. +Persistence/live-publication failures retain their existing refusal/rollback +behaviour. `TestNotificationHandlers/DeleteWebhook` pins both success fields; +its failure controls still prove that a rejected save never removes live state. + ### Approved addresses share a bounded connection budget Webhook and HTTP Apprise transports pass their validated numeric destinations diff --git a/docs/release-control/v6/internal/subsystems/storage-recovery.md b/docs/release-control/v6/internal/subsystems/storage-recovery.md index b7a63ca5e2..f66f9c54c3 100644 --- a/docs/release-control/v6/internal/subsystems/storage-recovery.md +++ b/docs/release-control/v6/internal/subsystems/storage-recovery.md @@ -775,6 +775,11 @@ Compact Coverage age cells use the existing metadata-size text and reclaim numer ## Shared Boundaries +Webhook DELETE's explicit success acknowledges saved/live destination removal, +not deletion of its retained queue configuration or failed-attempt history. +Queue records still follow the existing cancellation/retention policy; no +storage schema, backup, restore or data-removal acknowledgement is added. + ### Operator storage vocabulary without identity changes Storage topology names preserve Proxmox plugin vocabulary, including LVM-Thin, diff --git a/internal/api/alerting/notifications.go b/internal/api/alerting/notifications.go index e5756bb858..53721b2367 100644 --- a/internal/api/alerting/notifications.go +++ b/internal/api/alerting/notifications.go @@ -750,7 +750,9 @@ func (h *NotificationHandlers) DeleteWebhook(w http.ResponseWriter, r *http.Requ return } - if err := utils.WriteJSONResponse(w, map[string]string{"status": "success"}); err != nil { + // The destination editor accepts a mutation only after success is explicit. + // Keep status for existing clients; acknowledge only after durable/live removal. + if err := utils.WriteJSONResponse(w, map[string]interface{}{"success": true, "status": "success"}); err != nil { log.Error().Err(err).Str("webhookID", webhookID).Msg("Failed to write webhook deletion response") } } diff --git a/internal/api/alerting/notifications_test.go b/internal/api/alerting/notifications_test.go index fd2c682938..2b633cceea 100644 --- a/internal/api/alerting/notifications_test.go +++ b/internal/api/alerting/notifications_test.go @@ -718,6 +718,13 @@ func TestNotificationHandlers(t *testing.T) { h.DeleteWebhook(w, req) assert.Equal(t, 200, w.Code) + var result struct { + Success bool `json:"success"` + Status string `json:"status"` + } + assert.NoError(t, json.Unmarshal(w.Body.Bytes(), &result)) + assert.True(t, result.Success, "the destination editor must be able to accept the persisted deletion") + assert.Equal(t, "success", result.Status, "retain the legacy acknowledgement") }) t.Run("GetWebhookTemplates", func(t *testing.T) {