The launch journal

Migrating a decade of 1.2 XML.

31 July 2026 · the launch journal

Somewhere in your object store is ten years of EPCIS 1.1 and 1.2 XML — vendor exports, partner feeds, the archive your traceability story actually rests on. A 2.0 mandate is arriving, and the question is not whether to migrate but in what order, and what you are allowed to lose. The answers: archive first, and nothing silently.

What actually changed

Strip the version-bump noise and EPCIS 2.0 is four concrete deltas against 1.2:

deltawhat it means for your corpus
JSON-LD is a first-class syntaxevents carry an @context; XML remains valid, but the web-native form is JSON-LD and the two must translate deterministically
REST capture and query interfacesthe SOAP-era query surface gives way to HTTP interfaces with an official OpenAPI description — pinned on the conformance page
Sensor and certification extensionssensorElementList and friends: dimensions your 1.2 events could not carry at all
GS1 Digital Link identifiersthe same physical identity now arrives as either an EPC URN or a Digital Link URI; the CBV 2.0 §8.9 hash's normalization exists so both compute the same event identity

None of these rewrites your history's semantics. Which is exactly why the migration should start with history.

Archive first: translate and validate before touching production

The instinct is to wire the new pipeline first and backfill later. Resist it. The archive is the risk concentrate — every vendor quirk, every namespace invention, every timezone sin of the last decade lives there — and translating it is free, offline, and reversible. So the order is: translate and validate the archive first, dual-write second, cut over reads last.

Here is the first step on a real fixture from the golden corpus that ships with the package. golden-corpus/xml-translation/object-event-1.2.xml — a 1.2 shipping event with a po business transaction, owning-party source and destination — translates to 2.0 JSON-LD carrying the same epcList, bizStep: shipping, disposition: in_transit, the bizTransactionList with its po reference, and the PGLN source/destination pair, byte-for-byte deterministic. The job's fidelity report, reproduced from the run:

"fidelity": {
  "roundTripClean": true,
  "lossyPaths": [],
  "notes": "Clean round-trip: every source value maps to a standard EPCIS 2.0 field."
}

Now the more interesting case — the one your archive is full of. transformation-event-1.2-ext.xml carries a vendor extension field with no standard 2.0 home. The report does not normalize it away:

"fidelity": {
  "roundTripClean": true,
  "lossyPaths": [ "/epcisBody/eventList/0/example:internalRef" ],
  "notes": "Clean round-trip. 1 path(s) had no standard 2.0 home and were preserved
   under the extension mechanism (declared loss, values retained):
   /epcisBody/eventList/0/example:internalRef."
}

Read that carefully, because it is the trust contract of the whole migration: the path is named, the value is retained under the extension mechanism, and the loss ledger is part of the output — not a footnote in release notes, not a per-release claim about our test corpus. Your corpus is not our test corpus; a per-job report is the only honest grain. And the translated result is only delivered if it also validates against the pinned official EPCIS 2.0.1 schema — translation and conformance are one gate, not two hopes.

Dual-write, with the hash as the cross-era key

Once the archive translates clean (or with declared, reviewed losses), dual-write: production keeps emitting to the old store while every event also lands as 2.0 JSON-LD. The reconciliation key across eras is the CBV 2.0 §8.9 event hash — identity computed from event content, indifferent to whether the event arrived as 1.2 XML or 2.0 JSON-LD, checked here against OpenEPCIS reference vectors. POST /hash on this origin, or npx epcis.dev hash, gives you the ni:///sha-256 URI for any event; equal hashes across your two stores is the mechanical proof your dual-write is faithful. Cut reads over only when that equality holds across a full audit window.

The gotcha list, from the corpus

Three classes of surprise account for nearly everything the fidelity reports flag in legacy XML:

  • Namespaces. Vendor extension namespaces survive translation under the 2.0 extension mechanism — declared, retained, flagged. What they never do is silently become standard fields.
  • Timezones. 1.2's eventTimeZoneOffset is preserved exactly; events whose source systems disagreed about offsets will now disagree in a validator's face instead of in a recall.
  • Extension survival. The projection law that governs this whole surface: extensions ride along, and the projection always validates against the pinned official schema. A superset that stops being conformant is not a superset; it is a fork.

Run it on your corpus this afternoon

POST /translate answers on this origin — application/xml in, 2.0 JSON-LD plus the fidelity report out, no key, no account. The same engine runs on your own bench with npx epcis.dev translate archive/*.xml --json, which is the right tool for a decade of files. The transcript idiom is on the translate page, the pinned schema digests on /conformance/, and when the corpus is clean and you want the write half — a durable destination for the dual-write — the key list is where that opens.

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.