Two Publish calls can both return successful service receipts while representing only one delivered SNS FIFO message. Consider a synthetic sensor publisher that emits two separate occurrences, A and B. Both serialize the reading 7 into the same message body, but each puts its own event_id in MessageAttributes. The application sees two events. With content-based deduplication, however, the platform derives identity from the body and excludes those attributes. Unless the publisher supplies different stable deduplication keys, A and B can collide. This is an SNS FIFO content-based deduplication problem: the publisher's business identity and generated transport identity disagree.
That outcome is not proven by counting successful API calls. A publish receipt belongs to the request layer; delivery evidence belongs to the subscribed FIFO queue. The operational test therefore needs both: seven individually recorded publish attempts and an ordered subscriber ledger. It must also include the opposite control. A genuine retry of C should keep the same identity and be suppressed, rather than receiving a random key that turns every attempt into a new event.
This playbook separates documented behavior, reported commissioning evidence and a separately executed local plan. The commissioning record reports Python 3.13.5 and zero AWS calls. A future authorized live run must read back configuration, preserve every receipt, journal each SNS envelope before acknowledgement, and compare the actual ledger with A,C,D,E,END. Until that evidence exists, the rollout decision is HOLD. The expected suppression of B diagnoses a publisher defect; it does not make that defect acceptable.
Declare what makes these two events different
Event identity starts before SNS. A and B are intentionally different sensor occurrences even though the normalized reading is the same. Their initial producer records can be stated as (event_id=A, reading=7) and (event_id=B, reading=7). The first version of the publisher serializes only {"fixture": run_id, "reading": 7} into Message and moves event_id into a message attribute. That placement is deliberate: it exposes the difference between business identity and the input to SNS content-based deduplication.
Amazon’s maintained SNS FIFO message-deduplication documentation, accessed October 14, 2026, says that the generated value is based on the message body and that message attributes are not included. It also says an explicit MessageDeduplicationId overrides the generated value. Those two rules define the test. They do not say that A and B are the same business event; they say the publisher supplied the same generated deduplication identity.
The narrow SNS FIFO same message body case is therefore not enough to define the business event. MessageGroupId selects the ordering group, not the domain event. Under Topic-level scope, deduplication identity is shared across the topic; assigning another group is not the repair tested here.
A retry has the opposite meaning. The second C request is not another occurrence. It is another attempt to publish the same occurrence, so this fixture requires its complete request to remain identical. That is a stricter producer contract than the service's deduplication-key comparison. Randomizing the explicit key would defeat the intended retry suppression.
Before constructing requests, fix the subscriber oracle:
Intended occurrence | Expected subscriber entry | Reason |
A | A | First body-derived identity is deliverable. |
B | none | Unwanted collision with A demonstrates the defect. |
C | C | Stable explicit event key separates C from A/B. |
D | D | A different explicit key separates D from C. |
C retry | none | Wanted collision for the same occurrence. |
E | E | Changed body creates another generated identity. |
END | END | Distinct terminal body bounds the ordered observation. |
This identity-first review complements broader cloud application operating practices; here, the acceptance object is narrower: one publisher contract, one topic, one subscription and one evidence ledger.
Prepare an isolated FIFO subscription
Use a newly created, owned SNS FIFO topic and a newly created FIFO SQS queue in the same Region. The fixture has one publisher, one queue consumer, one message group, no Lambda trigger, no competing subscription reader and no reused production resource. Set topic ContentBasedDeduplication=true and declare FifoThroughputScope=Topic. Keep the queue delay at zero. Disable raw message delivery so the queue body contains the SNS envelope, and remove subscription filters so the test can inspect the event_id attribute without a filter changing delivery.
Follow AWS's queue permission example: allow the sns.amazonaws.com service principal to call sqs:SendMessage on the exact queue only when aws:SourceArn equals the exact topic ARN. Review other policy statements for unintended access. Give the operator only the actions required to inspect, publish, receive, delete and clean up this named fixture. Review any KMS permissions separately; the driver does not audit them.
Record the topic ARN, queue URL, queue ARN, subscription ARN, Region and resource-creation evidence before publishing. Read back actual settings. The GetTopicAttributes API documents FIFO and content-based-deduplication fields; the deduplication guide establishes Topic- versus MessageGroup-level scope. As consulted on October 14, 2026, the API page did not enumerate FifoThroughputScope. The driver therefore requires Topic in the live response rather than inferring a default. This one-group experiment cannot distinguish the two scopes by delivery behavior alone; configuration read-back establishes the declared scope.
Use GetSubscriptionAttributes to inspect the association, endpoint, protocol, pending-confirmation state, filter policy and raw-delivery setting. The driver checks the route and delivery-format fields; confirmation and readiness remain operator checks. Use GetQueueAttributes to capture QueueArn, FifoQueue, DelaySeconds, visibility settings and policy. Approximate message counts are eventually consistent and cannot prove freshness, exclusive ownership or emptiness. Isolation requires resource provenance and exclusive control. This is the discipline needed at other cloud-native data handoffs: verify the attached endpoint, not just its intended name.
Record the conditions behind the delivery claim
AWS's deduplication delivery conditions require a permitted FIFO queue, deletion before visibility expiry, no subscription filter and no network disruption preventing delivery acknowledgement. Its ordering page uses broader wording. Apply the conditional statement to this workflow; neither page establishes distributed exactly-once business processing. These are maintained AWS documentation pages consulted on October 14, 2026, not a version-pinned deployment record.
A wrong queue ARN, unconfirmed subscription, nonempty filter, raw-delivery mismatch, missing FifoThroughputScope, unresolved queue policy or uncertain resource readiness is HOLD. It is not evidence that B was deduplicated. The fixture does not provision resources, audit every IAM or KMS path, or prove that no unrecorded consumer exists. Those are explicit operator preconditions.
Run the complete request and evidence driver
Save the following unchanged source as sns_dedup_fixture.py, using UTF-8, LF line endings and one final newline. Run it from a writable directory. The default command performs only local request construction:
BASH
python sns_dedup_fixture.pyFor a separately authorized AWS run, create an isolated environment, install Boto3, record the resolved package versions, configure normal AWS credentials, export the four required fixture values, and provide the explicit owned-fixture confirmation flag. The following commands use a POSIX shell; they were not used to invoke AWS for this article:
BASH
python -m venv .venv
. .venv/bin/activate
python -m pip install boto3
python -c "import boto3, botocore; print(boto3.__version__, botocore.__version__)"export AWS_REGION='replace-with-reviewed-region'
export SNS_TOPIC_ARN='replace-with-owned-fifo-topic-arn'
export SQS_QUEUE_URL='replace-with-owned-fifo-queue-url'
export SNS_SUBSCRIPTION_ARN='replace-with-reviewed-subscription-arn'python sns_dedup_fixture.py --live --confirm-owned-isolated-fixtureThe report starts at HOLD. After the implemented checks and an exact event-label ledger, the driver sets PASS_BOUNDED_LIVE_MANIFEST; that machine status still needs the evidence review described below. Each run gets a new run_id. Returned Publish dictionaries are appended in memory and written by finally on normal completion or ordinary exceptions. Abrupt termination or a failed final write can leave receipts unavailable. Treat an incomplete artifact as HOLD, not as proof that no call occurred.
The configuration uses total_max_attempts=1. The Botocore configuration reference defines this as one total attempt, including the initial request, so the configured SNS and SQS requests have no automatic SDK retries. That does not make network outcomes certain. It keeps retry decisions explicit and requires reconciliation of an ambiguous call before another attempt.
For a recognized event from the expected topic and fixture, the driver appends the parsed SNS envelope to received.jsonl, flushes it and calls fsync before deletion. A foreign or unrecognized message raises before that append and is not deleted. The journal preserves parsed field values, including the nested Message string, but not the original outer envelope formatting. The current receipt handle is used solely for controlled acknowledgement.
The complete source is:
PYTHON
"""Default: write a local plan. --live requires an owned, isolated AWS fixture."""
import argparse
import hashlib
import json
import os
import platform
import tempfile
import time
import uuid
from datetime import datetime, timezone
from pathlib import Path
def require(ok, message):
if not ok:
raise RuntimeError(message)
def main():
parser = argparse.ArgumentParser()
parser.add_argument("--live", action="store_true")
parser.add_argument("--confirm-owned-isolated-fixture", action="store_true")
args = parser.parse_args()
source = Path(__file__).resolve()
root = Path(tempfile.mkdtemp(prefix="sns-dedup-", dir=source.parent))
run_id = uuid.uuid4().hex
body = json.dumps({"fixture": run_id, "reading": 7}, separators=(",", ":"))
changed = json.dumps({"fixture": run_id, "reading": 8}, separators=(",", ":"))
ending = json.dumps({"fixture": run_id, "end": True}, separators=(",", ":"))
specifications = [("A", body, None), ("B", body, None), ("C", body, run_id + ":C"), ("D", body, run_id + ":D"), ("C", body, run_id + ":C"), ("E", changed, None), ("END", ending, None)]
plan = []
for event, message, dedup_id in specifications:
request = {"Message": message, "MessageGroupId": run_id, "MessageAttributes": {"event_id": {"DataType": "String", "StringValue": event}}}
if dedup_id is not None:
request["MessageDeduplicationId"] = dedup_id
plan.append(request)
report = {"fixture": "sns-dedup-v1", "status": "HOLD", "run_id": run_id, "started_utc": datetime.now(timezone.utc).isoformat(), "python": platform.python_version(), "source_sha256": hashlib.sha256(source.read_bytes()).hexdigest(), "plan": plan, "expected_delivery_ids": ["A", "C", "D", "E", "END"], "publish_receipts": [], "delivery_ids": []}
try:
require(plan[0]["Message"] == plan[1]["Message"] and plan[0]["MessageAttributes"] != plan[1]["MessageAttributes"], "Invalid A/B plan")
require(plan[2] == plan[4], "Retry must retain the complete C request")
report["planned_body_sha256"] = [hashlib.sha256(p["Message"].encode("utf-8")).hexdigest() for p in plan]
if not args.live:
report["status"] = "PLAN_ONLY_NOT_AWS_EVIDENCE"
return
require(args.confirm_owned_isolated_fixture, "Explicit owned-fixture confirmation required")
import boto3
from botocore.config import Config
region = os.environ["AWS_REGION"]
topic = os.environ["SNS_TOPIC_ARN"]
queue = os.environ["SQS_QUEUE_URL"]
subscription = os.environ["SNS_SUBSCRIPTION_ARN"]
config = Config(region_name=region, connect_timeout=5, read_timeout=30, retries={"total_max_attempts": 1, "mode": "standard"})
sns, sqs = boto3.client("sns", config=config), boto3.client("sqs", config=config)
report["boto3"] = boto3.__version__
ta = sns.get_topic_attributes(TopicArn=topic)["Attributes"]
sa = sns.get_subscription_attributes(SubscriptionArn=subscription)["Attributes"]
qa = sqs.get_queue_attributes(QueueUrl=queue, AttributeNames=["All"])["Attributes"]
report["configuration"] = {"topic": ta, "subscription": sa, "queue": qa}
require(ta.get("FifoTopic") == "true" and ta.get("ContentBasedDeduplication") == "true", "Topic must be content-deduplicating FIFO")
require(ta.get("FifoThroughputScope") == "Topic", "Topic-level scope not evidenced by response")
require(qa.get("FifoQueue") == "true" and qa.get("DelaySeconds") == "0", "Unexpected queue")
require(sa.get("TopicArn") == topic and sa.get("Endpoint") == qa["QueueArn"] and sa.get("Protocol") == "sqs", "Subscription identity mismatch")
require(sa.get("RawMessageDelivery") == "false" and json.loads(sa.get("FilterPolicy", "{}")) == {}, "Remove raw delivery and filtering")
# Queue permissions, exclusive ownership, freshness and readiness are operator preconditions.
# Approximate queue counts cannot establish those facts.
started = time.monotonic()
for request in plan:
receipt = sns.publish(TopicArn=topic, **request)
report["publish_receipts"].append(receipt)
require(receipt["ResponseMetadata"]["HTTPStatusCode"] == 200, "Publish failed")
report["publish_elapsed_seconds"] = time.monotonic() - started
require(report["publish_elapsed_seconds"] < 240, "Publish interval exceeds lab margin")
deadline = time.monotonic() + 120
while time.monotonic() < deadline and "END" not in report["delivery_ids"]:
response = sqs.receive_message(QueueUrl=queue, MaxNumberOfMessages=1, WaitTimeSeconds=10, VisibilityTimeout=60)
for message in response.get("Messages", []):
envelope = json.loads(message["Body"])
content = json.loads(envelope["Message"])
require(envelope["TopicArn"] == topic and content.get("fixture") == run_id, "Foreign message: stop without deleting it")
event = envelope["MessageAttributes"]["event_id"]["Value"]
require(event in ("A", "B", "C", "D", "E", "END"), "Unexpected event")
record = {"event_id": event, "sqs_message_id": message["MessageId"], "envelope": envelope}
with (root / "received.jsonl").open("a", encoding="utf-8") as log:
log.write(json.dumps(record) + "\n")
log.flush()
os.fsync(log.fileno())
report["delivery_ids"].append(event)
sqs.delete_message(QueueUrl=queue, ReceiptHandle=message["ReceiptHandle"])
require(report["delivery_ids"] == report["expected_delivery_ids"], "Delivery manifest mismatch or incomplete")
report["status"] = "PASS_BOUNDED_LIVE_MANIFEST"
except Exception as error:
report["error"] = {"type": type(error).__name__, "message": str(error)}
raise
finally:
report["finished_utc"] = datetime.now(timezone.utc).isoformat()
path = root / "results.json"
path.write_text(json.dumps(report, indent=2, default=str) + "\n", encoding="utf-8")
print(path)
if __name__ == "__main__":
main()Read a local plan as a local plan
The supplied commissioning record reports a start at 2026-10-14T06:44:02.510114+00:00 and finish at 2026-10-14T06:44:02.510219+00:00, Python 3.13.5, and source SHA-256 3df3e39fd71cf9f6e03ef5ec9c5b592081807212fca95b54301faaae1d3934e7. It reports seven planned requests, empty receipt and delivery lists, and zero AWS Publish, ReceiveMessage or DeleteMessage calls. Its status was PLAN_ONLY_NOT_AWS_EVIDENCE. The optional branch was reported as syntax-compiled, not integration-tested. These timestamps identify a run, not a benchmark.
The commissioning packet did not supply its random run_id or body-hash array. A separate local execution of the unchanged source provides the concrete oracle below. It ran on Python 3.13.5, returned exit code 0, recorded the same source hash and produced seven requests with empty receipt and delivery lists. AWS was not invoked. The following identity belongs to that separate local run:
TEXT
run_id = 43e6bf17c93e4b7a9f82af641c54fbbf
started_utc = 2026-10-14T08:54:25.963125+00:00
finished_utc = 2026-10-14T08:54:25.963229+00:00
status = PLAN_ONLY_NOT_AWS_EVIDENCEThe seven-request manifest below uses H7, H8 and HEND as aliases for the exact UTF-8 body hashes printed immediately after the table. The expected entries are service-contract oracles, not observed AWS deliveries.
Request | Event | Body hash | Explicit deduplication ID | Expected entry |
1 | A | H7 | none | A |
2 | B | H7 | none | none |
3 | C | H7 | 43e6bf17c93e4b7a9f82af641c54fbbf:C | C |
4 | D | H7 | 43e6bf17c93e4b7a9f82af641c54fbbf:D | D |
5 | C retry | H7 | 43e6bf17c93e4b7a9f82af641c54fbbf:C | none |
6 | E | H8 | none | E |
7 | END | HEND | none | END |
Exact SHA-256 values for the manifest aliases:
H7 = 33181e48423707b319fc054214e8f53c9e8e8cb78191af3a23ed465641f15da6
H8 = 9f7017077bac392e2896280a9c48911925e54104e3caa02a5ddf1003f82bf784
HEND = b1710b04fbf97c48fbd06833a5026149346cc8b1ce426ee581903768a6850b24These values establish local serialization and hashing for this run. The source also compiled successfully, but that check cannot verify Boto3 service models, credentials, policies, propagation or SNS/SQS integration. A new run needs its own report; source edits or changed line endings produce a different source digest. The live ledger remains expected, not observed.
Compare the two attribute-distinct publishes
Requests 1 and 2 make the defect visible without any AWS call. Their exact Message strings are equal byte for byte. Their MessageGroupId values are equal. Neither request contains an explicit MessageDeduplicationId. The only request-level difference is the nested attribute value: A versus B.
For the independent local plan above, both bodies are:
JSON
{"fixture":"43e6bf17c93e4b7a9f82af641c54fbbf","reading":7}Both therefore have the local SHA-256 value:
TEXT
33181e48423707b319fc054214e8f53c9e8e8cb78191af3a23ed465641f15da6That value is a producer-side oracle, not a service-returned hash or a delivery record. The Publish API hash-input rule specifies SHA-256 over the body, excluding attributes, when content-based deduplication is enabled and no explicit ID is supplied. The SNS message attributes deduplication distinction is therefore concrete: event_id=A and event_id=B change the authored event attributes, not the bytes being hashed.
A and B remain two intended business events in the producer manifest, but the request contract assigns them the same generated deduplication identity. Do not relabel B as a retry to make the ledger appear complete. Equal local hashes do not establish that AWS accepted, tracked or suppressed either request. The SNS FIFO different-attributes, same-body case is a publisher-identity defect supported by the documented rule; proving a particular remote execution still requires its own receipts and destination evidence.
Separate the publish receipt from subscriber evidence
During a live run, preserve each sns.publish response, including ResponseMetadata, HTTP status, MessageId and FIFO SequenceNumber. The Publish response definition identifies the assigned message ID and a non-consecutive sequence value that increases within a message group. Record these exactly, with the request position. They are SNS receipts, not subscriber-consumption receipts or proof of a committed business effect.
Seven HTTP 200 responses do not imply seven queue messages. The FIFO-specific Publish rule explicitly permits successful acceptance without delivery for a repeated deduplication ID within scope and interval. That qualification matters even though the API introduction describes immediate delivery more broadly. Do not require distinct response IDs for deduplicated calls, subtract sequence numbers to infer a count, or replace the destination ledger with receipt arithmetic.
Tie destination evidence back to configuration. A message from another topic, run or publisher is not evidence for this manifest. AWS's raw-delivery format comparison shows the SNS JSON envelope retained when raw delivery is disabled. The driver reads the nested Message and MessageAttributes from that envelope, rather than asking SQS for transport-level message attributes. It validates TopicArn and the run marker before recording a recognized event.
Keep time bounds separate from absence proof
The seven sequential Publish calls must finish in less than 240 seconds, an authored margin inside the documented five-minute deduplication interval. The receive loop checks a deadline 120 seconds after polling begins, or stops at the first END. This is a soft loop deadline: a receive or delete already in progress can finish later. Neither limit is an AWS latency guarantee or a hard whole-process timeout.
The ReceiveMessage API supports long polling through WaitTimeSeconds and warns that the client’s HTTP response timeout should exceed that wait. The driver uses a 10-second long poll and a 30-second read timeout. Even so, a finite sequence of empty polls is not permanent absence proof. Missing END, an incomplete ledger or a receive exception is HOLD. It cannot be relabelled “B was definitely deduplicated” merely because the test window expired.
Give distinct events distinct explicit identifiers
C and D keep the same serialized reading body used by A and B, but they add explicit identities:
TEXT
C -> 43e6bf17c93e4b7a9f82af641c54fbbf:C
D -> 43e6bf17c93e4b7a9f82af641c54fbbf:DIn a real publisher, the value should come from a durable event-identity allocation rule, not from the transport attempt. It might be a stable event UUID, an outbox record key or a namespace plus immutable source-event ID. The fixture’s run_id:event format is intentionally synthetic and bounded to one disposable run.
The explicit-ID override rule lets C and D remain distinct despite equal body hashes. The repair is to allocate stable event keys whenever different occurrences can share a body. Under Topic-level scope, choose a namespace that avoids collisions across all publishers of that topic, not just within one message group. The synthetic run_id:event keys demonstrate that separation; they are not a production identity registry.
The repair is not to generate a UUID immediately before every Publish attempt. A new per-attempt key would stop retries from sharing the intended deduplication identity. Allocate the key to the occurrence and preserve it across process restarts, request reconstruction and approved replay. Review both properties together:
Identity property | Required result |
Two distinct events with identical bodies | Different stable deduplication IDs |
Two attempts for the same event | The same deduplication ID and materially identical request |
An implementation that satisfies only the first row is still unsafe.
Retain the same identity for a genuine retry
Request 5 is a deliberate retry of C. It is the SNS FIFO retry identity control in the manifest. The source asserts plan[2] == plan[4], so the event attribute, body, message group and explicit deduplication ID are all identical. Within the documented interval and Topic-level scope, the wanted result is one delivered C, not two. This is the control that prevents the article from recommending unique keys without a retry boundary.
The SNS deduplication guide describes a five-minute interval; the Publish API calls it a five-minute minimum. Neither supports assuming a precise expiry instant at which replay becomes safe. Within the documented scope and interval, a repeated successful identity is accepted without another delivery, and tracking continues after delivery. The second C is sent later in the same bounded publish phase with the same key, so the expected ledger contains one C.
Do not turn that rule into a sleep-based recovery algorithm. Waiting just over five minutes and publishing again does not answer whether the first C reached the queue or whether an external effect occurred. If a client loses the response, keep the stable event identity, preserve the partial receipt log and move to RECONCILE. This producer-side reasoning is intentionally separate from per-record consumer recovery, which owns a different failure boundary.
Do not turn deduplication into an eternal uniqueness guarantee
The five-minute interval is not a permanent event registry. The same explicit ID can be deliverable again outside the tracked interval, and a message already delivered can have caused an effect that SNS cannot inspect. Consumer idempotency, database transaction boundaries and cross-window replay policy remain outside this publisher qualification.
The bounded claim is smaller: in a live run satisfying the declared conditions, the repeat of C should be suppressed while C and D remain separate. The local dictionary equality verifies request construction only. It does not demonstrate that the managed service performed that suppression.
Change the body and preserve the end marker
E changes the reading from 7 to 8 and omits an explicit deduplication ID. END also omits an explicit key but uses a separate body with "end":true. For the recorded local run, all three exact body hashes differ: E uses H8 and END uses HEND from the manifest. Their distinction is checked against concrete serialized inputs, not assumed from the event labels.
Embedding the run identifier changes the hash inputs between runs with different identifiers. This limits accidental cross-run reuse of body-derived identities; a new group alone would not provide that protection under Topic-level scope. Run identifiers are for laboratory isolation, not for production retry allocation. Do not insert timestamps, random nonces or attempt counters merely to force every attempt through content-based deduplication.
C must retain its request on retry. With an explicit key, merely changing its body does not bypass the deduplication key already supplied; changing that key would alter the retry identity. Neither mutation resolves an uncertain prior outcome. Keep the event record, serialization version and identity stable until the owner decides whether replay is justified.
END provides a terminal message in the same group. Because it has a distinct generated identity and follows E in the sequential publish loop, its observation lets the test make a bounded statement about the messages published before it under the reviewed configuration. It is a control message, not a business-event pattern to copy blindly into every architecture.
Reconcile the ordered subscriber ledger
The expected live sequence is exactly the SNS SQS FIFO delivery manifest for this run:
TEXT
A,C,D,E,ENDRecord each full parsed SNS envelope in order before acknowledging its SQS delivery. Preserve the topic ARN, SNS message ID, nested message string, message attributes, any SNS sequence field present, SQS message ID and extracted event. Do not collapse rows into a set. An unexpected B or repeated C must remain visible, even when that makes the assertion fail.
All publishes use one MessageGroupId. AWS's FIFO ordering documentation supports same-group ordered delivery to the FIFO subscriber under the stated conditions. Seeing END bounds the recorded prefix; the code compares the labels through that first END with the oracle. It does not continue polling for later duplicates. Missing, extra or reordered recognized events before END fail the list comparison.
Before acceptance, compare every journaled envelope["Message"] string with its intended plan entry, recompute its UTF-8 SHA-256, check the event attribute, and correlate delivered SNS message IDs with the relevant Publish receipts. The driver saves these fields but automatically checks only topic, run marker, allowed event labels and final label order. The body-to-event and receipt-to-envelope comparisons are required reviewer checks, not assertions already present in the source.
The DeleteMessage API requires the receipt handle from the relevant receive, not a message ID. This fixture supplies the handle from the same response after synchronizing its journal. It does not save DeleteMessage responses or per-message timing. Retain separate operational evidence that acknowledgement completed before the 60-second visibility timeout and that no acknowledgement failure occurred. The related current SQS delivery acknowledgement article treats that separate boundary.
A foreign topic or fixture stops the driver without deletion. Malformed envelopes and unknown event labels also stop before deletion and may not appear in received.jsonl. Preserve the error and leave that traffic for its owner. An empty journal after such a failure does not prove the queue was empty.
Explain what the end marker does not establish
END does not establish infinite future absence, global exactly-once execution, production filter correctness, external transaction durability or safe replay outside the deduplication interval. A marker cannot compensate for missing configuration, acknowledgement evidence or permission review. A repeated SQS delivery caused by visibility or acknowledgement problems also differs from a newly published business occurrence.
The marker only bounds this sequential, same-group observation. If delivery or acknowledgement conditions deviate, reopen the decision and classify the run as HOLD or RECONCILE rather than extending the claim.
Classify missing and contradictory evidence
Use the evidence state, not a blanket SNS-defect diagnosis. Wrong resources, unready subscriptions, permissions, old traffic and partial execution can all break the expected comparison. A service rejection differs from a lost response: the former may establish nonacceptance, while the latter leaves acceptance uncertain. Preserve the distinction when selecting the next action.
Evidence state | Decision | Operational interpretation |
Topic is not FIFO/content-deduplicating, scope is not evidenced as Topic, queue is not FIFO, delay is nonzero, subscription identity differs, raw delivery is enabled, or a filter exists | HOLD | The declared test boundary was not established. Fix or recreate the fixture before interpreting delivery. |
Publish returns an explicit rejection, or its response is lost | HOLD or RECONCILE | HOLD a proven rejection and correct its cause. RECONCILE uncertain acceptance before another attempt; preserve earlier receipts and the error. |
Fewer than seven receipt records after an apparently complete run | HOLD or RECONCILE | The publish phase is incomplete or its provenance is uncertain; do not compare delivery counts as if all requests ran. |
END is missing by the receive deadline | HOLD | The finite poll does not prove permanent absence or deduplication. Check permissions, readiness, queue identity and logs. |
B appears in the ledger | HOLD pending diagnosis | Verify actual bodies, keys, scope and interval. Preserve the contradiction; REPAIR only a demonstrated publisher or configuration fault. |
C appears twice | HOLD; RECONCILE uncertain effects | Compare both envelopes and SQS message IDs. Changed retry identity and redelivery after acknowledgement or visibility problems require different diagnoses. |
Exact A,C,D,E,END, seven successful receipts, matching envelopes and complete operator evidence | ACCEPT_BOUNDED for the repair | Qualify only the distinct-event and stable-retry rule illustrated by C/D. The A/B negative-control publisher still requires REPAIR. |
Local plan only, regardless of local assertions | HOLD for rollout | Request construction is verified; remote integration is not. |
Negative controls can behave as designed while failing the application contract. B’s suppression is the expected diagnostic result but an unacceptable publisher design. Conversely, the second C’s suppression is both the expected diagnostic result and the desired retry behavior. An aggregate “test passed” label that erases that distinction is misleading.
Recover the event contract before replaying data
When an identity collision has already occurred, deploying explicit keys fixes future publishing but does not restore B. Recovery starts from an independent source of truth: a transactional outbox, immutable sensor-event store, producer journal or another owned record that proves B existed as a distinct occurrence. The SNS receipts alone are insufficient because an accepted duplicate can lack subscriber delivery by design.
Build a recovery inventory containing the source event ID, original body bytes or serialization version, original attributes, attempted deduplication identity, publish time range, any receipt, subscriber evidence and known downstream effects. Identify which events fell inside the same deduplication scope and interval. Do not infer the missing event solely from sequence-number arithmetic.
Before replay, assign a decision owner. When authoritative records support B's distinct existence, lack of delivery and absence of downstream effects, the owner may approve a stable explicit key mapped to that same B occurrence. This repairs its transport identity without inventing a new business event. When the prior outcome remains uncertain, use RECONCILE and consult the producer, subscriber and domain records. A random new key is not evidence that replay is safe.
This is the same boundary discipline needed with external API uncertainty before retries: the caller must distinguish “rejected,” “accepted,” and “outcome unknown.” Generic exponential backoff is not the missing decision. The event contract must state whether an attempt is a retry of the same occurrence or an authorized replay after evidence review.
Preserve the original results.json, received.jsonl, source hash, environment versions and resource identifiers. Make corrections in a new run with a new run_id; do not edit prior evidence to produce the desired five-row ledger.
Set the bounded acceptance decision
The acceptance object is the relationship among five things: durable business event identity, exact serialized body, explicit or generated SNS deduplication key, request receipt and ordered subscriber evidence. Accepting only one layer is insufficient.
Use these decisions consistently:
REPAIR when two distinct events can receive the same deduplication identity, as A and B do, or when retry code allocates a new random identity on every attempt.
RECONCILE before retrying an event whose prior acceptance, delivery or downstream effect is uncertain. Preserve the old identity and evidence while resolving the ambiguity.
HOLD when configuration read-back, permissions, receipts, terminal delivery or execution provenance is missing; when only a local plan exists; or when the manifest contradicts the oracle without a supported diagnosis.
ACCEPT_BOUNDED applies only to the repaired identity rule illustrated by C, D and C's retry, after complete configuration and operator evidence, seven successful receipts, exact envelope/body checks and the expected live ledger. It does not accept the deliberately defective A/B allocation.
PASS_BOUNDED_LIVE_MANIFEST means the driver's checks completed, not that every acceptance prerequisite was independently established. The reviewer must still inspect the recorded bodies, receipt correlations, permissions, readiness and acknowledgement evidence. A matching live ledger can validate the bounded C/D repair while leaving A/B at REPAIR. Scope the decision to the actual source hash, SDK versions, Region and resources.
The reported commissioning result and the separate local execution support request construction: seven requests, equal A/B bodies with different attributes, and identical C request dictionaries. Neither run called AWS. They are useful inputs to review, not permission to deploy an unreconciled identity change.
Keep implementation and publication decisions separate
An article can explain documented behavior and provide a proposed live protocol while reporting that only local planning was executed. That distinction is part of the technical evidence, not a service-success claim. There are no live account IDs, Publish receipts, sequence numbers or subscriber timestamps to present from these local runs.
Publication of a qualified protocol and approval of an implementation are separate decisions. Operational approval requires an authorized live run and retained artifacts. In this playbook, absent remote evidence remains HOLD for rollout, and the known A/B identity defect remains REPAIR regardless of how the article is formatted.
Assign the producer and subscriber responsibilities
The producer owns business-event identity. It must allocate a stable key before transport, persist that key with the event record and reuse it for every retry of the same occurrence. It also owns canonical serialization. A change in whitespace, field order, numeric representation or body schema can change a generated SHA-256 identity even when the domain value appears equivalent. If attributes can distinguish events with identical bodies, relying on generated identity is a contract defect unless the body is changed as part of the domain schema or an explicit key is supplied.
Platform owners own the SNS topic, subscription, queue, IAM and optional KMS path. They must establish Topic-level scope, FIFO resources, the exact endpoint, no filter, non-raw delivery, zero queue delay, exclusive ownership and readiness. Record resource tags and creation evidence, approve the budget for service requests and any additional services, and define cleanup before running. Remove only the reviewed topic, subscription and queue created for this lab. Avoid wildcard or name-pattern deletion.
The fixture consumer owns evidence capture. It validates the topic and run marker, synchronizes recognized envelopes before acknowledgement, retains repeated labels and uses the just-received handle. Local fsync is not a complete backup strategy; retain the resulting files in the approved evidence store. The consumer performs no business operation and makes no claim that deletion proves a committed domain transaction.
The reviewer owns the manifest and decision vocabulary. Version the fixture, expected order and source digest together. Rerun qualification after changes to serialization, attribute placement, key allocation, scope, delivery settings, queue policy or SDK retry configuration. Record actual Python, Boto3 and Botocore versions, Region, queue URL and resource ARNs. The driver records Python and Boto3, but the package-version command and operator inventory supply additional fields; copying old metadata is not evidence.
A practical ownership table keeps those boundaries visible:
Owner | Must provide | Must not infer |
Producer team | Durable event key, stable retry key, exact body bytes, seven-request manifest | That different attributes create different content hashes |
Platform team | Read-back configuration, least-privilege policy, readiness and isolated resources | That approximate queue counts prove isolation |
Fixture consumer | Ordered envelopes, current-handle acknowledgement, preserved foreign traffic | That delete success equals one business effect |
Release reviewer | Source/environment identity, receipts, exact ledger and decision record | That local planning or syntax compilation proves AWS behavior |
The bounded contract can then be stated without ambiguity: distinct business occurrences receive distinct stable explicit IDs when body equality is possible; retries of the same occurrence retain the same ID; service acceptance and subscriber delivery are evidenced separately; uncertain outcomes are reconciled before replay.
Build cloud application skills through structured practice
For broader foundations, explore Refonte Learning's Cloud Development programme. Its Program Specifics section lists three months and 12–15 hours per week. The page describes cloud architecture, application deployment, Docker, Kubernetes, infrastructure as code, DevOps and CI/CD, security, monitoring and a cloud-application capstone. It also advertises a Training Certificate and a Certificate of Internship upon successful completion. These published foundations are relevant to deployment review and cloud application design; they do not establish coverage of this exact SNS/SQS laboratory or an individual placement guarantee.
The practical requirement remains the same: reconcile intended business-event identity with the key actually supplied, then compare service receipts with subscriber evidence. A and B expose the collision; C and D test distinct explicit identities; C's repeat tests retry stability. The expected A,C,D,E,END ledger is diagnostic, not observed here. Until a reviewed live run supplies the missing evidence, keep rollout at HOLD and repair the A/B identity allocation.
