blog · 2026-07-31 · written for: ED-3 — the in-house supply-chain platform team (the platform / staff engineer who evaluates by running, never by taking a meeting); B2D motion

Camera to event: the browser scan demo, explained

One thesis: a phone browser goes from camera frame to validated EPCIS event in four visible steps — decode, resolve, enrich, emit — and publishing the pipeline as an inspectable demo, network trace and all, is the honesty posture applied to scanning. Nothing in the demo is special-cased. Every step is the same verb you can run yourself, and the demo's job is to prove that by showing its work.

If you run a platform team, you have seen the other kind of demo: a video of a phone scanning a box, a dissolve, a dashboard. What happened between the camera and the chart is precisely the part you are being asked to buy, and precisely the part the video hides. This page walks the four steps with their actual contracts, because a staff engineer's evaluation starts where the dissolve is.

Step 1 — decode: frame to bytes

The browser's native path is the BarcodeDetector API, and the honest status in mid-2026 is: usable opportunistically, dependable nowhere. Chrome on Android supports it fully; desktop support is partial and platform-dependent; Safari keeps it behind a flag; Firefox does not ship it. So the demo's rule is the only defensible one: feature-detect BarcodeDetector and use it when present; fall back to a WASM build of zxing-cpp — the one actively maintained open decoder complete across the GS1-relevant symbologies — and never depend on the native API.

The decode step's output contract is the same either way:

{ payload: "010950600013435210AB12\u001d21987",
  symbology: "qr_code",
  symbologyIdentifier: "]Q3" }

Bytes, symbology, and the AIM identifier — including the embedded FNC1/GS separators (\u001d, ASCII 29), unmangled. Multi-frame voting sits above the decoder: a decode must agree across consecutive frames before it is accepted, because a mis-read that survives into an event record costs more than a half-second of latency. This step is the only one that touches the camera, and it runs entirely on the device.

Step 2 — resolve: bytes to identity

The payload goes to resolve — the same pure function the CLI ships, compiled for the browser. ]Q3 says GS1-mode QR; FNC1-first says element string; the pinned AI table parses (01) GTIN, (10) lot, (21) serial; the mod-10 check digit is verified; GTIN+serial makes this an SGTIN — an instance, not a class.

scheme     gs1.element-string
gtin       09506000134352    check: mod10 pass
ai.10      AB12              lot
ai.21      987               serial
identity   urn:epc:id:sgtin:9506000.013435.987

The demo renders this response verbatim from its own network tab — no reformatting, no summary card standing in front of the actual JSON. If you open developer tools while scanning, you see exactly what the page saw.

Step 3 — enrich: identity to licensed facts

The canonical identity goes to enrich, and the response is the part worth slowing down on: every attribute arrives wrapped in the provenance envelope — value, source, dataset version, sha256, license class, fetched-at. A food GTIN answers from public-domain USDA FoodData Central and the ODbL Open Facts layer; the ODbL attribution notice is rendered in the response where the license requires it, not in a footer where it decays. An identifier with no record in any licensed source returns an honest absence — exit-1 semantics, not an invented product name.

This is the one step that is legitimately a network step, and the demo shows the request and response as they crossed the wire, because "which rails answered, under which license" is the entire question a platform team should be asking of an enrichment layer.

Step 4 — emit: facts to a conformant event

One tap turns the scan into what it always was in disguise: an EPCIS 2.0 ObjectEvent. The parsed AIs populate it deterministically — the SGTIN into epcList, time and timezone from the device clock, bizStep and disposition from the demo's scanning context — and the event does not leave the function until it validates against epcis.dev's pinned copy of the official EPCIS 2.0.1 schema. The demo shows the validation verdict alongside the event JSON: the claim "this is a valid event" is one click from its check, which is the family's standing rule for every claim on every page.

One field in that event deserves its own sentence. The performer rides in a namespaced extension — the standard's five dimensions record what, when, where, why, and how, and the family carries who, the attested observer — a human, an agent, or an embodied agent, in id.org.ai terms — as a conformant extension, never collapsed into capturedBy, the warrantor account that posted the event. The observer and the voucher are different grains, and the event keeps them different.

Why "inspectable" is the specification

The four steps are glass boxes by design, and that is a checkable property, not a tone:

  • Decode is open source, and its fallback logic is feature-detection you can read.
  • Resolve is pure and local — run npx barcoding.dev resolve on the same payload and diff the output against the demo's network trace. They match because they are the same core.
  • Enrich names its sources per attribute, so the response is its own audit trail.
  • Emit gates on a pinned public schema, so the validation verdict is reproducible against GS1's own artifact.

A scanning demo that cannot survive its own network tab is a video. This one is a pipeline, and the pipeline is the product: the same four contracts, wired into your readers instead of a phone camera, are what the after-the-beep architecture looks like — the construction mechanics of step 4 get the full field-by-field treatment in From scan to conformant event.

The hosted demo — camera in the browser, all four steps live — is provisioned with API access: get an API key, and say what your platform captures today. The pure steps never wait for anyone: resolve and verify run local, offline, unmetered, now.

Keys open the network half of the verb set. Get an API key — say what arrives at your door, and the provisioning order follows the list.

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.