Traceable Production Audit Trails with JetStream Sourcing
A manufacturing use case: every production-line event captured immutably in JetStream for compliance audits, plus stream sourcing/mirroring to combine multiple lines into one traceability view without republishing anything.
- nats
- jetstream
- manufacturing
- compliance
- tutorial
The scenario
A factory with several production lines must keep an immutable, queryable audit trail of every step a part goes through (for recalls and regulatory compliance) — while each line's own systems still want their own independent, line-scoped stream for local tooling.
Step 1 — server + one stream per line
docker run -d --name nats-server -p 4222:4222 -p 8222:8222 -v nats-js-store:/data nats:latest -js -sd /data
nats context add local --server="nats://localhost:4222" --selectnats stream add LINE_1 \
--subjects "line.1.event" \
--storage file --retention limits \
--max-age=365d --max-msgs=-1 --max-bytes=-1 --defaultsnats stream add LINE_2 \
--subjects "line.2.event" \
--storage file --retention limits \
--max-age=365d --max-msgs=-1 --max-bytes=-1 --defaultsStep 2 — each line publishes independently
nats pub line.1.event '{"part":"P-1001","step":"weld","result":"pass"}'
nats pub line.1.event '{"part":"P-1002","step":"weld","result":"fail","reason":"misalign"}'
nats pub line.2.event '{"part":"P-2001","step":"paint","result":"pass"}'Step 3 — a factory-wide audit stream, sourced from both, no republishing
Instead of every line also publishing a second copy to a shared subject (which risks drift between the two), a sourced stream pulls messages from other streams directly, at the JetStream level:
nats stream add FACTORY_AUDIT \
--source LINE_1 --source LINE_2 \
--storage file --retention limits \
--max-age=2555d --max-msgs=-1 --max-bytes=-1 --defaults--max-age=2555d (~7 years) reflects a typical regulatory retention requirement — tune this to the
actual compliance rules that apply.
nats stream view FACTORY_AUDIT --subject "line.1.event"Every event published to either line's own stream now also appears — immutably, in original
publish order per source — in FACTORY_AUDIT, with no line's software needing to know the audit
stream exists.
Step 4 — mirroring: an identical read-only copy for a separate compliance cluster
Sourcing combines and can transform subjects; mirroring instead makes an exact 1:1 copy of a single stream — useful for handing an unmodifiable, identical replica to a separate, access-restricted compliance environment:
nats stream add FACTORY_AUDIT_MIRROR \
--mirror FACTORY_AUDIT \
--storage file --max-age=2555d --max-msgs=-1 --max-bytes=-1 --defaultsTroubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
FACTORY_AUDIT is missing events published before it was created | Sourcing only forwards messages from the point the source stream had them retained, not retroactively for messages already deleted by the source's own retention | Create the audit/compliance stream before or very early alongside line streams, and keep line streams' own retention generous enough to overlap |
nats stream add FACTORY_AUDIT --source ... fails validation | Typo'd source stream name, or source stream doesn't exist yet | nats stream ls to confirm exact stream names before wiring sourcing |
| Compliance team sees duplicate-looking entries | Both a line's own stream and a manually-duplicated publish path exist (i.e. sourcing wasn't used, the line republishes manually) | Prefer stream sourcing over any manual "publish twice" pattern — it's exactly what avoids this class of drift/duplication bug |
| Mirror stream falls behind the source | Normal under network partition — mirrors catch up once connectivity resumes | Monitor nats stream info FACTORY_AUDIT_MIRROR for lag; alert if it grows unexpectedly, rather than assuming instant consistency |
Recap
| Concept | Takeaway |
|---|---|
| One stream per line | Keeps each line's local tooling independent and simple |
--source A --source B | Combines multiple streams' messages into one, without any producer changes |
--mirror | An exact, separately-retained read-only copy — good for isolated compliance environments |
Long --max-age | Model the actual regulatory retention period, not an arbitrary default |