### Pull Request Title: fix(retention): enforce at-most-one active legal hold atomically#544
Open
SHEROSE0 wants to merge 1 commit into
Open
Conversation
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
#closes #364
Description
This PR resolves a critical race condition in the legal-hold creation flow where concurrent requests could bypass application-level existence checks, resulting in duplicate active holds. By shifting enforcement to the database layer, we ensure that legal holds—which gate destructive purges—remain consistent even under heavy concurrent load.
Changes
unique_active_legal_hold_per_invoiceon thelegal_holdstable. This ensures the database engine atomically rejects duplicate active records whereactive = trueandexpires_at > NOW().src/routes/retention.jsto catchSequelizeUniqueConstraintErrorand return a standard409 Conflictresponse, providing a robust API contract.Testing & Validation
Integration Tests: Added test cases in
tests/retention.integration.test.jsto simulate simultaneous duplicate requests; verified that only one succeeds while others receive a409.Edge Case Coverage:
Expired holds (verified: allows new hold creation).
Cross-tenant integrity (verified: constrained by
invoice_id).Release/Re-create cycles.
Linting:
npm run lintpassed.Security Notes
The previous implementation relied on application-side state validation, creating a window where concurrent requests could pass the check before the first request committed. By utilizing a PostgreSQL partial index, we ensure that the state is locked at the database level during the insert transaction. This guarantees that no two transactions can ever result in two active holds for the same invoice, preventing potential data corruption in the
retentionPurgejob.Acceptance Criteria Checklist
tests/retention.integration.test.jscoverage requirements met.docs/retention.mdreconciled.#closes