---
id: ATS-PFM-QRY-014
scope: CAP-PFM-QRY-014
product_area: platform
module: queries
revision: 2
status: proposed
approved_by: null
approved_at: null
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
  rhythm-v2-docs: 99376625d3e7ac26936413cfc31a88fba6562464
scenarios:
  - id: ATS-PFM-QRY-014-01
    title: Query-export admission mirrors execution access
    level: api
    proves:
      - BR-CAP-PFM-QRY-014-01
    preconditions: A caller lacks the source dataset’s query grant.
    steps: Request a query export.
    expected: The request returns 403 before any export row is written.
    negative: true
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#refuses
        a caller who may not query the dataset (403)
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#refuses
        a caller who may not query the dataset, as running the query would (403)
      - TST-PFM-QRY-301
      - TST-PFM-QRY-302
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#refuses
      a caller who may not query the dataset (403)
    category: permission
    binding_note: >-
      The cited suites contribute separate assertions. The complete combined example is not a single
      executable journey.
  - id: ATS-PFM-QRY-014-02
    title: Export source and options must be unambiguous
    level: api
    proves:
      - BR-CAP-PFM-QRY-014-02
      - INV-DOM-PFM-QRY-004-03
    preconditions: Both a saved question and an on-screen definition are available.
    steps: 'Submit both sources, request CSV descriptions and omit options.'
    expected: Invalid choices return 400; omitted choices default to Excel with labels and no descriptions.
    negative: true
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#refuses
        a body naming both a saved query and a definition (400)
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#refuses
        descriptions for a CSV (400)
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        the contract’s defaults: Excel, labels, no descriptions
      - TST-PFM-QRY-301
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#refuses
      a body naming both a saved query and a definition (400)
    category: validation
  - id: ATS-PFM-QRY-014-03
    title: Whole-result export preserves order and confinement
    level: api
    proves:
      - BR-CAP-PFM-QRY-014-03
    preconditions: A query matches two thousand six hundred rows with similar records elsewhere.
    steps: Generate its file.
    expected: 'Every matching local row occurs once in the specified order, with no foreign rows.'
    negative: false
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
        every planted row exactly once, in the query’s sort, with no gap
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
        none of the other tenant's contacts of the same name
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        202 with the export queued
      - TST-PFM-QRY-302
      - TST-PFM-QRY-301
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
      every planted row exactly once, in the query’s sort, with no gap
    category: invariant
    binding_note: >-
      The cited suites contribute separate assertions. The complete combined example is not a single
      executable journey.
  - id: ATS-PFM-QRY-014-04
    title: Spreadsheet text is safely serialized
    level: unit
    proves:
      - BR-CAP-PFM-QRY-014-04
    preconditions: Fixture values include different types and formula-like text.
    steps: Write the CSV output.
    expected: >-
      Encoding, quoting and date forms are preserved, and dangerous text is prefixed without changing negative
      numeric values.
    negative: false
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/unit/services/exports/csv-writer.spec.ts#starts with a UTF-8
        byte-order mark
      - >-
        services-platform:modules/queries/tests/unit/services/exports/csv-writer.spec.ts#ends every line with
        CRLF
      - >-
        services-platform:modules/queries/tests/unit/services/exports/csv-writer.spec.ts#prefixes a value
        starting with = with an apostrophe
      - >-
        services-platform:modules/queries/tests/unit/services/exports/csv-writer.spec.ts#never prefixes a
        negative number
      - >-
        services-platform:modules/queries/tests/unit/services/exports/csv-writer.spec.ts#writes an instant as
        ISO 8601 with the tenant zone's offset
      - TST-PFM-QRY-394
    provenance: test
    source: >-
      services-platform:modules/queries/tests/unit/services/exports/csv-writer.spec.ts#starts with a UTF-8
      byte-order mark
    category: rule
  - id: ATS-PFM-QRY-014-05
    title: Row-limit failure gives a useful reason
    level: api
    proves:
      - BR-CAP-PFM-QRY-014-05
    preconditions: The query result exceeds the configured output-row limit.
    steps: Run file generation.
    expected: The export records a too-many-rows failure with an actionable sentence.
    negative: true
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#fails
        too_many_rows with the sentence a person reads
      - TST-PFM-QRY-302
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#fails
      too_many_rows with the sentence a person reads
    category: boundary
  - id: ATS-PFM-QRY-014-06
    title: Lost access prevents file generation
    level: api
    proves:
      - BR-CAP-PFM-QRY-014-06
    preconditions: A requester loses query access and staff contexts differ in active workspace grants.
    steps: Run their exports.
    expected: Unauthorized requesters receive access-lost failure without a file; granted staff complete production.
    negative: true
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#fails
        access_lost
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
        no file
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#builds
        the export through an active approved grant, with the query’s rows
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#fails
        access_lost for staff holding no grant into the tenant
      - TST-PFM-QRY-302
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#fails
      access_lost
    category: permission
  - id: ATS-PFM-QRY-014-07
    title: Status and downloads follow export audience
    level: api
    proves:
      - BR-CAP-PFM-QRY-014-07
      - INV-DOM-PFM-QRY-004-04
    preconditions: 'An export has requester, colleague and manager contexts plus ready and expired states.'
    steps: Read and download under each context and state.
    expected: >-
      Authorized audiences succeed, unrelated callers receive 404, pending files return 409 and expired files
      return 410.
    negative: true
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        the requester their export
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        404 to a colleague who did not ask for it
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        a holder of platform.query-schedules.manage
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        409 not_ready before the file exists
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        410 expired once the file has been removed, and the export says expired
      - >-
        react-console:acceptance/reporting/playwright/journeys/print-and-export/RPT-ACC-0347.spec.ts#RPT-ACC-0347
      - TST-PFM-QRY-301
      - TST-PFM-QRY-108
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
      the requester their export
    category: permission
    binding_note: >-
      The cited suites contribute separate assertions. The complete combined example is not a single
      executable journey.
  - id: ATS-PFM-QRY-014-08
    title: File keys and expiry remain export-specific
    level: api
    proves:
      - BR-CAP-PFM-QRY-014-08
    preconditions: A query export has completed.
    steps: Inspect its storage location and retained execution record.
    expected: Storage is under its workspace and export location and expiry follows completion by thirty days.
    negative: false
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#records
        the rows, the bytes, the completion and the expiry 30 days later
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#stores
        the file under the tenant’s and the export’s prefix, as CSV
      - >-
        react-console:acceptance/reporting/playwright/journeys/print-and-export/RPT-ACC-0348.spec.ts#RPT-ACC-0348
      - TST-PFM-QRY-302
      - TST-PFM-QRY-109
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#records
      the rows, the bytes, the completion and the expiry 30 days later
    category: transition
    binding_note: >-
      The cited suites contribute separate assertions. The complete combined example is not a single
      executable journey.
  - id: ATS-PFM-QRY-014-09
    title: Recipient links validate membership and expiry
    level: api
    proves:
      - BR-CAP-PFM-QRY-014-09
    preconditions: 'A completed delivery has valid, altered, expired and removed-recipient tokens.'
    steps: Open every link and inspect download accounting.
    expected: The valid link redirects and is recorded; invalid cases receive the plain invalid-link page.
    negative: true
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#redirects
        a valid link to the presigned file
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#records
        the download on the delivery
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        the plain page, never JSON, for a tampered token
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        the plain page for an expired token
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        the plain page for a recipient no longer on the schedule
      - >-
        services-platform:modules/queries/tests/unit/services/exports/download-tokens.spec.ts#refuses a token
        whose payload was altered
      - >-
        services-platform:modules/queries/tests/unit/services/exports/download-tokens.spec.ts#refuses a token
        at its expiry
      - TST-PFM-QRY-301
      - TST-PFM-QRY-395
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#redirects
      a valid link to the presigned file
    category: permission
    binding_note: >-
      The cited suites contribute separate assertions. The complete combined example is not a single
      executable journey.
  - id: ATS-PFM-QRY-014-10
    title: Reference export follows selected columns
    level: api
    proves:
      - BR-CAP-PFM-QRY-014-11
    preconditions: Questions include spouse references with and without identity columns and an empty result.
    steps: Export every selection.
    expected: >-
      Names remain display values, identities appear only when selected, and the empty result retains
      headings.
    negative: false
    data_needs: Use the existing binding’s controlled fixture data.
    bound_to:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
        the spouse’s name and, in its own column, the Spouse ID, as CSV
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
        no id at all when the Spouse ID is not selected
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
        the headings and nothing else
      - TST-PFM-QRY-302
    provenance: test
    source: >-
      services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
      the spouse’s name and, in its own column, the Spouse ID, as CSV
    category: edge
  - id: ATS-PFM-QRY-014-11
    title: A file contains the ordered answer beyond the first page
    category: interaction
    level: api
    exercises:
      - CAP-PFM-QRY-002
      - CAP-PFM-QRY-014
    proves:
      - BR-CAP-PFM-QRY-002-01
      - BR-CAP-PFM-QRY-014-03
    preconditions: 'A contact question spans more than one page, with duplicate sort values and a foreign workspace control.'
    steps: Run the first page and export the same on-screen definition.
    expected: >-
      The file matches the complete ordered local answer; later pages occur once and foreign records never
      appear.
    negative: false
    data_needs: >-
      Use an isolated workspace with the described source records, access grants and a surviving control
      workspace.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/queries/tests/integration/services/execute/run-query.integration.spec.js#confines
        a statement carrying no tenant predicate at all, so row-level security holds beneath the compiler
      - >-
        services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js#writes
        every planted row exactly once, in the query’s sort, with no gap
  - id: ATS-PFM-QRY-014-12
    title: Report files use the shared file-access boundary
    category: interaction
    level: api
    exercises:
      - CAP-PFM-QRY-013
      - CAP-PFM-QRY-014
    proves:
      - BR-CAP-PFM-QRY-013-01
      - BR-CAP-PFM-QRY-014-07
    preconditions: A requester has report use but another reader lacks export ownership and schedule authority.
    steps: 'Render a table report, follow it as the requester and request the same export as the unrelated reader.'
    expected: The requester downloads the completed file and the unrelated reader receives not found.
    negative: false
    data_needs: >-
      Use an isolated workspace with the described source records, access grants and a surviving control
      workspace.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/queries/tests/integration/services/rendering/report-exports.integration.spec.js#answers
        a colleague who may not see the private report 404, writing nothing
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        404 to a colleague who did not ask for it, even once the file exists
  - id: ATS-PFM-QRY-014-13
    title: Retry reuses each recipient’s file delivery
    category: interaction
    level: api
    exercises:
      - CAP-PFM-QRY-014
      - CAP-PFM-QRY-015
    proves:
      - BR-CAP-PFM-QRY-014-09
      - BR-CAP-PFM-QRY-015-08
    preconditions: A query schedule has two active recipients and a completed export.
    steps: 'Process the occurrence, retry it and follow one recipient link after that recipient loses membership.'
    expected: >-
      Two recipient-keyed communications requests remain; the departed recipient receives the invalid-link
      page.
    negative: false
    data_needs: >-
      Use an isolated workspace with the described source records, access grants and a surviving control
      workspace.
    bound_to: []
    provenance: proposed
    source:
      - >-
        services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js#answers
        the plain page for a recipient no longer on the schedule
      - >-
        services-platform:modules/queries/tests/integration/services/schedules/scheduled-delivery.integration.spec.js#submits
        nothing new when the same run is run again
evidence:
  - >-
    Pinned reference:
    react-console:acceptance/reporting/playwright/journeys/print-and-export/RPT-ACC-0347.spec.ts
  - >-
    Pinned reference:
    react-console:acceptance/reporting/playwright/journeys/print-and-export/RPT-ACC-0348.spec.ts
  - >-
    Pinned reference:
    services-platform:modules/queries/tests/integration/services/exports/query-export-routes.integration.spec.js
  - >-
    Pinned reference:
    services-platform:modules/queries/tests/integration/services/exports/run-query-export.integration.spec.js
  - 'Pinned reference: services-platform:modules/queries/tests/unit/services/exports/csv-writer.spec.ts'
  - 'Pinned reference: services-platform:modules/queries/tests/unit/services/exports/download-tokens.spec.ts'
coverage:
  rule: covered
  transition: 'gap: FND-PFM-QRY-064'
  validation: covered
  permission: covered
  edge: covered
  failure: 'gap: FND-PFM-QRY-064'
  boundary: covered
  concurrency: 'gap: FND-PFM-QRY-064'
  invariant: 'gap: FND-PFM-QRY-064'
  interaction: covered
coverage_findings:
  - FND-PFM-QRY-064
transition_obligations:
  - state: STATE-PFM-QRY-006
    transition: 1
    from: queued
    to: running
    trigger: A worker receives the queued export.
    source: 'services-platform:modules/queries/services/exports/run-query-export.ts'
    gap: FND-PFM-QRY-064
  - state: STATE-PFM-QRY-006
    transition: 2
    from: running
    to: running
    trigger: The queue delivers unfinished work again.
    source: 'services-platform:modules/queries/db/query-exports.table.ts'
    gap: FND-PFM-QRY-064
  - state: STATE-PFM-QRY-006
    transition: 3
    from: running
    to: succeeded
    trigger: The generator finishes and stores the object.
    source: 'services-platform:modules/queries/services/exports/run-query-export.ts'
    gap: FND-PFM-QRY-064
  - state: STATE-PFM-QRY-006
    transition: 4
    from: running
    to: failed
    trigger: A permanent refusal occurs or infrastructure retries are exhausted.
    source: 'services-platform:modules/queries/services/rendering/render-report.ts'
    gap: FND-PFM-QRY-064
  - state: STATE-PFM-QRY-006
    transition: 5
    from: queued
    to: failed
    trigger: Pre-render checks find lost access or a missing report.
    source: 'services-platform:modules/queries/services/rendering/render-report.ts'
    gap: FND-PFM-QRY-064
  - state: STATE-PFM-QRY-006
    transition: 6
    from: succeeded
    to: expired
    trigger: The recorded completion-based expiry passes.
    source: >-
      services-platform:modules/queries/services/persistence/query-exports.ts:completeQueryExport;
      modules/queries/services/exports/query-export-library.ts
    gap: FND-PFM-QRY-064
unproved_rules: []
---

# Download the complete answer to a query: Capability Test Specification

## Coverage

| Rule | Scenario or Finding |
|---|---|
| `BR-CAP-PFM-QRY-014-01` | `ATS-PFM-QRY-014-01` |
| `BR-CAP-PFM-QRY-014-02` | `ATS-PFM-QRY-014-02` |
| `BR-CAP-PFM-QRY-014-03` | `ATS-PFM-QRY-014-03`, `ATS-PFM-QRY-014-11` |
| `BR-CAP-PFM-QRY-014-04` | `ATS-PFM-QRY-014-04` |
| `BR-CAP-PFM-QRY-014-05` | `ATS-PFM-QRY-014-05` |
| `BR-CAP-PFM-QRY-014-06` | `ATS-PFM-QRY-014-06` |
| `BR-CAP-PFM-QRY-014-07` | `ATS-PFM-QRY-014-07`, `ATS-PFM-QRY-014-12` |
| `BR-CAP-PFM-QRY-014-08` | `ATS-PFM-QRY-014-08` |
| `BR-CAP-PFM-QRY-014-09` | `ATS-PFM-QRY-014-09`, `ATS-PFM-QRY-014-13` |
| `BR-CAP-PFM-QRY-014-11` | `ATS-PFM-QRY-014-10` |

## Interactions With Related Capabilities

The combined examples change a source, its audience or its retained state before checking the dependent outcome. Unbound examples remain proposed, and the coverage finding retains design obligations that these examples do not establish.

| Related Capability | Scenario |
|---|---|
| `CAP-PFM-QRY-002` | `ATS-PFM-QRY-014-11` |
| `CAP-PFM-QRY-013` | `ATS-PFM-QRY-014-12` |
| `CAP-PFM-QRY-015` | `ATS-PFM-QRY-014-13` |

## Evidence and Binding Limits

The source revisions pin the inspected assertions. A test binding identifies written assertions, and does not establish a successful run or approval of the expected behavior.
