# Say `shipping`, not "shipped."

*31 July 2026 · the launch journal*

Every warehouse system speaks a private dialect. `SHP-OUT-04`. `RCVD`. `QC-HOLD`. Inside one building, the dialect works — everyone knows what stage 04 means, and the meaning lives in a wiki, a code table, or the head of whoever configured the WMS in 2019. The moment an event crosses a company boundary, the dialect becomes a translation project: your partner's analyst emails to ask what `SHP-OUT-04` means, your analyst asks what their `STG-COMPLETE` means, and a reconciliation meeting is born that will recur, quarterly, forever.

EPCIS solved this in the standard itself. The **Core Business Vocabulary** — CBV 2.0 — is the *why* dimension's dictionary: a closed, standardized set of `bizStep` identifiers (what business process was underway) and `disposition` identifiers (what state the objects were left in). `shipping`, `receiving`, `picking`, `in_transit`, `sellable_accessible` — forty-one business steps, thirty-three dispositions, each with one published meaning. Use them and two parties who have never exchanged a code table can query each other's events and agree on what they say. The vocabulary *is* the join key.

## This is not a style guideline. The schema enforces it.

Here is the part that surprises teams who assumed `bizStep` was a free-text field. The pinned official EPCIS 2.0.1 JSON schema enumerates the CBV values — a bare word must be one of the standardized identifiers, and anything else must be a full URI in a *non-CBV* namespace (the sanctioned escape hatch, of which more below). Feed the validator an event whose `bizStep` is a WMS status code and watch the standard defend itself:

```
$ npx epcis.dev validate freetext-bizstep.json
schema-error	/epcisBody/eventList/0/bizStep	must match format "uri"
schema-error	/epcisBody/eventList/0/bizStep	must be equal to one of the allowed values
schema-error	/epcisBody/eventList/0/bizStep	must match a schema in anyOf
schema-error	/epcisBody/eventList/0	must match "then" schema
schema-error	/	must match "then" schema
invalid	errors=5
$ echo $?
1
```

`SHP-OUT-04` is not merely unfashionable — it is **schema-invalid**, rejected by GS1's own published artifact with exit `1`. Change it to `shipping` and the same document returns `valid`. The RFP checkbox that says "CBV-conformant vocabulary" is checkable by a machine in twelve microseconds, which is precisely [the kind of claim worth putting in an RFP](/epcis-2-0-conformance-isv/).

## Ten real status strings, mapped

The translation your integration actually needs, for the ten strings every WMS seems to grow — every CBV value below verified against the pinned context's enumerations:

| WMS status string | bizStep | disposition |
|---|---|---|
| `SHP-OUT-04` / "Shipped to customer" | `shipping` | `in_transit` |
| `RCVD` | `receiving` | `in_progress` |
| `PICKED` | `picking` | `in_progress` |
| `STAGED` | `staging_outbound` | `in_progress` |
| `LOADED` | `loading` | `in_progress` (then `container_closed` at seal) |
| `PUTAWAY` | `storing` | `sellable_accessible` |
| `QC-HOLD` | `inspecting` | `non_sellable_other` |
| `RET-IN` (customer return received) | `receiving` | `returned` |
| `DMG-WRITEOFF` | `inspecting` | `damaged` |
| `SHIP-CANCEL` | `void_shipping` | — |

Note the shape of the mapping: a WMS string usually collapses *process* and *state* into one code, and CBV splits them back apart. "Shipped" is a past-tense state in the dialect; in the vocabulary it is a process (`shipping`) that left the goods in a state (`in_transit`). That split is why the title of this entry is a rule worth enforcing in code review — the event records a step underway and a disposition resulting, not a verdict remembered.

Also note the pairs are not arbitrary. CBV constrains which dispositions sensibly follow which steps — `in_transit` follows `shipping`, not `storing`; `sellable_accessible` follows `stocking` or `storing`, not `destroying`. A validator checks the values; a reviewer checks the pairs; a partner's verifier agent, [branching on typed results](/journal/the-tarball-contains-its-own-contract/), consumes both without a phone call.

## The two strings with no CBV home

Every real code table has entries the vocabulary deliberately does not cover — in our ten-system sample, things like `AWAITING-BILLING` (a finance workflow state, not an observation about goods) and `INV-SYNC-OK` (a system-integration heartbeat). Do not shoehorn them into `other`, and do not stop recording them. The standard's own extension path is the answer: a full URI under **your** namespace (`https://example.com/voc/awaiting-billing`) is exactly what the schema's non-CBV URI branch admits, and it validates. The discipline is the boundary: standardized meanings ride the standardized identifiers, private meanings ride namespaced URIs that *announce* they are private — never a bare string that looks like vocabulary but resolves to a wiki page. (The same discipline, one dimension over, is how [a performer rides an event conformantly](/epcis-who-performer-worker-agent/).)

The compounding payoff is that vocabulary discipline is what makes the *joins* mechanical. The [despatch-advice reconciliation](/business-transactions/) works because both sides' receiving events say `receiving`; a trace across a co-manufacturer works because commissioning says `commissioning` in both buildings; the exec dashboard upstairs at [visibility.cloud](https://visibility.cloud/) rolls up dispositions it did not have to translate. Every home-grown status string you retire is one future meeting canceled — and the validator will tell you, today, exactly how many you have left: `npx epcis.dev validate`, exit codes honest, [fixtures in the tarball](/run-it-yourself/).

*Sources: CBV 2.0 bizStep/disposition vocabularies as enumerated in the pinned `epcis-context.jsonld` and enforced by the pinned `epcis-json-schema.json` (digests at [/conformance/](/conformance/)); rejection transcript run 2026-07-31 with the packaged CLI, exit 1.*

---

**Proof, not adjectives.** This build could not verify the gateway's test suite green, so no sentence on this page claims a passing run — the last verified run is recorded in the repository's own CI, not here. Live on this origin, no key: POST /translate, /validate, /hash. No conformance attestation has ever been issued. The dated ledger is /what-ships-today/.
