~ 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
231 lines · 13.9 KB Raw History
// node --test web/socialweb-computer-ui/socialweb-cubit.test.mjs  (after `cargo wasm`)
// The page cubit over the real AtircCubit + WebrtcCubit and the real atirc and requester wasm;
// the OAuth client, the session's PDS (atirc lines through it) and the bidder are stubbed.
import assert from "node:assert/strict";
import { readFile } from "node:fs/promises";
import { registerHooks } from "node:module";
import { test } from "node:test";

import { load } from "../../../atlibs/rust/src/harness.mjs";

registerHooks({
	resolve(specifier, context, next) {
		// atlibs/webrtc/ is the atlibs-webrtc sibling checkout's js/; the rest of atlibs/ is core's javascript/src
		const lib = specifier.startsWith("atlibs/webrtc/") ? new URL(`../../../atlibs-webrtc/js/${specifier.slice("atlibs/webrtc/".length)}`, import.meta.url).href
			: specifier.startsWith("atlibs/") && new URL(`../../../atlibs/javascript/src/${specifier.slice("atlibs/".length)}`, import.meta.url).href;
		return next(lib || specifier, context);
	},
});
const { SocialwebCubit, channelView, classify, sharedSessions } = await import("./socialweb-cubit.js");
const { requestScope, grantedSessions, enabledFeatures } = await import("atlibs/oauth/index.js");
const { AtircCubit } = await import("../../../atlibs-agents/web/atirc/atirc-cubit.js");
const { WebrtcCubit } = await import("../webrtc/webrtc-cubit.js");

const T = "com.publicdomainrelay.temp";
const ALICE = "did:plc:lpfuqerea3deuoyrn7ojser4";
const OTHER = "did:plc:someoneelse0000000000000";
const CHANNEL = "#alice_test";
// atirc is the atlibs-agents sibling checkout's module (its own `cargo wasm`); the requester is this repo's
const repo = (name) => (name === "atirc" ? "../../../atlibs-agents/" : "../../");
const release = (name) => new URL(`${repo(name)}target/wasm32-unknown-unknown/release/${name}.wasm`, import.meta.url);
const manifest = async (name) => JSON.parse(await readFile(new URL(`${repo(name)}modules/${name}/module.json`, import.meta.url), "utf8"));
const wasm = async (name) => ({ module: new Uint8Array(await readFile(release(name))), calls: (await manifest(name)).calls });
const context = async (name, fetch) => {
	const { run } = await load(release(name));
	const store = new Map();
	return { wasm: { run: async ({ entry, input }) => run(entry, input) }, http: { fetch }, settings: { get: async (k) => store.get(k), set: async (k, v) => store.set(k, v) } };
};

const POLICY = {
	sessions: {
		requester: { scope: `repo:${T}.compute.vm?action=create&action=delete rpc:${T}.market.submitRfp?aud=*` },
		atirc: { scope: "rpc:chat.atirc.list?aud=did:web:irc.fedfork.com%23atirc rpc:chat.atirc.post?aud=did:web:irc.fedfork.com%23atirc" },
	},
	features: { agents: { requires: ["requester", "atirc"] } },
};

const line = (seq, text, { did = ALICE, nick = "bob-hat-9", identity = `${nick}.alice.test` } = {}) =>
	({ seq, channel: CHANNEL, did, nick, identity, text, ts: `2026-11-08T20:00:${String(seq).padStart(2, "0")}Z`, source: "xrpc" });

