mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
Make managed resource binding upsert atomic
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
ed39a0c504
commit
c47806d6cc
2 files changed
+53
-19
No files matched your search
@@ -509,6 +509,44 @@ describeEmbeddedPostgres("built-in agents", () => {
|
||||
expect(coachGrantKeys).not.toContain("skills:create");
|
||||
});
|
||||
|
||||
it("recreates missing managed resource bindings idempotently during concurrent reconcile", async () => {
|
||||
const companyId = await seedCompany({ requireApproval: false });
|
||||
await agentService(db).create(companyId, {
|
||||
name: "CEO",
|
||||
role: "ceo",
|
||||
status: "idle",
|
||||
adapterType: "codex_local",
|
||||
adapterConfig: { model: "gpt-5.4" },
|
||||
runtimeConfig: {},
|
||||
permissions: {},
|
||||
});
|
||||
const builtIns = builtInAgentService(db);
|
||||
await builtIns.ensure(companyId, "reflection-coach");
|
||||
await db.delete(builtInManagedResources).where(eq(builtInManagedResources.companyId, companyId));
|
||||
|
||||
const states = await Promise.all([
|
||||
builtIns.ensure(companyId, "reflection-coach"),
|
||||
builtIns.ensure(companyId, "reflection-coach"),
|
||||
]);
|
||||
|
||||
expect(states).toHaveLength(2);
|
||||
for (const state of states) {
|
||||
expect(state.resources.map((resource) => [resource.resourceKind, resource.stockStatus])).toEqual([
|
||||
["instructions", "stock_current"],
|
||||
["skill", "stock_current"],
|
||||
["routine", "stock_current"],
|
||||
]);
|
||||
}
|
||||
const bindings = await db
|
||||
.select()
|
||||
.from(builtInManagedResources)
|
||||
.where(eq(builtInManagedResources.companyId, companyId));
|
||||
expect(bindings).toHaveLength(3);
|
||||
expect(new Set(bindings.map((binding) =>
|
||||
`${binding.bundleKey}:${binding.resourceKind}:${binding.resourceKey}`
|
||||
)).size).toBe(3);
|
||||
});
|
||||
|
||||
it("preserves new-agent approval gates during automatic Reflection Coach provisioning", async () => {
|
||||
const companyId = await seedCompany({ requireApproval: true });
|
||||
const mutationPolicy = {
|
||||
|
||||
@@ -662,29 +662,25 @@ export function builtInAgentService(db: Db) {
|
||||
stockHash: string;
|
||||
defaultsJson: Record<string, unknown>;
|
||||
}) {
|
||||
const existing = await getManagedResourceBinding(
|
||||
input.companyId,
|
||||
input.bundleKey,
|
||||
input.resourceKind,
|
||||
input.resourceKey,
|
||||
);
|
||||
if (existing) {
|
||||
return db
|
||||
.update(builtInManagedResources)
|
||||
.set({
|
||||
const now = new Date();
|
||||
return db
|
||||
.insert(builtInManagedResources)
|
||||
.values(input)
|
||||
.onConflictDoUpdate({
|
||||
target: [
|
||||
builtInManagedResources.companyId,
|
||||
builtInManagedResources.bundleKey,
|
||||
builtInManagedResources.resourceKind,
|
||||
builtInManagedResources.resourceKey,
|
||||
],
|
||||
set: {
|
||||
resourceId: input.resourceId,
|
||||
stockVersion: input.stockVersion,
|
||||
stockHash: input.stockHash,
|
||||
defaultsJson: input.defaultsJson,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(builtInManagedResources.id, existing.id))
|
||||
.returning()
|
||||
.then((rows) => rows[0] ?? null);
|
||||
}
|
||||
return db
|
||||
.insert(builtInManagedResources)
|
||||
.values(input)
|
||||
updatedAt: now,
|
||||
},
|
||||
})
|
||||
.returning()
|
||||
.then((rows) => rows[0] ?? null);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user