Skip to content

Refuse queue deletion when routing rules still reference it - #808

Merged
juanmrad merged 10 commits into
roostorg:mainfrom
reitblatt:queue-delete-behavior
Aug 4, 2026
Merged

Refuse queue deletion when routing rules still reference it#808
juanmrad merged 10 commits into
roostorg:mainfrom
reitblatt:queue-delete-behavior

Conversation

@reitblatt

@reitblatt reitblatt commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Context & Requests for Reviewers

Part of #738 (design #1).

Previously, routing_rules.destination_queue_id and appeals_routing_rules.destination_queue_id had ON DELETE CASCADE FK constraints, so deleting a queue would silently drop any routing rules that pointed to it, breaking routing for the org without any warning.

This PR:

  • Changes both FK constraints to ON DELETE RESTRICT via a new migration (db/src/scripts/api-server-pg/2026.06.17T18.15.16.restrict_routing_rules_queue_fkeys.sql).
  • Catches the resulting FK violation in QueueOperations.deleteManualReviewQueue, queries the blocking rule names from both tables, and throws a QueueHasDependentRoutingRulesError (HTTP 409).
  • Surfaces the error on the client as a modal listing the blocking rule names, each linked to the routing rules dashboard, with a prompt to update or delete those rules first.

Note on inflight jobs: The current implementation calls queue.obliterate({ force: true }) before the DB delete, which removes all waiting, active, and delayed Bull jobs from Redis immediately. More nuanced handling of inflight jobs (e.g. draining rather than obliterating) is tracked in #738 and will be addressed in a follow-up PR.

Tests

  • Two new regression tests in QueueOperations.test.ts — one for routing_rules and one for appeals_routing_rules — verify that deleteManualReviewQueue rejects with QueueHasDependentRoutingRulesError when a rule references the target queue.
  • Manual: verified the error modal renders correctly with linked rule names. Temporarily disabled client-side check, and confirmed server error was surfaced back to user:
Screenshot 2026-06-18 at 09 42 21

Note: I did not test the logic for appeals rules because I couldn't figure out how to set one up. Pointers welcome!

(Optional) Rollout Plan

The migration adds ON DELETE RESTRICT constraints in place of the existing ON DELETE CASCADE ones. No data migration is needed. The constraint change is safe to apply to a live database — it only affects future deletes, not existing rows.

Summary by CodeRabbit

  • New Features

    • Added clearer feedback when deleting a manual review queue is blocked by routing rules.
    • The deletion error dialog identifies linked routing rules and explains how to resolve the issue.
  • Bug Fixes

    • Prevented queues from being deleted while active routing rules still reference them.
    • Improved handling of queue deletion failures with informative fallback messages.
    • Ensured failed queue cleanup does not affect successful database deletion.

@coderabbitai

coderabbitai Bot commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@juanmrad, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 35 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d4693f0a-3f87-435a-8dad-c61a916886ef

📥 Commits

Reviewing files that changed from the base of the PR and between 14e8760 and df9d86b.

📒 Files selected for processing (2)
  • client/src/webpages/dashboard/investigation/ItemInvestigation.tsx
  • server/services/manualReviewToolService/modules/QueueOperations.test.ts
📝 Walkthrough

Walkthrough

Queue deletion now rejects queues referenced by routing rules, returns structured dependent-rule errors, and displays those errors in the dashboard. Database constraints, server handling, service wiring, regression tests, and client modal rendering are updated.

Changes

Queue deletion FK enforcement and error feedback

Layer / File(s) Summary
DB migration and error contract
db/src/scripts/api-server-pg/..., server/services/manualReviewToolService/modules/QueueOperations.ts
Routing-rule foreign keys use restrictive deletes. QueueOperations exposes a typed 409 error with dependent rule names.
FK violation handling in queue deletion
server/services/manualReviewToolService/modules/QueueOperations.ts, server/services/manualReviewToolService/manualReviewToolService.ts
Queue deletion translates routing-rule FK violations, delays queue obliteration until successful database deletion, traces obliteration failures, and receives the tracer dependency.
Regression coverage and routing behavior documentation
server/services/manualReviewToolService/modules/QueueOperations.test.ts, server/services/manualReviewToolService/modules/JobRouting.test.ts
Tests cover deletion blocked by standard and appeals routing rules. The enqueue fallback comment reflects restrictive foreign keys.
Client deletion error modal
client/src/webpages/dashboard/mrt/ManualReviewQueuesDashboard.tsx
The dashboard parses structured mutation errors and renders dependent routing rules or a fallback error message in a dismissible modal.

