---
id: CAP-PFM-DJE-008
title: Restore a bulk update while preserving later changes
feature: FEAT-PFM-DJE-02
product_area: platform
module: data-job-engine
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - CAP-DJE-ENG-005
source_sha:
  macroservice-libraries: 54536a441935c6e09fdb7f96f0bec71c52ba4e03
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#A3'
  - 'services-crm:docs/milestones/CRM-M7-data-operations.md#D-39'
  - 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#S09'
purpose: >-
  Administrators plan restoration of reversible record changes and review anything that must remain as it
  is.
outcomes:
  - Restoration has its own reviewed job.
  - Later field changes remain protected and explain exclusions.
  - Unsupported restoration is reported explicitly.
concepts:
  - CON-PFM-DJE-001
actors:
  - ACT-PFM-035
surfaces:
  - console
  - api
preconditions: []
postconditions: []
permissions:
  - key: crm.contacts.bulk
    where_checked: api
    scope: tenant
  - key: crm.contacts.update
    where_checked: api
    scope: tenant
  - key: crm.organizations.bulk
    where_checked: api
    scope: tenant
  - key: crm.organizations.update
    where_checked: api
    scope: tenant
  - key: data.jobs.rollback
    where_checked: console
    scope: tenant
rules:
  - id: BR-CAP-PFM-DJE-008-01
    proved_by: [ATS-PFM-DJE-004-09, ATS-PFM-DJE-005-10, ATS-PFM-DJE-008-01, ATS-PFM-DJE-008-07, ATS-PFM-DJE-008-08, TST-PFM-DJE-119]
    statement: >-
      Rollback planning creates a separate validated job linked to the original, requiring its own impact
      fingerprint for application.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#plans
      it already validated, because planning a rollback IS validating it
    origin: 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#A3'
  - id: BR-CAP-PFM-DJE-008-02
    proved_by: [ATS-PFM-DJE-005-10, ATS-PFM-DJE-006-09, ATS-PFM-DJE-008-02, ATS-PFM-DJE-008-08, ATS-PFM-DJE-008-09, TST-PFM-DJE-080, TST-PFM-DJE-119]
    statement: >-
      A record is reversible only when the affected fields still hold the job’s values and have no later
      changes; exclusions identify the field, editor and time, or report deletion, without quoting
      values.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#says
      which field changed since the job and when, without saying what it was changed to
    origin: 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#A3'
  - id: BR-CAP-PFM-DJE-008-03
    proved_by: [ATS-PFM-DJE-006-09, ATS-PFM-DJE-008-03, ATS-PFM-DJE-008-09, TST-PFM-DJE-032]
    statement: >-
      Rollback input must carry each record’s current version; missing versions refuse the plan without
      creating work, and edits after planning conflict at application.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      macroservice-libraries:packages/data-jobs/src/tests/integration/rollback-and-retry.integration.spec.ts#refuses
      a rollback plan with an unversioned item, and writes no rollback job
    origin: 'services-crm:docs/milestones/CRM-M7-data-operations.md#D-39'
  - id: BR-CAP-PFM-DJE-008-04
    proved_by: [ATS-PFM-DJE-007-10, ATS-PFM-DJE-008-04, ATS-PFM-DJE-008-10, TST-PFM-DJE-012]
    statement: >-
      Rollback refuses hard deletes, unfinished jobs, absent receipts, unsupported operations and jobs
      whose rollback has already been applied.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      macroservice-libraries:packages/data-jobs/src/data-job-actions.spec.ts#does not offer Rollback once
      a rollback has been applied, by the code the route answers, and still does while one is only
      planned
    origin: 'services-crm:docs/milestones/CRM-M7-data-operations.md#D-39'
  - id: BR-CAP-PFM-DJE-008-05
    proved_by: [ATS-PFM-DJE-008-05]
    statement: >-
      Rollback supports ordinary record fields; changing a collection or custom field makes that record
      non-reversible as a whole.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: documented
    source: 'services-crm:docs/data-jobs.md#Putting a bulk change back'
    origin: 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#A3'
  - id: BR-CAP-PFM-DJE-008-06
    proved_by: [ATS-PFM-DJE-008-06]
    statement: >-
      Rollback requires bulk and update permission, with update authority rechecked for the submitter
      during processing.
    kind: permission
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: documented
    source: >-
      macroservice-libraries:packages/data-jobs/README.md#What a rollback promises, and what Retry does
      beside it
    origin: 'services-crm:docs/milestones/CRM-M7-data-operations.md#D-39'
  - id: BR-CAP-PFM-DJE-008-07
    proved_by: []
    statement: >-
      Contact and organization update operations support rollback; imports, configuration changes and
      activity jobs do not declare it.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-crm:docs/data-jobs.md#What is not built'
    origin: none
  - id: BR-CAP-PFM-DJE-008-08
    proved_by: []
    statement: A rollback with no reversible records is created in completed-with-rejections state.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'macroservice-libraries:packages/data-jobs/README.md#Rolling a job back'
    origin: none
  - id: BR-CAP-PFM-DJE-008-09
    proved_by: [ATS-PFM-DJE-004-09, ATS-PFM-DJE-008-07]
    statement: >-
      A rollback job may itself be rolled back; merely planning rollback does not prevent another plan or
      retry of the original job.
    kind: policy
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'macroservice-libraries:packages/data-jobs/README.md#Rolling a job back'
    origin: none
