---
id: TST-PFM-DJE-109
title: Job API and outcome inspection
kind: api-lifecycle
product_area: platform
module: data-job-engine
revision: 1
status: draft
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  macroservice-libraries: 54536a441935c6e09fdb7f96f0bec71c52ba4e03
  services-crm: a8d1364f1dd44b723bb5f9acba12f983e9ec8d69
  services-membership: 5994c270e8a37a30f848e99ba5b5ee6b87c08893
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
repository: services-crm
files:
  - modules/data-jobs/tests/e2e/data-jobs.e2e.spec.js
implements:
  - ATS-PFM-DJE-001-01
  - ATS-PFM-DJE-001-02
  - ATS-PFM-DJE-005-02
environment: local
implementation_state: implemented
latest_result: not-run
latest_run_at: null
latest_run_ref: null
conformance: not-verified
estimated_minutes: 5
isolation: >-
  Suite-owned fixture records, job identifiers and workspace contexts; teardown removes the fixture
  data.
evidence:
  - 'services-crm:modules/data-jobs/tests/e2e/data-jobs.e2e.spec.js'
assertion_inventory:
  - data jobs API (end to end)
  - who may ask for a bulk change
  - accepts the submission of a caller holding both keys (premise)
  - 'refuses an unsigned call with 401, because a bulk change is nobody’s by default'
  - refuses a context that does not cover the tenant in the path with 403
  - refuses a caller who may update one contact but holds no bulk key with 403
  - refuses a caller who may change contacts in bulk but may not update one with 403
  - who may list the tenant’s jobs
  - answers a caller holding both keys a page with the job they just submitted on it (premise)
  - 'answers each row with what the job is, where it got to and who submitted it'
  - 'refuses an unsigned call with 401, because a job list is nobody’s by default'
  - refuses a context that does not cover the tenant in the path with 403
  - refuses a caller who may update one contact but holds no bulk key with 403
  - refuses a caller who may change contacts in bulk but may not update one with 403
  - leaves this tenant’s job out of the list another tenant reads
  - 'refuses a state no job can be in with 400, rather than a filter it quietly ignores'
  - refuses a query parameter the list does not offer with 400
  - a body that has not said what to change
  - refuses a submission carrying both a selection and prepared records with 400
  - refuses a submission carrying neither with 400
  - 'validating a change, applying it, and reading what it did'
  - accepts a validation and says where to read it
  - answers the validated job with the state and the counters a console shows
  - answers the applied job with both contacts applied and nothing rejected
  - 'changed the contact itself, which is what the job was for'
  - 'pages the outcomes, answering the cursor the next page starts at'
  - >-
    answers the rejection of the contact the database refused, with its code and no value of the
    record in it
  - a job of another tenant
  - >-
    is not found for a caller reading it in their own tenant, which is where a job of theirs would
    be
  - cancels only a job the path’s tenant has
  - applying a job that was never validated
  - 'is refused 409, because an apply is the approval of a validation'
  - who may ask for a bulk delete
  - accepts the delete of a caller holding the bulk key and the delete key (premise)
  - refuses an unsigned call with 401
  - refuses a caller who may change contacts in bulk but may not delete one with 403
  - refuses a caller who may delete one contact but holds no bulk key with 403
  - refuses the delete of an organization to a caller without the organization delete key with 403
  - the confirmation a delete is applied with
  - 'accepts the submission only as a validation, and says so by answering a validated job'
  - 'tells the client what it would delete, what it is blocked on and what it would cascade into'
  - tells the client the exact words a person has to type
  - refuses an apply that carries the digest but nothing typed with 409
  - refuses an apply whose typed text is not the one it asked for with 409
  - accepts the apply that carries both and queues the job
  - 'deleted the contacts, which is what the typed confirmation approved'
  - rolling a finished job back over the wire
  - had changed the contact (premise)
  - 'answers a validated rollback job naming the job it compensates, and what it would put back'
  - is applied through the ordinary apply route with the digest it answered
  - put the contact back to what it was before the job
  - 'reads back as a rollback, so a console can show which job it undoes'
  - >-
    refuses a second rollback of the same job with 409, because the first restored it already
    (already_rolled_back, DEF-CRM-0648)
  - gives already_rolled_back as the reason the status read offers no rollback of that job
  - 'refuses the rollback of a delete job with 409, because a hard delete has no undo'
registration_note: Registration describes existing source; the suite is not executed by this documentation job.
---

# Job API and outcome inspection

## Verification Boundary

The suite checks requests through the local production dispatcher and signed caller evaluation. The structured assertion inventory records the checks present at the pinned revision.

## What This Suite Proves

The existing checks exercise job api and outcome inspection. The specification bindings identify the narrower outcomes supported by those assertions.

## What It Does Not Prove

Local execution does not establish deployed queue, storage or service behavior. Independent test cases do not establish a complete cross-feature process.

## Real Dependencies and Test Doubles

The owning service exercises production persistence and domain behavior against real Postgres. The fixture supplies caller contexts; queue delivery, storage and external calls are doubled where the file installs a test port.

## Must Not Be Doubled

The database effects, security context and production operation under examination remain real. Their outcomes cannot be replaced with canned success responses.