Investigation type cleanup

Layer / File(s) Summary
Direct investigation item type
client/src/webpages/dashboard/investigation/ItemInvestigation.tsx
The call to getFieldValueForRole passes item.type without a type assertion.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ManualReviewQueuesDashboard
  participant GraphQLMutation
  participant QueueOperations
  participant PostgreSQL
  ManualReviewQueuesDashboard->>GraphQLMutation: request queue deletion
  GraphQLMutation->>QueueOperations: deleteManualReviewQueue
  QueueOperations->>PostgreSQL: delete queue row
  PostgreSQL-->>QueueOperations: FK violation
  QueueOperations->>PostgreSQL: query dependent rule names
  PostgreSQL-->>QueueOperations: rule names
  QueueOperations-->>GraphQLMutation: structured 409 error
  GraphQLMutation-->>ManualReviewQueuesDashboard: error message and rule names
  ManualReviewQueuesDashboard->>ManualReviewQueuesDashboard: render deletion error modal
Loading

Possibly related issues

Possibly related PRs

  • roostorg/coop#479 — Both PRs modify manual-review queue management in ManualReviewQueuesDashboard and QueueOperations.
  • roostorg/coop#644 — Both PRs modify shared manual-review queue-management code.
  • roostorg/coop#909 — Both PRs modify QueueOperations and its tests.

Suggested reviewers: julietshen, cassidyjames, vinaysrao1

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the primary change: preventing queue deletion when routing rules reference the queue.
Description check ✅ Passed The description covers the context, implementation, tests, known limitation, rollout plan, and user-facing behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
🛠️ Fix failing CI checks 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

Comment thread client/src/webpages/dashboard/mrt/ManualReviewQueuesDashboard.tsx Outdated
@reitblatt
reitblatt marked this pull request as ready for review June 19, 2026 23:09

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/services/manualReviewToolService/modules/QueueOperations.ts (1)

393-445: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Deletion failure now still destroys Redis jobs before returning 409.

With the new FK-reject path (Line 419 onward), the method can fail after queue.obliterate({ force: true }) has already run (Line 391), so users get “could not delete queue” but pending jobs are already gone. Please move obliteration after a successful DB delete path (numDeletedRows === 1n).

