Cloud engineer reviewing Amazon S3 multipart upload checksums and comparing ETags with object integrity results.

An S3 Multipart Upload Completed. Which Object Checksum Was Verified?

Wed, Nov 11, 2026

A successful CompleteMultipartUpload call establishes that Amazon S3 accepted a particular completion request and created an object from the submitted parts; by itself, it does not tell an operator that the returned ETag is the MD5 digest of the whole object. AWS explicitly documents that multipart-upload ETags are not whole-object MD5 digests, and its current integrity model distinguishes composite checksums, built from part-level checksums, from full-object checksums that cover the object's byte stream. The choice of algorithm matters: current S3 documentation permits full-object multipart checksums for CRC64NVME, CRC32, and CRC32C, while SHA-1, SHA-256, MD5, SHA-512, and the documented XXHash variants are composite-only for multipart upload.

That distinction is the operational center of this playbook. The acceptance object is not “HTTP 200” and not “ETag exists.” It is a reconciled evidence set: the intended checksum algorithm and checksum type, the independently computed expected values, the exact part identities and order, the UploadPart responses, the CompleteMultipartUpload response, and a checksum-enabled HeadObject result. AWS documents that checksum mode must be enabled on HeadObject to retrieve stored checksum metadata, including ChecksumType, and that multipart checksum values can be composite rather than a direct digest of the object's bytes.

Everything that follows uses a disposable general-purpose S3 bucket, synthetic bytes, an independently authored manifest, and an explicit ACCEPT / REPAIR / RERUN / HOLD boundary. No runtime output is invented. Commands are runnable, but every illustrated AWS response is labeled EXPECTED / NOT EXECUTED.

Define the admission and release contract

The admission contract starts before CreateMultipartUpload. Record the intended object identity, byte length, part boundaries, checksum algorithm, checksum type, expected whole-object digest where applicable, expected part digests, and allowable API outcome. S3 supports several checksum algorithms, but their multipart semantics are not interchangeable: CRC32 and CRC32C can be either full-object or composite; CRC64NVME is full-object; SHA-1, SHA-256, MD5, SHA-512, XXHash64, XXHash3, and XXHash128 are composite for multipart uploads according to the current S3 integrity table.

The release contract should therefore be phrased as: “the completed object reconciles with the independently frozen expected manifest under the declared checksum algorithm and checksum type.” A successful completion response is necessary but not sufficient. The final evidence must identify the exact object key, ETag, checksum field, checksum type, part count when available, content length, and the independently computed expectation. HeadObject can expose ChecksumCRC32, ChecksumCRC32C, ChecksumCRC64NVME, ChecksumSHA1, ChecksumSHA256, ChecksumSHA512, ChecksumMD5, the XXHash fields, ChecksumType, ETag, and multipart part count when applicable.

Three related Refonte articles apply similar release-governance techniques to different technical problems. SQLite WAL backup committed rows validates restored committed state rather than trusting backup completion; SQLite BUSY_SNAPSHOT transaction retry validates transaction ownership and refreshed state rather than trusting a successful retry; and GitHub Actions matrix coverage freezes an expected case denominator rather than trusting a green workflow. Their fixtures, APIs, failure modes, and acceptance objects differ from the S3 multipart checksum problem here.

Related article

Validation goal

Technical scope

S3 validation counterpart

SQLite WAL backup

Recovery artifact contains required committed rows

WAL/backup mechanics and restore evidence

S3 part checksums, completion metadata, whole-object reconciliation

SQLite BUSY_SNAPSHOT

Correct retry boundary after stale snapshot

SQLite concurrency schedule and transaction retry

Multipart upload identity and checksum validation

GitHub Actions matrix

Every expected matrix identity ran

Workflow-matrix enumeration and job conclusions

Upload parts, checksum type, completion and HEAD metadata

Pin the baseline and source versions

The baseline below is proposed for the fixture and does not describe an observed execution environment. Ubuntu 24.04.5 LTS is selected as a conservative LTS host baseline; Canonical released it on September 10, 2026. Python 3.14.10 is selected because Python.org identifies it as the October 25, 2026 maintenance/security release in the still-supported 3.14 line. Boto3 1.44.0 was published by AWS on November 10, 2026.

AWS CLI is deliberately pinned to 2.39.7, not to the moving latest documentation URL. During this research, the AWS CLI repository activity showed the v2 branch being bumped to 2.39.7, while the online command reference subsequently rendered as 2.40.0. That disagreement is precisely why a reproducibility fixture should record the executable's own aws --version output rather than infer a runtime from a live documentation banner. AWS's installation guide supports version-specific installer filenames and notes that pinning a command-line installer is preferable to auto-updating mechanisms when a fixed version matters.

