From 22c7f659f62654adc636c0ff1441ed899b957ee0 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 18 Jan 2026 00:50:33 +0000 Subject: [PATCH] fix: surface match metadata in audits and slack logs Co-authored-by: thewilloftheshadow --- src/channels/plugins/status-issues/discord.ts | 12 ++++++++++- .../plugins/status-issues/telegram.ts | 12 +++++++++-- src/discord/audit.ts | 6 ++++++ src/slack/monitor/slash.ts | 21 ++++++++++++++----- src/telegram/audit.ts | 19 ++++++++++++++--- 5 files changed, 59 insertions(+), 11 deletions(-) diff --git a/src/channels/plugins/status-issues/discord.ts b/src/channels/plugins/status-issues/discord.ts index 8c9880f53..7a6cb1df7 100644 --- a/src/channels/plugins/status-issues/discord.ts +++ b/src/channels/plugins/status-issues/discord.ts @@ -24,6 +24,8 @@ type DiscordPermissionsAuditSummary = { ok?: boolean; missing?: string[]; error?: string | null; + matchKey?: string; + matchSource?: string; }>; }; @@ -72,11 +74,15 @@ function readDiscordPermissionsAuditSummary(value: unknown): DiscordPermissionsA ? entry.missing.map((v) => asString(v)).filter(Boolean) : undefined; const error = asString(entry.error) ?? null; + const matchKey = asString(entry.matchKey) ?? undefined; + const matchSource = asString(entry.matchSource) ?? undefined; return { channelId, ok, missing: missing?.length ? missing : undefined, error, + matchKey, + matchSource, }; }) .filter(Boolean) as DiscordPermissionsAuditSummary["channels"]) @@ -122,11 +128,15 @@ export function collectDiscordStatusIssues( if (channel.ok === true) continue; const missing = channel.missing?.length ? ` missing ${channel.missing.join(", ")}` : ""; const error = channel.error ? `: ${channel.error}` : ""; + const matchMeta = + channel.matchKey || channel.matchSource + ? ` (matchKey=${channel.matchKey ?? "none"} matchSource=${channel.matchSource ?? "none"})` + : ""; issues.push({ channel: "discord", accountId, kind: "permissions", - message: `Channel ${channel.channelId} permission check failed.${missing}${error}`, + message: `Channel ${channel.channelId} permission check failed.${missing}${error}${matchMeta}`, fix: "Ensure the bot role can view + send in this channel (and that channel overrides don't deny it).", }); } diff --git a/src/channels/plugins/status-issues/telegram.ts b/src/channels/plugins/status-issues/telegram.ts index 6d302b487..30b68a987 100644 --- a/src/channels/plugins/status-issues/telegram.ts +++ b/src/channels/plugins/status-issues/telegram.ts @@ -17,6 +17,8 @@ type TelegramGroupMembershipAuditSummary = { ok?: boolean; status?: string | null; error?: string | null; + matchKey?: string; + matchSource?: string; }>; }; @@ -53,7 +55,9 @@ function readTelegramGroupMembershipAuditSummary( const ok = typeof entry.ok === "boolean" ? entry.ok : undefined; const status = asString(entry.status) ?? null; const error = asString(entry.error) ?? null; - return { chatId, ok, status, error }; + const matchKey = asString(entry.matchKey) ?? undefined; + const matchSource = asString(entry.matchSource) ?? undefined; + return { chatId, ok, status, error, matchKey, matchSource }; }) .filter(Boolean) as TelegramGroupMembershipAuditSummary["groups"]) : undefined; @@ -107,11 +111,15 @@ export function collectTelegramStatusIssues( if (group.ok === true) continue; const status = group.status ? ` status=${group.status}` : ""; const err = group.error ? `: ${group.error}` : ""; + const matchMeta = + group.matchKey || group.matchSource + ? ` (matchKey=${group.matchKey ?? "none"} matchSource=${group.matchSource ?? "none"})` + : ""; issues.push({ channel: "telegram", accountId, kind: "runtime", - message: `Group ${group.chatId} not reachable by bot.${status}${err}`, + message: `Group ${group.chatId} not reachable by bot.${status}${err}${matchMeta}`, fix: "Invite the bot to the group, then DM the bot once (/start) and restart the gateway.", }); } diff --git a/src/discord/audit.ts b/src/discord/audit.ts index f4a8eda3c..538b6f6ed 100644 --- a/src/discord/audit.ts +++ b/src/discord/audit.ts @@ -8,6 +8,8 @@ export type DiscordChannelPermissionsAuditEntry = { ok: boolean; missing?: string[]; error?: string | null; + matchKey?: string; + matchSource?: "id"; }; export type DiscordChannelPermissionsAudit = { @@ -97,12 +99,16 @@ export async function auditDiscordChannelPermissions(params: { ok: missing.length === 0, missing: missing.length ? missing : undefined, error: null, + matchKey: channelId, + matchSource: "id", }); } catch (err) { channels.push({ channelId, ok: false, error: err instanceof Error ? err.message : String(err), + matchKey: channelId, + matchSource: "id", }); } } diff --git a/src/slack/monitor/slash.ts b/src/slack/monitor/slash.ts index 6e1e0f684..7221d86eb 100644 --- a/src/slack/monitor/slash.ts +++ b/src/slack/monitor/slash.ts @@ -25,9 +25,9 @@ import { resolveCommandAuthorizedFromAuthorizers } from "../../channels/command- import type { ResolvedSlackAccount } from "../accounts.js"; import { - allowListMatches, normalizeAllowList, normalizeAllowListLower, + resolveSlackAllowListMatch, resolveSlackUserAllowed, } from "./allow-list.js"; import { resolveSlackChannelConfig, type SlackChannelConfigResolved } from "./channel-config.js"; @@ -201,12 +201,15 @@ export function registerSlackMonitorSlashCommands(params: { if (ctx.dmPolicy !== "open") { const sender = await ctx.resolveUserName(command.user_id); const senderName = sender?.name ?? undefined; - const permitted = allowListMatches({ + const allowMatch = resolveSlackAllowListMatch({ allowList: effectiveAllowFromLower, id: command.user_id, name: senderName, }); - if (!permitted) { + const allowMatchMeta = `matchKey=${allowMatch.matchKey ?? "none"} matchSource=${ + allowMatch.matchSource ?? "none" + }`; + if (!allowMatch.allowed) { if (ctx.dmPolicy === "pairing") { const { code, created } = await upsertChannelPairingRequest({ channel: "slack", @@ -214,6 +217,11 @@ export function registerSlackMonitorSlashCommands(params: { meta: { name: senderName }, }); if (created) { + logVerbose( + `slack pairing request sender=${command.user_id} name=${ + senderName ?? "unknown" + } (${allowMatchMeta})`, + ); await respond({ text: buildPairingReply({ channel: "slack", @@ -224,6 +232,9 @@ export function registerSlackMonitorSlashCommands(params: { }); } } else { + logVerbose( + `slack: blocked slash sender ${command.user_id} (dmPolicy=${ctx.dmPolicy}, ${allowMatchMeta})`, + ); await respond({ text: "You are not authorized to use this command.", response_type: "ephemeral", @@ -289,11 +300,11 @@ export function registerSlackMonitorSlashCommands(params: { return; } - const ownerAllowed = allowListMatches({ + const ownerAllowed = resolveSlackAllowListMatch({ allowList: effectiveAllowFromLower, id: command.user_id, name: senderName, - }); + }).allowed; if (isRoomish) { commandAuthorized = resolveCommandAuthorizedFromAuthorizers({ useAccessGroups: ctx.useAccessGroups, diff --git a/src/telegram/audit.ts b/src/telegram/audit.ts index df4e4c16d..8c64ebf47 100644 --- a/src/telegram/audit.ts +++ b/src/telegram/audit.ts @@ -8,6 +8,8 @@ export type TelegramGroupMembershipAuditEntry = { ok: boolean; status?: string | null; error?: string | null; + matchKey?: string; + matchSource?: "id"; }; export type TelegramGroupMembershipAudit = { @@ -105,9 +107,16 @@ export async function auditTelegramGroupMembership(params: { isRecord(json) && json.ok === false && typeof json.description === "string" ? json.description : `getChatMember failed (${res.status})`; - groups.push({ chatId, ok: false, status: null, error: desc }); - continue; - } + groups.push({ + chatId, + ok: false, + status: null, + error: desc, + matchKey: chatId, + matchSource: "id", + }); + continue; + } const status = isRecord((json as TelegramApiOk).result) ? ((json as TelegramApiOk<{ status?: string }>).result.status ?? null) : null; @@ -117,6 +126,8 @@ export async function auditTelegramGroupMembership(params: { ok, status, error: ok ? null : "bot not in group", + matchKey: chatId, + matchSource: "id", }); } catch (err) { groups.push({ @@ -124,6 +135,8 @@ export async function auditTelegramGroupMembership(params: { ok: false, status: null, error: err instanceof Error ? err.message : String(err), + matchKey: chatId, + matchSource: "id", }); } }