Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
55 commits
Select commit Hold shift + click to select a range
378117d
feat(epcis): capture private by default
May 5, 2026
86152d1
Merge slice/01-capture-private-by-default
May 5, 2026
75529f0
feat(epcis): per-request contextGraphId + subGraphName on capture
May 5, 2026
ff31903
test(epcis): live smoke for slice-02 per-request CG + sub-graph
May 5, 2026
92a5165
Merge slice/02-capture-per-request-cg
May 5, 2026
5768aa7
feat(epcis): add query partition selector
May 5, 2026
8f7f890
docs(epcis): add CONTEXT.md glossary for the package
May 5, 2026
343436f
feat(epcis): per-request contextGraphId + subGraphName on events query
May 5, 2026
be6ff27
test(epcis): live devnet e2e for slice-04 query route + summary report
May 5, 2026
56b4581
Merge slice/04-query-per-request-cg
May 5, 2026
b2a8268
fix(publisher): keep source root IRI through async-lift validation
May 5, 2026
a228612
test(publisher): live devnet probe for slice-03b finalized=false fix
May 5, 2026
18fdae3
Merge slice/03b-fix-swm-anchor-subject
May 5, 2026
15a3bd0
feat(cli): dkg epcis {capture,status,query} subcommands
May 5, 2026
0a28639
test(cli): live devnet e2e for slice-05 epcis subcommands + summary
May 5, 2026
1d56011
Merge slice/05-cli-epcis-subcommands
May 5, 2026
f039533
test(epcis): multi-node privacy + auth-gate smoke test (slice 06)
May 5, 2026
b880bbe
Merge slice/06-devnet-privacy-smoke-test
May 5, 2026
77ac274
chore(epcis): drop paranet/paranetId leftovers in EPCIS scope
May 6, 2026
b92dcec
fix(publisher): restore canonicalRootIri synthesis + adapt SWM in lift()
May 6, 2026
941b78e
chore(epcis): consolidate devnet probes; drop per-slice scripts and r…
May 6, 2026
6615390
refactor(epcis): trim dead code and reuse core URI helpers
May 6, 2026
0c9ec82
Merge branch 'main' into feat/epcis-async-private
May 6, 2026
8e5071d
fix(epcis): validate merged publishOptions from envelope + CLI flags
May 6, 2026
c6af29b
fix(epcis): fail --all pagination on malformed follow-up page
May 6, 2026
ac07c5d
fix(epcis): use sub-graph private URI when subGraphName is set
May 6, 2026
7ce343a
fix(epcis): join finalized sub-graph queries against root _meta
May 6, 2026
26f2fad
Merge branch 'main' into feat/epcis-async-private
May 7, 2026
5bbf41d
fix(epcis): address Codex pass — empty-string rejection, validation o…
May 7, 2026
2d4bd8e
feat(demo): EPCIS-on-DKG bicycle-assembly traceability walkthrough
May 7, 2026
9c72c57
fix(demo): address PR 440 review pass — eventFiles count, daemon /api…
May 7, 2026
fbed3ed
fix(demo): address PR 440 review pass — URN encoding, tz offset, sort…
May 7, 2026
9ea2c8e
fix(demo): address PR 440 review pass — fix ReferenceError on subscri…
May 7, 2026
f8be5c8
fix(demo): address PR 440 review pass — mixed-bucket ADD/OBSERVE spli…
May 7, 2026
1c5da82
fix(demo): address PR 440 review pass — DKG_API_PORT, completed-as-su…
May 7, 2026
c5fa347
fix(demo): address PR 440 review pass — anchor-baseline scoping, cond…
May 7, 2026
5177a9a
fix(demo): address PR 440 review pass — groupKey ambiguity, fetch rej…
May 7, 2026
fdea4db
fix(demo): address PR 440 review pass — manifest path resolution, sco…
May 7, 2026
fc37276
fix(demo): address PR 440 review pass — current-trace-only cleanup, o…
May 7, 2026
c2ec236
fix(demo): address PR 440 review pass — extend conditional-bearer to …
May 7, 2026
47fe713
fix(demo): address PR 440 review pass — full traceId in manifest name…
May 7, 2026
3fca3fb
fix(demo): address PR 440 review pass — reversible safeName, KC-scope…
May 7, 2026
5baebe5
fix(demo): address PR 440 review pass — per-partition baseline validi…
May 7, 2026
a2092b0
fix(demo): address PR 440 review pass — http-error retryable, Phase 7…
May 7, 2026
3cb0510
fix(demo): address PR 440 review pass — register on-chain in skip mod…
May 7, 2026
616f880
fix(demo): address PR 440 review pass — path traversal, naive-tz reje…
May 7, 2026
5353098
fix(demo): address PR 440 review pass — portable path-containment che…
May 7, 2026
1372f37
fix(demo): address PR 440 review pass — reject absolute paths in clea…
May 7, 2026
1893900
fix(demo): address PR 440 review pass — validate items shape, expand …
May 7, 2026
051f719
fix(demo): address PR 440 review pass — prototype-safe byStatus, glob…
May 7, 2026
49f2948
fix(demo): address PR 440 review pass — enforce single-trace-per-outD…
May 7, 2026
b16e0d9
fix(demo): address PR 440 review pass — node2 retry, public /api/stat…
May 7, 2026
756a689
fix(demo): rename allowList persona to generic 'Lab'
May 8, 2026
799e928
Merge pull request #440 from OriginTrail/demo/epcis-bike-line
zsculac May 8, 2026
b540fed
test(epcis): align --allowed-peer-without-policy test with merged-opt…
May 8, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions demo/epcis-bike/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# EPCIS-on-DKG Demo — Acme Bikes Assembly Line W18

