// Live Phase B round: the requester page's WebrtcCubit (req/web/webrtc) in headless Chrome, as an // app-password session (no OAuth popup in headless), against one bidder over its offering's webrtc // endpoint: contract (RFP, accept, event over WebRTC), a VM shell over the guest's datachannel, release, // then every record it wrote checked gone. Never prints the password or tokens. // ALICE_ATPROTO_HANDLE=.. ALICE_ATPROTO_PASSWORD=.. node round.mjs [command] import { browse } from "./browser.mjs"; const ROOT = process.env.PAGE_ROOT ?? new URL("../web/webrtc/", import.meta.url).pathname; const [bidder, command = "uname -a; id"] = process.argv.slice(2); const handle = process.env.ALICE_ATPROTO_HANDLE; const password = process.env.ALICE_ATPROTO_PASSWORD; if (!bidder?.startsWith("did:") || !handle || !password) throw new Error("usage: ALICE_ATPROTO_HANDLE ALICE_ATPROTO_PASSWORD node round.mjs [command]"); const log = (...a) => console.log(new Date().toISOString(), ...a); const login = await (await fetch("https://bsky.social/xrpc/com.atproto.server.createSession", { method: "POST", headers: { "content-type": "application/json" }, body: JSON.stringify({ identifier: handle, password }), })).json(); if (!login.accessJwt) throw new Error(`createSession failed: ${login.error ?? "?"}`); const pds = login.didDoc?.service?.find((s) => s.id === "#atproto_pds")?.serviceEndpoint ?? "https://bsky.social"; log("signed in", login.did, "pds", pds); const index = (await import("node:fs")).readFileSync(`${ROOT}index.html`, "utf8") .replace(/`); const out = await browse(async ({ open, evaluate, on }) => { on("Runtime.consoleAPICalled", (p) => log("page:", p.args.map((a) => a.value ?? a.description).join(" ").slice(0, 400))); await open("/"); await new Promise((r) => setTimeout(r, 3000)); log("session", await evaluate(`start(${JSON.stringify({ did: login.did, handle: login.handle, pds, jwt: login.accessJwt })})`)); if (process.env.POLICY) { const input = { selfDid: login.did, perspective: "requester", mode: "scope", counterpartyDid: bidder, subjectDid: bidder, rootRequesterDid: login.did }; log("policy", JSON.stringify(await evaluate(`(async () => { const m = await dbg.localModule(new URL('policy-requester-tangled-vouch.wasm', dbg.MODULES), new URL('policy-requester-tangled-vouch.json', dbg.MODULES)); const r = await dbg.runModule(dbg.ctx, m, 'evaluate', ${JSON.stringify(input)}, cubit.call); return { result: r.result, error: r.error, log: r.log?.slice?.(-5) } })()`)).slice(0, 1500)); return { seconds: 0, ran: { error: "policy only" }, wire: await evaluate("wire") }; } const t0 = Date.now(); const input = { bidders: [bidder], webrtc_ssh: true, iroh: false, via: "webrtc", tag: "ssh", command, poll_ms: 3000 }; // this profile's fresh attestation key: bound to the account for the round, its binding deleted after const bound = await evaluate(`cubit.run("bind_key", {}).then((r) => ({ ok: r }), (e) => ({ error: e.message }))`); log("bind_key", JSON.stringify(bound).slice(0, 300)); const ran = await evaluate(`cubit.sshKey().then((ssh_key) => cubit.run("contract", { ...${JSON.stringify(input)}, ssh_key })).then((r) => ({ ok: r }), (e) => ({ error: e.message }))`); const wire = await evaluate("wire"); const binding = JSON.stringify(bound.ok ?? {}).match(/at:\/\/[^"]*badgeBlueKeys\/[^"]+/)?.[0]; if (binding) { const [, , , collection, rkey] = binding.split("/"); log("unbind", binding.split("/").pop(), await evaluate(`cubit.call("com.atproto.repo.deleteRecord", { input: { repo: cubit.state.did, collection: "${collection}", rkey: "${rkey}" } }).then(() => "deleted", (e) => e.message)`)); } return { seconds: Math.round((Date.now() - t0) / 1000), ran, wire }; }, { root: ROOT, index }); const r = out.ran.ok ?? {}; log("contract", out.ran.error ? `ERROR ${out.ran.error}` : "ok", `${out.seconds}s`); log("winner", JSON.stringify(r.winner ?? null)?.slice(0, 200)); log("receipt", r.receipt?.uri); log("shell", JSON.stringify(r.shell ?? null)?.slice(0, 400)); log("release", JSON.stringify(r.release ?? null)?.slice(0, 300)); log("errors", JSON.stringify(r.errors ?? [])); log("offers written (computer.socialweb.ingress.offer)", out.wire.offers, "https submit* calls", out.wire.https.length); log("writes", JSON.stringify(out.wire.writes)); if (process.env.READS) for (const r of out.wire.reads) log("read", JSON.stringify(r).slice(0, 220)); // every record the run created is gone const gone = []; for (const uri of r.deleted ?? []) { const [repo, collection, rkey] = uri.replace("at://", "").split("/"); const res = await fetch(`${pds}/xrpc/com.atproto.repo.getRecord?${new URLSearchParams({ repo, collection, rkey })}`); gone.push([collection.split(".").slice(-2).join("."), res.status]); } log("deleted, now", JSON.stringify(gone)); const offersLeft = await (await fetch(`${pds}/xrpc/com.atproto.repo.listRecords?${new URLSearchParams({ repo: login.did, collection: "computer.socialweb.ingress.offer", limit: "10" })}`)).json(); log("ingress.offer records left (verification read only)", offersLeft.records?.length ?? offersLeft.error);