Skip to content

feat: ClientNode controller API additions + deprecate legacy controller API - #4091

Open
Apollon77 wants to merge 72 commits into
mainfrom
feat/clientnode-controller-api
Open

feat: ClientNode controller API additions + deprecate legacy controller API#4091
Apollon77 wants to merge 72 commits into
mainfrom
feat/clientnode-controller-api

Conversation

@Apollon77

Copy link
Copy Markdown
Collaborator

Summary

Adds the ClientNode-based controller convenience API that closes the gaps blocking deprecation of the legacy PairedNode / CommissioningController, and deprecates that legacy API for 0.19 removal. Everything is a thin, additive surface over existing machinery; legacy PairedNode/CommissioningController stay fully functional through the deprecation period.

Implemented from the plan docs/superpowers/plans/2026-07-14-clientnode-controller-api-additions.md.

What's added

  • Behaviors.forCluster(clusterId) + a model-scoped cluster name↔id resolver (relocated to @matter/types ClusterHelper, now with an optional MatterModel param defaulting to global Matter).
  • Peers.commissioned — commissioned operational peers (excludes commissionable discoveries and group nodes).
  • NodeLifecycle.isSeeded / seeded — node structure read at least once (BasicInformation present and beyond the root endpoint); seeded emits once. Also fixes Endpoints.size double-counting the root endpoint.
  • Commissioning-window helpers on CommissioningClient + ClientNode: openBasicCommissioningWindow / openEnhancedCommissioningWindow (returns { manualPairingCode, qrPairingCode }).
  • Peer.establishmentUnresponsive (@matter/protocol) — fires once when a CASE (re)establishment attempt retransmits past the MRP budget; observation-only, retransmission behavior unchanged.
  • Connection-state engine on NetworkClient exposed via NodeLifecycle: NodeConnectionState (enum values pinned to legacy NodeStates: Connected=0, Disconnected=1, Reconnecting=2, WaitingForDeviceDiscovery=3), connectionState, connectionStateChanged, isConnected. Disconnected = stopped/not-started/disabled; lifecycle on start is Disconnected → Reconnecting → Connected.

Deprecation

The legacy @project-chip/matter.js controller/device API (CommissioningController, PairedNode, Device/Endpoint/Aggregator/ComposedDevice, the cluster clients, and their public members — ~330 @deprecated tags) is marked deprecated for removal in 0.19, pointing at docs/MIGRATION_CONTROLLER_018.md (WIP guide included here).

Testing

Full gate green at HEAD: build --clean, format-verify, lint, and package suites — @matter/types 374/374, @matter/protocol 1450/1450, @matter/node 1471/1471, @project-chip/matter.js 5/5 (ESM/CJS/Web). New test files cover each addition with real-behavior assertions (idempotency, recovery, emit-once, transition ordering).

Reviews

Per-task spec+quality reviews (all clean) + a final independent adversarial whole-branch review (Ready to merge, 0 Critical/Important) + a "greg" architectural review (Approve-with-fixes — ClusterNaming relocation applied) + a second independent adversarial pass (Ready to merge). The two highest-risk paths — a synchronous signal emitted inside the MRP retransmission path, and reactor re-registration across stop/start — were each traced to correct handling (offline-transaction fault isolation; Reactors.add dedup).

Follow-ups (not in this PR)

  • Hard-send-failure (ENETUNREACH) as a third likelyOffline trigger — needs a new Peer-level observable; connection state currently degrades to Reconnecting in that window (mDNS still recovers).
  • Peers.#instrumentBasicInformation registers non-self-disposing .on handlers (pre-existing) that could accumulate if clusterInstalled re-emits.
  • Fill remaining MIGRATION_CONTROLLER_018.md sections (events bus, subscription examples, FAQ) alongside the shell + server migrations.

🤖 Generated with Claude Code

Apollon77 and others added 11 commits July 14, 2026 17:01
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lientNode

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…et on CASE reconnect

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Lifecycle

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Disconnected now means the network client is stopped, not started or
disabled, driven by lifecycle.shouldBeOffline instead of !isCommissioned.
A decommissioned node is destroyed and no longer drives connection state.
Reset the likelyOffline latch on the Disconnected branch so a re-enabled
or restarted node does not resume in WaitingForDeviceDiscovery, and set
targetState offline in ClientNode.disable() so disabling settles straight
to Disconnected without a spurious Reconnecting emission.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… ICD wiring

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ith optional model

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oval

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 14, 2026 21:05

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a set of controller-facing convenience APIs centered on ClientNode (controller peers) to enable deprecation of the legacy CommissioningController / PairedNode controller surface, while keeping the legacy API functional through the deprecation window.

Changes:

  • Added new ClientNode controller conveniences (commissioning-window helpers, commissioned-peer enumeration, seeded/connection-state lifecycle signals).
  • Added model-scoped cluster name/id resolution helpers and Behaviors.forCluster(clusterId) for cluster→behavior lookup.
  • Marked the legacy controller/device APIs in @project-chip/matter.js as deprecated for removal in 0.19 and added an initial migration guide + changelog entries.

