/** * The control plane's health check, run by celilo rather than by a hook. * * ## Why this is not a hook * * It was `modules/celilo-mgmt/scripts/health-check.ts` until celilo#1225, and * it failed under the hook jail in the way that is worst: **it reported a * healthy fleet's database missing.** The hook asked `existsSync(db_path)`, * the jail does not bind celilo's data directory (deliberately — that is where * `master.key` and `celilo.db` live), so the answer was `false` and the deploy * failed on a file that was sitting right there. Its other two checks shelled * out to the CLI, which a jailed hook cannot reach at all. * * Every one of those was celilo inspecting itself. Deleting `on_install` for * that reason and leaving this behind would have fixed one hook and left the * deploy failing on the next. * * ## What replaced it is stronger, not merely relocated * * The hook asked two questions: does a file exist, and is this hostname in * `celilo machine list`. `runFleetChecks` is what celilo already runs for * `celilo system doctor`, and it asks eight, including the four-part dispatcher * probe whose whole point is that "a process is up" reports green on a * dispatcher that is unsupervised, on stale code, and delivering nothing. * * So the module gets a better check by having none of its own. That is the * outcome to keep in mind before anyone adds a hook back: a health_check hook * here can only re-ask, worse, questions celilo can answer directly. */ import type { DbClient } from '../db/client'; import { withCeliloBusAsync } from './control-plane-bootstrap'; import { type FleetFinding, runFleetChecks } from './fleet-checks'; import type { HealthCheckItem } from './health-runner'; /** `FleetFinding`'s three statuses in `HealthCheckItem`'s vocabulary. */ const STATUS: Record = { ok: 'pass', warn: 'warn', fail: 'fail', }; /** * One finding as a health item. * * `detail` and `remediation` are folded into `details` rather than dropped: * the health path is what runs unattended every fifteen minutes, so the line * telling an operator what to DO has to survive the mapping. */ export function findingAsHealthCheck(finding: FleetFinding): HealthCheckItem { const details = [ ...finding.detail, ...(finding.remediation ? [`Fix: ${finding.remediation}`] : []), ]; return { name: finding.id, status: STATUS[finding.status], message: finding.summary, ...(details.length > 0 ? { details: details.join('\n') } : {}), }; } /** Run celilo's own fleet checks and report them as the control plane's health. */ export async function controlPlaneHealthChecks(db: DbClient): Promise { const findings = await withCeliloBusAsync((bus) => runFleetChecks(bus, db)); return findings.map(findingAsHealthCheck); }