mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-23 00:53:38 +02:00
Tighten managed skill reconcile behavior
This commit is contained in:
1 parent
ce42fb3c97
commit
4de53ef96f
4 files changed
+65
No files matched your search
@@ -1275,6 +1275,7 @@ export function createTestHarness(options: TestHarnessOptions): TestHarness {
|
||||
} satisfies PluginManagedSkillResolution;
|
||||
},
|
||||
async reset(skillKey, companyId) {
|
||||
requireCapability(manifest, capabilitySet, "skills.managed");
|
||||
const existing = await this.get(skillKey, companyId);
|
||||
const declaration = manifest.skills?.find((skill) => skill.skillKey === skillKey);
|
||||
if (!declaration) return existing;
|
||||
|
||||
@@ -174,6 +174,25 @@ describeEmbeddedPostgres("plugin-managed skills", () => {
|
||||
expect(reset.skill?.markdown).toContain("key: \"plugin/paperclip-managed-skills-test/wiki-maintainer\"");
|
||||
});
|
||||
|
||||
it("does not rewrite managed skill bindings when defaults are unchanged", async () => {
|
||||
const { companyId, services } = await seedCompanyAndPlugin();
|
||||
const created = await services.skills.managedReconcile({ companyId, skillKey: "wiki-maintainer" });
|
||||
expect(created.skillId).toBeTruthy();
|
||||
|
||||
const [binding] = await db.select().from(pluginManagedResources);
|
||||
const oldUpdatedAt = new Date("2026-01-01T00:00:00.000Z");
|
||||
await db
|
||||
.update(pluginManagedResources)
|
||||
.set({ updatedAt: oldUpdatedAt })
|
||||
.where(eq(pluginManagedResources.id, binding.id));
|
||||
|
||||
const reconciled = await services.skills.managedReconcile({ companyId, skillKey: "wiki-maintainer" });
|
||||
const [bindingAfter] = await db.select().from(pluginManagedResources);
|
||||
|
||||
expect(reconciled.status).toBe("resolved");
|
||||
expect(bindingAfter.updatedAt.toISOString()).toBe(oldUpdatedAt.toISOString());
|
||||
});
|
||||
|
||||
it("relinks an existing canonical skill without overwriting operator edits", async () => {
|
||||
const { companyId, services } = await seedCompanyAndPlugin();
|
||||
const created = await services.skills.managedReconcile({ companyId, skillKey: "wiki-maintainer" });
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import type { PaperclipPluginManifestV1 } from "@paperclipai/shared";
|
||||
import { createTestHarness } from "@paperclipai/plugin-sdk/testing";
|
||||
|
||||
describe("plugin SDK test harness", () => {
|
||||
it("requires skills.managed capability before resetting a missing declaration", async () => {
|
||||
const manifest: PaperclipPluginManifestV1 = {
|
||||
id: "paperclip.test-missing-managed-skill-capability",
|
||||
apiVersion: 1,
|
||||
version: "0.1.0",
|
||||
displayName: "Missing Managed Skill Capability",
|
||||
description: "Test plugin",
|
||||
author: "Paperclip",
|
||||
categories: ["automation"],
|
||||
capabilities: [],
|
||||
entrypoints: { worker: "./dist/worker.js" },
|
||||
skills: [{
|
||||
skillKey: "wiki-maintainer",
|
||||
displayName: "Wiki Maintainer",
|
||||
}],
|
||||
};
|
||||
const harness = createTestHarness({ manifest });
|
||||
|
||||
await expect(harness.ctx.skills.managed.reset("unknown-skill", "company-1")).rejects.toThrow(
|
||||
"missing required capability 'skills.managed'",
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -122,6 +122,17 @@ function buildSkillDefaults(
|
||||
};
|
||||
}
|
||||
|
||||
function stableJson(value: unknown): string {
|
||||
if (Array.isArray(value)) return `[${value.map(stableJson).join(",")}]`;
|
||||
if (value && typeof value === "object") {
|
||||
return `{${Object.entries(value as Record<string, unknown>)
|
||||
.sort(([left], [right]) => left.localeCompare(right))
|
||||
.map(([key, entry]) => `${JSON.stringify(key)}:${stableJson(entry)}`)
|
||||
.join(",")}}`;
|
||||
}
|
||||
return JSON.stringify(value);
|
||||
}
|
||||
|
||||
function resolution(
|
||||
pluginKey: string,
|
||||
companyId: string,
|
||||
@@ -177,6 +188,12 @@ export function pluginManagedSkillService(
|
||||
const defaultsJson = buildSkillDefaults(options.pluginKey, declaration);
|
||||
const existing = await getBinding(companyId, declaration.skillKey);
|
||||
if (existing) {
|
||||
if (
|
||||
existing.resourceId === skillId &&
|
||||
stableJson(existing.defaultsJson) === stableJson(defaultsJson)
|
||||
) {
|
||||
return existing;
|
||||
}
|
||||
return db
|
||||
.update(pluginManagedResources)
|
||||
.set({
|
||||
|
||||
Reference in new issue
Block a user