Skip to content

fix(background-check): persist exemption reason + justification on member#2863

Merged
tofikwest merged 2 commits into
mainfrom
tofik/bg-check-exempt-reason-persist
May 15, 2026
Merged

fix(background-check): persist exemption reason + justification on member#2863
tofikwest merged 2 commits into
mainfrom
tofik/bg-check-exempt-reason-persist

Conversation

@github-actions
Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot commented May 15, 2026

Summary

Customer reported "Mark as exempt" toast: "Failed to confirm exemption". Every customer is hard-blocked on the V1 exempt flow.

Root cause

apps/api/src/main.ts:137-139 sets forbidNonWhitelisted: true on the global ValidationPipe. The V1 frontend (already on main, PR #2839) sends:

```ts
PATCH /v1/people/:id body: {
backgroundCheckExempt: true,
backgroundCheckExemptReason: 'other',
backgroundCheckExemptJustification: 'Founder',
}
```

UpdatePeopleDto only declares backgroundCheckExempt. The two new fields aren't whitelisted → NestJS rejects with 400 Bad Request before the service runs → client toast.

I flagged this exact follow-up in PR #2839's description but called it "backend ignores them". I was wrong — forbidNonWhitelisted actively rejects, not silently drops. Customer-blocking.

What this PR does

  1. Prisma schema — adds two nullable columns to Member:
    • backgroundCheckExemptReason (varchar)
    • backgroundCheckExemptJustification (text)
  2. Migration20260515221108_add_background_check_exemption_reason_justification generated via bunx prisma migrate dev --name … (per CLAUDE.md, not hand-written SQL). Additive ALTER TABLE — no defaults required, no rewrite, no downtime.
  3. DTO — whitelists both as optional strings on UpdatePeopleDto with length caps (100 / 2000 chars).
  4. Service (MemberQueries.updateMember) — persists both fields on member.update. When backgroundCheckExempt === false, clears both to null so a future re-exemption starts clean. The AuditLogInterceptor already captures the request body for @RequirePermission endpoints, so historical reason/justification live in the audit log from the original exempt-true request.
  5. Tests — new member-queries.spec.ts with 4 cases:
    • persist on exempt=true
    • clear on exempt=false
    • clear overrides incoming stale reason on exempt=false (defensive)
    • patch without exempt leaves both fields untouched

Why "clear on un-exempt"

The audit log retains the original reason/justification from when the member was exempted (request body captured by AuditLogInterceptor). The columns on the Member row are current-state fields, not history. Un-exempting → no current reason → null. Re-exempting later means the admin must supply fresh reason + justification.

No frontend change

The V1 frontend already sends these fields; it just needs the API to stop rejecting them. Deploy the API and the customer is unblocked.

Verification

  • ✅ `npx jest src/people/utils/member-queries.spec.ts` — 4 of 4 tests pass.
  • ✅ `npx jest src/people` — 31 tests pass across 3 suites. (`people.service.spec.ts` is a pre-existing failed suite — verified by stashing and re-running on baseline.)
  • ✅ Typecheck — no new errors from this PR. The api typecheck has pre-existing unrelated failures in `integration-platform`, `risks`, `timelines`, `training` specs that are present on baseline.
  • ✅ Migration applied cleanly to the isolated worktree DB (`compdev_bg_check_exempt_persist`).

Test plan

  • Run `bunx prisma migrate deploy` against a staging DB and confirm the additive migration applies cleanly.
  • On staging: open any employee's Background Check tab → Mark as exempt → pick a reason → enter justification → Confirm exemption. Toast should be success, page should re-render to the exempt notice.
  • Query the member row directly to confirm all three columns persisted.
  • Toggle exempt off via the inverse toggle → re-query → confirm Reason and Justification are both NULL.
  • Check the audit log entry for the PATCH — confirm the original exempt-true body (with reason + justification text) was captured.

🤖 Generated with Claude Code

…mber

"Confirm exemption" was failing for every customer with a "Failed to
confirm exemption" toast. Root cause:

  apps/api/src/main.ts sets `forbidNonWhitelisted: true` on the global
  ValidationPipe. The V1 frontend sends `backgroundCheckExempt` together
  with `backgroundCheckExemptReason` + `backgroundCheckExemptJustification`,
  but the latter two were never declared on UpdatePeopleDto — so the
  PATCH /v1/people/:id request was rejected with 400 before the service
  ever ran.

This fix:

1. Adds nullable columns `backgroundCheckExemptReason` (varchar) and
   `backgroundCheckExemptJustification` (text) to the Member model
   (named prisma migrate dev migration — not hand-written SQL).
2. Whitelists both fields on UpdatePeopleDto as optional strings with
   sensible length caps (100 / 2000 chars).
3. Persists both fields on member.update; clears them to null when
   backgroundCheckExempt is set to false so a future re-exemption starts
   from a clean state. Audit log retains the prior values from the
   original exempt-true request via AuditLogInterceptor.
4. Adds 4 unit tests in member-queries.spec.ts covering: persist on
   exempt=true, clear on exempt=false, clear overrides incoming stale
   reason on exempt=false, untouched when patch omits exempt.

Pre-existing typecheck failures in unrelated specs and a pre-existing
people.service.spec runtime error were verified to also occur on the
baseline (origin/main) before this change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 15, 2026

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

Project Deployment Actions Updated (UTC)
app Ready Ready Preview, Comment May 15, 2026 10:32pm
comp-framework-editor Ready Ready Preview, Comment May 15, 2026 10:32pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
portal Skipped Skipped May 15, 2026 10:32pm

Request Review

@tofikwest tofikwest changed the title [dev] [tofikwest] tofik/bg-check-exempt-reason-persist fix(background-check): persist exemption reason + justification on member May 15, 2026
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.

No issues found across 5 files

Confidence score: 5/5

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

Re-trigger cubic

The Prisma schema change in this branch added two non-optional (nullable)
columns to Member — backgroundCheckExemptReason + backgroundCheckExemptJustification.
The strict-typed test mock createMockMember returns Member, so the mock
had to include both keys or the spread of overrides would surface them
as undefined and fail the Vercel build:

  Type 'undefined' is not assignable to type 'string | null'.

Initializing both to null in the default mock matches the DB default.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel vercel Bot temporarily deployed to Preview – portal May 15, 2026 22:28 Inactive
@tofikwest tofikwest merged commit 2c2da31 into main May 15, 2026
10 checks passed
@tofikwest tofikwest deleted the tofik/bg-check-exempt-reason-persist branch May 15, 2026 22:32
@claudfuen
Copy link
Copy Markdown
Contributor

🎉 This PR is included in version 3.56.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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants