perf(config): narrow channel legacy rule loading

This commit is contained in:
Vincent Koc
2026-04-13 20:37:11 +01:00
parent 43d4be9027
commit e02c6ca82a
5 changed files with 235 additions and 9 deletions

View File

@@ -0,0 +1,73 @@
import { describe, expect, it, vi } from "vitest";
const { loadBundledPluginPublicArtifactModuleSyncMock } = vi.hoisted(() => ({
loadBundledPluginPublicArtifactModuleSyncMock: vi.fn(
({ artifactBasename, dirName }: { artifactBasename: string; dirName: string }) => {
if (dirName === "discord" && artifactBasename === "doctor-contract-api.js") {
return {
legacyConfigRules: [
{
path: ["channels", "discord", "voice", "tts"],
message: "legacy discord rule",
},
],
};
}
if (dirName === "telegram" && artifactBasename === "contract-api.js") {
return {
legacyConfigRules: [
{
path: ["channels", "telegram", "groupMentionsOnly"],
message: "legacy telegram rule",
},
],
};
}
throw new Error(
`Unable to resolve bundled plugin public surface ${dirName}/${artifactBasename}`,
);
},
),
}));
vi.mock("../../plugins/public-surface-loader.js", () => ({
loadBundledPluginPublicArtifactModuleSync: loadBundledPluginPublicArtifactModuleSyncMock,
}));
import { loadBundledChannelDoctorContractApi } from "./doctor-contract-api.js";
describe("channel doctor contract api fast path", () => {
it("prefers the explicit doctor contract artifact for bundled channels", () => {
const api = loadBundledChannelDoctorContractApi("discord");
expect(api?.legacyConfigRules).toEqual([
{
path: ["channels", "discord", "voice", "tts"],
message: "legacy discord rule",
},
]);
expect(loadBundledPluginPublicArtifactModuleSyncMock).toHaveBeenCalledWith({
dirName: "discord",
artifactBasename: "doctor-contract-api.js",
});
});
it("falls back to the generic contract artifact when the doctor artifact is absent", () => {
const api = loadBundledChannelDoctorContractApi("telegram");
expect(api?.legacyConfigRules).toEqual([
{
path: ["channels", "telegram", "groupMentionsOnly"],
message: "legacy telegram rule",
},
]);
expect(loadBundledPluginPublicArtifactModuleSyncMock).toHaveBeenCalledWith({
dirName: "telegram",
artifactBasename: "doctor-contract-api.js",
});
expect(loadBundledPluginPublicArtifactModuleSyncMock).toHaveBeenCalledWith({
dirName: "telegram",
artifactBasename: "contract-api.js",
});
});
});

View File

@@ -0,0 +1,34 @@
import type { LegacyConfigRule } from "../../config/legacy.shared.js";
import { loadBundledPluginPublicArtifactModuleSync } from "../../plugins/public-surface-loader.js";
type BundledChannelDoctorContractApi = {
legacyConfigRules?: readonly LegacyConfigRule[];
};
function loadBundledChannelPublicArtifact(
channelId: string,
artifactBasenames: readonly string[],
): BundledChannelDoctorContractApi | undefined {
for (const artifactBasename of artifactBasenames) {
try {
return loadBundledPluginPublicArtifactModuleSync<BundledChannelDoctorContractApi>({
dirName: channelId,
artifactBasename,
});
} catch (error) {
if (
error instanceof Error &&
error.message.startsWith("Unable to resolve bundled plugin public surface ")
) {
continue;
}
}
}
return undefined;
}
export function loadBundledChannelDoctorContractApi(
channelId: string,
): BundledChannelDoctorContractApi | undefined {
return loadBundledChannelPublicArtifact(channelId, ["doctor-contract-api.js", "contract-api.js"]);
}

View File

