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. |
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__)
PYFor 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.zipAWS 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 COMPOSITEEXPECTED / 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.
