############### MQTT contract ############### The ``protocol`` package defines schema 1 under ``onehouse/v1``. Device, command, and message IDs use canonical uppercase ULIDs. Capabilities and events use lowercase ASCII letters and digits, with single internal hyphen or period separators. Topic builders reject invalid addresses. Private topic helpers cover device descriptor, availability, reported values, telemetry, events, commands, command batches, results, and Zigbee hub status. Subscription helpers supply the core input filters, each device's command filter, and hub status filter. The command filter includes the reserved ``$batch`` target. Private envelope helpers validate schema, IDs, optional UTC timestamps, and a required object payload. Unknown envelope fields, trailing JSON, unsupported schema versions, and malformed topics are errors. A receiver checks expiry against its own clock. The message ID stays unchanged on retry. Its timestamp is not the observation time. Measurements and online availability repeat at five-minute intervals. The device contact timeout is fifteen minutes. These constants do not run timers or implement publication suppression. ******************** Typed publications ******************** Each operation has a ``Build`` function and a ``Parse`` function, such as ``BuildReported`` and ``ParseReported``. Builders accept an ID, an optional target, metadata, and a typed payload. They validate the same contract as the parsers through shared typed validation. A builder encodes its validated payload without parsing it. Batch members use the same metadata and value validation without envelope encoding. Access flag presence checks apply to parsed JSON only. ``Parse`` selects the typed payload and address kind from an incoming topic. A caller does not split or join topics and does not build an envelope. A ``Publication`` contains topic, JSON bytes, QoS, and retention. It also supplies the expiry deadline, response topic, and correlation data when the operation needs them. The transport uses MQTT 5, sets the payload format to UTF-8 and the content type to ``application/json``, and derives the message expiry interval from the deadline. It rejects expired work before sending or processing it. The protocol package has no broker client or clock worker. .. list-table:: Delivery rules :header-rows: 1 - - Operation - QoS - Retained - - Descriptor, availability, reported, hub status - 1 - Yes - - Telemetry - 0 - No - - Events, commands, command batches, results - 1 - No A parser accepts a live delivery of retained-topic data with its retained flag false. It rejects a retained event, telemetry sample, command, batch, or result. Telemetry uses the same complete value payload as a reported snapshot. The publisher keeps the retained reported snapshot up to date, with QoS 1, without topic string operations: it uses ``BuildReported`` for the same device and capability. ********** Payloads ********** ``DescriptorPayload`` requires nonempty manufacturer, model, firmware, and capabilities. Optional attribution has a 2000-byte limit. Each capability has a unique valid ID and a valid type. Both Boolean access flags must be present. At least one must be true. The descriptor does not contain house metadata. ``AvailabilityPayload`` accepts ``online`` or ``offline``. A shared broker connection cannot have a separate last will for every device. The driver reports device failures and repeats online contact evidence. ``ValuePayload`` requires a JSON value and preserves complete objects. JSON null is a present value. The wire layer does not check capability-specific units, ranges, settings, or physical access. Commands use this payload and require an expiry. The receiver checks ownership, access, and expiry before it acts. ``EventPayload`` is an open object of event data. Every member is application data, not an optional wire field. Empty objects and custom members are valid. This supports exact event matching, physical button actions, and camera motion times without discarding event details. Unknown fields in envelopes and in closed payloads, including nested capabilities, batch members, and hub diagnostics, are rejected. Nested application values and event data are open by definition. ``BatchPayload`` holds 2 through 256 ordered commands for one device. Each member has a capability, metadata, and a value payload. Encoding inserts the member schema. The outer message ID equals the first member ID. Member IDs and capabilities are distinct. Every member requires its own expiry. A malformed member rejects the complete batch. The transport uses the earliest member deadline and no combined response topic. Each member has its own result. ``ResultPayload`` accepts ``accepted``, ``completed``, ``rejected``, or ``failed``. Rejected and failed results require a nonempty reason. Accepted and completed results must not have a reason. The correlation ID must equal the command ID in the topic. A result does not replace physical feedback. Duplicate detection, result ordering, and physical operation safety belong to the driver and core. ``HubStatusPayload`` contains one Zigbee hub snapshot with a matching hub ID, address, status, device counts, and optional pairing and diagnostics. Status is ``ready`` or ``unavailable``. Hub timestamps use UTC. Its envelope requires expiry. ``HubStatusLifetime`` calculates two poll intervals plus 50 seconds. The publisher refreshes status at the poll interval and after reconnect. It preserves diagnostic observation time and stale flags. Consumers reject expired snapshots and check source age before using a diagnostic value. Hub status is not core house-state input. ************** Verification ************** Unit tests cover all topic and payload forms, delivery rules, invalid fields and schemas, batch order and bounds, correlation, and expiry metadata. These tests do not verify a broker, a physical device, MQTT acknowledgement, reconnect behavior, publication suppression, or command execution.