Cold chain is just more dimensions.
31 July 2026 · the launch journal
Every reefer deployment ends up with the same architecture by accident: the readers write custody events into one system, the temperature loggers upload into the logger vendor's portal, and when a load shows up warm, somebody exports both to a spreadsheet and lines up timestamps by eye. The dispute over who owned the excursion then turns on whether two clocks, two systems, and one VLOOKUP can be made to agree — months after the fact.
EPCIS 2.0 removed the excuse for that architecture. The standard's sensor model — sensorElementList — puts telemetry inside the event record. The custody hop and the temperature evidence that rode with it are one artifact, validated against one schema, identified by one hash. Here is what that looks like, validated for real.
The fixture: a dock check with an excursion on board
An SSCC-labeled pallet is observed at the dock during transport. The event carries the observation — and the trip's temperature story:
{
"type": "ObjectEvent",
"eventTime": "2026-07-31T14:45:00.000Z",
"eventTimeZoneOffset": "-06:00",
"epcList": ["urn:epc:id:sscc:0614141.1234567890"],
"action": "OBSERVE",
"bizStep": "transporting",
"disposition": "in_transit",
"readPoint": { "id": "urn:epc:id:sgln:0614141.00999.DOCK-4" },
"sensorElementList": [
{
"sensorMetadata": {
"startTime": "2026-07-31T08:00:00.000Z",
"endTime": "2026-07-31T14:45:00.000Z",
"deviceID": "urn:epc:id:giai:4000001.T-0451",
"deviceMetadata": "https://id.gs1.org/8004/4000001T-0451"
},
"sensorReport": [
{ "type": "Temperature", "minValue": 1.2, "maxValue": 3.9,
"meanValue": 2.6, "uom": "CEL" },
{ "type": "Temperature", "time": "2026-07-31T12:20:00.000Z",
"value": 9.4, "uom": "CEL", "exception": "ALARM_CONDITION",
"deviceID": "urn:epc:id:giai:4000001.T-0451" }
]
}
]
}
$ npx epcis.dev validate sensor-reefer-excursion.json
valid schema=epcis-json-schema.json version=2.0.1
That verdict is against the pinned official GS1 EPCIS 2.0.1 schema — sha256 pins published on the conformance page — not against an in-house approximation of it. A partner's system that speaks EPCIS 2.0 loads this event cold.
The shape: metadata carries the frame, reports carry the readings
The model splits cleanly, and knowing which half carries what saves you from inventing fields:
sensorMetadata frames the element: the observation window (startTime/endTime — here, the whole leg since loading), and which instrument produced it. deviceID is a GIAI — the GS1 individual-asset key, so the logger is itself a first-class identified asset — and deviceMetadata points at the device's own record (calibration, model) as a resolvable GS1 Digital Link URI. When the dispute asks "was that logger even calibrated," the answer is a link in the record, not a scan of a certificate someone hopes still exists.
sensorReport entries carry the readings, and the standard gives you two grains to mix deliberately:
- Aggregates over the window — the first report: min 1.2, max 3.9, mean 2.6 °C. Three numbers that answer "was the leg in spec" for the 99% of legs that were.
- Point readings that matter — the second report: a time-stamped 9.4 °C at 12:20 flagged exception: "ALARM_CONDITION" — the standard's own alarm flag, with the declared boundary exceeded. The excursion is not an annotation in a portal; it is a typed member of the custody record, attributable to the instrument that measured it.
Units come from UN/CEFACT common codes (CEL), and measurement types from the standard's sensor vocabulary — Temperature here; humidity, shock, tilt, illuminance ride the same structure. No home-grown unit strings, which means no unit-reconciliation meeting with the partner whose logger reports Fahrenheit: their conformant record says FAH, yours says CEL, and both are convertible because both are declared.
What stays out of the event
The model's discipline runs both directions. A logger sampling every 30 seconds for 30 days produces ~86,000 readings; those do not belong in custody events, and the standard does not want them there. The event carries what the claim needs — window aggregates, boundary crossings, the device identity — and deviceMetadata/rawData URIs reference the full series where it lives. The custody record stays query-sized; the telemetry archive stays an archive; the link between them is explicit and survives both companies' system migrations.
The query surface then treats sensor payloads as first-class predicates — EQ_deviceID, time bounds over report times, value comparators — the same SimpleEventQuery machinery that filters bizSteps, because sensor data is not a sidecar here. "Every leg where this logger alarmed" is one call, not an export.
Why this matters at commissioning time
For the engineer standing in the DC at 4pm: the deliverable this model unlocks is one pipeline instead of two. The reader feed and the logger download construct one event per hop, validated locally before anything leaves the site — the same construct-and-validate loop as every other event in this journal, offline-tolerant, no cloud dependency in the critical path. The client gets a record where "the load went out of spec at 12:20, between these two custody hops, on this identified instrument" is a single validated artifact their QA team, their carrier, and their customer all read the same way.
And when the carrier disputes it: the event's identity is the CBV 2.0 §8.9 hash of its content — the append-only correction discipline applies to sensor-carrying events like any other, so the excursion record nobody can quietly edit is the excursion record everybody can trust.
Validate the fixture yourself — no account, entirely local:
npx epcis.dev validate sensor-reefer-excursion.json
The full local surface is on run it yourself. The exception views a shipper's operations team sees over these same events — excursions surfaced as work queues, not spreadsheets — are visibility.cloud's reading of the identical record.
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.