This document defines the shared contract baseline for all semantic MQTT buses in this repository.
It exists to keep adapters, historian ingestion, and future buses aligned on the same transport and payload rules.
Bus-specific documents such as home_bus.md and energy_bus.md define their own topic grammar, but they inherit the rules from this shared contract.
The operational namespace under <site>/sys/... is specified in more detail by sys_bus.md.
Two top-level namespaces are reserved:
<site>/<bus>/...<site>/sys/...Examples:
vad/home/bedroom/temperature/bedroom-sensor/valuevad/energy/load/living-room-tv/active_power/valuevad/sys/adapter/z2m-main/errorRules:
<site> MUST be stable and lowercase kebab-case<bus> MUST be a reserved bus identifier such as home, energy, network, compute, vehiclesys is reserved for operational topics and is not a semantic bus; see sys_bus.mdVersioning rule:
schema_refAll semantic buses use the same stream taxonomy:
value: live hot-path semantic sample, whether it represents a measurement, durable state, or transition notificationlast: retained last-known timestamped sample used for cold start and freshness evaluationset: command/request topicmeta: retained metadata for the sibling topic familyavailability: online/offline or degraded health signalRules:
set MUST NOT be retainedlast SHOULD be retainedmeta SHOULD be retainedavailability SHOULD be retained and SHOULD use LWT when supportedvalue, not split it across separate state and event streamsvalue publications when consecutive samples are semantically identicallast whenever the latest observed sample timestamp changesstate and event topics SHOULD be treated as compatibility-only during migration and SHOULD NOT be introduced by new adaptersIf a future need appears for diagnostics, replay, dead-letter handling, or adapter metrics, it MUST be modeled under <site>/sys/..., not by extending semantic bus streams.
Common naming rules:
Identity rules:
meta.source_ref, not in the semantic topic pathThe semantic MQTT bus is a high-efficiency event bus.
It is NOT:
Normative rule:
Canonical publication form:
<site>/<bus>/...0 or 1Discouraged adapter-side publish shape:
{
"topic": "vad/home/bedroom/temperature/bedroom-sensor/value",
"payload": 23.6,
"homeBus": {
"location": "bedroom"
},
"z2mPayload": {
"temperature": 23.6,
"battery": 91
},
"mapping": {
"source_field": "temperature"
}
}
Those structures may exist inside normalization logic, but MUST be stripped before the MQTT publish boundary.
Reason:
Two payload profiles are supported across all buses.
This is the default profile for hot paths.
Examples:
23.641trueonProfile A requirements:
metavalue streamsvalue streams consumed by lightweight clientsProfile A historian rule:
observed_at unless an equivalent timestamp is provided out of bandThis profile is used when timestamp, quality, or extra annotations must travel with each sample.
Canonical shape:
{
"value": 23.6,
"unit": "C",
"observed_at": "2026-03-08T10:15:12Z",
"quality": "good"
}
Optional fields:
published_at: adapter publish timesource_seq: source-side monotonic counter or sequence idannotations: free-form object for low-rate streamsProfile B requirements:
value is REQUIREDobserved_at SHOULD be included when the source provides a timestampquality SHOULD be included if the adapter had to estimate or degrade dataunit MAY be omitted for unitless, boolean, or enum valuesUse Profile B when:
last sample is used for startup decisions and consumers must evaluate whether it is still usableProfile B restriction:
meta, not inside every value sampleEach retained meta topic describes the sibling value and last stream family.
Minimum recommended shape:
{
"schema_ref": "mqbus.home.v1",
"payload_profile": "scalar",
"data_type": "number",
"unit": "C",
"adapter_id": "z2m-main",
"source": "zigbee2mqtt",
"source_ref": "0x00158d0008aa1111",
"source_topic": "zigbee2mqtt/bedroom_sensor",
"precision": 0.1,
"historian": {
"enabled": true,
"mode": "sample"
}
}
Recommended fields:
schema_ref: stable schema identifier such as mqbus.home.v1payload_profile: scalar or envelopedata_type: number, boolean, string, or jsonunit: canonical engineering unit when applicableadapter_id: canonical adapter instance idsource: source system such as zigbee2mqtt, modbus, snmpsource_ref: vendor or physical device identifiersource_topic: original inbound topic or equivalent source pathprecision: numeric precision hintdisplay_name: human-readable labeltags: optional list for analytics and discoveryhistorian: ingestion policy objectHistorian metadata contract:
historian.enabled: booleanhistorian.mode: one of sample, state, event, ignorehistorian.retention_class: optional storage class such as short, default, longhistorian.sample_period_hint_s: optional expected cadenceRules:
meta SHOULD be published before live value traffic for new streamsmeta updates MUST remain backward-compatible for existing consumers during v1meta and continue with degraded defaultsmeta, not repeated on each hot-path value publicationExample:
vad/home/bedroom/temperature/bedroom-sensor/meta -> {"unit":"C","precision":0.1,"adapter_id":"z2m-main"}vad/home/bedroom/temperature/bedroom-sensor/value -> 23.6The following timestamps are distinct:
observed_at: when the source system observed or measured the valuepublished_at: when the adapter normalized and published the messageingested_at: when the downstream worker processed the messageRules:
observed_atobserved_atobserved_at is omitted, historian workers SHOULD use ingested_atquality=estimatedThis rule is the key tradeoff between low-overhead scalar payloads and strict time fidelity.
The following quality values are recommended:
good: trusted value from sourceestimated: value or timestamp estimated by adapterdegraded: source known to be unstable or partially invalidstale: source not updated within expected cadenceinvalid: malformed or failed validationRules:
quality only when good is impliedinvalid payloads SHOULD NOT be emitted on semantic bus topicssysShared defaults:
value: QoS 1, retain false unless a bus-specific contract explicitly requires otherwiselast: QoS 1, retain trueset: QoS 1, retain falsemeta: QoS 1, retain trueavailability: QoS 1, retain trueAdditional rules:
value is the live stream and SHOULD remain lightweightlast is the cold-start bootstrap mechanism for latest known measurements on the semantic buslast SHOULD use Profile B and include observed_atlast sample as the latest known observation, not as proof of freshnessobserved_at, availability, and expected cadence from metavalue publications, but last SHOULD be updated whenever the latest observed sample timestamp changesvalue, not by retaining setmeta, retained last, and retained availabilitySimple set commands may use scalar payloads:
onoff21.5If correlation or richer semantics are required, a JSON envelope is allowed:
{
"value": "on",
"request_id": "01HRN8KZQ2D7P0S4M6B4CJ3M8Y",
"requested_at": "2026-03-08T10:20:00Z"
}
Rules:
valueOperational topics are for adapter health, replay control, and malformed input handling.
Detailed operational namespace rules are defined in sys_bus.md.
Recommended topics:
<site>/sys/adapter/<adapter_id>/availability<site>/sys/adapter/<adapter_id>/stats<site>/sys/adapter/<adapter_id>/error<site>/sys/adapter/<adapter_id>/dlqRecommended uses:
availability: retained adapter livenessstats: low-rate counters such as published points or dropped messageserror: structured adapter errors that deserve operator attentiondlq: dead-letter payloads for messages that could not be normalizedThis keeps operational concerns separate from semantic buses and avoids polluting historian input.
Debugging, replay diagnostics, and adapter-internal observability MUST be published under <site>/sys/..., not embedded into semantic bus payloads.
Retained topics are part of the contract and require explicit lifecycle handling.
Rules:
meta, last, and availability SHOULD be retained when they represent current truthHistorian workers SHOULD apply the following defaults:
value streams by defaultmeta.historian.mode as the semantic category of the value stream, for example sample, state, or eventlast, set, meta, and availability as time-series samplesCurrent PostgreSQL historian compatibility:
tdb_ingestion/mqtt_ingestion_api.mdenergy_total, *_bytes_total, and *_packets_total are valid bus metrics, but the current measurement API does not define their storage semantics; see tdb_ingestion/counter_ingestion_api.mdDefault field mapping:
value from payload or envelopeobserved_at from envelope if present, otherwise ingestion timeunit from envelope if present, otherwise cached retained meta.unitquality from envelope if present, otherwise goodThis allows historian workers to stay generic while bus contracts remain strict.