Skip to the content

Capsid menu

app/lib/health/verdicts.mjs

401 lines and 37 functions in DrDustinEdwards/dustinedwards-info, at commit 1590ba4 on 2026-10-24. This file on GitHub.

Functions

The functions of verdicts.mjs
FunctionLinesComplexityCoveredCRAPMutation
setDrift10 to 17117%OkCRAP 1.6Critical0%
<anonymous:14>14 to 1410%OkCRAP 2Critical0%
<anonymous:15>15 to 1510%OkCRAP 2Critical0%
askDriftVerdict25 to 412100%OkCRAP 2Ok100%
mediaDriftVerdict50 to 672100%OkCRAP 2Ok100%
mediaBackupDriftVerdict76 to 1086100%WatchCRAP 6Ok81.8%
contentDriftCompare126 to 1471100%OkCRAP 1Watch62.5%
<anonymous:127>127 to 1271100%OkCRAP 1Ok100%
<anonymous:128>128 to 1281100%OkCRAP 1Ok100%
<anonymous:131>131 to 1342100%OkCRAP 2Ok100%
<anonymous:135>135 to 1351100%OkCRAP 1Ok100%
<anonymous:138>138 to 1381100%OkCRAP 1Ok100%
<anonymous:139>139 to 1391100%OkCRAP 1Ok100%
<anonymous:142>142 to 1421100%OkCRAP 1Ok100%
<anonymous:143>143 to 1431100%OkCRAP 1Ok100%
contentDriftVerdict156 to 1581100%OkCRAP 1Ok80%
procedureDriftVerdict168 to 1741100%OkCRAP 1Ok100%
dictionaryDriftVerdict183 to 1851100%OkCRAP 1Ok100%
pageDriftVerdict194 to 1961100%OkCRAP 1Ok100%
cvDriftVerdict205 to 2071100%OkCRAP 1Ok100%
cvPdfDriftVerdict217 to 2324100%OkCRAP 4Ok95.5%
publicationDriftVerdict241 to 2431100%OkCRAP 1Ok100%
rosterDriftVerdict252 to 2541100%OkCRAP 1Ok100%
phageDriftVerdict263 to 2651100%OkCRAP 1Ok100%
registryDriftVerdict274 to 2761100%OkCRAP 1Watch60%
llmsDriftVerdict285 to 2871100%OkCRAP 1Ok80%
sourceDriftVerdict295 to 3122100%OkCRAP 2Ok100%
ftsEqualityVerdict321 to 3719100%WatchCRAP 9Watch66.7%
<anonymous:324>324 to 3242100%OkCRAP 2Ok88.9%
withTimeout385 to 4101100%OkCRAP 1Ok100%
<anonymous:386>386 to 4091100%OkCRAP 1Ok100%
<anonymous:388>388 to 3921100%OkCRAP 1Ok100%
<anonymous:397>397 to 4001100%OkCRAP 1Watch66.7%
<anonymous:401>401 to 4071100%OkCRAP 1Ok80%
publicHealthBody419 to 4331100%OkCRAP 1Ok100%
<anonymous:421>421 to 4211100%OkCRAP 1Ok100%
<anonymous:422>422 to 4313100%OkCRAP 3Ok100%

Imports

It imports app/lib/error-message.mjs. Imported by app/lib/health/checks.server.ts, app/lib/media/rebuild.server.ts, app/lib/operator/sync-tools.server.ts, app/lib/search/ask.server.ts, app/routes/api.health.ts.

Source

import { errorMessage } from "../error-message.mjs";

/**
 * A derived index against its source: the sizes, what it lacks and what it holds beyond the source,
 * each sorted. Kept apart so a missing key and an extra one cannot cancel out in a count.
 *
 * @param {Set<string>} expected
 * @param {Set<string>} present
 */
export function setDrift(expected, present) {
  return {
    expected: expected.size,
    present: present.size,
    missing: [...expected].filter((k) => !present.has(k)).sort(),
    extra: [...present].filter((k) => !expected.has(k)).sort(),
  };
}

