Commit 017785c4ce

017785c4ce07cfe026a56d72963eb977fea06440

parent: dad5253250

Verified · cmc

cmc <hello@cleberg.net> · 2026-08-22 07:24 UTC

Build Inspection, Provenance and Failures straight from the snapshot

Grouping the parameters left both call sites spelling out the same 46 lines of
snapshot.x -> x copying, which is duplication SonarCloud was right to flag: the
two lists could drift apart and nothing would say so.

Each of those three groups is populated entirely from the snapshot, so they get
an init(snapshot:) and the call sites collapse to .init(snapshot: snapshot).
The other four groups genuinely differ between the two sites and stay explicit.

Declared in extensions so the structs keep their synthesized memberwise
initializers, which the tests and the remaining call sites use.

Layout: unified · split

DomainDig/DomainMonitoringService.swift +3 −52
@@ -526,24 +526,7 @@ final class DomainMonitoringService {
526526 trackedDomainID: trackedDomainID,
527527 note: trackedDomains.first(where: { $0.id == trackedDomainID })?.note
528528 ),
529 inspection: HistoryEntry.Inspection(
530 dnsSections: snapshot.dnsSections,
531 sslInfo: snapshot.sslInfo,
532 httpHeaders: snapshot.httpHeaders,
533 reachabilityResults: snapshot.reachabilityResults,
534 ipGeolocation: snapshot.ipGeolocation,
535 emailSecurity: snapshot.emailSecurity,
536 mtaSts: snapshot.emailSecurity?.mtaSts,
537 ptrRecord: snapshot.ptrRecord,
538 redirectChain: snapshot.redirectChain,
539 subdomains: snapshot.subdomains,
540 extendedSubdomains: snapshot.extendedSubdomains,
541 dnsHistory: snapshot.dnsHistory,
542 portScanResults: snapshot.portScanResults,
543 hstsPreloaded: snapshot.hstsPreloaded,
544 availabilityResult: snapshot.availabilityResult,
545 suggestions: snapshot.suggestions
546 ),
529 inspection: .init(snapshot: snapshot),
547530 registration: HistoryEntry.Registration(
548531 ownership: snapshot.ownership,
549532 ownershipHistory: snapshot.ownershipHistory,
@@ -552,22 +535,7 @@ final class DomainMonitoringService {
552535 intelligence: HistoryEntry.Intelligence(
553536 reputation: snapshot.reputation
554537 ),
555 provenance: HistoryEntry.Provenance(
556 appVersion: snapshot.appVersion,
557 resultSource: snapshot.resultSource,
558 dataSources: snapshot.dataSources,
559 provenanceBySection: snapshot.provenanceBySection,
560 availabilityConfidence: snapshot.availabilityConfidence,
561 ownershipConfidence: snapshot.ownershipConfidence,
562 subdomainConfidence: snapshot.subdomainConfidence,
563 emailSecurityConfidence: snapshot.emailSecurityConfidence,
564 geolocationConfidence: snapshot.geolocationConfidence,
565 isPartialSnapshot: snapshot.isPartialSnapshot,
566 validationIssues: snapshot.validationIssues,
567 resolverDisplayName: snapshot.resolverDisplayName,
568 resolverURLString: snapshot.resolverURLString,
569 totalLookupDurationMs: snapshot.totalLookupDurationMs
570 ),
538 provenance: .init(snapshot: snapshot),
571539 summary: HistoryEntry.Summary(
572540 primaryIP: Self.primaryIPAddress(from: snapshot),
573541 finalRedirectURL: snapshot.redirectChain.last?.url,
@@ -576,24 +544,7 @@ final class DomainMonitoringService {
576544 httpGradeSummary: snapshot.httpSecurityGrade ?? snapshot.httpHeadersError,
577545 changeSummary: changeSummary
578546 ),
579 failures: HistoryEntry.Failures(
580 errorDetails: snapshot.errorDetails,
581 sslError: snapshot.sslError,
582 httpHeadersError: snapshot.httpHeadersError,
583 reachabilityError: snapshot.reachabilityError,
584 ipGeolocationError: snapshot.ipGeolocationError,
585 emailSecurityError: snapshot.emailSecurityError,
586 ownershipError: snapshot.ownershipError,
587 ownershipHistoryError: snapshot.ownershipHistoryError,
588 ptrError: snapshot.ptrError,
589 redirectChainError: snapshot.redirectChainError,
590 subdomainsError: snapshot.subdomainsError,
591 extendedSubdomainsError: snapshot.extendedSubdomainsError,
592 dnsHistoryError: snapshot.dnsHistoryError,
593 domainPricingError: snapshot.domainPricingError,
594 reputationError: snapshot.reputationError,
595 portScanError: snapshot.portScanError
596 )
547 failures: .init(snapshot: snapshot)
597548 )
598549
599550 history.insert(entry, at: 0)
DomainDig/DomainViewModel.swift +3 −52
@@ -1942,24 +1942,7 @@ final class DomainViewModel {
19421942 trackedDomainID: trackedDomainID,
19431943 note: currentHistoryEntry?.note
19441944 ),
1945 inspection: HistoryEntry.Inspection(
1946 dnsSections: snapshot.dnsSections,
1947 sslInfo: snapshot.sslInfo,
1948 httpHeaders: snapshot.httpHeaders,
1949 reachabilityResults: snapshot.reachabilityResults,
1950 ipGeolocation: snapshot.ipGeolocation,
1951 emailSecurity: snapshot.emailSecurity,
1952 mtaSts: snapshot.emailSecurity?.mtaSts,
1953 ptrRecord: snapshot.ptrRecord,
1954 redirectChain: snapshot.redirectChain,
1955 subdomains: snapshot.subdomains,
1956 extendedSubdomains: snapshot.extendedSubdomains,
1957 dnsHistory: snapshot.dnsHistory,
1958 portScanResults: snapshot.portScanResults,
1959 hstsPreloaded: snapshot.hstsPreloaded,
1960 availabilityResult: snapshot.availabilityResult,
1961 suggestions: snapshot.suggestions
1962 ),
1945 inspection: .init(snapshot: snapshot),
19631946 registration: HistoryEntry.Registration(
19641947 ownership: snapshot.ownership,
19651948 ownershipHistory: snapshot.ownershipHistory,
@@ -1976,22 +1959,7 @@ final class DomainViewModel {
19761959 intelligenceTimeline: intelligence.timelineEvents,
19771960 reputation: snapshot.reputation
19781961 ),
1979 provenance: HistoryEntry.Provenance(
1980 appVersion: snapshot.appVersion,
1981 resultSource: snapshot.resultSource,
1982 dataSources: snapshot.dataSources,
1983 provenanceBySection: snapshot.provenanceBySection,
1984 availabilityConfidence: snapshot.availabilityConfidence,
1985 ownershipConfidence: snapshot.ownershipConfidence,
1986 subdomainConfidence: snapshot.subdomainConfidence,
1987 emailSecurityConfidence: snapshot.emailSecurityConfidence,
1988 geolocationConfidence: snapshot.geolocationConfidence,
1989 isPartialSnapshot: snapshot.isPartialSnapshot,
1990 validationIssues: snapshot.validationIssues,
1991 resolverDisplayName: snapshot.resolverDisplayName,
1992 resolverURLString: snapshot.resolverURLString,
1993 totalLookupDurationMs: snapshot.totalLookupDurationMs
1994 ),
1962 provenance: .init(snapshot: snapshot),
19951963 summary: HistoryEntry.Summary(
19961964 primaryIP: Self.primaryIPAddress(from: snapshot),
19971965 finalRedirectURL: Self.finalRedirectTarget(from: snapshot),
@@ -2004,24 +1972,7 @@ final class DomainViewModel {
20041972 changeCount: domainDiff?.changeCount ?? changeSummary?.changedSections.count ?? 0,
20051973 severitySummary: changeSummary?.severity
20061974 ),
2007 failures: HistoryEntry.Failures(
2008 errorDetails: snapshot.errorDetails,
2009 sslError: snapshot.sslError,
2010 httpHeadersError: snapshot.httpHeadersError,
2011 reachabilityError: snapshot.reachabilityError,
2012 ipGeolocationError: snapshot.ipGeolocationError,
2013 emailSecurityError: snapshot.emailSecurityError,
2014 ownershipError: snapshot.ownershipError,
2015 ownershipHistoryError: snapshot.ownershipHistoryError,
2016 ptrError: snapshot.ptrError,
2017 redirectChainError: snapshot.redirectChainError,
2018 subdomainsError: snapshot.subdomainsError,
2019 extendedSubdomainsError: snapshot.extendedSubdomainsError,
2020 dnsHistoryError: snapshot.dnsHistoryError,
2021 domainPricingError: snapshot.domainPricingError,
2022 reputationError: snapshot.reputationError,
2023 portScanError: snapshot.portScanError
2024 )
1975 failures: .init(snapshot: snapshot)
20251976 )
20261977
20271978 if updateCurrentState {
DomainDig/Models.swift +83
@@ -1267,6 +1267,86 @@ struct MonitoringPendingAlert: Codable, Identifiable, Equatable, Sendable {
12671267 }
12681268}
12691269
1270
1271// Declared in an extension so the struct keeps its synthesized memberwise
1272// initializer: adding one inside the body would suppress it.
1273extension HistoryEntry.Inspection {
1274 /// Every field here is copied straight off the snapshot, and both call
1275 /// sites did exactly that. Naming it once keeps them from drifting.
1276 init(snapshot: LookupSnapshot) {
1277 self.init(
1278 dnsSections: snapshot.dnsSections,
1279 sslInfo: snapshot.sslInfo,
1280 httpHeaders: snapshot.httpHeaders,
1281 reachabilityResults: snapshot.reachabilityResults,
1282 ipGeolocation: snapshot.ipGeolocation,
1283 emailSecurity: snapshot.emailSecurity,
1284 mtaSts: snapshot.emailSecurity?.mtaSts,
1285 ptrRecord: snapshot.ptrRecord,
1286 redirectChain: snapshot.redirectChain,
1287 subdomains: snapshot.subdomains,
1288 extendedSubdomains: snapshot.extendedSubdomains,
1289 dnsHistory: snapshot.dnsHistory,
1290 portScanResults: snapshot.portScanResults,
1291 hstsPreloaded: snapshot.hstsPreloaded,
1292 availabilityResult: snapshot.availabilityResult,
1293 suggestions: snapshot.suggestions
1294 )
1295 }
1296}
1297
1298// Declared in an extension so the struct keeps its synthesized memberwise
1299// initializer: adding one inside the body would suppress it.
1300extension HistoryEntry.Provenance {
1301 /// Every field here is copied straight off the snapshot, and both call
1302 /// sites did exactly that. Naming it once keeps them from drifting.
1303 init(snapshot: LookupSnapshot) {
1304 self.init(
1305 appVersion: snapshot.appVersion,
1306 resultSource: snapshot.resultSource,
1307 dataSources: snapshot.dataSources,
1308 provenanceBySection: snapshot.provenanceBySection,
1309 availabilityConfidence: snapshot.availabilityConfidence,
1310 ownershipConfidence: snapshot.ownershipConfidence,
1311 subdomainConfidence: snapshot.subdomainConfidence,
1312 emailSecurityConfidence: snapshot.emailSecurityConfidence,
1313 geolocationConfidence: snapshot.geolocationConfidence,
1314 isPartialSnapshot: snapshot.isPartialSnapshot,
1315 validationIssues: snapshot.validationIssues,
1316 resolverDisplayName: snapshot.resolverDisplayName,
1317 resolverURLString: snapshot.resolverURLString,
1318 totalLookupDurationMs: snapshot.totalLookupDurationMs
1319 )
1320 }
1321}
1322
1323// Declared in an extension so the struct keeps its synthesized memberwise
1324// initializer: adding one inside the body would suppress it.
1325extension HistoryEntry.Failures {
1326 /// Every field here is copied straight off the snapshot, and both call
1327 /// sites did exactly that. Naming it once keeps them from drifting.
1328 init(snapshot: LookupSnapshot) {
1329 self.init(
1330 errorDetails: snapshot.errorDetails,
1331 sslError: snapshot.sslError,
1332 httpHeadersError: snapshot.httpHeadersError,
1333 reachabilityError: snapshot.reachabilityError,
1334 ipGeolocationError: snapshot.ipGeolocationError,
1335 emailSecurityError: snapshot.emailSecurityError,
1336 ownershipError: snapshot.ownershipError,
1337 ownershipHistoryError: snapshot.ownershipHistoryError,
1338 ptrError: snapshot.ptrError,
1339 redirectChainError: snapshot.redirectChainError,
1340 subdomainsError: snapshot.subdomainsError,
1341 extendedSubdomainsError: snapshot.extendedSubdomainsError,
1342 dnsHistoryError: snapshot.dnsHistoryError,
1343 domainPricingError: snapshot.domainPricingError,
1344 reputationError: snapshot.reputationError,
1345 portScanError: snapshot.portScanError
1346 )
1347 }
1348}
1349
12701350struct TrackedDomain: Codable, Identifiable, Equatable {
12711351 let id: UUID
12721352 var domain: String
@@ -2436,6 +2516,7 @@ struct HistoryEntry: Identifiable, Codable {
24362516 var hstsPreloaded: Bool? = nil
24372517 var availabilityResult: DomainAvailabilityResult? = nil
24382518 var suggestions: [DomainSuggestionResult] = []
2519
24392520 }
24402521
24412522 /// Who owns the domain and where it is hosted.
@@ -2474,6 +2555,7 @@ struct HistoryEntry: Identifiable, Codable {
24742555 var resolverDisplayName: String
24752556 var resolverURLString: String
24762557 var totalLookupDurationMs: Int? = nil
2558
24772559 }
24782560
24792561 /// Precomputed display values and change tracking.
@@ -2508,6 +2590,7 @@ struct HistoryEntry: Identifiable, Codable {
25082590 var domainPricingError: String? = nil
25092591 var reputationError: String? = nil
25102592 var portScanError: String? = nil
2593
25112594 }
25122595
25132596 init(identity: Identity,