fix(plugins): retain operator uninstall during reconciliation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
Devin FoleyandPaperclip committed 2026-09-19 08:57:50 -07:00
1 parent 7d8b8331d2
commit 5deab10a28
2 files changed
+14

No files matched your search

@@ -472,6 +472,17 @@ describe("ensureBundledPlugins", () => {
expect(deps.logger.error).toHaveBeenCalled();
});
it("does not replace an operator uninstall with a distribution approval request", 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 { deps, loadManifest, update, updateStatus, installPlugin } = makeDeps({ rows: { "acme.widget": { id: "row-widget", pluginKey: "acme.widget", status: "uninstalled" } } });
loadManifest.mockResolvedValue({ ...makeManifest("acme.widget", "0.1.0"), capabilities: ["issues.read"] });
await ensureBundledPlugins([{ ...distribution, distribution }], deps, { reinstallUninstalled: false });
expect(update).not.toHaveBeenCalled();
expect(updateStatus).not.toHaveBeenCalled();
expect(installPlugin).not.toHaveBeenCalled();
});
it("swallows a reconcile error and continues boot", async () => {
const { deps, update } = makeDeps({
rows: {
+3
View File
@@ -265,6 +265,9 @@ async function reconcileBundledPluginManifest(
verifiedManifest?: PaperclipPluginManifestV1,
): Promise<"upgrade_pending" | undefined> {
try {
// Managed reinstalls take the install path instead; this branch means the
// operator's uninstall must be retained, including its status.
if (install.distribution && existing.status === "uninstalled") return;
if (!verifiedManifest && !bundleManifestExists(install.localPath)) return;
const bundleManifest = verifiedManifest ?? await deps.loader.loadManifest(install.localPath);
if (!bundleManifest) return;