fix(discord): enrich allowlist resolution logs

This commit is contained in:
Peter Steinberger
2026-03-02 04:19:26 +00:00
parent d17f4432b3
commit 619dfa88cb
7 changed files with 211 additions and 14 deletions

View File

@@ -2,7 +2,9 @@ import { describe, expect, it, vi } from "vitest";
import type { RuntimeEnv } from "../../runtime.js";
const { resolveDiscordChannelAllowlistMock, resolveDiscordUserAllowlistMock } = vi.hoisted(() => ({
resolveDiscordChannelAllowlistMock: vi.fn(async () => []),
resolveDiscordChannelAllowlistMock: vi.fn(
async (_params: { entries: string[] }) => [] as Array<Record<string, unknown>>,
),
resolveDiscordUserAllowlistMock: vi.fn(async (params: { entries: string[] }) =>
params.entries.map((entry) => {
switch (entry) {
@@ -12,6 +14,8 @@ const { resolveDiscordChannelAllowlistMock, resolveDiscordUserAllowlistMock } =
return { input: entry, resolved: true, id: "222" };
case "Carol":
return { input: entry, resolved: false };
case "387":
return { input: entry, resolved: true, id: "387", name: "Peter" };
default:
return { input: entry, resolved: true, id: entry };
}
@@ -54,4 +58,39 @@ describe("resolveDiscordAllowlistConfig", () => {
expect(result.guildEntries?.["*"]?.channels?.["*"]?.users).toEqual(["Carol", "888"]);
expect(resolveDiscordUserAllowlistMock).toHaveBeenCalledTimes(2);
});
it("logs discord name metadata for resolved and unresolved allowlist entries", async () => {
resolveDiscordChannelAllowlistMock.mockResolvedValueOnce([
{
input: "145/c404",
resolved: false,
guildId: "145",
guildName: "Ops",
channelName: "missing-room",
},
]);
const runtime = { log: vi.fn(), error: vi.fn(), exit: vi.fn() } as unknown as RuntimeEnv;
await resolveDiscordAllowlistConfig({
token: "token",
allowFrom: ["387"],
guildEntries: {
"145": {
channels: {
c404: {},
},
},
},
fetcher: vi.fn() as unknown as typeof fetch,
runtime,
});
const logs = (runtime.log as ReturnType<typeof vi.fn>).mock.calls
.map(([line]) => String(line))
.join("\n");
expect(logs).toContain(
"discord channels unresolved: 145/c404 (guild:Ops; channel:missing-room)",
);
expect(logs).toContain("discord users resolved: 387→387 (name:Peter)");
});
});

View File

@@ -13,6 +13,71 @@ import { resolveDiscordUserAllowlist } from "../resolve-users.js";
type GuildEntries = Record<string, DiscordGuildEntry>;
type ChannelResolutionInput = { input: string; guildKey: string; channelKey?: string };
type DiscordChannelLogEntry = {
input: string;
guildId?: string;
guildName?: string;
channelId?: string;
channelName?: string;
note?: string;
};
type DiscordUserLogEntry = {
input: string;
id?: string;
name?: string;
guildName?: string;
note?: string;
};
function formatResolutionLogDetails(base: string, details: Array<string | undefined>): string {
const nonEmpty = details
.map((value) => value?.trim())
.filter((value): value is string => Boolean(value));
return nonEmpty.length > 0 ? `${base} (${nonEmpty.join("; ")})` : base;
}
function formatDiscordChannelResolved(entry: DiscordChannelLogEntry): string {
const target = entry.channelId ? `${entry.guildId}/${entry.channelId}` : entry.guildId;
const base = `${entry.input}${target}`;
return formatResolutionLogDetails(base, [
entry.guildName ? `guild:${entry.guildName}` : undefined,
entry.channelName ? `channel:${entry.channelName}` : undefined,
entry.note,
]);
}
function formatDiscordChannelUnresolved(entry: DiscordChannelLogEntry): string {
return formatResolutionLogDetails(entry.input, [
entry.guildName
? `guild:${entry.guildName}`
: entry.guildId
? `guildId:${entry.guildId}`
: undefined,
entry.channelName
? `channel:${entry.channelName}`
: entry.channelId
? `channelId:${entry.channelId}`
: undefined,
entry.note,
]);
}
function formatDiscordUserResolved(entry: DiscordUserLogEntry): string {
const base = `${entry.input}${entry.id}`;
return formatResolutionLogDetails(base, [
entry.name ? `name:${entry.name}` : undefined,
entry.guildName ? `guild:${entry.guildName}` : undefined,
entry.note,
]);
}
function formatDiscordUserUnresolved(entry: DiscordUserLogEntry): string {
return formatResolutionLogDetails(entry.input, [
entry.name ? `name:${entry.name}` : undefined,
entry.guildName ? `guild:${entry.guildName}` : undefined,
entry.note,
]);
}
function toGuildEntries(value: unknown): GuildEntries {
if (!value || typeof value !== "object") {
@@ -90,14 +155,10 @@ async function resolveGuildEntriesByChannelAllowlist(params: {
}
const sourceGuild = params.guildEntries[source.guildKey] ?? {};
if (!entry.resolved || !entry.guildId) {
unresolved.push(entry.input);
unresolved.push(formatDiscordChannelUnresolved(entry));
continue;
}
mapping.push(
entry.channelId
? `${entry.input}${entry.guildId}/${entry.channelId}`
: `${entry.input}${entry.guildId}`,
);
mapping.push(formatDiscordChannelResolved(entry));
const existing = nextGuilds[entry.guildId] ?? {};
const mergedChannels = {
...sourceGuild.channels,
@@ -153,7 +214,10 @@ async function resolveAllowFromByUserAllowlist(params: {
entries: allowEntries.map((entry) => String(entry)),
fetcher: params.fetcher,
});
const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers);
const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers, {
formatResolved: formatDiscordUserResolved,
formatUnresolved: formatDiscordUserUnresolved,
});
const allowFrom = canonicalizeAllowlistWithResolvedIds({
existing: params.allowFrom,
resolvedMap,
@@ -199,7 +263,10 @@ async function resolveGuildEntriesByUserAllowlist(params: {
entries: Array.from(userEntries),
fetcher: params.fetcher,
});
const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers);
const { resolvedMap, mapping, unresolved } = buildAllowlistResolutionSummary(resolvedUsers, {
formatResolved: formatDiscordUserResolved,
formatUnresolved: formatDiscordUserUnresolved,
});
const nextGuilds = { ...params.guildEntries };
for (const [guildKey, guildConfig] of Object.entries(params.guildEntries)) {
if (!guildConfig || typeof guildConfig !== "object") {

View File

@@ -549,6 +549,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
const logger = createSubsystemLogger("discord/monitor");
const guildHistories = new Map<string, HistoryEntry[]>();
let botUserId: string | undefined;
let botUserName: string | undefined;
let voiceManager: DiscordVoiceManager | null = null;
if (nativeDisabledExplicit) {
@@ -562,6 +563,7 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
try {
const botUser = await client.fetchUser("@me");
botUserId = botUser?.id;
botUserName = botUser?.username?.trim() || botUser?.globalName?.trim() || undefined;
} catch (err) {
runtime.error?.(danger(`discord: failed to fetch bot identity: ${String(err)}`));
}
@@ -657,7 +659,9 @@ export async function monitorDiscordProvider(opts: MonitorDiscordOpts = {}) {
runtime.log?.("discord: GuildPresences intent enabled — presence listener registered");
}
runtime.log?.(`logged in to discord${botUserId ? ` as ${botUserId}` : ""}`);
const botIdentity =
botUserId && botUserName ? `${botUserId} (${botUserName})` : (botUserId ?? botUserName ?? "");
runtime.log?.(`logged in to discord${botIdentity ? ` as ${botIdentity}` : ""}`);
lifecycleStarted = true;
await runDiscordGatewayLifecycle({