// One page: the stubbed OAuth client granting `scope`, the session's PDS (repo writes,
// service-auth, chat.atirc.* behind atproto-proxy), the bidder, and the three cubits.
async function page(scope, lines = []) {
	const xrpc = [];
	const channel = [...lines];
	const agent = {
		did: ALICE,
		com: { atproto: { server: { getSession: async () => ({ success: true, data: { handle: "alice.test" } }) } } },
		async fetchHandler(path, init) {
			const [nsid, query] = path.replace("/xrpc/", "").split("?");
			const input = init.body && JSON.parse(init.body);
			xrpc.push({ nsid, query, input, headers: init.headers });
			if (nsid === "chat.atirc.list") {
				const cursor = Number(new URLSearchParams(query).get("cursor"));
				const messages = channel.filter((m) => m.seq > cursor);
				return Response.json({ messages, cursor: messages.at(-1)?.seq ?? cursor });
			}
			if (nsid === "chat.atirc.post") {
				const posted = line(channel.length ? channel.at(-1).seq + 1 : 1, input.text, { nick: "alice" });
				channel.push(posted);
				return Response.json(posted);
			}
			if (nsid === "com.atproto.server.getServiceAuth") return Response.json({ token: "svc-jwt" });
			if (nsid === "com.atproto.repo.createRecord") return Response.json({ uri: `at://${ALICE}/${input.collection}/r${xrpc.length}`, cid: `bafy${xrpc.length}` });
			return new Response("{}", { status: 400 });
		},
	};
	const signIns = [];
	const revoked = [];
	const client = Promise.resolve({
		init: async () => scope && { session: { did: ALICE, getTokenInfo: async () => ({ scope }) } },
		signIn: async (identifier, options) => signIns.push({ identifier, scope: options.scope }),
		revoke: async (did) => revoked.push(did),
	});
	const sessions = sharedSessions(client, POLICY);
	const bidder = [];
	const http = async (url, init) => (bidder.push({ url, init }), Response.json({ ok: true, name: "bid-1" }));
	const secp256k1 = { utils: { randomSecretKey: () => new Uint8Array(32).fill(1) }, getPublicKey: () => new Uint8Array(33).fill(2), sign: () => new Uint8Array(64) };
	const compute = new WebrtcCubit({ client: Promise.resolve(sessions.client("requester")), makeAgent: () => agent, secp256k1, module: wasm("requester"), ctx: await context("requester", http) });
	const atirc = new AtircCubit({ client: Promise.resolve(sessions.client("atirc")), makeAgent: () => agent, module: wasm("atirc"), ctx: await context("atirc", http) });
	const cubit = new SocialwebCubit({ sessions, policy: POLICY, atirc, compute });
	await cubit.init();
	return { cubit, atirc, compute, xrpc, channel, signIns, revoked, bidder };
}

test("policy: request scope, granted sessions, features", () => {
	assert.equal(requestScope(POLICY, []), "atproto");
	const all = requestScope(POLICY, ["requester", "atirc"]);
	assert.deepEqual(grantedSessions(POLICY, all), ["requester", "atirc"]);
	assert.deepEqual(grantedSessions(POLICY, requestScope(POLICY, ["atirc"])), ["atirc"]);
	assert.deepEqual(grantedSessions(POLICY, "atproto rpc:chat.atirc.list?aud=did:web:irc.fedfork.com%23atirc"), [], "half of a session is none of it");
	assert.deepEqual(enabledFeatures(POLICY, ["atirc"]), []);
	assert.deepEqual(enabledFeatures(POLICY, ["atirc", "requester"]), ["agents"]);
});

test("classify and channelView: scrobbles, owner questions, agents, VM requests from your agents only", () => {
	const Q = "0cd72e00-7953-42d4-8306-49c1b6102ba8";
	assert.equal(classify({ text: "🚀 shipped it" }).kind, "scrobble");
	assert.equal(classify({ text: "✅ done" }).kind, "scrobble");
	assert.equal(classify({ text: "1. not an emoji" }).kind, "chat");
	assert.equal(classify({ text: "plain line 🚀" }).kind, "chat");
	assert.deepEqual(classify({ text: 'vm-request {"bidders":["did:plc:x"],"rm":"-rf"}' }), { kind: "vm-request", input: { bidders: ["did:plc:x"] } });
	assert.equal(classify({ text: "vm-request {not json" }).kind, "chat");

	const view = channelView([
		line(1, `[ask ${Q}] push the branch?`, { nick: "atlibs" }),
		line(2, "🚀 shipped atlibs ssh"),
		line(3, 'vm-request {"user_data":"#cloud-config\\n"}'),
		line(4, 'vm-request {"user_data":"x"}', { did: OTHER, nick: "mallory" }),
		line(5, `[decided ${Q}] keep it local`, { nick: "atlibs" }),
		line(6, "vm-request 3: rfp at://x", { nick: "alice" }),
		line(7, 'vm-request {"bidders":["did:plc:y"]}'),
	], { did: ALICE, handle: "alice.test", channel: CHANNEL });
	assert.deepEqual(view.lines.map((l) => l.kind), ["ask", "scrobble", "vm-request", "vm-request", "decided", "vm-reply", "vm-request"]);
	assert.deepEqual(view.questions, [{ id: Q, question: "push the branch?", askedBy: "atlibs.alice.test", status: "decided", outcome: "keep it local", by: "atlibs.alice.test" }]);
	assert.deepEqual(view.requests.map((r) => [r.seq, r.replied]), [[7, false], [3, true]], "mallory's request (another DID) is not listed");
	const bob = view.agents.find((a) => a.nick === "bob-hat-9");
	assert.deepEqual([bob.lines, bob.scrobbles], [3, 1]);
});