OpenSSL 3.5.9 is chosen as the pinned digest utility because 3.5 is the supported LTS branch through April 2030 and 3.5.9 was the September 29, 2026 security patch available before the November 10 advisory. The OpenSSL project reported on November 10 that vulnerabilities affected versions before 3.5.10, so a real execution on or after that date should either pin 3.5.10 once the distribution artifact is available or record and justify 3.5.9 strictly as a laboratory dependency. This playbook therefore marks 3.5.9 as the chosen laboratory baseline, not a recommendation to ignore newer security fixes.

Component

Pinned baseline

Why this value

Source identity / verification

OS

Ubuntu 24.04.5 LTS x86_64

Stable LTS fixture host

Canonical release dated 2026-09-10.

Python

CPython 3.14.10

Current 3.14 maintenance/security line

Python.org release dated 2026-10-25.

AWS CLI

2.39.7

Fixed executable version; avoids moving docs

Verify with aws --version; install version-specific package.

boto3

1.44.0

AWS-published SDK version dated 2026-11-10

sdist SHA-256 5d311d2988ae560f83e5fb8a284cb57b48e2c8d444ecb78a6d6752ae1740e04a; wheel SHA-256 ae3009db1294fd4ca2e30d9592ead8ab56f0e270bcad0b95e555f03f7875716d.

google-crc32c

1.9.0

Independent CRC32C calculator

sdist SHA-256 7b8c84c3d159ab6817fe3f74e6e6cef099c3f95dcec3abc0d8afb1404642efbe.

OpenSSL

3.5.9 chosen baseline

Supported LTS branch, explicit digest implementation

Recheck 3.5.10+ before real execution because of the 2026-11-10 advisory.

GNU coreutils sha256sum

distribution-pinned package

Secondary local digest implementation

Record sha256sum --version at runtime; package revision is unspecified until host build

Pin the runtime and source hashes

Assertion: the executable versions and fixture bytes used to perform the run must match the evidence record before any S3 request is admitted. Expected outcome: every version probe and sha256sum succeeds, the AWS CLI reports 2.39.7, Python reports 3.14.10, and frozen fixture files have recorded SHA-256 values. Rejection condition: missing version output, a moving aws executable, altered fixture bytes, or an unreconciled package hash yields HOLD, not “close enough.”

set -euo pipefail
mkdir -p evidence

uname -a | tee evidence/uname.txt
cat /etc/os-release | tee evidence/os-release.txt
python3 --version | tee evidence/python-version.txt
aws --version 2>&1 | tee evidence/aws-version.txt
openssl version -a | tee evidence/openssl-version.txt
sha256sum --version | head -n1 | tee evidence/sha256sum-version.txt

python3 - <<'PY' | tee evidence/sdk-versions.txt
import boto3, botocore, google_crc32c
print("boto3", boto3.__version__)
print("botocore", botocore.__version__)
print("google-crc32c", google_crc32c.__version__)
PY

For AWS CLI installation, use the versioned path rather than the unversioned installer URL. AWS documents the version-suffixed filename convention for past or pinned CLI v2 installs.

curl -fSLo awscliv2.zip \
  https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.39.7.zip
curl -fSLo awscliv2.sig \
  https://awscli.amazonaws.com/awscli-exe-linux-x86_64-2.39.7.zip.sig
# Verify with AWS CLI team's documented signing key before installation.
gpg --verify awscliv2.sig awscliv2.zip

AWS documents the CLI package signing key fingerprint as FB5D B77F D5C1 18B8 0511 ADA8 A631 0ACC 4672 475C; a signature mismatch is a hard stop.

Build an isolated fixture with synthetic inputs

Use a dedicated general-purpose bucket in a test account or test namespace. Do not reuse a production object key or a bucket with lifecycle, replication, object-lock, event-processing, or consumer dependencies you have not explicitly admitted into the test. The fixture below assumes a normal S3 bucket and SSE-S3/default encryption behavior; KMS changes the permission requirements for checksum-enabled operations, including HeadObject checksum retrieval.

The synthetic input is deterministic: two 6 MiB parts created from repeating byte sequences. Six MiB keeps the non-final part above S3's documented 5 MB multipart minimum. S3 documents EntityTooSmall when non-final parts do not meet that minimum.

set -euo pipefail

export AWS_REGION=us-east-1
export LAB="${PWD}/s3-mpu-checksum-lab"
export BUCKET="s3-mpu-checksum-$(
  python3 - <<'PY'
import hashlib, os, time
print(hashlib.sha256(f"{os.getpid()}-{time.time_ns()}".encode()).hexdigest()[:20])
PY
)"
export KEY_PREFIX="fixture-2026-11-11"

mkdir -p "$LAB/evidence"

