

















@@ -545,6 +545,109 @@ describe("update-cli", () => {
545545expect(spawn).not.toHaveBeenCalled();
546546});
547547548+it("uses a fail-closed integrity policy for post-core plugin updates", async () => {
549+await withEnvAsync(
550+{
551+OPENCLAW_UPDATE_POST_CORE: "1",
552+OPENCLAW_UPDATE_POST_CORE_CHANNEL: "stable",
553+},
554+async () => {
555+await updateCommand({ restart: false });
556+},
557+);
558+559+const updateCall = updateNpmInstalledPlugins.mock.calls[0]?.[0] as
560+| {
561+onIntegrityDrift?: (drift: {
562+pluginId: string;
563+spec: string;
564+expectedIntegrity: string;
565+actualIntegrity: string;
566+resolvedSpec?: string;
567+}) => Promise<boolean>;
568+}
569+| undefined;
570+const onIntegrityDrift = updateCall?.onIntegrityDrift;
571+expect(onIntegrityDrift).toBeTypeOf("function");
572+if (!onIntegrityDrift) {
573+throw new Error("missing integrity drift handler");
574+}
575+576+vi.mocked(runtimeCapture.log).mockClear();
577+await expect(
578+onIntegrityDrift({
579+pluginId: "demo",
580+spec: "@openclaw/demo@1.0.0",
581+resolvedSpec: "@openclaw/demo@1.0.0",
582+expectedIntegrity: "sha512-old",
583+actualIntegrity: "sha512-new",
584+}),
585+).resolves.toBe(false);
586+const logs = vi.mocked(runtimeCapture.log).mock.calls.map((call) => String(call[0]));
587+expect(logs.join("\n")).toContain("Plugin update aborted");
588+});
589+590+it("includes plugin integrity drift details in update json output", async () => {
591+updateNpmInstalledPlugins.mockImplementationOnce(
592+async (params: {
593+config: OpenClawConfig;
594+onIntegrityDrift?: (drift: {
595+pluginId: string;
596+spec: string;
597+resolvedSpec?: string;
598+resolvedVersion?: string;
599+expectedIntegrity: string;
600+actualIntegrity: string;
601+dryRun: boolean;
602+}) => Promise<boolean>;
603+}) => {
604+const proceed = await params.onIntegrityDrift?.({
605+pluginId: "demo",
606+spec: "@openclaw/demo@1.0.0",
607+resolvedSpec: "@openclaw/demo@1.0.0",
608+resolvedVersion: "1.0.0",
609+expectedIntegrity: "sha512-old",
610+actualIntegrity: "sha512-new",
611+dryRun: false,
612+});
613+return {
614+changed: false,
615+config: params.config,
616+outcomes: [
617+{
618+pluginId: "demo",
619+status: "error",
620+message:
621+proceed === false
622+ ? "Failed to update demo: aborted: npm package integrity drift detected for @openclaw/demo@1.0.0"
623+ : "unexpected drift continuation",
624+},
625+],
626+};
627+},
628+);
629+vi.mocked(defaultRuntime.writeJson).mockClear();
630+631+await updateCommand({ json: true, restart: false });
632+633+const jsonOutput = vi.mocked(defaultRuntime.writeJson).mock.calls.at(-1)?.[0] as
634+| UpdateRunResult
635+| undefined;
636+expect(jsonOutput?.postUpdate?.plugins?.integrityDrifts).toEqual([
637+{
638+pluginId: "demo",
639+spec: "@openclaw/demo@1.0.0",
640+resolvedSpec: "@openclaw/demo@1.0.0",
641+resolvedVersion: "1.0.0",
642+expectedIntegrity: "sha512-old",
643+actualIntegrity: "sha512-new",
644+action: "aborted",
645+},
646+]);
647+expect(jsonOutput?.postUpdate?.plugins?.status).toBe("error");
648+expect(jsonOutput?.postUpdate?.plugins?.npm.outcomes[0]?.status).toBe("error");
649+});
650+548651it.each([
549652{
550653name: "preview mode",
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。