From 1cf7ba496c0fdaf91c75968696a1f24c2e2d7310 Mon Sep 17 00:00:00 2001 From: Dotta Date: Wed, 17 Jun 2026 15:33:50 +0000 Subject: [PATCH] fix(db): constrain annotation ownership --- .../0103_routine_description_annotations.sql | 16 ++++++++++++++++ .../src/schema/document_annotation_comments.ts | 7 ++++++- .../db/src/schema/document_annotation_threads.ts | 7 ++++++- server/src/__tests__/routines-service.test.ts | 6 ++++++ 4 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/db/src/migrations/0103_routine_description_annotations.sql b/packages/db/src/migrations/0103_routine_description_annotations.sql index 9c34612b618..42078598b96 100644 --- a/packages/db/src/migrations/0103_routine_description_annotations.sql +++ b/packages/db/src/migrations/0103_routine_description_annotations.sql @@ -62,6 +62,22 @@ DO $$ BEGIN END IF; END $$; --> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 FROM "pg_constraint" WHERE "conname" = 'document_annotation_threads_exactly_one_owner_chk' + ) THEN + ALTER TABLE "document_annotation_threads" ADD CONSTRAINT "document_annotation_threads_exactly_one_owner_chk" CHECK (num_nonnulls("issue_id", "routine_id") = 1); + END IF; +END $$; +--> statement-breakpoint +DO $$ BEGIN + IF NOT EXISTS ( + SELECT 1 FROM "pg_constraint" WHERE "conname" = 'document_annotation_comments_exactly_one_owner_chk' + ) THEN + ALTER TABLE "document_annotation_comments" ADD CONSTRAINT "document_annotation_comments_exactly_one_owner_chk" CHECK (num_nonnulls("issue_id", "routine_id") = 1); + END IF; +END $$; +--> statement-breakpoint CREATE INDEX IF NOT EXISTS "document_annotation_threads_company_routine_status_idx" ON "document_annotation_threads" USING btree ("company_id","routine_id","status"); --> statement-breakpoint CREATE INDEX IF NOT EXISTS "document_annotation_comments_company_routine_created_at_idx" ON "document_annotation_comments" USING btree ("company_id","routine_id","created_at"); diff --git a/packages/db/src/schema/document_annotation_comments.ts b/packages/db/src/schema/document_annotation_comments.ts index 05214e9b021..f5273ad1e0c 100644 --- a/packages/db/src/schema/document_annotation_comments.ts +++ b/packages/db/src/schema/document_annotation_comments.ts @@ -1,5 +1,6 @@ import type { IssueCommentAuthorType } from "@paperclipai/shared"; -import { index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { sql } from "drizzle-orm"; +import { check, index, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import { agents } from "./agents.js"; import { companies } from "./companies.js"; import { documentAnnotationThreads } from "./document_annotation_threads.js"; @@ -50,5 +51,9 @@ export const documentAnnotationComments = pgTable( ), issueCommentIdx: index("document_annotation_comments_issue_comment_idx").on(table.issueCommentId), bodySearchIdx: index("document_annotation_comments_body_search_idx").using("gin", table.body.op("gin_trgm_ops")), + exactlyOneOwnerChk: check( + "document_annotation_comments_exactly_one_owner_chk", + sql`num_nonnulls(${table.issueId}, ${table.routineId}) = 1`, + ), }), ); diff --git a/packages/db/src/schema/document_annotation_threads.ts b/packages/db/src/schema/document_annotation_threads.ts index a856c647b41..d09c40d28d4 100644 --- a/packages/db/src/schema/document_annotation_threads.ts +++ b/packages/db/src/schema/document_annotation_threads.ts @@ -4,7 +4,8 @@ import type { DocumentAnnotationAnchorState, DocumentAnnotationThreadStatus, } from "@paperclipai/shared"; -import { index, integer, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; +import { sql } from "drizzle-orm"; +import { check, index, integer, jsonb, pgTable, text, timestamp, uuid } from "drizzle-orm/pg-core"; import { agents } from "./agents.js"; import { companies } from "./companies.js"; import { documentRevisions } from "./document_revisions.js"; @@ -73,5 +74,9 @@ export const documentAnnotationThreads = pgTable( table.companyId, table.anchorState, ), + exactlyOneOwnerChk: check( + "document_annotation_threads_exactly_one_owner_chk", + sql`num_nonnulls(${table.issueId}, ${table.routineId}) = 1`, + ), }), ); diff --git a/server/src/__tests__/routines-service.test.ts b/server/src/__tests__/routines-service.test.ts index 22756ff9727..30d4ba9b1df 100644 --- a/server/src/__tests__/routines-service.test.ts +++ b/server/src/__tests__/routines-service.test.ts @@ -9,6 +9,8 @@ import { companySecrets, companySecretVersions, createDb, + documentRevisions, + documents, executionWorkspaces, heartbeatRuns, instanceSettings, @@ -17,6 +19,7 @@ import { issues, projectWorkspaces, projects, + routineDocuments, routineRuns, routines, routineTriggers, @@ -65,6 +68,9 @@ describeEmbeddedPostgres("routine service live-execution coalescing", () => { await db.delete(routineRuns); await db.delete(routineTriggers); await db.delete(routines); + await db.delete(routineDocuments); + await db.delete(documentRevisions); + await db.delete(documents); await db.delete(companySecretVersions); await db.delete(companySecrets); await db.delete(heartbeatRuns);