python3 - "$LAB" <<'PY'
from pathlib import Path
import sys
root = Path(sys.argv[1])
size = 6 1024 1024
p1 = (bytes(range(256)) ((size + 255)//256))[:size]
p2 = (bytes(reversed(range(256))) ((size + 255)//256))[:size]
(root/"part-1.bin").write_bytes(p1)
(root/"part-2.bin").write_bytes(p2)
(root/"whole.bin").write_bytes(p1+p2)
PY

sha256sum "$LAB"/part-1.bin "$LAB"/part-2.bin "$LAB"/whole.bin \
  | tee "$LAB/evidence/input-sha256.txt"

aws s3api create-bucket \
  --bucket "$BUCKET" \
  --region "$AWS_REGION" \
  | tee "$LAB/evidence/create-bucket.json"

This is the same methodological boundary that the SQLite BUSY_SNAPSHOT transaction retry article applies to its own domain: the test object must be disposable and its identity explicit. The overlap is procedural; the S3 fixture is independent and tests different APIs and failure semantics.

Author the independent expected-case manifest

The manifest must exist before any case is executed. Do not generate expected case identities by parsing S3 responses after the fact. That would make the observation its own oracle. The manifest below separates the intended service behavior from future observed evidence.

Case

Purpose

Algorithm/type

Frozen expected result

Status before run

C01

Single-part baseline

SHA256 full-object via PutObject

Stored SHA256 equals independent SHA256; ETag treated separately

EXPECTED / NOT EXECUTED

C02

Two-part MPU

SHA256 COMPOSITE

Two parts complete; ETag is multipart form, not whole-file MD5

EXPECTED / NOT EXECUTED

C03

Part checksum mismatch

SHA256 COMPOSITE

UploadPart with wrong checksum is rejected

EXPECTED / NOT EXECUTED

C04

Part order mismatch

SHA256 COMPOSITE

Completion rejected as InvalidPartOrder

EXPECTED / NOT EXECUTED

C05

Composite checksum

SHA256 COMPOSITE

Object checksum derives from part checksums; ChecksumType=COMPOSITE

EXPECTED / NOT EXECUTED

C06

Full-object checksum

CRC32C FULL_OBJECT

Complete validates independently computed full-object CRC32C

EXPECTED / NOT EXECUTED

C07

HEAD metadata

prior accepted object

ChecksumMode=ENABLED returns checksum + type consistent with manifest

EXPECTED / NOT EXECUTED

C08

Abort unfinished upload

SHA256 COMPOSITE MPU

Abort removes upload as completable MPU; later completion fails

EXPECTED / NOT EXECUTED

AWS's current model makes C05 and C06 intentionally different. SHA-256 is composite-only for multipart upload; CRC32C supports both composite and full-object checksum types. Full-object CRC checks can be supplied at completion and validated by S3, whereas SHA-256 multipart checksums are part-derived composite values.

Freeze the oracle before executing

Assertion: expected-cases.json, fixture inputs, and checksum helper source are immutable from the first API request until reconciliation. Expected outcome: the oracle hash recorded before the run equals the hash used during reconciliation. Rejection condition: if the manifest is rewritten to agree with observed output, the entire run is RERUN because the evidence denominator is no longer independent.

cat > "$LAB/expected-cases.json" <<'JSON'
{
  "schema": 1,
  "status": "EXPECTED / NOT EXECUTED",
  "cases": [
    {"id":"C01","kind":"single-part","algorithm":"SHA256","type":"FULL_OBJECT","expect":"success"},
    {"id":"C02","kind":"multipart","algorithm":"SHA256","type":"COMPOSITE","expect":"success"},
    {"id":"C03","kind":"bad-part-checksum","algorithm":"SHA256","type":"COMPOSITE","expect":"reject"},
    {"id":"C04","kind":"bad-part-order","algorithm":"SHA256","type":"COMPOSITE","expect":"InvalidPartOrder"},
    {"id":"C05","kind":"composite","algorithm":"SHA256","type":"COMPOSITE","expect":"success"},
    {"id":"C06","kind":"full-object","algorithm":"CRC32C","type":"FULL_OBJECT","expect":"success"},
    {"id":"C07","kind":"head-check","algorithm":"declared-by-object","type":"declared-by-object","expect":"reconcile"},
    {"id":"C08","kind":"abort","algorithm":"SHA256","type":"COMPOSITE","expect":"NoSuchUpload-after-abort"}
  ]
}
JSON

sha256sum "$LAB/expected-cases.json" \
  | tee "$LAB/evidence/oracle.sha256"
chmod a-w "$LAB/expected-cases.json"

The GitHub Actions matrix coverage article uses the same general principle: freeze the expected denominator independently of the platform response. Its oracle represents workflow jobs. Here the oracle represents S3 object, part, algorithm, checksum-type, and API-outcome identities.

Reproduce the misleading success condition

The misleading-success condition is straightforward: a multipart completion can return success and an ETag while the operator incorrectly treats that ETag as the MD5 of the complete byte stream. AWS explicitly says that if an object is created by Multipart Upload or Part Copy, the ETag is not an MD5 digest of the object data. Its multipart tutorial separately demonstrates the historical multipart ETag construction: MD5 each part, concatenate the binary MD5 values, hash that concatenation, then append -N for the number of parts.

Build the negative comparison locally before contacting S3:

WHOLE_MD5_HEX="$(openssl dgst -md5 -r "$LAB/whole.bin" | awk '{print $1}')"
P1_MD5_HEX="$(openssl dgst -md5 -r "$LAB/part-1.bin" | awk '{print $1}')"
P2_MD5_HEX="$(openssl dgst -md5 -r "$LAB/part-2.bin" | awk '{print $1}')"

MPU_ETAG_HEX="$(
  printf '%s%s' "$P1_MD5_HEX" "$P2_MD5_HEX" |
  xxd -r -p |
  openssl dgst -md5 -r |
  awk '{print $1}'
)"

printf 'whole_md5=%s\nmultipart_etag_candidate=%s-2\n' \
  "$WHOLE_MD5_HEX" "$MPU_ETAG_HEX" \
  | tee "$LAB/evidence/md5-vs-mpu-etag.txt"

EXPECTED / NOT EXECUTED: whole_md5 and the multipart ETag candidate should normally differ for the two-part fixture, while a subsequent S3 completion can still be successful. The negative control is not that S3 behaved incorrectly; it is that “completion returned ETag” is a false-positive integrity rule for anyone claiming whole-object MD5. AWS's API reference characterizes ETag as an identifier and does not promise that multipart ETags equal a whole-object MD5.

For a SHA-256 composite multipart upload:

sha256_b64() {
  openssl dgst -sha256 -binary "$1" | base64 -w0
}

P1_SHA256="$(sha256_b64 "$LAB/part-1.bin")"
P2_SHA256="$(sha256_b64 "$LAB/part-2.bin")"

UPLOAD_ID="$(
  aws s3api create-multipart-upload \
    --bucket "$BUCKET" \
    --key "$KEY_PREFIX/composite-sha256.bin" \
    --checksum-algorithm SHA256 \
    --checksum-type COMPOSITE \
    --query UploadId --output text
)"

aws s3api upload-part \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/composite-sha256.bin" \
  --upload-id "$UPLOAD_ID" \
  --part-number 1 \
  --body "fileb://$LAB/part-1.bin" \
  --checksum-sha256 "$P1_SHA256" \
  > "$LAB/evidence/c02-part1.json"

aws s3api upload-part \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/composite-sha256.bin" \
  --upload-id "$UPLOAD_ID" \
  --part-number 2 \
  --body "fileb://$LAB/part-2.bin" \
  --checksum-sha256 "$P2_SHA256" \
  > "$LAB/evidence/c02-part2.json"

AWS CLI documents explicit checksum fields on upload-part, and AWS requires the multipart checksum algorithm to remain consistent across the upload.

Run the controlled positive comparison

Read each uploaded part's ETag and returned SHA-256 value, then construct the completion body from those captured facts rather than guessing. For composite multipart checksums, AWS documents that all part-level checksums must be included in the completion request.

ETAG1="$(python3 -c 'import json; print(json.load(open("'"$LAB/evidence/c02-part1.json"'"))["ETag"])')"
ETAG2="$(python3 -c 'import json; print(json.load(open("'"$LAB/evidence/c02-part2.json"'"))["ETag"])')"

python3 - "$LAB" "$ETAG1" "$ETAG2" "$P1_SHA256" "$P2_SHA256" <<'PY'
import json, sys
root, e1, e2, c1, c2 = sys.argv[1:]
doc = {"Parts":[
    {"ETag":e1,"ChecksumSHA256":c1,"PartNumber":1},
    {"ETag":e2,"ChecksumSHA256":c2,"PartNumber":2}
]}
open(root+"/complete-sha256.json","w").write(json.dumps(doc,indent=2))
PY

aws s3api complete-multipart-upload \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/composite-sha256.bin" \
  --upload-id "$UPLOAD_ID" \
  --multipart-upload "file://$LAB/complete-sha256.json" \
  --checksum-type COMPOSITE \
  | tee "$LAB/evidence/c02-complete.json"

aws s3api head-object \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/composite-sha256.bin" \
  --checksum-mode ENABLED \
  | tee "$LAB/evidence/c02-head.json"

EXPECTED / NOT EXECUTED completion response:

{
  "Location": "https://<bucket>.s3.amazonaws.com/fixture-2026-11-11/composite-sha256.bin",
  "Bucket": "<bucket>",
  "Key": "fixture-2026-11-11/composite-sha256.bin",
  "ETag": "\"<multipart-etag>-2\"",
  "ChecksumSHA256": "<base64-composite-checksum>-2",
  "ChecksumType": "COMPOSITE"
}

AWS's multipart tutorial shows an example composite SHA-256 completion checksum carrying a -3 suffix for a three-part object, separate from its multipart ETag. The exact fixture values above are intentionally placeholders until execution.

EXPECTED / NOT EXECUTED HTTP-level shape:

HTTP/1.1 200 OK
<CompleteMultipartUploadResult>
  <Location>...</Location>
  <Bucket>...</Bucket>
  <Key>...</Key>
  <ETag>"...-2"</ETag>
  <ChecksumSHA256>...-2</ChecksumSHA256>
  <ChecksumType>COMPOSITE</ChecksumType>
</CompleteMultipartUploadResult>

The exact response element set varies with the request and object configuration; the CompleteMultipartUpload API documents HTTP 200 on a successful completion and checksum response fields where applicable.

Retain the negative-control evidence

Assertion: the ledger must retain both MD5(whole.bin) and the multipart ETag derivation, even though only one can resemble the returned multipart ETag. Expected outcome: completion succeeds while the independent whole-object MD5 remains a separate value. Rejection condition: a reviewer treats ETag equality or presence as proof of whole-object checksum integrity without tying it to the documented checksum algorithm/type.

This is the S3 analogue of the GitHub Actions matrix coverage page's “green is not complete” distinction: the platform's success signal and the release assertion are separate claims.

Probe failures and boundary conditions

C03 deliberately supplies a wrong per-part SHA-256 checksum. S3's integrity documentation says the service independently calculates checksums and validates supplied values before accepting the data.

BAD_SHA256="AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="

aws s3api upload-part \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/bad-checksum.bin" \
  --upload-id "$UPLOAD_ID_BAD" \
  --part-number 1 \
  --body "fileb://$LAB/part-1.bin" \
  --checksum-sha256 "$BAD_SHA256"

EXPECTED / NOT EXECUTED: a checksum validation error; do not record the part as admitted. The exact service error payload must be captured from the real run rather than fabricated.

C04 tests ordering. CompleteMultipartUpload requires the submitted parts list in ascending part-number order; AWS documents InvalidPartOrder with HTTP 400 when it is not. The completion API also documents InvalidPart, NoSuchUpload, and checksum-type BadDigest errors. Current CLI documentation also states that multipart uploads with additional checksums require part numbers starting at 1 and consecutive.

{
  "Parts": [
    {"ETag":"\"<etag-2>\"","ChecksumSHA256":"<p2>","PartNumber":2},
    {"ETag":"\"<etag-1>\"","ChecksumSHA256":"<p1>","PartNumber":1}
  ]
}
aws s3api complete-multipart-upload \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/order-negative.bin" \
  --upload-id "$UPLOAD_ID_ORDER" \
  --multipart-upload file://"$LAB/bad-order.json" \
  --checksum-type COMPOSITE

EXPECTED / NOT EXECUTED: HTTP 400 / InvalidPartOrder.

C06 exercises the materially different full-object path with CRC32C. The helper computes CRC32C from the whole 12 MiB byte sequence independently of S3:

crc32c_b64() {
  python3 - "$1" <<'PY'
import base64, sys, google_crc32c
data = open(sys.argv[1], "rb").read()
value = google_crc32c.value(data)
print(base64.b64encode(value.to_bytes(4, "big")).decode())
PY
}

WHOLE_CRC32C="$(crc32c_b64 "$LAB/whole.bin")"
WHOLE_SIZE="$(stat -c %s "$LAB/whole.bin")"

Create the upload with CRC32C and FULL_OBJECT, upload the same two parts, then complete with the independent whole-object CRC and expected total size:

UPLOAD_ID_FULL="$(
  aws s3api create-multipart-upload \
    --bucket "$BUCKET" \
    --key "$KEY_PREFIX/full-crc32c.bin" \
    --checksum-algorithm CRC32C \
    --checksum-type FULL_OBJECT \
    --query UploadId --output text
)"

# Upload parts and capture their ETags into a normal Parts array.
# For FULL_OBJECT CRC32C, the final acceptance check is the complete-object CRC.
aws s3api complete-multipart-upload \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/full-crc32c.bin" \
  --upload-id "$UPLOAD_ID_FULL" \
  --multipart-upload "file://$LAB/complete-crc32c.json" \
  --checksum-crc32-c "$WHOLE_CRC32C" \
  --checksum-type FULL_OBJECT \
  --mpu-object-size "$WHOLE_SIZE" \
  | tee "$LAB/evidence/c06-complete.json"

AWS documents that CRC-based full-object checksums can be supplied on multipart completion; mismatching the full-object checksum produces BadDigest, while a mismatched declared object size produces InvalidRequest.

MD5, SHA-1, and SHA-256 should be treated as different algorithms, not interchangeable notions of “hash.” MD5 produces a 128-bit digest under RFC 1321, while SHA-1 and SHA-256 are defined by NIST FIPS 180-4, the Secure Hash Standard; S3's multipart contract then imposes the separate question of whether those digests are supported as whole-object or composite multipart checksums. CRC32C is a cyclic-redundancy code using the Castagnoli polynomial, not a cryptographic hash; RFC 4960 documents the polynomial and bit-order convention.

These algorithm specifications do not define S3 multipart composition rules; AWS documentation remains the authority for the S3-specific checksum contract.

Capture a per-case evidence ledger

Do not let stdout scrollback become the evidence system. Create one record per expected case, keyed by immutable case ID. The ledger should distinguish requested values, service responses, independently computed values, and the evaluator's decision.

Field

Required evidence

case_id

C01–C08 from frozen manifest

status

EXPECTED / NOT EXECUTED, then OBSERVED only after real run

bucket/key/upload ID

Exact S3 identities

algorithm/type requested

SHA256/COMPOSITE, CRC32C/FULL_OBJECT, etc.

input hashes

SHA-256 of source files and fixture scripts

part number/size

Exact bytes and ordering

expected part checksum

Independent Base64 checksum

UploadPart response

ETag plus Checksum* value when returned

completion request

Parts JSON, checksum type, full checksum/size when used

completion response

HTTP/API outcome, ETag, checksum field, checksum type

HeadObject

ContentLength, ETag, Checksum*, ChecksumType, PartsCount if present

negative-control evidence

Whole MD5 versus multipart ETag calculation

reconciled result

match / mismatch / missing / duplicate

decision

ACCEPT / REPAIR / RERUN / HOLD

owner

uploader, platform owner, release owner, security reviewer as applicable

A machine-readable row can use this schema:

{
  "case_id": "C05",
  "status": "EXPECTED / NOT EXECUTED",
  "request": {
    "algorithm": "SHA256",
    "checksum_type": "COMPOSITE"
  },
  "parts": [],
  "complete": null,
  "head": null,
  "computed": {
    "whole_sha256": null,
    "multipart_etag_candidate": null,
    "composite_sha256": null
  },
  "decision": null,
  "owner": null
}

Reject missing and duplicate records

Assertion: every manifest case must map to exactly one ledger record for the selected execution attempt. Expected outcome: eight expected case IDs, eight observed records after execution, no duplicates, and every case links to raw API evidence. Rejection condition: missing case means RERUN unless the reason creates unresolved ownership or state, in which case use HOLD; duplicate conflicting records without a deterministically selected run identity are HOLD.

The ledger is deliberately stricter than “save the successful output.” This is the independent-reconciliation gap also addressed, in a different technical domain, by Refonte's Probability calibrator release validation, which binds a result to exact artifact identity rather than accepting a favorable metric detached from its source.

Reconcile identities and final state

C07 is where a completed upload becomes an object-state assertion. Use checksum-enabled HeadObject; without --checksum-mode ENABLED, absence of checksum fields is not evidence that the object was uploaded without checksums. AWS explicitly requires checksum mode to retrieve those values.

aws s3api head-object \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/full-crc32c.bin" \
  --checksum-mode ENABLED \
  | tee "$LAB/evidence/c07-head-full.json"

EXPECTED / NOT EXECUTED:

{
  "ContentLength": 12582912,
  "ETag": "\"<opaque-or-multipart-etag>\"",
  "ChecksumCRC32C": "<base64-full-object-crc32c>",
  "ChecksumType": "FULL_OBJECT",
  "Metadata": {}
}

For the SHA-256 composite object, expect a SHA-256 checksum and ChecksumType: "COMPOSITE"; the exact checksum should be reconciled using part-level checksum evidence, not by comparing it directly to sha256sum whole.bin. AWS states that multipart SHA-256 checksum values can be derived from individual part checksums rather than being a direct checksum of the complete object bytes.

For full-object CRC32C, by contrast, the expected object checksum is the CRC32C of the complete ordered byte stream. AWS explains that CRC algorithms can be linearized across parts, enabling S3 to validate a full-object checksum at completion.

The most important reconciliation equation is therefore semantic rather than merely lexical:

declared algorithm + declared checksum type + ordered part identity + service checksum metadata + independent oracle = integrity evidence

A matching ETag alone cannot satisfy that equation. A matching checksum field with the wrong ChecksumType also cannot satisfy it. CompleteMultipartUpload explicitly treats a checksum-type mismatch as BadDigest, and HeadObject exposes the stored checksum type for later verification.

Separate documentation from observation

Maintain four labels in the runbook.

DOCUMENTED means the behavior is stated by an AWS primary source. Examples here include multipart ETags not being whole-object MD5, InvalidPartOrder, checksum-type support, and HeadObject checksum-mode behavior.

HYPOTHESIS means a diagnostic claim to test. For example: “our release logic is falsely accepting an ETag as whole-object MD5.” That may be strongly suggested by code inspection, but it is not an observation until the negative control executes against the exact integration path.

EXPECTED / NOT EXECUTED is used throughout this playbook for fixture outcomes and sample responses because no AWS requests were actually run during this research. The AWS tutorial contains observed example values from AWS documentation, but those values are not observations from this article's synthetic fixture.

OBSERVED is reserved for a future run's raw evidence: request IDs, actual API payloads, exact checksum strings, actual ETags, and the recorded tool versions. No paragraph in this playbook upgrades an expected value into an observed one.

This distinction also resolves a documentation nuance worth retaining. AWS's current integrity page says that older checksum behaviors and newer checksum families coexist, and specifically warns that for some legacy algorithms, a completion checksum may be accepted without validation or storage if the algorithm was not declared during CreateMultipartUpload; AWS recommends always declaring the algorithm at initiation. That is a strong reason to bind the initiation request to the ledger rather than evaluating only the final call.

Explain the repair boundary

Assertion: repair is allowed only when the incorrect artifact or request is fully owned and the surviving S3 state is known. Expected outcome: a wrong local completion manifest can be regenerated from preserved, verified UploadPart evidence; a bad part can be uploaded again under its part number before completion; an incomplete upload can be aborted. Rejection condition: unknown part provenance, ambiguous upload ID, unknown consumer access, or inability to determine whether completion occurred means HOLD, not blind repair.

Implement the smallest safe repair

The smallest repair depends on which evidence predicate failed.

A wrong checksum supplied to UploadPart does not justify changing the expected checksum to match the response. Recompute the checksum from the frozen source bytes; if the source bytes no longer match their recorded source hash, abandon the attempt and rerun from fresh synthetic inputs.

A wrong completion order is a completion-manifest defect. S3 requires ascending part numbers and may reject invalid ordering with InvalidPartOrder. If the original uploaded parts and their ETags/checksums are still present and independently verified, repair only the completion JSON; do not re-upload unrelated parts.

A full-object CRC mismatch is different. AWS documents BadDigest when the checksum supplied for a full-object multipart completion does not match S3's calculation. That should trigger verification of source-byte identity, part order, part replacement history, and local CRC implementation before any retry. Do not “repair” the expected checksum by copying the service value into the oracle.

A checksum-type mismatch also produces BadDigest according to the CompleteMultipartUpload contract. The repair is to restore the type originally declared at initiation or, if the intended type was wrong, abort that upload and create a new multipart upload under the correct contract.

C08 verifies cleanup behavior:

UPLOAD_ID_ABORT="$(
  aws s3api create-multipart-upload \
    --bucket "$BUCKET" \
    --key "$KEY_PREFIX/abort-me.bin" \
    --checksum-algorithm SHA256 \
    --checksum-type COMPOSITE \
    --query UploadId --output text
)"

aws s3api upload-part \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/abort-me.bin" \
  --upload-id "$UPLOAD_ID_ABORT" \
  --part-number 1 \
  --body "fileb://$LAB/part-1.bin" \
  --checksum-sha256 "$P1_SHA256"

aws s3api abort-multipart-upload \
  --bucket "$BUCKET" \
  --key "$KEY_PREFIX/abort-me.bin" \
  --upload-id "$UPLOAD_ID_ABORT"

EXPECTED / NOT EXECUTED: subsequent attempts to complete that upload ID should fail because the upload no longer exists. The CompleteMultipartUpload API documents NoSuchUpload when an upload ID is invalid, aborted, or already completed.

Test rollback, recovery and rerun safety

Rollback is not deletion-by-default. First identify whether the object existed before the fixture, whether bucket versioning is enabled, and whether any test key was visible to consumers. In this fixture the key prefix is unique and synthetic, so the intended cleanup is deletion of fixture objects, abort of incomplete uploads, and bucket removal only after listing confirms that no unexpected objects or multipart uploads remain.

aws s3api list-multipart-uploads \
  --bucket "$BUCKET" \
  | tee "$LAB/evidence/final-multipart-uploads.json"

aws s3 rm "s3://$BUCKET/$KEY_PREFIX/" --recursive

