fix(plugins): compare distribution manifests semantically

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin Foley
2026-09-19 08:57:51 -07:00
co-authored by Paperclip
parent 4ad5977d24
commit 488b3754ae
2 changed files with 13 additions and 1 deletions
@@ -498,6 +498,17 @@ describe("ensureBundledPlugins", () => {
expect(deps.lifecycle.load).not.toHaveBeenCalled();
});
it("ignores object key order when comparing a distribution manifest read from JSONB", async () => {
const localPath = path.join(CATALOG_ROOT, "distribution/widget");
const distribution = { key: "widget", pluginKey: "acme.widget", version: "0.1.0", directory: "widget", digest: `sha256:${"a".repeat(64)}`, localPath, entrypoints: { worker: "dist/worker.js" } };
const manifest = { ...makeManifest("acme.widget", "0.1.0"), entrypoints: { worker: "dist/worker.js", ui: "dist/ui" } };
const reordered = { entrypoints: { ui: "dist/ui", worker: "dist/worker.js" }, capabilities: [], version: "0.1.0", apiVersion: 1, id: "acme.widget" };
const { deps, loadManifest, update } = makeDeps({ rows: { "acme.widget": { id: "row-widget", pluginKey: "acme.widget", status: "ready", packagePath: localPath, manifestJson: reordered } } });
loadManifest.mockResolvedValue(manifest);
await ensureBundledPlugins([{ ...distribution, distribution }], deps, { reinstallUninstalled: true });
expect(update).not.toHaveBeenCalled();
});
it("swallows a reconcile error and continues boot", async () => {
const { deps, update } = makeDeps({
rows: {
+2 -1
View File
@@ -1,5 +1,6 @@
import path from "node:path";
import fs from "node:fs";
import { isDeepStrictEqual } from "node:util";
import type { PaperclipPluginManifestV1 } from "@paperclipai/shared";
import { assertDistributionManifestCapabilities, readDistributionPluginCatalog, type DistributionPlugin } from "./distribution-plugin-catalog.js";
@@ -278,7 +279,7 @@ async function reconcileBundledPluginManifest(
requiresApproval = bundleManifest.capabilities.some((capability) => !approved.has(capability));
}
const rebindPackage = install.distribution && existing.packagePath !== install.localPath && existing.status !== "uninstalled";
const refreshDistribution = install.distribution && JSON.stringify(bundleManifest) !== JSON.stringify(existing.manifestJson);
const refreshDistribution = install.distribution && !isDeepStrictEqual(bundleManifest, existing.manifestJson);
if (bundleManifest.version === existing.version && !rebindPackage && !requiresApproval && !refreshDistribution) return;
await deps.registry.update(existing.id, {
version: bundleManifest.version,