mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-10-25 09:41:04 +01:00
Fix Greptile findings on extracted work
This commit is contained in:
1 parent
1a9dc88279
commit
8074c5bd11
7 files changed
+78
-13
No files matched your search
@@ -0,0 +1,16 @@
|
||||
DELETE FROM "issue_work_products" loser
|
||||
USING "issue_work_products" keeper
|
||||
WHERE loser."company_id" = keeper."company_id"
|
||||
AND loser."issue_id" = keeper."issue_id"
|
||||
AND loser."type" = keeper."type"
|
||||
AND loser."provider" = keeper."provider"
|
||||
AND loser."external_id" = keeper."external_id"
|
||||
AND (
|
||||
keeper."updated_at" > loser."updated_at"
|
||||
OR (
|
||||
keeper."updated_at" = loser."updated_at"
|
||||
AND keeper."id"::text > loser."id"::text
|
||||
)
|
||||
);--> statement-breakpoint
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS "issue_work_products_company_issue_identity_uq"
|
||||
ON "issue_work_products" USING btree ("company_id", "issue_id", "type", "provider", "external_id");
|
||||
@@ -918,6 +918,13 @@
|
||||
"when": 1783025224120,
|
||||
"tag": "0130_run_responsible_user_invariant",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 131,
|
||||
"version": "7",
|
||||
"when": 1783343000000,
|
||||
"tag": "0131_issue_work_product_identity_unique",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
pgTable,
|
||||
text,
|
||||
timestamp,
|
||||
uniqueIndex,
|
||||
uuid,
|
||||
} from "drizzle-orm/pg-core";
|
||||
import type { SourceTrustMetadata } from "@paperclipai/shared";
|
||||
@@ -58,6 +59,13 @@ export const issueWorkProducts = pgTable(
|
||||
table.provider,
|
||||
table.externalId,
|
||||
),
|
||||
companyIssueProviderExternalUq: uniqueIndex("issue_work_products_company_issue_identity_uq").on(
|
||||
table.companyId,
|
||||
table.issueId,
|
||||
table.type,
|
||||
table.provider,
|
||||
table.externalId,
|
||||
),
|
||||
companyUpdatedIdx: index("issue_work_products_company_updated_idx").on(
|
||||
table.companyId,
|
||||
table.updatedAt,
|
||||
|
||||
@@ -394,7 +394,7 @@ describeEmbeddedPostgres("heartbeat stale queued-run invalidation", () => {
|
||||
expect(countExecuteCallsForRun(run!.id)).toBe(1);
|
||||
});
|
||||
|
||||
it("skips generic timer wakes by default when there is no assigned issue work", async () => {
|
||||
it("allows legacy generic timer wakes by default when no skip policy is set", async () => {
|
||||
const { agentId } = await seedCompanyAndAgent({
|
||||
heartbeatConfig: {
|
||||
enabled: true,
|
||||
@@ -406,8 +406,9 @@ describeEmbeddedPostgres("heartbeat stale queued-run invalidation", () => {
|
||||
triggerDetail: "schedule",
|
||||
});
|
||||
|
||||
expect(run).toBeNull();
|
||||
expect(mockAdapterExecute).not.toHaveBeenCalled();
|
||||
expect(run).not.toBeNull();
|
||||
await waitForCondition(async () => countExecuteCallsForRun(run!.id) > 0);
|
||||
expect(countExecuteCallsForRun(run!.id)).toBe(1);
|
||||
});
|
||||
|
||||
it("allows explicit proactive generic timer wakes without assigned issue work", async () => {
|
||||
|
||||
@@ -251,4 +251,34 @@ describeEmbeddedPostgres("work product adoption", () => {
|
||||
reviewState: "needs_board_review",
|
||||
});
|
||||
});
|
||||
|
||||
it("deduplicates concurrent adoption for the same external work product", async () => {
|
||||
await seedBase();
|
||||
|
||||
const input = {
|
||||
db,
|
||||
companyId,
|
||||
issueId,
|
||||
runId,
|
||||
projectId,
|
||||
executionWorkspaceId,
|
||||
resultJson: {
|
||||
pullRequestUrl: "https://github.com/paperclipai/paperclip/pull/11218",
|
||||
},
|
||||
};
|
||||
|
||||
await Promise.all([
|
||||
adoptWorkProductsForRun(input),
|
||||
adoptWorkProductsForRun(input),
|
||||
]);
|
||||
|
||||
const rows = await db.select().from(issueWorkProducts).where(eq(issueWorkProducts.issueId, issueId));
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0]).toMatchObject({
|
||||
type: "pull_request",
|
||||
provider: "github",
|
||||
externalId: "paperclipai/paperclip#11218",
|
||||
isPrimary: true,
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -8672,7 +8672,7 @@ export function heartbeatService(db: Db, options: HeartbeatServiceOptions = {})
|
||||
heartbeat.skipTimerWhenNoActionableWork ??
|
||||
heartbeat.requireActionableTimerWork ??
|
||||
heartbeat.issueOnlyTimer,
|
||||
true,
|
||||
false,
|
||||
),
|
||||
maxDailyRuns: normalizeOptionalNonNegativeInteger(
|
||||
heartbeat.maxDailyRuns ?? heartbeat.dailyRunLimit ?? heartbeat.dailyRunCap ?? heartbeat.maxRunsPerDay,
|
||||
|
||||
@@ -472,22 +472,25 @@ export async function adoptWorkProductsForRun(input: {
|
||||
));
|
||||
}
|
||||
|
||||
if (existing) {
|
||||
await tx
|
||||
.update(issueWorkProducts)
|
||||
.set(values)
|
||||
.where(eq(issueWorkProducts.id, existing.id));
|
||||
return "updated" as const;
|
||||
}
|
||||
|
||||
await tx
|
||||
.insert(issueWorkProducts)
|
||||
.values({
|
||||
...values,
|
||||
createdByRunId: input.runId,
|
||||
createdAt: now,
|
||||
})
|
||||
.onConflictDoUpdate({
|
||||
target: [
|
||||
issueWorkProducts.companyId,
|
||||
issueWorkProducts.issueId,
|
||||
issueWorkProducts.type,
|
||||
issueWorkProducts.provider,
|
||||
issueWorkProducts.externalId,
|
||||
],
|
||||
set: values,
|
||||
});
|
||||
return "created" as const;
|
||||
|
||||
return existing ? "updated" as const : "created" as const;
|
||||
});
|
||||
|
||||
if (outcome === "created") created += 1;
|
||||
|
||||
Reference in new issue
Block a user