diff options
| author | Christian Cleberg <[email protected]> | 2026-07-20 15:33:50 -0500 |
|---|---|---|
| committer | Christian Cleberg <[email protected]> | 2026-07-20 15:52:46 -0500 |
| commit | 7b880ea410f37a7df22594ed9b268448621db4d8 (patch) | |
| tree | 90b1fe3489e291a0e0abf652caa3c7c52f59e31f | |
| parent | 8b661d9f36381ee432ece5e77a17d5ae6357d327 (diff) | |
| download | domain-dig-7b880ea410f37a7df22594ed9b268448621db4d8.tar.gz domain-dig-7b880ea410f37a7df22594ed9b268448621db4d8.tar.bz2 domain-dig-7b880ea410f37a7df22594ed9b268448621db4d8.zip | |
fix: report unreachable domains instead of 'No meaningful changes'
Closes #10.
resolvedSnapshotAfterFallback replaces a failed lookup's snapshot with
the previous one, so alertDescriptor compared the old snapshot against
itself, found matching hashes, and the run reported 'No meaningful
changes' for a domain that was never actually reached. Nothing in the
UI or the monitoring log distinguished that from a genuine no-change.
MonitoringDomainResult now carries unreachableReason, set when the
fallback fires. It is Optional so already-persisted monitoring logs
still decode. The run summary reads 'Could not check — kept the previous
result' with the underlying error, and monitoringEvents emits a warning-
severity monitoringFailure so configured integrations hear about it
rather than seeing silence.
| -rw-r--r-- | DomainDig/DomainMonitoringService.swift | 43 | ||||
| -rw-r--r-- | DomainDig/Models.swift | 8 |
2 files changed, 48 insertions, 3 deletions
diff --git a/DomainDig/DomainMonitoringService.swift b/DomainDig/DomainMonitoringService.swift index 6fb5fc9..917ede0 100644 --- a/DomainDig/DomainMonitoringService.swift +++ b/DomainDig/DomainMonitoringService.swift @@ -291,6 +291,12 @@ final class DomainMonitoringService { previousSnapshot: previousSnapshot ) let snapshot = Self.resolvedSnapshotAfterFallback(inspectedSnapshot, previousSnapshot: previousSnapshot) + // When the fallback fires, `snapshot` *is* `previousSnapshot`, so + // every comparison below is old-against-old and would otherwise + // report "No meaningful changes" for a domain we never reached. + let unreachableReason = (previousSnapshot != nil && Self.shouldFallbackToSnapshot(inspectedSnapshot)) + ? Self.firstErrorMessage(in: inspectedSnapshot) + : nil let savedEntry: HistoryEntry? if snapshot.statusMessage == nil { savedEntry = persistSnapshot( @@ -330,14 +336,16 @@ final class DomainMonitoringService { historyEntryID: savedEntry?.id ?? snapshot.historyEntryID, checkedAt: now, didChange: alertDescriptor != nil, - summaryMessage: snapshot.statusMessage + summaryMessage: unreachableReason.map { "Could not check — kept the previous result. \($0)" } + ?? snapshot.statusMessage ?? alertDescriptor?.message ?? savedEntry?.changeSummary?.message ?? "No meaningful changes", alertSeverity: alertDescriptor?.severity, certificateWarningLevel: DomainDiffService.certificateWarningLevel(for: snapshot), resultSource: snapshot.resultSource, - errorMessage: snapshot.statusMessage + errorMessage: snapshot.statusMessage, + unreachableReason: unreachableReason ) results.append(result) @@ -397,6 +405,21 @@ final class DomainMonitoringService { ) } + if let unreachableReason = result.unreachableReason { + return MonitoringEvent( + type: .monitoringFailure, + severity: .warning, + domain: result.domain, + timestamp: result.checkedAt, + summary: result.summaryMessage, + details: [ + "reason": unreachableReason, + "trigger": log.trigger.rawValue, + "resultSource": result.resultSource.rawValue + ] + ) + } + if result.certificateWarningLevel == .critical { return MonitoringEvent( type: .certificateExpiring, @@ -943,6 +966,22 @@ final class DomainMonitoringService { ) } + /// First reported error on a snapshot, used to explain a fallback. Mirrors + /// the ordering `shouldFallbackToSnapshot` inspects. + private static func firstErrorMessage(in snapshot: LookupSnapshot) -> String { + [ + snapshot.dnsError, + snapshot.httpHeadersError, + snapshot.sslError, + snapshot.ownershipError, + snapshot.subdomainsError, + snapshot.redirectChainError, + snapshot.ipGeolocationError + ] + .compactMap { $0 } + .first ?? "The lookup could not reach the domain." + } + private static func shouldFallbackToSnapshot(_ snapshot: LookupSnapshot) -> Bool { let candidateMessages = [ snapshot.dnsError, diff --git a/DomainDig/Models.swift b/DomainDig/Models.swift index b86fc07..6989cba 100644 --- a/DomainDig/Models.swift +++ b/DomainDig/Models.swift @@ -1544,6 +1544,10 @@ struct MonitoringDomainResult: Codable, Identifiable, Equatable { let certificateWarningLevel: CertificateWarningLevel let resultSource: LookupResultSource let errorMessage: String? + /// Non-nil when the lookup failed and the previous snapshot was reused, so + /// this run compared the old data against itself and cannot claim the + /// domain is unchanged. Optional so already-persisted logs still decode. + let unreachableReason: String? init( id: UUID = UUID(), @@ -1555,7 +1559,8 @@ struct MonitoringDomainResult: Codable, Identifiable, Equatable { alertSeverity: MonitoringAlertSeverity?, certificateWarningLevel: CertificateWarningLevel, resultSource: LookupResultSource, - errorMessage: String? = nil + errorMessage: String? = nil, + unreachableReason: String? = nil ) { self.id = id self.domain = domain @@ -1567,6 +1572,7 @@ struct MonitoringDomainResult: Codable, Identifiable, Equatable { self.certificateWarningLevel = certificateWarningLevel self.resultSource = resultSource self.errorMessage = errorMessage + self.unreachableReason = unreachableReason } } |
