fix: reject stale plugin metadata inventory

This commit is contained in:
Shakker
2026-04-27 16:43:31 +01:00
parent 197c83138e
commit 58b4407cda
3 changed files with 103 additions and 2 deletions

View File

@@ -289,4 +289,70 @@ describe("loadPluginLookUpTable", () => {
}),
);
});
it("rebuilds when a provided metadata snapshot has stale plugin inventory", async () => {
const snapshotPlugins = [
createManifestRecord({
id: "telegram",
origin: "bundled",
channels: ["telegram"],
}),
];
const requestedPlugins = [
createManifestRecord({
id: "telegram",
origin: "bundled",
channels: ["telegram"],
}),
createManifestRecord({
id: "discord",
origin: "bundled",
channels: ["discord"],
}),
];
const config = {
channels: {
telegram: { token: "configured" },
},
} as OpenClawConfig;
const policyHash = resolveInstalledPluginIndexPolicyHash(config);
const snapshotIndex = createIndex(snapshotPlugins, { policyHash });
const requestedIndex = createIndex(requestedPlugins, { policyHash });
const snapshotRegistry: PluginManifestRegistry = {
plugins: snapshotPlugins,
diagnostics: [],
};
const requestedRegistry: PluginManifestRegistry = {
plugins: requestedPlugins,
diagnostics: [],
};
loadPluginManifestRegistryForInstalledIndex
.mockReturnValueOnce(snapshotRegistry)
.mockReturnValueOnce(requestedRegistry);
const { loadPluginMetadataSnapshot } = await import("./plugin-metadata-snapshot.js");
const { loadPluginLookUpTable } = await import("./plugin-lookup-table.js");
const metadataSnapshot = loadPluginMetadataSnapshot({
config,
env: {},
index: snapshotIndex,
});
loadPluginManifestRegistryForInstalledIndex.mockClear();
const table = loadPluginLookUpTable({
config,
env: {},
index: requestedIndex,
metadataSnapshot,
});
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledWith(
expect.objectContaining({
index: requestedIndex,
config,
}),
);
expect(table.manifestRegistry).toBe(requestedRegistry);
});
});

View File

@@ -60,6 +60,7 @@ export function loadPluginLookUpTable(params: LoadPluginLookUpTableParams): Plug
snapshot: params.metadataSnapshot,
config: requestedSnapshotConfig,
workspaceDir: params.workspaceDir,
index: params.index,
})
? params.metadataSnapshot
: loadPluginMetadataSnapshot({

View File

@@ -51,14 +51,48 @@ export type LoadPluginMetadataSnapshotParams = {
index?: PluginRegistrySnapshot;
};
function indexesMatch(
left: PluginRegistrySnapshot | undefined,
right: PluginRegistrySnapshot | undefined,
): boolean {
if (!left || !right) {
return true;
}
if (
left.version !== right.version ||
left.hostContractVersion !== right.hostContractVersion ||
left.compatRegistryVersion !== right.compatRegistryVersion ||
left.migrationVersion !== right.migrationVersion ||
left.policyHash !== right.policyHash ||
left.plugins.length !== right.plugins.length
) {
return false;
}
for (let index = 0; index < left.plugins.length; index += 1) {
const leftPlugin = left.plugins[index];
const rightPlugin = right.plugins[index];
if (
!rightPlugin ||
leftPlugin.pluginId !== rightPlugin.pluginId ||
leftPlugin.manifestHash !== rightPlugin.manifestHash ||
leftPlugin.installRecordHash !== rightPlugin.installRecordHash
) {
return false;
}
}
return true;
}
export function isPluginMetadataSnapshotCompatible(params: {
snapshot: Pick<PluginMetadataSnapshot, "policyHash" | "workspaceDir">;
snapshot: Pick<PluginMetadataSnapshot, "index" | "policyHash" | "workspaceDir">;
config: OpenClawConfig;
workspaceDir?: string;
index?: PluginRegistrySnapshot;
}): boolean {
return (
params.snapshot.policyHash === resolveInstalledPluginIndexPolicyHash(params.config) &&
(params.snapshot.workspaceDir ?? "") === (params.workspaceDir ?? "")
(params.snapshot.workspaceDir ?? "") === (params.workspaceDir ?? "") &&
indexesMatch(params.snapshot.index, params.index)
);
}