fable-agent/src/fable5/rsi-reconcile.test.ts

89 lines
3.8 KiB
TypeScript

import * as fs from "node:fs";
import * as os from "node:os";
import * as path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { reconcileRsi, verifyCanaryProof } from "./rsi-reconcile.js";
const roots: string[] = [];
afterEach(() => {
for (const root of roots.splice(0)) fs.rmSync(root, { recursive: true, force: true });
});
function tmpFile(): string {
const root = fs.mkdtempSync(path.join(os.tmpdir(), "fable-rsi-reconcile-"));
roots.push(root);
return path.join(root, "receipt.json");
}
function runner(overrides: Record<string, { status: number; stdout: string; stderr?: string }> = {}) {
return (_command: string, args: string[]) => {
const script = args.at(-1) ?? "";
const hit = Object.entries(overrides).find(([needle]) => script.includes(needle))?.[1];
if (hit) return { status: hit.status, stdout: hit.stdout, stderr: hit.stderr ?? "" };
if (script.includes("skill_health.py|rsi-diagnosis")) return { status: 0, stdout: "0 */6 * * * docker cp /tmp/skill_health.py hermes:/tmp/skill_health.py\n", stderr: "" };
if (script.includes("test -f /tmp/skill_health.py")) return { status: 0, stdout: "present\n", stderr: "" };
if (script.includes("tail -160")) return { status: 0, stdout: "RSI: 65/65 (100%)\nAll healthy — ZTE idle\n", stderr: "" };
if (script.includes("systemctl is-active")) return { status: 0, stdout: "active\n", stderr: "" };
if (script.includes("/deploy")) return { status: 0, stdout: "present\n", stderr: "" };
return { status: 1, stdout: "", stderr: "unexpected" };
};
}
describe("rsi reconcile", () => {
it("marks healthy but autopatch unproven when all health checks pass", () => {
const receipt = reconcileRsi({ host: "example", runner: runner(), now: new Date("2026-06-18T00:00:00Z") });
expect(receipt.facts.cron_present).toBe(true);
expect(receipt.facts.latest_score).toBe("65/65 (100%)");
expect(receipt.decision).toBe("healthy_but_autopatch_unproven");
});
it("marks healthy when auto-patch evidence exists", () => {
const receipt = reconcileRsi({ host: "example", runner: runner({ "tail -160": { status: 0, stdout: "RSI: 65/65 (100%)\nauto-patching 1 degraded skills\nAll patched successfully\n" } }) });
expect(receipt.facts.auto_patch_proven).toBe(true);
expect(receipt.decision).toBe("healthy");
});
it("marks healthy when a fresh canary proof receipt exists", () => {
const proof = tmpFile();
fs.writeFileSync(proof, [
"rsi_canary: degraded",
"STATUS_BEFORE:1",
"auto-patching 1 degraded skills",
"All patched successfully",
"rsi_canary: recovered",
"STATUS_PATCH:0",
"STATUS_AFTER:0",
].join("\n"));
expect(verifyCanaryProof(proof)).toBe(true);
const receipt = reconcileRsi({ host: "example", canaryProof: proof, runner: runner() });
expect(receipt.facts.auto_patch_proven).toBe(true);
expect(receipt.facts.canary_proof).toBe(proof);
expect(receipt.decision).toBe("healthy");
});
it("keeps healthy when only cron naming drifts", () => {
const receipt = reconcileRsi({ host: "example", runner: runner({ "skill_health.py|rsi-diagnosis": { status: 1, stdout: "" } }) });
expect(receipt.facts.cron_present).toBe(false);
expect(receipt.decision).toBe("healthy_but_autopatch_unproven");
});
it("marks degraded when live skill health is missing", () => {
const receipt = reconcileRsi({ host: "example", runner: runner({ "test -f /tmp/skill_health.py": { status: 1, stdout: "" } }) });
expect(receipt.facts.skill_health_present).toBe(false);
expect(receipt.decision).toBe("degraded");
});
it("writes a receipt", () => {
const out = tmpFile();
reconcileRsi({ host: "example", out, runner: runner() });
expect(fs.readFileSync(out, "utf-8")).toContain("healthy_but_autopatch_unproven");
});
});