A practical, end-to-end walkthrough of the v10-rc EPCIS plugin against synthesized supply-chain data. One bicycle, 7 station events, full privacy story.

## What this is

**Acme Bikes** is a fictional bicycle manufacturer used here to keep the demo grounded in something readable while staying free of any partner data. Their **Assembly Line W18** produces road bikes. Each bicycle passes through 7 stations (frame welding, painting, wheel assembly, drivetrain, paint inspection, functional test, packing) before shipping. Every station emits a structured event — which item, where, when, status — that maps directly to the GS1 **EPCIS 2.0** supply-chain standard.

This demo follows **one bicycle** (`trace_id 7c4f8d2a-9e3b-4a6d-b517-8f9e0a1b2c3d`, item `BIKE-2026-W18-0001`) through the line. It captures every station event with the v10-rc EPCIS plugin, queries the data back, and shows what each party (Acme owner, granted research lab, external auditor, competitor) can see at each step.

The privacy story is the central beat: by default, EPCIS captures publish a **public anchor** (proves the event happened) plus a **private payload** (full event body, locally readable, optionally granted to specific peers via allowList). The demo demonstrates this contrast on synthesized data that's safe to commit and replay in any environment.

## Prerequisites

- Node.js 22+ — matches the repo-level requirement (`README.md:70`). The demo also uses built-in `fetch`, which is stable from Node 18 onward; the 22 lower bound here is set by the repo, not the demo itself, and is enforced when you run `pnpm -C packages/cli build` from the next bullet.
- Local DKG daemon running and reachable on `~/.dkg/api.port`. Start it with `dkg start`.
- Either a recent `dkg` on your `$PATH` *with* the `epcis` subcommand, **or** the local CLI build (`pnpm -C packages/cli build` from repo root). `run.mjs` prefers the local build automatically.
- The local devnet must be in a **healthy** state — chain adapter responding, contracts deployed and in sync. If the devnet has been running across contract redeploys, captures will finalize with `Async lift cannot mark chain inclusion`. Stopping and restarting the daemon (`dkg stop && dkg start`) typically resolves this; see commit `27490f2b fix(devnet): redeploy contracts when artifacts outpace running chain` for the underlying fix.

