Vili/scripts/test-gpu-gate-http.mjs
Meta Vens e37a2ad3ec Pin the daemon's slot hold at the HTTP edge with a stubbed slow worker
A real daemon on stub wsl.exe and nvidia-smi.exe runs one job held open by the test; requests during
it are 503 source slot, the slot frees at the job's end, and the next job is admitted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 02:29:49 +02:00

300 lines
14 KiB
JavaScript

// Boots the real daemon and observes the gate where it is decided: the HTTP edge. Needs the
// daemon's own dependencies (ws, cultcache-ts), so it runs on a machine that has them. Every boot
// sets VILI_GPU_MIN_FREE_MIB=999999 so an allowed request ends in a VRAM refusal and never starts
// the worker: no test here may start a GPU job.
import assert from "node:assert/strict";
import { spawn, spawnSync } from "node:child_process";
import { existsSync, readFileSync } from "node:fs";
import { copyFile, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import test from "node:test";
import { fileURLToPath } from "node:url";
const daemonPath = path.join(path.dirname(fileURLToPath(import.meta.url)), "vili-daemon.mjs");
let nextPort = 18000 + Math.floor(Math.random() * 1000);
async function bootDaemon(stateRoot, { host = "127.0.0.1", env = { VILI_GPU_MIN_FREE_MIB: "999999" } } = {}) {
const port = nextPort++;
const child = spawn(process.execPath, [daemonPath, "--host", host, "--port", String(port), "--state-root", stateRoot], {
env: { ...process.env, ...env },
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
});
let output = "";
child.stdout.on("data", (chunk) => { output += chunk; });
child.stderr.on("data", (chunk) => { output += chunk; });
const deadline = Date.now() + 30000;
while (!output.includes("Vili listening")) {
if (child.exitCode !== null || Date.now() > deadline) throw new Error(`daemon did not start: ${output.slice(-500)}`);
await new Promise((resolve) => setTimeout(resolve, 100));
}
const stop = () => new Promise((resolve) => {
if (child.exitCode !== null) resolve();
else { child.once("exit", resolve); child.kill(); }
});
return { child, port, stop };
}
const url = (d, route, host = "127.0.0.1") => `http://${host}:${d.port}${route}`;
const generate = (d, body) => fetch(url(d, "/motion/generate"), {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
const setPermission = (d, body, host = "127.0.0.1") => fetch(url(d, "/gpu-permission", host), {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify(body),
});
const readPermission = async (d) => (await fetch(url(d, "/gpu-permission"))).json();
test("http: refusal at the daemon edge", async () => {
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-"));
const d = await bootDaemon(stateRoot);
try {
assert.deepEqual(await readPermission(d), { allowed: false, changedAt: null, jobRunning: false, lastRefusal: null });
assert.equal((await generate(d, {})).status, 400, "request-shape errors come ahead of the gate");
const refused = await generate(d, { prompt: "a person waves hello, never seen before" });
assert.equal(refused.status, 503);
assert.equal(refused.headers.get("retry-after"), "60");
assert.deepEqual(await refused.json(), { ok: false, code: "gpu-busy", source: "household", gpu: { allowed: false, changedAt: null } });
assert.deepEqual(await readdir(path.join(stateRoot, "jobs")), [], "no job file");
assert.equal((await readdir(stateRoot)).includes("artifacts"), false, "no artifact directory");
const state = await (await fetch(url(d, "/operator-state"))).json();
assert.equal(state.gpu.lastRefusal.source, "household");
assert.equal(state.gpu.allowed, false);
} finally {
await d.stop();
await rm(stateRoot, { recursive: true, force: true });
}
});
test("http: the permission persists across a daemon restart", async () => {
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-persist-"));
let d = await bootDaemon(stateRoot);
try {
const on = await setPermission(d, { allowed: true });
assert.equal(on.status, 200);
const onBody = await on.json();
assert.equal(onBody.ok, true);
assert.equal(onBody.gpu.allowed, true);
const before = await readPermission(d);
assert.equal(before.allowed, true);
assert.ok(before.changedAt);
assert.equal((await setPermission(d, { allowed: "yes" })).status, 400);
assert.equal((await setPermission(d, {})).status, 400);
assert.deepEqual(await readPermission(d), before, "a refused write changes nothing");
await d.stop();
d = await bootDaemon(stateRoot);
const after = await readPermission(d);
assert.equal(after.allowed, true);
assert.equal(after.changedAt, before.changedAt);
await setPermission(d, { allowed: false });
await d.stop();
d = await bootDaemon(stateRoot);
assert.equal((await readPermission(d)).allowed, false, "off persists too");
} finally {
await d.stop();
await rm(stateRoot, { recursive: true, force: true });
}
});
test("http: an allowed request reaches the slot and the VRAM read", async () => {
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-slot-"));
const d = await bootDaemon(stateRoot);
try {
await setPermission(d, { allowed: true });
const refused = await generate(d, { prompt: "a person waves hello, never seen before" });
assert.equal(refused.status, 503);
const body = await refused.json();
assert.equal(body.source, "vram", "admitted through acquire(), then refused for VRAM");
assert.equal(body.minFreeMiB, 999999);
assert.deepEqual(await readdir(path.join(stateRoot, "jobs")), [], "no job file");
assert.equal((await readdir(stateRoot)).includes("artifacts"), false);
const state = await readPermission(d);
assert.equal(state.lastRefusal.source, "vram");
assert.equal(state.jobRunning, false);
} finally {
await d.stop();
await rm(stateRoot, { recursive: true, force: true });
}
});
test("http: the switch is the household's", async () => {
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-lan-"));
const d = await bootDaemon(stateRoot, { host: "0.0.0.0" });
try {
const lan = Object.values(os.networkInterfaces()).flat().find((i) => i.family === "IPv4" && !i.internal)?.address;
assert.ok(lan, "the host has a non-loopback IPv4 address");
const before = await readPermission(d);
const refused = await setPermission(d, { allowed: true }, lan);
assert.equal(refused.status, 403);
assert.deepEqual(await refused.json(), { ok: false, code: "household-only" });
assert.deepEqual(await readPermission(d), before, "GET still reads the previous value");
assert.equal((await setPermission(d, { allowed: true })).status, 200, "loopback still sets it");
} finally {
await d.stop();
await rm(stateRoot, { recursive: true, force: true });
}
});
test("http: a web page cannot flip the household's switch or start a job", async () => {
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-csrf-"));
const d = await bootDaemon(stateRoot);
const post = (route, headers, body) => fetch(url(d, route), { method: "POST", headers, body });
const prompt = JSON.stringify({ prompt: "a person waves hello, never seen before" });
try {
const before = await readPermission(d);
for (const route of ["/gpu-permission", "/motion/generate"]) {
const payload = route === "/gpu-permission" ? JSON.stringify({ allowed: true }) : prompt;
const simple = await post(route, { "content-type": "text/plain" }, payload);
assert.equal(simple.status, 403, `${route}: a simple text/plain POST`);
assert.deepEqual(await simple.json(), { ok: false, code: "cross-site" });
assert.equal((await post(route, {}, payload)).status, 403, `${route}: no content-type`);
const hostile = await post(route, { "content-type": "application/json", origin: "http://evil.example-origin" }, payload);
assert.equal(hostile.status, 403, `${route}: a hostile Origin`);
const body = JSON.stringify(await hostile.json());
assert.equal(body.includes("evil.example-origin"), false, "the refusal never echoes the Origin");
assert.equal(
(await post(route, { "content-type": "application/json", origin: `http://127.0.0.1:${d.port}.evil.example` }, payload)).status,
403,
`${route}: an Origin that only starts like ours`,
);
}
assert.deepEqual(await readPermission(d), before, "no refused request changed anything");
assert.deepEqual(await readdir(path.join(stateRoot, "jobs")), [], "no job file");
const preflight = await fetch(url(d, "/gpu-permission"), {
method: "OPTIONS",
headers: { origin: "http://evil.example-origin", "access-control-request-method": "POST", "access-control-request-headers": "content-type" },
});
assert.equal(preflight.ok, false, "the daemon approves no CORS preflight");
assert.equal(preflight.headers.get("access-control-allow-methods"), null);
const own = await post("/gpu-permission", { "content-type": "application/json; charset=utf-8", origin: `http://127.0.0.1:${d.port}` }, JSON.stringify({ allowed: true }));
assert.equal(own.status, 200, "the daemon's own origin still works");
assert.equal((await setPermission(d, { allowed: false })).status, 200, "a caller with no Origin (the tray) still works");
} finally {
await d.stop();
await rm(stateRoot, { recursive: true, force: true });
}
});
// The daemon reaches the GPU only through the processes wsl.exe and nvidia-smi.exe. This test puts
// one small compiled stub on PATH under both names (Windows, where the daemon lives), so the real
// daemon runs a real job against a worker that takes as long as the test says and touches no GPU:
// nvidia-smi.exe answers 20000 free MiB, the worker health call answers ok, and the "/generate"
// call waits until the test creates a release file.
const stubSource = `
using System; using System.IO; using System.Reflection; using System.Threading;
class Stub {
static int Main(string[] args) {
string exe = Path.GetFileNameWithoutExtension(Assembly.GetEntryAssembly().Location).ToLowerInvariant();
string dir = Environment.GetEnvironmentVariable("VILI_STUB_DIR");
if (exe == "nvidia-smi") { Console.WriteLine("20000"); return 0; }
string script = args.Length > 0 ? args[args.Length - 1] : "";
if (script.Contains("/health")) { Console.WriteLine("{\\"ok\\":true,\\"model\\":\\"stub\\"}"); return 0; }
if (script.Contains("/generate")) {
File.AppendAllText(Path.Combine(dir, "started"), "x");
while (!File.Exists(Path.Combine(dir, "release"))) Thread.Sleep(25);
Console.WriteLine("{\\"ok\\":true}");
}
return 0;
}
}`;
async function stubbedDaemon(stubDir, stateRoot) {
const source = path.join(stubDir, "stub.cs");
await writeFile(source, stubSource);
const build = path.join(stubDir, "build.ps1");
await writeFile(build, [
"param($Source, $Out)",
"Add-Type -TypeDefinition (Get-Content -Raw $Source) -OutputAssembly $Out -OutputType ConsoleApplication",
].join("\r\n"));
const built = spawnSync("powershell.exe", [
"-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", build, source, path.join(stubDir, "wsl.exe"),
], { encoding: "utf8", windowsHide: true });
assert.equal(built.status, 0, `the stub did not compile: ${built.stderr || built.stdout}`);
await copyFile(path.join(stubDir, "wsl.exe"), path.join(stubDir, "nvidia-smi.exe"));
const pathKey = Object.keys(process.env).find((key) => key.toLowerCase() === "path") ?? "PATH";
return bootDaemon(stateRoot, {
env: {
[pathKey]: `${stubDir}${path.delimiter}${process.env[pathKey]}`,
VILI_STUB_DIR: stubDir,
VILI_GPU_MIN_FREE_MIB: "1",
},
});
}
test("http: the daemon holds the slot for the whole job", async () => {
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-hold-"));
const stubDir = await mkdtemp(path.join(os.tmpdir(), "vili-gate-stub-"));
const d = await stubbedDaemon(stubDir, stateRoot);
const prompt = { prompt: "a person waves hello, never seen before" };
const within = (promise, ms, what) => Promise.race([
promise,
new Promise((_, reject) => setTimeout(() => reject(new Error(`${what} did not answer within ${ms} ms`)), ms)),
]);
try {
await setPermission(d, { allowed: true });
const first = generate(d, prompt);
const deadline = Date.now() + 20000;
while (!(await readPermission(d)).jobRunning) {
assert.ok(Date.now() < deadline, "the first job never took the slot");
await new Promise((resolve) => setTimeout(resolve, 50));
}
while (!existsSync(path.join(stubDir, "started"))) {
assert.ok(Date.now() < deadline, "the first job never reached the worker");
await new Promise((resolve) => setTimeout(resolve, 50));
}
for (let i = 0; i < 3; i += 1) {
const second = await within(generate(d, prompt), 5000, "a request during the job");
assert.equal(second.status, 503, "a request during the job is refused at once");
assert.deepEqual(await second.json(), { ok: false, code: "gpu-busy", source: "slot" });
}
assert.equal(readFileSync(path.join(stubDir, "started"), "utf8"), "x", "exactly one job reached the worker");
assert.equal((await readPermission(d)).jobRunning, true, "the slot is still held");
await writeFile(path.join(stubDir, "release"), "");
const done = await within(first, 20000, "the first job");
assert.equal(done.status, 200);
assert.equal((await readPermission(d)).jobRunning, false, "the slot frees when the job ends");
const next = await within(generate(d, prompt), 20000, "the next job");
assert.equal(next.status, 200, "the next job is admitted after the slot frees");
assert.equal(readFileSync(path.join(stubDir, "started"), "utf8"), "xx");
} finally {
await d.stop();
await rm(stateRoot, { recursive: true, force: true });
await rm(stubDir, { recursive: true, force: true });
}
});
test("http: a malformed override refuses to start and names the variable, not its value", async () => {
const stateRoot = await mkdtemp(path.join(os.tmpdir(), "vili-gate-env-"));
const child = spawn(process.execPath, [daemonPath, "--host", "127.0.0.1", "--port", String(nextPort++), "--state-root", stateRoot], {
env: { ...process.env, VILI_GPU_MIN_FREE_MIB: "10240MiB-secret" },
stdio: ["ignore", "pipe", "pipe"],
windowsHide: true,
});
let output = "";
child.stdout.on("data", (chunk) => { output += chunk; });
child.stderr.on("data", (chunk) => { output += chunk; });
try {
const code = await new Promise((resolve) => child.on("exit", resolve));
assert.notEqual(code, 0);
assert.match(output, /VILI_GPU_MIN_FREE_MIB must be a finite non-negative number/);
assert.equal(output.includes("10240MiB-secret"), false);
assert.equal(output.includes("Vili listening"), false);
} finally {
child.kill();
await rm(stateRoot, { recursive: true, force: true });
}
});