// Anyone may post "[answer <id>] ..." text: only the channel owner's answer, the asker's (or
// owner's) decision and asks from the owner's account count; the rest are unverified chat.
test("channelView: forged owner-question lines are unverified and change nothing", () => {
	const Q = "11111111-2222-3333-4444-555555555555";
	const R = "66666666-7777-8888-9999-aaaaaaaaaaaa";
	const mallory = { did: OTHER, nick: "mallory", identity: "mallory.example" };
	const owner = { nick: "alice", identity: "alice.test" };
	const view = channelView([
		line(1, `[ask ${Q}] deploy?`, { nick: "atlibs" }),
		line(2, `[answer ${Q}] yes, deploy`, mallory),
		line(3, `[answer ${Q}] yes, deploy`, { nick: "bob-hat-9" }),
		line(4, `[decided ${Q}] deployed`, { nick: "bob-hat-9" }),
		line(5, `[ask ${R}] wire the money?`, mallory),
		line(6, `[ask ${Q}] overwrite the question`, mallory),
		line(7, `[answer ${Q}] no, hold`, owner),
	], { did: ALICE, handle: "alice.test", channel: CHANNEL });
	assert.equal(view.owner, "alice.test");
	assert.deepEqual(view.lines.map((l) => [l.kind, Boolean(l.unverified)]), [
		["ask", false], ["chat", true], ["chat", true], ["chat", true], ["chat", true], ["chat", true], ["answer", false],
	]);
	assert.deepEqual(view.questions, [{ id: Q, question: "deploy?", askedBy: "atlibs.alice.test", status: "answered", outcome: "no, hold", by: "alice.test" }]);

	// Without any owner line or a signed-in owner, no ask verifies.
	const stranger = channelView([line(1, `[ask ${Q}] deploy?`, { nick: "atlibs" })], { did: OTHER, handle: "mallory.example", channel: CHANNEL });
	assert.deepEqual([stranger.questions, stranger.lines[0].unverified], [[], true]);
});

test("one session: only atirc granted, so compute is signed out and agents is off", async () => {
	const { cubit, atirc, compute, signIns } = await page(requestScope(POLICY, ["atirc"]), [line(1, "hello")]);
	assert.deepEqual([cubit.state.session, cubit.state.granted, cubit.state.features], ["signed-in", ["atirc"], []]);
	assert.equal(atirc.state.session, "signed-in");
	assert.equal(atirc.state.messages.list[0].text, "hello");
	assert.equal(compute.state.session, "signed-out");

	await cubit.announce();
	assert.match(cubit.state.announce.error, /agents needs the requester and atirc sessions/);

	// Turning compute on: one new consent for both sessions, as the signed-in DID.
	await cubit.setSessions(["atirc", "requester"]);
	assert.deepEqual(signIns, [{ identifier: ALICE, scope: requestScope(POLICY, ["atirc", "requester"]) }]);
});

