refactor: unify dm policy store reads and reason codes

This commit is contained in:
Peter Steinberger
2026-02-26 17:47:51 +01:00
parent 53e30475e2
commit cd80c7e7ff
21 changed files with 259 additions and 92 deletions

View File

@@ -1,10 +1,12 @@
import type { OpenClawConfig } from "openclaw/plugin-sdk";
import {
DM_GROUP_ACCESS_REASON,
createReplyPrefixOptions,
evictOldHistoryKeys,
logAckFailure,
logInboundDrop,
logTypingFailure,
readStoreAllowFromForDmPolicy,
recordPendingHistoryEntryIfEnabled,
resolveAckReaction,
resolveDmGroupAccessWithLists,
@@ -500,9 +502,11 @@ export async function processMessage(
const dmPolicy = account.config.dmPolicy ?? "pairing";
const groupPolicy = account.config.groupPolicy ?? "allowlist";
const storeAllowFrom = await core.channel.pairing
.readAllowFromStore("bluebubbles")
.catch(() => []);
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
provider: "bluebubbles",
dmPolicy,
readStore: (provider) => core.channel.pairing.readAllowFromStore(provider),
});
const accessDecision = resolveDmGroupAccessWithLists({
isGroup,
dmPolicy,
@@ -530,7 +534,7 @@ export async function processMessage(
if (accessDecision.decision !== "allow") {
if (isGroup) {
if (accessDecision.reason === "groupPolicy=disabled") {
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_DISABLED) {
logVerbose(core, runtime, "Blocked BlueBubbles group message (groupPolicy=disabled)");
logGroupAllowlistHint({
runtime,
@@ -541,7 +545,7 @@ export async function processMessage(
});
return;
}
if (accessDecision.reason === "groupPolicy=allowlist (empty allowlist)") {
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_EMPTY_ALLOWLIST) {
logVerbose(core, runtime, "Blocked BlueBubbles group message (no allowlist)");
logGroupAllowlistHint({
runtime,
@@ -552,7 +556,7 @@ export async function processMessage(
});
return;
}
if (accessDecision.reason === "groupPolicy=allowlist (not allowlisted)") {
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_NOT_ALLOWLISTED) {
logVerbose(
core,
runtime,
@@ -575,7 +579,7 @@ export async function processMessage(
return;
}
if (accessDecision.reason === "dmPolicy=disabled") {
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.DM_POLICY_DISABLED) {
logVerbose(core, runtime, `Blocked BlueBubbles DM from ${message.senderId}`);
logVerbose(core, runtime, `drop: dmPolicy disabled sender=${message.senderId}`);
return;
@@ -1382,9 +1386,11 @@ export async function processReaction(
const dmPolicy = account.config.dmPolicy ?? "pairing";
const groupPolicy = account.config.groupPolicy ?? "allowlist";
const storeAllowFrom = await core.channel.pairing
.readAllowFromStore("bluebubbles")
.catch(() => []);
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
provider: "bluebubbles",
dmPolicy,
readStore: (provider) => core.channel.pairing.readAllowFromStore(provider),
});
const accessDecision = resolveDmGroupAccessWithLists({
isGroup: reaction.isGroup,
dmPolicy,

View File

@@ -5,6 +5,7 @@ import {
formatTextWithAttachmentLinks,
logInboundDrop,
isDangerousNameMatchingEnabled,
readStoreAllowFromForDmPolicy,
resolveControlCommandGate,
resolveOutboundMediaUrls,
resolveAllowlistProviderRuntimeGroupPolicy,
@@ -120,10 +121,11 @@ export async function handleIrcInbound(params: {
const configAllowFrom = normalizeIrcAllowlist(account.config.allowFrom);
const configGroupAllowFrom = normalizeIrcAllowlist(account.config.groupAllowFrom);
const storeAllowFrom =
dmPolicy === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore(CHANNEL_ID).catch(() => []);
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
provider: CHANNEL_ID,
dmPolicy,
readStore: (provider) => core.channel.pairing.readAllowFromStore(provider),
});
const storeAllowList = normalizeIrcAllowlist(storeAllowFrom);
const groupMatch = resolveIrcGroupMatch({

View File

@@ -5,6 +5,7 @@ import {
formatAllowlistMatchMeta,
logInboundDrop,
logTypingFailure,
readStoreAllowFromForDmPolicy,
resolveControlCommandGate,
type PluginRuntime,
type RuntimeEnv,
@@ -213,10 +214,11 @@ export function createMatrixRoomMessageHandler(params: MatrixMonitorHandlerParam
}
const senderName = await getMemberDisplayName(roomId, senderId);
const storeAllowFrom =
dmPolicy === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore("matrix").catch(() => []);
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
provider: "matrix",
dmPolicy,
readStore: (provider) => core.channel.pairing.readAllowFromStore(provider),
});
const effectiveAllowFrom = normalizeMatrixAllowList([...allowFrom, ...storeAllowFrom]);
const groupAllowFrom = cfg.channels?.matrix?.groupAllowFrom ?? [];
const effectiveGroupAllowFrom = normalizeMatrixAllowList(groupAllowFrom);

View File

@@ -7,6 +7,7 @@ import type {
} from "openclaw/plugin-sdk";
import {
buildAgentMediaPayload,
DM_GROUP_ACCESS_REASON,
createReplyPrefixOptions,
createTypingCallbacks,
logInboundDrop,
@@ -17,6 +18,7 @@ import {
recordPendingHistoryEntryIfEnabled,
isDangerousNameMatchingEnabled,
resolveControlCommandGate,
readStoreAllowFromForDmPolicy,
resolveDmGroupAccessWithLists,
resolveAllowlistProviderRuntimeGroupPolicy,
resolveDefaultGroupPolicy,
@@ -358,9 +360,11 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
account.config.groupAllowFrom ?? [],
);
const storeAllowFrom = normalizeMattermostAllowList(
dmPolicy === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore("mattermost").catch(() => []),
await readStoreAllowFromForDmPolicy({
provider: "mattermost",
dmPolicy,
readStore: (provider) => core.channel.pairing.readAllowFromStore(provider),
}),
);
const accessDecision = resolveDmGroupAccessWithLists({
isGroup: kind !== "direct",
@@ -415,7 +419,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
if (accessDecision.decision !== "allow") {
if (kind === "direct") {
if (accessDecision.reason === "dmPolicy=disabled") {
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.DM_POLICY_DISABLED) {
logVerboseMessage(`mattermost: drop dm (dmPolicy=disabled sender=${senderId})`);
return;
}
@@ -447,15 +451,15 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
logVerboseMessage(`mattermost: drop dm sender=${senderId} (dmPolicy=${dmPolicy})`);
return;
}
if (accessDecision.reason === "groupPolicy=disabled") {
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_DISABLED) {
logVerboseMessage("mattermost: drop group message (groupPolicy=disabled)");
return;
}
if (accessDecision.reason === "groupPolicy=allowlist (empty allowlist)") {
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_EMPTY_ALLOWLIST) {
logVerboseMessage("mattermost: drop group message (no group allowlist)");
return;
}
if (accessDecision.reason === "groupPolicy=allowlist (not allowlisted)") {
if (accessDecision.reasonCode === DM_GROUP_ACCESS_REASON.GROUP_POLICY_NOT_ALLOWLISTED) {
logVerboseMessage(`mattermost: drop group sender=${senderId} (not in groupAllowFrom)`);
return;
}
@@ -856,9 +860,11 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
// Enforce DM/group policy and allowlist checks (same as normal messages)
const dmPolicy = account.config.dmPolicy ?? "pairing";
const storeAllowFrom = normalizeMattermostAllowList(
dmPolicy === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore("mattermost").catch(() => []),
await readStoreAllowFromForDmPolicy({
provider: "mattermost",
dmPolicy,
readStore: (provider) => core.channel.pairing.readAllowFromStore(provider),
}),
);
const reactionAccess = resolveDmGroupAccessWithLists({
isGroup: kind !== "direct",

View File

@@ -7,6 +7,7 @@ import {
resolveControlCommandGate,
resolveDefaultGroupPolicy,
isDangerousNameMatchingEnabled,
readStoreAllowFromForDmPolicy,
resolveMentionGating,
formatAllowlistMatchMeta,
resolveEffectiveAllowFromLists,
@@ -128,10 +129,11 @@ export function createMSTeamsMessageHandler(deps: MSTeamsMessageHandlerDeps) {
const senderName = from.name ?? from.id;
const senderId = from.aadObjectId ?? from.id;
const dmPolicy = msteamsCfg?.dmPolicy ?? "pairing";
const storedAllowFrom =
dmPolicy === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore("msteams").catch(() => []);
const storedAllowFrom = await readStoreAllowFromForDmPolicy({
provider: "msteams",
dmPolicy,
readStore: (provider) => core.channel.pairing.readAllowFromStore(provider),
});
const useAccessGroups = cfg.commands?.useAccessGroups !== false;
// Check DM policy for direct messages.

View File

@@ -4,6 +4,7 @@ import {
createReplyPrefixOptions,
formatTextWithAttachmentLinks,
logInboundDrop,
readStoreAllowFromForDmPolicy,
resolveControlCommandGate,
resolveOutboundMediaUrls,
resolveAllowlistProviderRuntimeGroupPolicy,
@@ -96,10 +97,11 @@ export async function handleNextcloudTalkInbound(params: {
const configAllowFrom = normalizeNextcloudTalkAllowlist(account.config.allowFrom);
const configGroupAllowFrom = normalizeNextcloudTalkAllowlist(account.config.groupAllowFrom);
const storeAllowFrom =
dmPolicy === "allowlist"
? []
: await core.channel.pairing.readAllowFromStore(CHANNEL_ID).catch(() => []);
const storeAllowFrom = await readStoreAllowFromForDmPolicy({
provider: CHANNEL_ID,
dmPolicy,
readStore: (provider) => core.channel.pairing.readAllowFromStore(provider),
});
const storeAllowList = normalizeNextcloudTalkAllowlist(storeAllowFrom);
const roomMatch = resolveNextcloudTalkRoomMatch({