Replay anything. Capture once.
31 July 2026 · the launch journal
You commission sites for a living, so you already know the failure modes the whiteboard version of "the reader POSTs an event" politely omits. The dock door drops Wi-Fi and the reader buffers offline for an hour, then flushes the buffer — twice, because the first flush timed out after the server had already committed. The forklift gate double-fires on one pallet. The night batch job replays yesterday's file because the cron lock file didn't survive a reboot. None of this is anyone's bug; it is what the physical world does to integrations, and it happens during commissioning week, with the client watching.
The question that decides whether your week is fine or terrible: what does the capture endpoint do with the same event, sent again?
Hash-as-identity makes "did I already send this?" a non-question.
This gateway's answer comes from the identity model, not from bolted-on plumbing: an event's identity is the CBV 2.0 §8.9 hash of its own content. The same observation hashes to the same identity no matter when it arrives, how many times it arrives, or which retry sent it. So dedupe needs no distributed lock, no sequence-number agreement with the sender, no per-device state on the server. Re-capturing an identical event is a benign no-op acknowledgement, never a re-append — that law is a committed corpus fixture (golden-corpus/sequences/duplicate-benign-replay.json), and here it is running, verbatim, through the capture door.
The same document, captured twice in one session:
→ capture { document: object-event-shipping.json }
← { "success": true,
"captureID": "4f33487b-0bd8-43e2-b0c7-00b86bd0fcef",
"errors": [],
"eventIDs": ["urn:uuid:6f9b62a2-5c1e-4c39-9e2b-1a2b3c4d5e6f"] }
→ capture { document: object-event-shipping.json } # the retry
← { "success": true,
"captureID": "87c9c410-8bc4-4a35-b454-ce2213c0fa2b",
"errors": [],
"eventIDs": ["urn:uuid:6f9b62a2-5c1e-4c39-9e2b-1a2b3c4d5e6f"] }
Two accepted capture jobs — the sender is never punished for retrying — one event identity. Then the check that matters, a trace over the EPC both jobs referenced:
→ trace_epc { epc: "urn:epc:id:sgtin:0614141.107346.2018" }
← eventList: 1 event
eventID: urn:uuid:6f9b62a2-5c1e-4c39-9e2b-1a2b3c4d5e6f
One stored event. The retry cost the record nothing, and it cost you nothing: the reader can flush its offline buffer with a clear conscience, the batch job can replay the whole day, and your capture script needs exactly one policy — on any doubt, send it again. Idempotency is what turns retries from a data-quality risk into the correct default.
Why the bookkeeping fields can't break it.
The subtle failure in homegrown dedupe schemes is that the "same" event is never byte-identical on retry — the second attempt gets a new server timestamp, maybe a new GUID. §8.9 anticipates this: the algorithm excludes eventID, recordTime, and errorDeclaration from the hash. Identity covers the observation — what was seen, when, where, why, how — and not the bookkeeping stamped around it. The gateway filling in recordTime at admission, or an eventID differing between a device's local log and the retry path, cannot fork the identity. (Corollary for your capture wiring: don't "pre-strip" anything before hashing; the algorithm already knows what doesn't count.)
And the benign-replay law has a sharp edge, which the corpus also pins (duplicate-different-content.json): same eventID with different content is not a replay — it is a conflict, and it is refused with a typed error rather than silently merged. Dedupe never becomes overwrite. The record stays append-only; corrections happen out loud, via errorDeclaration, never by a second write wearing the first one's ID.
The schemes this replaces, and where they shear.
You have inherited the alternatives on other people's sites, so you know their failure smell:
- Sequence numbers shear at the buffer boundary — the offline reader that reboots mid-flush renumbers, and now "gap detection" pages someone at 2 a.m. about pallets that were scanned fine.
- Vendor GUIDs as identity shear at the retry boundary — the retry mints a fresh GUID for the same observation, and the store faithfully keeps both. Every duplicate in that system was sent honestly.
- "Dedupe window" heuristics (same EPC within N seconds) shear on real operations — two genuine scans of one case, thirty seconds apart at the same door, is a Tuesday.
Content-hash identity has none of these seams because there is nothing to coordinate: the identity travels inside the event itself.
What this means for the kit you ship.
Concretely, for the capture wiring you leave behind at a site: buffer offline without fear, flush on reconnect, retry on any non-response, and treat an acknowledgement for a known identity as success — the transcript above is the contract. Validation is local and free before anything leaves the box (npx epcis.dev validate, the same pinned schema the door enforces), so a mis-mapped event fails on your bench, not at the client's door. And the whole capture-law suite — idempotent replay, conflict refusal, no partial acceptance — ships as runnable corpus sequences you can demo to the client's IT team the day you commission.
Capture keys for the hosted spine are provisioned from the list at /get-a-key/, in order — the flow asks what you'll be capturing so a field deployment lands with the right scope on day one. Until yours comes up, the local doors run the identical pipeline: same validator, same hash, same laws, exit codes you can script against.
Replay anything. Capture once. Commission the site, and let the network do its worst.
We answer in writing. We take at most five conversations a month, only when you ask for one, and only after you already have the written read.