test("signed out: login asks for the identity and the ticked sessions in one consent", async () => {
	const { cubit, atirc, compute, signIns } = await page(undefined);
	assert.deepEqual([cubit.state.session, atirc.state.session, compute.state.session], ["signed-out", "signed-out", "signed-out"]);
	await cubit.login("alice.test", ["requester"]);
	assert.deepEqual(signIns, [{ identifier: "alice.test", scope: requestScope(POLICY, ["requester"]) }]);
});

test("both sessions: announce the VM, approve an agent's vm-request, answer it on the channel", async () => {
	const endpoint = "https://bidder.example";
	const { cubit, atirc, compute, xrpc, channel, bidder, revoked } = await page(requestScope(POLICY, ["requester", "atirc"]), [
		line(1, `vm-request ${JSON.stringify({ user_data: "#cloud-config\n", bidders: [endpoint] })}`),
		line(2, 'vm-request {"user_data":"x"}', { did: OTHER, nick: "mallory" }),
	]);
	assert.deepEqual(cubit.state.features, ["agents"]);
	assert.equal(compute.state.session, "signed-in");

	// Announce needs a VM the page found.
	await cubit.announce();
	assert.match(cubit.state.announce.error, /no receipt/);
	compute.emit({ receipt: { uri: `at://did:plc:hat9/${T}.market.receipt/receipt-1`, cid: "bafyrc", bidder: "did:plc:hat9" }, tag: "ssh", phase: "open" });
	await cubit.announce();
	assert.equal(cubit.state.announce.status, "done");
	const posted = xrpc.filter((c) => c.nsid === "chat.atirc.post");
	assert.equal(posted[0].input.text, `vm: at://did:plc:hat9/${T}.market.receipt/receipt-1 tag ssh open (socialweb-computer-ui)`);
	assert.equal(posted[0].headers["atproto-proxy"], "did:web:irc.fedfork.com#atirc");

	// Requests run only once the owner allows them, and only from the account's agents.
	await cubit.approve(1);
	assert.match(cubit.state.approvals[1].error, /not allowed/);
	cubit.allowAgentRequests(true);
	await cubit.approve(2);
	assert.match(cubit.state.approvals[2].error, /no vm-request 2 from your agents/);
	await cubit.approve(1);
	assert.equal(cubit.state.approvals[1].status, "done", cubit.state.approvals[1].error);
	const rfp = xrpc.find((c) => c.input?.collection === `${T}.market.rfp`);
	assert.equal(rfp.input.record.signatures[0].issuer, ALICE);
	assert.equal(bidder[0].url, `${endpoint}/xrpc/${T}.market.submitRfp`);
	assert.match(channel.at(-1).text, /^vm-request 1: rfp at:\/\/.*market\.rfp.* submitted to 1 bidder\(s\)$/);
	await atirc.refresh();
	assert.equal(channelView(atirc.state.messages.list, { did: ALICE, handle: "alice.test", channel: CHANNEL }).requests.find((r) => r.seq === 1).replied, true);
	cubit.emit({ approvals: {} });
	await cubit.approve(1);
	assert.match(cubit.state.approvals[1].error, /already answered/);

	await cubit.logout();
	assert.deepEqual(revoked, [ALICE]);
	assert.deepEqual([cubit.state.session, atirc.state.session, compute.state.session], ["signed-out", "signed-out", "signed-out"]);
});

// A page whose plugin context lacks the rkey ledger has every cleanup delete refused ("not created by
// this module"): each page that runs the requester must declare managed_records on that context.
test("pages that run the requester declare storage.managed_records on its plugin context", async () => {
	for (const page of ["socialweb-computer-ui", "webrtc"]) {
		const main = await readFile(new URL(`../${page}/main.js`, import.meta.url), "utf8");
		const contexts = [...main.matchAll(/createPluginContext\(\{([^)]*)\}\)/g)].map((m) => m[1]);
		const requester = contexts.filter((c) => /attestKeys/.test(c));
		assert.ok(requester.length > 0, `${page}: the requester's context (the one with attestKeys)`);
		for (const c of requester) assert.match(c, /storage:\s*\{\s*managed_records:\s*\{\s*\}\s*\}/, `${page}: ${c}`);
	}
});