Reviewed changes

Copilot reviewed 35 out of 35 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
packages/types/test/cluster/ClusterNamingTest.ts Adds tests for new cluster name↔id helper resolution (including custom MatterModel).
packages/types/src/cluster/ClusterHelper.ts Extends cluster/endpoint name resolution to accept a MatterModel and adds attribute/command/event id↔name helpers.
packages/protocol/test/peer/PeerEstablishmentProgressTest.ts Adds tests for establishment-unresponsive detection logic and MRP-driven behavior.
packages/protocol/src/peer/PeerConnection.ts Adds retransmission observer hook + establishmentUnresponsiveDetector and wires it into CASE establishment.
packages/protocol/src/peer/Peer.ts Exposes peer.establishmentUnresponsive observable for higher layers to react to likely-offline detection.
packages/node/test/node/NodeLifecycleSeededTest.ts Adds tests for NodeLifecycle.isSeeded/seeded latch behavior and listener disposal.
packages/node/test/node/client/PeersCommissionedTest.ts Adds tests for Peers.commissioned filtering behavior.
packages/node/test/endpoint/properties/BehaviorsForClusterTest.ts Adds tests for Behaviors.forCluster(clusterId).
packages/node/test/behavior/system/network/ConnectionStateTest.ts Adds tests for NodeLifecycle connection-state transitions and idempotency.
packages/node/test/behavior/system/commissioning/CommissioningWindowTest.ts Adds tests for basic/enhanced commissioning-window helpers and revoke tolerance.
packages/node/src/node/NodeLifecycle.ts Adds seeded tracking + connection-state enum/observables and convenience accessors.
packages/node/src/node/ClientNode.ts Adds commissioning-window helpers and adjusts disable flow to avoid transient connection-state artifacts.
packages/node/src/node/client/Peers.ts Adds peers.commissioned and seeded evaluation/latching logic for peers.
packages/node/src/endpoint/properties/Endpoints.ts Fixes endpoints.size to avoid double-counting the root endpoint.
packages/node/src/endpoint/properties/Behaviors.ts Adds Behaviors.forCluster(clusterId) lookup.
packages/node/src/behavior/system/network/NetworkClient.ts Adds connection-state recomputation engine and likely-offline latch wiring.
packages/node/src/behavior/system/commissioning/CommissioningClient.ts Adds openBasicCommissioningWindow / openEnhancedCommissioningWindow helpers + revoke tolerance.
packages/matter.js/src/PaseCommissioner.ts Marks legacy commissioner wrapper API deprecated (0.19 removal).
packages/matter.js/src/device/PairedNode.ts Marks legacy PairedNode controller API surface deprecated (0.19 removal).
packages/matter.js/src/device/Endpoint.ts Marks legacy endpoint wrapper API surface deprecated (0.19 removal).
packages/matter.js/src/device/DeviceTypes.ts Marks legacy device types helpers deprecated (0.19 removal).
packages/matter.js/src/device/DeviceInformation.ts Marks legacy device-info wrapper types deprecated (0.19 removal).
packages/matter.js/src/device/Device.ts Marks legacy device wrapper classes deprecated (0.19 removal).
packages/matter.js/src/device/ComposedDevice.ts Marks legacy composed-device wrapper deprecated (0.19 removal).
packages/matter.js/src/device/Aggregator.ts Marks legacy aggregator wrapper deprecated (0.19 removal).
packages/matter.js/src/ControllerStore.ts Marks legacy controller store surface deprecated (0.19 removal).
packages/matter.js/src/CommissioningController.ts Marks legacy controller/discovery APIs deprecated (0.19 removal).
packages/matter.js/src/cluster/client/InteractionClient.ts Marks legacy interaction client and related types deprecated (0.19 removal).
packages/matter.js/src/cluster/client/EventClient.ts Marks legacy event client deprecated (0.19 removal).
packages/matter.js/src/cluster/client/DecodedDataReport.ts Marks legacy decoded-report types/helpers deprecated (0.19 removal).
packages/matter.js/src/cluster/client/ClusterClientTypes.ts Marks legacy cluster client types deprecated (0.19 removal).
packages/matter.js/src/cluster/client/ClusterClient.ts Marks legacy cluster client factory APIs deprecated (0.19 removal).
packages/matter.js/src/cluster/client/AttributeClient.ts Marks legacy attribute client deprecated (0.19 removal).
docs/MIGRATION_CONTROLLER_018.md Adds initial (WIP) migration guide from legacy controller API to ClientNode.
CHANGELOG.md Documents new controller conveniences and legacy API deprecation in WIP changelog.

Comment thread packages/matter.js/src/CommissioningController.ts
Apollon77 and others added 4 commits July 15, 2026 08:06
…plicate side effects

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…urrent API

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@mergify

mergify Bot commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Tick the box to add this pull request to the merge queue (same as @mergifyio queue).

  • Queue this pull request

Apollon77 and others added 6 commits July 15, 2026 11:34
…migration guide

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ation guide

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…te stream

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…fecycle

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…namespace

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Apollon77
Apollon77 marked this pull request as draft July 17, 2026 11:00
Apollon77 and others added 4 commits July 18, 2026 16:23
…Node

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e; explicit return type

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oningController

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…, discovery data

- Interim per-project recipe for propagating the fabric label to peers (until the
  node-management layer lands); UpdateFabricLabel targets the accessing fabric
- How to read the negotiated subscription interval (ClientSubscription.maxInterval)
- Full mDNS DiscoveryData fields (RI/PH/PI/T/SII/SAI) on discovered nodes and
  peer.descriptor.discoveryData — no gap vs the legacy discovery record

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apollon77 and others added 13 commits July 20, 2026 11:33
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…issioning failure

- Set discoveryData on the live Peer.descriptor (the CommissioningClient.descriptor
  write landed after forAddress already bound the peer, so it never took effect)
- Delete the node on any start()/commissioningComplete() throw, not only on a
  non-Ok errorCode, so a failed finalize cannot leave a phantom commissioned peer;
  keep setStateOf/persist outside the try so a post-success persist failure does
  not orphan a genuinely commissioned device

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…mmissioned peer

completeCommissioning() called forAddress(), which returns the existing
node for an already-commissioned address instead of always creating one,
and its catch block unconditionally deleted that node on any failure —
including a non-throwing NoFailSafe errorCode from a duplicate or retried
CommissioningComplete. Guard with a non-creating lookup before forAddress()
so an already-commissioned peer is rejected up front instead of reaching
the delete path.
…ssioning tests

The commissionable re-announcement is an async reaction to sessions.deleted that can
settle after the failsafe-destroyed signal, so asserting adSpy.count on failsafeGone
alone races under load (intermittent low-rate flake). Await the advertisement itself
via a spy-armed promise alongside the existing signal.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…e timeout in completeCommissioning

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…oth-or-nothing)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ric is absent

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…dentifier

Remove comments that restate the next line or the test title; collapse the
triplicated re-announcement-race rationale to one canonical doc + pointers; fix a
comment referencing a non-existent completeCommission().

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…contract

Clarify that finalizeCommissioning must drive the completion to done and only then
resolve (throw to roll back), so commission() reflects the real outcome and the PASE
session is not torn down mid-handoff. Update the migration-guide recipe to await
completeCommissioning inside the hook, and note in the split-commissioning test why it
completes in a second phase (two same-fabric-identity controllers cannot run the nested
flow concurrently in one process).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 62 out of 63 changed files in this pull request and generated 2 comments.

Comment thread packages/nodejs-shell/src/shell/cmd_discover.ts
Comment thread packages/node/src/behavior/system/commissioning/CommissioningClient.ts Outdated
Apollon77 and others added 3 commits July 20, 2026 21:46
- completeCommissioning now reads the node's structure (isNewlyCommissioned) and
  subscribes unless opted out, mirroring commission(), so the finalized peer is seeded
  rather than blind (lifecycle.seeded would otherwise never emit); adds an options arg
  and a regression assertion
- await the MaybePromise from node.act(subscriptionActive) in the shutdown/leave guards
- drop redundant `as ServerNode` casts, fix a copy-paste JSDoc on peers.get(), stop
  Factory.create mutating the caller's options, rename a shadowed destructure
- CHANGELOG: correct isSeeded location (ClientNodeLifecycle) and add the missing
  ClientNode-API entries (completeCommissioning, connection state, forCluster,
  disable/enable, commissioned view, ClusterLookup)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- cmd commission: omit the passcode from the debug dump entirely (spread+override still
  flows it through the logged object) so CodeQL's clear-text-logging alert clears
- openEnhancedCommissioningWindow: throw a clear error before opening the window if the
  peer's BasicInformation is not seeded, instead of a cryptic behavior-not-found throw
  when reading the VID/PID encoded into the QR code
- cmd discover: sort the address-fallback dedupe key so re-advertisement in a different
  order is not treated as a new device

Copilot typo (CommissioningController "at least") and the passcode/establishment-observer
threads were already correct/fixed on this branch — no change needed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 62 out of 63 changed files in this pull request and generated 2 comments.

Comment thread packages/nodejs-shell/src/shell/cmd_commission.ts
Comment thread packages/nodejs-shell/src/util/awaitSeeded.ts
Apollon77 and others added 3 commits July 21, 2026 14:56
Passing ip without port (or vice versa) silently fell through to
discovery commissioning. Reject the partial address instead.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Apollon77 and others added 2 commits July 27, 2026 17:18
…OGRESS

0.17.7 released without this branch; its entries were sitting under the
released heading. Relocate them to a WORK IN PROGRESS section on top.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants