Skip to content

fix(node): default server exports no longer inherit base implementation features - #4159

Open
Apollon77 wants to merge 5 commits into
mainfrom
fix/cluster-feature-defaults
Open

fix(node): default server exports no longer inherit base implementation features#4159
Apollon77 wants to merge 5 commits into
mainfrom
fix/cluster-feature-defaults

Conversation

@Apollon77

Copy link
Copy Markdown
Collaborator

Fixes #4145.

Problem

A cluster's own feature table was enabling features, and the mechanism that reverted a feature selection had silently stopped working.

Two independent causes:

  1. Spec fallback ingested as "supported". translate-cluster.ts mapped the spec feature table's def/fallback column into the model's default, which ClusterModel.supportedFeatures reads as "this feature is on". The spec lists | 0 | OO | OnOff | O | … | 1 | for LevelControl — that trailing 1 is a fallback, not a default selection. Groups.GN carried a hand-set default from an old bulk commit.

  2. ClusterBehavior.for() stopped reverting features. syncFeatures() read namespace.supportedFeatures, but since 780d1d05b (2026-03-22, the ClusterNamespace codegen refactor) cluster namespaces no longer carry that property, so the check became a permanent no-op. Eleven XxxServer extends XxxBaseServer.for(Xxx) exports therefore published the features their base enables internally. First released in 0.17.0; 0.16.x is unaffected.

The reporter's symptom: LevelControlServer leaked Lighting, so a PumpDevice enforced the [LT] MinLevel 1–254 constraint and rejected minLevel: 0.

Rule

No feature is enabled on a per-cluster basis. Only a device type requirement or an explicit implementation decision enables one.

Enforced at three layers: codegen can't reintroduce the fallback; MatterDefinitionTest asserts no standard cluster enables a feature by default; a sweep over all behavior exports asserts only nine allowlisted features are on.

Changes

Model / codegen

  • drop the feature fallback mapping; remove the four stale defaults from groups and level-control
  • new FeatureSelectionErrors() assesses a selection against FeatureMap conformance — choice groups (O.a, .a+, .a-), mandatory-given-others, disallowed-given-others

Behaviors

  • 13 server exports use .with() as the single feature-drop mechanism
  • ClusterBehavior.with() throws on a feature the cluster does not define instead of silently selecting nothing
  • EnergyEvseServer selects ChargingPreferences — the only unconditionally mandatory feature in the model, and without it EnergyEvseDevice could not be instantiated at all
  • a LIT ICD must select CheckInProtocolSupport and UserActiveModeTrigger (spec: UAT: LITS, O); the previous docstring recommended an invalid combination

ValidationFeatureSelectionError at endpoint init. It cannot run at type-construction time: 177 behavior exports and 59 device-type requirement exports legitimately ship an incomplete selection because the application chooses.

CachesClusterBehaviorCache keys on the namespace (two namespaces sharing a schema no longer collide) and honors forClient; feature variants converge on the schema they derive from; the deprecated Cluster.with() shim memoizes; both schema caches are WeakMap.

Behavior changes

  • Devices relying on the leaked features since 0.17.0 must select them via .with(...). Their persisted cluster state resets once, as it is keyed by feature selection.
  • A non-conformant selection that boots today will now fail at endpoint construction.
  • ClusterTypeWithCompatTest previously asserted the shim returns a fresh clone per call; that freshness is what defeated the behavior cache, so the invariant is now one clone per selection.

Verification

Clean build, full monorepo suite, format-verify and lint all green. +49 tests (model +22, node +26, types +1).

Each production change was verified to be detected by its tests: reverting the feature work fails 5 tests, reverting the cache work fails 4.

Two rounds of independent adversarial review are folded in. The second round found the dead EnergyEvseDevice, an unreachable LevelControl.Frequency (provisional features assess as inapplicable and were being rejected), and a cache regression introduced by the namespace key — all fixed, with a device-type sweep test that would have caught the first.

🤖 Generated with Claude Code

…on features

A cluster's own feature table must not enable features. Only a device type
requirement or an explicit implementation decision may do so.

