Files
openworker/deploy/reference/qualify-operations.mjs

332 lines
10 KiB
JavaScript

// Explicit local Grafana + isolated PG schema. Never touches a live app schema.
// Run after building harness/server-reference/examples/operations_probe.
import { spawn } from "node:child_process";
import { once } from "node:events";
import { readFile, writeFile } from "node:fs/promises";
import { join, resolve } from "node:path";
import { randomUUID } from "node:crypto";
import assert from "node:assert/strict";
import { load, compose, until, repo } from "./stack.mjs";
import { write as provision } from "./operations.mjs";
const root = resolve(process.argv[2] ?? ".ow-stack");
const m = await load(root);
if (!m.observability || !process.env.OW_PG_TEST_URL)
throw Error(
"Existing local observability state and isolated OW_PG_TEST_URL required",
);
const schema = `ow_ops_${randomUUID().replaceAll("-", "")}`;
const binary = join(repo, "harness/target/debug/examples/operations_probe");
const env = {
...process.env,
OW_OPS_SCHEMA: schema,
OW_OPS_ENDPOINT: `http://127.0.0.1:${m.ports.otlp}`,
};
const auth =
"Basic " +
Buffer.from(
"admin:" +
(await readFile(join(root, "private/grafana-password"), "utf8")).trim(),
).toString("base64");
const api = async (path, options = {}) => {
const r = await fetch(`http://127.0.0.1:${m.ports.grafana}${path}`, {
...options,
headers: { Authorization: auth, "Content-Type": "application/json" },
signal: AbortSignal.timeout(5000),
});
if (!r.ok) throw Error(`Grafana ${path.split("?")[0]} failed: ${r.status}`);
return r.json();
};
async function setDatasourceURL(url) {
const current = await api("/api/datasources/uid/prometheus");
return api("/api/datasources/uid/prometheus", {
method: "PUT",
body: JSON.stringify({ ...current, url }),
});
}
const query = async (expr) =>
(
await api(
"/api/datasources/proxy/uid/prometheus/api/v1/query?query=" +
encodeURIComponent(expr),
)
).data.result;
const children = new Set();
function start(mode) {
const e = { ...env };
const p = spawn(binary, [mode], {
env: e,
stdio: ["ignore", "pipe", "pipe"],
});
let output = "",
error = "";
p.stdout.on("data", (b) => {
if (output.length < 65536) output += b;
});
p.stderr.on("data", (b) => {
if (error.length < 65536) error += b;
});
p.done = once(p, "close").then(([code, signal]) => ({
code,
signal,
output,
error,
}));
children.add(p);
return p;
}
async function run(mode) {
const p = start(mode),
r = await p.done;
children.delete(p);
if (r.code !== 0) {
await writeFile(join(root, "logs", `${schema}-${mode}.log`), r.error, {
mode: 0o600,
});
throw Error(`Probe ${mode} failed; inspect private qualification log`);
}
return r.output;
}
async function stop(p, signal = "SIGINT") {
const kill = setTimeout(() => {
if (p.exitCode === null && p.signalCode === null) p.kill("SIGKILL");
}, 10000);
kill.unref();
if (p.exitCode === null && p.signalCode === null) p.kill(signal);
try {
await p.done;
} finally {
clearTimeout(kill);
}
children.delete(p);
}
const evidence = { schema, started: new Date().toISOString(), tests: [] };
const passed = (name) => {
evidence.tests.push(name);
console.log("PASS", name);
};
let plan,
seeded = false,
samplerA,
samplerB,
worker,
errors,
originalDatasource;
async function states() {
const r = await api("/api/prometheus/grafana/api/v1/rules");
return r.data.groups
.flatMap((g) => g.rules)
.filter((r) => r.name.startsWith(`${schema} · `));
}
async function expect(suffix, state) {
const title = plan.alerts.groups[0].rules.find((r) =>
r.uid.endsWith(`-${suffix}`),
).title;
await until(async () => {
const r = (await states()).find((r) => r.name === title);
return r?.state === state && r.health === "ok";
}, 90000);
}
async function value(expr, expected) {
await until(async () => {
const rows = await query(expr);
return rows.length === 1 && Number(rows[0].value[1]) === expected;
}, 45000);
}
try {
plan = await provision(root, {
deployment: schema,
expectedSlots: 2,
freshSeconds: 20,
queueSeconds: 3,
pendingSeconds: 10,
intervalSeconds: 10,
errorWindowSeconds: 30,
});
await compose(m, [
"up",
"-d",
"--wait",
"--wait-timeout",
"120",
"observability",
]);
await until(async () => (await api("/api/health")).database === "ok", 60000);
// Compose does not restart a healthy container when only mounted JSON changes.
// Remove only this reference's earlier qualification rules, never app rules.
const previous = await api("/api/v1/provisioning/alert-rules");
plan.alerts.deleteRules = previous
.filter(
(r) =>
/^ow_ops_[a-f0-9]{32}$/.test(r.labels?.deployment ?? "") &&
r.labels.deployment !== schema,
)
.map((r) => ({ orgId: 1, uid: r.uid }));
await writeFile(
join(root, "operations-alerts.json"),
JSON.stringify(plan.alerts, null, 2) + "\n",
{ mode: 0o600 },
);
await api("/api/admin/provisioning/alerting/reload", { method: "POST" });
await api("/api/admin/provisioning/dashboards/reload", { method: "POST" });
const dashboard = (await api(`/api/dashboards/uid/${plan.dashboard.uid}`))
.dashboard;
assert.equal(dashboard.panels.length, 14);
assert.equal(dashboard.title, plan.dashboard.title);
assert.equal(
dashboard.panels[0].targets[0].expr,
plan.dashboard.panels[0].targets[0].expr,
);
const loaded = await api("/api/v1/provisioning/alert-rules");
assert.equal(
loaded.filter((r) => r.labels?.deployment === schema).length,
11,
);
passed("11 local-only Grafana rules provisioned");
seeded = true;
await run("seed");
worker = start("worker");
samplerA = start("sample");
samplerB = start("sample");
const q = plan.queries;
await value(q.active, 1);
await value(q.queued, 1);
await value(q.approvals, 1);
await value(q.capacity, 2);
passed(
"real PG snapshot; two samplers do not double-count work; real idle worker advertises 2 slots",
);
await expect("queue", "firing");
await expect("stalled", "firing");
await expect("unknown", "firing");
passed(
"aged queue, stalled active run and recent uncertain transitions fire",
);
await run("clear");
await value(q.active, 0);
await value(q.approvals, 0);
await expect("queue", "inactive");
await expect("stalled", "inactive");
await expect("unknown", "inactive");
passed(
"durable state clear resolves work alerts; waiting approvals were not counted as stalls",
);
await stop(worker, "SIGKILL");
await expect("capacity", "firing");
worker = start("worker");
await expect("capacity", "inactive");
passed("real worker process crash and restart fires/clears capacity alert");
await run("break-sampler");
await expect("database", "firing");
await expect("state", "firing");
await expect("monitoring", "inactive");
await stop(samplerA);
await stop(samplerB);
samplerA = start("sample");
samplerB = start("sample");
await expect("database", "inactive");
await expect("state", "inactive");
passed(
"query timeout from a lock on only the qualification audit table fires observer/stale alerts; restart clears them",
);
errors = start("errors");
await expect("model", "firing");
await expect("tool", "firing");
await stop(errors);
await expect("model", "inactive");
await expect("tool", "inactive");
passed(
"synthetic model/tool error telemetry fires and quiet window clears alerts (no live model calls)",
);
await stop(samplerA, "SIGKILL");
await stop(samplerB, "SIGKILL");
await expect("monitoring", "firing");
await expect("state", "firing");
assert.equal((await query(q.queued)).length, 0);
samplerA = start("sample");
await expect("monitoring", "inactive");
await expect("state", "inactive");
passed(
"lost samplers produce missing-telemetry alert and unknown queue, then recover",
);
originalDatasource = await api("/api/datasources/uid/prometheus");
await setDatasourceURL("http://127.0.0.1:1");
await until(
async () => (await states()).some((r) => r.health === "error"),
60000,
);
await setDatasourceURL(originalDatasource.url);
originalDatasource = null;
await until(
async () => (await states()).every((r) => r.health === "ok"),
60000,
);
passed(
"Grafana query failure is an explicit error, not healthy; datasource restoration clears it",
);
const labels = await query(
`{__name__=~"openworker_server_.*|openworker_worker_.*"} and on(job,instance) target_info{openworker_deployment="${schema}"}`,
);
assert.ok(labels.length > 0);
assert.ok(!JSON.stringify(labels).includes("SECRET"));
assert.ok(
labels.every(
(r) =>
!Object.keys(r.metric).some((k) =>
/session|action|prompt|argument/.test(k),
),
),
);
passed(
"Prometheus operational samples carry no fixture content or per-session/action labels",
);
evidence.passed = true;
evidence.dashboard = `http://127.0.0.1:${m.ports.grafana}/d/${plan.dashboard.uid}`;
} finally {
if (originalDatasource)
await setDatasourceURL(originalDatasource.url).catch(() => {
evidence.datasourceRestoreRequired = true;
console.error("Datasource restore failed; manual recovery required");
});
await Promise.all([...children].map((p) => stop(p)));
if (seeded)
await run("cleanup").catch(() => {
evidence.cleanupRequired = schema;
console.error(
"Qualification schema cleanup failed; identity saved in evidence",
);
});
// Keep the evidence dashboard; pause this finished experiment's alerts. The
// next reference start can explicitly regenerate defaults for its deployment.
if (plan) {
for (const r of plan.alerts.groups[0].rules) r.isPaused = true;
await writeFile(
join(root, "operations-alerts.json"),
JSON.stringify(plan.alerts, null, 2) + "\n",
{ mode: 0o600 },
);
await api("/api/admin/provisioning/alerting/reload", {
method: "POST",
}).catch(() => {
evidence.alertPauseRequired = true;
console.error(
"Alert cleanup reload unavailable; saved paused provisioning for next startup",
);
});
}
evidence.finished = new Date().toISOString();
if (
evidence.cleanupRequired ||
evidence.datasourceRestoreRequired ||
evidence.alertPauseRequired
)
evidence.passed = false;
await writeFile(
join(root, "operations-qualification.json"),
JSON.stringify(evidence, null, 2) + "\n",
{ mode: 0o600 },
);
}
assert.equal(evidence.passed, true, "Qualification or cleanup incomplete");
console.log(JSON.stringify(evidence, null, 2));