/**
 * Both directions summed: a missing record and a stale one are each a wrong answer. Matches the admin badge.
 *
 * @param {{ expected: number, present: number, missing: string[], stale: string[] }} status
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function askDriftVerdict(status) {
  const drift = status.missing.length + status.stale.length;
  if (drift === 0) {
    return {
      ok: true,
      detail: `Ask index agrees with D1: ${status.expected} expected, ${status.present} present.`,
    };
  }
  return {
    ok: false,
    detail:
      `Ask index drift ${drift}: ${status.missing.length} missing, ` +
      `${status.stale.length} stale, ${status.expected} expected, ` +
      `${status.present} present. Repair with sync-ask on /admin/posts.`,
    counts: { expected: status.expected, present: status.present },
  };
}

/**
 * Both directions summed, since they can cancel in a count comparison while both are true. Here R2 is
 * the truth and D1 the projection; media-backup-drift is the other way round.
 *
 * @param {{ expected: number, present: number, missing: string[], extra: string[] }} status
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function mediaDriftVerdict(status) {
  const drift = status.missing.length + status.extra.length;
  if (drift === 0) {
    return {
      ok: true,
      detail: `Media index agrees with R2 and public/: ${status.expected} expected, ${status.present} present.`,
    };
  }
  return {
    ok: false,
    detail:
      `Media index drift ${drift}: ${status.missing.length} missing, ` +
      `${status.extra.length} extra, ${status.expected} expected, ` +
      `${status.present} present. Repair with sync_media on the operator API, ` +
      `or the rebuild action on /admin/media.`,
    counts: { expected: status.expected, present: status.present },
  };
}

/**
 * The counts travel so zero missing cannot mean zero examined. Mismatched is counted apart from missing:
 * a wrong copy, not an absent one, means something rewrote the mirror.
 *
 * @param {{ objects: number, twins: number, missing: string[], mismatched: string[] }} status
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function mediaBackupDriftVerdict(status) {
  const missing = status.missing.length;
  const mismatched = status.mismatched.length;

  if (status.objects === 0) {
    return {
      ok: true,
      detail:
        "MEDIA bucket holds no objects, so the mirror has nothing to hold either. " +
        "0 objects, 0 twins, 0 missing: examined nothing rather than verified anything.",
    };
  }

  if (missing === 0 && mismatched === 0) {
    return {
      ok: true,
      detail:
        `Every MEDIA object has a byte-identical twin: ${status.objects} objects, ` +
        `${status.twins} twins, 0 missing.`,
    };
  }

  return {
    ok: false,
    detail:
      `Media backup drift: ${status.objects} objects, ${status.twins} twins, ` +
      `${missing} missing, ${mismatched} mismatched. ` +
      `First missing: ${status.missing[0] ?? "none"}. ` +
      `First mismatched: ${status.mismatched[0] ?? "none"}. ` +
      `Repair with backup_media on the operator API, which only ever copies.`,
    counts: { expected: status.objects, present: status.twins },
  };
}

/**
 * Shared with the sync_posts repair, so what the check calls drift and what the repair repairs agree.
 *
 *   changed  a slug present on both sides whose blob sha differs: the file
 *            moved and the row has not followed (or a row's provenance was
 *            corrupted, which repairs identically).
 *   unrowed  a file with no row: a post committed from a clone that no sync
 *            has landed yet, or a lost row.
 *   unfiled  a row with no file: a post deleted from the repository whose
 *            rows outlived it.
 *
 * @param {Array<{ slug: string, sha: string }>} files from the Contents
 *   directory listing, blob sha per markdown file
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ changed: string[], unrowed: string[], unfiled: string[] }}
 */
export function contentDriftCompare(files, rows) {
  const rowBySlug = new Map(rows.map((r) => [r.slug, r]));
  const fileSlugs = new Set(files.map((f) => f.slug));

  const changed = files
    .filter((f) => {
      const row = rowBySlug.get(f.slug);
      return row !== undefined && row.source_blob_sha !== f.sha;
    })
    .map((f) => f.slug)
    .sort();
  const unrowed = files
    .filter((f) => !rowBySlug.has(f.slug))
    .map((f) => f.slug)
    .sort();
  const unfiled = rows
    .filter((r) => !fileSlugs.has(r.slug))
    .map((r) => r.slug)
    .sort();

  return { changed, unrowed, unfiled };
}

