Skip to content

Verified: Publishing to MQTT (Sparkplug B)

Each claim the Publishing to MQTT (Sparkplug B) page makes, with the tests that prove it in the last green CI run: 23 of 25 verified. The claims live in docs/claims/sparkplug.yaml.

Last green CI run 16 Oct 2026, 14:48 UTC on c7618d3 · all runtime claims

SPB-001 verified

The node's birth certificate (NBIRTH) lists the whole tag store with names, types and current values, and each tag's `unit:` and `desc:` arrive as the `engUnit` and `documentation` metric properties, including on template members.

where the docs say so

  • pass TestBirthStatesUnitAndDescAsProperties in sparkplug
  • pass TestBirthListsTheWholeTagStoreWithTypesAndValues in sparkplug
  • pass TestPropertiesRoundTrip in sparkplug
  • pass TestTCKConformance in sparkplug

SPB-002 verified

Types map faithfully: BOOL to Boolean, integers to Int64, REAL to Double, UDT to Template.

where the docs say so

SPB-003 verified

A host can write the tags `writable:` names back via NCMD: a scalar command overwrites the tag in the store for the program to act on.

where the docs say so

  • pass TestWriteGateOpensMatchingTagsOnly in sparkplug
  • pass TestCommandScalarStillReplaces in sparkplug

SPB-005 verified

`Node Control/Rebirth` is honored: the node answers the command with a fresh NBIRTH.

where the docs say so

  • pass TestTCKConformance in sparkplug
  • pass TestRebirthCommandStopsDataUntilTheNBIRTH in sparkplug

SPB-006 verified

Only `broker` and `group-id` are required.

where the docs say so

SPB-007 verified

The password is never read from the manifest: it comes from `NAUTILUS_MQTT_PASSWORD`.

where the docs say so

  • pass TestSparkplugPasswordComesFromTheEnvironment in internal/project (3 of 3 passed)

SPB-008 verified

`ssl://host:8883` brokers connect over TLS.

where the docs say so

  • pass TestEdgeNodeConnectsOverTLS in sparkplug

SPB-009 verified

`edge-node` defaults to the project name.

where the docs say so

SPB-010 verified

`deadband` publishes a numeric only when it moved more than that in absolute units; zero publishes on any change.

where the docs say so

  • pass TestRBEDeadbandAndHeartbeat in sparkplug
  • pass TestRBEZeroDeadbandPublishesAnyChange in sparkplug
  • pass TestRBEDeadbandOnStructMembers in sparkplug
  • pass TestRBEDeadbandOnNestedStruct in sparkplug
  • pass TestSparkplugManifestClassesReachTheNode in internal/project

SPB-011 verified

`min-interval` means never publishing more often than that, even on change.

where the docs say so

  • pass TestRBEDeadbandAndHeartbeat in sparkplug
  • pass TestRBEMinIntervalStillAppliesToStructs in sparkplug
  • pass TestSparkplugManifestClassesReachTheNode in internal/project

SPB-012 verified

`max-interval` publishes at least that often even when the value is unchanged (heartbeat).

where the docs say so

SPB-013 verified

`every-change: true` publishes every transition unconditionally, ignoring deadband and rate limits, while an unchanged sample stays quiet.

where the docs say so · TestRBEDisableAndBoolChange: a transition inside min-interval publishes and an unchanged sample stays quiet. TestSparkplugManifestClassesReachTheNode: a 0.001 move under a 100 deadband, inside a 1 h min-interval, publishes.

SPB-014 verified

`metric-classes` assigns tags to a named class by glob, and every tag unassigned uses `default-class`.

where the docs say so

SPB-015 partly verified

Every field connection publishes as its own Sparkplug device under the edge node, carrying the input tags it delivers (its `__Online` companion included); computed tags, program state, outputs and every tag of the `memory` loopback publish at node level.

where the docs say so · One device per connection carrying its inputs and `__Online`, the memory loopback making none, and tags outside a device's list riding the NBIRTH are asserted. That outputs publish at node level is not, and the code disagrees for modbus: a writable binding that is also polled (not `writeonly`) is listed in its source's Connections() inputs, so that output rides the device. TestMetricsSplitBetweenDevicesAndTheNode only shows the node honouring the device lists it is handed.

SPB-016 verified

A device's DBIRTH/DDEATH follow its connection's health: when one link drops the host sees that device die rather than stale values, while every other device stays born.

where the docs say so

  • pass TestOneDeviceDyingLeavesTheOthersBorn in sparkplug
  • pass TestDeviceBirthWaitsForFirstRead in sparkplug

SPB-017 verified

With `primary-host` set, the node defers its birth until the host's STATE is ONLINE, and both the 2.x (plain ONLINE/OFFLINE) and 3.0 (JSON) STATE encodings are accepted. By default (`primary-host-offline: buffer`), when the host goes offline the node stays connected and born (no NDEATH) and stops publishing data, which is buffered and replayed as historical with store-forward and not sent without it.