The spec's feature-table fallback column was ingested as the model's
"supported" encoding, and `ClusterBehavior.for()` silently stopped reverting
feature selection when cluster namespaces lost `supportedFeatures`, so eleven
default server exports published the features their base implementation
enables internally.

- codegen no longer maps the feature fallback column to a feature default, and
  the stale defaults are removed from Groups and LevelControl
- default server exports use `.with()` as the single feature-drop mechanism
- `FeatureSelectionErrors()` assesses a selection against FeatureMap
  conformance; adding a server cluster to an endpoint fails when it does not
  conform
- `EnergyEvseServer` selects its unconditionally mandatory ChargingPreferences
  feature, and a LongIdleTimeSupport ICD must also select
  CheckInProtocolSupport and UserActiveModeTrigger
- `ClusterBehavior.with()` rejects a feature the cluster does not define
- the behavior cache keys on the namespace, feature variants converge on the
  schema they derive from, and the deprecated `Cluster.with()` shim memoizes

Fixes #4145

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 27, 2026 12:54

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 fixes feature-selection leakage from default server exports (notably LevelControlServer) by ensuring cluster features are never implicitly enabled by a cluster’s own feature table, and by restoring effective feature-reset behavior in behavior exports. It also adds validation and tests to prevent regressions across codegen, model, and node behavior layers.

Changes:

  • Codegen/model: stop mapping spec “fallback” feature values into model defaults; add FeatureSelectionErrors() and assert standard clusters enable no features by default.
  • Node behaviors: make default XxxServer exports featureless via .with(); enforce invalid feature names and validate feature-map conformance at endpoint construction.
  • Caching/tests: strengthen namespace/schema caching behavior and add broad sweeps to ensure only allowlisted server exports enable features unconditionally.

Reviewed changes

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

Show a summary per file
File Description
support/codegen/src/mom/spec/translate-cluster.ts Removes spec fallback/default mapping for features during cluster translation.
packages/types/test/cluster/ClusterTypeWithCompatTest.ts Updates compat-shim expectations to “one clone per feature selection”.
packages/types/src/cluster/ClusterType.ts Memoizes Cluster.with() clones per selection (compat shim).
packages/node/test/node/ServerSubscriptionTest.ts Updates LIT ICD feature selection to include UserActiveModeTrigger.
packages/node/test/node/client/ClientNodePhysicalPropertiesIcdTest.ts Updates ICD feature selection to include UserActiveModeTrigger.
packages/node/test/node/client/ClientNodeInteractionIcdTest.ts Updates ICD feature selection to include UserActiveModeTrigger.
packages/node/test/devices/DeviceFeatureSelectionTest.ts Adds sweep test asserting device default behaviors have conformant feature selections.
packages/node/test/behaviors/icd-management/IcdManagementServerTest.ts Updates ICD tests for mandatory UserActiveModeTrigger under LITS/DSLS.
packages/node/test/behaviors/general-diagnostics/GeneralDiagnosticsFeaturesTest.ts Adds test coverage for advisory log tied to DataModelTest + maxPathsPerInvoke.
packages/node/test/behavior/system/icd/litSupportedTest.ts Updates LIT ICD selection to include UserActiveModeTrigger.
packages/node/test/behavior/system/icd/IcdClientTest.ts Updates ICD feature selections to include UserActiveModeTrigger.
packages/node/test/behavior/cluster/ClusterBehaviorFeaturesTest.ts Adds comprehensive tests for with()/for() feature semantics and export invariants.
packages/node/test/behavior/cluster/ClusterBehaviorCacheTest.ts Expands cache tests to cover namespace-aware caching and schema/behavior sharing.
packages/node/src/behaviors/window-covering/WindowCoveringServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/thermostat/ThermostatServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/switch/SwitchServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/smoke-co-alarm/SmokeCoAlarmServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/service-area/ServiceAreaServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/power-topology/PowerTopologyServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/power-source/PowerSourceServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/on-off/OnOffServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/mode-select/ModeSelectServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/level-control/LevelControlServer.ts Fixes LevelControlServer export to be featureless via .with().
packages/node/src/behaviors/icd-management/IcdManagementServer.ts Corrects LITS guidance to include mandatory UserActiveModeTrigger.
packages/node/src/behaviors/general-diagnostics/GeneralDiagnosticsServer.ts Adds runtime advisory when DataModelTest is enabled but not required.
packages/node/src/behaviors/energy-evse/EnergyEvseServer.ts Ensures mandatory ChargingPreferences feature is selected.
packages/node/src/behaviors/electrical-energy-measurement/ElectricalEnergyMeasurementServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/door-lock/DoorLockServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behaviors/color-control/ColorControlServer.ts Switches default server export to .with() to drop base-enabled features.
packages/node/src/behavior/internal/ServerBehaviorBacking.ts Adds endpoint-time feature selection validation (FeatureSelectionError).
packages/node/src/behavior/cluster/ClusterBehaviorType.ts Fixes feature sync, improves schema caching, and rejects unknown feature selections.
packages/node/src/behavior/cluster/ClusterBehaviorCache.ts Makes cache namespace-aware and honors client/server distinction.
packages/node/src/behavior/cluster/ClusterBehavior.ts Clarifies for() feature-selection semantics in docs.
packages/model/test/standard/MatterDefinitionTest.ts Adds assertion that standard clusters enable no features by default.
packages/model/test/logic/FeatureSelectionErrorsTest.ts Adds unit tests for FeatureSelectionErrors() behavior.
packages/model/src/standard/elements/level-control.element.ts Removes incorrect feature “default” values from LevelControl FeatureMap.
packages/model/src/standard/elements/groups.element.ts Removes incorrect feature “default” value from Groups FeatureMap.
packages/model/src/logic/index.ts Exports FeatureSelectionErrors() from model logic index.
packages/model/src/logic/FeatureSelectionErrors.ts Implements feature selection conformance checking (choices, dependencies, disallow).
CHANGELOG.md Documents behavior/model fixes and new validation.