## How to run

Default — paced, narrated walkthrough. Each phase prints its story, then waits for `Enter`. Read at your own speed; the prior phase output stays on screen until you advance.

```sh
node run.mjs
```

Unattended (still narrated, but no pauses):

```sh
node run.mjs --no-pause
```

Agent-friendly NDJSON mode (one JSON line per phase step, no narrative, no pauses):

```sh
node run.mjs --json | jq .
```

Skip context-graph creation (useful when the CG is already registered and you want to skip the daemon round-trip). Skip mode requires `EPCIS_DEMO_CG` to be the **fully qualified** CG ID — bare names exit early with a clear error because the auto-resolution path is bypassed:

```sh
EPCIS_DEMO_CG=0xabc.../dmaast-bike-demo node run.mjs --skip-cg-create
```

Override the context graph ID:

```sh
EPCIS_DEMO_CG=my-test-cg node run.mjs
```

By default the demo auto-suffixes its CG name with a per-run timestamp (e.g. `dmaast-bike-demo-mz4hk7n0`) so naive re-runs always create a fresh context graph. The ETL produces deterministic event IDs, so re-capturing the same fixtures into an existing CG hits publisher duplicate-root rejection mid-Phase-1 and never reaches the verification phases — so **pinning `EPCIS_DEMO_CG=<name>` does not, on its own, let you iterate Phase 7**. Phase 1 will hard-fail before Phase 7 runs. To iterate Phase 7 against a stable CG you would need a separate "skip-capture" mode (not provided), so the supported workflow is: let the demo create a fresh CG per run. Pin `EPCIS_DEMO_CG` only when targeting a CG whose `bike-line` sub-graph does not already contain these event IDs.

## How to navigate

| What you want | Where to look |
|---|---|
| Regenerate fixtures from source data | [`lib/etl.mjs`](./lib/etl.mjs) and [`fixtures/README.md`](./fixtures/README.md) |
| EPCIS field mapping rules | [`lib/epc-mapping.mjs`](./lib/epc-mapping.mjs) |
| The synthesized raw source | [`fixtures/source-raw/acme-bikes-line-w18.json`](./fixtures/source-raw/acme-bikes-line-w18.json) |

## What's NOT in this demo

These are deliberately excluded:

- **Multi-node setup.** AllowList grant is *recorded* (Phase 6); cross-node read enforcement uses the same plugin code path but requires a second node to exercise.
- **Kafka / streaming ingest.** EPCIS is the channel; the upstream wiring is separate.
- **Real partner data.** The fixtures are fully synthesized — no customer or partner identifiers anywhere. If you want to drive the demo from a real export, you'll need to author your own raw source file with the same shape as `fixtures/source-raw/acme-bikes-line-w18.json` and point `BIKE_SOURCE` at it.
- **UI integration.** node-ui's Explorer / graph-viz exists; this demo is CLI/API only.
- **Live-chain hardening.** Devnet-only.

## License

Apache-2.0 (matches the parent repo).
45 changes: 45 additions & 0 deletions demo/epcis-bike/fixtures/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# Fixtures

Pre-generated EPCIS 2.0 documents from one synthesized Acme Bikes assembly trace.

## Contents

| File | Purpose |
|---|---|
| `source-raw/acme-bikes-line-w18.json` | The synthesized raw source — 7 cycle records on the assembly line. Committed because it's fully synthetic; ETL is reproducible from a clean clone. |
| `event-NN-<station>.json` | One EPCIS document per station event, in chronological order. Each document holds exactly one `ObjectEvent`. |
| `trace-7c4f8d2a-bike-line.json` | Manifest: trace ID, time range, stations visited, item IDs, plus per-event metadata (eventID, bizStep, disposition, action). |
| `source-snapshot.json` | Source basename + SHA-256 of the synthesized raw source used at ETL time. |

The Phase 6 allowList grant is demonstrated against a synthesized "shipping" event built in memory by `run.mjs` and written to `os.tmpdir()` per run — no committed file.