where the docs say so

  • pass TestParseState in sparkplug
  • pass TestPrimaryHostDefersTheBirthUntilOnline in sparkplug (3 of 3 passed)
  • pass TestNoPrimaryHostBirthsOnConnect in sparkplug
  • pass TestStoreForwardBuffersWhileThePrimaryHostIsOffline in sparkplug
  • pass TestPrimaryHostOfflineWithoutStoreForwardSendsNothing in sparkplug
  • pass TestPrimaryHostOfflineDefaultKeepsTheSession in sparkplug

SPB-026 verified

With `primary-host-offline: disconnect`, when the host goes offline the node publishes NDEATH (the ending session's bdSeq) before a clean disconnect, connects again with the next bdSeq, and births only once the host is ONLINE again; with store-forward it buffers meanwhile and replays as historical after the new birth. An offline STATE older than the last online one is ignored. The key needs `primary-host`.

where the docs say so

  • pass TestPrimaryHostOfflineDisconnectEndsTheSession in sparkplug
  • pass TestHostOfflineDisconnectDoesNotBirthWhileTheHostIsOffline in sparkplug
  • pass TestSparkplugPrimaryHostOffline in internal/project

SPB-018 verified

With `store-forward: N`, data messages are buffered while the broker is unreachable (or the primary host is offline) and replayed, marked historical, once delivery resumes.

where the docs say so

  • pass TestStoreForwardBuffersAcrossABrokerOutage in sparkplug
  • pass TestBrokerDropStartsANewSessionWithTheNextBdSeq in sparkplug
  • pass TestStoreForwardBuffersWhileThePrimaryHostIsOffline in sparkplug
  • pass TestNoPublishWhileTheConnectionIsNotOpen in sparkplug
  • pass TestTimedOutTickIsBufferedWhenStoreForwardIsOn in sparkplug

SPB-019 verified

The store-and-forward buffer is a bounded ring that drops the oldest record when full, and the drain is rate-limited.

where the docs say so

  • pass TestStoreForwardRingAndDrain in sparkplug
  • pass TestStoreForwardRingDropsOldestAndDrainsInBatches in sparkplug

SPB-028 verified

With `store-forward-file`, the store-and-forward buffer is journaled to that file as it fills, so a process exit during an outage does not lose it: the next start loads it and replays it, marked historical and with its original timestamps, after the first birth (whether the outage was the broker or the primary host, in either `primary-host-offline` mode), then clears the file. The file is bounded like the ring; a torn last record is skipped with a log line and an unreadable file is set aside. Without the key nothing is written to disk.

where the docs say so

  • pass TestStoreForwardFileSurvivesARestartDuringABrokerOutage in sparkplug
  • pass TestStoreForwardFileSurvivesARestartDuringAHostOutage in sparkplug (3 of 3 passed)
  • pass TestStoreForwardFileRoundTrip in sparkplug
  • pass TestStoreForwardFileIsBoundedLikeTheRing in sparkplug
  • pass TestStoreForwardFileSkipsATornLastRecord in sparkplug
  • pass TestStoreForwardFileSetsAnUnreadableFileAside in sparkplug
  • pass TestStoreForwardFileMisconfiguration in sparkplug
  • pass TestStoreForwardWithoutAFileTouchesNoDisk in sparkplug
  • pass TestSparkplugStoreForwardFile in internal/project

SPB-022 verified

`bdseq-file` persists the birth-death sequence number across restarts; without it bdSeq restarts at 0 each run.

where the docs say so

  • pass TestBdSeqFilePersistsAcrossRestarts in sparkplug

SPB-027 verified

A dropped broker connection is a new session: the broker's will carries the old bdSeq, and the node reconnects with the next bdSeq, persisted to `bdseq-file`, in both its new will and its new NBIRTH, so a host applies the next death and ignores a late will from the old connection. Commands, the primary host's STATE and store-and-forward carry on across the reconnect.

where the docs say so

  • pass TestBrokerDropStartsANewSessionWithTheNextBdSeq in sparkplug
  • pass TestBrokerDropResubscribesToThePrimaryHost in sparkplug
  • pass TestBrokerDropThenHostOfflineDisconnect in sparkplug
  • pass TestEdgeBrokerDropIsANewSessionToTheHost in sparkplug/host

SPB-023 verified

The node passes the Sparkplug TCK edge-node profile, run against a live node on every push.

where the docs say so · Runs only with NAUTILUS_TCK=1 in the sparkplug-tck CI job (strong evidence there; skipped locally).

SPB-024 verified

MQTT and protobuf live only in the sparkplug package; the runtime core's one dependency outside the Go standard library is gopkg.in/yaml.v3.

where the docs say so

  • pass TestMQTTAndProtobufLiveOnlyInTheSparkplugPackage in sparkplug
  • pass TestRuntimeCoreDoesNotDependOnMQTTOrProtobuf in sparkplug
  • pass TestRuntimeCoreDependsOnlyOnStdlibAndYAML in sparkplug

SPB-025 partly verified

`driver: {type: sparkplug-host}` presents an edge-node group as input tags in a central project, with writes going back as NCMD/DCMD.

where the docs say so · Covered in depth by the sparkplug-host claims file.