fix(decisions): allow board orphan cleanup

Let company-scoped board operators remove stale queue sidecars after their attention source disappears while preserving source-read 404s for agents.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
DottaandPaperclip committed 2026-08-01 18:24:04 +00:00
1 parent d32c69a23b
commit 3310823de0
2 files changed
+21 -2

No files matched your search

@@ -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 () => {
+13 -1
View File
@@ -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(