mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
e4aab26d01
* fix(cli): avoid repeated state migration scans * fix(build): preserve source runtime provenance * fix(cli): persist state checkpoint before plugin convergence * fix(cli): invalidate migration checkpoints on input changes * fix(plugins): refresh changed doctor contracts * fix(plugins): preserve warm Doctor contract indexes * test(plugins): align manifest registry fixtures * test(cli): isolate ACP process state * fix(doctor): persist refreshed plugin indexes * fix(doctor): verify persisted plugin index from disk * fix(doctor): fence plugin index persistence * fix(doctor): retain validated migration lease * fix(plugins): canonicalize persisted build metadata * fix(plugins): preserve config policy during install repair
122 lines
4.3 KiB
TypeScript
122 lines
4.3 KiB
TypeScript
// Built-CLI proof for durable Doctor plugin-index refresh during gateway startup.
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
import type { OpenClawConfig } from "../../src/config/types.openclaw.js";
|
|
import { hasActiveStartupMigrationLease } from "../../src/infra/startup-migration-checkpoint.js";
|
|
import {
|
|
readPersistedInstalledPluginIndexSync,
|
|
writePersistedInstalledPluginIndexSync,
|
|
} from "../../src/plugins/installed-plugin-index-store.js";
|
|
import { clearPluginMetadataLifecycleCaches } from "../../src/plugins/plugin-metadata-lifecycle.js";
|
|
import { loadPluginMetadataSnapshot } from "../../src/plugins/plugin-metadata-snapshot.js";
|
|
import { writeManagedNpmPlugin } from "../../src/plugins/test-helpers/managed-npm-plugin.js";
|
|
import { closeOpenClawStateDatabaseForTest } from "../../src/state/openclaw-state-db.js";
|
|
import {
|
|
createOpenClawTestInstance,
|
|
type OpenClawTestInstance,
|
|
} from "../helpers/openclaw-test-instance.js";
|
|
|
|
const instances: OpenClawTestInstance[] = [];
|
|
|
|
afterEach(async () => {
|
|
await Promise.all(instances.splice(0).map((instance) => instance.cleanup()));
|
|
clearPluginMetadataLifecycleCaches();
|
|
closeOpenClawStateDatabaseForTest();
|
|
});
|
|
|
|
describe("Doctor plugin index persistence built CLI proof", () => {
|
|
it("starts after replacing and verifying a stale persisted Doctor index", async () => {
|
|
const instance = await createOpenClawTestInstance({
|
|
name: "doctor-plugin-index-persistence",
|
|
env: {
|
|
OPENCLAW_TEST_FAST: "1",
|
|
},
|
|
startTimeoutMs: 90_000,
|
|
});
|
|
instances.push(instance);
|
|
|
|
const config = JSON.parse(fs.readFileSync(instance.configPath, "utf8")) as OpenClawConfig;
|
|
const pluginId = "legacy-doctor-index";
|
|
const pluginDir = writeManagedNpmPlugin({
|
|
stateDir: instance.stateDir,
|
|
packageName: "@openclaw/legacy-doctor-index",
|
|
pluginId,
|
|
version: "1.0.0",
|
|
});
|
|
const packageJsonPath = path.join(pluginDir, "package.json");
|
|
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8")) as {
|
|
openclaw: Record<string, unknown>;
|
|
};
|
|
fs.writeFileSync(
|
|
packageJsonPath,
|
|
JSON.stringify({
|
|
...packageJson,
|
|
openclaw: {
|
|
...packageJson.openclaw,
|
|
build: {
|
|
bundledDist: false,
|
|
openclawVersion: "2026.7.2",
|
|
pluginSdkVersion: "2026.7.2",
|
|
},
|
|
},
|
|
}),
|
|
"utf8",
|
|
);
|
|
fs.writeFileSync(
|
|
path.join(pluginDir, "doctor-contract-api.cjs"),
|
|
"module.exports = { stateMigrations: [] };\n",
|
|
"utf8",
|
|
);
|
|
|
|
const current = loadPluginMetadataSnapshot({
|
|
config,
|
|
env: instance.env,
|
|
stateDir: instance.stateDir,
|
|
});
|
|
const legacyIndex = {
|
|
...current.index,
|
|
plugins: current.index.plugins.map((plugin) => {
|
|
const {
|
|
doctorContractFile: _doctorContractFile,
|
|
doctorContractHash: _doctorContractHash,
|
|
...legacyPlugin
|
|
} = plugin;
|
|
return legacyPlugin;
|
|
}),
|
|
};
|
|
writePersistedInstalledPluginIndexSync(legacyIndex, { env: instance.env });
|
|
clearPluginMetadataLifecycleCaches();
|
|
closeOpenClawStateDatabaseForTest();
|
|
|
|
expect(await instance.entrypoint()).toEqual([
|
|
expect.stringMatching(/^dist\/index\.(?:js|mjs)$/u),
|
|
]);
|
|
await instance.startGateway();
|
|
expect(hasActiveStartupMigrationLease({ env: instance.env }), instance.logs()).toBe(false);
|
|
|
|
clearPluginMetadataLifecycleCaches();
|
|
closeOpenClawStateDatabaseForTest();
|
|
const reread = loadPluginMetadataSnapshot({
|
|
config,
|
|
env: instance.env,
|
|
stateDir: instance.stateDir,
|
|
allowCurrent: false,
|
|
});
|
|
expect(reread.registrySource, instance.logs()).toBe("persisted");
|
|
expect(reread.registryDiagnostics, instance.logs()).toStrictEqual([]);
|
|
|
|
const persisted = readPersistedInstalledPluginIndexSync({ env: instance.env });
|
|
const persistedPlugin = persisted?.plugins.find((plugin) => plugin.pluginId === pluginId);
|
|
expect(persistedPlugin, instance.logs()).toMatchObject({
|
|
doctorContractFile: {
|
|
ctimeMs: expect.any(Number),
|
|
mtimeMs: expect.any(Number),
|
|
size: expect.any(Number),
|
|
},
|
|
doctorContractHash: expect.stringMatching(/^[a-f0-9]{64}$/u),
|
|
packageBuild: { bundledDist: false },
|
|
});
|
|
}, 120_000);
|
|
});
|