fix(config): cap missing-provider group-policy warning cache with shared dedupe helper (#108806)

This commit is contained in:
RileyJJY
2026-07-16 23:18:38 +08:00
committed by GitHub
parent aaa37a0c26
commit 352ef94d40
2 changed files with 47 additions and 4 deletions
+38 -1
View File
@@ -1,5 +1,5 @@
// Covers runtime group-policy resolution from config and context.
import { beforeEach, describe, expect, it } from "vitest";
import { beforeEach, describe, expect, it, vi } from "vitest";
import {
GROUP_POLICY_BLOCKED_LABEL,
resolveAllowlistProviderRuntimeGroupPolicy,
@@ -63,4 +63,41 @@ describe("warnMissingProviderGroupPolicyFallbackOnce", () => {
expect(lines[0]).toContain("channels.runtime-policy-test is missing");
expect(lines[0]).toContain("room messages blocked");
});
describe("warning dedupe cache bounds", () => {
let warnOnceFn: typeof warnMissingProviderGroupPolicyFallbackOnce;
// Fresh module instance so the module-level cache starts empty for this block.
beforeEach(async () => {
vi.resetModules();
const mod = await import("./runtime-group-policy.js");
warnOnceFn = mod.warnMissingProviderGroupPolicyFallbackOnce;
});
it("refreshes recent keys and re-warns evicted keys once the cap overflows", () => {
const lines: string[] = [];
const warnForAccount = (accountId: string) =>
warnOnceFn({
providerMissingFallbackApplied: true,
providerKey: "runtime-policy-evict-test",
accountId,
log: (message) => lines.push(message),
});
for (let i = 0; i < 4096; i++) {
warnForAccount(`account-${i}`);
}
expect(lines).toHaveLength(4096);
// Recent duplicate stays deduped and refreshes its recency.
expect(warnForAccount("account-0")).toBe(false);
expect(lines).toHaveLength(4096);
// Overflow evicts the oldest untouched key (account-1), not the refreshed one.
expect(warnForAccount("account-overflow")).toBe(true);
expect(warnForAccount("account-0")).toBe(false);
expect(warnForAccount("account-1")).toBe(true);
expect(lines).toHaveLength(4098);
});
});
});
+9 -3
View File
@@ -1,5 +1,6 @@
// Resolves runtime group-policy settings for channels and sessions.
import { normalizeOptionalString } from "@openclaw/normalization-core/string-coerce";
import { createDedupeCache } from "../infra/dedupe.js";
import type { GroupPolicy } from "./types.base.js";
type RuntimeGroupPolicyResolution = {
@@ -90,7 +91,13 @@ export function resolveAllowlistProviderRuntimeGroupPolicy(
});
}
const warnedMissingProviderGroupPolicy = new Set<string>();
const MAX_WARNED_MISSING_PROVIDER_GROUP_POLICY_KEYS = 4096;
// Warn-once keys accumulate per provider/account for the process lifetime;
// bounding them means evicted keys can re-warn instead of growing without limit.
const warnedMissingProviderGroupPolicy = createDedupeCache({
ttlMs: 0,
maxSize: MAX_WARNED_MISSING_PROVIDER_GROUP_POLICY_KEYS,
});
/**
* Log the missing-provider fail-closed fallback once per provider/account.
@@ -107,10 +114,9 @@ export function warnMissingProviderGroupPolicyFallbackOnce(params: {
return false;
}
const key = `${params.providerKey}:${params.accountId ?? "*"}`;
if (warnedMissingProviderGroupPolicy.has(key)) {
if (warnedMissingProviderGroupPolicy.check(key)) {
return false;
}
warnedMissingProviderGroupPolicy.add(key);
const blockedLabel = normalizeOptionalString(params.blockedLabel) || "group messages";
params.log(
`${params.providerKey}: channels.${params.providerKey} is missing; defaulting groupPolicy to "allowlist" (${blockedLabel} blocked until explicitly configured).`,