---
id: ATS-PFM-DJE-002
scope: CAP-PFM-DJE-002
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
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
scenarios:
  - id: ATS-PFM-DJE-002-01
    title: Validation leaves records intact
    level: api
    proves:
      - BR-CAP-PFM-DJE-002-01
    preconditions: Prepare a title update for two contacts.
    steps:
      - do: Validate the request and inspect its job detail.
        expect: >-
          The job waits in validated state and exposes planned impact and outcomes; target records
          are unchanged.
        provenance: test
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#changes
          nothing while it is validating
    expected: >-
      The job waits in validated state and exposes planned impact and outcomes; target records are
      unchanged.
    negative: false
    data_needs: No additional fixture is needed.
    bound_to:
      - TST-PFM-DJE-117
    provenance: test
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#changes
      nothing while it is validating
    category: transition
    binding_evidence:
      - >-
        services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#changes
        nothing while it is validating
      - >-
        services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#reports
        what the validation would have done, so the submitter can decide to apply it
      - >-
        services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#leaves
        the validated job waiting to be applied, not finished
  - id: ATS-PFM-DJE-002-02
    title: Approval must identify the saved impact
    level: api
    proves:
      - BR-CAP-PFM-DJE-002-02
    preconditions: Prepare validated contact and configuration jobs.
    steps:
      - do: 'Attempt approval without a fingerprint, with a different one, and with the saved one.'
        expect: >-
          Invalid fingerprints are refused while the jobs remain validated; a matching approval
          queues work.
        provenance: test
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
          an apply of the validated update that echoes no impact digest
    expected: >-
      Invalid fingerprints are refused while the jobs remain validated; a matching approval queues
      work.
    negative: true
    data_needs: No additional fixture is needed.
    bound_to:
      - TST-PFM-DJE-117
      - TST-PFM-DJE-116
    provenance: test
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
      an apply of the validated update that echoes no impact digest
    category: validation
    binding_evidence:
      - >-
        services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
        an apply of the validated update that echoes no impact digest
      - >-
        services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
        an apply of the validated update whose digest is not the one the validation answered
      - >-
        services-crm:modules/data-jobs/tests/integration/services/jobs/configuration-data-jobs.integration.spec.js#leaves
        the job validated after both refusals, so the refused applies queued nothing
  - id: ATS-PFM-DJE-002-03
    title: Acting on an old preview expires it
    level: api
    proves:
      - BR-CAP-PFM-DJE-002-03
    preconditions: Prepare a job whose validation finished 25 hours earlier.
    steps:
      - do: Read the job and then attempt approval.
        expect: The read still shows validated; approval expires the job and is refused.
        provenance: proposed
        source: 'macroservice-libraries:packages/data-jobs/src/job-control.ts:applyValidatedJob'
    expected: The read still shows validated; approval expires the job and is refused.
    negative: true
    data_needs: A clock that the fixture can control.
    bound_to: []
    provenance: proposed
    source: 'macroservice-libraries:packages/data-jobs/src/job-control.ts:applyValidatedJob'
    category: boundary
    binding_evidence: []
  - id: ATS-PFM-DJE-002-04
    title: An unvalidated job refuses approval
    level: api
    proves:
      - BR-CAP-PFM-DJE-002-04
    preconditions: Submit an organization job directly for application.
    steps:
      - do: Invoke its apply action.
        expect: The response is 409.
        provenance: test
        source: >-
          services-crm:modules/data-jobs/tests/e2e/organization-update.e2e.spec.js#refuses the apply
          of a job that was never validated with 409, because an apply is the approval of a
          validation
    expected: The response is 409.
    negative: true
    data_needs: No additional fixture is needed.
    bound_to:
      - TST-PFM-DJE-110
    provenance: test
    source: >-
      services-crm:modules/data-jobs/tests/e2e/organization-update.e2e.spec.js#refuses the apply of
      a job that was never validated with 409, because an apply is the approval of a validation
    category: validation
    binding_evidence:
      - >-
        services-crm:modules/data-jobs/tests/e2e/organization-update.e2e.spec.js#refuses the apply
        of a job that was never validated with 409, because an apply is the approval of a validation
  - id: ATS-PFM-DJE-002-05
    title: Impact aggregation retains all batch contributions
    level: unit
    proves:
      - BR-CAP-PFM-DJE-002-05
    preconditions: Prepare several batch impact reports.
    steps:
      - do: Combine those reports.
        expect: 'Change counts, affected-collection counts and blocked-reason counts are added.'
        provenance: test
        source: >-
          macroservice-libraries:packages/data-jobs/src/define-data-operation.spec.ts#adds the
          counts, which is what makes a digest speak for every batch and not the last one
    expected: 'Change counts, affected-collection counts and blocked-reason counts are added.'
    negative: false
    data_needs: No additional fixture is needed.
    bound_to:
      - TST-PFM-DJE-015
    provenance: test
    source: >-
      macroservice-libraries:packages/data-jobs/src/define-data-operation.spec.ts#adds the counts,
      which is what makes a digest speak for every batch and not the last one
    category: rule
    binding_evidence:
      - >-
        macroservice-libraries:packages/data-jobs/src/define-data-operation.spec.ts#adds the counts,
        which is what makes a digest speak for every batch and not the last one
      - >-
        macroservice-libraries:packages/data-jobs/src/define-data-operation.spec.ts#adds a cascade
        count per child collection, keeping the ones only one batch reached
      - >-
        macroservice-libraries:packages/data-jobs/src/define-data-operation.spec.ts#adds the blocked
        records per reason code the domain gave, the same way
  - id: ATS-PFM-DJE-002-06
    title: Saved submission reaches approval unchanged
    category: interaction
    level: api
    exercises:
      - CAP-PFM-DJE-001
      - CAP-PFM-DJE-002
    proves:
      - BR-CAP-PFM-DJE-001-04
      - BR-CAP-PFM-DJE-002-01
      - BR-CAP-PFM-DJE-002-02
    preconditions: Create two contacts with distinct starting values.
    steps:
      - do: >-
          Submit a common change for validation, alter the caller's local request, and approve the
          saved preview.
        expect: >-
          The contacts receive the saved change; the altered local request supplies no replacement
          input.
        provenance: proposed
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#freezes
          the patch on every item, so a later apply writes what was submitted and not what is asked
          for then;
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#changes
          nothing while it is validating;
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
          an apply of the validated update whose digest is not the one the validation answered
    expected: >-
      The contacts receive the saved change; the altered local request supplies no replacement
      input.
    negative: false
    data_needs: Create two contacts with distinct starting values.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#freezes
      the patch on every item, so a later apply writes what was submitted and not what is asked for
      then;
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#changes
      nothing while it is validating;
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
      an apply of the validated update whose digest is not the one the validation answered
  - id: ATS-PFM-DJE-002-07
    title: A later edit changes the approved job's result
    category: interaction
    level: api
    exercises:
      - CAP-PFM-DJE-002
      - CAP-PFM-DJE-006
    proves:
      - BR-CAP-PFM-DJE-002-01
      - BR-CAP-PFM-DJE-002-02
      - BR-CAP-PFM-DJE-006-01
    preconditions: Prepare two contacts and a validated title change.
    steps:
      - do: Edit one contact after preview and approve the original fingerprint.
        expect: >-
          The edited contact remains intact with a conflict while the other receives the saved
          title.
        provenance: proposed
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#changes
          nothing while it is validating;
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
          an apply of the validated update whose digest is not the one the validation answered;
          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
    expected: The edited contact remains intact with a conflict while the other receives the saved title.
    negative: true
    data_needs: Prepare two contacts and a validated title change.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#changes
      nothing while it is validating;
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
      an apply of the validated update whose digest is not the one the validation answered;
      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
  - id: ATS-PFM-DJE-002-08
    title: A stale deletion approval removes nothing
    category: interaction
    level: api
    exercises:
      - CAP-PFM-DJE-002
      - CAP-PFM-DJE-007
    proves:
      - BR-CAP-PFM-DJE-002-02
      - BR-CAP-PFM-DJE-007-01
      - BR-CAP-PFM-DJE-007-03
    preconditions: Prepare a deletion preview with eligible and blocked contacts.
    steps:
      - do: >-
          Attempt approval with another fingerprint or count phrase, inspect the records, then send
          the returned approval values.
        expect: >-
          Refused approvals preserve every record; the matching approval removes only eligible
          contacts.
        provenance: proposed
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
          an apply of the validated update whose digest is not the one the validation answered;
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-delete.integration.spec.js#refuses
          a delete submitted to be applied, because a delete is validated first;
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-delete.integration.spec.js#refuses
          an apply whose confirmation names another number of contacts
    expected: Refused approvals preserve every record; the matching approval removes only eligible contacts.
    negative: false
    data_needs: Prepare a deletion preview with eligible and blocked contacts.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
      an apply of the validated update whose digest is not the one the validation answered;
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-delete.integration.spec.js#refuses
      a delete submitted to be applied, because a delete is validated first;
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-delete.integration.spec.js#refuses
      an apply whose confirmation names another number of contacts
  - id: ATS-PFM-DJE-002-09
    title: Dialog approval is tied to its saved preview
    category: interaction
    level: api
    exercises:
      - CAP-PFM-DJE-002
      - CAP-PFM-DJE-009
    proves:
      - BR-CAP-PFM-DJE-002-02
      - BR-CAP-PFM-DJE-009-07
      - BR-CAP-PFM-DJE-009-10
    preconditions: Open a contact deletion dialog with an eligible selection.
    steps:
      - do: >-
          Enter incorrect approval text, verify no apply request, then approve the returned phrase
          and repeat the click.
        expect: Incorrect text sends no approval and the accepted click applies one saved preview.
        provenance: proposed
        source: >-
          services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
          an apply of the validated update whose digest is not the one the validation answered;
          react-console:acceptance/data-jobs/playwright/journeys/dialogs/DJE-ACC-0112.spec.ts#DJE-ACC-0112;
          react-console:acceptance/data-jobs/playwright/journeys/dialogs/DJE-ACC-0105.spec.ts#DJE-ACC-0105
    expected: Incorrect text sends no approval and the accepted click applies one saved preview.
    negative: false
    data_needs: Open a contact deletion dialog with an eligible selection.
    bound_to: []
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
      an apply of the validated update whose digest is not the one the validation answered;
      react-console:acceptance/data-jobs/playwright/journeys/dialogs/DJE-ACC-0112.spec.ts#DJE-ACC-0112;
      react-console:acceptance/data-jobs/playwright/journeys/dialogs/DJE-ACC-0105.spec.ts#DJE-ACC-0105