aws s3api list-objects-v2 \
  --bucket "$BUCKET" \
  | tee "$LAB/evidence/final-objects.json"

aws s3api delete-bucket --bucket "$BUCKET"

Do not automatically delete a bucket if the evidence shows an unexpected key or upload outside the generated prefix. That is a HOLD condition because the fixture boundary is no longer what the operator believed it was.

Rerun safety also requires a fresh key prefix and fresh upload IDs. Multipart upload IDs are tied to the initiated upload; after completion or abort, reuse is not a valid recovery mechanism. AWS documents NoSuchUpload for an invalid, aborted, or completed upload ID.

A related Refonte example is Pytest strict XPASS release gate: its point is that a nominally green or expected framework status is not automatically a release decision. Here the analogous distinction is completion status versus reconciled object-integrity evidence.

Assign recovery ownership

Assertion: every non-ACCEPT result has one named owner who is authorized to change exactly the failing layer. Expected outcome: uploader owner handles source/part reconstruction; platform owner handles S3 request configuration; release owner adjudicates evidence completeness; security or identity owner resolves credential/signing anomalies. Rejection condition: if two teams can mutate the same upload without a common ledger, or no owner can establish what object became externally visible, use HOLD.

A rerun is safe only after the prior upload's terminal state is known. A repair is safe only when it does not rewrite the oracle. A rollback is safe only when object identity and consumer visibility are known.