## Regenerate from source

The fixtures are generated from the committed synthesized source `source-raw/acme-bikes-line-w18.json`. To regenerate after editing the source:

```sh
node ../lib/etl.mjs

# or override
node ../lib/etl.mjs \
--source ./source-raw/acme-bikes-line-w18.json \
--trace-id 7c4f8d2a-9e3b-4a6d-b517-8f9e0a1b2c3d \
--out ./

# or via env
BIKE_SOURCE=./source-raw/acme-bikes-line-w18.json node ../lib/etl.mjs
```

ETL is deterministic: same source + same trace ID → identical eventIDs. The seed is `trace_id|unit_id|process_name|ended` for the common case where one source record yields one EPCIS document. `process_name` is part of the seed so per-station cycle counters that share `unit_id` across stations don't collide on the same eventID. When a single source record splits into multiple sibling EPCIS docs (mixed status — e.g. `Passed` vs `Rejected` items in the same cycle — and/or mixed first-seen action — first-seen items become `ADD`, already-seen items become `OBSERVE`), each sibling's seed gains a JSON-encoded `groupKey` segment (`{"status":"...","action":"add"|"observe"}`) so siblings get distinct eventIDs and the publisher's duplicate-root validator can't reject the second one.

## Mapping rules

See `lib/epc-mapping.mjs` for the mapping logic.

