From 9c954e7502bcea700acff8d8b826ba1fa52f5f89 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Wed, 30 Sep 2026 16:59:51 +0200 Subject: [PATCH] Bifrost pump: one verb, one path; refuse anything else with a failed receipt With discord-dm gone the verb set, listCommandRecords filter, resolveBridgeVerb throw and the verb variable decided nothing, and the filter hid non-discord-post commands so they stayed pending with no receipt. Delete all of it: a command whose verb is not discord-post now reaches processOneCommand and is refused with a failed receipt (error names no input value). Payload staging moves inside the try so a refusal cannot escape as a thrown error. Adds tests/cultmesh-bridge-commands.test.mjs: discord-dm refused with failed receipt and non-ok result, canary-free error, discord-post still reaches the actuator. Co-Authored-By: Claude Sonnet 5.5 --- tests/cultmesh-bridge-commands.test.mjs | 89 +++++++++++++++++++++++++ tools/cultmesh-bridge-commands.mjs | 38 ++++------- 2 files changed, 102 insertions(+), 25 deletions(-) create mode 100644 tests/cultmesh-bridge-commands.test.mjs diff --git a/tests/cultmesh-bridge-commands.test.mjs b/tests/cultmesh-bridge-commands.test.mjs new file mode 100644 index 0000000..2c0d56f --- /dev/null +++ b/tests/cultmesh-bridge-commands.test.mjs @@ -0,0 +1,89 @@ +import test from "node:test"; +import assert from "node:assert/strict"; +import { spawnSync } from "node:child_process"; +import { createRequire } from "node:module"; +import { mkdtemp } from "node:fs/promises"; +import { tmpdir } from "node:os"; +import { resolve } from "node:path"; +import { + discordPostCommandDefinition, + discordPostCommandDocumentType, + discordPostCommandSchemaId, +} from "../tools/bifrost-discord-command-documents.mjs"; + +const root = resolve(import.meta.dirname, ".."); +const cultLib = resolve(process.env.VOIDBOT_CULTLIB_ROOT || resolve(root, "..", "CultLib")); +const tool = resolve(root, "tools", "cultmesh-bridge-commands.mjs"); +const canary = "CANARY-9f3a1c77"; + +function loadRuntime() { + const mesh = resolve(cultLib, "packages", "cultmesh-ts", "dist", "index.js"); + const cache = resolve(cultLib, "packages", "cultcache-ts", "dist", "index.js"); + const requireCult = createRequire(mesh); + return { CultMesh: requireCult(mesh).CultMesh, defineDocumentType: requireCult(cache).defineDocumentType }; +} + +async function putCommand(store, commandId, command) { + const { CultMesh, defineDocumentType } = loadRuntime(); + const definition = discordPostCommandDefinition(defineDocumentType); + const node = await CultMesh.createNode(store, { documents: [definition] }); + await node.put(definition, commandId, { + schemaName: discordPostCommandDocumentType, + schemaVersion: discordPostCommandSchemaId, + commandId, + command, + status: "pending", + requestedBy: "test", + requestedAt: new Date().toISOString(), + updatedAt: new Date().toISOString(), + source: { kind: "test", id: commandId }, + payload: { channelId: "123", content: canary }, + }); + await node.flush?.(); +} + +function run(...args) { + const env = { ...process.env, BIFROST_SKIP_LOCAL_ENV: "true", VOIDBOT_CULTLIB_ROOT: cultLib }; + delete env.BIFROST_DISCORD_BOT_TOKEN; + delete env.DISCORD_BOT_TOKEN; + const result = spawnSync(process.execPath, [tool, ...args], { cwd: root, encoding: "utf8", env }); + return { ...result, json: JSON.parse(result.stdout) }; +} + +test("a non-discord-post command is refused with a failed receipt, never left pending", async () => { + const store = resolve(await mkdtemp(resolve(tmpdir(), "bifrost-pump-")), "store.cc"); + await putCommand(store, "dm1", "discord-dm"); + + const processed = run("process", "--store", store); + assert.equal(processed.json.commandCount, 1); + assert.equal(processed.json.ok, false); + assert.equal(processed.json.processed[0].status, "failed"); + + const receipt = run("receipt", "--store", store, "--command-id", "dm1").json; + assert.equal(receipt.ok, false); + assert.equal(receipt.receipt.status, "failed"); + assert.equal(receipt.receipt.ok, false); + assert.match(receipt.receipt.error, /only discord-post is supported/); + + const again = run("process", "--store", store); + assert.equal(again.json.commandCount, 0, "a refused command is not pending or running afterwards"); +}); + +test("a refusal echoes no input value", async () => { + const store = resolve(await mkdtemp(resolve(tmpdir(), "bifrost-pump-")), "store.cc"); + await putCommand(store, "dm2", `${canary}-verb`); + const processed = run("process", "--store", store, "--command-id", "dm2"); + assert.equal(processed.json.processed[0].status, "failed"); + assert.equal(processed.json.processed[0].error.includes(canary), false); +}); + +test("a discord-post command still reaches the actuator", async () => { + const store = resolve(await mkdtemp(resolve(tmpdir(), "bifrost-pump-")), "store.cc"); + await putCommand(store, "post1", "discord-post"); + const processed = run("process", "--store", store); + assert.equal(processed.json.commandCount, 1); + const receipt = processed.json.processed[0]; + assert.equal(receipt.action, "discord-post"); + assert.match(receipt.error, /Bifrost Discord actuator failed/); + assert.doesNotMatch(receipt.error, /only discord-post is supported/); +}); diff --git a/tools/cultmesh-bridge-commands.mjs b/tools/cultmesh-bridge-commands.mjs index 576ace4..8d4885b 100644 --- a/tools/cultmesh-bridge-commands.mjs +++ b/tools/cultmesh-bridge-commands.mjs @@ -131,7 +131,6 @@ async function deleteCommand(options) { } async function processOneCommand(node, command, options) { - const verb = resolveBridgeVerb(command); const running = { ...command, status: "running", @@ -142,13 +141,16 @@ async function processOneCommand(node, command, options) { const payload = command.payload && typeof command.payload === "object" ? command.payload : {}; const tempDir = resolve(repoRoot, ".bifrost", "cultmesh-command-payloads", command.commandId); const contentPath = resolve(tempDir, "content.md"); - await mkdir(tempDir, { recursive: true }); - await writeFile(contentPath, requireString(payload.content, "payload.content"), "utf8"); try { + if ((optionalString(command.command) ?? "discord-post") !== "discord-post") { + throw new Error("Unsupported Bifrost bridge command; only discord-post is supported."); + } + await mkdir(tempDir, { recursive: true }); + await writeFile(contentPath, requireString(payload.content, "payload.content"), "utf8"); const args = [ "tools/bifrost-bridge.mjs", - verb, + "discord-post", "--channel-id", requireString(payload.channelId, "payload.channelId"), "--content-file", @@ -162,11 +164,9 @@ async function processOneCommand(node, command, options) { "--identity", optionalString(command.actor?.id) ?? optionalString(payload.identityId) ?? optionalString(payload.personaName) ?? "bifrost", ]; - if (verb === "discord-post") { - pushOption(args, "--persona-name", payload.personaName); - pushOption(args, "--persona-avatar-url", payload.personaAvatarUrl); - pushOption(args, "--reply-to-message-id", payload.replyToMessageId); - } + pushOption(args, "--persona-name", payload.personaName); + pushOption(args, "--persona-avatar-url", payload.personaAvatarUrl); + pushOption(args, "--reply-to-message-id", payload.replyToMessageId); pushOption(args, "--receipt-store", options["receipt-store"]); const result = spawnSync(process.execPath, args, { @@ -181,11 +181,11 @@ async function processOneCommand(node, command, options) { if (result.status !== 0 || result.error) { throw new Error(renderSpawnFailure(result)); } - const posted = parseJson(result.stdout, `bifrost bridge ${verb} receipt`); + const posted = parseJson(result.stdout, "bifrost bridge discord-post receipt"); const receipt = buildReceipt(command, { status: "completed", ok: true, - action: verb, + action: "discord-post", channelId: posted.channelId, messageId: posted.messageId, transport: posted.transport, @@ -205,7 +205,7 @@ async function processOneCommand(node, command, options) { const receipt = buildReceipt(command, { status: "failed", ok: false, - action: verb, + action: "discord-post", canonicalReceiptId: `crossing_${command.commandId}`, error: error instanceof Error ? error.message : String(error), }); @@ -223,18 +223,6 @@ async function processOneCommand(node, command, options) { } } -const supportedBridgeVerbs = new Set(["discord-post"]); - -function resolveBridgeVerb(command) { - const verb = optionalString(command.command) ?? "discord-post"; - if (!supportedBridgeVerbs.has(verb)) { - throw new Error( - `Unsupported Bifrost bridge command "${verb}". Supported: ${[...supportedBridgeVerbs].join(", ")}.`, - ); - } - return verb; -} - function buildReceipt(command, result) { const completedAt = new Date().toISOString(); return { @@ -266,7 +254,7 @@ function listCommandRecords(node) { } return node.cache.getAll(commandDefinition()) .map(unwrapRecord) - .filter((entry) => entry && entry.commandId && supportedBridgeVerbs.has(entry.command ?? "discord-post")); + .filter((entry) => entry && entry.commandId); } async function openCommandNode(storePath) {