Suggested fix
-    const queue = await this.getOrCreateBullQueue({ orgId, queueId });
-
-    await queue.obliterate({ force: true });
+    const queue = await this.getOrCreateBullQueue({ orgId, queueId });

     let numDeletedRows: bigint;
     try {
       numDeletedRows = await this.transactionWithRetry(async (transaction) => {
@@
       throw e;
     }

-    return numDeletedRows === 1n;
+    if (numDeletedRows !== 1n) {
+      return false;
+    }
+
+    await queue.obliterate({ force: true });
+    return true;
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@server/services/manualReviewToolService/modules/QueueOperations.ts` around
lines 393 - 445, The queue.obliterate method call is currently executing before
the database transaction that deletes the queue, causing Redis jobs to be
destroyed even when the database deletion fails due to foreign key constraints.
Move the queue.obliterate call to execute only after the transactionWithRetry
completes successfully and numDeletedRows is greater than 0, ensuring that Redis
jobs are only deleted if the database deletion succeeds and does not trigger the
isForeignKeyViolationError path that throws an error response.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@client/src/webpages/dashboard/mrt/ManualReviewQueuesDashboard.tsx`:
- Around line 227-230: The code in the JSON.parse block for ruleNames does not
validate that the parsed result is actually an array before assignment, which
can cause a runtime error when the render code at line 487 attempts to call .map
on ruleNames. Add a runtime validation check after JSON.parse(rawDetail) to
ensure the parsed value is an array before assigning it to ruleNames. If the
validation fails, keep ruleNames as an empty array or undefined to prevent
errors in the downstream .map call.

In `@server/services/manualReviewToolService/modules/QueueOperations.ts`:
- Around line 419-445: The current implementation assumes all foreign key
violation errors are due to routing rule dependencies, but this could be
incorrect if new RESTRICT foreign keys are added to the manual_review_queues
table in the future. Add a constraint name check within the
isForeignKeyViolationError(e) block to verify that e.constraint matches one of
the two expected routing rule constraint names
(routing_rules_destination_queue_id_fkey or
appeals_routing_rules_destination_queue_id_fkey) before executing the query
logic and throwing makeQueueHasDependentRoutingRulesError. If the constraint
name does not match either of these, skip the query block and simply throw the
original error instead.

---

Outside diff comments:
In `@server/services/manualReviewToolService/modules/QueueOperations.ts`:
- Around line 393-445: The queue.obliterate method call is currently executing
before the database transaction that deletes the queue, causing Redis jobs to be
destroyed even when the database deletion fails due to foreign key constraints.
Move the queue.obliterate call to execute only after the transactionWithRetry
completes successfully and numDeletedRows is greater than 0, ensuring that Redis
jobs are only deleted if the database deletion succeeds and does not trigger the
isForeignKeyViolationError path that throws an error response.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 43f18fd4-9cb8-4675-8342-cbfcc0df4d9e

📥 Commits

Reviewing files that changed from the base of the PR and between 10f26ff and ac6f7be.

📒 Files selected for processing (5)
  • client/src/webpages/dashboard/mrt/ManualReviewQueuesDashboard.tsx
  • db/src/scripts/api-server-pg/2026.06.17T18.15.16.restrict_routing_rules_queue_fkeys.sql
  • server/services/manualReviewToolService/modules/JobRouting.test.ts
  • server/services/manualReviewToolService/modules/QueueOperations.test.ts
  • server/services/manualReviewToolService/modules/QueueOperations.ts

Comment thread client/src/webpages/dashboard/mrt/ManualReviewQueuesDashboard.tsx
Comment thread server/services/manualReviewToolService/modules/QueueOperations.ts Outdated

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Prevents manual review queue deletion from silently breaking routing by changing DB foreign keys from ON DELETE CASCADE to ON DELETE RESTRICT, then surfacing a specific conflict error to users listing the routing rules that block deletion.

Changes:

  • Add a Postgres migration to restrict deletion of queues referenced by routing_rules / appeals_routing_rules.
  • Server: catch FK violations during queue deletion, look up blocking rule names, and throw QueueHasDependentRoutingRulesError (409) with rule names in detail.
  • Client + tests: show a modal listing blocking rule names and add regression tests covering both routing-rule tables.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
server/services/manualReviewToolService/modules/QueueOperations.ts Catch FK violations on queue delete and raise a 409 error containing blocking routing rule names; update test-only delete helper for RESTRICT behavior.
server/services/manualReviewToolService/modules/QueueOperations.test.ts Add regression tests ensuring queue deletion rejects when routing rules reference the queue.
server/services/manualReviewToolService/modules/JobRouting.test.ts Update test commentary to reflect RESTRICT FK behavior and the test-only deletion helper’s semantics.
db/src/scripts/api-server-pg/2026.06.17T18.15.16.restrict_routing_rules_queue_fkeys.sql Change the two destination-queue FKs from CASCADE to RESTRICT.
client/src/webpages/dashboard/mrt/ManualReviewQueuesDashboard.tsx Surface server deletion errors via a modal and link users to the routing rules dashboard.

Comment thread server/services/manualReviewToolService/modules/QueueOperations.ts Outdated
Comment thread server/services/manualReviewToolService/modules/QueueOperations.ts
Comment thread server/services/manualReviewToolService/modules/QueueOperations.ts
Comment thread client/src/webpages/dashboard/mrt/ManualReviewQueuesDashboard.tsx Outdated
reitblatt and others added 5 commits July 13, 2026 19:19
Fixes issue roostorg#738 (design #1).

Changes the FK constraints on routing_rules.destination_queue_id and
appeals_routing_rules.destination_queue_id from ON DELETE CASCADE to
ON DELETE RESTRICT. The service now catches the resulting FK violation
and throws QueueHasDependentRoutingRulesError, naming the blocking
rules, instead of silently cascade-deleting them and breaking routing.

The client's previously silent onError handler is replaced with a modal
that surfaces the server's error message so the user knows which rules
to update before retrying.

Regression tests cover both routing_rules and appeals_routing_rules.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
deleteManualReviewQueueForTestsDO_NOT_USE was written when routing_rules
had CASCADE FKs on destination_queue_id. Now that the FK is RESTRICT,
the helper must explicitly delete referencing routing rules and appeals
routing rules before removing the queue, or the DB rejects the delete.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Move queue.obliterate() after DB transaction so Redis jobs aren't
  destroyed if deletion is blocked by FK constraints
- Gate FK error mapping on specific constraint names to avoid swallowing
  unrelated 23503 errors from future RESTRICT FKs
- Add org_id filter to routing rule deletes in test helper
- Validate JSON.parse result is string[] before assigning to ruleNames
- Use index as React key to avoid collisions on duplicate rule names

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@reitblatt
reitblatt force-pushed the queue-delete-behavior branch from 26b6c85 to 32653b3 Compare July 13, 2026 21:22

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@server/services/manualReviewToolService/modules/QueueOperations.ts`:
- Around line 508-512: Update the deleteManualReviewQueue flow around
queue.obliterate({ force: true }) to catch and explicitly log or otherwise
surface obliteration failures after the database delete commits, referencing the
existing recovery path recover-mrt-queue.ts. Preserve the committed deletion
result while ensuring retries cannot silently treat the missing row as a
successful no-op and hide the orphaned Redis queue state.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 56247178-6a9b-4db5-b410-fdd9d360046f

📥 Commits

Reviewing files that changed from the base of the PR and between ac6f7be and 32653b3.

📒 Files selected for processing (5)
  • client/src/webpages/dashboard/mrt/ManualReviewQueuesDashboard.tsx
  • db/src/scripts/api-server-pg/2026.06.17T18.15.16.restrict_routing_rules_queue_fkeys.sql
  • server/services/manualReviewToolService/modules/JobRouting.test.ts
  • server/services/manualReviewToolService/modules/QueueOperations.test.ts
  • server/services/manualReviewToolService/modules/QueueOperations.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • db/src/scripts/api-server-pg/2026.06.17T18.15.16.restrict_routing_rules_queue_fkeys.sql
  • server/services/manualReviewToolService/modules/JobRouting.test.ts
  • client/src/webpages/dashboard/mrt/ManualReviewQueuesDashboard.tsx
  • server/services/manualReviewToolService/modules/QueueOperations.test.ts

Comment thread server/services/manualReviewToolService/modules/QueueOperations.ts
reitblatt and others added 2 commits July 13, 2026 22:02
Rename the routing-rules migration to a fresh timestamp since main
gained a later migration (add_sepia) during the rebase, and dedupe
the two new QueueOperations.test.ts cases behind a shared helper to
get the file back under the 500-line lint limit.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…mmit

If queue.obliterate() throws after the DB delete has already committed,
a retry would see numDeletedRows === 0n, skip obliterate() entirely, and
silently leave orphaned Bull/Redis data behind. Wrap it in try/catch and
surface the failure to the active tracing span, matching the best-effort
cleanup pattern used elsewhere (e.g. UserApi.logout).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@juanmrad juanmrad left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

small nit. please fix merge conflicts. Otherwise LGTM

Comment on lines +477 to +481
((e as { constraint?: string }).constraint ===
'routing_rules_destination_queue_id_fkey' ||
(e as { constraint?: string }).constraint ===
'appeals_routing_rules_destination_queue_id_fkey')
) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: The cast (e as { constraint?: string }).constraint is repeated twice. Consider a single narrow:

const constraint = (e as { constraint?: string }).constraint;
if (
  isForeignKeyViolationError(e) &&
  (constraint === 'routing_rules_destination_queue_id_fkey' ||
   constraint === 'appeals_routing_rules_destination_queue_id_fkey')
)

Reads better and avoids the duplication.

@julietshen

Copy link
Copy Markdown
Member

Hi @reitblatt! Setting up an Appeals queue requires going into the settings (new settings page released in 1.0 a month ago) and say "enable appeals". When you create a queue, you should then see a checkbox that says "This is an appeals queue" and you can populate it with jobs that correspond to those. I'll double check how to do that programmatically...

# Conflicts:
#	server/services/manualReviewToolService/modules/QueueOperations.test.ts
@juanmrad
juanmrad merged commit 3b706ec into roostorg:main Aug 4, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants