Public: anyone can read this; the crew can change it.

Vitals raises one need per domain whose last drill is red or eight days old, and the need says the exact command that runs the drill #2196

Merged opened this

Check · Review · Land · Live done — Landed and live in production.

Merged

scout/forge:scout/2175-vitals-raises-one-need-per-domain-whose → main 1 commit fork

  • Merged. scout/2175-vitals-raises-one-need-per-domain-whose was merged into main as a635a9b.

Review

Sign in to review this change.
9 files changed +206 −3
src/actions/health.ts +1
@@ -30,6 +30,7 @@ export function readHealth(
3030 dataDir: app.config.dataDir,
3131 envs,
3232 signals: app.signals,
33 forgeUrl: app.config.url ?? `http://localhost:${app.config.port}`,
3334 });
3435 return { ...v, ...forge, ok: forge.ok && v.ok };
3536 }
src/core/domain_drills.ts +13 −1
@@ -2,7 +2,7 @@
22
33 import type { Database } from "bun:sqlite";
44 import { id } from "../kernel/id.ts";
5 import type { Observed, Verdict } from "../kernel/domain_drill.ts";
5 import type { NewestDrill, Observed, Verdict } from "../kernel/domain_drill.ts";
66
77 export interface DrillRecord extends Verdict {
88 readonly id: string;
@@ -43,3 +43,15 @@ export function lastDrill(db: Database, domain: string): LastDrill | null {
4343 .get(domain);
4444 return r === null ? null : { green: r.green === 1, why: r.why, backSeconds: r.back_seconds, at: r.at };
4545 }
46
47 /** The newest drill of every domain, in one query. */
48 export function newestDrills(db: Database): NewestDrill[] {
49 return db
50 .query<{ domain: string; green: number; why: string; at: string }, []>(
51 `SELECT d.domain, d.green, d.why, d.at FROM domain_drill d
52 WHERE d.rowid = (SELECT x.rowid FROM domain_drill x WHERE x.domain = d.domain
53 ORDER BY x.at DESC, x.rowid DESC LIMIT 1)`,
54 )
55 .all()
56 .map((r) => ({ domain: r.domain, green: r.green === 1, why: r.why, at: r.at }));
57 }
src/kernel/domain_drill.ts +86
@@ -37,3 +37,89 @@ export function drillVerdict(
3737 }
3838 return { green: true, why: `every phrase seen; back in ${backSeconds}s of ${limit}s` };
3939 }
40
41 /** A domain drill with no green in this long, or whose newest is red, is a need. */
42 export const DOMAIN_DRILL_STALE_MS = 8 * 24 * 3_600_000;
43
44 /** The newest drill kept for one domain. */
45 export interface NewestDrill {
46 readonly domain: string;
47 readonly green: boolean;
48 readonly why: string;
49 readonly at: string;
50 }
51
52 export interface DomainDrillNeed {
53 /** `drill:<domain-key>`, or `drill:never` for every domain not yet drilled. */
54 readonly cause: string;
55 readonly sentence: string;
56 readonly since: string;
57 }
58
59 /** A word safe in a shell as it is; anything else is single-quoted. */
60 function shellWord(s: string): string {
61 return /^[A-Za-z0-9_.:\/@%+=,-]+$/.test(s) ? s : `'${s.replace(/'/g, `'\\''`)}'`;
62 }
63
64 /** The exact command that runs one domain's drill against this forge. */
65 export function drillCommand(key: string, forgeUrl: string): string {
66 return `bun scripts/drills/run.ts ${shellWord(key)} --forge ${shellWord(forgeUrl)}`;
67 }
68
69 const WHY_MAX = 160;
70
71 /** The failing phrase: the quoted words of the verdict, else the verdict itself. */
72 function failing(why: string): string {
73 const quoted = /"([^"]+)"/.exec(why);
74 const text = (quoted ? quoted[1]! : why).replace(/[\r\n]+/g, " ");
75 return text.length > WHY_MAX ? `${text.slice(0, WHY_MAX)}…` : text;
76 }
77
78 /**
79 * One need per drillable domain whose newest drill is red, or whose newest
80 * green is over eight days old; and one for all the drillable domains never
81 * drilled, once the forge itself (`forgeSince`) is over eight days old. A
82 * newer green drill is the newest, so it clears its domain.
83 */
84 export function domainDrillNeeds(
85 newest: readonly NewestDrill[],
86 now: number,
87 forgeSince: string | undefined,
88 forgeUrl: string,
89 domains: readonly Domain[],
90 ): DomainDrillNeed[] {
91 const byDomain = new Map(newest.map((d) => [d.domain, d]));
92 const days = Math.floor(DOMAIN_DRILL_STALE_MS / 86_400_000);
93 const needs: DomainDrillNeed[] = [];
94 const never: Domain[] = [];
95 for (const d of domains) {
96 if (d.drill.name === null) continue;
97 const last = byDomain.get(d.key);
98 if (last === undefined) {
99 never.push(d);
100 continue;
101 }
102 const at = Date.parse(last.at);
103 if (!last.green) {
104 needs.push({
105 cause: `drill:${d.key}`,
106 sentence: `The ${d.key} drill failed: ${failing(last.why)}. Run it again: ${drillCommand(d.key, forgeUrl)}`,
107 since: last.at,
108 });
109 } else if (now - at > DOMAIN_DRILL_STALE_MS) {
110 needs.push({
111 cause: `drill:${d.key}`,
112 sentence: `No ${d.key} drill in ${days} days; the last one passed on ${last.at.slice(0, 10)}. Run it: ${drillCommand(d.key, forgeUrl)}`,
113 since: new Date(at + DOMAIN_DRILL_STALE_MS).toISOString(),
114 });
115 }
116 }
117 const began = Date.parse(forgeSince ?? "");
118 if (never.length > 0 && now - began > DOMAIN_DRILL_STALE_MS)
119 needs.push({
120 cause: "drill:never",
121 sentence: `No drill has ever run for ${never.map((d) => d.key).join(", ")}. Run each: ${never.map((d) => drillCommand(d.key, forgeUrl)).join("; ")}`,
122 since: new Date(began + DOMAIN_DRILL_STALE_MS).toISOString(),
123 });
124 return needs;
125 }
src/kernel/vitals.ts +15
@@ -23,6 +23,9 @@ import { STALE_AFTER_MS } from "../runs/lease.ts";
2323 import { BUDGETS } from "./budgets.ts";
2424 import { lastMirror, mirrorNeed } from "../snapshot/mirror.ts";
2525 import { drillNeed, firstSeen as drillFirstSeen, lastVerdict } from "../snapshot/verdict.ts";
26 import { newestDrills } from "../core/domain_drills.ts";
27 import { DOMAINS } from "./domains.ts";
28 import { domainDrillNeeds } from "./domain_drill.ts";
2629 import { heldChecks, redSentence } from "../checks/red_main.ts";
2730
2831 /** Free disk under this share of the volume is something to say. */
@@ -95,6 +98,8 @@ export interface VitalsInput {
9598 /** Every environment the runtime holds, when one is up. */
9699 readonly envs?: readonly EnvCount[];
97100 readonly now?: number;
101 /** The address people reach this forge at, named in the drill commands. */
102 readonly forgeUrl?: string;
98103 /** The free-disk share below which the disk is a need; `DISK_FREE_MIN` unless a test says. */
99104 readonly diskFreeMin?: number;
100105 /** One forge's own signals (`App.signals`). */
@@ -319,6 +324,16 @@ export function vitals(input: VitalsInput): Vitals {
319324 if (owed !== undefined)
320325 need("drill", owed.sentence, owed.since, "/settings/backups");
321326
327 // A domain whose drill is red or eight days old is a card with the command that runs it.
328 for (const owed of domainDrillNeeds(
329 newestDrills(input.db),
330 now,
331 drillFirstSeen(input.dataDir),
332 input.forgeUrl ?? "http://localhost",
333 DOMAINS,
334 ))
335 need(owed.cause, owed.sentence, owed.since, "/");
336
322337 // A snapshot that did not land off the box, or a mirror a day behind, is a card until it does.
323338 const behind = mirrorNeed(lastMirror(input.dataDir), now);
324339 if (behind !== undefined) need("mirror", behind.sentence, behind.since, "/settings/backups");
src/main.ts +1
@@ -446,6 +446,7 @@ const tickAsks = async (): Promise<void> => {
446446 db: app.db,
447447 dataDir: app.config.dataDir,
448448 signals: app.signals,
449 forgeUrl,
449450 }).degraded,
450451 Date.now(),
451452 app.log,
src/web/pages/board.ts +1
@@ -334,6 +334,7 @@ function forgeStrip(ctx: Ctx): Html | null {
334334 db: app.db,
335335 dataDir: app.config.dataDir,
336336 signals: app.signals,
337 forgeUrl: app.config.url ?? `http://localhost:${app.config.port}`,
337338 envs: runtime
338339 ? listApps(app.db).flatMap((record) => runtime.status(record))
339340 : [],
src/web/studio/asks.ts +1
@@ -1346,6 +1346,7 @@ export function forgeNeedsFor(ctx: Ctx, user: User | undefined): Degraded[] {
13461346 db: ctx.app.db,
13471347 dataDir: ctx.app.config.dataDir,
13481348 signals: ctx.app.signals,
1349 forgeUrl: ctx.app.config.url ?? `http://localhost:${ctx.app.config.port}`,
13491350 envs,
13501351 }).degraded;
13511352 }
test/vitals-domain-drill.test.ts added +86
@@ -0,0 +1,86 @@
1 import { expect, test } from "bun:test";
2 import { mkdtempSync } from "node:fs";
3 import { tmpdir } from "node:os";
4 import { join } from "node:path";
5 import { drillCommand, domainDrillNeeds, type NewestDrill } from "../src/kernel/domain_drill.ts";
6 import { DOMAINS } from "../src/kernel/domains.ts";
7
8 const NOW = Date.parse("2026-10-16T12:00:00Z");
9 const DAY = 86_400_000;
10 const URL_ = "https://forge.example";
11 const iso = (ms: number) => new Date(ms).toISOString();
12 const drilled = DOMAINS.filter((d) => d.drill.name !== null).map((d) => d.key);
13 const green = (domain: string, at: number): NewestDrill => ({ domain, green: true, why: "ok", at: iso(at) });
14 const needs = (newest: NewestDrill[], since = iso(NOW - 30 * DAY)) =>
15 domainDrillNeeds(newest, NOW, since, URL_, DOMAINS);
16
17 test("a red drill raises its domain's need, naming the phrase and the command", () => {
18 const rest = drilled.filter((k) => k !== "snapshots").map((k) => green(k, NOW - DAY));
19 const red: NewestDrill = { domain: "snapshots", green: false, why: 'the forge never said "The restore drill failed at"', at: iso(NOW - DAY) };
20 const n = needs([...rest, red]);
21 expect(n.map((x) => x.cause)).toEqual(["drill:snapshots"]);
22 expect(n[0]!.sentence).toContain('The restore drill failed at');
23 expect(n[0]!.sentence).toContain(`bun scripts/drills/run.ts snapshots --forge ${URL_}`);
24 });
25
26 test("a newer green drill clears the need", () => {
27 const all = drilled.map((k) => green(k, NOW - DAY));
28 expect(needs(all)).toEqual([]);
29 });
30
31 test("eight days is the edge: exactly eight is quiet, a millisecond over speaks", () => {
32 const rest = drilled.slice(1).map((k) => green(k, NOW - DAY));
33 const key = drilled[0]!;
34 expect(needs([green(key, NOW - 8 * DAY), ...rest])).toEqual([]);
35 const n = needs([green(key, NOW - 8 * DAY - 1), ...rest]);
36 expect(n.map((x) => x.cause)).toEqual([`drill:${key}`]);
37 });
38
39 test("domains never drilled are one need, once the forge is over eight days old", () => {
40 const n = needs([]);
41 expect(n.length).toBe(1);
42 expect(n[0]!.cause).toBe("drill:never");
43 for (const k of drilled) expect(n[0]!.sentence).toContain(drillCommand(k, URL_));
44 expect(needs([], iso(NOW - 8 * DAY))).toEqual([]);
45 expect(needs([], iso(NOW - 8 * DAY - 1)).length).toBe(1);
46 expect(domainDrillNeeds([], NOW, undefined, URL_, DOMAINS)).toEqual([]);
47 });
48
49 test("domains that cannot be drilled are never asked for", () => {
50 expect(needs([]).map((x) => x.sentence).join(" ")).not.toContain("scripts/drills/run.ts edge");
51 });
52
53 test("the command is the real key and URL, shell-escaped", () => {
54 expect(drillCommand("compute", "https://f.example:8443")).toBe(
55 "bun scripts/drills/run.ts compute --forge https://f.example:8443",
56 );
57 expect(drillCommand("a b", "http://x/?q=1&r='2'")).toBe(
58 `bun scripts/drills/run.ts 'a b' --forge 'http://x/?q=1&r='\\''2'\\'''`,
59 );
60 expect(drillCommand("x; rm -rf /", URL_)).toContain("'x; rm -rf /'");
61 });
62
63 test("a verdict that is not a quoted phrase is quoted whole, and bounded", () => {
64 const rest = drilled.filter((k) => k !== "runner").map((k) => green(k, NOW - DAY));
65 const why = "back after 900s, " + "x".repeat(500);
66 const n = needs([...rest, { domain: "runner", green: false, why, at: iso(NOW) }]);
67 expect(n[0]!.sentence).toContain("back after 900s");
68 expect(n[0]!.sentence.length).toBeLessThan(400);
69 });
70
71 test("vitals raises them from the newest drill of each domain, in one query", async () => {
72 const { vitals } = await import("../src/kernel/vitals.ts");
73 const { newestDrills, recordDrill } = await import("../src/core/domain_drills.ts");
74 const { createApp } = await import("../src/app.ts");
75 const dir = mkdtempSync(join(tmpdir(), "vdd-"));
76 const app = createApp({ dataDir: dir, port: 0 });
77 const seen = { health: "", board: "" };
78 recordDrill(app.db, "u", "compute", seen, 1, { green: false, why: 'the forge never said "x"' });
79 const sentence = () =>
80 vitals({ db: app.db, signals: app.signals, dataDir: dir, forgeUrl: URL_ }).needs.find((s) => s.includes("compute drill"));
81 expect(sentence()).toContain(`bun scripts/drills/run.ts compute --forge ${URL_}`);
82 app.db.query("UPDATE domain_drill SET at = '2020-01-01T00:00:00.000Z'").run();
83 recordDrill(app.db, "u", "compute", seen, 1, { green: true, why: "ok" });
84 expect(sentence()).toBeUndefined();
85 expect(newestDrills(app.db).length).toBe(1);
86 });
test/vitals.test.ts +2 −2
@@ -154,8 +154,8 @@ test("the number of queries does not grow with runs", () => {
154154 if (i % 2) app.db.query("UPDATE run SET state='running' WHERE id=?1").run(r.id);
155155 }
156156 expect(count()).toBe(before);
157 // The queue, the running runs, the runs killed twice for their budget, the lane's depth and finished checks, and the red mains holding pulls' checks.
158 expect(before).toBe(6);
157 // The queue, the running runs, the runs killed twice for their budget, the lane's depth and finished checks, and the red mains holding pulls' checks. and the newest drill of every domain.
158 expect(before).toBe(7);
159159 });
160160
161161 test("errors are counted, bounded, redacted, and a burst is said", async () => {

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs