Skip to content

feat(background-checks): admin cancel/delete/retry (CS-475)#2993

Merged
Marfuen merged 17 commits into
mainfrom
cs-475
Jun 2, 2026
Merged

feat(background-checks): admin cancel/delete/retry (CS-475)#2993
Marfuen merged 17 commits into
mainfrom
cs-475

Conversation

@Marfuen
Copy link
Copy Markdown
Contributor

@Marfuen Marfuen commented Jun 2, 2026

CS-475 — Admin cancel / delete / retry for background checks

Relates to: https://linear.app/compai/issue/CS-475

Problem

Customers get blocked when a background check errors. BackgroundCheckRequest has @@unique([organizationId, memberId]) (one row per member), so requestForMember() silently returns the stuck/errored row — there's currently no way to clear, cancel, or re-run it.

What this adds

Three admin actions on a member's background check, surfaced in the employee Background Check tab and gated by RBAC:

  • Retry (member:update) — resubmit a failed/cancelled check. Free (reuses the original payment, no new charge), resets the record in place, keeps webhook history. Varies the Identity idempotency key by attempt (comp-background-check:{memberId}:{attempt}) so a genuinely fresh vendor check is created instead of the deduped old one.
  • Cancel (member:update) — stop an invited/in_progress/in_review check Comp-side (writes the existing-but-unused cancelled status). handleWebhook now ignores events for cancelled rows so a late Identity webhook can't resurrect it.
  • Delete (member:delete) — hard-delete the record (cascades webhook events), freeing the unique constraint so a fresh request can be made.

A state-machine guard rejects invalid transitions (e.g. retrying a completed check). Auditors are excluded automatically (no update/delete).

Changes

  • DB: rerunCount column + migration.
  • API: POST /v1/people/:id/background-check/retry, …/cancel, DELETE /v1/people/:id/background-check; new cancelForMember/retryForMember/deleteForMember service methods; attempt-aware Identity client; webhook terminal-guard for cancelled rows.
  • Frontend: BackgroundCheckAdminActions component (status + permission gated, two-step delete confirm) wired into the employee Background Check tab.

Testing

  • API: jest src/background-checks41/41 passing (8 suites), incl. new controller + service tests.
  • Frontend: new BackgroundCheckAdminActions 5/5; EmployeeBackgroundCheck 17/17.
  • 1 pre-existing vitest failure in an exemption test (Radix Select / jsdom) — exists verbatim on main, untouched here.

Decisions / scope

  • Retry billing: free (reuses original payment).
  • Cancel is Comp-side only — the Identity (Convex) vendor has no confirmed cancel/delete endpoint; confirming + wiring server-side cancellation is a follow-up.
  • Data: cancel/retry reset in place & keep history; delete is the explicit hard-delete.

Notes for reviewers

  • Repo-wide turbo typecheck is not fully green due to pre-existing issues unrelated to this PR (@trycompai/billing dist/ not built in fresh worktrees; errors in risks/training/timelines/offboarding-checklist/etc.). No typecheck errors in any file changed here.
  • Design spec + implementation plan live under docs/superpowers/ (gitignored / local only).

Draft until manual click-through verification of retry/cancel/delete in the running app.

🤖 Generated with Claude Code


Summary by cubic

Adds admin actions to retry, cancel, or delete a member’s background check so stuck requests can be cleared and resubmitted (CS-475). Ensures fresh vendor checks on both new requests and retries via record-scoped idempotency, and blocks late webhooks from changing cancelled checks.

  • New Features

    • Retry (free): resubmits failed/cancelled, increments rerunCount, uses Identity Idempotency-Key comp-background-check:{backgroundCheckRequestId} (create) and comp-background-check:{backgroundCheckRequestId}:{attempt} (retries), resets statuses/report in place, and on retry failure restores the prior status to preserve the cancelled terminal guard.
    • Cancel: allows invited/in_progress/in_reviewcancelled; webhook records the event but ignores status changes for cancelled rows.
    • Delete: hard-deletes to free the org+member unique slot for a fresh request.
    • API: POST /v1/people/:id/background-check/retry, POST /v1/people/:id/background-check/cancel, DELETE /v1/people/:id/background-check; retry passes requester email; state guards block invalid transitions.
    • UI: BackgroundCheckAdminActions in the employee Background Check tab, permission-gated with confirm-before-delete; clears a pending delete confirm on Retry/Cancel.
  • Migration

    • Add rerunCount to background_check_requests (default 0). Run Prisma migrations.

Written for commit a149fa4. Summary will update on new commits.

Review in cubic

Marfuen and others added 13 commits June 2, 2026 10:43
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Three pre-existing direct calls to `BackgroundCheckIdentityClient.createBackgroundCheck()`
in the spec file were missing the newly required `attempt` field, causing TS2345 type
errors after Task 2 made `attempt: number` required in the client signature.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…o bring service under 300 lines; add missing not-found test

