~ knotless Search/
↑↓ move↵ open⇧↵ all results

johnandersen777.bsky.social/atlibs-socialweb-computer-requester

socialweb.computer market requester and policy modules for atlibs, with its web pages and lexicons

0
0
main
203 lines · 12.9 KB Raw History
// 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 });
});