diff --git a/.env.schema b/.env.schema index efff7d5..9d90b2e 100644 --- a/.env.schema +++ b/.env.schema @@ -137,11 +137,18 @@ GATEWAY_BROKER_URL= # @sensitive=false @type=url SLACK_BROKER_URL= -# Gateway workspace/team ID registered with broker (preferred) +# Gateway org ID registered with broker (preferred) +# @sensitive=false @type=string +GATEWAY_BROKER_ORG_ID= + +# Legacy alias for GATEWAY_BROKER_ORG_ID +# @sensitive=false @type=string +SLACK_BROKER_ORG_ID= + +# Deprecated workspace/team ID aliases (still accepted for migration) # @sensitive=false @type=string(startsWith=T) GATEWAY_BROKER_WORKSPACE_ID= -# Legacy alias for GATEWAY_BROKER_WORKSPACE_ID # @sensitive=false @type=string(startsWith=T) SLACK_BROKER_WORKSPACE_ID= diff --git a/CONFIGURATION.md b/CONFIGURATION.md index c37875f..28baffa 100644 --- a/CONFIGURATION.md +++ b/CONFIGURATION.md @@ -53,7 +53,7 @@ If you're using the Slack broker OAuth flow, register this server after install: ```bash sudo baudbot broker register \ --broker-url https://your-broker.example.com \ - --workspace-id T0123ABCD \ + --org-id org_1234abcd \ --registration-token ``` @@ -121,8 +121,10 @@ Set by `sudo baudbot broker register` when using brokered Slack OAuth flow. |----------|-------------| | `GATEWAY_BROKER_URL` | **Preferred** broker base URL | | `SLACK_BROKER_URL` | Legacy alias for `GATEWAY_BROKER_URL` (still supported) | -| `GATEWAY_BROKER_WORKSPACE_ID` | **Preferred** Slack workspace/team ID (`T...`) | -| `SLACK_BROKER_WORKSPACE_ID` | Legacy alias for `GATEWAY_BROKER_WORKSPACE_ID` | +| `GATEWAY_BROKER_ORG_ID` | **Preferred** broker org ID | +| `SLACK_BROKER_ORG_ID` | Legacy alias for `GATEWAY_BROKER_ORG_ID` | +| `GATEWAY_BROKER_WORKSPACE_ID` | Deprecated workspace/team ID alias (still accepted for migration) | +| `SLACK_BROKER_WORKSPACE_ID` | Deprecated alias for `GATEWAY_BROKER_WORKSPACE_ID` | | `GATEWAY_BROKER_SERVER_PRIVATE_KEY` | **Preferred** server X25519 private key (base64) | | `SLACK_BROKER_SERVER_PRIVATE_KEY` | Legacy alias for `GATEWAY_BROKER_SERVER_PRIVATE_KEY` | | `GATEWAY_BROKER_SERVER_PUBLIC_KEY` | **Preferred** server X25519 public key (base64) | @@ -254,7 +256,7 @@ SENTRY_CHANNEL_ID=C0987654321 # Gateway broker registration (optional, set by: sudo baudbot broker register) GATEWAY_BROKER_URL=https://broker.example.com -GATEWAY_BROKER_WORKSPACE_ID=T0123ABCD +GATEWAY_BROKER_ORG_ID=org_1234abcd # Optional broker auth token fields (set by broker register when provided) # GATEWAY_BROKER_ACCESS_TOKEN=... # GATEWAY_BROKER_ACCESS_TOKEN_EXPIRES_AT=2026-02-22T22:15:00.000Z diff --git a/bin/baudbot b/bin/baudbot index fef1362..1cfa282 100755 --- a/bin/baudbot +++ b/bin/baudbot @@ -504,11 +504,11 @@ case "$COMMAND_NAME" in exec "$NODE_BIN" "$BAUDBOT_ROOT/bin/broker-register.mjs" "$@" ;; --help|-h|"") - echo "Usage: sudo baudbot broker register [--broker-url URL] [--workspace-id ID] --registration-token TOKEN [--no-restart] [-v|--verbose]" + echo "Usage: sudo baudbot broker register [--broker-url URL] [--org-id ID] --registration-token TOKEN [--no-restart] [-v|--verbose]" ;; *) echo "Unknown broker subcommand: ${1:-}" - echo "Usage: sudo baudbot broker register [--broker-url URL] [--workspace-id ID] --registration-token TOKEN [--no-restart] [-v|--verbose]" + echo "Usage: sudo baudbot broker register [--broker-url URL] [--org-id ID] --registration-token TOKEN [--no-restart] [-v|--verbose]" exit 1 ;; esac diff --git a/bin/broker-register.mjs b/bin/broker-register.mjs index 9e1db42..f7665e1 100755 --- a/bin/broker-register.mjs +++ b/bin/broker-register.mjs @@ -2,9 +2,9 @@ /** * Slack broker registration CLI. * - * Registers this baudbot server with a Slack broker workspace using: + * Registers this baudbot server with a broker org using: * - broker URL - * - workspace ID + * - org ID * - registration token from dashboard callback * * On success, stores broker config and generated server key material in: @@ -22,9 +22,10 @@ import { pathToFileURL } from "node:url"; const { subtle } = webcrypto; -const WORKSPACE_ID_RE = /^T[A-Z0-9]+$/; +const ORG_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:-]*$/; const ENV_KEYS = [ "SLACK_BROKER_URL", + "SLACK_BROKER_ORG_ID", "SLACK_BROKER_WORKSPACE_ID", "SLACK_BROKER_SERVER_PRIVATE_KEY", "SLACK_BROKER_SERVER_PUBLIC_KEY", @@ -47,7 +48,8 @@ export function usageText() { "", "Options:", " --broker-url URL Broker base URL (e.g. https://broker.example.com)", - " --workspace-id ID Slack workspace ID (e.g. T0123ABCD)", + " --org-id ID Broker org ID (e.g. org_1234abcd)", + " --workspace-id ID Deprecated alias for --org-id (supported for compatibility)", " --registration-token TOKEN Registration token from dashboard callback (required)", " --no-restart Skip automatic agent restart after registration", " -v, --verbose Show detailed registration progress", @@ -60,7 +62,7 @@ export function usageText() { export function parseArgs(argv) { const out = { brokerUrl: "", - workspaceId: "", + orgId: "", registrationToken: "", verbose: false, help: false, @@ -95,13 +97,24 @@ export function parseArgs(argv) { continue; } + if (arg.startsWith("--org-id=")) { + out.orgId = arg.slice("--org-id=".length); + continue; + } + if (arg === "--org-id") { + i++; + out.orgId = argv[i] || ""; + continue; + } + + // Backward-compatible CLI alias. if (arg.startsWith("--workspace-id=")) { - out.workspaceId = arg.slice("--workspace-id=".length); + out.orgId = arg.slice("--workspace-id=".length); continue; } if (arg === "--workspace-id") { i++; - out.workspaceId = argv[i] || ""; + out.orgId = argv[i] || ""; continue; } @@ -115,7 +128,6 @@ export function parseArgs(argv) { continue; } - throw new Error(`unknown argument: ${arg}`); } @@ -145,8 +157,14 @@ export function normalizeBrokerUrl(raw) { return parsed.toString().replace(/\/$/, ""); } +export function validateOrgId(orgId) { + const normalized = String(orgId || "").trim(); + return normalized.length > 0 && normalized.length <= 128 && ORG_ID_RE.test(normalized); +} + +// Backward-compatible export for older imports/tests. export function validateWorkspaceId(workspaceId) { - return WORKSPACE_ID_RE.test(String(workspaceId || "")); + return validateOrgId(workspaceId); } function decodeBase64Url(value) { @@ -222,10 +240,10 @@ export function mapRegisterError(status, errorText) { return "registration token already used — re-run OAuth install and use a fresh token"; } if (status === 409 && /already active/i.test(text)) { - return "workspace already active — unregister the current server first"; + return "org already active — unregister the current server first"; } - if (status === 404 && /workspace not found/i.test(text)) { - return "workspace not found — complete broker OAuth install first"; + if (status === 404 && /(workspace|org) not found/i.test(text)) { + return "org not found — complete dashboard registration first"; } if (status >= 500) { return `broker server error (${status}) — ${text}`; @@ -235,7 +253,7 @@ export function mapRegisterError(status, errorText) { export async function registerWithBroker({ brokerUrl, - workspaceId, + orgId, registrationToken, serverKeys, fetchImpl = fetch, @@ -246,13 +264,15 @@ export async function registerWithBroker({ const endpoint = new URL("/api/register", brokerUrl); const registerRequestBody = { - workspace_id: workspaceId, + org_id: orgId, + // Keep workspace_id during migration so older broker APIs still accept this payload. + workspace_id: orgId, server_pubkey: serverKeys.server_pubkey, server_signing_pubkey: serverKeys.server_signing_pubkey, registration_token: registrationToken, }; - logger(`Registering workspace ${workspaceId} at ${endpoint}`); + logger(`Registering org ${orgId} at ${endpoint}`); let registerResponse; try { @@ -262,7 +282,7 @@ export async function registerWithBroker({ body: JSON.stringify(registerRequestBody), }); } catch (err) { - throw new Error(`network failure registering workspace: ${err instanceof Error ? err.message : "unknown error"}`); + throw new Error(`network failure registering org: ${err instanceof Error ? err.message : "unknown error"}`); } let registerResponseBody = {}; @@ -471,11 +491,15 @@ async function collectInputs(parsedArgs) { const brokerUrl = parsedArgs.brokerUrl || existing.SLACK_BROKER_URL + || existing.GATEWAY_BROKER_URL || (await prompt("Broker URL: ")); - const workspaceId = parsedArgs.workspaceId + const orgId = parsedArgs.orgId + || existing.SLACK_BROKER_ORG_ID + || existing.GATEWAY_BROKER_ORG_ID || existing.SLACK_BROKER_WORKSPACE_ID - || (await prompt("Workspace ID (starts with T): ")); + || existing.GATEWAY_BROKER_WORKSPACE_ID + || (await prompt("Org ID: ")); const registrationToken = parsedArgs.registrationToken || (await prompt("Registration token: ")); @@ -485,7 +509,7 @@ async function collectInputs(parsedArgs) { return { brokerUrl: normalizeBrokerUrl(brokerUrl), - workspaceId: workspaceId.trim(), + orgId: orgId.trim(), registrationToken, configTargets, }; @@ -493,13 +517,13 @@ async function collectInputs(parsedArgs) { export async function runRegistration({ brokerUrl, - workspaceId, + orgId, registrationToken, fetchImpl = fetch, logger = () => {}, }) { - if (!validateWorkspaceId(workspaceId)) { - throw new Error("workspace ID must match Slack team ID format (e.g. T0123ABCD)"); + if (!validateOrgId(orgId)) { + throw new Error("org ID is required and must use only letters, numbers, '.', '_', ':', or '-'"); } if (!registrationToken) { @@ -512,7 +536,7 @@ export async function runRegistration({ const serverKeys = await generateServerKeyMaterial(); const registration = await registerWithBroker({ brokerUrl: normalizedBrokerUrl, - workspaceId, + orgId, registrationToken, serverKeys, fetchImpl, @@ -521,7 +545,9 @@ export async function runRegistration({ const updates = { SLACK_BROKER_URL: normalizedBrokerUrl, - SLACK_BROKER_WORKSPACE_ID: workspaceId, + SLACK_BROKER_ORG_ID: orgId, + // Keep workspace key for backward compatibility with older runtimes. + SLACK_BROKER_WORKSPACE_ID: orgId, SLACK_BROKER_SERVER_PRIVATE_KEY: serverKeys.server_private_key, SLACK_BROKER_SERVER_PUBLIC_KEY: serverKeys.server_pubkey, SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: serverKeys.server_signing_private_key, @@ -615,7 +641,7 @@ export async function main(argv = process.argv.slice(2)) { logger("Collecting registration inputs..."); const input = await collectInputs(parsed); - logger(`Using broker ${input.brokerUrl} for workspace ${input.workspaceId}`); + logger(`Using broker ${input.brokerUrl} for org ${input.orgId}`); logger(`Config targets: ${input.configTargets.map((t) => t.path).join(", ")}`); const { updates } = await runRegistration({ ...input, logger }); @@ -646,4 +672,4 @@ if (isMainModule()) { console.error(`❌ ${message}`); process.exit(1); }); -} \ No newline at end of file +} diff --git a/bin/broker-register.test.mjs b/bin/broker-register.test.mjs index e137b83..d76374c 100644 --- a/bin/broker-register.test.mjs +++ b/bin/broker-register.test.mjs @@ -8,6 +8,7 @@ import { pathToFileURL } from "node:url"; import { parseArgs, normalizeBrokerUrl, + validateOrgId, validateWorkspaceId, mapRegisterError, registerWithBroker, @@ -38,15 +39,15 @@ test("parseArgs parses long-form options", () => { const parsed = parseArgs([ "--broker-url", "https://broker.example.com/", - "--workspace-id", - "T123ABC", + "--org-id", + "org_123abc", "--registration-token", "token-xyz", ]); assert.deepEqual(parsed, { brokerUrl: "https://broker.example.com/", - workspaceId: "T123ABC", + orgId: "org_123abc", registrationToken: "token-xyz", verbose: false, help: false, @@ -54,6 +55,11 @@ test("parseArgs parses long-form options", () => { }); }); +test("parseArgs accepts --workspace-id as backward-compatible alias for --org-id", () => { + const parsed = parseArgs(["--workspace-id", "T123ABC"]); + assert.equal(parsed.orgId, "T123ABC"); +}); + test("parseArgs sets verbose=true for -v and --verbose", () => { const short = parseArgs(["-v"]); assert.equal(short.verbose, true); @@ -93,10 +99,12 @@ test("parseArgs rejects legacy auth-code argument", () => { assert.throws(() => parseArgs(["--auth-code", "legacy"]), /unknown argument/); }); -test("validation helpers normalize and enforce broker/workspace formats", () => { +test("validation helpers normalize and enforce broker/org formats", () => { assert.equal(normalizeBrokerUrl("https://broker.example.com/"), "https://broker.example.com"); - assert.equal(validateWorkspaceId("T0ABC123"), true); - assert.equal(validateWorkspaceId("workspace-123"), false); + assert.equal(validateOrgId("org_abc-123"), true); + assert.equal(validateOrgId("T0ABC123"), true); + assert.equal(validateOrgId("bad id with spaces"), false); + assert.equal(validateWorkspaceId("org_legacy_alias"), true); assert.throws(() => normalizeBrokerUrl("ftp://broker.example.com"), /http:\/\/ or https:\/\//); }); @@ -104,7 +112,7 @@ test("validation helpers normalize and enforce broker/workspace formats", () => test("mapRegisterError returns actionable messages", () => { assert.match(mapRegisterError(400, "missing registration proof"), /registration token is required/); assert.match(mapRegisterError(403, "invalid registration token"), /invalid registration token/); - assert.match(mapRegisterError(409, "workspace already active"), /already active/); + assert.match(mapRegisterError(409, "workspace already active"), /org already active/); assert.match(mapRegisterError(500, "oops"), /broker server error/); }); @@ -123,7 +131,8 @@ test("registerWithBroker fetches pubkeys then posts registration payload", async if (String(url).endsWith("/api/register")) { const payload = JSON.parse(init.body); - assert.equal(payload.workspace_id, "TTEST123"); + assert.equal(payload.org_id, "org_test_123"); + assert.equal(payload.workspace_id, "org_test_123"); assert.equal(payload.server_pubkey, FIXTURE_SERVER_KEYS.server_pubkey); assert.equal(payload.server_signing_pubkey, FIXTURE_SERVER_KEYS.server_signing_pubkey); assert.equal(payload.registration_token, "token-abc"); @@ -145,7 +154,7 @@ test("registerWithBroker fetches pubkeys then posts registration payload", async const result = await registerWithBroker({ brokerUrl: "https://broker.example.com", - workspaceId: "TTEST123", + orgId: "org_test_123", registrationToken: "token-abc", serverKeys: FIXTURE_SERVER_KEYS, fetchImpl, @@ -187,7 +196,7 @@ test("registerWithBroker sends registration_token when provided", async () => { await registerWithBroker({ brokerUrl: "https://broker.example.com", - workspaceId: "TTEST123", + orgId: "org_test_123", registrationToken: "token-abc", serverKeys: FIXTURE_SERVER_KEYS, fetchImpl, @@ -234,14 +243,17 @@ test("runRegistration integration path succeeds against live local HTTP server", try { const result = await runRegistration({ brokerUrl, - workspaceId: "TABC12345", + orgId: "org_live_12345", registrationToken: "token-from-dashboard", }); assert.ok(receivedRegisterPayload); - assert.equal(receivedRegisterPayload.workspace_id, "TABC12345"); + assert.equal(receivedRegisterPayload.org_id, "org_live_12345"); + assert.equal(receivedRegisterPayload.workspace_id, "org_live_12345"); assert.equal(receivedRegisterPayload.registration_token, "token-from-dashboard"); assert.equal(receivedRegisterPayload.server_callback_url, undefined); + assert.equal(result.updates.SLACK_BROKER_ORG_ID, "org_live_12345"); + assert.equal(result.updates.SLACK_BROKER_WORKSPACE_ID, "org_live_12345"); assert.ok(result.updates.SLACK_BROKER_SERVER_PRIVATE_KEY); assert.ok(result.updates.SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY); assert.equal(result.updates.SLACK_BROKER_PUBLIC_KEY, brokerPubkey); @@ -278,7 +290,7 @@ test("runRegistration does not write SLACK_BOT_TOKEN even when broker returns en const result = await runRegistration({ brokerUrl: "https://broker.example.com", - workspaceId: "TABC12345", + orgId: "org_bot_token_check", registrationToken: "token-from-dashboard", fetchImpl, }); @@ -290,7 +302,7 @@ test("runRegistration requires registration token", async () => { await assert.rejects( runRegistration({ brokerUrl: "https://broker.example.com", - workspaceId: "TABC12345", + orgId: "org_missing_token", }), /registration token is required/, ); diff --git a/bin/config.sh b/bin/config.sh index 0dd4ef1..363add1 100755 --- a/bin/config.sh +++ b/bin/config.sh @@ -524,7 +524,10 @@ if [ "$SLACK_CHOICE" = "Use baudbot.ai Slack integration (easy)" ]; then else clear_keys \ SLACK_BROKER_URL \ + SLACK_BROKER_ORG_ID \ SLACK_BROKER_WORKSPACE_ID \ + GATEWAY_BROKER_ORG_ID \ + GATEWAY_BROKER_WORKSPACE_ID \ SLACK_BROKER_SERVER_PRIVATE_KEY \ SLACK_BROKER_SERVER_PUBLIC_KEY \ SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY \ @@ -690,6 +693,7 @@ ordered_keys=( KERNEL_API_KEY LINEAR_API_KEY SLACK_BROKER_URL + SLACK_BROKER_ORG_ID SLACK_BROKER_WORKSPACE_ID SLACK_BROKER_SERVER_PRIVATE_KEY SLACK_BROKER_SERVER_PUBLIC_KEY @@ -761,4 +765,4 @@ else fi echo "" echo -e "Next: ${BOLD}sudo baudbot deploy${RESET} to push config to the agent" -echo "" \ No newline at end of file +echo "" diff --git a/bin/config.test.sh b/bin/config.test.sh index 5e4c7e4..1485ef1 100644 --- a/bin/config.test.sh +++ b/bin/config.test.sh @@ -126,11 +126,14 @@ expect_file_contains "rerun keeps existing Anthropic key" "$ENV5" "ANTHROPIC_API # Test 6: Advanced Slack mode clears stale broker registration keys # Input: 1=API key tier, 2=OpenAI, key, 2=advanced Slack, tokens, ... HOME6="$TMPDIR/clear-broker" -write_existing_env "$HOME6" 'OPENAI_API_KEY=sk-old\nSLACK_BROKER_URL=https://broker.example.com\nSLACK_BROKER_WORKSPACE_ID=T0123\nSLACK_BROKER_PUBLIC_KEY=abc\n' +write_existing_env "$HOME6" 'OPENAI_API_KEY=sk-old\nSLACK_BROKER_URL=https://broker.example.com\nSLACK_BROKER_ORG_ID=org_old\nSLACK_BROKER_WORKSPACE_ID=T0123\nGATEWAY_BROKER_ORG_ID=org_old_gateway\nGATEWAY_BROKER_WORKSPACE_ID=Told\nSLACK_BROKER_PUBLIC_KEY=abc\n' run_config "$HOME6" '1\n2\nsk-openai-new\n2\nxoxb-new\nxapp-new\n\nn\nn\n' ENV6="$HOME6/.baudbot/.env" expect_file_not_contains "advanced clears broker URL" "$ENV6" "SLACK_BROKER_URL=" +expect_file_not_contains "advanced clears broker org" "$ENV6" "SLACK_BROKER_ORG_ID=" expect_file_not_contains "advanced clears broker workspace" "$ENV6" "SLACK_BROKER_WORKSPACE_ID=" +expect_file_not_contains "advanced clears gateway broker org" "$ENV6" "GATEWAY_BROKER_ORG_ID=" +expect_file_not_contains "advanced clears gateway broker workspace" "$ENV6" "GATEWAY_BROKER_WORKSPACE_ID=" expect_file_contains "advanced retains socket bot token" "$ENV6" "SLACK_BOT_TOKEN=xoxb-new" # Test 7: Subscription login tier with existing auth.json skips OAuth flow diff --git a/bin/doctor.sh b/bin/doctor.sh index 2faad25..3d792eb 100755 --- a/bin/doctor.sh +++ b/bin/doctor.sh @@ -223,7 +223,6 @@ if [ -f "$ENV_FILE" ]; then BROKER_REQUIRED_PAIRS=( "GATEWAY_BROKER_URL:SLACK_BROKER_URL" - "GATEWAY_BROKER_WORKSPACE_ID:SLACK_BROKER_WORKSPACE_ID" "GATEWAY_BROKER_SERVER_PRIVATE_KEY:SLACK_BROKER_SERVER_PRIVATE_KEY" "GATEWAY_BROKER_SERVER_PUBLIC_KEY:SLACK_BROKER_SERVER_PUBLIC_KEY" "GATEWAY_BROKER_SERVER_SIGNING_PRIVATE_KEY:SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY" @@ -232,6 +231,11 @@ if [ -f "$ENV_FILE" ]; then ) BROKER_MODE_READY=true + if [ -z "$(read_first_env_value GATEWAY_BROKER_ORG_ID SLACK_BROKER_ORG_ID)" ] \ + && [ -z "$(read_first_env_value GATEWAY_BROKER_WORKSPACE_ID SLACK_BROKER_WORKSPACE_ID)" ]; then + BROKER_MODE_READY=false + fi + for pair in "${BROKER_REQUIRED_PAIRS[@]}"; do IFS=':' read -r preferred_key legacy_key <<<"$pair" if [ -z "$(read_first_env_value "$preferred_key" "$legacy_key")" ]; then diff --git a/bin/lib/baudbot-runtime.sh b/bin/lib/baudbot-runtime.sh index 68ce612..2e89917 100644 --- a/bin/lib/baudbot-runtime.sh +++ b/bin/lib/baudbot-runtime.sh @@ -65,7 +65,7 @@ broker_mode_configured() { local env_file="/home/${1:-baudbot_agent}/.config/.env" [ -r "$env_file" ] || return 1 grep -Eq '^(GATEWAY_BROKER_URL|SLACK_BROKER_URL)=[^[:space:]].*$' "$env_file" || return 1 - grep -Eq '^(GATEWAY_BROKER_WORKSPACE_ID|SLACK_BROKER_WORKSPACE_ID)=[^[:space:]].*$' "$env_file" || return 1 + grep -Eq '^(GATEWAY_BROKER_ORG_ID|SLACK_BROKER_ORG_ID|GATEWAY_BROKER_WORKSPACE_ID|SLACK_BROKER_WORKSPACE_ID)=[^[:space:]].*$' "$env_file" || return 1 } print_broker_connection_status() { diff --git a/docs/operations.md b/docs/operations.md index 1db80e2..e85e209 100644 --- a/docs/operations.md +++ b/docs/operations.md @@ -83,10 +83,10 @@ sudo baudbot env sync --restart ## Slack broker registration ```bash -# Register this server to a broker workspace (after OAuth callback) +# Register this server to a broker org (after dashboard registration) sudo baudbot broker register \ --broker-url https://your-broker.example.com \ - --workspace-id T0123ABCD \ + --org-id org_1234abcd \ --registration-token ``` diff --git a/gateway-bridge/broker-bridge.mjs b/gateway-bridge/broker-bridge.mjs index 797e81c..ab3c6c5 100755 --- a/gateway-bridge/broker-bridge.mjs +++ b/gateway-bridge/broker-bridge.mjs @@ -129,7 +129,7 @@ for (const warning of gatewayAliasWarnings) { for (const key of [ "SLACK_BROKER_URL", - "SLACK_BROKER_WORKSPACE_ID", + "SLACK_BROKER_ORG_ID", "SLACK_BROKER_SERVER_PRIVATE_KEY", "SLACK_BROKER_SERVER_PUBLIC_KEY", "SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY", @@ -154,7 +154,7 @@ const GITHUB_IGNORED_USERS = parseIgnoredUsers(process.env.GITHUB_IGNORED_USERS) const slackRateLimiter = createRateLimiter({ maxRequests: 5, windowMs: 60_000 }); const apiRateLimiter = createRateLimiter({ maxRequests: 30, windowMs: 60_000 }); -const workspaceId = process.env.SLACK_BROKER_WORKSPACE_ID; +const brokerOrgId = String(process.env.SLACK_BROKER_ORG_ID || process.env.SLACK_BROKER_WORKSPACE_ID || "").trim(); const brokerBaseUrl = String(process.env.SLACK_BROKER_URL || "").replace(/\/$/, ""); const brokerAccessToken = String(process.env.SLACK_BROKER_ACCESS_TOKEN || "").trim(); const brokerAccessTokenExpiresAt = String(process.env.SLACK_BROKER_ACCESS_TOKEN_EXPIRES_AT || "").trim(); @@ -210,7 +210,8 @@ const brokerHealth = { updated_at: new Date().toISOString(), outbound_mode: outboundMode, broker_url: brokerBaseUrl, - workspace_id: workspaceId, + org_id: brokerOrgId, + workspace_id: brokerOrgId, poll: { last_ok_at: null, last_error_at: null, @@ -548,7 +549,7 @@ function getThreadId(channel, threadTs) { function signProtocolRequest(action, timestamp, protocolRequestPayload) { const canonical = canonicalizeProtocolRequest( - workspaceId, + brokerOrgId, INBOX_PROTOCOL_VERSION, action, timestamp, @@ -626,7 +627,8 @@ async function pullInbox() { const signature = signPullRequest(timestamp, MAX_MESSAGES, BROKER_WAIT_SECONDS); const inboxPullRequestBody = { - workspace_id: workspaceId, + org_id: brokerOrgId, + workspace_id: brokerOrgId, protocol_version: INBOX_PROTOCOL_VERSION, max_messages: MAX_MESSAGES, wait_seconds: BROKER_WAIT_SECONDS, @@ -646,7 +648,8 @@ async function ackInbox(messageIds) { const signature = signProtocolRequest("inbox.ack", timestamp, { message_ids: messageIds }); await brokerFetch("/api/inbox/ack", { - workspace_id: workspaceId, + org_id: brokerOrgId, + workspace_id: brokerOrgId, protocol_version: INBOX_PROTOCOL_VERSION, message_ids: messageIds, timestamp, @@ -671,14 +674,15 @@ async function sendViaBroker({ action, routing, actionRequestBody }) { // Sign over full send payload (routing + nonce) to match broker's // canonicalizeSendRequest() from modem-dev/baudbot-services#12. const canonical = canonicalizeSendRequest( - workspaceId, action, timestamp, encryptedBody, nonceB64, routing, + brokerOrgId, action, timestamp, encryptedBody, nonceB64, routing, ); const sig = sodium.crypto_sign_detached(canonical, cryptoState.serverSignSecretKey); const signature = toBase64(sig); try { const result = await brokerFetch("/api/send", { - workspace_id: workspaceId, + org_id: brokerOrgId, + workspace_id: brokerOrgId, action, routing, encrypted_body: encryptedBody, @@ -794,8 +798,13 @@ function pruneDedupe() { } function verifyBrokerEnvelope(message) { + const routingId = String(message?.org_id || message?.workspace_id || ""); + if (!routingId) { + throw new Error(`missing broker envelope org_id/workspace_id (message_id: ${message?.message_id || "unknown"})`); + } + const canonical = canonicalizeEnvelope( - message.workspace_id, + routingId, message.broker_timestamp, message.encrypted, ); @@ -812,7 +821,7 @@ function decryptEnvelope(message) { cryptoState.serverBoxPublicKey, cryptoState.serverBoxSecretKey, ); - } catch (err) { + } catch { // Wrap libsodium errors (e.g., "incorrect key pair for the given ciphertext") // into a format that isPoisonMessageError() can detect throw new Error(`failed to decrypt broker envelope (message_id: ${message.message_id || "unknown"})`); @@ -825,7 +834,11 @@ function decryptEnvelope(message) { function isPoisonMessageError(err) { const message = err instanceof Error ? err.message : String(err); - return message.includes("invalid broker envelope signature") || message.includes("failed to decrypt broker envelope"); + return ( + message.includes("invalid broker envelope signature") + || message.includes("failed to decrypt broker envelope") + || message.includes("missing broker envelope org_id/workspace_id") + ); } function isGenericEnvelope(payload) { @@ -1330,7 +1343,7 @@ async function startPollLoop() { logInfo("⚡ Gateway bridge (broker pull mode) is running!"); logInfo(` outbound mode: ${outboundMode} (via broker)`); logInfo(` broker: ${brokerBaseUrl}`); - logInfo(` workspace: ${workspaceId}`); + logInfo(` org: ${brokerOrgId}`); logInfo(` inbox protocol: ${INBOX_PROTOCOL_VERSION}`); logInfo(` broker auth token: ${brokerAccessToken ? "configured" : "not configured"}`); logInfo( @@ -1340,4 +1353,4 @@ async function startPollLoop() { logInfo(` allowed users: ${ALLOWED_USERS.length || "all"}`); logInfo(` pi socket: ${socketPath || "(not found — will retry on message)"}`); await startPollLoop(); -})(); \ No newline at end of file +})(); diff --git a/gateway-bridge/crypto.mjs b/gateway-bridge/crypto.mjs index 70aa4f1..d2a2589 100644 --- a/gateway-bridge/crypto.mjs +++ b/gateway-bridge/crypto.mjs @@ -51,6 +51,9 @@ export function canonicalizeOutbound(workspace, action, timestamp, encryptedBody * * Uses deterministic JSON serialization (sorted keys) to match broker * json-stable-stringify canonicalization. + * + * Note: payload key remains `workspace_id` for wire compatibility, even when + * the routing identifier value is an org ID. */ export function canonicalizeProtocolRequest(workspace, protocolVersion, action, timestamp, payload) { return utf8Bytes( @@ -73,6 +76,9 @@ export function canonicalizeProtocolRequest(workspace, protocolVersion, action, * * Uses deterministic JSON serialization (sorted keys) so both sides produce * identical canonical bytes regardless of object key insertion order. + * + * Note: payload key remains `workspace_id` for wire compatibility, even when + * the routing identifier value is an org ID. */ export function canonicalizeSendRequest(ws, action, timestamp, encryptedBody, nonce, routing) { return utf8Bytes( diff --git a/gateway-bridge/env-aliases.mjs b/gateway-bridge/env-aliases.mjs index 5b3cf61..23c7578 100644 --- a/gateway-bridge/env-aliases.mjs +++ b/gateway-bridge/env-aliases.mjs @@ -13,6 +13,7 @@ export const GATEWAY_ENV_ALIAS_PAIRS = [ ["GATEWAY_ALLOWED_USERS", "SLACK_ALLOWED_USERS"], ["GATEWAY_CHANNEL_ID", "SLACK_CHANNEL_ID"], ["GATEWAY_BROKER_URL", "SLACK_BROKER_URL"], + ["GATEWAY_BROKER_ORG_ID", "SLACK_BROKER_ORG_ID"], ["GATEWAY_BROKER_WORKSPACE_ID", "SLACK_BROKER_WORKSPACE_ID"], ["GATEWAY_BROKER_SERVER_PRIVATE_KEY", "SLACK_BROKER_SERVER_PRIVATE_KEY"], ["GATEWAY_BROKER_SERVER_PUBLIC_KEY", "SLACK_BROKER_SERVER_PUBLIC_KEY"], @@ -66,6 +67,47 @@ export function resolveGatewayEnvAliases(env = process.env) { ); } + const gatewayOrgId = env.GATEWAY_BROKER_ORG_ID; + const slackOrgId = env.SLACK_BROKER_ORG_ID; + const gatewayWorkspaceId = env.GATEWAY_BROKER_WORKSPACE_ID; + const slackWorkspaceId = env.SLACK_BROKER_WORKSPACE_ID; + + const hasGatewayOrgId = hasConfiguredValue(gatewayOrgId); + const hasSlackOrgId = hasConfiguredValue(slackOrgId); + const hasGatewayWorkspaceId = hasConfiguredValue(gatewayWorkspaceId); + const hasSlackWorkspaceId = hasConfiguredValue(slackWorkspaceId); + + if (hasGatewayOrgId && hasGatewayWorkspaceId && String(gatewayOrgId) !== String(gatewayWorkspaceId)) { + warnings.push( + "⚠️ Both GATEWAY_BROKER_ORG_ID and GATEWAY_BROKER_WORKSPACE_ID are set; using GATEWAY_BROKER_ORG_ID.", + ); + } + if (hasSlackOrgId && hasSlackWorkspaceId && String(slackOrgId) !== String(slackWorkspaceId)) { + warnings.push( + "⚠️ Both SLACK_BROKER_ORG_ID and SLACK_BROKER_WORKSPACE_ID are set; using SLACK_BROKER_ORG_ID.", + ); + } + + let brokerOrgId = ""; + if (hasGatewayOrgId) { + brokerOrgId = String(gatewayOrgId); + } else if (hasSlackOrgId) { + brokerOrgId = String(slackOrgId); + } else if (hasGatewayWorkspaceId) { + brokerOrgId = String(gatewayWorkspaceId); + warnings.push("⚠️ GATEWAY_BROKER_WORKSPACE_ID is deprecated; use GATEWAY_BROKER_ORG_ID."); + } else if (hasSlackWorkspaceId) { + brokerOrgId = String(slackWorkspaceId); + warnings.push("⚠️ SLACK_BROKER_WORKSPACE_ID is deprecated; use GATEWAY_BROKER_ORG_ID/SLACK_BROKER_ORG_ID."); + } + + if (brokerOrgId) { + // Canonical runtime value for broker identity. + resolved.SLACK_BROKER_ORG_ID = brokerOrgId; + // Keep legacy workspace key populated for older runtime paths still expecting it. + resolved.SLACK_BROKER_WORKSPACE_ID = brokerOrgId; + } + return { resolved, warnings }; } diff --git a/gateway-bridge/env-aliases.test.mjs b/gateway-bridge/env-aliases.test.mjs index aee95f7..e14f26e 100644 --- a/gateway-bridge/env-aliases.test.mjs +++ b/gateway-bridge/env-aliases.test.mjs @@ -64,3 +64,40 @@ test("applyGatewayEnvAliases mutates env with resolved canonical legacy keys", ( assert.equal(env.SLACK_BROKER_URL, "https://broker.gateway.example"); assert.equal(result.warnings.length, 0); }); + +test("maps GATEWAY_BROKER_ORG_ID to SLACK_BROKER_ORG_ID and legacy workspace key", () => { + const env = { + GATEWAY_BROKER_ORG_ID: "org_abc123", + }; + + const result = applyGatewayEnvAliases(env); + + assert.equal(env.SLACK_BROKER_ORG_ID, "org_abc123"); + assert.equal(env.SLACK_BROKER_WORKSPACE_ID, "org_abc123"); + assert.equal(result.warnings.length, 0); +}); + +test("falls back from deprecated workspace id when org id is absent", () => { + const env = { + SLACK_BROKER_WORKSPACE_ID: "T123LEGACY", + }; + + const result = resolveGatewayEnvAliases(env); + + assert.equal(result.resolved.SLACK_BROKER_ORG_ID, "T123LEGACY"); + assert.equal(result.resolved.SLACK_BROKER_WORKSPACE_ID, "T123LEGACY"); + assert.ok(result.warnings.some((warning) => warning.includes("SLACK_BROKER_WORKSPACE_ID is deprecated"))); +}); + +test("prefers org id over workspace id when both are set", () => { + const env = { + GATEWAY_BROKER_ORG_ID: "org_preferred", + GATEWAY_BROKER_WORKSPACE_ID: "Tlegacy_should_not_win", + }; + + const result = resolveGatewayEnvAliases(env); + + assert.equal(result.resolved.SLACK_BROKER_ORG_ID, "org_preferred"); + assert.equal(result.resolved.SLACK_BROKER_WORKSPACE_ID, "org_preferred"); + assert.ok(result.warnings.some((warning) => warning.includes("using GATEWAY_BROKER_ORG_ID"))); +}); diff --git a/install.sh b/install.sh index 12d6ff3..1227394 100755 --- a/install.sh +++ b/install.sh @@ -296,7 +296,7 @@ if has_env_any GATEWAY_BOT_TOKEN SLACK_BOT_TOKEN \ fi if has_env_any GATEWAY_BROKER_URL SLACK_BROKER_URL \ - && has_env_any GATEWAY_BROKER_WORKSPACE_ID SLACK_BROKER_WORKSPACE_ID \ + && (has_env_any GATEWAY_BROKER_ORG_ID SLACK_BROKER_ORG_ID || has_env_any GATEWAY_BROKER_WORKSPACE_ID SLACK_BROKER_WORKSPACE_ID) \ && has_env_any GATEWAY_BROKER_SERVER_PRIVATE_KEY SLACK_BROKER_SERVER_PRIVATE_KEY \ && has_env_any GATEWAY_BROKER_SERVER_PUBLIC_KEY SLACK_BROKER_SERVER_PUBLIC_KEY \ && has_env_any GATEWAY_BROKER_SERVER_SIGNING_PRIVATE_KEY SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY \ diff --git a/pi/skills/control-agent/startup-pi.sh b/pi/skills/control-agent/startup-pi.sh index 091ef23..2d63d78 100755 --- a/pi/skills/control-agent/startup-pi.sh +++ b/pi/skills/control-agent/startup-pi.sh @@ -109,7 +109,7 @@ fi BRIDGE_SCRIPT="" if [ -f "$BRIDGE_DIR/broker-bridge.mjs" ] && varlock run --path "$HOME/.config/" -- sh -c ' test -n "${GATEWAY_BROKER_URL:-${SLACK_BROKER_URL:-}}" && - test -n "${GATEWAY_BROKER_WORKSPACE_ID:-${SLACK_BROKER_WORKSPACE_ID:-}}" && + ( test -n "${GATEWAY_BROKER_ORG_ID:-${SLACK_BROKER_ORG_ID:-}}" || test -n "${GATEWAY_BROKER_WORKSPACE_ID:-${SLACK_BROKER_WORKSPACE_ID:-}}" ) && test -n "${GATEWAY_BROKER_SERVER_PRIVATE_KEY:-${SLACK_BROKER_SERVER_PRIVATE_KEY:-}}" && test -n "${GATEWAY_BROKER_SERVER_PUBLIC_KEY:-${SLACK_BROKER_SERVER_PUBLIC_KEY:-}}" && test -n "${GATEWAY_BROKER_SERVER_SIGNING_PRIVATE_KEY:-${SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY:-}}" && diff --git a/test/broker-bridge.integration.test.mjs b/test/broker-bridge.integration.test.mjs index 60d7a57..06bfff6 100644 --- a/test/broker-bridge.integration.test.mjs +++ b/test/broker-bridge.integration.test.mjs @@ -131,7 +131,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...cleanEnv(), SLACK_BROKER_URL: `http://127.0.0.1:${brokerAddress.port}`, - SLACK_BROKER_WORKSPACE_ID: "T123BROKER", + SLACK_BROKER_ORG_ID: "T123BROKER", SLACK_BROKER_SERVER_PRIVATE_KEY: b64(32, 11), SLACK_BROKER_SERVER_PUBLIC_KEY: b64(32, 12), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: b64(32, 13), @@ -196,7 +196,7 @@ describe("broker pull bridge semi-integration", () => { const messages = pullCount === 1 ? [{ message_id: "m-poison-1", - workspace_id: "T123BROKER", + org_id: "T123BROKER", encrypted: b64(64), broker_timestamp: Math.floor(Date.now() / 1000), // valid-length signature bytes, but not valid for payload/key @@ -252,7 +252,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...cleanEnv(), SLACK_BROKER_URL: brokerUrl, - SLACK_BROKER_WORKSPACE_ID: "T123BROKER", + SLACK_BROKER_ORG_ID: "T123BROKER", SLACK_BROKER_SERVER_PRIVATE_KEY: b64(32, 11), SLACK_BROKER_SERVER_PUBLIC_KEY: b64(32, 12), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: b64(32, 13), @@ -295,6 +295,7 @@ describe("broker pull bridge semi-integration", () => { await Promise.race([ackWait, bridgeExited]); + expect(ackPayload.org_id).toBe("T123BROKER"); expect(ackPayload.workspace_id).toBe("T123BROKER"); expect(ackPayload.protocol_version).toBe("2026-02-1"); expect(ackPayload.message_ids).toContain("m-poison-1"); @@ -390,7 +391,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...cleanEnv(), SLACK_BROKER_URL: brokerUrl, - SLACK_BROKER_WORKSPACE_ID: "T123BROKER", + SLACK_BROKER_ORG_ID: "T123BROKER", SLACK_BROKER_SERVER_PRIVATE_KEY: toBase64(serverBoxKeypair.privateKey), SLACK_BROKER_SERVER_PUBLIC_KEY: toBase64(serverBoxKeypair.publicKey), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: b64(32, 13), @@ -433,6 +434,7 @@ describe("broker pull bridge semi-integration", () => { await Promise.race([ackWait, bridgeExited]); + expect(ackPayload.org_id).toBe("T123BROKER"); expect(ackPayload.workspace_id).toBe("T123BROKER"); expect(ackPayload.protocol_version).toBe("2026-02-1"); expect(ackPayload.message_ids).toContain("m-decrypt-fail-1"); @@ -482,7 +484,7 @@ describe("broker pull bridge semi-integration", () => { const brokerSign = sodium.crypto_sign_keypair(); const serverSignSeed = sodium.randombytes_buf(sodium.crypto_sign_SEEDBYTES); - const workspaceId = "T123BROKER"; + const orgId = "T123BROKER"; const eventPayload = { type: "event_callback", event: { @@ -502,7 +504,7 @@ describe("broker pull bridge semi-integration", () => { const encryptedB64 = toBase64(encrypted); const brokerSignature = toBase64( sodium.crypto_sign_detached( - canonicalizeEnvelope(workspaceId, brokerTimestamp, encryptedB64), + canonicalizeEnvelope(orgId, brokerTimestamp, encryptedB64), brokerSign.privateKey, ), ); @@ -517,7 +519,7 @@ describe("broker pull bridge semi-integration", () => { const messages = pullCount === 1 ? [{ message_id: "m-valid-1", - workspace_id: workspaceId, + workspace_id: orgId, encrypted: encryptedB64, broker_timestamp: brokerTimestamp, broker_signature: brokerSignature, @@ -571,7 +573,7 @@ describe("broker pull bridge semi-integration", () => { HOME: tempHome, PI_SESSION_ID: sessionId, SLACK_BROKER_URL: brokerUrl, - SLACK_BROKER_WORKSPACE_ID: workspaceId, + SLACK_BROKER_ORG_ID: orgId, SLACK_BROKER_SERVER_PRIVATE_KEY: toBase64(serverBox.privateKey), SLACK_BROKER_SERVER_PUBLIC_KEY: toBase64(serverBox.publicKey), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: toBase64(serverSignSeed), @@ -615,7 +617,8 @@ describe("broker pull bridge semi-integration", () => { await Promise.race([completeWait, bridgeExited]); - expect(ackPayload.workspace_id).toBe(workspaceId); + expect(ackPayload.org_id).toBe(orgId); + expect(ackPayload.workspace_id).toBe(orgId); expect(ackPayload.message_ids).toContain("m-valid-1"); expect(receivedCommands.length).toBe(1); @@ -638,7 +641,7 @@ describe("broker pull bridge semi-integration", () => { it("uses protocol-versioned inbox.pull signatures with wait_seconds by default", async () => { await sodium.ready; - const workspaceId = "T123BROKER"; + const orgId = "T123BROKER"; const signingSeed = Buffer.alloc(32, 21); const signKeypair = sodium.crypto_sign_seed_keypair(new Uint8Array(signingSeed)); let pullPayload = null; @@ -707,7 +710,7 @@ describe("broker pull bridge semi-integration", () => { ...cleanEnv(), HOME: tempHome, SLACK_BROKER_URL: brokerUrl, - SLACK_BROKER_WORKSPACE_ID: workspaceId, + SLACK_BROKER_ORG_ID: orgId, SLACK_BROKER_SERVER_PRIVATE_KEY: b64(32, 11), SLACK_BROKER_SERVER_PUBLIC_KEY: b64(32, 12), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: signingSeed.toString("base64"), @@ -724,7 +727,8 @@ describe("broker pull bridge semi-integration", () => { await waitFor(() => pullPayload !== null, 10_000, 50, "timeout waiting for inbox pull request"); - expect(pullPayload.workspace_id).toBe(workspaceId); + expect(pullPayload.org_id).toBe(orgId); + expect(pullPayload.workspace_id).toBe(orgId); expect(pullPayload.protocol_version).toBe("2026-02-1"); expect(pullPayload.max_messages).toBe(10); expect(pullPayload.wait_seconds).toBe(20); @@ -746,7 +750,7 @@ describe("broker pull bridge semi-integration", () => { expect(pullPayload.meta.session_total_tokens).toBe(54321); expect(pullPayload.meta.session_total_cost_usd).toBe(1.25); - const canonical = canonicalizeProtocolRequest(workspaceId, "2026-02-1", "inbox.pull", pullPayload.timestamp, { + const canonical = canonicalizeProtocolRequest(orgId, "2026-02-1", "inbox.pull", pullPayload.timestamp, { max_messages: 10, wait_seconds: 20, }); @@ -760,7 +764,7 @@ describe("broker pull bridge semi-integration", () => { it("uses protocol-versioned inbox.pull signature with wait_seconds=0", async () => { await sodium.ready; - const workspaceId = "T123BROKER"; + const orgId = "T123BROKER"; const signingSeed = Buffer.alloc(32, 22); const signKeypair = sodium.crypto_sign_seed_keypair(new Uint8Array(signingSeed)); let pullPayload = null; @@ -811,7 +815,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...cleanEnv(), SLACK_BROKER_URL: brokerUrl, - SLACK_BROKER_WORKSPACE_ID: workspaceId, + SLACK_BROKER_ORG_ID: orgId, SLACK_BROKER_SERVER_PRIVATE_KEY: b64(32, 11), SLACK_BROKER_SERVER_PUBLIC_KEY: b64(32, 12), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: signingSeed.toString("base64"), @@ -829,12 +833,13 @@ describe("broker pull bridge semi-integration", () => { await waitFor(() => pullPayload !== null, 10_000, 50, "timeout waiting for protocol inbox pull request"); - expect(pullPayload.workspace_id).toBe(workspaceId); + expect(pullPayload.org_id).toBe(orgId); + expect(pullPayload.workspace_id).toBe(orgId); expect(pullPayload.protocol_version).toBe("2026-02-1"); expect(pullPayload.max_messages).toBe(10); expect(pullPayload.wait_seconds).toBe(0); - const canonical = canonicalizeProtocolRequest(workspaceId, "2026-02-1", "inbox.pull", pullPayload.timestamp, { + const canonical = canonicalizeProtocolRequest(orgId, "2026-02-1", "inbox.pull", pullPayload.timestamp, { max_messages: 10, wait_seconds: 0, }); @@ -848,7 +853,7 @@ describe("broker pull bridge semi-integration", () => { it("clamps max_messages before signing pull requests", async () => { await sodium.ready; - const workspaceId = "T123BROKER"; + const orgId = "T123BROKER"; const signingSeed = Buffer.alloc(32, 23); const signKeypair = sodium.crypto_sign_seed_keypair(new Uint8Array(signingSeed)); let pullPayload = null; @@ -899,7 +904,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...cleanEnv(), SLACK_BROKER_URL: brokerUrl, - SLACK_BROKER_WORKSPACE_ID: workspaceId, + SLACK_BROKER_ORG_ID: orgId, SLACK_BROKER_SERVER_PRIVATE_KEY: b64(32, 11), SLACK_BROKER_SERVER_PUBLIC_KEY: b64(32, 12), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: signingSeed.toString("base64"), @@ -917,12 +922,13 @@ describe("broker pull bridge semi-integration", () => { await waitFor(() => pullPayload !== null, 10_000, 50, "timeout waiting for clamped inbox pull request"); - expect(pullPayload.workspace_id).toBe(workspaceId); + expect(pullPayload.org_id).toBe(orgId); + expect(pullPayload.workspace_id).toBe(orgId); expect(pullPayload.protocol_version).toBe("2026-02-1"); expect(pullPayload.max_messages).toBe(100); expect(pullPayload.wait_seconds).toBe(20); - const canonical = canonicalizeProtocolRequest(workspaceId, "2026-02-1", "inbox.pull", pullPayload.timestamp, { + const canonical = canonicalizeProtocolRequest(orgId, "2026-02-1", "inbox.pull", pullPayload.timestamp, { max_messages: 100, wait_seconds: 20, }); @@ -936,7 +942,7 @@ describe("broker pull bridge semi-integration", () => { it("sends broker bearer token when configured", async () => { await sodium.ready; - const workspaceId = "T123BROKER"; + const orgId = "T123BROKER"; const bridgeApiPort = await reserveFreePort(); let outboundAuthorization = null; @@ -983,7 +989,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...cleanEnv(), SLACK_BROKER_URL: brokerUrl, - SLACK_BROKER_WORKSPACE_ID: workspaceId, + SLACK_BROKER_ORG_ID: orgId, SLACK_BROKER_SERVER_PRIVATE_KEY: b64(32, 11), SLACK_BROKER_SERVER_PUBLIC_KEY: b64(32, 12), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: Buffer.alloc(32, 24).toString("base64"), @@ -1040,7 +1046,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...envWithoutBrokerToken, SLACK_BROKER_URL: "http://127.0.0.1:65535", - SLACK_BROKER_WORKSPACE_ID: "T123BROKER", + SLACK_BROKER_ORG_ID: "T123BROKER", SLACK_BROKER_SERVER_PRIVATE_KEY: b64(32, 11), SLACK_BROKER_SERVER_PUBLIC_KEY: b64(32, 12), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: Buffer.alloc(32, 26).toString("base64"), @@ -1085,7 +1091,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...process.env, SLACK_BROKER_URL: "http://127.0.0.1:65535", - SLACK_BROKER_WORKSPACE_ID: "T123BROKER", + SLACK_BROKER_ORG_ID: "T123BROKER", SLACK_BROKER_SERVER_PRIVATE_KEY: b64(32, 11), SLACK_BROKER_SERVER_PUBLIC_KEY: b64(32, 12), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: Buffer.alloc(32, 25).toString("base64"), @@ -1157,7 +1163,7 @@ describe("broker pull bridge semi-integration", () => { const brokerSign = sodium.crypto_sign_keypair(); const serverSignSeed = sodium.randombytes_buf(sodium.crypto_sign_SEEDBYTES); - const testWorkspaceId = "T123BROKER"; + const testOrgId = "T123BROKER"; // Generic envelope wrapping a Slack event_callback const genericEnvelope = { @@ -1184,7 +1190,7 @@ describe("broker pull bridge semi-integration", () => { const encryptedB64 = toBase64(encrypted); const brokerSignature = toBase64( sodium.crypto_sign_detached( - canonicalizeEnvelope(testWorkspaceId, brokerTimestamp, encryptedB64), + canonicalizeEnvelope(testOrgId, brokerTimestamp, encryptedB64), brokerSign.privateKey, ), ); @@ -1198,7 +1204,7 @@ describe("broker pull bridge semi-integration", () => { const messages = pullCount === 1 ? [{ message_id: "m-generic-slack-1", - workspace_id: testWorkspaceId, + workspace_id: testOrgId, encrypted: encryptedB64, broker_timestamp: brokerTimestamp, broker_signature: brokerSignature, @@ -1247,7 +1253,7 @@ describe("broker pull bridge semi-integration", () => { HOME: tempHome, PI_SESSION_ID: sessionId, SLACK_BROKER_URL: `http://127.0.0.1:${address.port}`, - SLACK_BROKER_WORKSPACE_ID: testWorkspaceId, + SLACK_BROKER_ORG_ID: testOrgId, SLACK_BROKER_SERVER_PRIVATE_KEY: toBase64(serverBox.privateKey), SLACK_BROKER_SERVER_PUBLIC_KEY: toBase64(serverBox.publicKey), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: toBase64(serverSignSeed), @@ -1314,7 +1320,7 @@ describe("broker pull bridge semi-integration", () => { const brokerSign = sodium.crypto_sign_keypair(); const serverSignSeed = sodium.randombytes_buf(sodium.crypto_sign_SEEDBYTES); - const testWorkspaceId = "T123BROKER"; + const testOrgId = "T123BROKER"; const dashboardEnvelope = { source: "dashboard", @@ -1334,7 +1340,7 @@ describe("broker pull bridge semi-integration", () => { const encryptedB64 = toBase64(encrypted); const brokerSignature = toBase64( sodium.crypto_sign_detached( - canonicalizeEnvelope(testWorkspaceId, brokerTimestamp, encryptedB64), + canonicalizeEnvelope(testOrgId, brokerTimestamp, encryptedB64), brokerSign.privateKey, ), ); @@ -1348,7 +1354,7 @@ describe("broker pull bridge semi-integration", () => { const messages = pullCount === 1 ? [{ message_id: "m-dashboard-1", - workspace_id: testWorkspaceId, + workspace_id: testOrgId, encrypted: encryptedB64, broker_timestamp: brokerTimestamp, broker_signature: brokerSignature, @@ -1395,7 +1401,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...cleanEnv(), SLACK_BROKER_URL: `http://127.0.0.1:${address.port}`, - SLACK_BROKER_WORKSPACE_ID: testWorkspaceId, + SLACK_BROKER_ORG_ID: testOrgId, SLACK_BROKER_SERVER_PRIVATE_KEY: toBase64(serverBox.privateKey), SLACK_BROKER_SERVER_PUBLIC_KEY: toBase64(serverBox.publicKey), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: toBase64(serverSignSeed), @@ -1454,7 +1460,7 @@ describe("broker pull bridge semi-integration", () => { const brokerSign = sodium.crypto_sign_keypair(); const serverSignSeed = sodium.randombytes_buf(sodium.crypto_sign_SEEDBYTES); - const testWorkspaceId = "T123BROKER"; + const testOrgId = "T123BROKER"; const unknownEnvelope = { source: "future_service", @@ -1471,7 +1477,7 @@ describe("broker pull bridge semi-integration", () => { const encryptedB64 = toBase64(encrypted); const brokerSignature = toBase64( sodium.crypto_sign_detached( - canonicalizeEnvelope(testWorkspaceId, brokerTimestamp, encryptedB64), + canonicalizeEnvelope(testOrgId, brokerTimestamp, encryptedB64), brokerSign.privateKey, ), ); @@ -1485,7 +1491,7 @@ describe("broker pull bridge semi-integration", () => { const messages = pullCount === 1 ? [{ message_id: "m-unknown-src-1", - workspace_id: testWorkspaceId, + workspace_id: testOrgId, encrypted: encryptedB64, broker_timestamp: brokerTimestamp, broker_signature: brokerSignature, @@ -1532,7 +1538,7 @@ describe("broker pull bridge semi-integration", () => { env: { ...cleanEnv(), SLACK_BROKER_URL: `http://127.0.0.1:${address.port}`, - SLACK_BROKER_WORKSPACE_ID: testWorkspaceId, + SLACK_BROKER_ORG_ID: testOrgId, SLACK_BROKER_SERVER_PRIVATE_KEY: toBase64(serverBox.privateKey), SLACK_BROKER_SERVER_PUBLIC_KEY: toBase64(serverBox.publicKey), SLACK_BROKER_SERVER_SIGNING_PRIVATE_KEY: toBase64(serverSignSeed),