- Extract `cancelForMember` and `retryForMember` to `background-check-retry.ts`
  following the existing `background-check-report-snapshot.ts` helper pattern,
  bringing `background-checks.service.ts` from 376 to 293 lines
- Add missing `throws when no check exists` test to the `retryForMember` suite,
  matching the parallel coverage in `cancelForMember` (NotFoundException at
  service.ts:304 was previously untested)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Remove inner `{backgroundCheck && ...}` wrapper inside the
  `if (backgroundCheck)` branch — the inner condition was always truthy.
- Fix the `onChange` callback type mismatch (void vs Promise<void>).
- Add two tests that pass a non-null `initialBackgroundCheck` and assert
  that Retry/Cancel buttons from `BackgroundCheckAdminActions` render.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@linear
Copy link
Copy Markdown

linear Bot commented Jun 2, 2026

CS-475

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 2, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment Jun 2, 2026 8:05pm
comp-framework-editor Ready Ready Preview, Comment Jun 2, 2026 8:05pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped Jun 2, 2026 8:05pm

Request Review

@Marfuen
Copy link
Copy Markdown
Contributor Author

Marfuen commented Jun 2, 2026

@cubic-dev-ai ultrareview this

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented Jun 2, 2026

@cubic-dev-ai ultrareview this

@Marfuen Starting ultrareview - a deeper analysis than a regular review. I'll post findings when complete.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

Ultrareview completed in 11m 18s

4 issues found across 20 files

Confidence score: 3/5

  • There is some meaningful regression risk here: apps/api/src/background-checks/background-checks.service.ts forcing initial requests to attempt: 0 can reuse the first idempotency key after delete/re-request, which may block creating a truly fresh vendor check in CS-475 resubmissions.
  • The most severe backend behavior concern is in apps/api/src/background-checks/background-check-retry.ts, where retry failure can overwrite cancelled checks to failed, potentially allowing late webhook status resurrection on records that should stay cancelled.
  • apps/api/src/background-checks/background-check-retry.ts also appears to allow delete across any background-check status instead of limiting to error-recovery states, and apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/BackgroundCheckAdminActions.tsx can leave delete pre-confirmed after Retry/Cancel, creating avoidable user-action risk.
  • Pay close attention to apps/api/src/background-checks/background-checks.service.ts, apps/api/src/background-checks/background-check-retry.ts, and apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/BackgroundCheckAdminActions.tsx - idempotency, cancellation integrity, delete guardrails, and UI confirmation state need validation.

Linked issue analysis

Linked issue: CS-475: [QOL] - Create admin options to allow to cancel/delete background checks

Status Acceptance criteria Notes
Add admin actions (Retry / Cancel / Delete) surfaced in the employee Background Check tab and gated by RBAC Frontend component added and wired into the employee Background Check UI; it checks permissions before rendering and shows the correct buttons per status.
Expose API endpoints to retry, cancel, and delete a member background check Controller routes and HTTP methods were added with permission decorators and appropriate summaries.
Retry resubmits a failed/cancelled check free of charge, varies idempotency key per attempt, and increments rerunCount Service logic creates a new Identity check with an attempt-based idempotency key, updates the DB with the new identity id, and increments rerunCount; identity client constructs the idempotency key based on attempt; migration and schema add rerunCount.
Cancel marks in-flight checks cancelled and prevents late vendor webhooks from resurrecting the record Cancel service updates status to cancelled; webhook handler early-returns when record.status === cancelled so later webhooks don't change state; tests assert no update occurs for cancelled rows.
Delete hard-deletes the background check record (cascading webhook events) and frees the organization+member unique slot Delete service deletes the DB record; service/controller wrappers added and tests confirm deletion behavior and errors when missing.
Enforce state-machine guards for allowed transitions (e.g., can't retry completed, can't cancel completed) assertTransitionAllowed enforces allowed statuses for cancel/retry and service tests verify invalid transitions are rejected.
RBAC: actions require appropriate permissions and auditors cannot update/delete Controller methods are decorated with RequirePermission; frontend checks usePermissions to gate UI; tests mock permissions and assert rendered buttons and controller delegation.

Reply with feedback, questions, or to request a fix.

Fix all with cubic | Re-trigger cubic

Comment thread apps/api/src/background-checks/background-checks.service.ts Outdated
Comment thread apps/api/src/background-checks/background-check-retry.ts Outdated
Comment thread apps/api/src/background-checks/background-check-retry.ts
- Key Identity idempotency on the record id (not memberId) so a delete +
  re-request creates a fresh vendor check instead of colliding (P1)
- Retry failure restores the prior status instead of forcing 'failed',
  preserving the cancelled webhook terminal-guard (P1)
- Clear the pending delete confirmation when Retry/Cancel is clicked (P2)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@Marfuen
Copy link
Copy Markdown
Contributor Author

Marfuen commented Jun 2, 2026

@cubic-dev-ai ultrareview this again

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented Jun 2, 2026

@cubic-dev-ai ultrareview this again

