Skip to content

Acceptance criteria

Part of the temporary reversible deletion plan. Each criterion is testable and names how it is verified. Releases are those in rollout §3; slices are in slices. The integrated plan's criteria each one satisfies are named where they overlap.

Verification methods. U = unit test. I = integration test on a real Mongo replica set. A = architecture test. E = E2E spec on the hermetic local stack. S = soak drill (testing §5). B = benchmark on Bramble. St = staging check. R = review of the diff or docs.

R0: guards, dark (S0 to S3d)

ID Criterion Method
DL-R0-01 With no deletion data in the database, every existing test suite passes unchanged, and an unmarked Study and an undeleted Project serialise byte-identically to before U, I
DL-R0-02 Every Study write path refuses a deletion-marked Study with the typed study-deleted refusal (HTTP 410; hub reason StudyDeleted) and writes nothing I (extended honouring tests)
DL-R0-03 The build fails if a Study write omits the composite predicate, if an id load of a Study neither compares ProjectId nor is allow-listed with a reason, or if a new member deletes from pmStudy, pmSystematicSearch or pmProject outside the allow-list A
DL-R0-04 For a project in the deleted state, every Project* and Stage* route and hub call answers 410 to a member and 404 to anyone else, in all three authority modes; recovery routes answer only to the owner (within the window) and application administrators U, I
DL-R0-05 A deleted project appears in no list: My projects, public projects, the administrator list, invitations and join requests I
DL-R0-06 Scheduled and queued work skips a deleted project (statistics jobs including scheduled maintenance, daily observations, exports, delegated work); reservation timeouts and Bulk PDF clean-up still run U, I
DL-R0-07 The deletion operation has monotonic states with no erasing state, exact-token leases with takeover after expiry, at most one sweeping operation per project, and a restore-window end that never moves U, I
DL-R0-08 Ledger records contain no names, titles, reasons or other free text U (schema)
DL-R0-09 ADR-024 is merged and records the decisions in decisions and risks; the uncommitted ADR-014 draft is marked superseded R

R1: in-flight work (S3b, S3c, S3d)

ID Criterion Method
DL-R1W-01 A deletion is accepted (202, Requested) whatever is running; it never fails because something is still running; from the request on, new imports, uploads and bulk study updates in the project are refused at their routes and at job start U, I, E
DL-R1W-02 The item's work is identified by the rule in in-flight work §1: for a search, its own import, its uploads and version 2 bulk updates whose plan touches its Studies; for a project, every import, upload and bulk update U
DL-R1W-03 Uploads: every Queued, Uploading or unclaimed Uploaded upload of the item is cancelled through the shared cancellation service; uploads past the agent's claim are waited for; a cancel that loses to the claim joins the wait U, I, E (Bulk PDF lane)
DL-R1W-04 Uploads: when the first Study moves, every upload of the item is Aborting or terminal; a delivery racing the move cannot mark a moved Study; clean-up of a cancelled upload completes after its search is unlinked and after its project is deleted; earlier-delivered PDFs are kept and come back on restore I, E
DL-R1W-05 Imports: an import of the item in Uploading, Received, Parsing or Saving is marked Error with reason CancelledForDeletion and the deletion operation id, through the same versioned write as the failure path U, I
DL-R1W-06 Imports: a cancelled parse stops at its start check or within one batch, whatever the statistics flags; after the clean-up no unrevealed Study of the search remains, the import's fence is released and the saga ends Error; the reference file in S3 is kept I, E
DL-R1W-07 Imports: cancel and completion have exactly one winner in both orders: completion first reveals the search and the deletion detaches it; cancel first makes completion refuse and clean up I
DL-R1W-08 Version 2 bulk updates: the durable cancel request is honoured by a live attempt before its next batch and by every takeover; with no live attempt the deletion runner acquires the lease and drives the operation; each phase ends as in in-flight work §4.1: Planning to Locking end Cancelled with no change, Applying rolls back every applied change (before-image check), Committed is kept and waited for U, I
DL-R1W-09 Version 2 bulk updates: a RollbackQuarantined operation blocks the deletion with BulkStudyUpdateQuarantined and nothing proceeds until it is resolved I
DL-R1W-10 Version 1 bulk updates: cancelled while UploadingToS3 or ValidatingOnServer during a project deletion, with no fence admitted and nothing applied; waited for while applying; waited for in every status during a search deletion U, I
DL-R1W-11 Fences: the deletion's fence is admitted only after every cancelled or awaited job is terminal and has released its own fence; it is never held while waiting U, I
DL-R1W-12 No late re-admission: after the deletion's release, a redelivered parse start, a late import failure replay and a stalled bulk-update attempt each leave no active fence (fence diagnostics) I
DL-R1W-13 Idempotency and crashes: repeating any cancel is a no-op; after a crash at any step the next lease holder continues without cancelling twice; a runner dying mid-rollback is taken over and the rollback completes I
DL-R1W-14 Audit: each cancelled job has a ledger record (UploadCancelledForDeletion, ImportCancelledForDeletion, BulkStudyUpdateCancelledForDeletion) naming the deleting person as initiator and system as executor, and carries the deletion operation id on its own record; the job's owner sees the cancellation reason U, I, E
DL-R1W-15 Work that is waited for (recommendation) is waited for, and a wait longer than WaitAlertHours sets BlockedReason and raises the alert U, I
DL-R1W-16 End to end: deleting a search while its import runs, and a project while a bulk study update runs, both end with the item deleted, nothing of the cancelled job applied, and every family InParity after rebuild E, S (searchDeleteDuringImport, projectDeleteDuringBulkUpdate)
DL-R1W-17 Abandoned version 1 bulk update (Chris, 14 October 2026, option A): a version 1 job applying when a deletion arrives is waited for until it has not changed for 30 minutes (Version1StaleAfter), then treated as abandoned: the deletion proceeds, a warning is logged and one BulkStudyUpdateAbandonedForDeletion ledger record names the job, however often the participant is asked. The job itself is left unchanged. Positive control: the same job is waited for one minute before the bound I

R1: reversible search deletion (S4a, S4b, S5)

ID Criterion Method
DL-R1S-01 With the flag on, a RemoveSearch holder deletes a search: once in-flight work is handled it leaves the search list, and within the sweep every Study of it leaves every read surface in the leak catalogue I, E
DL-R1S-02 With the flag off, both search DELETE routes perform production's legacy immediate deletion (owner decision 2026-10-14, syrf#4172) and never create a reversible deletion U, I
DL-R1S-03 Under forced interleaving no save lands on a moved Study; a save that lands first is kept and returns on restore (the legacy part of AC-R2a-53, for searches) I
DL-R1S-04 A crash at any point is resumed by lease takeover with exact counts and nothing moved twice I, S (pmRestartMidSweep)
DL-R1S-05 Recently deleted lists the search for 30 days with references count, deleted by, "Deleted on" and "Can be restored until", visible only to RemoveSearch holders and the owner; after the window it is no longer listed E
DL-R1S-06 Restore within the window by a RemoveSearch holder returns every Study with its screening decisions, annotations and saved sessions, revives no reservation, relinks the search and its import-job entry, and leaves stage search filters as they were; after the window a user restore is refused I, E
DL-R1S-07 After a restore, stored derived screening fields match the current agreement settings (drift check driftedStudies: 0) and no Study keeps answers to a question deleted while the search was deleted I, S (searchRestoreAfterChange)
DL-R1S-08 Statistics: during delete or restore every affected family falls back to live; afterwards, once rebuilt, the per-family parity audit reads InParity for all nine families; QuestionAnswers is in the fence (#3849); the fence is keyed by the operation id I, E, S
DL-R1S-09 No Study with a holding id carries a pending fold set at the end of any deletion I, S
DL-R1S-10 The ledger records the request, the deletion and the restore with actor, time and counts; the SignalR events reach open pages I, E
DL-R1S-11 The new UI meets the FEAT-023 M3 contract in light and dark themes, passes the repo-wide guard specs, has preview acceptance, and offers no "delete permanently" action U, E, R
DL-R1S-12 user-guide/studies/nav-studies.md and user-guide/studies/manage-studies.md describe reversible deletion, the 30-day window, administrator restore afterwards, and that SyRF keeps deleted data R
DL-R1S-13 A 100,000-Study search is deleted and restored in under 10 minutes each, with save p95 within 20% of idle during the sweep (initial target) B

R1: reversible project deletion (S6, S7)

ID Criterion Method
DL-R1P-01 With the flag on, the owner deletes a project and, once in-flight work is handled, every member loses access (R0 gate), with the deleted page on direct links; with the flag off the route performs production's legacy immediate deletion (owner decision 2026-10-14, syrf#4172; AC-R2a-52, legacy part) I, E
DL-R1P-02 ProjectStatisticsControl.IsProjectDeleted is set in the deletion transaction when a control exists; statistics reads report the project unavailable; no stored row is changed or removed I
DL-R1P-03 Recently deleted projects shows the owner their deleted projects for 30 days with "Deleted on" and "Can be restored until"; after the window they are no longer listed E
DL-R1P-04 Restore by the owner within the window, or by an application administrator at any time, returns the project with members, groups, stages, searches and data unchanged; releases every reservation recorded before the deletion; revives none (AC-R2a-55, legacy part) I, E
DL-R1P-05 After a restore with no activity while deleted, every family reads InParity before any backfill (the kept rows are served again); with activity, only the changed families are Stale and all read InParity after the backfill I, S (projectDeleteRestore)
DL-R1P-06 The ledger records the request, the deletion and the restore with actor, time and counts; the optional reason is shown in Recently deleted (AC-R2a-56, without preview digest) I, E
DL-R1P-07 user-guide/projects/settings.md describes reversible project deletion, the 30-day window, administrator restore afterwards, and that SyRF keeps deleted data R

R1: administrator restore and kept data (S8)

ID Criterion Method
DL-R1A-01 Only the application-administrator capability can list deletions and restore through api/admin/deletions, in or after the window, for searches and projects; every call is audited U, I
DL-R1A-02 An administrator restore applies the same waits, checks and statistics steps as a user restore and ends InParity I, S (restoreAfterWindow)
DL-R1A-03 Nothing is erased: after deletes, restores, window ends and every background job, every Study, search and project document of the fixture still exists I, S (nothingErased)
DL-R1A-04 The detached-Studies and deleted-projects gauges are exported and documented in the runbook U, R