diff --git a/src/config/runtime-group-policy.test.ts b/src/config/runtime-group-policy.test.ts new file mode 100644 index 00000000000..f49acda5cad --- /dev/null +++ b/src/config/runtime-group-policy.test.ts @@ -0,0 +1,32 @@ +import { describe, expect, it } from "vitest"; +import { resolveRuntimeGroupPolicy } from "./runtime-group-policy.js"; + +describe("resolveRuntimeGroupPolicy", () => { + it("fails closed when provider config is missing and no defaults are set", () => { + const resolved = resolveRuntimeGroupPolicy({ + providerConfigPresent: false, + }); + expect(resolved.groupPolicy).toBe("allowlist"); + expect(resolved.providerMissingFallbackApplied).toBe(true); + }); + + it("keeps configured fallback when provider config is present", () => { + const resolved = resolveRuntimeGroupPolicy({ + providerConfigPresent: true, + configuredFallbackPolicy: "open", + }); + expect(resolved.groupPolicy).toBe("open"); + expect(resolved.providerMissingFallbackApplied).toBe(false); + }); + + it("ignores global defaults when provider config is missing", () => { + const resolved = resolveRuntimeGroupPolicy({ + providerConfigPresent: false, + defaultGroupPolicy: "disabled", + configuredFallbackPolicy: "open", + missingProviderFallbackPolicy: "allowlist", + }); + expect(resolved.groupPolicy).toBe("allowlist"); + expect(resolved.providerMissingFallbackApplied).toBe(true); + }); +}); diff --git a/src/config/runtime-group-policy.ts b/src/config/runtime-group-policy.ts new file mode 100644 index 00000000000..12be2c2f8b9 --- /dev/null +++ b/src/config/runtime-group-policy.ts @@ -0,0 +1,23 @@ +import type { GroupPolicy } from "./types.base.js"; + +export type RuntimeGroupPolicyResolution = { + groupPolicy: GroupPolicy; + providerMissingFallbackApplied: boolean; +}; + +export function resolveRuntimeGroupPolicy(params: { + providerConfigPresent: boolean; + groupPolicy?: GroupPolicy; + defaultGroupPolicy?: GroupPolicy; + configuredFallbackPolicy?: GroupPolicy; + missingProviderFallbackPolicy?: GroupPolicy; +}): RuntimeGroupPolicyResolution { + const configuredFallbackPolicy = params.configuredFallbackPolicy ?? "open"; + const missingProviderFallbackPolicy = params.missingProviderFallbackPolicy ?? "allowlist"; + const groupPolicy = params.providerConfigPresent + ? (params.groupPolicy ?? params.defaultGroupPolicy ?? configuredFallbackPolicy) + : (params.groupPolicy ?? missingProviderFallbackPolicy); + const providerMissingFallbackApplied = + !params.providerConfigPresent && params.groupPolicy === undefined; + return { groupPolicy, providerMissingFallbackApplied }; +} diff --git a/src/imessage/monitor/provider.group-policy.test.ts b/src/imessage/monitor/provider.group-policy.test.ts new file mode 100644 index 00000000000..c28d7c10b4b --- /dev/null +++ b/src/imessage/monitor/provider.group-policy.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { __testing } from "./monitor-provider.js"; + +describe("resolveIMessageRuntimeGroupPolicy", () => { + it("fails closed when channels.imessage is missing and no defaults are set", () => { + const resolved = __testing.resolveIMessageRuntimeGroupPolicy({ + providerConfigPresent: false, + }); + expect(resolved.groupPolicy).toBe("allowlist"); + expect(resolved.providerMissingFallbackApplied).toBe(true); + }); + + it("keeps open fallback when channels.imessage is configured", () => { + const resolved = __testing.resolveIMessageRuntimeGroupPolicy({ + providerConfigPresent: true, + }); + expect(resolved.groupPolicy).toBe("open"); + expect(resolved.providerMissingFallbackApplied).toBe(false); + }); + + it("ignores explicit global defaults when provider config is missing", () => { + const resolved = __testing.resolveIMessageRuntimeGroupPolicy({ + providerConfigPresent: false, + defaultGroupPolicy: "disabled", + }); + expect(resolved.groupPolicy).toBe("allowlist"); + expect(resolved.providerMissingFallbackApplied).toBe(true); + }); +}); diff --git a/src/telegram/group-access.group-policy.test.ts b/src/telegram/group-access.group-policy.test.ts new file mode 100644 index 00000000000..9374230e1b1 --- /dev/null +++ b/src/telegram/group-access.group-policy.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { resolveTelegramRuntimeGroupPolicy } from "./group-access.js"; + +describe("resolveTelegramRuntimeGroupPolicy", () => { + it("fails closed when channels.telegram is missing and no defaults are set", () => { + const resolved = resolveTelegramRuntimeGroupPolicy({ + providerConfigPresent: false, + }); + expect(resolved.groupPolicy).toBe("allowlist"); + expect(resolved.providerMissingFallbackApplied).toBe(true); + }); + + it("keeps open fallback when channels.telegram is configured", () => { + const resolved = resolveTelegramRuntimeGroupPolicy({ + providerConfigPresent: true, + }); + expect(resolved.groupPolicy).toBe("open"); + expect(resolved.providerMissingFallbackApplied).toBe(false); + }); + + it("ignores explicit defaults when provider config is missing", () => { + const resolved = resolveTelegramRuntimeGroupPolicy({ + providerConfigPresent: false, + defaultGroupPolicy: "disabled", + }); + expect(resolved.groupPolicy).toBe("allowlist"); + expect(resolved.providerMissingFallbackApplied).toBe(true); + }); +}); diff --git a/src/web/inbound/access-control.group-policy.test.ts b/src/web/inbound/access-control.group-policy.test.ts new file mode 100644 index 00000000000..8419a1e5d7a --- /dev/null +++ b/src/web/inbound/access-control.group-policy.test.ts @@ -0,0 +1,29 @@ +import { describe, expect, it } from "vitest"; +import { __testing } from "./access-control.js"; + +describe("resolveWhatsAppRuntimeGroupPolicy", () => { + it("fails closed when channels.whatsapp is missing and no defaults are set", () => { + const resolved = __testing.resolveWhatsAppRuntimeGroupPolicy({ + providerConfigPresent: false, + }); + expect(resolved.groupPolicy).toBe("allowlist"); + expect(resolved.providerMissingFallbackApplied).toBe(true); + }); + + it("keeps open fallback when channels.whatsapp is configured", () => { + const resolved = __testing.resolveWhatsAppRuntimeGroupPolicy({ + providerConfigPresent: true, + }); + expect(resolved.groupPolicy).toBe("open"); + expect(resolved.providerMissingFallbackApplied).toBe(false); + }); + + it("ignores explicit default policy when provider config is missing", () => { + const resolved = __testing.resolveWhatsAppRuntimeGroupPolicy({ + providerConfigPresent: false, + defaultGroupPolicy: "disabled", + }); + expect(resolved.groupPolicy).toBe("allowlist"); + expect(resolved.providerMissingFallbackApplied).toBe(true); + }); +});