@Marfuen Ultrareview monthly budget exhausted (12/12 used). Budget resets at the start of next month.

@Marfuen
Copy link
Copy Markdown
Contributor Author

Marfuen commented Jun 2, 2026

@cubic-dev-ai review this

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented Jun 2, 2026

@cubic-dev-ai review this

@Marfuen I have started the AI code review. It will take a few minutes to complete.

@Marfuen
Copy link
Copy Markdown
Contributor Author

Marfuen commented Jun 2, 2026

@cubic-dev-ai review this

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai Bot commented Jun 2, 2026

@cubic-dev-ai review this

@Marfuen I have started the AI code review. It will take a few minutes to complete.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

cubic analysis

No issues found across 20 files

Confidence score: 5/5

  • Automated review surfaced no issues in the provided summaries.
  • No files require special attention.

Linked issue analysis

Linked issue: CS-475: [QOL] - Create admin options to allow to cancel/delete background checks

Status Acceptance criteria Notes
Add admin Retry action that resubmits a failed/cancelled background check (free), increments a rerun/attempt counter, and uses a per-attempt idempotency key so the vendor creates a fresh check. retryForMember creates a new Identity request with idempotencyKey `comp-background-check:{existing.id}:{attempt}`, increments rerunCount, and the tests assert no charge and the idempotency key usage.
Add admin Cancel action to mark an in-flight check (invited / in_progress / in_review) cancelled and ensure late vendor webhooks cannot resurrect it. cancelForMember enforces allowed statuses and updates status to cancelled; webhook handler short-circuits when record.status === cancelled; tests verify cancelled record is not updated by webhooks.
Add admin Delete action to hard-delete the background check record (cascading webhook events) and free the organization+member unique slot for a fresh request. deleteForMember performs a hard delete; controller exposes DELETE endpoint; tests verify delete called and that it frees the slot (delete invoked).
Expose API endpoints for admin actions: POST /v1/people/:id/background-check/retry, POST .../cancel, and DELETE /v1/people/:id/background-check (RBAC protected). Controller adds routes with RequirePermission('member','update'/'delete'); controller tests exercise delegation to service methods.
Enforce state-machine guards so invalid transitions are rejected (e.g., cannot retry a completed or in_progress check when not allowed). assertTransitionAllowed enumerates allowed states for cancel/retry and is used before state changes; tests assert rejected transitions (e.g., retrying in_progress, cancelling completed).
Frontend: Add BackgroundCheckAdminActions component in the employee Background Check tab, permission-gated, with two-step delete confirmation and behavior to clear pending delete when Retry/Cancel is clicked. New component implements Retry/Cancel/Delete flows, uses permission checks, supports two-step delete and clears confirm on retry; integrated into EmployeeBackgroundCheck component; frontend tests verify UI and behaviors.
DB migration: add rerunCount column to background_check_requests defaulting to 0. Prisma schema updated and migration SQL added to add rerunCount column with default 0.
Retries are free (do not create a new charge / reuse original payment) as described. retry path creates an Identity check and updates the record without invoking paymentService.charge; tests confirm charge is not called during retry.
⚠️ Auditors are excluded automatically (no update/delete) per PR description. Controller routes are permission-gated and frontend checks permissions, but there is no explicit change in this PR showing a named 'auditor' exclusion rule — this likely relies on existing permission system configuration outside these diffs.

Re-trigger cubic

@vercel vercel Bot temporarily deployed to Preview – portal June 2, 2026 20:01 Inactive
@Marfuen Marfuen merged commit 51c3b3d into main Jun 2, 2026
7 of 9 checks passed
@Marfuen Marfuen deleted the cs-475 branch June 2, 2026 20:02
claudfuen pushed a commit that referenced this pull request Jun 2, 2026
# [3.67.0](v3.66.2...v3.67.0) (2026-06-02)

### Bug Fixes

* **api:** guarantee non-null SoA justification on YES defaults ([7f564df](7f564df))
* **api:** include a default justification on SoA ([732f262](732f262))
* **app:** able to edit the justification ([2939178](2939178))
* **app:** fix empty justification issue on SoA ([43fa889](43fa889))
* **app:** keep SoA justification dialog open when save fails ([a5621cb](a5621cb))
* **app:** return a generic default when no family match on SoA ([6682be1](6682be1))
* **app:** show default justification at all times on SoA ([13f468a](13f468a))
* **background-checks:** move admin actions into the status card footer ([#2998](#2998)) ([dcd4b4d](dcd4b4d))
* **people:** stop tracking background checks for auditor-only members (CS-416) ([#2995](#2995)) ([4e7d57d](4e7d57d))

### Features

* **admin:** add Finding Templates management to admin panel ([c381397](c381397))
* **background-checks:** admin cancel/delete/retry (CS-475) ([#2993](#2993)) ([51c3b3d](51c3b3d))
* **background-checks:** hourly reconciliation for stuck checks (CS-473) ([#2996](#2996)) ([3d6e609](3d6e609))
@claudfuen
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.67.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants