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 |