scenarios:
  - id: SC-CAP-PFM-DJE-008-01
    given: A colleague edits two contacts after a ten-contact bulk update.
    when: An administrator prepares and applies restoration.
    then: >-
      The plan restores eight untouched contacts and preserves the two later edits as explained
      exclusions.
    provenance: test
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#plans
      a rollback of the eight nobody touched, and refuses the two somebody did
edge_cases: []
constraints: []
depends_on: []
related:
  - CAP-PFM-DJE-004
  - CAP-PFM-DJE-006
  - CAP-PFM-DJE-007
journeys:
  - JNY-PFM-DJE-002
open_questions: []
evidence:
  - >-
    Reviewed evidence: macroservice-libraries (54536a44): README.md (Rolling a job back);
    src/plan-data-job-rollback.ts, data-job-rollback.ts, data-job-actions.ts; tests
    data-job-rollback.spec.ts, data-job-actions.spec.ts, rollback-and-retry.integration.spec.ts,
    hard-delete-and-rollback.integration.spec.ts.
  - >-
    Reviewed evidence: services-crm (a8d1364f): docs/data-jobs.md (Putting a bulk change back);
    services/operations/record-rollback-plan.ts, contact-update-rollback.ts,
    organization-update-rollback.ts; tests contact-update-rollback.integration.spec.js,
    organization-update-rollback.integration.spec.js.
  - >-
    Reviewed evidence: react-console (baf6dae3): lib/api/data-jobs/rules.ts (ROLLBACK_SUPPORTED);
    components/data-management/data-jobs/job-rollback-panel.tsx; lib/authz/backend-aliases.ts; acceptance
    DJE-ACC-0150, CRM-ACC-0659.
controls: [TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-LOG-002, TC-LOG-003, TC-LOG-004, TC-LOG-005, TC-MON-001, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-PRV-001, TC-PRV-002, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-PFM-DJE-02-01, FAS-PFM-DJE-02-02, FAS-PFM-DJE-02-03, FAS-PFM-DJE-02-04, FAS-PFM-DJE-02-05]
suites: [TST-PFM-DJE-012, TST-PFM-DJE-032, TST-PFM-DJE-080, TST-PFM-DJE-119]
---

# Restore a bulk update while preserving later changes

## Purpose

Administrators plan restoration of reversible record changes and review anything that must remain as it is.

## How It Works

An administrator requests rollback from a completed update. Rhythm compares its receipts and recorded history with the current records. It identifies records that can be restored and explains exclusions. A later change to an affected field prevents restoration of that record. Input containing a custom field or collection is also outside the supported restoration scope. The plan becomes a separate validated job that the administrator reviews and applies. Version checks preserve edits made after planning. A plan with no eligible records is already finished with rejections. The backend supports organization updates as well as contact updates, although the console exposes only contact restoration. The product should explain that surface difference and the records excluded from restoration.

## Relationships

- Job controls prevent retry after an applied rollback.
- Intervening-edit protection supplies related concurrency safeguards.
- Hard deletion is outside restoration scope.
