---
id: ATS-PFM-DJE-008
scope: CAP-PFM-DJE-008
product_area: platform
module: data-job-engine
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  macroservice-libraries: 54536a441935c6e09fdb7f96f0bec71c52ba4e03
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-DJE-008-01
    title: Rollback planning creates linked validated work
    level: api
    proves:
      - BR-CAP-PFM-DJE-008-01
    preconditions: Complete an update over ten contacts.
    steps:
      - do: Prepare its restoration plan.
        expect: >-
          The separate validated job links to the original and retains the overwritten values for
          eligible records.
        provenance: test
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#answers
          a rollback job of its own, linked to the job it compensates
    expected: >-
      The separate validated job links to the original and retains the overwritten values for
      eligible records.
    negative: false
    data_needs: No additional fixture is needed.
    bound_to:
      - TST-PFM-DJE-119
    provenance: test
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#answers
      a rollback job of its own, linked to the job it compensates
    category: transition
    binding_evidence:
      - >-
        services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#answers
        a rollback job of its own, linked to the job it compensates
      - >-
        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
      - >-
        services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#freezes
        one item per contact it will put back, each carrying the value the job overwrote
  - id: ATS-PFM-DJE-008-02
    title: Later edits remain outside restoration
    level: api
    proves:
      - BR-CAP-PFM-DJE-008-02
    preconditions: Edit two of ten contacts after their bulk update.
    steps:
      - do: Plan and apply rollback.
        expect: >-
          Eight contacts are restored; two exclusions identify changed fields, editors and times
          while preserving their edits.
        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
    expected: >-
      Eight contacts are restored; two exclusions identify changed fields, editors and times while
      preserving their edits.
    negative: true
    data_needs: No additional fixture is needed.
    bound_to:
      - TST-PFM-DJE-119
      - TST-PFM-DJE-080
    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
    category: concurrency
    binding_evidence:
      - >-
        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
      - >-
        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
      - >-
        services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#leaves
        the two contacts somebody edited as that person left them
      - >-
        react-console:acceptance/data-jobs/playwright/journeys/dialogs-cross-application/DJE-ACC-0150.spec.ts#DJE-ACC-0150
  - id: ATS-PFM-DJE-008-03
    title: Restoration requires versioned input
    level: api
    proves:
      - BR-CAP-PFM-DJE-008-03
    preconditions: Supply an unversioned plan and separately edit a record after a valid plan.
    steps:
      - do: Attempt planning and application.
        expect: The unversioned plan creates no job; the later edit produces a conflict.
        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
    expected: The unversioned plan creates no job; the later edit produces a conflict.
    negative: true
    data_needs: No additional fixture is needed.
    bound_to:
      - TST-PFM-DJE-032
    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
    category: validation
    binding_evidence:
      - >-
        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
      - >-
        macroservice-libraries:packages/data-jobs/src/tests/integration/rollback-and-retry.integration.spec.ts#never
        overwrites an edit made after the job or after the plan: each is an explicit changed_since
        row
  - id: ATS-PFM-DJE-008-04
    title: Unsupported rollback states have coded refusals
    level: unit
    proves:
      - BR-CAP-PFM-DJE-008-04
    preconditions: 'Prepare delete, running and already-restored job states.'
    steps:
      - do: Evaluate rollback availability for each and for a merely planned rollback.
        expect: Unsupported cases return their refusal codes; planning alone does not block another plan.
        provenance: test
        source: >-
          macroservice-libraries:packages/data-jobs/src/data-job-actions.spec.ts#refuses Rollback by
          the code the service would answer
    expected: Unsupported cases return their refusal codes; planning alone does not block another plan.
    negative: true
    data_needs: No additional fixture is needed.
    bound_to:
      - TST-PFM-DJE-012
    provenance: test
    source: >-
      macroservice-libraries:packages/data-jobs/src/data-job-actions.spec.ts#refuses Rollback by the
      code the service would answer
    category: edge
    binding_evidence:
      - >-
        macroservice-libraries:packages/data-jobs/src/data-job-actions.spec.ts#refuses Rollback by
        the code the service would answer
      - >-
        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
  - id: ATS-PFM-DJE-008-05
    title: Custom-field changes exclude whole records
    level: browser
    proves:
      - BR-CAP-PFM-DJE-008-05
    preconditions: Prepare a job that sets a title and a custom value on ten contacts.
    steps:
      - do: Plan restoration and inspect its refusals.
        expect: >-
          Those contacts are non-reversible as whole records; their titles are not independently
          restored.
        provenance: proposed
        source: >-
          react-console:acceptance/crm/playwright/journeys/data-operations/CRM-ACC-0659.spec.ts#CRM-ACC-0659
    expected: >-
      Those contacts are non-reversible as whole records; their titles are not independently
      restored.
    negative: true
    data_needs: Contacts with the relevant custom field.
    bound_to: []
    provenance: proposed
    source: >-
      react-console:acceptance/crm/playwright/journeys/data-operations/CRM-ACC-0659.spec.ts#CRM-ACC-0659
    category: boundary
    removed_binding_evidence:
      - >-
        react-console:acceptance/crm/playwright/journeys/data-operations/CRM-ACC-0659.spec.ts#CRM-ACC-0659
  - id: ATS-PFM-DJE-008-06
    title: Rollback refuses a caller lacking update access
    level: api
    proves:
      - BR-CAP-PFM-DJE-008-06
    preconditions: Use a caller with bulk access but no update permission.
    steps:
      - do: Request rollback planning.
        expect: The proposed API proof expects 403.
        provenance: proposed
        source: >-
          macroservice-libraries:packages/data-jobs/src/data-job-actions.spec.ts#offers Rollback for
          a finished job, requiring the bulk key and the per-record key the plan re-checks
    expected: The proposed API proof expects 403.
    negative: true
    data_needs: Caller contexts with distinct grants.
    bound_to: []
    provenance: proposed
    source: >-
      macroservice-libraries:packages/data-jobs/src/data-job-actions.spec.ts#offers Rollback for a
      finished job, requiring the bulk key and the per-record key the plan re-checks
    category: permission
    binding_evidence: []
  - id: ATS-PFM-DJE-008-07
    title: Applied restoration prevents reopening original blocked work
    category: interaction
    level: api
    exercises:
      - CAP-PFM-DJE-004
      - CAP-PFM-DJE-008
    proves:
      - BR-CAP-PFM-DJE-004-05
      - BR-CAP-PFM-DJE-008-01
      - BR-CAP-PFM-DJE-008-09
    preconditions: Finish an update with reversible changes and blocked input.
    steps:
      - do: >-
          Plan a restoration, verify planning alone leaves retry available, apply the restoration,
          and attempt retry of the original.
        expect: Only application of restoration prevents later retry; restored values stay intact.
        provenance: proposed
        source: >-
          macroservice-libraries:packages/data-jobs/src/tests/integration/rollback-and-retry.integration.spec.ts#refuses
          to retry a job that has been rolled back, so its blocked rows are not written after the
          undo;
          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;
          macroservice-libraries:packages/data-jobs/README.md#Rolling a job back
    expected: Only application of restoration prevents later retry; restored values stay intact.
    negative: false
    data_needs: Finish an update with reversible changes and blocked input.
    bound_to: []
    provenance: proposed
    source: >-
      macroservice-libraries:packages/data-jobs/src/tests/integration/rollback-and-retry.integration.spec.ts#refuses
      to retry a job that has been rolled back, so its blocked rows are not written after the undo;
      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;
      macroservice-libraries:packages/data-jobs/README.md#Rolling a job back
  - id: ATS-PFM-DJE-008-08
    title: Restoration retains an inspectable link and exclusions
    category: interaction
    level: api
    exercises:
      - CAP-PFM-DJE-005
      - CAP-PFM-DJE-008
    proves:
      - BR-CAP-PFM-DJE-005-05
      - BR-CAP-PFM-DJE-008-01
      - BR-CAP-PFM-DJE-008-02
    preconditions: Complete a contact title update and edit one affected title afterward.
    steps:
      - do: 'Plan and apply restoration, then inspect both jobs and the excluded contact.'
        expect: >-
          The restoration is linked to the original; reversible contacts are restored and the later
          edit remains with an exclusion.
        provenance: proposed
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#stamps
          the job on the revision, so every record one job changed can be listed;
          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;
          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
    expected: >-
      The restoration is linked to the original; reversible contacts are restored and the later edit
      remains with an exclusion.
    negative: false
    data_needs: Complete a contact title update and edit one affected title afterward.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#stamps
      the job on the revision, so every record one job changed can be listed;
      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;
      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
  - id: ATS-PFM-DJE-008-09
    title: An edit after restoration planning remains protected
    category: interaction
    level: api
    exercises:
      - CAP-PFM-DJE-006
      - CAP-PFM-DJE-008
    proves:
      - BR-CAP-PFM-DJE-006-01
      - BR-CAP-PFM-DJE-008-02
      - BR-CAP-PFM-DJE-008-03
    preconditions: Complete an update and plan its restoration using current versions.
    steps:
      - do: Edit a reversible contact after planning and approve the restoration.
        expect: >-
          The later edit survives as a changed-since conflict while other reversible contacts are
          restored.
        provenance: proposed
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-versions.integration.spec.js#skips
          the contact that changed after the validation as a named conflict;
          services-crm:modules/data-jobs/services/operations/contact-update.ts:prevalidate;
          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;
          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
    expected: >-
      The later edit survives as a changed-since conflict while other reversible contacts are
      restored.
    negative: true
    data_needs: Complete an update and plan its restoration using current versions.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-versions.integration.spec.js#skips
      the contact that changed after the validation as a named conflict;
      services-crm:modules/data-jobs/services/operations/contact-update.ts:prevalidate;
      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;
      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
  - id: ATS-PFM-DJE-008-10
    title: A completed hard deletion offers no restoration
    category: interaction
    level: api
    exercises:
      - CAP-PFM-DJE-007
      - CAP-PFM-DJE-008
    proves:
      - BR-CAP-PFM-DJE-007-07
      - BR-CAP-PFM-DJE-007-08
      - BR-CAP-PFM-DJE-008-04
    preconditions: Prepare contacts eligible for deletion.
    steps:
      - do: 'Validate and approve deletion, inspect its receipts, and request rollback.'
        expect: Deleted records remain absent and the unsupported restoration creates no compensating job.
        provenance: proposed
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-delete.integration.spec.js#removed,
          per collection, exactly the number of rows the validation had counted;
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#refuses
          the rollback, because there is no undo for a hard delete;
          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
    expected: Deleted records remain absent and the unsupported restoration creates no compensating job.
    negative: false
    data_needs: Prepare contacts eligible for deletion.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-delete.integration.spec.js#removed,
      per collection, exactly the number of rows the validation had counted;
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-update-rollback.integration.spec.js#refuses
      the rollback, because there is no undo for a hard delete;
      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
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.
  - >-
    Testing back-fill: related capability rules, domain invariants, state transitions and crossing
    workflows at the pinned source revisions.