evidence:
  - >-
    Reviewed evidence: macroservice-libraries (54536a44): README.md (Deleting records; Versions);
    src/job-control.ts, data-job-request.ts, define-data-operation.ts (mergeDataJobImpacts),
    data-job-config.ts (validationExpiryHours).
  - >-
    Reviewed evidence: services-crm (a8d1364f): docs/data-jobs.md (What a job is; Listing a tenant's
    jobs); tests contact-data-jobs.integration.spec.js, configuration-data-jobs.integration.spec.js,
    organization-update.e2e.spec.js.
  - >-
    Reviewed evidence: services-platform (ad3f6a65): docs/milestones/plans/PLAT-M19-plan.md (A1,
    S19), PLAT-M21-plan.md (walkthrough).
  - >-
    Reviewed evidence: react-console (baf6dae3):
    components/data-management/data-jobs/new/step-validate.tsx;
    acceptance/crm/journeys/data-operations/CRM-ACC-0662.
  - >-
    Testing back-fill: related capability rules, domain invariants, state transitions and crossing
    workflows at the pinned source revisions.
coverage:
  rule: covered
  transition: 'gap: FND-PFM-DJE-062'
  validation: covered
  permission: 'gap: FND-PFM-DJE-062'
  edge: 'gap: FND-PFM-DJE-062'
  failure: 'gap: FND-PFM-DJE-062'
  boundary: covered
  concurrency: 'gap: FND-PFM-DJE-062'
  invariant: 'gap: FND-PFM-DJE-062'
  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-001
  - CAP-PFM-DJE-006
  - CAP-PFM-DJE-007