Apply the ACCEPT / REPAIR / RERUN / HOLD matrix

The final decision is evidence-driven rather than HTTP-driven.

Condition

Decision

Rationale

Recovery owner

Manifest complete; exact versions recorded; all required cases present once; declared algorithm/type match initiation, completion, and HEAD; independent expected values reconcile

ACCEPT

Evidence supports the declared integrity contract

Release owner

Local completion JSON wrong but uploaded parts, checksums, ETags, and upload ID are independently reconciled

REPAIR

Narrow local artifact can be corrected without changing oracle

Uploader

One part checksum is wrong but source bytes and upload ownership are known

REPAIR

Re-upload that part, recapture evidence, then complete

Uploader

Full-object CRC completion returns BadDigest; source bytes or part order may be wrong

RERUN after root-cause isolation, or HOLD if identity is ambiguous

Completion did not satisfy whole-object checksum contract

Uploader + release owner

Wrong ChecksumType was declared for the intended experiment

RERUN from new MPU

Type is part of the initiation contract

Platform owner

Completion returns success but only ETag was captured

RERUN

Insufficient evidence of which checksum contract was verified

Release owner

HeadObject performed without checksum mode and checksum fields are missing

REPAIR evidence collection

Reissue HEAD with ChecksumMode=ENABLED

Reviewer

ETag equals expected multipart ETag but whole-object MD5 was the claimed release criterion

HOLD

Multipart ETag is not whole-object MD5 proof

Release owner

Missing/duplicate case records or oracle modified after run

RERUN

Evidence set cannot prove denominator or independence

Release owner

Unknown upload ID, unknown source bytes, unexpected keys, or external consumer activity

HOLD

Safe repair/rollback boundary cannot be established

Service owner

C03 bad checksum unexpectedly succeeds under the exact intended request

HOLD

Negative control contradicts expected contract; preserve raw evidence and investigate

Platform owner

C04 wrong order rejected as documented

Evidence PASS for negative control

Expected failure confirms gate sensitivity, not object acceptance

Reviewer

C08 abort succeeds and upload cannot later complete

Evidence PASS for cleanup control

Recovery path behaved as expected

Uploader

The release decision should state exactly what was proven. For a SHA-256 composite case: “part-level SHA-256 evidence reconciled, completion stored a SHA-256 composite checksum, and checksum-enabled HEAD reports COMPOSITE.” Do not rewrite that as “the SHA-256 of the whole object was verified,” because S3's documented multipart SHA-256 path is composite.

For CRC32C full-object: “the independently computed CRC32C of the complete ordered byte stream was supplied at completion, S3 accepted it, and HEAD reports the expected CRC32C with FULL_OBJECT.” AWS documents this as a server-side full-object checksum validation model.

The question in the title therefore has no single answer without the ledger. If the upload used SHA-256 composite checksums, the object checksum is a composition of part-level SHA-256 evidence. If it used CRC32C FULL_OBJECT, S3 can validate the complete byte-stream checksum. If an operator has only the multipart ETag, the evidence does not establish a whole-object MD5 digest.

Final release statement for this unexecuted playbook: RERUN / NOT YET ELIGIBLE FOR ACCEPT. The fixture, oracle, negative controls, evidence schema, and acceptance matrix are specified, but no AWS calls were executed for this article. Therefore no object can be labeled ACCEPTED, no sample checksum can be presented as observed, and no service behavior beyond the cited AWS documentation is claimed as an execution result.

Develop related skills with Refonte Learning

As of November 11, 2026, the Refonte Learning Cloud Development program page describes a three-month program with a stated dedication of 12–15 hours per week, and lists topics including cloud architecture, containers, Kubernetes, infrastructure as code, cloud security, monitoring, DevOps, and cost management. It also says no prior experience is required, with basic programming knowledge beneficial, and describes a potential internship rather than an unconditional internship guarantee.

The published program scope does not establish that it covers this S3 multipart checksum fixture, AWS CLI checksum semantics, or the specific release-acceptance method in this playbook. For engineers who want broader cloud-development practice around reproducible deployments, infrastructure behavior, and operational validation, review the Refonte Learning Cloud Development program and compare its published scope with your own training needs.