Comment thread packages/types/src/cluster/ClusterType.ts Outdated
…lones

A repeated feature produced a distinct cache key, so `Cluster.with(A, A)` and
`Cluster.with(A)` yielded separate clones of identical content and split the
behavior cache keyed on namespace identity.

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

mergify Bot commented Jul 27, 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

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 40 out of 40 changed files in this pull request and generated 1 comment.

Comment thread packages/types/src/cluster/ClusterType.ts
Apollon77 and others added 2 commits July 27, 2026 17:22
The shim memoizes a clone per selection, so an unvalidated name grew the cache
without bound and only surfaced when the endpoint was constructed.

Co-Authored-By: Claude Opus 5 (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 40 out of 40 changed files in this pull request and generated 1 comment.

Comment thread packages/types/src/cluster/ClusterType.ts
Selection now records against `Model.operationalIsSupported`, so a feature's
`default` conveys only the specification's fallback value. The spec parser keeps
parsing that column and the model keeps carrying it; it has no effect.

A feature the specification mandates without condition is not an application
choice, so server behaviors select it regardless. Client behaviors defer to the
features their peer reports.

- `FeatureSelectionErrors()` reports against `IllegalFeatureCombinations` so
  runtime cannot diverge from the assessment codegen uses, and surfaces through
  `ValidatedElements` memoized per schema rather than per behavior instance
- `FeatureSet.resolve()` replaces four feature-name resolvers that accepted
  three different vocabularies
- fix the OR case in `IllegalFeatureCombinations`, which emitted stringified
  keys and omitted its own feature, so `DoorLock.User` was never required
- General Diagnostics asserts DataModelTest above a maxPathsPerInvoke of one,
  and its state getters no longer name their own type, which made any feature
  selection unconstructible

Co-Authored-By: Claude Opus 5 (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 40 out of 40 changed files in this pull request and generated no new comments.

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.

LevelControlServer default export still has OnOff+Lighting enabled despite the 'no Features again' strip

2 participants