Skip to main content

max / makenotwork

pom: project test, DNS, and CORS checks onto status.json The three signals PoM already collected but did not yet restate on the shared contract. Each is one more condition on the target node, additive by design -- a target without the config for one emits nothing for it, and no viewer change is needed. - tests: pending when never run, degraded when the last run failed or is stale (PoM's own staleness verdict: age past the configured threshold, or a version deployed since), ok otherwise. Degraded, not failed: a red suite is a regression signal, but the running service is health's job. - dns: one aggregate condition; a record whose lookup errored or whose value does not match degrades the target and is named. A record that points the site somewhere dead already shows as a failed health check. - cors: one aggregate condition; a preflight that failed to run or was answered without allowing the origin degrades the target. status.rs stays a pure function of (targets, now); api.rs sources the rows. +10 status tests (32 total), clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Author: Max Johnson <me@maxj.phd> · 2026-07-23 00:18 UTC
Commit: 40a467d7a9405f6fb7fa99a583ad8ffcca3f21a2
Parent: 6bcef42
2 files changed, +484 insertions, -0 deletions
@@ -614,6 +614,80 @@ async fn build_status_view(state: &ApiState) -> Vec<crate::status::TargetView> {
614 614 error: s.error,
615 615 });
616 616
617 + // Tests: the latest run plus PoM's staleness verdict, sourced exactly as
618 + // build_target_status does (version at test time vs current version).
619 + let tests = if let Some(tests_config) = &target_config.tests {
620 + let latest_test = db::get_latest_test_run(&state.pool, &name)
621 + .await
622 + .ok()
623 + .flatten();
624 + let current_version = health.as_ref().and_then(|h| h.version.clone());
625 + let tested_version = if let Some(test) = &latest_test {
626 + db::get_version_at_time(&state.pool, &name, &test.started_at)
627 + .await
628 + .ok()
629 + .flatten()
630 + } else {
631 + None
632 + };
633 + let staleness = compute_test_staleness(
634 + current_version.as_deref(),
635 + tested_version.as_deref(),
636 + latest_test.as_ref().map(|t| t.started_at.as_str()),
637 + tests_config.staleness_days,
638 + );
639 + Some(crate::status::TestsView {
640 + ran: latest_test.is_some(),
641 + passed: latest_test.as_ref().is_some_and(|t| t.passed),
642 + total_passed: latest_test.as_ref().and_then(|t| t.summary.total_passed),
643 + total_failed: latest_test.as_ref().and_then(|t| t.summary.total_failed),
644 + started_at: latest_test.as_ref().map(|t| t.started_at.clone()),
645 + stale: staleness.stale,
646 + stale_reason: staleness.reason,
647 + })
648 + } else {
649 + None
650 + };
651 +
652 + // DNS: one entry per monitored record. Absent config yields no rows and
653 + // therefore no condition.
654 + let dns = {
655 + let rows = db::get_latest_dns_checks(&state.pool, &name)
656 + .await
657 + .unwrap_or_default();
658 + (!rows.is_empty()).then(|| crate::status::DnsView {
659 + checked_at: rows.iter().map(|r| r.checked_at.clone()).max(),
660 + records: rows
661 + .into_iter()
662 + .map(|r| crate::status::DnsRecordView {
663 + name: r.name,
664 + record_type: r.record_type,
665 + matches: r.matches,
666 + error: r.error,
667 + })
668 + .collect(),
669 + })
670 + };
671 +
672 + // CORS: one entry per monitored URL.
673 + let cors = {
674 + let rows = db::get_latest_cors_checks(&state.pool, &name)
675 + .await
676 + .unwrap_or_default();
677 + (!rows.is_empty()).then(|| crate::status::CorsView {
678 + checked_at: rows.iter().map(|r| r.checked_at.clone()).max(),
679 + checks: rows
680 + .into_iter()
681 + .map(|r| crate::status::CorsCheckView {
682 + url: r.url,
683 + origin: r.origin,
684 + passes: r.passes,
685 + error: r.error,
686 + })
687 + .collect(),
688 + })
689 + };
690 +
617 691 targets.push(crate::status::TargetView {
618 692 name,
619 693 label: target_config.label.clone(),
@@ -625,6 +699,9 @@ async fn build_status_view(state: &ApiState) -> Vec<crate::status::TargetView> {
625 699 whois,
626 700 backups,
627 701 scan_pipeline,
702 + tests,
703 + dns,
704 + cors,
628 705 });
629 706 }
630 707
@@ -78,6 +78,13 @@ pub(crate) struct TargetView {
78 78 pub backups: Vec<BackupView>,
79 79 /// Latest scan-pipeline check. `None` if not monitored for this target.
80 80 pub scan_pipeline: Option<ScanView>,
81 + /// Latest test run and PoM's staleness verdict. `None` if the target has no
82 + /// test config.
83 + pub tests: Option<TestsView>,
84 + /// Latest DNS record checks. `None` if DNS is not monitored for this target.
85 + pub dns: Option<DnsView>,
86 + /// Latest CORS preflight checks. `None` if CORS is not monitored.
87 + pub cors: Option<CorsView>,
81 88 }
82 89
83 90 pub(crate) struct HealthView {
@@ -125,6 +132,49 @@ pub(crate) struct ScanView {
125 132 pub error: Option<String>,
126 133 }
127 134
135 + pub(crate) struct TestsView {
136 + /// Whether any run has ever been recorded. Separates "no evidence"
137 + /// (pending) from a run that failed.
138 + pub ran: bool,
139 + /// Whether the most recent run passed.
140 + pub passed: bool,
141 + pub total_passed: Option<i64>,
142 + pub total_failed: Option<i64>,
143 + /// When the most recent run started, RFC 3339.
144 + pub started_at: Option<String>,
145 + /// PoM's staleness verdict for the run: true when it is older than the
146 + /// configured threshold or a version was deployed after it ran.
147 + pub stale: bool,
148 + pub stale_reason: Option<String>,
149 + }
150 +
151 + pub(crate) struct DnsView {
152 + /// One entry per monitored record; non-empty by construction.
153 + pub records: Vec<DnsRecordView>,
154 + /// Latest checked_at across the records, for the condition's `since`.
155 + pub checked_at: Option<String>,
156 + }
157 +
158 + pub(crate) struct DnsRecordView {
159 + pub name: String,
160 + pub record_type: String,
161 + pub matches: bool,
162 + pub error: Option<String>,
163 + }
164 +
165 + pub(crate) struct CorsView {
166 + /// One entry per monitored URL; non-empty by construction.
167 + pub checks: Vec<CorsCheckView>,
168 + pub checked_at: Option<String>,
169 + }
170 +
171 + pub(crate) struct CorsCheckView {
172 + pub url: String,
173 + pub origin: String,
174 + pub passes: bool,
175 + pub error: Option<String>,
176 + }
177 +
128 178 /// Restate every monitored target as the shared payload.
129 179 ///
130 180 /// `now` is an argument rather than read from the clock so the mapping stays
@@ -156,6 +206,15 @@ fn target_node(target: &TargetView) -> Node {
156 206 if let Some(scan) = &target.scan_pipeline {
157 207 conditions.push(scan_condition(scan));
158 208 }
209 + if let Some(tests) = &target.tests {
210 + conditions.push(tests_condition(tests));
211 + }
212 + if let Some(dns) = &target.dns {
213 + conditions.push(dns_condition(dns));
214 + }
215 + if let Some(cors) = &target.cors {
216 + conditions.push(cors_condition(cors));
217 + }
159 218
160 219 // The target's status is the worst thing said about it. There is always at
161 220 // least the health condition, so the max is well-defined.
@@ -364,6 +423,142 @@ fn scan_condition(scan: &ScanView) -> Condition {
364 423 }
365 424 }
366 425
426 + /// Test state as a condition. A never-run target is `pending` — evidence of
427 + /// nothing, like one never health-checked. A failing or stale run is `degraded`,
428 + /// not `failed`: a red suite is a real regression signal, but the running
429 + /// service is covered by `health`, so a test result colors the target yellow
430 + /// (look at this) rather than red (it is down). Staleness reuses PoM's own
431 + /// verdict, which fires on age past the configured threshold or a version
432 + /// deployed since the last run.
433 + fn tests_condition(tests: &TestsView) -> Condition {
434 + if !tests.ran {
435 + return Condition {
436 + condition_type: "tests".into(),
437 + status: Status::Pending,
438 + since: None,
439 + detail: Some("no tests have been run yet".into()),
440 + };
441 + }
442 + let since = tests.started_at.as_deref().and_then(parse_instant);
443 + let (status, detail) = if !tests.passed {
444 + let detail = match (tests.total_passed, tests.total_failed) {
445 + (Some(p), Some(f)) => format!("last run failed: {f} failed, {p} passed"),
446 + _ => "last test run failed".into(),
447 + };
448 + (Status::Degraded, detail)
449 + } else if tests.stale {
450 + (
451 + Status::Degraded,
452 + tests
453 + .stale_reason
454 + .clone()
455 + .unwrap_or_else(|| "test results are stale".into()),
456 + )
457 + } else {
458 + let detail = match tests.total_passed {
459 + Some(p) => format!("tests passing, {p} passed"),
460 + None => "tests passing".into(),
461 + };
462 + (Status::Ok, detail)
463 + };
464 + Condition {
465 + condition_type: "tests".into(),
466 + status,
467 + since,
468 + detail: Some(detail),
469 + }
470 + }
471 +
472 + /// DNS state as one aggregate condition across a target's monitored records. A
473 + /// record whose lookup errored or whose value does not match is `degraded`: a
474 + /// wrong or unverifiable record is a real misconfiguration, but a record that
475 + /// points the site somewhere dead already shows as a failed `health` check, so
476 + /// DNS colors the target yellow and names what is off. All records clean is
477 + /// `ok`. One aggregate condition rather than one per record keeps a target that
478 + /// watches several records to a single legible row.
479 + fn dns_condition(dns: &DnsView) -> Condition {
480 + let bad: Vec<String> = dns
481 + .records
482 + .iter()
483 + .filter_map(|r| {
484 + if let Some(error) = &r.error {
485 + Some(format!(
486 + "{} {} lookup failed: {error}",
487 + r.name, r.record_type
488 + ))
489 + } else if !r.matches {
490 + Some(format!("{} {} does not match", r.name, r.record_type))
491 + } else {
492 + None
493 + }
494 + })
495 + .collect();
496 + let since = dns.checked_at.as_deref().and_then(parse_instant);
497 + let (status, detail) = if bad.is_empty() {
498 + (
499 + Status::Ok,
500 + format!(
501 + "{} record{} match",
502 + dns.records.len(),
503 + plural(dns.records.len())
504 + ),
505 + )
506 + } else {
507 + (Status::Degraded, bad.join("; "))
508 + };
509 + Condition {
510 + condition_type: "dns".into(),
511 + status,
512 + since,
513 + detail: Some(detail),
514 + }
515 + }
516 +
517 + /// CORS state as one aggregate condition across a target's monitored URLs. A
518 + /// preflight that failed to run, or that the server answered without allowing
519 + /// the origin, is `degraded`: broken CORS breaks a browser client, but it is a
520 + /// configuration fault rather than the service being down, so it colors the
521 + /// target yellow. All preflights allowed is `ok`.
522 + fn cors_condition(cors: &CorsView) -> Condition {
523 + let bad: Vec<String> = cors
524 + .checks
525 + .iter()
526 + .filter_map(|c| {
527 + if let Some(error) = &c.error {
528 + Some(format!("{} preflight failed: {error}", c.url))
529 + } else if !c.passes {
530 + Some(format!("{} does not allow {}", c.url, c.origin))
531 + } else {
532 + None
533 + }
534 + })
535 + .collect();
536 + let since = cors.checked_at.as_deref().and_then(parse_instant);
537 + let (status, detail) = if bad.is_empty() {
538 + (
539 + Status::Ok,
540 + format!(
541 + "{} preflight check{} pass",
542 + cors.checks.len(),
543 + plural(cors.checks.len())
544 + ),
545 + )
546 + } else {
547 + (Status::Degraded, bad.join("; "))
548 + };
549 + Condition {
550 + condition_type: "cors".into(),
551 + status,
552 + since,
553 + detail: Some(detail),
554 + }
555 + }
556 +
557 + /// The plural suffix for a count: "" for one, "s" otherwise.
558 + fn plural(n: usize) -> &'static str {
559 + if n == 1 { "" } else { "s" }
560 + }
561 +
367 562 /// Uptime as a bar, latency as a magnitude, version as a comparable — each a
368 563 /// number that *means* something rather than a pre-rendered string.
369 564 fn target_fields(target: &TargetView) -> Vec<Field> {
@@ -439,6 +634,9 @@ mod tests {
439 634 whois: None,
440 635 backups: Vec::new(),
441 636 scan_pipeline: None,
637 + tests: None,
638 + dns: None,
639 + cors: None,
442 640 }
443 641 }
444 642
@@ -867,6 +1065,215 @@ mod tests {
867 1065 assert!(scan.detail.as_deref().unwrap().contains("502 Bad Gateway"));
868 1066 }
869 1067
1068 + fn tests_view(passed: bool, stale: bool) -> TestsView {
1069 + TestsView {
1070 + ran: true,
1071 + passed,
1072 + total_passed: Some(226),
1073 + total_failed: if passed { Some(0) } else { Some(3) },
1074 + started_at: Some(checked_at()),
1075 + stale,
1076 + stale_reason: if stale {
1077 + Some("tests are 12 days old (threshold: 7d)".into())
1078 + } else {
1079 + None
1080 + },
1081 + }
1082 + }
1083 +
1084 + fn condition<'a>(n: &'a Node, ty: &str) -> &'a Condition {
1085 + n.conditions
1086 + .iter()
1087 + .find(|c| c.condition_type == ty)
1088 + .unwrap_or_else(|| panic!("no {ty} condition"))
1089 + }
1090 +
1091 + #[test]
1092 + fn a_never_run_test_target_is_pending_not_healthy() {
1093 + let mut t = healthy("mnw");
1094 + t.tests = Some(TestsView {
1095 + ran: false,
1096 + passed: false,
1097 + total_passed: None,
1098 + total_failed: None,
1099 + started_at: None,
1100 + stale: true,
1101 + stale_reason: Some("no tests have been run".into()),
1102 + });
1103 + let p = payload(&[t], now());
1104 + let n = node(&p, "target:mnw");
1105 + // Pending is quieter than degraded: no evidence is not a failure.
1106 + assert_eq!(n.status, Status::Pending);
1107 + assert_eq!(
1108 + condition(n, "tests").detail.as_deref(),
1109 + Some("no tests have been run yet")
1110 + );
1111 + }
1112 +
1113 + #[test]
1114 + fn a_failing_test_run_degrades_but_does_not_fail_the_target() {
1115 + // A red suite is a regression signal; the running service is health's job.
1116 + let mut t = healthy("mnw");
1117 + t.tests = Some(tests_view(false, false));
1118 + let p = payload(&[t], now());
1119 + let n = node(&p, "target:mnw");
1120 + assert_eq!(n.status, Status::Degraded);
1121 + let c = condition(n, "tests");
1122 + assert_eq!(c.status, Status::Degraded);
1123 + assert!(c.detail.as_deref().unwrap().contains("3 failed"));
1124 + }
1125 +
1126 + #[test]
1127 + fn a_stale_but_passing_test_run_degrades_the_target() {
1128 + let mut t = healthy("mnw");
1129 + t.tests = Some(tests_view(true, true));
1130 + let p = payload(&[t], now());
1131 + let n = node(&p, "target:mnw");
1132 + assert_eq!(n.status, Status::Degraded);
1133 + assert!(
1134 + condition(n, "tests")
1135 + .detail
1136 + .as_deref()
1137 + .unwrap()
1138 + .contains("12 days old")
1139 + );
1140 + }
1141 +
1142 + #[test]
1143 + fn a_fresh_passing_test_run_leaves_the_target_ok() {
1144 + let mut t = healthy("mnw");
1145 + t.tests = Some(tests_view(true, false));
1146 + let p = payload(&[t], now());
1147 + let n = node(&p, "target:mnw");
1148 + assert_eq!(n.status, Status::Ok);
1149 + assert_eq!(condition(n, "tests").status, Status::Ok);
1150 + }
1151 +
1152 + #[test]
1153 + fn a_dns_mismatch_degrades_the_target_and_names_the_record() {
1154 + let mut t = healthy("mnw");
1155 + t.dns = Some(DnsView {
1156 + records: vec![
1157 + DnsRecordView {
1158 + name: "makenot.work".into(),
1159 + record_type: "A".into(),
1160 + matches: true,
1161 + error: None,
1162 + },
1163 + DnsRecordView {
1164 + name: "makenot.work".into(),
1165 + record_type: "MX".into(),
1166 + matches: false,
1167 + error: None,
1168 + },
1169 + ],
1170 + checked_at: Some(checked_at()),
1171 + });
1172 + let p = payload(&[t], now());
1173 + let n = node(&p, "target:mnw");
1174 + assert_eq!(n.status, Status::Degraded);
1175 + let c = condition(n, "dns");
1176 + assert_eq!(c.status, Status::Degraded);
1177 + assert!(c.detail.as_deref().unwrap().contains("MX does not match"));
1178 + }
1179 +
1180 + #[test]
1181 + fn a_dns_lookup_error_reads_as_a_flaky_degrade_not_a_mismatch() {
1182 + let mut t = healthy("mnw");
1183 + t.dns = Some(DnsView {
1184 + records: vec![DnsRecordView {
1185 + name: "makenot.work".into(),
1186 + record_type: "TXT".into(),
1187 + matches: false,
1188 + error: Some("SERVFAIL".into()),
1189 + }],
1190 + checked_at: Some(checked_at()),
1191 + });
1192 + let p = payload(&[t], now());
1193 + let c = condition(node(&p, "target:mnw"), "dns");
1194 + assert_eq!(c.status, Status::Degraded);
1195 + assert!(
1196 + c.detail
1197 + .as_deref()
1198 + .unwrap()
1199 + .contains("lookup failed: SERVFAIL")
1200 + );
1201 + }
1202 +
1203 + #[test]
1204 + fn all_dns_records_matching_stays_ok_and_counts_them() {
1205 + let mut t = healthy("mnw");
1206 + t.dns = Some(DnsView {
1207 + records: vec![DnsRecordView {
1208 + name: "makenot.work".into(),
1209 + record_type: "A".into(),
1210 + matches: true,
1211 + error: None,
1212 + }],
1213 + checked_at: Some(checked_at()),
1214 + });
1215 + let p = payload(&[t], now());
1216 + let n = node(&p, "target:mnw");
1217 + assert_eq!(n.status, Status::Ok);
1218 + // Singular, not "1 records match".
1219 + assert_eq!(
1220 + condition(n, "dns").detail.as_deref(),
1221 + Some("1 record match")
1222 + );
1223 + }
1224 +
1225 + #[test]
1226 + fn a_cors_misconfiguration_degrades_the_target() {
1227 + let mut t = healthy("mnw");
1228 + t.cors = Some(CorsView {
1229 + checks: vec![CorsCheckView {
1230 + url: "https://makenot.work/api".into(),
1231 + origin: "https://app.makenot.work".into(),
1232 + passes: false,
1233 + error: None,
1234 + }],
1235 + checked_at: Some(checked_at()),
1236 + });
1237 + let p = payload(&[t], now());
1238 + let n = node(&p, "target:mnw");
1239 + assert_eq!(n.status, Status::Degraded);
1240 + assert!(
1241 + condition(n, "cors")
1242 + .detail
1243 + .as_deref()
1244 + .unwrap()
1245 + .contains("does not allow https://app.makenot.work")
1246 + );
1247 + }
1248 +
1249 + #[test]
1250 + fn passing_cors_preflights_stay_ok() {
1251 + let mut t = healthy("mnw");
1252 + t.cors = Some(CorsView {
1253 + checks: vec![CorsCheckView {
1254 + url: "https://makenot.work/api".into(),
1255 + origin: "https://app.makenot.work".into(),
1256 + passes: true,
1257 + error: None,
1258 + }],
1259 + checked_at: Some(checked_at()),
1260 + });
1261 + let p = payload(&[t], now());
1262 + assert_eq!(node(&p, "target:mnw").status, Status::Ok);
1263 + }
1264 +
1265 + #[test]
1266 + fn the_new_conditions_do_not_disturb_a_target_that_has_none_of_them() {
1267 + // A target without test/dns/cors config emits none of the three, exactly
1268 + // as before — the additive property the shared contract exists to give.
1269 + let p = payload(&[healthy("mnw")], now());
1270 + let n = node(&p, "target:mnw");
1271 + assert!(n.conditions.iter().all(|c| c.condition_type != "tests"));
1272 + assert!(n.conditions.iter().all(|c| c.condition_type != "dns"));
1273 + assert!(n.conditions.iter().all(|c| c.condition_type != "cors"));
1274 + assert_eq!(p.validate(), Ok(()));
1275 + }
1276 +
870 1277 #[test]
871 1278 fn render_is_a_pure_function_of_state_and_clock() {
872 1279 let a = payload(&[healthy("mnw")], now());