workflows:
  - WF-PFM-DJE-001
  - WF-PFM-DJE-003
coverage_evidence:
  - rule: BR-CAP-PFM-DJE-002-01
    scenarios:
      - ATS-PFM-DJE-002-01
      - ATS-PFM-DJE-002-06
      - ATS-PFM-DJE-002-07
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#changes
      nothing while it is validating
  - rule: BR-CAP-PFM-DJE-002-02
    scenarios:
      - ATS-PFM-DJE-002-02
      - ATS-PFM-DJE-002-06
      - ATS-PFM-DJE-002-07
      - ATS-PFM-DJE-002-08
      - ATS-PFM-DJE-002-09
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/integration/services/jobs/contact-data-jobs.integration.spec.js#refuses
      an apply of the validated update whose digest is not the one the validation answered
  - rule: BR-CAP-PFM-DJE-002-03
    scenarios:
      - ATS-PFM-DJE-002-03
    provenance: proposed
    source: 'macroservice-libraries:packages/data-jobs/src/job-control.ts:applyValidatedJob'
  - rule: BR-CAP-PFM-DJE-002-04
    scenarios:
      - ATS-PFM-DJE-002-04
    provenance: proposed
    source: >-
      services-crm:modules/data-jobs/tests/e2e/organization-update.e2e.spec.js#refuses the apply of
      a job that was never validated with 409, because an apply is the approval of a validation
  - rule: BR-CAP-PFM-DJE-002-05
    scenarios:
      - ATS-PFM-DJE-002-05
    provenance: proposed
    source: >-
      macroservice-libraries:packages/data-jobs/src/define-data-operation.spec.ts#adds the counts,
      which is what makes a digest speak for every batch and not the last one
---

# Review a change before approving its application: 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

Saved submission reaches approval unchanged. A later edit changes the approved job's result. A stale deletion approval removes nothing. Dialog approval is tied to its saved preview. The interaction scenarios use the related capabilities' rule references and identify the shared fixture and observable outcome.
