diff --git a/server/src/__tests__/decision-queues-routes.test.ts b/server/src/__tests__/decision-queues-routes.test.ts index 1c056b771c3..bca0b210f6e 100644 --- a/server/src/__tests__/decision-queues-routes.test.ts +++ b/server/src/__tests__/decision-queues-routes.test.ts @@ -224,6 +224,9 @@ describeEmbeddedPostgres("decision queue routes", () => { "issue_thread_interaction", "review", ]); + await request(app(agentActor(companyId, agentId))) + .delete(`/api/companies/${companyId}/decision-queues/triage/items/approval/${approvalId}`) + .expect(404); await request(app(board)) .delete(`/api/companies/${companyId}/decision-queues/triage/items/review/${issueId}`) @@ -236,7 +239,11 @@ describeEmbeddedPostgres("decision queue routes", () => { await db.delete(approvals).where(eq(approvals.id, approvalId)); await request(app(board)) .delete(`/api/companies/${companyId}/decision-queues/triage/items/approval/${approvalId}`) - .expect(404); + .expect(200); + expect(await db.select().from(decisionQueueItems).where(and( + eq(decisionQueueItems.companyId, companyId), + eq(decisionQueueItems.sourceKind, "approval"), + ))).toHaveLength(0); }); it("materializes data-backed starter queues from plan, question, and pull-request signals", async () => { diff --git a/server/src/services/decision-queues.ts b/server/src/services/decision-queues.ts index 47398e12128..a0dfd24586b 100644 --- a/server/src/services/decision-queues.ts +++ b/server/src/services/decision-queues.ts @@ -539,7 +539,19 @@ export function decisionQueueService(db: Db) { }) => { const queue = await getQueue(input.companyId, input.key); if (!queue) throw notFound("Decision queue not found"); - await requireSourceRead(db, input.authActor, input.companyId, input.sourceKind, input.sourceId); + const sourceReadable = await canReadDecisionSource( + db, + input.authActor, + input.companyId, + input.sourceKind, + input.sourceId, + ); + // Board operators may clean up an existing sidecar after its source has + // disappeared. Agents still need source-level read authority so removal + // cannot be used to probe hidden membership. + if (!sourceReadable && input.authActor.type !== "board") { + throw notFound("Attention source not found"); + } return db.transaction(async (tx) => { const txDb = tx as unknown as Db; const removed = await txDb.delete(decisionQueueItems).where(and(