fix: guard plugin metadata snapshot reuse

This commit is contained in:
Shakker
2026-04-27 16:25:09 +01:00
parent 5240422f03
commit ab28cfa9d4
6 changed files with 106 additions and 19 deletions

View File

@@ -1,5 +1,6 @@
import { beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
import type { PluginRegistrySnapshot } from "./plugin-registry.js";
@@ -47,13 +48,16 @@ function createManifestRecord(
};
}
function createIndex(plugins: readonly PluginManifestRecord[]): PluginRegistrySnapshot {
function createIndex(
plugins: readonly PluginManifestRecord[],
params: { policyHash?: string } = {},
): PluginRegistrySnapshot {
return {
version: 1,
hostContractVersion: "test",
compatRegistryVersion: "test",
migrationVersion: 1,
policyHash: "policy",
policyHash: params.policyHash ?? "policy",
generatedAtMs: 1,
installRecords: {},
diagnostics: [],
@@ -194,6 +198,15 @@ describe("loadPluginLookUpTable", () => {
}),
];
const index = createIndex(plugins);
const config = {
channels: {
telegram: { token: "configured" },
},
} as OpenClawConfig;
const compatibleIndex = {
...index,
policyHash: resolveInstalledPluginIndexPolicyHash(config),
};
const manifestRegistry: PluginManifestRegistry = {
plugins,
diagnostics: [],
@@ -203,22 +216,14 @@ describe("loadPluginLookUpTable", () => {
const { loadPluginLookUpTable } = await import("./plugin-lookup-table.js");
const metadataSnapshot = loadPluginMetadataSnapshot({
config: {
channels: {
telegram: { token: "configured" },
},
} as OpenClawConfig,
config,
env: {},
index,
index: compatibleIndex,
});
loadPluginManifestRegistryForInstalledIndex.mockClear();
const table = loadPluginLookUpTable({
config: {
channels: {
telegram: { token: "configured" },
},
} as OpenClawConfig,
config,
env: {},
metadataSnapshot,
});
@@ -229,4 +234,59 @@ describe("loadPluginLookUpTable", () => {
expect(table.metrics.indexPluginCount).toBe(1);
expect(table.metrics.manifestPluginCount).toBe(1);
});
it("rebuilds when a provided metadata snapshot has a stale plugin policy", async () => {
const plugins = [
createManifestRecord({
id: "telegram",
origin: "bundled",
channels: ["telegram"],
}),
];
const snapshotConfig = {
plugins: {
allow: ["telegram"],
},
} as OpenClawConfig;
const requestedConfig = {
plugins: {
allow: ["other-plugin"],
},
} as OpenClawConfig;
const snapshotIndex = createIndex(plugins, {
policyHash: resolveInstalledPluginIndexPolicyHash(snapshotConfig),
});
const requestedIndex = createIndex(plugins, {
policyHash: resolveInstalledPluginIndexPolicyHash(requestedConfig),
});
const manifestRegistry: PluginManifestRegistry = {
plugins,
diagnostics: [],
};
loadPluginManifestRegistryForInstalledIndex.mockReturnValue(manifestRegistry);
const { loadPluginMetadataSnapshot } = await import("./plugin-metadata-snapshot.js");
const { loadPluginLookUpTable } = await import("./plugin-lookup-table.js");
const metadataSnapshot = loadPluginMetadataSnapshot({
config: snapshotConfig,
env: {},
index: snapshotIndex,
});
loadPluginManifestRegistryForInstalledIndex.mockClear();
loadPluginLookUpTable({
config: requestedConfig,
env: {},
index: requestedIndex,
metadataSnapshot,
});
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledOnce();
expect(loadPluginManifestRegistryForInstalledIndex).toHaveBeenCalledWith(
expect.objectContaining({
index: requestedIndex,
config: requestedConfig,
}),
);
});
});

View File

@@ -6,6 +6,7 @@ import {
} from "./channel-plugin-ids.js";
import { hashJson } from "./installed-plugin-index-hash.js";
import {
isPluginMetadataSnapshotCompatible,
loadPluginMetadataSnapshot,
type PluginMetadataSnapshot,
type PluginMetadataSnapshotOwnerMaps,
@@ -52,14 +53,21 @@ export type LoadPluginLookUpTableParams = {
};
export function loadPluginLookUpTable(params: LoadPluginLookUpTableParams): PluginLookUpTable {
const requestedSnapshotConfig = params.activationSourceConfig ?? params.config;
const metadataSnapshot =
params.metadataSnapshot ??
loadPluginMetadataSnapshot({
config: params.config,
params.metadataSnapshot &&
isPluginMetadataSnapshotCompatible({
snapshot: params.metadataSnapshot,
config: requestedSnapshotConfig,
workspaceDir: params.workspaceDir,
env: params.env,
...(params.index ? { index: params.index } : {}),
});
})
? params.metadataSnapshot
: loadPluginMetadataSnapshot({
config: requestedSnapshotConfig,
workspaceDir: params.workspaceDir,
env: params.env,
...(params.index ? { index: params.index } : {}),
});
const { index, manifestRegistry } = metadataSnapshot;
const startupPlanStartedAt = performance.now();
const channelPluginIds = resolveChannelPluginIdsFromRegistry({ manifestRegistry });

View File

@@ -1,4 +1,5 @@
import type { OpenClawConfig } from "../config/types.openclaw.js";
import { resolveInstalledPluginIndexPolicyHash } from "./installed-plugin-index-policy.js";
import { loadPluginManifestRegistryForInstalledIndex } from "./manifest-registry-installed.js";
import type { PluginManifestRecord, PluginManifestRegistry } from "./manifest-registry.js";
import type { PluginDiagnostic } from "./manifest-types.js";
@@ -30,6 +31,8 @@ export type PluginMetadataSnapshotMetrics = {
};
export type PluginMetadataSnapshot = {
policyHash: string;
workspaceDir?: string;
index: PluginRegistrySnapshot;
registryDiagnostics: readonly PluginRegistrySnapshotDiagnostic[];
manifestRegistry: PluginManifestRegistry;
@@ -48,6 +51,17 @@ export type LoadPluginMetadataSnapshotParams = {
index?: PluginRegistrySnapshot;
};
export function isPluginMetadataSnapshotCompatible(params: {
snapshot: Pick<PluginMetadataSnapshot, "policyHash" | "workspaceDir">;
config: OpenClawConfig;
workspaceDir?: string;
}): boolean {
return (
params.snapshot.policyHash === resolveInstalledPluginIndexPolicyHash(params.config) &&
(params.snapshot.workspaceDir ?? "") === (params.workspaceDir ?? "")
);
}
function appendOwner(owners: Map<string, string[]>, ownedId: string, pluginId: string): void {
const existing = owners.get(ownedId);
if (existing) {
@@ -149,6 +163,8 @@ export function loadPluginMetadataSnapshot(
const totalMs = performance.now() - totalStartedAt;
return {
policyHash: index.policyHash,
...(params.workspaceDir ? { workspaceDir: params.workspaceDir } : {}),
index,
registryDiagnostics: registryResult.diagnostics,
manifestRegistry,