Commit 8b661d9f36

8b661d9f36381ee432ece5e77a17d5ae6357d327

parent: d9bd61cc74

Unsigned

cmc <hello@cleberg.net> · 2026-07-20 20:31 UTC

fix: make disabled targets and forced queue processing visible

Closes #8, closes #9.

enqueue(events:) filtered to enabled targets before writing any
DeliveryRecord, so events routed to a disabled integration disappeared
with nothing in the Delivery Log. Disabled targets now record a .skipped
entry with a reason, matching how filter mismatches are already surfaced.

sendTest bypassed the isEnabled check entirely, so a test event
delivered against a target that silently dropped every real event —
exactly the wrong signal when someone is verifying their setup. It now
skips with the same reason.

processQueueNow only restarted the processing task; it never moved
nextAttemptAt, so an item in retry backoff stayed undue and the fresh
task went straight back to sleep. Backoff reaches an hour, so the button
appeared inert for the one case it exists to handle. It now pulls every
queued item forward, and reports when the queue is empty instead of
returning silently.

Layout: unified · split

DomainDig/IntegrationService.swift +52 −6
@@ -100,9 +100,22 @@ final class IntegrationService {
100100
101101 func enqueue(events: [MonitoringEvent]) {
102102 guard !events.isEmpty else { return }
103 let eligibleTargets = targets.filter(\.isEnabled)
104103 for event in events {
105 for target in eligibleTargets {
104 for target in targets {
105 guard target.isEnabled else {
106 appendRecord(
107 DeliveryRecord(
108 integrationID: target.id,
109 eventID: event.id,
110 status: .skipped,
111 destination: destinationLabel(for: target),
112 summary: event.summary,
113 failureReason: Self.disabledTargetReason
114 )
115 )
116 continue
117 }
118
106119 if let reason = filterMismatchReason(for: event, target: target) {
107120 appendRecord(
108121 DeliveryRecord(
@@ -150,7 +163,7 @@ final class IntegrationService {
150163 }
151164
152165 func sendTest(for targetID: UUID) {
153 guard targets.contains(where: { $0.id == targetID }) else { return }
166 guard let target = targets.first(where: { $0.id == targetID }) else { return }
154167 let event = MonitoringEvent(
155168 type: .test,
156169 severity: .info,
@@ -161,13 +174,31 @@ final class IntegrationService {
161174 "environment": "local-first"
162175 ]
163176 )
164 queue.append(QueuedDelivery(integrationID: targetID, event: event))
177
178 // Real events skip a disabled target, so a test event must too —
179 // otherwise a test succeeds against a target that silently drops
180 // everything monitoring sends it.
181 guard target.isEnabled else {
182 appendRecord(
183 DeliveryRecord(
184 integrationID: target.id,
185 eventID: event.id,
186 status: .skipped,
187 destination: destinationLabel(for: target),
188 summary: event.summary,
189 failureReason: Self.disabledTargetReason
190 )
191 )
192 return
193 }
194
195 queue.append(QueuedDelivery(integrationID: target.id, event: event))
165196 appendRecord(
166197 DeliveryRecord(
167 integrationID: targetID,
198 integrationID: target.id,
168199 eventID: event.id,
169200 status: .pending,
170 destination: targets.first(where: { $0.id == targetID }).map(destinationLabel(for:)) ?? "Unknown",
201 destination: destinationLabel(for: target),
171202 summary: event.summary
172203 )
173204 )
@@ -175,7 +206,20 @@ final class IntegrationService {
175206 scheduleProcessing()
176207 }
177208
209 /// Restarting the processing task alone leaves any item still in retry
210 /// backoff undue, so the loop would skip it and sleep again. Pulling every
211 /// queued item forward is what makes this button mean "now".
178212 func processQueueNow() {
213 guard !queue.isEmpty else {
214 statusMessage = "No deliveries are waiting."
215 return
216 }
217
218 let now = Date()
219 for index in queue.indices {
220 queue[index].nextAttemptAt = now
221 }
222 persistQueue()
179223 scheduleProcessing(force: true)
180224 }
181225
@@ -427,6 +471,8 @@ final class IntegrationService {
427471 URL(string: string)?.host ?? "Configured"
428472 }
429473
474 private static let disabledTargetReason = "This integration is disabled."
475
430476 private static func secretReference(for integrationID: UUID, suffix: String) -> String {
431477 "integration.\(integrationID.uuidString).\(suffix)"
432478 }