@@ -0,0 +1,108 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
const {
loadBundledChannelDoctorContractApiMock,
getBootstrapChannelPluginMock,
listPluginDoctorLegacyConfigRulesMock,
} = vi.hoisted(() => ({
loadBundledChannelDoctorContractApiMock: vi.fn(),
getBootstrapChannelPluginMock: vi.fn(),
listPluginDoctorLegacyConfigRulesMock: vi.fn(() => []),
}));
vi.mock("./doctor-contract-api.js", () => ({
loadBundledChannelDoctorContractApi: loadBundledChannelDoctorContractApiMock,
}));
vi.mock("./bootstrap-registry.js", () => ({
getBootstrapChannelPlugin: getBootstrapChannelPluginMock,
}));
vi.mock("../../plugins/doctor-contract-registry.js", () => ({
listPluginDoctorLegacyConfigRules: listPluginDoctorLegacyConfigRulesMock,
}));
import { collectChannelLegacyConfigRules } from "./legacy-config.js";
describe("collectChannelLegacyConfigRules", () => {
beforeEach(() => {
loadBundledChannelDoctorContractApiMock.mockReset();
getBootstrapChannelPluginMock.mockReset();
listPluginDoctorLegacyConfigRulesMock.mockReset();
listPluginDoctorLegacyConfigRulesMock.mockReturnValue([]);
});
it("uses bundled doctor contract rules before falling back to registry scans", () => {
loadBundledChannelDoctorContractApiMock.mockImplementation((channelId: string) =>
channelId === "discord"
? {
legacyConfigRules: [
{
path: ["channels", "discord", "voice", "tts"],
message: "legacy discord rule",
},
],
}
: undefined,
);
const rules = collectChannelLegacyConfigRules({
channels: {
discord: {},
},
});
expect(rules).toEqual([
{
path: ["channels", "discord", "voice", "tts"],
message: "legacy discord rule",
},
]);
expect(getBootstrapChannelPluginMock).not.toHaveBeenCalled();
expect(listPluginDoctorLegacyConfigRulesMock).not.toHaveBeenCalled();
});
it("falls back to bootstrap rules and only scans unresolved channels", () => {
getBootstrapChannelPluginMock.mockImplementation((channelId: string) =>
channelId === "slack"
? {
doctor: {
legacyConfigRules: [
{
path: ["channels", "slack", "legacy"],
message: "legacy slack rule",
},
],
},
}
: undefined,
);
listPluginDoctorLegacyConfigRulesMock.mockReturnValue([
{
path: ["channels", "custom-chat", "legacy"],
message: "legacy custom rule",
},
]);
const rules = collectChannelLegacyConfigRules({
channels: {
slack: {},
"custom-chat": {},
},
});
expect(rules).toEqual([
{
path: ["channels", "slack", "legacy"],
message: "legacy slack rule",
},
{
path: ["channels", "custom-chat", "legacy"],
message: "legacy custom rule",
},
]);
expect(listPluginDoctorLegacyConfigRulesMock).toHaveBeenCalledWith({
pluginIds: ["custom-chat"],
});
});
});

View File

@@ -1,6 +1,7 @@
import type { LegacyConfigRule } from "../../config/legacy.shared.js";
import { listPluginDoctorLegacyConfigRules } from "../../plugins/doctor-contract-registry.js";
import { getBootstrapChannelPlugin } from "./bootstrap-registry.js";
import { loadBundledChannelDoctorContractApi } from "./doctor-contract-api.js";
import type { ChannelId } from "./types.public.js";
function collectConfiguredChannelIds(raw: unknown): ChannelId[] {
@@ -19,14 +20,25 @@ function collectConfiguredChannelIds(raw: unknown): ChannelId[] {
export function collectChannelLegacyConfigRules(raw?: unknown): LegacyConfigRule[] {
const channelIds = collectConfiguredChannelIds(raw);
const rules: LegacyConfigRule[] = [];
const unresolvedChannelIds: ChannelId[] = [];
for (const channelId of channelIds) {
const plugin = getBootstrapChannelPlugin(channelId);
if (!plugin) {
const contractRules = loadBundledChannelDoctorContractApi(channelId)?.legacyConfigRules;
if (Array.isArray(contractRules) && contractRules.length > 0) {
rules.push(...contractRules);
continue;
}
rules.push(...(plugin.doctor?.legacyConfigRules ?? []));
const plugin = getBootstrapChannelPlugin(channelId);
if (plugin?.doctor?.legacyConfigRules?.length) {
rules.push(...plugin.doctor.legacyConfigRules);
continue;
}
unresolvedChannelIds.push(channelId);
}
if (unresolvedChannelIds.length > 0) {
rules.push(...listPluginDoctorLegacyConfigRules({ pluginIds: unresolvedChannelIds }));
}
rules.push(...listPluginDoctorLegacyConfigRules({ pluginIds: channelIds }));
const seen = new Set<string>();
return rules.filter((rule) => {

View File

@@ -573,11 +573,10 @@ export function validateConfigObjectRaw(
raw: unknown,
): { ok: true; config: OpenClawConfig } | { ok: false; issues: ConfigValidationIssue[] } {
const policyIssues = collectUnsupportedSecretRefPolicyIssues(raw);
const legacyIssues = findLegacyConfigIssues(
raw,
raw,
listPluginDoctorLegacyConfigRules({ pluginIds: collectRelevantDoctorPluginIds(raw) }),
);
const extraLegacyRules = listPluginDoctorLegacyConfigRules({
pluginIds: collectRelevantDoctorPluginIds(raw),
});
const legacyIssues = findLegacyConfigIssues(raw, raw, extraLegacyRules);
if (legacyIssues.length > 0) {
return {
ok: false,