/** * Running a built-in check for a monitor. * * Deliberately NOT `runAudit`. That runner computes all fourteen categories and * needs the whole world injected — proxmox connections, a registry client, * secret decryption, a terraform binary — which is the wrong cost and the wrong * failure surface for a check that runs every few minutes. A monitor owns one * category, so it calls that category directly. * * Only the categories the MVP schedules are wired. An unwired one fails loudly * rather than silently returning no findings, which the reconciler would read * as "nothing is wrong". */ import type { DbClient } from '../../db/client'; import { auditAbandonedOperations, loadAbandonedOperations } from '../audit/abandoned-operations'; import { loadBackupAuditInfo } from '../audit/backup-source'; import { auditBackups } from '../audit/backups'; import { auditDiskSpace } from '../audit/disk-space'; import { auditMachinesReachable } from '../audit/machines-reachable'; import { auditPublicDns } from '../audit/public-dns'; import { loadPublicDnsEvidence, loadPublicDnsRecords, savePublicDnsEvidence, } from '../audit/public-dns-source'; import type { DriftCategory, DriftFinding } from '../audit/types'; import { probeDiskUsage } from '../disk-probe'; import { probeMachines } from '../machine-probe'; import { createPublicDnsProbe, loadPublicDnsProbeSettings } from '../public-dns-probe'; /** Categories a monitor can currently schedule. */ export const SCHEDULABLE_BUILTIN_CHECKS: readonly DriftCategory[] = [ 'machines_reachable', 'backups', 'disk_space', 'abandoned_operations', 'public_dns', ]; export function isSchedulableBuiltin(category: string): category is DriftCategory { return (SCHEDULABLE_BUILTIN_CHECKS as readonly string[]).includes(category); } export async function runBuiltinCheckForMonitor( category: DriftCategory, db: DbClient, ): Promise { if (category === 'machines_reachable') { return auditMachinesReachable({ results: await probeMachines() }); } // Same shape as machines_reachable — one bounded SSH round trip per target — // but it differs on both ends: it MEASURES the local box rather than exempting // it, and it covers celilo-provisioned instances as well as the machine pool. // See disk-probe.ts for why the second one is load-bearing. if (category === 'disk_space') { return auditDiskSpace({ results: await probeDiskUsage(db) }); } // Local DB reads only — cheap enough to run on every sweep, which is // the whole reason this category is schedulable and most are not. if (category === 'backups') { return auditBackups({ modules: loadBackupAuditInfo(db) }); } if (category === 'abandoned_operations') { return auditAbandonedOperations({ records: loadAbandonedOperations(db) }); } // One DNS query per ledger name plus one HTTP echo, all bounded — cheap // enough for a scheduled check, and the only one of these that looks at the // fleet from OUTSIDE. It is the only place `undetermined` counters advance, // which is why the check has to be scheduled and not merely available to // `celilo system audit`: consecutive absence of evidence is a finding, and // nothing is consecutive if it only runs when an operator asks. if (category === 'public_dns') { return auditPublicDns({ records: loadPublicDnsRecords(db), probe: createPublicDnsProbe(loadPublicDnsProbeSettings(db)), evidence: loadPublicDnsEvidence(db), saveEvidence: (evidence) => savePublicDnsEvidence(db, evidence), }); } throw new Error( `Built-in check "${category}" is not schedulable yet. Schedulable: ${SCHEDULABLE_BUILTIN_CHECKS.join(', ')}.`, ); }