- `items.<id>` → `epcList[i]` as `urn:acme:bike:item:<id>` (custom URN — Acme Bikes is a fictional manufacturer; URN segment is normalized by `safeUrnSegment` so spaces / slashes / non-ASCII in source data don't produce invalid IRIs)
- `process_name` → `bizLocation.id` and `readPoint.id` as `urn:acme:bike:station:<process_name>` (same `safeUrnSegment` normalization applies)
- `process_name` matching `inspection|test|inspecting` → CBV `inspecting`; otherwise CBV `assembling`
- `items.<id>.status`: `Passed` → CBV `in_progress`, `Rejected` → CBV `damaged`, `Skipped` → CBV `unknown`
- `action`: per item, `ADD` for first-seen EPCs in the trace and `OBSERVE` for already-seen EPCs. When a single status group contains BOTH first-seen and already-seen items, the ETL splits the group into separate `ADD` and `OBSERVE` sibling documents (with distinct `groupKey`s) instead of collapsing the whole group to one action — the EPCIS spec reserves `ADD` for true first observations, and the previous "collapse to OBSERVE" / "collapse to ADD" approaches both lost information for one of the sub-groups. For the demo's uniform-status fixture each item appears in exactly one record per station, so the practical pattern is "doc 1: ADD, docs 2..N: OBSERVE".
- `eventID` derived from `urn:uuid:<v5(trace_id|unit_id|process_name|ended)>` — or `urn:uuid:<v5(trace_id|unit_id|process_name|ended|<groupKey>)>` when a single source record splits into multiple sibling EPCIS documents (see deterministic note above).
- Source timestamps MUST carry an explicit timezone offset (`Z` or `±HH:MM` / `±HHMM`). Naive timestamps without an offset are rejected at ETL time — `Date.parse` interprets them in the host's LOCAL timezone, which would mis-order records relative to UTC-suffixed timestamps in the same source.
35 changes: 35 additions & 0 deletions demo/epcis-bike/fixtures/event-01-FrameWelding.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"@context": {
"@vocab": "https://gs1.github.io/EPCIS/",
"epcis": "https://gs1.github.io/EPCIS/",
"cbv": "https://ref.gs1.org/cbv/",
"type": "@type",
"id": "@id",
"eventID": "@id"
},
"type": "EPCISDocument",
"schemaVersion": "2.0",
"creationDate": "2026-05-12T10:15:00.000Z",
"epcisBody": {
"eventList": [
{
"eventID": "urn:uuid:2b36e74e-b623-53a0-9208-6e61588a7173",
"type": "ObjectEvent",
"eventTime": "2026-05-12T08:12:00.000Z",
"eventTimeZoneOffset": "+00:00",
"epcList": [
"urn:acme:bike:item:BIKE-2026-W18-0001"
],
"action": "ADD",
"bizStep": "https://ref.gs1.org/cbv/BizStep-assembling",
"disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
"readPoint": {
"id": "urn:acme:bike:station:FrameWelding"
},
"bizLocation": {
"id": "urn:acme:bike:station:FrameWelding"
}
}
]
}
}
35 changes: 35 additions & 0 deletions demo/epcis-bike/fixtures/event-02-Painting.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"@context": {
"@vocab": "https://gs1.github.io/EPCIS/",
"epcis": "https://gs1.github.io/EPCIS/",
"cbv": "https://ref.gs1.org/cbv/",
"type": "@type",
"id": "@id",
"eventID": "@id"
},
"type": "EPCISDocument",
"schemaVersion": "2.0",
"creationDate": "2026-05-12T10:15:00.000Z",
"epcisBody": {
"eventList": [
{
"eventID": "urn:uuid:2a0d116a-a10e-58b7-b6b2-73024ed64fad",
"type": "ObjectEvent",
"eventTime": "2026-05-12T08:42:00.000Z",
"eventTimeZoneOffset": "+00:00",
"epcList": [
"urn:acme:bike:item:BIKE-2026-W18-0001"
],
"action": "OBSERVE",
"bizStep": "https://ref.gs1.org/cbv/BizStep-assembling",
"disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
"readPoint": {
"id": "urn:acme:bike:station:Painting"
},
"bizLocation": {
"id": "urn:acme:bike:station:Painting"
}
}
]
}
}
35 changes: 35 additions & 0 deletions demo/epcis-bike/fixtures/event-03-WheelAssembly.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"@context": {
"@vocab": "https://gs1.github.io/EPCIS/",
"epcis": "https://gs1.github.io/EPCIS/",
"cbv": "https://ref.gs1.org/cbv/",
"type": "@type",
"id": "@id",
"eventID": "@id"
},
"type": "EPCISDocument",
"schemaVersion": "2.0",
"creationDate": "2026-05-12T10:15:00.000Z",
"epcisBody": {
"eventList": [
{
"eventID": "urn:uuid:4a779c36-24bd-5095-b6f5-3e977b150a85",
"type": "ObjectEvent",
"eventTime": "2026-05-12T09:05:00.000Z",
"eventTimeZoneOffset": "+00:00",
"epcList": [
"urn:acme:bike:item:BIKE-2026-W18-0001"
],
"action": "OBSERVE",
"bizStep": "https://ref.gs1.org/cbv/BizStep-assembling",
"disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
"readPoint": {
"id": "urn:acme:bike:station:WheelAssembly"
},
"bizLocation": {
"id": "urn:acme:bike:station:WheelAssembly"
}
}
]
}
}
35 changes: 35 additions & 0 deletions demo/epcis-bike/fixtures/event-04-DrivetrainInstallation.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"@context": {
"@vocab": "https://gs1.github.io/EPCIS/",
"epcis": "https://gs1.github.io/EPCIS/",
"cbv": "https://ref.gs1.org/cbv/",
"type": "@type",
"id": "@id",
"eventID": "@id"
},
"type": "EPCISDocument",
"schemaVersion": "2.0",
"creationDate": "2026-05-12T10:15:00.000Z",
"epcisBody": {
"eventList": [
{
"eventID": "urn:uuid:147d51ac-2a3e-544b-8a8a-b214e85ae333",
"type": "ObjectEvent",
"eventTime": "2026-05-12T09:30:00.000Z",
"eventTimeZoneOffset": "+00:00",
"epcList": [
"urn:acme:bike:item:BIKE-2026-W18-0001"
],
"action": "OBSERVE",
"bizStep": "https://ref.gs1.org/cbv/BizStep-assembling",
"disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
"readPoint": {
"id": "urn:acme:bike:station:DrivetrainInstallation"
},
"bizLocation": {
"id": "urn:acme:bike:station:DrivetrainInstallation"
}
}
]
}
}
35 changes: 35 additions & 0 deletions demo/epcis-bike/fixtures/event-05-PaintInspection.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"@context": {
"@vocab": "https://gs1.github.io/EPCIS/",
"epcis": "https://gs1.github.io/EPCIS/",
"cbv": "https://ref.gs1.org/cbv/",
"type": "@type",
"id": "@id",
"eventID": "@id"
},
"type": "EPCISDocument",
"schemaVersion": "2.0",
"creationDate": "2026-05-12T10:15:00.000Z",
"epcisBody": {
"eventList": [
{
"eventID": "urn:uuid:a4767217-a4a5-5aae-b35c-c881d98a5c35",
"type": "ObjectEvent",
"eventTime": "2026-05-12T09:45:00.000Z",
"eventTimeZoneOffset": "+00:00",
"epcList": [
"urn:acme:bike:item:BIKE-2026-W18-0001"
],
"action": "OBSERVE",
"bizStep": "https://ref.gs1.org/cbv/BizStep-inspecting",
"disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
"readPoint": {
"id": "urn:acme:bike:station:PaintInspection"
},
"bizLocation": {
"id": "urn:acme:bike:station:PaintInspection"
}
}
]
}
}
35 changes: 35 additions & 0 deletions demo/epcis-bike/fixtures/event-06-FunctionalTest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"@context": {
"@vocab": "https://gs1.github.io/EPCIS/",
"epcis": "https://gs1.github.io/EPCIS/",
"cbv": "https://ref.gs1.org/cbv/",
"type": "@type",
"id": "@id",
"eventID": "@id"
},
"type": "EPCISDocument",
"schemaVersion": "2.0",
"creationDate": "2026-05-12T10:15:00.000Z",
"epcisBody": {
"eventList": [
{
"eventID": "urn:uuid:f0426c50-f395-51dd-82c3-ffdf8f8abd44",
"type": "ObjectEvent",
"eventTime": "2026-05-12T10:00:00.000Z",
"eventTimeZoneOffset": "+00:00",
"epcList": [
"urn:acme:bike:item:BIKE-2026-W18-0001"
],
"action": "OBSERVE",
"bizStep": "https://ref.gs1.org/cbv/BizStep-inspecting",
"disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
"readPoint": {
"id": "urn:acme:bike:station:FunctionalTest"
},
"bizLocation": {
"id": "urn:acme:bike:station:FunctionalTest"
}
}
]
}
}
35 changes: 35 additions & 0 deletions demo/epcis-bike/fixtures/event-07-Packing.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"@context": {
"@vocab": "https://gs1.github.io/EPCIS/",
"epcis": "https://gs1.github.io/EPCIS/",
"cbv": "https://ref.gs1.org/cbv/",
"type": "@type",
"id": "@id",
"eventID": "@id"
},
"type": "EPCISDocument",
"schemaVersion": "2.0",
"creationDate": "2026-05-12T10:15:00.000Z",
"epcisBody": {
"eventList": [
{
"eventID": "urn:uuid:b44fdba6-1ce0-5486-a2ff-0b963d3d08eb",
"type": "ObjectEvent",
"eventTime": "2026-05-12T10:15:00.000Z",
"eventTimeZoneOffset": "+00:00",
"epcList": [
"urn:acme:bike:item:BIKE-2026-W18-0001"
],
"action": "OBSERVE",
"bizStep": "https://ref.gs1.org/cbv/BizStep-assembling",
"disposition": "https://ref.gs1.org/cbv/Disp-in_progress",
"readPoint": {
"id": "urn:acme:bike:station:Packing"
},
"bizLocation": {
"id": "urn:acme:bike:station:Packing"
}
}
]
}
}
Loading
Loading