coverage:
  rule: 'gap: FND-PFM-DJE-068'
  transition: 'gap: FND-PFM-DJE-068'
  validation: covered
  permission: covered
  edge: covered
  failure: 'gap: FND-PFM-DJE-068'
  boundary: covered
  concurrency: covered
  invariant: 'gap: FND-PFM-DJE-068'
  interaction: covered
coverage_notes:
  transition: >-
    Selected lifecycle examples do not cover every applicable object and batch transition. The
    linked finding retains the remaining matrix.
  execution: >-
    Covered means a specification scenario exists. Proposed scenarios and registered suites with no
    run result do not establish executed conformance.
related:
  - CAP-PFM-DJE-004
  - CAP-PFM-DJE-006
  - CAP-PFM-DJE-007
workflows:
  - WF-PFM-DJE-005
coverage_evidence:
  - rule: BR-CAP-PFM-DJE-008-01
    scenarios:
      - ATS-PFM-DJE-008-01
      - ATS-PFM-DJE-008-07
      - ATS-PFM-DJE-008-08
    provenance: proposed
    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
  - rule: BR-CAP-PFM-DJE-008-02
    scenarios:
      - ATS-PFM-DJE-008-02
      - ATS-PFM-DJE-008-08
      - ATS-PFM-DJE-008-09
    provenance: proposed
    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
  - rule: BR-CAP-PFM-DJE-008-03
    scenarios:
      - ATS-PFM-DJE-008-03
      - ATS-PFM-DJE-008-09
    provenance: proposed
    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
  - rule: BR-CAP-PFM-DJE-008-04
    scenarios:
      - ATS-PFM-DJE-008-04
      - ATS-PFM-DJE-008-10
    provenance: proposed
    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
  - rule: BR-CAP-PFM-DJE-008-05
    scenarios:
      - ATS-PFM-DJE-008-05
    provenance: proposed
    source: 'services-crm:docs/data-jobs.md#Putting a bulk change back'
  - rule: BR-CAP-PFM-DJE-008-06
    scenarios:
      - ATS-PFM-DJE-008-06
    provenance: proposed
    source: >-
      macroservice-libraries:packages/data-jobs/README.md#What a rollback promises, and what Retry
      does beside it
---

# Restore a bulk update while preserving later changes: Capability Test Specification

## Coverage

The structured coverage rows link required rules to their scenarios. A covered category has a defined check; executable coverage and successful execution require separate evidence. The category map keeps incomplete lifecycle matrices and other missing checks visible as findings.

## Interactions With Related Capabilities

Applied restoration prevents reopening original blocked work. Restoration retains an inspectable link and exclusions. An edit after restoration planning remains protected. A completed hard deletion offers no restoration. The interaction scenarios use the related capabilities' rule references and identify the shared fixture and observable outcome.