/**
 * present counts rows in full agreement, so equal totals with one sha changed still read as drift.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function contentDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, { label: "Content", noun: "post", tool: "sync_posts" });
}

/**
 * The same comparison for the procedure files and the procedures table, so what counts as drift is one
 * rule for both.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function procedureDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, {
    label: "Procedure",
    noun: "procedure",
    tool: "sync_procedures",
  });
}

/**
 * The same comparison for the dictionary entry files and the dictionary_entries table.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function dictionaryDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, { label: "Dictionary", noun: "dictionary entry", tool: "sync_dictionary" });
}

/**
 * The same comparison for the page files and the pages table.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function pageDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, { label: "Page", noun: "page", tool: "sync_pages" });
}

/**
 * The same comparison for the CV files and the cv table.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function cvDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, { label: "CV", noun: "CV", tool: "sync_cv" });
}

/**
 * The CV's PDF against the CV D1 holds now: the stored object's fingerprint (custom metadata) against the
 * fingerprint of the data. `stored` is null when there is no object and "" when there is one that carries no
 * fingerprint (neither is current). Derived store against its source, as the others, repaired by sync_cv_pdf.
 *
 * @param {{ stored: string | null, expected: string }} sides
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function cvPdfDriftVerdict({ stored, expected }) {
  if (stored === expected) {
    return { ok: true, detail: `The stored CV PDF was drawn from the CV data D1 holds now (CV data ${expected}).` };
  }
  const what =
    stored === null
      ? "there is no stored CV PDF"
      : stored === ""
        ? "the stored CV PDF carries no fingerprint"
        : `the stored CV PDF was drawn from CV data ${stored}`;
  return {
    ok: false,
    detail: `CV PDF drift 1: ${what}, and D1's CV is CV data ${expected}. Repair with sync_cv_pdf.`,
    counts: { expected: 1, present: 0 },
  };
}

/**
 * The same comparison for the publication files and the publications table.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function publicationDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, { label: "Publication", noun: "publication", tool: "sync_publications" });
}

/**
 * The same comparison for the roster files and the roster table.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function rosterDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, { label: "Roster", noun: "cohort", tool: "sync_roster" });
}

/**
 * The same comparison for the phage files and the phages table.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function phageDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, { label: "Phage", noun: "phage", tool: "sync_phages" });
}

/**
 * The same comparison for the registry files and the registry table. An item is read by its address, `<kind>/<id>`.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function registryDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, { label: "Registry", noun: "registry item", tool: "sync_registry" });
}

/**
 * The same comparison for content/llms.txt and the settings row /llms.txt is served from: one file, one row.
 *
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function llmsDriftVerdict(files, rows) {
  return sourceDriftVerdict(files, rows, { label: "llms.txt", noun: "llms.txt", tool: "sync_llms" });
}

/**
 * @param {Array<{ slug: string, sha: string }>} files
 * @param {Array<{ slug: string, source_blob_sha: string | null }>} rows
 * @param {{ label: string, noun: string, tool: string }} words
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
function sourceDriftVerdict(files, rows, { label, noun, tool }) {
  const { changed, unrowed, unfiled } = contentDriftCompare(files, rows);
  const drift = changed.length + unrowed.length + unfiled.length;
  if (drift === 0) {
    return {
      ok: true,
      detail: `D1 agrees with the repository: ${files.length} ${noun} file(s), every blob sha matched by its row.`,
    };
  }
  return {
    ok: false,
    detail:
      `${label} drift ${drift}: ${changed.length} sha-changed, ${unrowed.length} ` +
      `file(s) with no row, ${unfiled.length} row(s) with no file, against ` +
      `${files.length} ${noun} file(s). Repair with ${tool} on the operator API.`,
    counts: { expected: files.length, present: files.length - changed.length - unrowed.length },
  };
}

/**
 * Counted on the _docsize shadows: COUNT(*) on an external-content fts5 table reads through to its
 * content table and never fails. A count that cannot be read FAILS rather than comparing undefined.
 *
 * @param {Record<string, unknown>} counts
 * @returns {{ ok: boolean, detail: string, counts?: { expected: number, present: number } }}
 */
