// The SetSpark client's contract-independent half: config, key file read // per call, idempotency keys, and the HTTP core against a local server that // plays the record service. No network, no real key. import { test, after } from "node:test"; import assert from "node:assert/strict"; import { createServer } from "node:http"; import { once } from "node:events"; import { chmodSync, mkdirSync, symlinkSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { SETSPARK_REFUSAL, SetsparkRefusal, IDEMPOTENCY_HEADER, MESSAGE_MAX_CHARS, USER_AGENT, SETSPARK_TOOL_NAMES, LIST_MAX, loadSetsparkConfig, readKey, idempotencyKey, connectorKey, callApi, renderRefusal, createSetsparkApi, renderRecord, hideIds, } from "../src/setspark.mjs"; import { loadToolsConfig, createToolSet, enabledToolNames } from "../src/tools.mjs"; import { validateBinding, resolveToolRoots, reloadDiff } from "../src/binding.mjs"; import { validateRequest } from "../src/approvals.mjs"; import { makeRoot, rawBinding } from "./helpers.mjs"; const KEY_A = "ssk_" + "a".repeat(40); const KEY_B = "ssk_" + "b".repeat(40); function keyFile(root, content = KEY_A, mode = 0o600) { const dir = join(root, "secrets"); mkdirSync(dir, { recursive: true, mode: 0o700 }); const path = join(dir, "setspark.key"); writeFileSync(path, content.length === 0 ? "" : `${content}\n`, { mode: 0o600 }); chmodSync(path, mode); return path; } const seen = []; const server = createServer((req, res) => { const chunks = []; req.on("data", (c) => chunks.push(c)); req.on("end", () => { const body = Buffer.concat(chunks).toString("utf8"); seen.push({ method: req.method, path: req.url, auth: req.headers.authorization, key: req.headers[IDEMPOTENCY_HEADER], ua: req.headers["user-agent"], type: req.headers["content-type"], body }); const json = (status, obj) => { res.writeHead(status, { "content-type": "application/json" }); res.end(JSON.stringify(obj)); }; const parsed = body ? JSON.parse(body) : null; const path = req.url.split("?")[0]; const view = { request_id: 12, decision_id: "DEC-012", state: "open", proposal_version: 2, proposal_digest: "0123456789abcdef0123456789abcdef", required_approvers: ["100000000000000002", "100000000000000004"], channel_id: null, message_id: null, at: "2026-09-20T00:00:00Z", approvals: [] }; // the verbs' routes (contract a5425a2) if (path === "/v1/records" && req.method === "POST") return json(201, { id: "WI-7", record_type: parsed.record_type, revision: 1, ...parsed.record, created_at: "2026-09-20T00:00:00Z", updated_at: "2026-09-20T00:00:00Z" }); if (path === "/v1/records" && req.method === "GET") return json(200, { record_type: "work_item", items: [{ id: "WI-7", record_type: "work_item", revision: 1, title: "Ship it", status: "active" }, { id: "WI-8", record_type: "work_item", revision: 4, title: "Later", status: "active", priority: "low" }], limit: 20, offset: 0 }); if (path === "/v1/records/WI-7" && req.method === "GET") return json(200, { id: "WI-7", record_type: "work_item", revision: 3, title: "Ship it", status: "active", owner: "Jason", tags: ["a", "b"], accepted_snapshot: { hidden: true }, body: "Two lines.\nOf body." }); if (path === "/v1/records/WI-7" && req.method === "PATCH") return json(200, { id: "WI-7", record_type: "work_item", revision: parsed.revision + 1, title: "Ship it", ...parsed.fields }); if (path === "/v1/records/DEC-9" && req.method === "GET") return json(200, { id: "DEC-9", record_type: "decision", revision: 2, title: "Pick one", status: "Proposed", required_approvers: ["discord:100000000000000100", "discord:199999999999999999"], approvals: [{ approver: "100000000000000100", at: "t" }] }); if (path === "/v1/records/DEC-9" && req.method === "PATCH") return json(200, { id: "DEC-9", record_type: "decision", revision: parsed.revision + 1, title: "Pick one", ...parsed.fields }); // Discord ids where the model could read them (the id-hiding boundary) if (path === "/v1/records/DEC-10" && req.method === "GET") return json(200, { id: "DEC-10", record_type: "decision", revision: 1, title: "Ask 100000000000000100", required_approvers: ["discord:100000000000000100", "100000000000000101"], proposal_digest: "12345678901234567890abcdef0123456789abcdef0123456789abcdef012345", nested: { text: "approver discord:100000000000000100", "199999999999999999": "keyed" }, owner_id: 100000000000000100, note: `${"x".repeat(485)} 100000000000000101`, body: "Ask <@100000000000000100> and <@!199999999999999999>." }); if (path === "/v1/records/DEC-10" && req.method === "PATCH") return json(409, { code: "stale_revision", message: "approver discord:100000000000000101 changed", current_revision: 2, changed_fields: ["required_approvers", "100000000000000100"] }); if (path === "/v1/records/DEC-12" && req.method === "PATCH") return json(400, { code: `${"x".repeat(49)} 100000000000000100`, message: "no" }); if (path === "/v1/records/DEC-11" && req.method === "PATCH") return json(422, { code: "validation", message: `required_approvers: 199999999999999999 is not a user ${"z".repeat(332)} 100000000000000100` }); if (path === "/v1/resolve" && new URL(req.url, "http://x").searchParams.get("q") === "leak") return json(200, { query: "leak", matches: [{ id: "DEC-10", record_type: "decision", title: "Ask <@100000000000000101>", exact: false }] }); if (path === "/v1/documents" && req.method === "POST" && parsed.collection === "leak") return json(201, { id: "doc-2", title: "Notes for 100000000000000100", url: "https://outline.example.test/doc/199999999999999999" }); if (path === "/v1/records/WI-9" && req.method === "PATCH") return json(409, { code: "stale_revision", message: "behind", current_revision: 5, changed_fields: ["status"] }); if (path === "/v1/resolve") return json(200, { query: "ship", matches: [{ id: "WI-7", record_type: "work_item", title: "Ship it", exact: false }] }); if (path === "/v1/approval-requests" && req.method === "POST") return json(201, view); if (path === "/v1/approval-requests/12/message") return json(200, { ...view, channel_id: parsed.channel_id, message_id: parsed.message_id }); if (path === "/v1/approval-requests/12" && req.method === "GET") return json(200, { ...view, message_id: "500000000000000002", approvals: [{ approver: "100000000000000002", at: "t", message_id: "500000000000000003", source_url: "https://discord.com/channels/100000000000000001/100000000000000010/500000000000000003" }] }); if (path === "/v1/approvals" && req.method === "POST") return json(201, { ...view, approver: parsed.author_id, approved: [parsed.author_id], accepted: true, status: "open", revision: 3 }); if (path === "/v1/documents" && req.method === "POST") return json(201, { id: "doc-1", title: parsed.title, collection: parsed.collection, url: "https://outline.example.test/doc/abc" }); switch (req.url) { case "/v1/work_items": return json(201, { id: "SS-101", revision: 1, echo: parsed }); case "/v1/work_items/SS-101": return json(200, { id: "SS-101", revision: 3 }); case "/v1/stale": return json(409, { code: "stale_revision", message: "revision 2 is behind", current_revision: 3, changed_fields: ["title", "status"] }); case "/v1/replay": return json(422, { code: "idempotency_mismatch", message: "same key, different request" }); case "/v1/nokey": return json(401, { code: "unauthorized", message: "bad key" }); case "/v1/missing": return json(404, { code: "not_found", message: "no SS-999" }); case "/v1/bad": return json(400, { code: "validation", message: "x".repeat(2000) }); case "/v1/boom": return json(500, { code: "internal", message: "db down" }); case "/v1/html": res.writeHead(200, { "content-type": "text/html" }); return res.end("
hi
"); case "/v1/big": res.writeHead(200, { "content-type": "application/json" }); return res.end(`{"pad":"${"y".repeat(300000)}"}`); case "/v1/slow": return setTimeout(() => json(200, { late: true }), 3000).unref(); case "/v1/whoami": return json(200, { auth: req.headers.authorization }); default: return json(404, { code: "not_found", message: "no route" }); } }); }); server.listen(0, "127.0.0.1"); await once(server, "listening"); const base = `http://127.0.0.1:${server.address().port}`; after(() => server.close()); function config(root, extra = {}) { return loadSetsparkConfig({ baseUrl: base, keyFile: keyFile(root), principal: "sage", timeoutMs: 1000, ...extra }); } test("setspark config: a bare https or loopback origin, a private key file, a principal", () => { const root = makeRoot(); const kf = keyFile(root); const c = loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage" }); assert.deepEqual(c, { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", timeoutMs: 15000, maxResponseBytes: 262144, approvers: {} }); assert.equal(loadSetsparkConfig({ baseUrl: "https://api.setspark.io/", keyFile: kf, principal: "sage" }).baseUrl, "https://api.setspark.io"); assert.throws(() => loadSetsparkConfig(null), /not an object/); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", extra: 1 }), /unknown key/); assert.throws(() => loadSetsparkConfig({ baseUrl: "http://api.setspark.io", keyFile: kf, principal: "sage" }), /must be https/); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io/v1", keyFile: kf, principal: "sage" }), /no path/); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://u:p@api.setspark.io", keyFile: kf, principal: "sage" }), /no path/); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io?x=1", keyFile: kf, principal: "sage" }), /no path/); assert.throws(() => loadSetsparkConfig({ baseUrl: "nope", keyFile: kf, principal: "sage" }), /not a valid url/); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: "relative", principal: "sage" }), /absolute path/); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: join(root, "none"), principal: "sage" }), /not found/); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: kf, principal: "Sage!" }), /principal/); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", timeoutMs: 10 }), /timeoutMs/); // mode, symlink, empty const loose = keyFile(makeRoot(), KEY_A, 0o644); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: loose, principal: "sage" }), /mode 0600/); const empty = keyFile(makeRoot(), ""); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: empty, principal: "sage" }), /is empty/); const link = join(makeRoot(), "link.key"); symlinkSync(kf, link); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: link, principal: "sage" }), /symlink/); }); test("setspark config: reaches the tools config and the binding as a fixed key", () => { const root = makeRoot(); const docs = join(root, "docs"); mkdirSync(docs); const kf = keyFile(root); const tools = loadToolsConfig({ roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage" } }); assert.equal(tools.setspark.baseUrl, "https://api.setspark.io"); assert.equal(tools.setspark.principal, "sage"); assert.throws(() => loadToolsConfig({ roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io" } }), /keyFile/); const b = validateBinding(rawBinding({ tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage" } } })); assert.equal(b.tools.setspark.keyFile, kf); assert.throws(() => validateBinding(rawBinding({ tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", nope: 1 } } })), /unknown key/); }); test("setspark config: the binding's key survives resolveToolRoots and the engine's JSON hand-off to the extension", () => { const root = makeRoot(); const docs = join(root, "docs"); mkdirSync(docs); const dataRoot = join(root, "data"); mkdirSync(dataRoot); const kf = keyFile(root); const b = validateBinding(rawBinding({ tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", timeoutMs: 5000 } } })); const resolved = resolveToolRoots(b, { dataRoot }); assert.deepEqual(resolved.setspark, { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", timeoutMs: 5000, approvers: { owner: "100000000000000100" } }); for (const name of SETSPARK_TOOL_NAMES) assert.ok(enabledToolNames(resolved).includes(name), `${name} reaches pi's --tools list`); const ext = loadToolsConfig(JSON.parse(JSON.stringify(resolved))); assert.deepEqual(ext.setspark, b.tools.setspark, "the extension rebuilds the same config, response cap included"); const plain = resolveToolRoots(validateBinding(rawBinding({ tools: { roots: [{ name: "docs", path: docs }] } })), { dataRoot }); assert.equal("setspark" in plain, false); assert.ok(!enabledToolNames(plain).some((n) => SETSPARK_TOOL_NAMES.includes(n))); }); test("setspark config: approvers come from the binding's users, never from the binding's setspark key", () => { const root = makeRoot(); const docs = join(root, "docs"); mkdirSync(docs); const kf = keyFile(root); const users = [{ id: "100000000000000100", name: "Jason" }, { id: "100000000000000101", name: "carmen" }]; const b = validateBinding(rawBinding({ users, tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage" } } })); assert.deepEqual(b.tools.setspark.approvers, { jason: "100000000000000100", carmen: "100000000000000101" }); assert.throws(() => validateBinding(rawBinding({ users, tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", approvers: { mallory: "100000000000000199" } } } })), /approvers come from users/); assert.throws(() => validateBinding(rawBinding({ users: [{ id: "100000000000000100", name: "Jason" }, { id: "100000000000000101", name: "jason" }], tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage" } } })), /two users named jason/); const withCarmenOut = validateBinding(rawBinding({ users: [users[0]], tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage" } } })); assert.throws(() => reloadDiff(b, withCarmenOut), /tools cannot change/, "pi's approvers are fixed at start, so a user change needs a restart"); const channelsOnly = validateBinding(rawBinding({ users: [{ ...users[0], channels: [rawBinding().channels[0].id] }, users[1]], tools: { roots: [{ name: "docs", path: docs }], setspark: { baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage" } } })); assert.doesNotThrow(() => reloadDiff(b, channelsOnly), "a user's channels still reload"); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", approvers: { jason: "not-an-id" } }), /approvers/); assert.throws(() => loadSetsparkConfig({ baseUrl: "https://api.setspark.io", keyFile: kf, principal: "sage", approvers: { jason: "100000000000000100", jay: "100000000000000100" } }), /approvers/); }); test("setspark verbs: required_approvers go out as discord ids from names and come back as names", async () => { const root = makeRoot(); const named = createToolSet(loadToolsConfig({ roots: [{ name: "docs", path: root }], maxCallsPerTurn: 12, setspark: { baseUrl: base, keyFile: keyFile(root), principal: "sage", timeoutMs: 1000, approvers: { jason: "100000000000000100", carmen: "100000000000000101" } } })); named.setTurn(TURN); seen.length = 0; const created = await named.call("record_create", { record_type: "decision", record: { title: "Pick one", status: "Proposed", work_item: "SS-1", required_approvers: ["Jason", "@carmen"] } }); assert.equal(created.ok, true, created.text); assert.deepEqual(JSON.parse(seen[0].body).record.required_approvers, ["discord:100000000000000100", "discord:100000000000000101"]); const unknown = await named.call("record_create", { record_type: "decision", record: { title: "Pick one", required_approvers: ["Jason", "Mallory"] } }); assert.equal(unknown.ok, false); assert.match(unknown.text, /entry 2 is not a known user name; use names from: jason, carmen/); const raw = await named.call("record_create", { record_type: "decision", record: { title: "Pick one", required_approvers: ["discord:100000000000000100"] } }); assert.equal(raw.ok, false, "an id is not a name"); assert.doesNotMatch(raw.text, /100000000000000100/, "a refused id is not echoed back"); const twice = await named.call("record_create", { record_type: "decision", record: { title: "Pick one", required_approvers: ["jason", "Jason"] } }); assert.match(twice.text, /the same person twice/); const notList = await named.call("record_update", { id: "DEC-9", revision: 2, fields: { required_approvers: "jason" } }); assert.equal(notList.ok, false); assert.equal(seen.length, 1, "refused calls send nothing"); const updated = await named.call("record_update", { id: "DEC-9", revision: 2, fields: { required_approvers: ["carmen"] } }); assert.equal(updated.ok, true, updated.text); assert.deepEqual(JSON.parse(seen[1].body).fields.required_approvers, ["discord:100000000000000101"]); const got = await named.call("record_get", { id: "DEC-9" }); assert.match(got.text, /required_approvers: jason, unknown user/); assert.doesNotMatch(got.text, /1000000000000001|1999999999/, "no discord id reaches the model"); const none = createToolSet(loadToolsConfig({ roots: [{ name: "docs", path: root }], setspark: { baseUrl: base, keyFile: keyFile(root), principal: "sage", timeoutMs: 1000 } })); none.setTurn(TURN); const noUsers = await none.call("record_create", { record_type: "decision", record: { title: "Pick one", required_approvers: ["jason"] } }); assert.match(noUsers.text, /use names from: \(none\)/); }); test("setspark verbs: no Discord user id reaches tool text, whatever shape the service returns it in", async () => { const root = makeRoot(); const ids = ["100000000000000100", "100000000000000101", "199999999999999999"]; const t = createToolSet(loadToolsConfig({ roots: [{ name: "docs", path: root }], maxCallsPerTurn: 12, setspark: { baseUrl: base, keyFile: keyFile(root), principal: "sage", timeoutMs: 1000, approvers: { jason: "100000000000000100", carmen: "100000000000000101" } } })); t.setTurn(TURN); const texts = []; const got = await t.call("record_get", { id: "DEC-10" }); assert.equal(got.ok, true, got.text); texts.push(got.text); assert.match(got.text, /title: Ask jason/); assert.match(got.text, /required_approvers: jason, carmen/); assert.match(got.text, /nested: \{"text":"approver jason","unknown user":"keyed"\}/); assert.match(got.text, /Ask jason and unknown user\./); assert.match(got.text, /proposal_digest: 12345678901234567890abcdef/, "a digit run inside a digest is not an id"); const stale = await t.call("record_update", { id: "DEC-10", revision: 1, fields: { status: "Proposed" } }); assert.equal(stale.ok, false); assert.match(stale.text, /changed: required_approvers, jason/); assert.match(stale.text, /approver carmen changed/); texts.push(stale.text); const rejected = await t.call("record_update", { id: "DEC-11", revision: 1, fields: { status: "Proposed" } }); assert.equal(rejected.ok, false); assert.match(rejected.text, /unknown user is not a user/); texts.push(rejected.text); // a field capped before it is shown: the service's code, a bad property // name, a bad filter name const code = await t.call("record_update", { id: "DEC-12", revision: 1, fields: { status: "Proposed" } }); assert.equal(code.ok, false); texts.push(code.text); const prop = await t.call("record_create", { record_type: "decision", record: { title: "x", [`${"x".repeat(17)} 100000000000000101`]: "v" } }); assert.match(prop.text, /property name that is not allowed/); texts.push(prop.text); const filter = await t.call("record_list", { record_type: "decision", filters: { [`${"x".repeat(17)} 199999999999999999`]: "v" } }); assert.match(filter.text, /property name not allowed/); texts.push(filter.text); const found = await t.call("resolve_id", { query: "leak" }); assert.match(found.text, /Ask carmen/); texts.push(found.text); const doc = await t.call("create_document", { collection: "leak", title: "Notes", text: "x" }); assert.equal(doc.ok, true, doc.text); texts.push(doc.text); const opened = await t.call("open_approval_request", { decision_id: "DEC-012", proposal_version: 2, proposal_digest: "0123456789abcdef0123456789abcdef" }); texts.push(opened.text); for (const text of texts) for (const id of ids) assert.equal(text.includes(id), false, `id ${id} in: ${text}`); for (const text of texts) assert.doesNotMatch(text, /(? discord:100000000000000100 SS-027 v2"), "unknown user unknown user SS-027 v2"); }); test("setspark contract: a decision made with names opens a request the connector accepts; names stored by an old record still refuse", async () => { // A local stand-in for the service's contract: records keep // required_approvers as written (discord: