---
id: CAP-PFM-DJE-011
title: Upload a bounded file for reliable job input
feature: FEAT-PFM-DJE-04
product_area: platform
module: data-job-engine
revision: 1
status: draft
product_intent: confirmed
implementation_evidence: observed
supersedes:
  - CAP-DJE-ENG-007
  - RUL-DJE-ENG-028
  - RUL-DJE-ENG-029
source_sha:
  services-platform: ad3f6a6582843008ed0f5a17b131878d15829af1
  react-console: baf6dae382d6f41dce976c7f848667ccc37e076f
intent_sources:
  - 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#A10'
  - 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#S16'
  - 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#S18'
  - 'services-crm:docs/milestones/CRM-M7-data-operations.md#E-16'
purpose: >-
  Staff and connected systems upload a file directly to storage and retain a completed version for later
  job processing.
outcomes:
  - File parts travel directly to storage.
  - Clients can resume from parts already received.
  - Completion checks declared size and supported digest verification.
concepts:
  - CON-PFM-DJE-003
actors:
  - ACT-PFM-035
  - ACT-PFM-036
  - ACT-PFM-037
surfaces:
  - console
  - api
preconditions: []
postconditions: []
permissions:
  - key: platform.data-jobs.upload
    where_checked: api
    scope: tenant
rules:
  - id: BR-CAP-PFM-DJE-011-01
    proved_by: [ATS-PFM-DJE-011-01, TST-PFM-DJE-164, TST-PFM-DJE-193]
    statement: >-
      Upload operations, including session reads and listings, require upload permission and an
      authorized workspace in the caller’s security context.
    kind: permission
    enforced_at:
      - api
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/data-jobs/tests/e2e/data-jobs.e2e.spec.js#refuses reading a session,
      because which files a tenant loaded is not a read-only user’s business
    origin: none
  - id: BR-CAP-PFM-DJE-011-02
    proved_by: [ATS-PFM-DJE-011-02, TST-PFM-DJE-164, TST-PFM-DJE-178]
    statement: >-
      An upload declaration specifies a slash-free filename of at most 255 characters, a supported
      content type and an exact size from one byte to 5 TiB, with an optional SHA-256 digest; invalid
      declarations create no session.
    kind: validation
    enforced_at:
      - api
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/data-jobs/tests/integration/services/uploads/upload-sessions.integration.spec.js#refuses
      a declaration before it creates anything in S3, so a refused request leaves no upload behind
    origin: none
  - id: BR-CAP-PFM-DJE-011-03
    proved_by: [ATS-PFM-DJE-011-03, TST-PFM-DJE-190]
    statement: >-
      Part size is at least 16 MiB, otherwise the declared size divided by 9,000 rounded upward to a
      whole MiB; clients use that size.
    kind: calculation
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/data-jobs/tests/unit/part-sizing.spec.ts#rounds a part size the division
      leaves between whole MiB up to the next whole MiB
    origin: none
  - id: BR-CAP-PFM-DJE-011-04
    proved_by: [ATS-PFM-DJE-011-04, TST-PFM-DJE-178]
    statement: >-
      Clients can request at most 100 signed part addresses at once, each valid for one hour; requests
      can be repeated only while the session is open.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/data-jobs/tests/integration/services/uploads/upload-sessions.integration.spec.js#refuses
      more than a hundred parts at a time, which is the page the protocol presigns
    origin: none
  - id: BR-CAP-PFM-DJE-011-05
    proved_by: [ATS-PFM-DJE-011-05, ATS-PFM-DJE-011-11, ATS-PFM-DJE-016-14, TST-PFM-DJE-178]
    statement: >-
      Completion supplies every part once with its storage receipt; Rhythm sorts them by part number and
      refuses a size mismatch by settling the session as aborted.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: 'services-platform:modules/data-jobs/services/uploads/upload-sessions.ts:requireCompletedParts'
    origin: none
  - id: BR-CAP-PFM-DJE-011-06
    proved_by: [ATS-PFM-DJE-011-06, ATS-PFM-DJE-011-11, ATS-PFM-DJE-016-14, TST-PFM-DJE-178]
    statement: >-
      Declared digests are measured for files up to 256 MiB, or checked against a digest supplied by
      storage; mismatches abort completion, while larger files without a storage digest retain an
      unverified declaration.
    kind: validation
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-platform:modules/data-jobs/tests/integration/services/uploads/upload-sessions.integration.spec.js#refuses
      a completion whose object is not the file the declared SHA-256 describes, naming sha256;
      services-platform:modules/data-jobs/services/uploads/upload-sessions.ts:verifiedDigestOf
    origin: 'services-crm:docs/milestones/CRM-M7-data-operations.md#E-16'
  - id: BR-CAP-PFM-DJE-011-07
    proved_by: [ATS-PFM-DJE-011-07, ATS-PFM-DJE-011-10, ATS-PFM-DJE-014-10, TST-PFM-DJE-178]
    statement: >-
      Open sessions settle as completed, aborted or expired; completion cannot be aborted, repeated abort
      is harmless and reads settle storage-abandoned sessions as expired.
    kind: lifecycle
    enforced_at:
      - service
      - database
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/data-jobs/tests/integration/services/uploads/upload-sessions.integration.spec.js#refuses
      to abort a completed session, whose object a job may already be reading
    origin: none
  - id: BR-CAP-PFM-DJE-011-08
    proved_by: [ATS-PFM-DJE-011-08, TST-PFM-DJE-178]
    statement: >-
      Completion with missing storage parts returns a descriptive refusal and leaves the session open for
      repair.
    kind: policy
    enforced_at:
      - service
    must_prove: true
    provenance: test
    source: >-
      services-platform:modules/data-jobs/tests/integration/services/uploads/upload-sessions.integration.spec.js#refuses
      in words a completion naming a part S3 does not hold, rather than failing as a server fault
    origin: 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#S18'
  - id: BR-CAP-PFM-DJE-011-09
    proved_by: [ATS-PFM-DJE-011-09, ATS-PFM-DJE-011-10, ATS-PFM-DJE-014-10, TST-PFM-DJE-164, TST-PFM-DJE-178]
    statement: >-
      Session metadata omits internal storage coordinates; another workspace’s session is
      indistinguishable from a missing session, while signed part addresses provide the upload
      destination.
    kind: invariant
    enforced_at:
      - service
    must_prove: true
    provenance: observed
    source: >-
      services-platform:modules/data-jobs/tests/integration/services/uploads/upload-sessions.integration.spec.js#refuses
      to complete another tenant’s session, and makes no S3 call for it;
      services-platform:modules/data-jobs/services/uploads/upload-sessions.ts:presignUploadParts
    origin: 'services-platform:docs/milestones/plans/PLAT-M19-plan.md#S11'
  - id: BR-CAP-PFM-DJE-011-10
    proved_by: []
    statement: >-
      Storage lifecycle policies retain ordinary uploads for 30 days unless tagged to keep, chunks for 14
      days and exports for seven days, including stored versions, and abandon incomplete parts after two
      days.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-platform:docs/data-jobs/uploads.md#Retention'
    origin: none
  - id: BR-CAP-PFM-DJE-011-11
    proved_by: [ATS-PFM-DJE-011-12, ATS-PFM-DJE-017-10]
    statement: >-
      Workspace deletion removes upload records and stored file versions, while reset preserves uploaded
      input.
    kind: lifecycle
    enforced_at:
      - service
    must_prove: false
    provenance: documented
    source: 'services-platform:docs/data-jobs/uploads.md#Deletion'
    origin: none
scenarios:
  - id: SC-CAP-PFM-DJE-011-01
    given: A producer has a file containing exactly 200 GiB.
    when: It requests an upload session.
    then: >-
      The part-sizing calculation gives 8,534 parts at 24 MiB, which the client can upload directly to
      storage.
    provenance: inferred
    source: 'services-platform:modules/data-jobs/db/data-job-uploads.table.ts:partSizeOf'
    note: >-
      At 200 × 1024³ bytes, rounding ceil(size / 9000) to MiB gives 24 MiB; ceil(size / partSize) is
      8534.
edge_cases: []
constraints: []
depends_on: []
related:
  - CAP-PFM-DJE-014
  - CAP-PFM-DJE-016
journeys:
  - JNY-PFM-DJE-001
  - JNY-PFM-DJE-004
  - JNY-PFM-DJE-005
open_questions:
  - OQ-PFM-DJE-001
evidence:
  - >-
    Reviewed evidence: services-platform (ad3f6a65): docs/data-jobs/uploads.md;
    modules/data-jobs/aws/api/data-jobs.routes.ts; services/uploads/upload-sessions.ts,
    upload-storage.ts; services/persistence/uploads.ts; db/data-job-uploads.table.ts;
    data-jobs.settings.ts; aws/resources/data-jobs-storage.s3.ts; tests
    upload-sessions.integration.spec.js, uploads.integration.spec.js,
    uploads.queries.integration.spec.js, data-jobs.e2e.spec.js, unit/part-sizing.spec.ts,
    unit/upload-permission.spec.ts.
  - >-
    Reviewed evidence: react-console (baf6dae3): lib/api/data-jobs/uploads.ts, multipart.ts, sha256.ts;
    components/data-management/data-jobs/new/upload-progress.tsx.
controls: [TC-AVL-004, TC-AZ-001, TC-AZ-002, TC-AZ-003, TC-AZ-004, TC-CHG-001, TC-CHG-002, TC-CHG-003, TC-CHG-004, TC-CHG-005, TC-CHG-006, TC-CHG-007, TC-CRY-001, TC-CRY-002, TC-CRY-003, TC-CRY-004, TC-DAT-001, TC-DAT-002, TC-DAT-003, TC-DAT-004, TC-DAT-005, TC-IAM-001, TC-IAM-002, TC-IAM-004, TC-IAM-005, TC-LOG-001, TC-LOG-004, TC-LOG-005, TC-PI-001, TC-PI-002, TC-PI-003, TC-PI-004, TC-PI-005, TC-TEN-001, TC-TEN-002, TC-TEN-003, TC-VEN-001, TC-VEN-002, TC-VEN-003]
exercised_by: [FAS-PFM-DJE-04-01, FAS-PFM-DJE-04-03, FAS-PFM-DJE-04-04]
suites: [TST-PFM-DJE-164, TST-PFM-DJE-178, TST-PFM-DJE-190, TST-PFM-DJE-193]
---

# Upload a bounded file for reliable job input

## Purpose

Staff and connected systems upload a file directly to storage and retain a completed version for later job processing.

## How It Works

A client declares the file name, type, size and any digest when opening a session. Rhythm chooses the part size and count. The client obtains signed destinations and sends parts directly to storage. It can inspect received parts and request new addresses when resuming. Completion supplies the receipts for all parts. Rhythm checks the assembled size and, where supported, the declared digest. A size or digest mismatch settles the session as aborted. Missing parts leave the session open so the client can repair its input. Completed uploads identify the stored version used by jobs. The product should decide how large-file verification becomes dependable before migration readiness relies on it.

## Relationships

- File processing reads the completed stored version.
- Migration registration compares package declarations with upload evidence.
