Skip to content

Matter Ecosystem Observations

Who this page is for

Contributors and AI agents working on the Matter side of a bridge. The parity rules are the Matter Behavioural-Parity Contract; an operator-facing guide belongs to the host application (OpenCCU-Loom keeps one at docs/user/matter.md).

Matter controllers do not implement the specification uniformly, and the ways they diverge are not discoverable from the specification. What is known about them is scattered across the code as comments next to the line each finding forced — valuable where it sits, but impossible to answer "what does this project know about Apple Home?" from. This page is that answer.

Every entry names its source. An entry backed by a code comment cites the file; an entry inherited from matter.js cites its document; an entry nobody here has reproduced says so in those words. Nothing on this page is inferred from how a controller "probably" behaves — when the mechanism is unknown, the entry records the observation and stops.

A source path beginning internal/ is OpenCCU-Loom's: the observation was made against that daemon while this module still lived inside it, and the device projection it names is still the host's. Everything else is a path in this repository.


How to read the confidence column

Level Meaning
Reproduced Observed against a real controller during this project's own bring-up, with the wire evidence named in the cited code.
Inherited Taken from matter.js, which encodes it after their own interop testing. Not independently reproduced here.
Reported Third-party report we act on but have not reproduced.

A finding does not become more true by being acted on. Several entries below are Inherited or Reported and shape real code; that is a deliberate choice to follow the gold standard rather than re-derive it, not a claim of measurement.


Apple Home

Apple's HAP-service mapper is the strictest consumer of the bridge's structure. It rebuilds a HomeKit service graph from the Matter topology at pairing time, and it aborts the pairing rather than degrading when the graph does not fit.

Observation Consequence for the bridge Confidence Source
The HAP-service mapper keys its service lookup on the first entry of Descriptor.DeviceTypeList. A list starting with BridgedNode drops the endpoint into an "Other" fallback and the bridge renders as unsupported. DeviceTypeList is always emitted primary-first, BridgedNode last. Reproduced go-fabric endpoint/materialize.go
The mapper validates the (deviceType, revision) tuple against an internal whitelist. A mismatch surfaces as "Attribute report is not parsed into a known struct" and aborts the rebuild with HAPErrorDomain Code=14. Device-type revisions are codegen'd from the matter.js snapshot, never hand-written. Reproduced go-fabric endpoint/helpers.go
An endpoint without Identify in its ServerList fails the rebuild with HAPErrorDomain Code=24, regardless of how complete the rest of the surface is. Identify is mounted on every bridged endpoint before the source's own clusters. Reproduced go-fabric endpoint/materialize.go
An endpoint advertising a cluster ID matter.js does not know (the draft Schedules cluster 0x0024) is rejected with HAPErrorDomain Code=24, killing the pair after Subscribe-Initial. Verified by decoding the outbound Subscribe-Initial: four Thermostat endpoints carried 0x24 with Status_0x84, and Apple sent RemoveFabric immediately after. The Schedules infrastructure stays unmounted until a canonical cluster ships. Reproduced internal/model/custom/climate/matter.go
Missing BridgedDeviceBasicInformation or Descriptor on a bridged endpoint aborts the pair with HMMTRErrorDomain Code 9 ("no topology / no link layer") roughly 9 s after CommissioningComplete. Empty fields inside BridgedDeviceBasicInformation surface as HAPErrorDomain Code=24. Both clusters are mounted unconditionally, with VendorName / ProductName / SerialNumber / UniqueID populated. Reproduced go-fabric endpoint/materialize.go
The post-CommissioningComplete flow writes AccessControl.ACL to install HomePod / Apple TV edge controllers as additional Administer subjects. Without a decoder for that structured write the WriteResponse never lands, Apple times out after 10 s and tears down the fabric via RemoveFabric. Every writable list/struct attribute needs an explicit decoder case; a fall-through reaches MatterWrite as nil. Reproduced go-fabric bridge/attribute_value_reader.go
A CountryCode silently decoded as "" had Apple sending RemoveFabric about 80 s into the pair. UTF-8 TLV fields are read from el.String, never el.Octets. Reproduced go-fabric bridge/fields_reader.go
Cancelling a pairing in Apple Home and retrying returns BUSY for up to 900 s when the aborted attempt left the fail-safe armed. RevokeCommissioning expires the fail-safe unconditionally, before the window-state check. Reproduced go-fabric bridge/commissioning_window.go
Parallel CASE sessions are opened from one controller, and Sigma3 is retransmitted by the MRP layer. Sigma2 replies are deduplicated per exchange ID. Reproduced go-fabric bridge/bridge.go, bridge/case_provider.go
Per-boot rotation of UniqueID breaks the HAP-service mapper's device identity across restarts. UniqueID is stable across restarts; rotation exists only as a deliberate debug escape. Reproduced go-fabric bootid/bootid.go
Names for the bridge itself are ignored at pairing; devices show as "Matter Accessory". Per-device names from BridgedDeviceBasicInformation do apply. Nothing to do — the per-device path is the one that works. Inherited matter.js docs/KNOWN_ISSUES
The root endpoint is queried for BridgedDeviceBasicInformation (0x39) and OTA (0x2a), neither of which exists there per the specification. The resulting Status 195 in the log is expected. Do not "fix" these by mounting the clusters on the root endpoint. Inherited matter.js docs/KNOWN_ISSUES
Deleting a device from Apple Home does not remove every fabric; the leftover may need clearing from Apple system settings. Nothing to do; relevant when diagnosing a stale fabric. Inherited matter.js docs/KNOWN_ISSUES

Amazon Alexa

Alexa is the least tolerant of structural surprises and the slowest to adopt new device types. Two of its constraints shape the bridge's topology.

Observation Consequence for the bridge Confidence Source
A bridged endpoint is recognised only by the clusters its device type specifies. An end device carrying a cluster the Device Library does not name for it as mandatory or optional is recognised as the reduced type — a temperature sensor with an added humidity cluster registers as temperature only. The device-type conformance guards. See below. Inherited matter.js docs/ECOSYSTEMS, "Composed Devices"
Power-source information belongs at the bridged-node level, not on an endpoint whose device type does not specify it. PowerSource rides on one of the device's own endpoints, where BridgedNode specifies it. Inherited matter.js docs/ECOSYSTEMS
Devices are discovered only on UDP port 5540. 5540 is the default; nothing special is done for Alexa. Inherited matter.js docs/ECOSYSTEMS and KNOWN_ISSUES
An Endpoint 1 must exist beside the root endpoint, either as the main device or an aggregator. Endpoint 1 is always the Aggregator. Inherited matter.js docs/ECOSYSTEMS
Device types introduced after Alexa's bridge support — the Matter 1.3 Water Leak Detector (0x0043) among them — can render an entire bridge unresponsive: every accessory on it stops reacting. The leak measurement class materialises as Contact Sensor (0x0015) instead. Reported pkg/interfaces/matter.go; third-party report via docs/user/matter.md
A bridge exposing too many accessories cannot be paired; the practical ceiling is reported around 80–100. Nothing enforced; operators trim the exposure selection. Reported docs/user/matter.md
The WaterValve device type is unsupported. The irrigation valve is exposed as a plain on/off endpoint (ADR 0049). Reported docs/user/matter.md

Google Home

Observation Consequence for the bridge Confidence Source
Setpoint writes echo the read SystemMode back on state changes; a stale wire value made every Apple/Google setpoint write fail with an IM error. The thermostat projection keeps the mode value current. Reproduced internal/model/custom/climate/matter.go
Slider drags emit 5–10 position commands in quick succession. Cover positions are debounced. Reproduced internal/model/custom/cover/matter_debounce.go
Window Covering is supported for lift only. Nothing to do; tilt is exposed and simply not surfaced there. Inherited matter.js docs/ECOSYSTEMS

Other ecosystems

Ecosystem Observation Confidence Source
Samsung SmartThings Commissioning may report a timeout in the app even when pairing completed successfully. Inherited matter.js docs/KNOWN_ISSUES
Tuya Bridges are not supported at all. Inherited matter.js docs/ECOSYSTEMS and KNOWN_ISSUES
Home Assistant Prefers TemperatureMeasurement over the Thermostat cluster's LocalTemperature for chart history. The reading is available either way: a thermostat channel's ACTUAL_TEMPERATURE materialises as its own TemperatureSensor endpoint. Reported internal/model/custom/climate/matter.go (recorded when the duplicate cluster was removed)

The guards that hold this

Ecosystem behaviour is not testable from here — no controller is in the loop. What is testable is conformance to the specification those controllers read, and that is where the guards sit:

Guard What it holds
TestBridgedEndpointClustersConformToTheirDeviceType Every endpoint source's clusters against its own device type, over the whole hydrated fleet.
TestEveryMandatoryAttributeIsAnswered Every conformance-M attribute of every mounted cluster answers a read.
TestFeatureGatedAttributesAreAnsweredWhenAdvertised An attribute gated on a feature answers a read when the server advertises that feature's bit.
TestEveryMandatoryCommandIsAccepted Every conformance-M command appears in AcceptedCommandList.
TestMeasurementClassProjectsOntoAConformantDeviceType Every measurement class names a device type whose definition permits its cluster.

All five read the same oracle: the matter.js HEAD schema snapshot, which the go-fabric module generates and this repo pins (make sync-matter-schema). They live under tests/integration/ and tests/contract/.

The one thing they cannot do is confirm a real controller's reaction. That needs the chip-tool suite (.github/workflows/chiptool.yml, needs-chiptool label), which is the only place a real commissioner is in the loop.

Adding to this page

Add an entry when a controller's behaviour forces a decision in the code. Cite the file the decision lives in, and pick the confidence level honestly — an Inherited entry acted on is useful, an Inherited entry labelled Reproduced is a trap for whoever revisits it. If the mechanism behind an observation is unknown, record the observation and leave the mechanism blank rather than supplying a plausible one.