export function ftsEqualityVerdict(counts) {
  const NAMES = ["posts", "postsFts", "docs", "identity", "prose"];

  const unreadable = NAMES.filter((n) => !Number.isInteger(counts[n]) || Number(counts[n]) < 0);
  if (unreadable.length > 0) {
    return {
      ok: false,
      detail:
        `FTS counts unreadable: ${unreadable.join(", ")} ` +
        `${unreadable.length === 1 ? "is" : "are"} missing or not a whole number. ` +
        `A count that cannot be read is not a passing check.`,
    };
  }

  const posts = Number(counts.posts);
  const postsFts = Number(counts.postsFts);
  const docs = Number(counts.docs);
  const identity = Number(counts.identity);
  const prose = Number(counts.prose);

  const broken = [];
  if (posts !== postsFts) broken.push(`posts=${posts} but posts_fts_docsize=${postsFts}`);
  if (identity !== docs || prose !== docs) {
    broken.push(
      `search_docs=${docs} but search_identity_docsize=${identity} and ` +
        `search_prose_docsize=${prose}`,
    );
  }

  if (broken.length === 0) {
    return {
      ok: true,
      detail:
        `FTS indexes agree: posts=${posts}=posts_fts_docsize, ` +
        `search_docs=${docs}=identity=prose.`,
    };
  }

  return {
    ok: false,
    detail:
      `FTS index drift: ${broken.join("; ")}. ` +
      `Rebuild with INSERT INTO <index>(<index>) VALUES('rebuild'). ` +
      `Do NOT DELETE FROM either index; that corrupts it further.`,
    // The first disagreeing pair only; the full five are in detail, which stays off the wire.
    counts:
      posts !== postsFts
        ? { expected: posts, present: postsFts }
        : { expected: docs, present: identity !== docs ? identity : prose },
  };
}

/** Measured: ask-index-drift maxed at 2332 ms over 12 samples, so 3 s clears it without false alarms. */
export const CHECK_TIMEOUT_MS = 3000;

/**
 * A timeout is a failed check. It does not cancel the work: only the RESPONSE is bounded, and abandoned
 * work keeps accumulating under sustained timeouts.
 *
 * @param {Promise<{ ok: boolean, detail: string, counts?: { expected: number, present: number } }>} promise
 * @param {number} ms
 * @param {string} name
 * @returns {Promise<{ ok: boolean, detail: string, counts?: { expected: number, present: number } }>}
 */
export function withTimeout(promise, ms, name) {
  return new Promise((resolve) => {
    const timer = setTimeout(
      () =>
        resolve({
          ok: false,
          detail: `check ${name} did not answer within ${ms}ms and is reported failed`,
        }),
      ms,
    );

    promise.then(
      (value) => {
        clearTimeout(timer);
        resolve(value);
      },
      (error) => {
        clearTimeout(timer);
        resolve({
          ok: false,
          detail: `check threw: ${errorMessage(error)}`,
        });
      },
    );
  });
}

/**
 * Detail strings never reach this unauthenticated wire: they carry an R2 object key and shadow table
 * names. A FAILING check carries its two counts, so a flap can be sized; a passing one, name and ok.
 *
 * @param {{ checks: Array<{ name: string, ok: boolean, counts?: { expected: number, present: number } }> }} run
 * @returns {{ ok: boolean, checks: Array<{ name: string, ok: boolean, expected?: number, present?: number }> }}
 */
export function publicHealthBody(run) {
  return {
    ok: run.checks.every((c) => c.ok),
    checks: run.checks.map((c) => {
      // Field by field, not spread, so a field added to HealthCheck later cannot leak onto the wire.
      if (c.ok || !c.counts) return { name: c.name, ok: c.ok };
      return {
        name: c.name,
        ok: c.ok,
        expected: c.counts.expected,
        present: c.counts.present,
      };
    }),
  };
}