johnandersen777.bsky.social/atlibs-socialweb-computer-requester
socialweb.computer market requester and policy modules for atlibs, with its web pages and lexicons
0
0
// node --test modules/vm-mcp/test.mjs (after `cargo wasm` here and in ../atlibs-iroh, and the pinned modules
// vendored in compositions/ (README: Test); ../atlibs: its JS host)
// vm-mcp served as MCP (atlibs actionsServer) over the requester's fake market: through the composition
// (iroh) and alone with its hook defaults (webrtc, as the agent page runs it). The model's tools take a
// short VM id; the kept list is one record, read at its key.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { test } from "node:test";
import { bindParameters, loadComposition, runCompose } from "../../../atlibs/javascript/src/compose/index.js";
import { actionsServer } from "../../../atlibs/javascript/src/mcp/index.js";
import { did } from "../../../atlibs/rust/src/harness.mjs";
import { ENDPOINT, HAT7, HAT9, market } from "../requester/fake-market.mjs";
const KEPT = "computer.socialweb.vm.kept";
// A module: this repo's modules/, or atlibs-iroh's; the requester's policy default is policy-open here
// (tangled-vouch's own tests cover it).
const lib = (key) => {
const name = key === "policy-requester-tangled-vouch" ? "policy-open" : key;
const dir = name === "iroh-user-data" ? "../../../atlibs-iroh/" : "../../";
const spec = JSON.parse(readFileSync(new URL(`${dir}modules/${name}/module.json`, import.meta.url), "utf8"));
return { ...spec, name: key, module: readFileSync(new URL(`${dir}target/wasm32-unknown-unknown/release/${name.replaceAll("-", "_")}.wasm`, import.meta.url)) };
};
// A module as the composition pins it: the published (container) build `atlibs add` vendored in compositions/.
const vendored = (key) => {
const at = (ext) => new URL(`../../compositions/modules/${key}.${ext}`, import.meta.url);
return { ...JSON.parse(readFileSync(at("json"), "utf8")), name: key, module: readFileSync(at("wasm")) };
};
const instances = new Map();
const wasm = {
run: async ({ module, entry, input }) => {
if (!instances.has(module)) instances.set(module, new WebAssembly.Instance(new WebAssembly.Module(module), {}).exports);
const x = instances.get(module);
const data = new TextEncoder().encode(JSON.stringify(input));
const ptr = x.alloc(data.length);
new Uint8Array(x.memory.buffer, ptr, data.length).set(data);
const packed = BigInt.asUintN(64, x[entry](ptr, data.length));
return JSON.parse(new TextDecoder().decode(new Uint8Array(x.memory.buffer, Number(packed >> 32n), Number(packed & 0xffffffffn))));
},
};
// The user's PDS (the kept record, putRecord compare-and-swap on its CID) over the fake market, and the
// guest agent's MCP shell behind the host's iroh: and webrtc: fetchers. put(input) may answer a putRecord first.
function world({ bidders = [HAT7], put, refuses = false } = {}) {
const m = market({ bidders, refuses });
const kept = new Map();
let n = 0;
const calls = [];
const respond = (call) => {
calls.push(call);
if (call.params?.collection === KEPT || call.input?.collection === KEPT) {
if (call.nsid === "com.atproto.repo.getRecord") {
const r = kept.get(call.params.rkey);
return r ? { ok: true, output: { uri: `at://${did}/${KEPT}/${call.params.rkey}`, ...r } } : { ok: false, error: "RecordNotFound: Could not locate record" };
}
assert.equal(call.nsid, "com.atproto.repo.putRecord");
const early = put?.(call.input);
if (early) return early;
if ((call.input.swapRecord ?? null) !== (kept.get(call.input.rkey)?.cid ?? null)) return { ok: false, error: "InvalidSwap: Record was at bafy-other" };
kept.set(call.input.rkey, { cid: `bafykept${++n}`, value: call.input.record });
return { ok: true, output: { uri: `at://${did}/${KEPT}/${call.input.rkey}`, cid: `bafykept${n}` } };
}
const out = m.respond(call);
// a live receipt names the bidder's submitEvent: where a kept VM's release goes
if (out.ok && call.params?.collection?.endsWith("market.receipt")) return { ok: true, output: { ...out.output, value: { ...out.output.value, submitEvent: ENDPOINT } } };
return out;
};
const call = async (nsid, request) => {
const out = await respond({ nsid, ...request });
if (out?.ok === false) throw new Error(out.error ?? "call failed");
return out?.output;
};
const shell = [];
const fetch = async ({ url, body }) => {
const { params } = JSON.parse(body);
shell.push({ url, ...params });
assert.equal(params.name, "exec");
const out = { exitCode: 0, stdout: `ran ${params.arguments.command}\n`, stderr: "" };
return { status: 200, headers: {}, body: JSON.stringify({ jsonrpc: "2.0", id: 1, result: { content: [{ type: "text", text: JSON.stringify(out) }], structuredContent: out } }) };
};
const records = new Map();
const managed_records = { get: async (k) => records.get(k) ?? null, put: async (k, v) => void records.set(k, v), delete: async (k) => void records.delete(k) };
const ctx = { wasm, storage: { managed_records } };
return { ctx, call, calls, kept, shell, fetchers: { "iroh:": fetch, "webrtc:": fetch } };
}
const http = { "iroh://*": {}, "webrtc://*": {} };
const budget = { calls: Infinity, wallMs: Infinity, events: Infinity };
// The composition, served as MCP: each tools/call one runCompose of instance vm.
async function viaComposition(w, inputs = {}) {
const manifest = JSON.parse(readFileSync(new URL("../../compositions/socialweb-computer-vm-mcp.composition.json", import.meta.url), "utf8"));
// fast polls; the vouch policy is policy-open here (unpinned): tangled-vouch's own tests judge bidders
for (const i of manifest.instances) i.input = { ...i.input, ...(i.name === "machine" && { poll_ms: 1 }) };
Object.assign(manifest.instances.find((i) => i.name === "vouch"), { module: "policy-open", pin: undefined });
const bound = bindParameters(manifest, { inputs, host: { sshPublicKey: "ssh-ed25519 AAAA test" } });
const comp = await loadComposition(w.ctx, bound, { load: async (key) => (key === "policy-open" ? lib(key) : vendored(key)), provides: ["com.fedfork.exec.iroh.connect"] });
const run = (mod, entry, args) => runCompose(w.ctx, comp, "vm", entry, { ...args, did }, { callFor: () => w.call, fetchers: w.fetchers, http, budget });
return actionsServer({ name: "vm-mcp", modules: [vendored("vm-mcp")], run, confirm: async () => true });
}
// vm-mcp alone, its hooks served by their defaults (requester, process-mcp), as the agent page runs a lib.
function viaDefaults(w, input = {}) {
const host = { "vm-mcp": { call: w.call, callFor: () => w.call, load: async (key) => lib(key), fetchers: w.fetchers, http, budget, input: { did, via: "webrtc", ...input } } };
return actionsServer({ name: "vm-mcp", modules: [lib("vm-mcp")], ctx: w.ctx, host, confirm: async () => true });
}
const tool = async (server, name, args = {}) => {
const reply = await server.message({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name, arguments: args } });
const r = reply.result;
return r.isError ? { error: r.content[0].text } : r.structuredContent;
};
test("vm-mcp: four tools, typed for a model; vm_list alone is read-only", async () => {
const server = await viaComposition(world());
const { tools } = (await server.message({ jsonrpc: "2.0", id: 1, method: "tools/list" })).result;
assert.deepEqual(tools.map((t) => [t.name, t.annotations.readOnlyHint]), [["vm_create", false], ["vm_list", true], ["vm_exec", false], ["vm_release", false], ["vm_forget", false]]);
const exec = tools.find((t) => t.name === "vm_exec").inputSchema;
assert.deepEqual([Object.keys(exec.properties), exec.required], [["vm", "command", "stdin", "timeout_s"], ["vm", "command"]]);
assert.deepEqual(tools.find((t) => t.name === "vm_create").inputSchema.required, undefined, "vm_create needs no argument");
});
for (const [how, serve, scheme] of [["the composition (iroh)", viaComposition, "iroh:"], ["its hook defaults (webrtc)", viaDefaults, "webrtc:"]]) {
test(`vm-mcp through ${how}: create, list, exec, release; one record at its key, never listRecords`, async () => {
const w = world();
const server = await serve(w);
const vm = await tool(server, "vm_create", { cpus: 2, hold_minutes: 30 });
assert.equal(vm.error, undefined, vm.error);
assert.match(vm.vm, /^vm-[0-9a-f]{8}$/);
assert.equal(vm.bidder, HAT7);
assert.ok(vm.shell.startsWith(`${scheme}///`), vm.shell);
assert.equal(Date.parse(vm.expires) - Date.parse(vm.created), 30 * 60_000);
assert.deepEqual(Object.keys(vm).sort(), ["bidder", "cpus", "created", "disk", "expired", "expires", "mem", "shell", "vm"]);
const spec = w.calls.find((c) => c.nsid === "com.atproto.repo.createRecord" && c.input.collection.endsWith("compute.vm")).input.record;
assert.equal(spec.cpus, 2);
assert.equal(spec.expiresAt, vm.expires, "the bidder ends it at compute.vm expiresAt");
const listed = await tool(server, "vm_list");
assert.deepEqual(listed.vms.map((v) => [v.vm, v.expired]), [[vm.vm, false]]);
const stored = w.kept.get("self").value.vms[0];
assert.ok(stored.receipt.uri && stored.records.length, "the record keeps what releases it");
const ran = await tool(server, "vm_exec", { vm: vm.vm, command: "uname -a", timeout_s: 5 });
assert.deepEqual(ran, { exit: 0, stdout: "ran uname -a\n", stderr: "" });
assert.deepEqual([w.shell[0].url, w.shell[0].arguments], [vm.shell, { command: "uname -a", timeoutMs: 5000 }]);
assert.match((await tool(server, "vm_exec", { vm: "vm-nope", command: "id" })).error, /no VM vm-nope here \(yours: vm-/);
const released = await tool(server, "vm_release", { vm: vm.vm });
assert.deepEqual(released, { vm: vm.vm, released: true, terminated: true });
assert.ok(w.calls.some((c) => c.nsid.endsWith("submitEvent") && c.service === ENDPOINT), "vm.delete went to the bidder");
assert.ok(w.calls.some((c) => c.nsid === "com.atproto.repo.deleteRecord" && c.input.collection.endsWith("market.rfp")), "the rent's records were deleted");
assert.deepEqual((await tool(server, "vm_list")).vms, []);
assert.ok(!w.calls.some((c) => c.nsid === "com.atproto.repo.listRecords"), "never listRecords");
});
}
test("vm-mcp: no bid keeps nothing and says what to change; the host's limits bound a create", async () => {
const w = world();
const server = viaDefaults(w, { max_vms: 1, max_cpus: 4 });
const none = await tool(server, "vm_create", { bidders: [HAT9] });
assert.match(none.error, /^no VM \(.*no acceptable bid.*\); nothing is kept\. Try vm_create again/);
assert.deepEqual(w.kept.get("self").value.vms, [], "nothing kept");
assert.match((await tool(server, "vm_create", { cpus: 5 })).error, /cpus must be an integer from 1 to 4/);
assert.match((await tool(server, "vm_create", { bidders: ["hat-7"] })).error, /bidders must be 1 to 10 bidder DIDs/);
assert.equal((await tool(server, "vm_create")).error, undefined, "the default bidders: hat-7 bids");
assert.match((await tool(server, "vm_create")).error, /you keep 1 VMs, at most 1: vm_release one first/);
});
test("vm-mcp: a lost putRecord race re-reads; a VM it cannot record is released", async () => {
let races = 2; // the probe (a lost swap still proves the write), then the record
const w = world({ put: () => (races-- > 0 ? { ok: false, error: "InvalidSwap: Record was at bafy-other" } : undefined) });
const vm = await tool(viaDefaults(w), "vm_create");
assert.equal(vm.error, undefined, vm.error);
assert.equal(w.kept.get("self").value.vms.length, 1);
const scopeless = world({ put: () => ({ ok: false, error: "missing scope repo:computer.socialweb.vm.kept" }) });
assert.match((await tool(viaDefaults(scopeless), "vm_create")).error, /^cannot keep your VM list \(missing scope .*\); nothing was rented\. Sign in again/);
assert.ok(!scopeless.calls.some((c) => c.nsid.endsWith("submitRfp")), "no rent without the scope");
let puts = 0;
const w2 = world({ put: () => (puts++ > 0 ? { ok: false, error: "AuthRequired: scope" } : undefined) });
const lost = await tool(viaDefaults(w2), "vm_create");
assert.match(lost.error, /^the VM came up but could not be recorded \(AuthRequired: scope\); it was released$/);
assert.ok(w2.calls.some((c) => c.nsid.endsWith("submitEvent")));
});
test("vm-mcp: a VM whose bidder refuses the release stays kept; vm_forget drops it after one try and says so", async () => {
const w = world({ refuses: true });
const server = viaDefaults(w);
const vm = await tool(server, "vm_create");
assert.equal(vm.error, undefined, vm.error);
assert.match((await tool(server, "vm_release", { vm: vm.vm })).error, new RegExp(`^${vm.vm} is still kept: .*submitEvent.*vm_forget drops it`));
assert.deepEqual((await tool(server, "vm_list")).vms.map((v) => v.vm), [vm.vm]);
const forgot = await tool(server, "vm_forget", { vm: vm.vm });
assert.deepEqual([forgot.vm, forgot.forgotten, forgot.released, forgot.terminated], [vm.vm, true, false, false]);
assert.match(forgot.note, /did not go through .*until its expiry/);
assert.deepEqual((await tool(server, "vm_list")).vms, []);
// a release that goes through: forget is a release
const w2 = world();
const s2 = viaDefaults(w2);
const vm2 = await tool(s2, "vm_create");
assert.deepEqual(await tool(s2, "vm_forget", { vm: vm2.vm }), { vm: vm2.vm, released: true, terminated: true, forgotten: true });
});