diff --git a/openspec/changes/consolidate-specs/.openspec.yaml b/openspec/changes/consolidate-specs/.openspec.yaml new file mode 100644 index 00000000..1b062d3a --- /dev/null +++ b/openspec/changes/consolidate-specs/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-08-04 diff --git a/openspec/changes/consolidate-specs/design.md b/openspec/changes/consolidate-specs/design.md new file mode 100644 index 00000000..d3fe394f --- /dev/null +++ b/openspec/changes/consolidate-specs/design.md @@ -0,0 +1,62 @@ +## Context + +This change stacks on top of PR #258 (`migrate-specs-to-v4`), which made all 35 specs pass `openspec validate --specs --strict`. That left the *organization* problem untouched: 35 flat spec directories of inconsistent scope. The forcing function is scale — SBMD device drivers are expected to grow to 50–100+, and OpenSpec discovery is flat (one directory deep), so neither per-driver specs nor subdirectories are viable. + +Key facts grounding the design: +- Each `.sbmd.js` driver declares exactly one `deviceClass`; the device-class set is bounded (~8: light, doorLock, thermostat, sensor, environmentalSensor, airQualitySensor, + planned windowCovering, presence, lightController). +- Concrete drivers are already validated by `scripts/ci/validate_sbmd_v4_specs.py` against a JSON schema at build time — a mechanism independent of OpenSpec. + +``` + drivers │ 10 ───────────────▶ 100+ (scales freely) + .sbmd.js files │ core/deviceDrivers/matter/sbmd/specs/*.sbmd.js + governed by │ validate_sbmd_v4_specs.py + JSON schema ← not OpenSpec + ───────────────┼─────────────────────────────── + device classes │ ~8 ───────────────▶ ~10-15 (bounded) + OpenSpec specs │ per capability + per device class (bounded ~15-25) +``` + +## Goals / Non-Goals + +**Goals:** +- Bound OpenSpec spec count by capabilities and device classes, not drivers. +- Consolidate the existing 35 specs into coherent, consistently-scoped capabilities. +- Encode the organizing principle so future changes cannot regress into per-driver sprawl. +- Keep `openspec validate --specs --strict` green throughout. + +**Non-Goals:** +- Net-new capability documentation for undocumented device classes (doorLock, sensor, airQualitySensor). +- Any requirement-meaning change; any code/driver/API change. + +## Decisions + +**D1. A spec is a capability or a device class — never a driver.** +The driver *is* its `.sbmd.js` file, governed by the schema validator. Encoding this in `openspec/config.yaml` (context + `rules.proposal` + `rules.specs`) makes it a standing constraint the propose/apply skills inject. *Alternative:* rely on reviewer discipline — rejected; it does not scale and is what produced the current sprawl. + +**D2. Merge fine-grained framework/cross-cutting specs by concatenating requirements.** +`sbmd-runtime`, `sbmd-mappers`, `sbmd-endpoint-resolution`, `sbmd-claiming`, `agent-skills`, `release-process`, and `matter-testing` are lossless concatenations of their sources' `## Requirements` under one new title + purpose. Requirement bodies are byte-preserved. + +**D3. Reframe per-driver specs to device-class specs at the class-contract level.** +`device-class-light`, `device-class-thermostat`, and `device-class-environmental-sensor` describe what the *class* guarantees (endpoint profile, resources, Matter cluster/attribute bindings). Requirements that asserted a driver *file* exists, or that integration tests pass, are dropped — those are owned by the `.sbmd.js` files, the schema validator, and the `matter-testing` spec respectively. The `environmentalSensor` spec is rewritten (its source was driver- and test-centric); light and thermostat retain their accurate resource/binding requirements verbatim. + +**D4. Do not fabricate specs for undocumented classes.** +`doorLock`, `sensor`, and `airQualitySensor` had no prior OpenSpec coverage. Rather than author rushed normative specs from driver files, they remain governed by their drivers + tooling; class-contract specs can be added later per D1 when warranted. + +## Risks / Trade-offs + +- **Reframing environmental-sensor could alter a stated fact.** → Requirements were rewritten directly from the source spec's technical content (clusters 0x0402/0x0405, int16/uint16, null sentinels 0x8000/0xFFFF); scenarios preserve the exact values. +- **Partial device-class coverage (3 of ~6 classes) looks inconsistent.** → Acceptable: the model and governance are established; the other classes were never documented as specs, so nothing is lost, and D1 makes completing them a routine follow-up. +- **Merged specs are larger.** → Still coherent (one capability each) and far more navigable than 35 fragments; strict validation keeps them well-formed. + +## Migration Plan + +1. Add the governance rule to `openspec/config.yaml`. +2. Concatenate the framework/cross-cutting merges (scripted, lossless). +3. Reframe the three device-class specs; delete consumed source directories. +4. Run `openspec validate --specs --strict` — must be green (20/20). +5. Commit and submit as PR #2 stacked on #258 via `gh stack`. + +Rollback is trivial — documentation/organization only; reverting the branch restores the prior layout with no runtime effect. + +## Open Questions + +- Do we want class-contract specs for `doorLock`, `sensor`, and `airQualitySensor` now, or as follow-ups? (This change defers them.) diff --git a/openspec/changes/consolidate-specs/proposal.md b/openspec/changes/consolidate-specs/proposal.md new file mode 100644 index 00000000..29c308a0 --- /dev/null +++ b/openspec/changes/consolidate-specs/proposal.md @@ -0,0 +1,32 @@ +## Why + +The flat `openspec/specs/` list had grown to 35 specs of wildly inconsistent granularity — some describe an entire subsystem, others a single device driver. With spec-based Matter drivers (SBMD) expected to scale to 50–100+, a per-driver spec model would explode the flat namespace: OpenSpec spec discovery is exactly one directory deep, so subfolders are not an option (nested `spec.md` files are silently ignored). We need an organization whose spec count is bounded by capabilities and device classes, not by the driver catalog. + +## What Changes + +- **Governance rule (the durable fix):** encode in `openspec/config.yaml` that OpenSpec specs are authored per capability or per device class — never per concrete driver. A new supported device is added as a `.sbmd.js` driver file governed by the SBMD schema validator (`scripts/ci/validate_sbmd_v4_specs.py`), not as a new spec. This keeps the spec count bounded (~15–25) as drivers scale to 100+. +- **Consolidate 35 → 20 specs** (requirement meaning preserved): + - **SBMD framework**: `sbmd-runtime` (←`sbmd-system` + `sbmd-v4-runtime` + `sbmd-script-execution-limits`), `sbmd-mappers` (←`sbmd-resource-prerequisites` + `sbmd-seed-from-attribute`), `sbmd-endpoint-resolution` (←`device-type-endpoint-resolution` + `endpoint-cluster-fallback`), `sbmd-claiming` (←`vendor-product-claiming`). + - **Device classes**: `device-class-light` (←`sbmd-v4-light-driver`), `device-class-thermostat` (←`matter-thermostat-sbmd`), `device-class-environmental-sensor` (←`temperature-humidity-sbmd-drivers`) — reframed from per-driver to class-contract level. + - **Cross-cutting**: `agent-skills` (←7 `agent-skill-*`), `release-process` (←`changelog-generation` + `release-workflow`), `matter-testing` (←`matter-test-infrastructure` + `matterjs-virtual-device-framework` + `matterjs-door-lock-device` + `matter-thermostat-testing` + `python-sideband-client`). +- The device-class reframes **drop** requirements that were about a driver *file* existing or about integration tests passing; those concerns are now owned by the `.sbmd.js` files + the SBMD schema validator + the `matter-testing` spec. + +## Capabilities + +### New Capabilities + + +### Modified Capabilities + + +## Impact + +- **Specs**: `openspec/specs/` goes 35 → 20 directories (18 consumed by merges, 3 reframed/renamed, 12 unchanged). +- **Config**: `openspec/config.yaml` gains the spec-organization governance rule (context + proposal/specs rules) that the propose/apply skills inject. +- **No** code, build, CI, API, or runtime impact — documentation/organization only. The `spec-validation` CI gate from the base change (PR #258) keeps all 20 specs strictly valid. + +## Non-goals + +- Authoring brand-new device-class specs for classes that had no prior OpenSpec coverage (`doorLock`, `sensor`, `airQualitySensor`). Their drivers exist as `.sbmd.js` files under the SBMD validator; a class-contract spec can be added later per the governance rule when warranted. +- Changing any requirement's meaning, or altering drivers, tooling, or the public API. +- Subdirectory organization of specs — proven unsupported by OpenSpec discovery. diff --git a/openspec/changes/consolidate-specs/tasks.md b/openspec/changes/consolidate-specs/tasks.md new file mode 100644 index 00000000..b697d3f5 --- /dev/null +++ b/openspec/changes/consolidate-specs/tasks.md @@ -0,0 +1,28 @@ +## 1. Governance + +- [x] 1.1 Add the spec-organization governance rule to `openspec/config.yaml` (context section + `rules.proposal` + `rules.specs`): specs are per capability or per device class, never per driver + +## 2. SBMD framework consolidation + +- [x] 2.1 Create `sbmd-runtime` from `sbmd-system` + `sbmd-v4-runtime` + `sbmd-script-execution-limits` +- [x] 2.2 Create `sbmd-mappers` from `sbmd-resource-prerequisites` + `sbmd-seed-from-attribute` +- [x] 2.3 Create `sbmd-endpoint-resolution` from `device-type-endpoint-resolution` + `endpoint-cluster-fallback` +- [x] 2.4 Rename `vendor-product-claiming` to `sbmd-claiming` + +## 3. Device-class reframe + +- [x] 3.1 Reframe `sbmd-v4-light-driver` → `device-class-light` (class contract; drop driver-file and integration-test requirements) +- [x] 3.2 Reframe `matter-thermostat-sbmd` → `device-class-thermostat` (retitle + repurpose; preserve resource/binding requirements) +- [x] 3.3 Reframe `temperature-humidity-sbmd-drivers` → `device-class-environmental-sensor` (rewrite to class-contract level; drop per-driver-file and test requirements) + +## 4. Cross-cutting consolidation + +- [x] 4.1 Create `agent-skills` from the 7 `agent-skill-*` specs +- [x] 4.2 Create `release-process` from `changelog-generation` + `release-workflow` +- [x] 4.3 Create `matter-testing` from `matter-test-infrastructure` + `matterjs-virtual-device-framework` + `matterjs-door-lock-device` + `matter-thermostat-testing` + `python-sideband-client` + +## 5. Verify + +- [x] 5.1 Delete all consumed source spec directories +- [x] 5.2 Run `openspec validate --specs --strict` — all specs valid, exit 0 (35 → 20) +- [ ] 5.3 Submit PR #2 stacked on #258 via `gh stack` diff --git a/openspec/config.yaml b/openspec/config.yaml index 16bbef86..9f0750e2 100644 --- a/openspec/config.yaml +++ b/openspec/config.yaml @@ -82,11 +82,23 @@ context: | - C++ naming: PascalCase classes, camelCase methods - Test naming: test files prefixed with test or suffixed with Test/test + ## Spec Organization + OpenSpec specs are authored per capability or per device class — never per concrete + device driver. A new supported device is added as a `.sbmd.js` driver file under + core/deviceDrivers/matter/sbmd/specs/ (governed by the SBMD schema validator, + scripts/ci/validate_sbmd_specs.py), not as a new OpenSpec spec. This keeps the + spec count bounded by the number of framework capabilities and device classes + (~8-15) even as the driver catalog scales to 100+. A new OpenSpec spec is justified + only by a new framework capability or a new device class. Specs live in a flat + layout (openspec/specs//spec.md — discovery is one directory deep); group + related specs with name prefixes (e.g. sbmd-*, device-class-*), not subdirectories. + rules: proposal: - Always include a "Non-goals" section to clarify scope boundaries - Reference affected layers explicitly (API, core, drivers, subsystems, etc.) - Note which CMake feature flags are relevant + - Do not introduce a new spec per device driver; new device support is a `.sbmd.js` file, and new specs are justified only by a new framework capability or device class design: - Include ASCII architecture diagrams showing how changes fit into existing layers - Call out thread safety considerations (GLib main loop, Matter event loop, pthread mutexes) @@ -97,6 +109,7 @@ rules: - For Matter features, specify cluster IDs, attribute IDs, and TLV types - For SBMD specs, include the complete mapper scripts - Define expected GObject signals/events for new functionality + - Scope device support by device class (device-class-*), not by concrete driver; the per-driver detail lives in the `.sbmd.js` file and its schema tasks: - Break tasks into chunks of max 4 hours - Separate API changes from implementation changes diff --git a/openspec/specs/agent-skill-build/spec.md b/openspec/specs/agent-skill-build/spec.md deleted file mode 100644 index 32bb6bcf..00000000 --- a/openspec/specs/agent-skill-build/spec.md +++ /dev/null @@ -1,53 +0,0 @@ -# Agent Skill: Build - -## Purpose - -Defines the structure and required content of the `build` agent skill (`.github/skills/build/SKILL.md`), which teaches an AI agent how to build BartonCore: the `build.sh` hierarchy, CMake configuration flags, the development build profile, and an error-recovery pattern. - -## Requirements - -### Requirement: Build skill SKILL.md conforms to Agent Skills spec -The `build` skill SHALL be located at `.github/skills/build/SKILL.md`. The frontmatter SHALL include `name: build`, a `description` field explaining the skill covers building BartonCore, and `compatibility` noting it requires the BartonCore Docker development container. The `name` field SHALL match the parent directory name. - -#### Scenario: Valid frontmatter -- **WHEN** the SKILL.md file is parsed -- **THEN** the YAML frontmatter SHALL contain `name: build` matching the directory name -- **AND** the `description` field SHALL be non-empty and under 1024 characters -- **AND** the `description` SHALL mention building, compiling, and CMake - -### Requirement: Build skill documents the build hierarchy -The skill body SHALL explain the layered build system: `build.sh` is the top-level convenience script that calls CMake, and CMake in turn invokes `build-matter.sh` when needed. The skill SHALL make clear that for incremental rebuilds after code changes, `cmake --build build` is the preferred command. - -#### Scenario: Agent performs incremental rebuild -- **WHEN** the agent needs to rebuild after modifying source files -- **THEN** the skill SHALL instruct the agent to run `cmake --build build` - -#### Scenario: Agent performs full build from scratch -- **WHEN** the agent needs a clean or first-time build -- **THEN** the skill SHALL instruct the agent to run `./build.sh` - -#### Scenario: Agent understands build-matter.sh is automatic -- **WHEN** the agent reads the build skill -- **THEN** the skill SHALL state that `build-matter.sh` is invoked automatically by CMake and rarely needs manual execution - -### Requirement: Build skill documents CMake configuration flags -The skill SHALL list the key CMake boolean flags that control the build: `BCORE_ZIGBEE`, `BCORE_MATTER`, `BCORE_THREAD`, `BCORE_PHILIPS_HUE`, `BCORE_BUILD_REFERENCE`, `BCORE_BUILD_WITH_ASAN`, `BCORE_BUILD_WITH_SSP`, `BCORE_GEN_GIR`, `BCORE_TEST_COVERAGE`, and `BCORE_MATTER_VALIDATE_SCHEMAS`. The skill SHALL explain how to pass flags (via `-D` to cmake or appended to `build.sh`). - -#### Scenario: Agent disables a subsystem for build -- **WHEN** the agent needs to build without Zigbee support -- **THEN** the skill SHALL show how to pass `-DBCORE_ZIGBEE=OFF` to the build - -### Requirement: Build skill documents dev profile -The skill SHALL note that the default development profile is at `config/cmake/platforms/dev/linux.cmake` and is automatically used by `build.sh`. The skill SHALL list what the dev profile enables (Debug build, ASAN, SSP, GIR, random Matter port). - -#### Scenario: Agent understands default build configuration -- **WHEN** the agent runs `./build.sh` without arguments -- **THEN** the skill SHALL explain that it uses the dev profile with Debug, ASAN, and SSP enabled - -### Requirement: Build skill includes error recovery pattern -The skill SHALL instruct the agent that if a build command fails with a tool-not-found error (e.g., `cmake: command not found`, `make: command not found`), the agent SHALL check for `/.dockerenv`. If the file does not exist, the agent SHALL inform the user they need to run inside the BartonCore development container. - -#### Scenario: Build fails outside Docker -- **WHEN** a build command fails with a missing tool error -- **AND** the file `/.dockerenv` does not exist -- **THEN** the agent SHALL stop and tell the user to run inside the development container diff --git a/openspec/specs/agent-skill-debug/spec.md b/openspec/specs/agent-skill-debug/spec.md deleted file mode 100644 index bc1825d7..00000000 --- a/openspec/specs/agent-skill-debug/spec.md +++ /dev/null @@ -1,55 +0,0 @@ -# Agent Skill: Debug - -## Purpose - -Defines the structure and required content of the `debug` agent skill, covering the three debugging workflows — gdb for the reference app and unit tests, pdb for Python integration tests, and gdb with `python3-gdb` for native visibility from Python — plus ASAN considerations and an error-recovery pattern. - -## Requirements - -### Requirement: Debug skill SKILL.md conforms to Agent Skills spec -The `debug` skill SHALL be located at `.github/skills/debug/SKILL.md`. The frontmatter SHALL include `name: debug`, a `description` field explaining the skill covers debugging BartonCore with gdb and pdb, and `compatibility` noting the BartonCore Docker development container. - -#### Scenario: Valid frontmatter -- **WHEN** the SKILL.md file is parsed -- **THEN** the YAML frontmatter SHALL contain `name: debug` matching the directory name -- **AND** the `description` SHALL mention debugging, gdb, pdb, reference app, and integration tests - -### Requirement: Debug skill documents gdb for the reference app -The skill SHALL explain how to debug the reference app with gdb. The skill SHALL document the binary location (`build/reference/barton-core-reference`), common command-line flags (`-b` for SBMD specs path, `-z` to disable Zigbee, `-t` to disable Thread, `-m` to disable Matter), and how to start a gdb session. The skill SHALL note that the VS Code launch configurations (`(gdb) Reference App` variants) provide an alternative. - -#### Scenario: Agent debugs the reference app with gdb -- **WHEN** the agent needs to debug the reference app -- **THEN** the skill SHALL show how to launch `gdb build/reference/barton-core-reference` and pass arguments -- **AND** the skill SHALL mention available VS Code launch configs as an alternative - -### Requirement: Debug skill documents pdb for integration tests -The skill SHALL explain how to debug Python integration tests using pdb. The skill SHALL show how to insert breakpoints (`breakpoint()` or `import pdb; pdb.set_trace()`) and how to run pytest with debugger support (`./testing/py_test.sh testing/test/.py -s --no-header`). The skill SHALL note that the VS Code Python debugger can also be used via the Testing panel. - -#### Scenario: Agent debugs an integration test with pdb -- **WHEN** the agent needs to debug a Python integration test -- **THEN** the skill SHALL show how to insert a breakpoint and run the test with `-s` flag -- **AND** the skill SHALL mention VS Code Testing panel as an alternative - -### Requirement: Debug skill documents gdb with python3-gdb for native visibility -The skill SHALL explain how to use gdb with python3 to debug C/C++ code exercised by Python integration tests. The skill SHALL document the workflow: run `gdb python3`, answer yes to debug info download prompts, set C breakpoints (may require running first for symbol loading), then execute the test via `run -m pytest testing/test/.py`. The skill SHALL note that if the build uses ASAN, the agent must run `set env LD_PRELOAD=` in gdb before `run`. The skill SHALL note the command to find libasan: `gcc -print-file-name=libasan.so`. - -#### Scenario: Agent debugs native code via Python tests -- **WHEN** the agent needs to debug C/C++ code triggered by a Python integration test -- **THEN** the skill SHALL show the gdb+python3 workflow step by step -- **AND** the skill SHALL include the ASAN LD_PRELOAD setup if needed - -### Requirement: Debug skill documents ASAN considerations -The skill SHALL explain that the development build enables Address Sanitizer (ASAN) by default. The skill SHALL document that `testing/py_test.sh` handles ASAN preloading automatically for pytest runs, and that for gdb sessions the agent must manually preload libasan using `set env LD_PRELOAD` before running. The skill SHALL note that `ASAN_OPTIONS=verify_asan_link_order=0` is set in the container environment. - -#### Scenario: Agent understands ASAN in debugging context -- **WHEN** the agent reads the debug skill -- **THEN** it SHALL understand that ASAN is enabled by default in dev builds -- **AND** it SHALL know how to handle ASAN when debugging - -### Requirement: Debug skill includes error recovery pattern -The skill SHALL instruct the agent that if `gdb` is not found, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. The skill SHALL note that the Docker container has `SYS_PTRACE` capability enabled for gdb to work. - -#### Scenario: gdb not found outside Docker -- **WHEN** `gdb` is not found -- **AND** `/.dockerenv` does not exist -- **THEN** the agent SHALL stop and tell the user to run inside the development container diff --git a/openspec/specs/agent-skill-format-code/spec.md b/openspec/specs/agent-skill-format-code/spec.md deleted file mode 100644 index 8972c66f..00000000 --- a/openspec/specs/agent-skill-format-code/spec.md +++ /dev/null @@ -1,52 +0,0 @@ -# Agent Skill: Format Code - -## Purpose - -Defines the structure and required content of the `format-code` agent skill, covering `clang-format` usage, the diff-only formatting rule, the manual blank-line conventions `clang-format` cannot enforce, and the pre-commit hook. - -## Requirements - -### Requirement: Format code skill SKILL.md conforms to Agent Skills spec -The `format-code` skill SHALL be located at `.github/skills/format-code/SKILL.md`. The frontmatter SHALL include `name: format-code`, a `description` field explaining the skill covers C/C++ code formatting, and `compatibility` noting the BartonCore Docker development container. - -#### Scenario: Valid frontmatter -- **WHEN** the SKILL.md file is parsed -- **THEN** the YAML frontmatter SHALL contain `name: format-code` matching the directory name -- **AND** the `description` SHALL mention clang-format, formatting, and C/C++ - -### Requirement: Format code skill documents clang-format usage -The skill SHALL instruct the agent to run `clang-format` from the repository root so it picks up the `.clang-format` configuration. The skill SHALL show the command to format a file: `clang-format -i `. - -#### Scenario: Agent formats a file -- **WHEN** the agent needs to format a C/C++ file -- **THEN** the skill SHALL instruct the agent to run `clang-format -i ` from the repo root - -### Requirement: Format code skill documents the diff-only rule -The skill SHALL state that agents MUST only format code that is part of the current diff. Agents SHALL NOT reformat entire files they did not modify. The pre-commit hook enforces formatting on staged files automatically. - -#### Scenario: Agent respects diff-only rule -- **WHEN** the agent has modified a C/C++ file -- **THEN** the agent SHALL only format the file it modified -- **AND** the agent SHALL NOT run clang-format on unmodified files - -### Requirement: Format code skill documents manual blank-line rules -The skill SHALL document the blank-line conventions that clang-format cannot enforce: a blank line before `if`, `for`, `while`, `switch`, and `return` statements (unless preceded by an opening brace or another control-flow statement's opening line), and a blank line after a closing brace `}` (unless followed by `else`, `catch`, or another closing brace). - -#### Scenario: Agent applies blank-line rules -- **WHEN** the agent writes or modifies C/C++ code -- **THEN** the agent SHALL apply the manual blank-line conventions - -### Requirement: Format code skill documents pre-commit hook -The skill SHALL note that a pre-commit hook (`hooks/pre-commit`) automatically runs clang-format on staged files at commit time. The skill SHALL note that hooks are installed via `./hooks/install.sh`. - -#### Scenario: Agent understands pre-commit integration -- **WHEN** the agent reads the format code skill -- **THEN** it SHALL know that formatting is enforced automatically at commit time - -### Requirement: Format code skill includes error recovery pattern -The skill SHALL instruct the agent that if `clang-format` is not found, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. - -#### Scenario: clang-format not found outside Docker -- **WHEN** `clang-format` is not found -- **AND** `/.dockerenv` does not exist -- **THEN** the agent SHALL stop and tell the user to run inside the development container diff --git a/openspec/specs/agent-skill-integration-tests/spec.md b/openspec/specs/agent-skill-integration-tests/spec.md deleted file mode 100644 index c7a7aa67..00000000 --- a/openspec/specs/agent-skill-integration-tests/spec.md +++ /dev/null @@ -1,55 +0,0 @@ -# Agent Skill: Integration Tests - -## Purpose - -Defines the structure and required content of the `run-integration-tests` agent skill, covering pytest execution via `py_test.sh`, test filtering, prerequisites, and pytest configuration for BartonCore's Python integration tests. - -## Requirements - -### Requirement: Integration test skill SKILL.md conforms to Agent Skills spec -The `run-integration-tests` skill SHALL be located at `.github/skills/run-integration-tests/SKILL.md`. The frontmatter SHALL include `name: run-integration-tests`, a `description` field explaining the skill covers running Python integration tests, and `compatibility` noting the BartonCore Docker development container. - -#### Scenario: Valid frontmatter -- **WHEN** the SKILL.md file is parsed -- **THEN** the YAML frontmatter SHALL contain `name: run-integration-tests` matching the directory name -- **AND** the `description` field SHALL mention integration tests, pytest, and Python - -### Requirement: Integration test skill documents test execution -The skill SHALL instruct the agent to run integration tests via `testing/py_test.sh testing/` from the repo root. The skill SHALL explain that `py_test.sh` is a wrapper that handles Address Sanitizer (ASAN) `LD_PRELOAD` setup automatically. - -#### Scenario: Agent runs all integration tests -- **WHEN** the agent needs to run integration tests -- **THEN** the skill SHALL instruct the agent to run `./testing/py_test.sh testing/` - -### Requirement: Integration test skill documents test filtering -The skill SHALL explain how to run specific tests: by file path (`./testing/py_test.sh testing/test/light_test.py`), by keyword (`./testing/py_test.sh testing/ -k "light"`), and by marker (`./testing/py_test.sh testing/ -m "requires_matterjs"`). - -#### Scenario: Agent runs specific integration tests -- **WHEN** the agent needs to run a subset of integration tests -- **THEN** the skill SHALL show filtering by file path, `-k` keyword, and `-m` marker - -### Requirement: Integration test skill documents prerequisites -The skill SHALL explain the prerequisite chain for integration tests: the project must be built, the built artifacts must be installed (`cmake --build build --target install`), D-Bus must be running (`sudo service dbus start`), and matter.js dependencies must be installed (`npm --prefix testing/mocks/devices/matterjs ci`) for tests using the `requires_matterjs` marker. The skill SHALL note that `scripts/ci/run_integration_tests.sh` performs all prerequisites automatically. - -#### Scenario: Agent prepares prerequisites manually -- **WHEN** the agent needs to run integration tests after a fresh build -- **THEN** the skill SHALL list the prerequisite steps in order - -#### Scenario: Agent uses CI script for full setup -- **WHEN** the agent wants to ensure all prerequisites are met -- **THEN** the skill SHALL offer `./scripts/ci/run_integration_tests.sh` as the all-in-one option - -### Requirement: Integration test skill documents pytest configuration -The skill SHALL note that pytest is configured in `pyproject.toml` with `testpaths = ["testing"]`, log level `DEBUG`, and `--capture=sys`. - -#### Scenario: Agent understands test discovery -- **WHEN** the agent needs to find where tests are defined -- **THEN** the skill SHALL direct the agent to `testing/test/` for test files and `pyproject.toml` for pytest configuration - -### Requirement: Integration test skill includes error recovery pattern -The skill SHALL instruct the agent that if `pytest` or `py_test.sh` fails with a command-not-found or import error, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. - -#### Scenario: pytest not found outside Docker -- **WHEN** `pytest` fails with a command-not-found or import error -- **AND** `/.dockerenv` does not exist -- **THEN** the agent SHALL stop and tell the user to run inside the development container diff --git a/openspec/specs/agent-skill-matter-devices/spec.md b/openspec/specs/agent-skill-matter-devices/spec.md deleted file mode 100644 index d791ba53..00000000 --- a/openspec/specs/agent-skill-matter-devices/spec.md +++ /dev/null @@ -1,61 +0,0 @@ -# Agent Skill: Matter Virtual Devices - -## Purpose - -Defines the structure and required content of the `matter-virtual-devices` agent skill, covering pre-built Matter sample apps, `chip-tool`, matter.js virtual devices, and authoring custom virtual device types. - -## Requirements - -### Requirement: Matter virtual devices skill SKILL.md conforms to Agent Skills spec -The `matter-virtual-devices` skill SHALL be located at `.github/skills/matter-virtual-devices/SKILL.md`. The frontmatter SHALL include `name: matter-virtual-devices`, a `description` field explaining the skill covers working with Matter test devices, and `compatibility` noting the BartonCore Docker development container. - -#### Scenario: Valid frontmatter -- **WHEN** the SKILL.md file is parsed -- **THEN** the YAML frontmatter SHALL contain `name: matter-virtual-devices` matching the directory name -- **AND** the `description` SHALL mention Matter, sample apps, chip-tool, virtual devices, and testing - -### Requirement: Matter skill documents pre-built sample apps -The skill SHALL list the Matter sample apps available in the Docker container at `/usr/local/bin/`: `chip-lighting-app`, `chip-lock-app`, `thermostat-app`, `contact-sensor-app`. The skill SHALL explain how to start each app and what command-line options they support (e.g., `--discriminator`, `--KVS`). The skill SHALL explain that these are CHIP SDK sample apps compiled from the Matter SDK. - -#### Scenario: Agent starts a pre-built light device -- **WHEN** the agent needs a Matter light device for testing -- **THEN** the skill SHALL show how to start `chip-lighting-app` with appropriate arguments - -#### Scenario: Agent starts a pre-built lock device -- **WHEN** the agent needs a Matter door lock device for testing -- **THEN** the skill SHALL show how to start `chip-lock-app` with appropriate arguments - -### Requirement: Matter skill documents chip-tool -The skill SHALL explain that `chip-tool` is available at `/usr/local/bin/chip-tool` and is used to commission and interact with Matter devices. The skill SHALL show common chip-tool commands: pairing (`chip-tool pairing code `), cluster operations (`chip-tool onoff on `), and reading attributes (`chip-tool read `). - -#### Scenario: Agent commissions a device with chip-tool -- **WHEN** the agent needs to commission a Matter device -- **THEN** the skill SHALL show the `chip-tool pairing code` command with placeholder node ID and pairing code - -#### Scenario: Agent sends a cluster command -- **WHEN** the agent needs to interact with a commissioned device -- **THEN** the skill SHALL show cluster command examples (e.g., `chip-tool onoff on`) - -### Requirement: Matter skill documents matter.js virtual devices -The skill SHALL explain the matter.js virtual device framework located at `testing/mocks/devices/matterjs/`. The skill SHALL describe the Python wrapper classes in `testing/mocks/devices/matter/` (`MatterLight`, `MatterDoorLock`) and how they spawn Node.js subprocesses. The skill SHALL explain the sideband API for programmatic control (`device.sideband.send()`, `device.sideband.get_state()`). - -#### Scenario: Agent understands matter.js device architecture -- **WHEN** the agent reads the matter virtual devices skill -- **THEN** it SHALL understand that Python classes wrap matter.js Node.js processes -- **AND** it SHALL know about the sideband control interface - -### Requirement: Matter skill documents creating custom virtual devices -The skill SHALL explain how to create a new matter.js virtual device: create a JavaScript entry point in `testing/mocks/devices/matterjs/src/`, create a Python wrapper class in `testing/mocks/devices/matter/` extending `MatterDevice`, and specify the `matterjs_entry_point`. The skill SHALL note that custom devices useful for testing should be committed into the test harness with new integration tests, but this decision is made by the human. - -#### Scenario: Agent creates a new virtual device type -- **WHEN** the agent needs a device type not already available -- **THEN** the skill SHALL outline the steps to create a new matter.js device and Python wrapper -- **AND** the skill SHALL note that the human decides whether to commit it - -### Requirement: Matter skill includes error recovery pattern -The skill SHALL instruct the agent that if sample app binaries or `chip-tool` are not found, or if `node` is not available for matter.js devices, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. - -#### Scenario: chip-tool not found outside Docker -- **WHEN** `chip-tool` is not found in PATH -- **AND** `/.dockerenv` does not exist -- **THEN** the agent SHALL stop and tell the user to run inside the development container diff --git a/openspec/specs/agent-skill-unit-tests/spec.md b/openspec/specs/agent-skill-unit-tests/spec.md deleted file mode 100644 index 40f9d13e..00000000 --- a/openspec/specs/agent-skill-unit-tests/spec.md +++ /dev/null @@ -1,45 +0,0 @@ -# Agent Skill: Unit Tests - -## Purpose - -Defines the structure and required content of the `run-unit-tests` agent skill, covering `ctest` execution, test filtering, and the CMocka and Google Test frameworks used by BartonCore's C/C++ unit tests. - -## Requirements - -### Requirement: Unit test skill SKILL.md conforms to Agent Skills spec -The `run-unit-tests` skill SHALL be located at `.github/skills/run-unit-tests/SKILL.md`. The frontmatter SHALL include `name: run-unit-tests`, a `description` field explaining the skill covers running C/C++ unit tests, and `compatibility` noting the BartonCore Docker development container. - -#### Scenario: Valid frontmatter -- **WHEN** the SKILL.md file is parsed -- **THEN** the YAML frontmatter SHALL contain `name: run-unit-tests` matching the directory name -- **AND** the `description` field SHALL mention unit tests, ctest, CMocka, and Google Test - -### Requirement: Unit test skill documents test execution -The skill SHALL instruct the agent to run unit tests via `ctest --output-on-failure --test-dir build`. The skill SHALL note that the project must be built first. - -#### Scenario: Agent runs all unit tests -- **WHEN** the agent needs to run unit tests -- **THEN** the skill SHALL instruct the agent to run `ctest --output-on-failure --test-dir build` - -### Requirement: Unit test skill documents test filtering -The skill SHALL explain how to run a subset of tests using `ctest -R --test-dir build` and how to list available tests with `ctest -N --test-dir build`. - -#### Scenario: Agent runs specific unit tests -- **WHEN** the agent needs to run only tests matching a pattern -- **THEN** the skill SHALL instruct the agent to use `ctest -R --output-on-failure --test-dir build` - -### Requirement: Unit test skill documents test frameworks -The skill SHALL note that C tests use CMocka and C++ tests use Google Test/Google Mock. The skill SHALL explain that tests are defined in CMakeLists.txt files using `bcore_add_cmocka_test()` and `bcore_add_cpp_test()` macros, located under `core/test/`, `libs/device/descriptors/c/test/`, `libs/device/philipsHue/c/test/` when enabled, and `api/c/test/`. - -#### Scenario: Agent understands test framework context -- **WHEN** the agent reads the unit test skill -- **THEN** it SHALL know that C tests use CMocka and C++ tests use Google Test -- **AND** it SHALL know the test source locations - -### Requirement: Unit test skill includes error recovery pattern -The skill SHALL instruct the agent that if `ctest` fails with a command-not-found error, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. - -#### Scenario: ctest not found outside Docker -- **WHEN** `ctest` fails with a command-not-found error -- **AND** `/.dockerenv` does not exist -- **THEN** the agent SHALL stop and tell the user to run inside the development container diff --git a/openspec/specs/agent-skill-validate-sbmd/spec.md b/openspec/specs/agent-skill-validate-sbmd/spec.md deleted file mode 100644 index 5f7c3bf8..00000000 --- a/openspec/specs/agent-skill-validate-sbmd/spec.md +++ /dev/null @@ -1,44 +0,0 @@ -# Agent Skill: Validate SBMD - -## Purpose - -Defines the structure and required content of the `validate-sbmd` agent skill, covering SBMD spec validation, stub generation, and spec file locations. - -## Requirements - -### Requirement: SBMD validation skill SKILL.md conforms to Agent Skills spec -The `validate-sbmd` skill SHALL be located at `.github/skills/validate-sbmd/SKILL.md`. The frontmatter SHALL include `name: validate-sbmd`, a `description` field explaining the skill covers validating SBMD specification files, and `compatibility` noting the BartonCore Docker development container. - -#### Scenario: Valid frontmatter -- **WHEN** the SKILL.md file is parsed -- **THEN** the YAML frontmatter SHALL contain `name: validate-sbmd` matching the directory name -- **AND** the `description` SHALL mention SBMD, validation, schema, and Matter drivers - -### Requirement: SBMD validation skill documents spec validation -The skill SHALL explain how to validate SBMD YAML files using `scripts/ci/validate_sbmd_specs.py`. The skill SHALL document the command syntax: `python3 scripts/ci/validate_sbmd_specs.py [ ...]`. The skill SHALL note the validator checks both YAML schema conformance and embedded JavaScript syntax. - -#### Scenario: Agent validates SBMD specs -- **WHEN** the agent needs to validate SBMD spec files after editing -- **THEN** the skill SHALL show the validation command with the correct schema and spec file paths - -### Requirement: SBMD validation skill documents stub generation -The skill SHALL explain how to generate JavaScript stubs from TypeScript definitions using `scripts/ci/generate_sbmd_stubs.py`. The skill SHALL document the command syntax: `python3 scripts/ci/generate_sbmd_stubs.py `. The skill SHALL note that stubs keep the validator in sync with TypeScript interface definitions. - -#### Scenario: Agent regenerates SBMD stubs -- **WHEN** the agent has modified TypeScript definition files -- **THEN** the skill SHALL show the stub generation command - -### Requirement: SBMD validation skill documents spec file locations -The skill SHALL identify that SBMD spec files live at `core/deviceDrivers/matter/sbmd/specs/`, the schema is generated during the build, and the stubs output is at `build/sbmd-stubs.json`. The skill SHALL note that validation runs automatically during build when `BCORE_MATTER_VALIDATE_SCHEMAS=ON` (the default). - -#### Scenario: Agent finds SBMD specs -- **WHEN** the agent needs to locate SBMD spec files -- **THEN** the skill SHALL direct the agent to `core/deviceDrivers/matter/sbmd/specs/` - -### Requirement: SBMD validation skill includes error recovery pattern -The skill SHALL instruct the agent that if `python3` or validation dependencies are not found, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. - -#### Scenario: Validation tools not found outside Docker -- **WHEN** validation scripts fail with missing dependencies -- **AND** `/.dockerenv` does not exist -- **THEN** the agent SHALL stop and tell the user to run inside the development container diff --git a/openspec/specs/agent-skills/spec.md b/openspec/specs/agent-skills/spec.md new file mode 100644 index 00000000..169cbc5e --- /dev/null +++ b/openspec/specs/agent-skills/spec.md @@ -0,0 +1,316 @@ +# Agent Skills + +## Purpose + +Specifies the structure and required content of the repository's AI agent skills (`.github/skills/*/SKILL.md`): each skill conforms to the Agent Skills format and documents its domain — building, debugging, code formatting, unit tests, integration tests, Matter virtual devices, and SBMD validation — including an error-recovery pattern. + +## Requirements + +### Requirement: Build skill SKILL.md conforms to Agent Skills spec +The `build` skill SHALL be located at `.github/skills/build/SKILL.md`. The frontmatter SHALL include `name: build`, a `description` field explaining the skill covers building BartonCore, and `compatibility` noting it requires the BartonCore Docker development container. The `name` field SHALL match the parent directory name. + +#### Scenario: Valid frontmatter +- **WHEN** the SKILL.md file is parsed +- **THEN** the YAML frontmatter SHALL contain `name: build` matching the directory name +- **AND** the `description` field SHALL be non-empty and under 1024 characters +- **AND** the `description` SHALL mention building, compiling, and CMake + +### Requirement: Build skill documents the build hierarchy +The skill body SHALL explain the layered build system: `build.sh` is the top-level convenience script that calls CMake, and CMake in turn invokes `build-matter.sh` when needed. The skill SHALL make clear that for incremental rebuilds after code changes, `cmake --build build` is the preferred command. + +#### Scenario: Agent performs incremental rebuild +- **WHEN** the agent needs to rebuild after modifying source files +- **THEN** the skill SHALL instruct the agent to run `cmake --build build` + +#### Scenario: Agent performs full build from scratch +- **WHEN** the agent needs a clean or first-time build +- **THEN** the skill SHALL instruct the agent to run `./build.sh` + +#### Scenario: Agent understands build-matter.sh is automatic +- **WHEN** the agent reads the build skill +- **THEN** the skill SHALL state that `build-matter.sh` is invoked automatically by CMake and rarely needs manual execution + +### Requirement: Build skill documents CMake configuration flags +The skill SHALL list the key CMake boolean flags that control the build: `BCORE_ZIGBEE`, `BCORE_MATTER`, `BCORE_THREAD`, `BCORE_PHILIPS_HUE`, `BCORE_BUILD_REFERENCE`, `BCORE_BUILD_WITH_ASAN`, `BCORE_BUILD_WITH_SSP`, `BCORE_GEN_GIR`, `BCORE_TEST_COVERAGE`, and `BCORE_MATTER_VALIDATE_SCHEMAS`. The skill SHALL explain how to pass flags (via `-D` to cmake or appended to `build.sh`). + +#### Scenario: Agent disables a subsystem for build +- **WHEN** the agent needs to build without Zigbee support +- **THEN** the skill SHALL show how to pass `-DBCORE_ZIGBEE=OFF` to the build + +### Requirement: Build skill documents dev profile +The skill SHALL note that the default development profile is at `config/cmake/platforms/dev/linux.cmake` and is automatically used by `build.sh`. The skill SHALL list what the dev profile enables (Debug build, ASAN, SSP, GIR, random Matter port). + +#### Scenario: Agent understands default build configuration +- **WHEN** the agent runs `./build.sh` without arguments +- **THEN** the skill SHALL explain that it uses the dev profile with Debug, ASAN, and SSP enabled + +### Requirement: Build skill includes error recovery pattern +The skill SHALL instruct the agent that if a build command fails with a tool-not-found error (e.g., `cmake: command not found`, `make: command not found`), the agent SHALL check for `/.dockerenv`. If the file does not exist, the agent SHALL inform the user they need to run inside the BartonCore development container. + +#### Scenario: Build fails outside Docker +- **WHEN** a build command fails with a missing tool error +- **AND** the file `/.dockerenv` does not exist +- **THEN** the agent SHALL stop and tell the user to run inside the development container + +### Requirement: Debug skill SKILL.md conforms to Agent Skills spec +The `debug` skill SHALL be located at `.github/skills/debug/SKILL.md`. The frontmatter SHALL include `name: debug`, a `description` field explaining the skill covers debugging BartonCore with gdb and pdb, and `compatibility` noting the BartonCore Docker development container. + +#### Scenario: Valid frontmatter +- **WHEN** the SKILL.md file is parsed +- **THEN** the YAML frontmatter SHALL contain `name: debug` matching the directory name +- **AND** the `description` SHALL mention debugging, gdb, pdb, reference app, and integration tests + +### Requirement: Debug skill documents gdb for the reference app +The skill SHALL explain how to debug the reference app with gdb. The skill SHALL document the binary location (`build/reference/barton-core-reference`), common command-line flags (`-b` for SBMD specs path, `-z` to disable Zigbee, `-t` to disable Thread, `-m` to disable Matter), and how to start a gdb session. The skill SHALL note that the VS Code launch configurations (`(gdb) Reference App` variants) provide an alternative. + +#### Scenario: Agent debugs the reference app with gdb +- **WHEN** the agent needs to debug the reference app +- **THEN** the skill SHALL show how to launch `gdb build/reference/barton-core-reference` and pass arguments +- **AND** the skill SHALL mention available VS Code launch configs as an alternative + +### Requirement: Debug skill documents pdb for integration tests +The skill SHALL explain how to debug Python integration tests using pdb. The skill SHALL show how to insert breakpoints (`breakpoint()` or `import pdb; pdb.set_trace()`) and how to run pytest with debugger support (`./testing/py_test.sh testing/test/.py -s --no-header`). The skill SHALL note that the VS Code Python debugger can also be used via the Testing panel. + +#### Scenario: Agent debugs an integration test with pdb +- **WHEN** the agent needs to debug a Python integration test +- **THEN** the skill SHALL show how to insert a breakpoint and run the test with `-s` flag +- **AND** the skill SHALL mention VS Code Testing panel as an alternative + +### Requirement: Debug skill documents gdb with python3-gdb for native visibility +The skill SHALL explain how to use gdb with python3 to debug C/C++ code exercised by Python integration tests. The skill SHALL document the workflow: run `gdb python3`, answer yes to debug info download prompts, set C breakpoints (may require running first for symbol loading), then execute the test via `run -m pytest testing/test/.py`. The skill SHALL note that if the build uses ASAN, the agent must run `set env LD_PRELOAD=` in gdb before `run`. The skill SHALL note the command to find libasan: `gcc -print-file-name=libasan.so`. + +#### Scenario: Agent debugs native code via Python tests +- **WHEN** the agent needs to debug C/C++ code triggered by a Python integration test +- **THEN** the skill SHALL show the gdb+python3 workflow step by step +- **AND** the skill SHALL include the ASAN LD_PRELOAD setup if needed + +### Requirement: Debug skill documents ASAN considerations +The skill SHALL explain that the development build enables Address Sanitizer (ASAN) by default. The skill SHALL document that `testing/py_test.sh` handles ASAN preloading automatically for pytest runs, and that for gdb sessions the agent must manually preload libasan using `set env LD_PRELOAD` before running. The skill SHALL note that `ASAN_OPTIONS=verify_asan_link_order=0` is set in the container environment. + +#### Scenario: Agent understands ASAN in debugging context +- **WHEN** the agent reads the debug skill +- **THEN** it SHALL understand that ASAN is enabled by default in dev builds +- **AND** it SHALL know how to handle ASAN when debugging + +### Requirement: Debug skill includes error recovery pattern +The skill SHALL instruct the agent that if `gdb` is not found, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. The skill SHALL note that the Docker container has `SYS_PTRACE` capability enabled for gdb to work. + +#### Scenario: gdb not found outside Docker +- **WHEN** `gdb` is not found +- **AND** `/.dockerenv` does not exist +- **THEN** the agent SHALL stop and tell the user to run inside the development container + +### Requirement: Format code skill SKILL.md conforms to Agent Skills spec +The `format-code` skill SHALL be located at `.github/skills/format-code/SKILL.md`. The frontmatter SHALL include `name: format-code`, a `description` field explaining the skill covers C/C++ code formatting, and `compatibility` noting the BartonCore Docker development container. + +#### Scenario: Valid frontmatter +- **WHEN** the SKILL.md file is parsed +- **THEN** the YAML frontmatter SHALL contain `name: format-code` matching the directory name +- **AND** the `description` SHALL mention clang-format, formatting, and C/C++ + +### Requirement: Format code skill documents clang-format usage +The skill SHALL instruct the agent to run `clang-format` from the repository root so it picks up the `.clang-format` configuration. The skill SHALL show the command to format a file: `clang-format -i `. + +#### Scenario: Agent formats a file +- **WHEN** the agent needs to format a C/C++ file +- **THEN** the skill SHALL instruct the agent to run `clang-format -i ` from the repo root + +### Requirement: Format code skill documents the diff-only rule +The skill SHALL state that agents MUST only format code that is part of the current diff. Agents SHALL NOT reformat entire files they did not modify. The pre-commit hook enforces formatting on staged files automatically. + +#### Scenario: Agent respects diff-only rule +- **WHEN** the agent has modified a C/C++ file +- **THEN** the agent SHALL only format the file it modified +- **AND** the agent SHALL NOT run clang-format on unmodified files + +### Requirement: Format code skill documents manual blank-line rules +The skill SHALL document the blank-line conventions that clang-format cannot enforce: a blank line before `if`, `for`, `while`, `switch`, and `return` statements (unless preceded by an opening brace or another control-flow statement's opening line), and a blank line after a closing brace `}` (unless followed by `else`, `catch`, or another closing brace). + +#### Scenario: Agent applies blank-line rules +- **WHEN** the agent writes or modifies C/C++ code +- **THEN** the agent SHALL apply the manual blank-line conventions + +### Requirement: Format code skill documents pre-commit hook +The skill SHALL note that a pre-commit hook (`hooks/pre-commit`) automatically runs clang-format on staged files at commit time. The skill SHALL note that hooks are installed via `./hooks/install.sh`. + +#### Scenario: Agent understands pre-commit integration +- **WHEN** the agent reads the format code skill +- **THEN** it SHALL know that formatting is enforced automatically at commit time + +### Requirement: Format code skill includes error recovery pattern +The skill SHALL instruct the agent that if `clang-format` is not found, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. + +#### Scenario: clang-format not found outside Docker +- **WHEN** `clang-format` is not found +- **AND** `/.dockerenv` does not exist +- **THEN** the agent SHALL stop and tell the user to run inside the development container + +### Requirement: Integration test skill SKILL.md conforms to Agent Skills spec +The `run-integration-tests` skill SHALL be located at `.github/skills/run-integration-tests/SKILL.md`. The frontmatter SHALL include `name: run-integration-tests`, a `description` field explaining the skill covers running Python integration tests, and `compatibility` noting the BartonCore Docker development container. + +#### Scenario: Valid frontmatter +- **WHEN** the SKILL.md file is parsed +- **THEN** the YAML frontmatter SHALL contain `name: run-integration-tests` matching the directory name +- **AND** the `description` field SHALL mention integration tests, pytest, and Python + +### Requirement: Integration test skill documents test execution +The skill SHALL instruct the agent to run integration tests via `testing/py_test.sh testing/` from the repo root. The skill SHALL explain that `py_test.sh` is a wrapper that handles Address Sanitizer (ASAN) `LD_PRELOAD` setup automatically. + +#### Scenario: Agent runs all integration tests +- **WHEN** the agent needs to run integration tests +- **THEN** the skill SHALL instruct the agent to run `./testing/py_test.sh testing/` + +### Requirement: Integration test skill documents test filtering +The skill SHALL explain how to run specific tests: by file path (`./testing/py_test.sh testing/test/light_test.py`), by keyword (`./testing/py_test.sh testing/ -k "light"`), and by marker (`./testing/py_test.sh testing/ -m "requires_matterjs"`). + +#### Scenario: Agent runs specific integration tests +- **WHEN** the agent needs to run a subset of integration tests +- **THEN** the skill SHALL show filtering by file path, `-k` keyword, and `-m` marker + +### Requirement: Integration test skill documents prerequisites +The skill SHALL explain the prerequisite chain for integration tests: the project must be built, the built artifacts must be installed (`cmake --build build --target install`), D-Bus must be running (`sudo service dbus start`), and matter.js dependencies must be installed (`npm --prefix testing/mocks/devices/matterjs ci`) for tests using the `requires_matterjs` marker. The skill SHALL note that `scripts/ci/run_integration_tests.sh` performs all prerequisites automatically. + +#### Scenario: Agent prepares prerequisites manually +- **WHEN** the agent needs to run integration tests after a fresh build +- **THEN** the skill SHALL list the prerequisite steps in order + +#### Scenario: Agent uses CI script for full setup +- **WHEN** the agent wants to ensure all prerequisites are met +- **THEN** the skill SHALL offer `./scripts/ci/run_integration_tests.sh` as the all-in-one option + +### Requirement: Integration test skill documents pytest configuration +The skill SHALL note that pytest is configured in `pyproject.toml` with `testpaths = ["testing"]`, log level `DEBUG`, and `--capture=sys`. + +#### Scenario: Agent understands test discovery +- **WHEN** the agent needs to find where tests are defined +- **THEN** the skill SHALL direct the agent to `testing/test/` for test files and `pyproject.toml` for pytest configuration + +### Requirement: Integration test skill includes error recovery pattern +The skill SHALL instruct the agent that if `pytest` or `py_test.sh` fails with a command-not-found or import error, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. + +#### Scenario: pytest not found outside Docker +- **WHEN** `pytest` fails with a command-not-found or import error +- **AND** `/.dockerenv` does not exist +- **THEN** the agent SHALL stop and tell the user to run inside the development container + +### Requirement: Matter virtual devices skill SKILL.md conforms to Agent Skills spec +The `matter-virtual-devices` skill SHALL be located at `.github/skills/matter-virtual-devices/SKILL.md`. The frontmatter SHALL include `name: matter-virtual-devices`, a `description` field explaining the skill covers working with Matter test devices, and `compatibility` noting the BartonCore Docker development container. + +#### Scenario: Valid frontmatter +- **WHEN** the SKILL.md file is parsed +- **THEN** the YAML frontmatter SHALL contain `name: matter-virtual-devices` matching the directory name +- **AND** the `description` SHALL mention Matter, sample apps, chip-tool, virtual devices, and testing + +### Requirement: Matter skill documents pre-built sample apps +The skill SHALL list the Matter sample apps available in the Docker container at `/usr/local/bin/`: `chip-lighting-app`, `chip-lock-app`, `thermostat-app`, `contact-sensor-app`. The skill SHALL explain how to start each app and what command-line options they support (e.g., `--discriminator`, `--KVS`). The skill SHALL explain that these are CHIP SDK sample apps compiled from the Matter SDK. + +#### Scenario: Agent starts a pre-built light device +- **WHEN** the agent needs a Matter light device for testing +- **THEN** the skill SHALL show how to start `chip-lighting-app` with appropriate arguments + +#### Scenario: Agent starts a pre-built lock device +- **WHEN** the agent needs a Matter door lock device for testing +- **THEN** the skill SHALL show how to start `chip-lock-app` with appropriate arguments + +### Requirement: Matter skill documents chip-tool +The skill SHALL explain that `chip-tool` is available at `/usr/local/bin/chip-tool` and is used to commission and interact with Matter devices. The skill SHALL show common chip-tool commands: pairing (`chip-tool pairing code `), cluster operations (`chip-tool onoff on `), and reading attributes (`chip-tool read `). + +#### Scenario: Agent commissions a device with chip-tool +- **WHEN** the agent needs to commission a Matter device +- **THEN** the skill SHALL show the `chip-tool pairing code` command with placeholder node ID and pairing code + +#### Scenario: Agent sends a cluster command +- **WHEN** the agent needs to interact with a commissioned device +- **THEN** the skill SHALL show cluster command examples (e.g., `chip-tool onoff on`) + +### Requirement: Matter skill documents matter.js virtual devices +The skill SHALL explain the matter.js virtual device framework located at `testing/mocks/devices/matterjs/`. The skill SHALL describe the Python wrapper classes in `testing/mocks/devices/matter/` (`MatterLight`, `MatterDoorLock`) and how they spawn Node.js subprocesses. The skill SHALL explain the sideband API for programmatic control (`device.sideband.send()`, `device.sideband.get_state()`). + +#### Scenario: Agent understands matter.js device architecture +- **WHEN** the agent reads the matter virtual devices skill +- **THEN** it SHALL understand that Python classes wrap matter.js Node.js processes +- **AND** it SHALL know about the sideband control interface + +### Requirement: Matter skill documents creating custom virtual devices +The skill SHALL explain how to create a new matter.js virtual device: create a JavaScript entry point in `testing/mocks/devices/matterjs/src/`, create a Python wrapper class in `testing/mocks/devices/matter/` extending `MatterDevice`, and specify the `matterjs_entry_point`. The skill SHALL note that custom devices useful for testing should be committed into the test harness with new integration tests, but this decision is made by the human. + +#### Scenario: Agent creates a new virtual device type +- **WHEN** the agent needs a device type not already available +- **THEN** the skill SHALL outline the steps to create a new matter.js device and Python wrapper +- **AND** the skill SHALL note that the human decides whether to commit it + +### Requirement: Matter skill includes error recovery pattern +The skill SHALL instruct the agent that if sample app binaries or `chip-tool` are not found, or if `node` is not available for matter.js devices, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. + +#### Scenario: chip-tool not found outside Docker +- **WHEN** `chip-tool` is not found in PATH +- **AND** `/.dockerenv` does not exist +- **THEN** the agent SHALL stop and tell the user to run inside the development container + +### Requirement: Unit test skill SKILL.md conforms to Agent Skills spec +The `run-unit-tests` skill SHALL be located at `.github/skills/run-unit-tests/SKILL.md`. The frontmatter SHALL include `name: run-unit-tests`, a `description` field explaining the skill covers running C/C++ unit tests, and `compatibility` noting the BartonCore Docker development container. + +#### Scenario: Valid frontmatter +- **WHEN** the SKILL.md file is parsed +- **THEN** the YAML frontmatter SHALL contain `name: run-unit-tests` matching the directory name +- **AND** the `description` field SHALL mention unit tests, ctest, CMocka, and Google Test + +### Requirement: Unit test skill documents test execution +The skill SHALL instruct the agent to run unit tests via `ctest --output-on-failure --test-dir build`. The skill SHALL note that the project must be built first. + +#### Scenario: Agent runs all unit tests +- **WHEN** the agent needs to run unit tests +- **THEN** the skill SHALL instruct the agent to run `ctest --output-on-failure --test-dir build` + +### Requirement: Unit test skill documents test filtering +The skill SHALL explain how to run a subset of tests using `ctest -R --test-dir build` and how to list available tests with `ctest -N --test-dir build`. + +#### Scenario: Agent runs specific unit tests +- **WHEN** the agent needs to run only tests matching a pattern +- **THEN** the skill SHALL instruct the agent to use `ctest -R --output-on-failure --test-dir build` + +### Requirement: Unit test skill documents test frameworks +The skill SHALL note that C tests use CMocka and C++ tests use Google Test/Google Mock. The skill SHALL explain that tests are defined in CMakeLists.txt files using `bcore_add_cmocka_test()` and `bcore_add_cpp_test()` macros, located under `core/test/`, `libs/device/descriptors/c/test/`, `libs/device/philipsHue/c/test/` when enabled, and `api/c/test/`. + +#### Scenario: Agent understands test framework context +- **WHEN** the agent reads the unit test skill +- **THEN** it SHALL know that C tests use CMocka and C++ tests use Google Test +- **AND** it SHALL know the test source locations + +### Requirement: Unit test skill includes error recovery pattern +The skill SHALL instruct the agent that if `ctest` fails with a command-not-found error, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. + +#### Scenario: ctest not found outside Docker +- **WHEN** `ctest` fails with a command-not-found error +- **AND** `/.dockerenv` does not exist +- **THEN** the agent SHALL stop and tell the user to run inside the development container + +### Requirement: SBMD validation skill SKILL.md conforms to Agent Skills spec +The `validate-sbmd` skill SHALL be located at `.github/skills/validate-sbmd/SKILL.md`. The frontmatter SHALL include `name: validate-sbmd`, a `description` field explaining the skill covers validating SBMD specification files, and `compatibility` noting the BartonCore Docker development container. + +#### Scenario: Valid frontmatter +- **WHEN** the SKILL.md file is parsed +- **THEN** the YAML frontmatter SHALL contain `name: validate-sbmd` matching the directory name +- **AND** the `description` SHALL mention SBMD, validation, schema, and Matter drivers + +### Requirement: SBMD validation skill documents spec validation +The skill SHALL explain how to validate SBMD v4 `.sbmd.js` driver files using `scripts/ci/validate_sbmd_specs.py`. The skill SHALL document the command syntax: `python3 scripts/ci/validate_sbmd_specs.py [ ...]`. The skill SHALL note that the validator uses Node.js to evaluate each `.sbmd.js` file, extract its `SbmdDriver()` registration object, and validate that object against the JSON schema (`sbmd-spec-schema.json`), and that this validation also runs automatically during the build via the `validate_sbmd_specs` target when `BCORE_MATTER_VALIDATE_SCHEMAS=ON` (the default). + +#### Scenario: Agent validates SBMD specs +- **WHEN** the agent needs to validate SBMD spec files after editing +- **THEN** the skill SHALL show the validation command with the correct schema directory and `.sbmd.js` spec file paths + +### Requirement: SBMD validation skill documents spec file locations +The skill SHALL identify that SBMD driver files live at `core/deviceDrivers/matter/sbmd/specs/` as `.sbmd.js` files, the JSON schema is at `core/deviceDrivers/matter/sbmd/schema/sbmd-spec-schema.json`, and the validation script is `scripts/ci/validate_sbmd_specs.py`. The skill SHALL note that validation runs automatically during build when `BCORE_MATTER_VALIDATE_SCHEMAS=ON` (the default). + +#### Scenario: Agent finds SBMD specs +- **WHEN** the agent needs to locate SBMD spec files +- **THEN** the skill SHALL direct the agent to `core/deviceDrivers/matter/sbmd/specs/` + +### Requirement: SBMD validation skill includes error recovery pattern +The skill SHALL instruct the agent that if `python3` or validation dependencies are not found, the agent SHALL check for `/.dockerenv`. If absent, the agent SHALL inform the user they need to run inside the development container. + +#### Scenario: Validation tools not found outside Docker +- **WHEN** validation scripts fail with missing dependencies +- **AND** `/.dockerenv` does not exist +- **THEN** the agent SHALL stop and tell the user to run inside the development container diff --git a/openspec/specs/changelog-generation/spec.md b/openspec/specs/changelog-generation/spec.md deleted file mode 100644 index 620f1241..00000000 --- a/openspec/specs/changelog-generation/spec.md +++ /dev/null @@ -1,29 +0,0 @@ -# Changelog Generation - -## Purpose - -Specifies how the project's changelog is generated and maintained across releases, ensuring new entries are prepended and a full historical changelog is preserved. - -## Requirements - -### Requirement: Changelog entries are prepended across releases -The release workflow SHALL produce a `CHANGELOG.md` where each new release entry is prepended -above previous entries, preserving the full release history in the committed file. - -#### Scenario: Second release after initial -- **WHEN** a release is performed and `CHANGELOG.md` already contains entries from prior releases -- **THEN** the new version's entry SHALL appear above the existing entries -- **THEN** all prior entries SHALL remain intact - -### Requirement: Full historical changelog exists -The repository SHALL contain changelog entries for all semver releases from 1.0.0 through the -current version, generated from conventional commit history. - -#### Scenario: Historical entries present -- **WHEN** a user views `CHANGELOG.md` -- **THEN** there SHALL be a section for each semver tag (1.0.0, 1.1.0, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 3.0.0, 3.1.0, 3.1.1, 4.0.0) -- **THEN** entries from 2.0.0 onward SHALL contain conventional commit details (type, scope, summary, hash, author) - -#### Scenario: Pre-conventional-commits releases -- **WHEN** a user views changelog entries for versions prior to 2.0.0 (1.0.0, 1.1.0) -- **THEN** those entries SHALL at minimum contain the version header and date diff --git a/openspec/specs/camera-session-lifecycle/spec.md b/openspec/specs/device-class-camera/spec.md similarity index 85% rename from openspec/specs/camera-session-lifecycle/spec.md rename to openspec/specs/device-class-camera/spec.md index dd6400b6..4f63e6c7 100644 --- a/openspec/specs/camera-session-lifecycle/spec.md +++ b/openspec/specs/device-class-camera/spec.md @@ -1,7 +1,8 @@ -# camera-session-lifecycle Specification +# Device Class: Camera ## Purpose -The protocol-agnostic abstract camera endpoint contract: the session lifecycle executes (`createSession`, `stream`, `takePicture`, `destroySession`) with no `sessionStatus` resource. The `stream` execute returns the active protocol and its entry-point URI; all in-session state, error, and teardown signaling lives on the protocol-specific endpoint (e.g. `ep/webrtc`), not the abstract one. + +Specifies the Barton `camera` device class: the protocol-agnostic abstract camera endpoint (`ep/camera`) exposing the session lifecycle as execute resources only — `createSession`, `stream`, `takePicture`, and `destroySession` — with no `sessionStatus` resource. The `stream` execute returns the active streaming protocol and its entry-point URI; all in-session state, error, and teardown signaling lives on the protocol-specific endpoint (e.g. `ep/webrtc`, specified separately by the `webrtc-signaling-endpoint` spec), not the abstract one. Concrete camera drivers are `.sbmd.js` files governed by the SBMD schema validator. ## Requirements ### Requirement: Abstract camera endpoint provides protocol-agnostic session lifecycle diff --git a/openspec/specs/device-class-environmental-sensor/spec.md b/openspec/specs/device-class-environmental-sensor/spec.md new file mode 100644 index 00000000..488fedba --- /dev/null +++ b/openspec/specs/device-class-environmental-sensor/spec.md @@ -0,0 +1,40 @@ +# Device Class: Environmental Sensor + +## Purpose + +Specifies the Barton `environmentalSensor` device class: a device with a `sensor`-profile endpoint exposing measured environmental quantities — `temperature` and `humidity` — mapped from the Matter Temperature Measurement (0x0402) and Relative Humidity Measurement (0x0405) clusters. Concrete environmental-sensor drivers (standalone temperature, standalone humidity, and vendor-specific composite devices) are `.sbmd.js` files governed by the SBMD schema validator. + +## Requirements + +### Requirement: Environmental sensor device type claiming +A device advertising Matter device type 0x0302 (Temperature Sensor) and/or 0x0307 (Humidity Sensor) SHALL be claimed into the `environmentalSensor` device class. A vendor-specific composite driver that matches by vendor and product ID SHALL take priority over the generic per-function drivers for the same device. + +#### Scenario: Standalone temperature sensor is claimed +- **WHEN** a Matter device exposes an endpoint with device type 0x0302 +- **THEN** it SHALL be claimed into the `environmentalSensor` class and expose a `temperature` resource + +#### Scenario: Vendor-specific composite device takes priority +- **WHEN** a Matter device exposes device types 0x0302 and 0x0307 and matches a vendor/product-specific driver +- **THEN** the vendor-specific driver SHALL claim the device instead of the generic temperature and humidity drivers + +### Requirement: Temperature resource +The `temperature` resource SHALL be backed by the Matter Temperature Measurement cluster (0x0402), MeasuredValue attribute (0x0000, int16). Its value SHALL be the raw hundredths-of-a-degree-Celsius value serialized as a string; a null measurement (0x8000) SHALL serialize as an empty string `""`. + +#### Scenario: Temperature read +- **WHEN** the MeasuredValue for cluster 0x0402 is 2550 +- **THEN** the `temperature` resource value SHALL be `"2550"` + +#### Scenario: Null temperature +- **WHEN** the MeasuredValue for cluster 0x0402 is null (0x8000) +- **THEN** the `temperature` resource value SHALL be `""` + +### Requirement: Humidity resource +The `humidity` resource SHALL be backed by the Matter Relative Humidity Measurement cluster (0x0405), MeasuredValue attribute (0x0000, uint16). Its value SHALL convert the raw hundredths-of-a-percent value to whole percent, serialized as a string; a null measurement (0xFFFF) SHALL serialize as an empty string `""`. + +#### Scenario: Humidity read +- **WHEN** the MeasuredValue for cluster 0x0405 is 5000 +- **THEN** the `humidity` resource value SHALL be `"50"` + +#### Scenario: Null humidity +- **WHEN** the MeasuredValue for cluster 0x0405 is null (0xFFFF) +- **THEN** the `humidity` resource value SHALL be `""` diff --git a/openspec/specs/device-class-light/spec.md b/openspec/specs/device-class-light/spec.md new file mode 100644 index 00000000..a2741632 --- /dev/null +++ b/openspec/specs/device-class-light/spec.md @@ -0,0 +1,48 @@ +# Device Class: Light + +## Purpose + +Specifies the Barton `light` device class: a device with a `light`-profile endpoint exposing on/off (`isOn`) and optional brightness (`currentLevel`) resources. It also specifies how an SBMD light driver maps the Matter On/Off (0x0006) and Level Control (0x0008) clusters onto those resources. Concrete light drivers are `.sbmd.js` files governed by the SBMD schema validator. + +## Requirements + +### Requirement: Light device type claiming +A device advertising a Matter light device type SHALL be claimed into the Barton `light` device class with at least one endpoint of profile `light`. The light class SHALL cover the Matter light device types 0x0100, 0x010a, 0x0101, 0x010b, 0x0102, 0x0200, 0x010d, 0x0210, 0x010c, 0x0220, 0x0103, 0x0104, and 0x0105. + +#### Scenario: Light device is claimed +- **WHEN** a Matter device advertising device type 0x0100 (On/Off Light) is commissioned +- **THEN** Barton SHALL create a device with device class `light` and an endpoint with profile `light` + +### Requirement: On/off resource (isOn) +The `isOn` resource on the light endpoint SHALL be readable, writable, dynamic, and emit events. It SHALL be backed by the Matter On/Off cluster (0x0006): attribute reports for the OnOff attribute (0x0000) SHALL update the resource, its initial value SHALL be seeded from device supplements, and writes SHALL send the On (0x0001) or Off (0x0000) command on cluster 0x0006. + +#### Scenario: On/Off attribute report updates resource +- **WHEN** a Matter attribute report for cluster 0x0006, attribute 0x0000 arrives with value `true` +- **THEN** the `isOn` resource is updated to `"true"` + +#### Scenario: Write true sends On command +- **WHEN** a Barton write operation sets `isOn` to `"true"` +- **THEN** the driver sends Matter command 0x0001 (On) on cluster 0x0006 + +#### Scenario: Write false sends Off command +- **WHEN** a Barton write operation sets `isOn` to `"false"` +- **THEN** the driver sends Matter command 0x0000 (Off) on cluster 0x0006 + +#### Scenario: Seed handler reads initial value +- **WHEN** the device is commissioned or the service restarts +- **THEN** the initial `isOn` value is seeded from the OnOff attribute in device supplements + +### Requirement: Brightness resource (currentLevel, optional) +The `currentLevel` resource on the light endpoint SHALL be optional, present only when the device exposes the Level Control cluster (0x0008). It SHALL map the Matter level (0–254) to a percentage string (0–100), and writes SHALL send the MoveToLevelWithOnOff command (0x0004) on cluster 0x0008. + +#### Scenario: Level attribute report updates resource as percentage +- **WHEN** a Matter attribute report for cluster 0x0008, attribute 0x0000 arrives with value 127 +- **THEN** the `currentLevel` resource is updated to `"50"` + +#### Scenario: Write percentage sends MoveToLevel command +- **WHEN** a Barton write sets `currentLevel` to `"75"` +- **THEN** the driver sends MoveToLevelWithOnOff with level 191 (round(75/100*254)), transition time 0 + +#### Scenario: Resource skipped when cluster absent +- **WHEN** a commissioned device does not have the Level Control cluster (0x0008) +- **THEN** the `currentLevel` resource is not created and no error occurs diff --git a/openspec/specs/matter-thermostat-sbmd/spec.md b/openspec/specs/device-class-thermostat/spec.md similarity index 95% rename from openspec/specs/matter-thermostat-sbmd/spec.md rename to openspec/specs/device-class-thermostat/spec.md index 236c5060..fe0170ee 100644 --- a/openspec/specs/matter-thermostat-sbmd/spec.md +++ b/openspec/specs/device-class-thermostat/spec.md @@ -1,8 +1,8 @@ -# Matter Thermostat SBMD Driver +# Device Class: Thermostat ## Purpose -Specifies the SBMD driver that claims the Matter Thermostat device type and maps its cluster attributes — setpoints, absolute limits, system mode, running state, and optional fan controls — to Barton resources with subscription reporting. +Specifies the Barton `thermostat` device class: a device with a `thermostat`-profile endpoint exposing setpoint, local-temperature, system-mode, running-state, and optional fan-control resources. It also specifies how an SBMD thermostat driver maps the Matter Thermostat (0x0301) device type and its Thermostat (0x0201) and Fan Control (0x0202) clusters onto those resources. Concrete thermostat drivers are `.sbmd.js` files governed by the SBMD schema validator. ## Requirements diff --git a/openspec/specs/endpoint-cluster-fallback/spec.md b/openspec/specs/endpoint-cluster-fallback/spec.md deleted file mode 100644 index 53034b97..00000000 --- a/openspec/specs/endpoint-cluster-fallback/spec.md +++ /dev/null @@ -1,41 +0,0 @@ -# Endpoint Cluster Fallback - -## Purpose - -Specifies cluster-based fallback for endpoint resolution, ensuring all resource and event binding routes through `ResolveEndpointForCluster` when device-type matching alone is insufficient. - -## Requirements - -### Requirement: Endpoint resolution with cluster-based fallback -`MatterDevice` SHALL provide a `ResolveEndpointForCluster` method that resolves a Matter endpoint for a given cluster ID. When an SBMD endpoint index is provided, it SHALL first try the SBMD-mapped endpoint. If that endpoint does not host the required cluster (verified via `DeviceDataCache::EndpointHasServerCluster`), it SHALL fall back to cluster-based lookup via `GetEndpointForCluster`. - -#### Scenario: Mapped endpoint hosts the cluster -- **WHEN** `ResolveEndpointForCluster` is called with cluster `0x0402` and SBMD endpoint index 0, and the mapped Matter endpoint hosts cluster `0x0402` -- **THEN** the method SHALL return the mapped endpoint ID - -#### Scenario: Mapped endpoint does not host the cluster — fallback -- **WHEN** `ResolveEndpointForCluster` is called with cluster `0x0405` and SBMD endpoint index 0, and the mapped Matter endpoint does NOT host cluster `0x0405` but another endpoint does -- **THEN** the method SHALL fall back to `GetEndpointForCluster` and return the endpoint that hosts `0x0405` - -#### Scenario: No SBMD endpoint index provided -- **WHEN** `ResolveEndpointForCluster` is called without an SBMD endpoint index (nullopt) -- **THEN** the method SHALL use `GetEndpointForCluster` directly - -#### Scenario: No endpoint hosts the cluster -- **WHEN** `ResolveEndpointForCluster` is called with a cluster ID that no endpoint hosts -- **THEN** the method SHALL return false - -#### Scenario: Cache data not yet available -- **WHEN** `ResolveEndpointForCluster` is called and `DeviceDataCache` is null or cannot verify cluster presence via Descriptor -- **THEN** the method SHALL use the SBMD-mapped endpoint directly without fallback - -### Requirement: All resource and event binding uses ResolveEndpointForCluster -All SBMD resource binding methods (`BindResourceReadInfo`, `BindWriteInfo`, `BindExecuteInfo`, `BindResourceEventInfo`) SHALL use `ResolveEndpointForCluster` instead of the previous inline if/else pattern for endpoint resolution. - -#### Scenario: Read binding on composite device -- **WHEN** `BindResourceReadInfo` is called for a resource whose cluster is on a different Matter endpoint than the SBMD-mapped one -- **THEN** the binding SHALL resolve to the correct endpoint via cluster-based fallback - -#### Scenario: Event binding on composite device -- **WHEN** `BindResourceEventInfo` is called for an event whose cluster is on a different Matter endpoint than the SBMD-mapped one -- **THEN** the binding SHALL resolve to the correct endpoint via cluster-based fallback diff --git a/openspec/specs/matter-test-infrastructure/spec.md b/openspec/specs/matter-test-infrastructure/spec.md deleted file mode 100644 index 3e91ec3d..00000000 --- a/openspec/specs/matter-test-infrastructure/spec.md +++ /dev/null @@ -1,151 +0,0 @@ -# Matter Test Infrastructure - -## Purpose - -Specifies the matter.js-based Matter test infrastructure: the refactored `MatterDevice` backend, device classes (light, door lock) migrated to matter.js side-band control, test fixtures, conditional execution markers, and Docker environment setup. - -## Requirements - -### Requirement: MatterDevice refactored for matter.js-only backend -The `MatterDevice` base class SHALL exclusively use matter.js virtual devices. The `_app_name` attribute and all CHIP SDK sample app subprocess code SHALL be removed. `MatterDevice.__init__()` SHALL require a `matterjs_entry_point` parameter specifying the JavaScript file to run. `MatterDevice.start()` SHALL spawn a Node.js subprocess, wait for the JSON ready signal on stdout, and configure the `SidebandClient`. - -#### Scenario: MatterDevice only supports matter.js -- **WHEN** a `MatterDevice` subclass is instantiated -- **THEN** it SHALL specify a `matterjs_entry_point` -- **AND** `start()` SHALL spawn `node ` as a subprocess -- **AND** there SHALL be no code path for launching CHIP SDK sample apps - -#### Scenario: SidebandClient auto-configured on start -- **WHEN** `device.start()` is called -- **THEN** the device SHALL parse the JSON ready signal from stdout -- **AND** configure a `SidebandClient` with the reported side-band port -- **AND** expose it via `device.sideband` - -### Requirement: MatterDoorLock device class -A `MatterDoorLock` Python class SHALL be a standard `MatterDevice` subclass in `testing/mocks/devices/matter/`. It SHALL specify `matterjs_entry_point="DoorLockDevice.js"` and `device_class="doorLock"`. - -#### Scenario: Start a door lock device -- **WHEN** `device.start()` is called on a `MatterDoorLock` instance -- **THEN** a Node.js subprocess SHALL be spawned running the door lock's JavaScript entry point -- **AND** the method SHALL block until the ready signal is received on stdout -- **AND** the device SHALL be ready for commissioning via Barton using its original commissioning code - -#### Scenario: Stop a device -- **WHEN** `device.stop()` is called -- **THEN** the Node.js subprocess SHALL be terminated (SIGTERM) -- **AND** associated resources (temp directories, ports) SHALL be cleaned up - -#### Scenario: Access commissioning code -- **WHEN** `device.get_commissioning_code()` is called -- **THEN** it SHALL return a valid Matter manual pairing code suitable for commissioning - -### Requirement: MatterLight migrated to matter.js -`MatterLight` SHALL be migrated from `chip-lighting-app` to a matter.js virtual light device. It SHALL specify `matterjs_entry_point="LightDevice.js"`. Cluster registration calls (`_register_cluster`) and cluster imports SHALL be removed since the cluster class system is eliminated. The `matter_light` pytest fixture SHALL remain unchanged in its interface. - -#### Scenario: MatterLight uses matter.js -- **WHEN** `MatterLight` is instantiated -- **THEN** it SHALL use a matter.js entry point instead of `chip-lighting-app` -- **AND** `start()` SHALL spawn a Node.js subprocess -- **AND** the `matter_light` fixture SHALL work identically to before -- **AND** `MatterLight` SHALL NOT import or register any chip-tool cluster classes - -### Requirement: Chip-tool cluster classes and device interactor removed -The entire `testing/mocks/devices/matter/clusters/` directory SHALL be removed. This includes `MatterCluster` (base class), `OnOffCluster`, `LevelControlCluster`, and `ColorControlCluster`. The `_register_cluster()` method, `get_cluster()` method, and `_cluster_classes` attribute SHALL be removed from `MatterDevice`. `device_interactor.py` (`ChipToolDeviceInteractor`) SHALL be deleted entirely — chip-tool is no longer used in any capacity. The `_set_interactor()` method, `_interactor` attribute, and `_chip_tool_node_id` attribute SHALL be removed from `MatterDevice`. The `device_interactor` plugin registration SHALL be removed from `conftest.py`. Tests SHALL interact with device state through the side-band interface (`device.sideband`) and commission devices directly through the Barton API. - -#### Scenario: No cluster class system -- **WHEN** `MatterDevice` is used -- **THEN** it SHALL NOT have `_register_cluster()`, `get_cluster()`, or `_cluster_classes` -- **AND** the `testing/mocks/devices/matter/clusters/` directory SHALL NOT exist - -#### Scenario: No device interactor -- **WHEN** a device fixture creates a matter.js virtual device -- **THEN** it SHALL NOT depend on `device_interactor` -- **AND** `device_interactor.py` SHALL NOT exist -- **AND** `conftest.py` SHALL NOT register `device_interactor` as a plugin - -#### Scenario: Tests use side-band instead of clusters -- **WHEN** a test needs to query or change device state from the device side -- **THEN** it SHALL use `device.sideband.send(operation)` or `device.sideband.get_state()` -- **AND** it SHALL NOT use chip-tool cluster methods - -#### Scenario: Barton commissions directly -- **WHEN** a test commissions a device -- **THEN** it SHALL use `default_environment.get_client().commission_device(device.get_commissioning_code(), timeout)` -- **AND** the commissioning code SHALL be the device's original code (no ECM window) - -### Requirement: Light test uses side-band -`light_test.py` SHALL be updated to use the matter.js side-band interface instead of chip-tool cluster methods. It SHALL use `device.sideband.send("toggle")` and `device.sideband.get_state()` in place of `get_cluster(OnOffCluster.CLUSTER_ID).toggle()` and `get_cluster(OnOffCluster.CLUSTER_ID).is_on()`. The `requires_matterjs` marker SHALL be added. - -#### Scenario: Light test toggle via side-band -- **WHEN** the test toggles the light -- **THEN** it SHALL use `matter_light.sideband.send("toggle")` -- **AND** it SHALL NOT use `OnOffCluster` or `get_cluster()` - -#### Scenario: Light test state query via side-band -- **WHEN** the test queries the light state -- **THEN** it SHALL use `matter_light.sideband.get_state()` to check the `onOff` field -- **AND** it SHALL NOT use `OnOffCluster.is_on()` - -### Requirement: MatterDoorLock fixture -A pytest fixture `matter_door_lock` SHALL provide a started matter.js door lock device, ready for commissioning by Barton in tests. The fixture SHALL simply start the device and yield it — no chip-tool commissioning step. - -#### Scenario: Fixture provides started door lock -- **WHEN** a test function declares `matter_door_lock` as a parameter -- **THEN** the fixture SHALL start the door lock virtual device -- **AND** yield the `MatterDoorLock` instance -- **AND** clean up the device after the test completes -- **AND** the fixture SHALL NOT depend on `device_interactor` - -### Requirement: Conditional test execution marker -A custom pytest marker `requires_matterjs` SHALL be available to mark tests that depend on Node.js and matter.js. Tests with this marker SHALL be automatically skipped when the runtime dependencies are not available. - -#### Scenario: Dependencies available -- **WHEN** `node` is on the PATH and the matter.js package is installed -- **THEN** tests marked with `@pytest.mark.requires_matterjs` SHALL run normally - -#### Scenario: Node.js not available -- **WHEN** `node` is not found on the PATH -- **THEN** tests marked with `@pytest.mark.requires_matterjs` SHALL be skipped with a message indicating Node.js is required - -#### Scenario: matter.js not installed -- **WHEN** `node` is available but the matter.js package is not installed -- **THEN** tests marked with `@pytest.mark.requires_matterjs` SHALL be skipped with a message indicating matter.js is required - -### Requirement: Docker environment setup -The Docker development image SHALL include Node.js 22.x, and the CI / development container tooling (e.g., entrypoint scripts or test setup) SHALL install matter.js v0.16.10 into the `testing/mocks/devices/matterjs` workspace so that matter.js virtual device tests work out of the box in CI and development containers. - -#### Scenario: Node.js available in Docker -- **WHEN** a developer runs tests inside the Docker development container -- **THEN** `node` and `npm` SHALL be available on the PATH - -#### Scenario: matter.js installed for virtual device tests -- **WHEN** the container runtime or test setup runs the matter.js installation step (for example, `npm --prefix testing/mocks/devices/matterjs install`) inside the Docker container -- **THEN** the matter.js package (v0.16.10) SHALL be installed in the `testing/mocks/devices/matterjs` workspace and available to the virtual device scripts - -### Requirement: Reference door lock integration test -A reference integration test SHALL demonstrate the full lifecycle of commissioning, controlling, and verifying a matter.js virtual door lock through Barton APIs and the side-band interface. The test SHALL use the `matter_door_lock` fixture and be indistinguishable in structure from tests using other `MatterDevice` subclasses. - -#### Scenario: Commission door lock through Barton -- **WHEN** the test commissions the virtual door lock using Barton's `commission_device` API -- **THEN** the device SHALL appear in `get_devices_by_device_class("doorLock")` -- **AND** the device SHALL have the expected common resources - -#### Scenario: Lock device via Barton and verify via side-band -- **WHEN** the test executes the `lock` resource on the device via Barton (`execute_resource`) -- **THEN** the `locked` resource (boolean) SHALL reflect `true` -- **AND** querying the device's side-band state SHALL confirm the device is locked - -#### Scenario: Unlock device via Barton and verify via side-band -- **WHEN** the test executes the `unlock` resource on the device via Barton (`execute_resource`) -- **THEN** the `locked` resource (boolean) SHALL reflect `false` -- **AND** querying the device's side-band state SHALL confirm the device is unlocked - -#### Scenario: Side-band unlock triggers Barton resource update -- **WHEN** the test triggers a side-band `unlock` operation (simulating manual unlock) -- **THEN** the Barton client SHALL receive a resource updated event for the `locked` resource -- **AND** the `locked` resource value SHALL be `false` - -#### Scenario: Side-band lock triggers Barton resource update -- **WHEN** the test triggers a side-band `lock` operation (simulating manual lock) -- **THEN** the Barton client SHALL receive a resource updated event for the `locked` resource -- **AND** the `locked` resource value SHALL be `true` diff --git a/openspec/specs/matter-testing/spec.md b/openspec/specs/matter-testing/spec.md new file mode 100644 index 00000000..7931a400 --- /dev/null +++ b/openspec/specs/matter-testing/spec.md @@ -0,0 +1,463 @@ +# Matter Testing + +## Purpose + +Specifies the Matter test infrastructure: the matter.js virtual device framework and its side-band control, the concrete virtual test devices (door lock, thermostat with and without fan control), the Python test fixtures and side-band client, conditional test execution, and Docker environment setup. + +## Requirements + +### Requirement: MatterDevice refactored for matter.js-only backend +The `MatterDevice` base class SHALL exclusively use matter.js virtual devices. The `_app_name` attribute and all CHIP SDK sample app subprocess code SHALL be removed. `MatterDevice.__init__()` SHALL require a `matterjs_entry_point` parameter specifying the JavaScript file to run. `MatterDevice.start()` SHALL spawn a Node.js subprocess, wait for the JSON ready signal on stdout, and configure the `SidebandClient`. + +#### Scenario: MatterDevice only supports matter.js +- **WHEN** a `MatterDevice` subclass is instantiated +- **THEN** it SHALL specify a `matterjs_entry_point` +- **AND** `start()` SHALL spawn `node ` as a subprocess +- **AND** there SHALL be no code path for launching CHIP SDK sample apps + +#### Scenario: SidebandClient auto-configured on start +- **WHEN** `device.start()` is called +- **THEN** the device SHALL parse the JSON ready signal from stdout +- **AND** configure a `SidebandClient` with the reported side-band port +- **AND** expose it via `device.sideband` + +### Requirement: MatterDoorLock device class +A `MatterDoorLock` Python class SHALL be a standard `MatterDevice` subclass in `testing/mocks/devices/matter/`. It SHALL specify `matterjs_entry_point="DoorLockDevice.js"` and `device_class="doorLock"`. + +#### Scenario: Start a door lock device +- **WHEN** `device.start()` is called on a `MatterDoorLock` instance +- **THEN** a Node.js subprocess SHALL be spawned running the door lock's JavaScript entry point +- **AND** the method SHALL block until the ready signal is received on stdout +- **AND** the device SHALL be ready for commissioning via Barton using its original commissioning code + +#### Scenario: Stop a device +- **WHEN** `device.stop()` is called +- **THEN** the Node.js subprocess SHALL be terminated (SIGTERM) +- **AND** associated resources (temp directories, ports) SHALL be cleaned up + +#### Scenario: Access commissioning code +- **WHEN** `device.get_commissioning_code()` is called +- **THEN** it SHALL return a valid Matter manual pairing code suitable for commissioning + +### Requirement: MatterLight migrated to matter.js +`MatterLight` SHALL be migrated from `chip-lighting-app` to a matter.js virtual light device. It SHALL specify `matterjs_entry_point="LightDevice.js"`. Cluster registration calls (`_register_cluster`) and cluster imports SHALL be removed since the cluster class system is eliminated. The `matter_light` pytest fixture SHALL remain unchanged in its interface. + +#### Scenario: MatterLight uses matter.js +- **WHEN** `MatterLight` is instantiated +- **THEN** it SHALL use a matter.js entry point instead of `chip-lighting-app` +- **AND** `start()` SHALL spawn a Node.js subprocess +- **AND** the `matter_light` fixture SHALL work identically to before +- **AND** `MatterLight` SHALL NOT import or register any chip-tool cluster classes + +### Requirement: Chip-tool cluster classes and device interactor removed +The entire `testing/mocks/devices/matter/clusters/` directory SHALL be removed. This includes `MatterCluster` (base class), `OnOffCluster`, `LevelControlCluster`, and `ColorControlCluster`. The `_register_cluster()` method, `get_cluster()` method, and `_cluster_classes` attribute SHALL be removed from `MatterDevice`. `device_interactor.py` (`ChipToolDeviceInteractor`) SHALL be deleted entirely — chip-tool is no longer used in any capacity. The `_set_interactor()` method, `_interactor` attribute, and `_chip_tool_node_id` attribute SHALL be removed from `MatterDevice`. The `device_interactor` plugin registration SHALL be removed from `conftest.py`. Tests SHALL interact with device state through the side-band interface (`device.sideband`) and commission devices directly through the Barton API. + +#### Scenario: No cluster class system +- **WHEN** `MatterDevice` is used +- **THEN** it SHALL NOT have `_register_cluster()`, `get_cluster()`, or `_cluster_classes` +- **AND** the `testing/mocks/devices/matter/clusters/` directory SHALL NOT exist + +#### Scenario: No device interactor +- **WHEN** a device fixture creates a matter.js virtual device +- **THEN** it SHALL NOT depend on `device_interactor` +- **AND** `device_interactor.py` SHALL NOT exist +- **AND** `conftest.py` SHALL NOT register `device_interactor` as a plugin + +#### Scenario: Tests use side-band instead of clusters +- **WHEN** a test needs to query or change device state from the device side +- **THEN** it SHALL use `device.sideband.send(operation)` or `device.sideband.get_state()` +- **AND** it SHALL NOT use chip-tool cluster methods + +#### Scenario: Barton commissions directly +- **WHEN** a test commissions a device +- **THEN** it SHALL use `default_environment.get_client().commission_device(device.get_commissioning_code(), timeout)` +- **AND** the commissioning code SHALL be the device's original code (no ECM window) + +### Requirement: Light test uses side-band +`light_test.py` SHALL be updated to use the matter.js side-band interface instead of chip-tool cluster methods. It SHALL use `device.sideband.send("toggle")` and `device.sideband.get_state()` in place of `get_cluster(OnOffCluster.CLUSTER_ID).toggle()` and `get_cluster(OnOffCluster.CLUSTER_ID).is_on()`. The `requires_matterjs` marker SHALL be added. + +#### Scenario: Light test toggle via side-band +- **WHEN** the test toggles the light +- **THEN** it SHALL use `matter_light.sideband.send("toggle")` +- **AND** it SHALL NOT use `OnOffCluster` or `get_cluster()` + +#### Scenario: Light test state query via side-band +- **WHEN** the test queries the light state +- **THEN** it SHALL use `matter_light.sideband.get_state()` to check the `onOff` field +- **AND** it SHALL NOT use `OnOffCluster.is_on()` + +### Requirement: MatterDoorLock fixture +A pytest fixture `matter_door_lock` SHALL provide a started matter.js door lock device, ready for commissioning by Barton in tests. The fixture SHALL simply start the device and yield it — no chip-tool commissioning step. + +#### Scenario: Fixture provides started door lock +- **WHEN** a test function declares `matter_door_lock` as a parameter +- **THEN** the fixture SHALL start the door lock virtual device +- **AND** yield the `MatterDoorLock` instance +- **AND** clean up the device after the test completes +- **AND** the fixture SHALL NOT depend on `device_interactor` + +### Requirement: Conditional test execution marker +A custom pytest marker `requires_matterjs` SHALL be available to mark tests that depend on Node.js and matter.js. Tests with this marker SHALL be automatically skipped when the runtime dependencies are not available. + +#### Scenario: Dependencies available +- **WHEN** `node` is on the PATH and the matter.js package is installed +- **THEN** tests marked with `@pytest.mark.requires_matterjs` SHALL run normally + +#### Scenario: Node.js not available +- **WHEN** `node` is not found on the PATH +- **THEN** tests marked with `@pytest.mark.requires_matterjs` SHALL be skipped with a message indicating Node.js is required + +#### Scenario: matter.js not installed +- **WHEN** `node` is available but the matter.js package is not installed +- **THEN** tests marked with `@pytest.mark.requires_matterjs` SHALL be skipped with a message indicating matter.js is required + +### Requirement: Docker environment setup +The Docker development image SHALL include Node.js 22.x, and the CI / development container tooling (e.g., entrypoint scripts or test setup) SHALL install matter.js v0.16.10 into the `testing/mocks/devices/matterjs` workspace so that matter.js virtual device tests work out of the box in CI and development containers. + +#### Scenario: Node.js available in Docker +- **WHEN** a developer runs tests inside the Docker development container +- **THEN** `node` and `npm` SHALL be available on the PATH + +#### Scenario: matter.js installed for virtual device tests +- **WHEN** the container runtime or test setup runs the matter.js installation step (for example, `npm --prefix testing/mocks/devices/matterjs install`) inside the Docker container +- **THEN** the matter.js package (v0.16.10) SHALL be installed in the `testing/mocks/devices/matterjs` workspace and available to the virtual device scripts + +### Requirement: Reference door lock integration test +A reference integration test SHALL demonstrate the full lifecycle of commissioning, controlling, and verifying a matter.js virtual door lock through Barton APIs and the side-band interface. The test SHALL use the `matter_door_lock` fixture and be indistinguishable in structure from tests using other `MatterDevice` subclasses. + +#### Scenario: Commission door lock through Barton +- **WHEN** the test commissions the virtual door lock using Barton's `commission_device` API +- **THEN** the device SHALL appear in `get_devices_by_device_class("doorLock")` +- **AND** the device SHALL have the expected common resources + +#### Scenario: Lock device via Barton and verify via side-band +- **WHEN** the test executes the `lock` resource on the device via Barton (`execute_resource`) +- **THEN** the `locked` resource (boolean) SHALL reflect `true` +- **AND** querying the device's side-band state SHALL confirm the device is locked + +#### Scenario: Unlock device via Barton and verify via side-band +- **WHEN** the test executes the `unlock` resource on the device via Barton (`execute_resource`) +- **THEN** the `locked` resource (boolean) SHALL reflect `false` +- **AND** querying the device's side-band state SHALL confirm the device is unlocked + +#### Scenario: Side-band unlock triggers Barton resource update +- **WHEN** the test triggers a side-band `unlock` operation (simulating manual unlock) +- **THEN** the Barton client SHALL receive a resource updated event for the `locked` resource +- **AND** the `locked` resource value SHALL be `false` + +#### Scenario: Side-band lock triggers Barton resource update +- **WHEN** the test triggers a side-band `lock` operation (simulating manual lock) +- **THEN** the Barton client SHALL receive a resource updated event for the `locked` resource +- **AND** the `locked` resource value SHALL be `true` + +### Requirement: Virtual device base class initialization +The matter.js virtual device base class (`VirtualDevice`) SHALL initialize a Matter `ServerNode` with configurable vendor ID, product ID, device name, passcode, discriminator, and port. The class SHALL handle all common Matter device setup so that subclasses only need to define their device type and side-band operations. + +#### Scenario: Base class creates a Matter server node +- **WHEN** a `VirtualDevice` subclass is instantiated with device type, vendor ID, product ID, passcode, discriminator, and port parameters +- **THEN** a Matter `ServerNode` SHALL be created with those parameters and the device SHALL be ready to accept commissioning + +#### Scenario: Default values for optional parameters +- **WHEN** a `VirtualDevice` subclass is instantiated without specifying vendor ID, product ID, or port +- **THEN** the base class SHALL use sensible defaults (vendor ID `0xFFF1`, product ID `0x8000`, port `0` for dynamic assignment) + +### Requirement: Side-band HTTP server +The `VirtualDevice` base class SHALL start an HTTP server on a dynamically assigned port that accepts side-band operation requests from test drivers. The server SHALL listen on `127.0.0.1` and accept JSON-encoded POST requests. + +#### Scenario: Side-band server starts on dynamic port +- **WHEN** the virtual device starts +- **THEN** an HTTP server SHALL start on a dynamically assigned port (port 0) +- **AND** the actual assigned port SHALL be reported via stdout in a parseable format + +#### Scenario: Side-band server accepts JSON POST requests +- **WHEN** a POST request is sent to `/sideband` with a JSON body containing an `operation` field +- **THEN** the server SHALL dispatch to the registered handler for that operation +- **AND** the response SHALL be JSON with `success` (boolean) and `result` (object) fields + +#### Scenario: Side-band server rejects unknown operations +- **WHEN** a POST request is sent with an `operation` value that has no registered handler +- **THEN** the server SHALL respond with HTTP 400 and `{ "success": false, "error": "Unknown operation: " }` + +### Requirement: Side-band operation registration +The `VirtualDevice` base class SHALL provide a `registerOperation(name, handler)` method that subclasses use to register their device-specific side-band operations. Each handler SHALL be an async function that receives the request payload and returns a result object. + +#### Scenario: Subclass registers a custom operation +- **WHEN** a subclass calls `registerOperation("myOp", handler)` during construction +- **THEN** POST requests with `{ "operation": "myOp" }` SHALL be dispatched to that handler + +#### Scenario: Subclass registers multiple operations +- **WHEN** a subclass registers operations "opA" and "opB" +- **THEN** both operations SHALL be independently dispatchable via the side-band HTTP interface + +### Requirement: Ready signal on stdout +The virtual device process SHALL emit a JSON ready signal on stdout when both the Matter server node and the side-band HTTP server are fully initialized and operational. + +#### Scenario: Ready signal includes connection details +- **WHEN** the virtual device has completed initialization +- **THEN** it SHALL print a single JSON line to stdout containing `{ "ready": true, "sidebandPort": , "matterPort": , "passcode": , "discriminator": }` + +### Requirement: Graceful shutdown +The virtual device SHALL handle SIGTERM and SIGINT signals by cleanly shutting down the Matter server node and the side-band HTTP server. + +#### Scenario: Process terminated with SIGTERM +- **WHEN** the virtual device process receives SIGTERM +- **THEN** the Matter server node SHALL be closed +- **AND** the side-band HTTP server SHALL be closed +- **AND** the process SHALL exit with code 0 + +### Requirement: Package configuration +A `package.json` SHALL exist in the virtual device source directory that declares matter.js v0.16.10 as a dependency and configures the project as an ES module. + +#### Scenario: Package dependencies are correct +- **WHEN** `npm install` is run in the virtual device source directory +- **THEN** matter.js v0.16.10 SHALL be installed along with any required peer dependencies + +### Requirement: Door lock device type +The matter.js door lock virtual device SHALL present itself as a Matter Door Lock device type (Device Type ID `0x000A`) with a DoorLock cluster (Cluster ID `0x0101`) on endpoint 1. + +#### Scenario: Device advertises as door lock +- **WHEN** the door lock virtual device is started and commissioned +- **THEN** it SHALL be discoverable as a Matter Door Lock device type +- **AND** the DoorLock cluster SHALL be available on endpoint 1 + +### Requirement: Matter lock and unlock commands +The door lock virtual device SHALL respond to standard Matter DoorLock cluster `LockDoor` and `UnlockDoor` commands, updating its internal lock state accordingly. + +#### Scenario: Lock door via Matter command +- **WHEN** a Matter `LockDoor` command is sent to the device +- **THEN** the device's lock state SHALL change to locked +- **AND** the device SHALL report the updated `LockState` attribute as `Locked` (value 1) + +#### Scenario: Unlock door via Matter command +- **WHEN** a Matter `UnlockDoor` command is sent to the device +- **THEN** the device's lock state SHALL change to unlocked +- **AND** the device SHALL report the updated `LockState` attribute as `Unlocked` (value 2) + +### Requirement: Side-band lock operation +The door lock SHALL register a side-band operation `lock` that simulates a user manually locking the device (e.g., turning the thumb-turn). This operation SHALL update the Matter cluster state, triggering standard Matter notifications. + +#### Scenario: Side-band lock triggers Matter state change +- **WHEN** a side-band `lock` operation is sent to the device +- **THEN** the device's `LockState` attribute SHALL change to `Locked` +- **AND** connected Matter controllers SHALL receive a state change notification +- **AND** the side-band response SHALL be `{ "success": true, "result": { "lockState": "locked" } }` + +### Requirement: Side-band unlock operation +The door lock SHALL register a side-band operation `unlock` that simulates a user manually unlocking the device. This operation SHALL update the Matter cluster state, triggering standard Matter notifications. + +#### Scenario: Side-band unlock triggers Matter state change +- **WHEN** a side-band `unlock` operation is sent to the device +- **THEN** the device's `LockState` attribute SHALL change to `Unlocked` +- **AND** connected Matter controllers SHALL receive a state change notification +- **AND** the side-band response SHALL be `{ "success": true, "result": { "lockState": "unlocked" } }` + +### Requirement: Matter lock and unlock commands emit LockOperation events +The door lock virtual device SHALL emit a `LockOperation` event (DoorLock cluster `0x0101`, event `0x0002`) whenever the lock state changes via a Matter `LockDoor` or `UnlockDoor` command, in addition to updating the `LockState` attribute. The `LockOperationType` field SHALL be `0` (Lock) for `LockDoor` and `1` (Unlock) for `UnlockDoor`. This is required so that Matter-command-driven lock/unlock operations are reflected in event-driven SBMD resources. + +#### Scenario: LockDoor command emits LockOperation event +- **WHEN** a Matter `LockDoor` command is sent to the device +- **THEN** the device SHALL emit a `LockOperation` event with `LockOperationType` = 0 (Lock) +- **AND** the `LockState` attribute SHALL be updated to `Locked` + +#### Scenario: UnlockDoor command emits LockOperation event +- **WHEN** a Matter `UnlockDoor` command is sent to the device +- **THEN** the device SHALL emit a `LockOperation` event with `LockOperationType` = 1 (Unlock) +- **AND** the `LockState` attribute SHALL be updated to `Unlocked` + +### Requirement: Side-band operations emit LockOperation events +The door lock virtual device SHALL emit a `LockOperation` event (DoorLock cluster `0x0101`, event `0x0002`) whenever the lock state changes via a side-band operation, in addition to updating the `LockState` attribute. The `LockOperationType` field in the event SHALL be `0` (Lock) for side-band lock operations and `1` (Unlock) for side-band unlock operations. This is required for SBMD drivers that use event-driven resource updates rather than attribute subscription, such as the `door-lock.sbmd` spec with `mapper.event` on the `locked` resource. + +#### Scenario: Side-band lock emits LockOperation event +- **WHEN** a side-band `lock` operation is sent to the virtual door lock +- **THEN** the device SHALL emit a `LockOperation` event with `LockOperationType` = 0 (Lock) +- **AND** the `LockState` attribute SHALL be updated to `Locked` + +#### Scenario: Side-band unlock emits LockOperation event +- **WHEN** a side-band `unlock` operation is sent to the virtual door lock +- **THEN** the device SHALL emit a `LockOperation` event with `LockOperationType` = 1 (Unlock) +- **AND** the `LockState` attribute SHALL be updated to `Unlocked` + +### Requirement: Side-band getState operation +The door lock SHALL register a side-band operation `getState` that returns the current state of the device, including lock state and any configured users/pin codes. + +#### Scenario: Get state of locked device +- **WHEN** a side-band `getState` operation is sent and the device is locked +- **THEN** the response SHALL include `{ "success": true, "result": { "lockState": "locked", "users": [...], "pinCodes": [...] } }` + +#### Scenario: Get state of unlocked device +- **WHEN** a side-band `getState` operation is sent and the device is unlocked +- **THEN** the response SHALL include `{ "success": true, "result": { "lockState": "unlocked", "users": [...], "pinCodes": [...] } }` + +### Requirement: Initial lock state +The door lock virtual device SHALL start in the locked state by default. + +#### Scenario: Device starts locked +- **WHEN** the door lock virtual device is started +- **THEN** the initial `LockState` attribute SHALL be `Locked` +- **AND** a side-band `getState` SHALL return `lockState: "locked"` + +### Requirement: User and PIN code management via Matter +The door lock SHALL support basic user and PIN code management through the Matter DoorLock cluster, enabling tests to create users and set credentials. + +#### Scenario: Set user credential via Matter +- **WHEN** a `SetCredential` command is sent via Matter with a PIN code +- **THEN** the credential SHALL be stored on the device +- **AND** a subsequent `getState` side-band operation SHALL include the credential in the `pinCodes` array + +### Requirement: matter.js virtual thermostat device +A matter.js virtual thermostat device (`ThermostatDevice.js`) SHALL be created extending `VirtualDevice` with: +- Matter Thermostat device type (0x0301) on endpoint 1 +- Thermostat cluster (0x0201) with initial state: LocalTemperature=2100 (21.00°C), OccupiedHeatingSetpoint=2000, OccupiedCoolingSetpoint=2600, SystemMode=Off +- Side-band operations: `setTemperature`, `setSystemMode`, `getState` + +#### Scenario: Virtual thermostat starts with default state +- **WHEN** the virtual thermostat device is started +- **THEN** it SHALL be a Matter Thermostat device type (0x0301) with LocalTemperature=2100, OccupiedHeatingSetpoint=2000, OccupiedCoolingSetpoint=2600, SystemMode=Off + +#### Scenario: Side-band set temperature +- **WHEN** the `setTemperature` side-band operation is called with a temperature value +- **THEN** the device SHALL update its LocalTemperature attribute to the specified value and the change SHALL be reported to subscribed controllers + +#### Scenario: Side-band set system mode +- **WHEN** the `setSystemMode` side-band operation is called with a mode value (e.g., "heat") +- **THEN** the device SHALL update its SystemMode attribute accordingly + +#### Scenario: Side-band get state +- **WHEN** the `getState` side-band operation is called +- **THEN** it SHALL return the current thermostat state including localTemperature, occupiedHeatingSetpoint, occupiedCoolingSetpoint, and systemMode + +### Requirement: matter.js virtual thermostat with fan control device +A matter.js virtual thermostat with fan control device (`ThermostatWithFanDevice.js`) SHALL be created by subclassing `ThermostatDevice` to add: +- Fan Control cluster (0x0202) on the same thermostat endpoint, with initial FanMode=Auto +- Additional side-band operations: `setFanMode`, `getFanState` + +The subclass SHALL reuse the base thermostat endpoint setup (Thermostat cluster, initial state, core side-band operations) and only add the Fan Control cluster and fan-related side-band operations. + +#### Scenario: Virtual thermostat with fan starts with fan control cluster +- **WHEN** the virtual thermostat with fan device is started +- **THEN** it SHALL include both the Thermostat cluster (0x0201) and Fan Control cluster (0x0202) on the thermostat endpoint + +#### Scenario: Side-band set fan mode +- **WHEN** the `setFanMode` side-band operation is called with a fan mode value +- **THEN** the device SHALL update its FanMode attribute on the Fan Control cluster + +#### Scenario: Side-band get fan state +- **WHEN** the `getFanState` side-band operation is called +- **THEN** it SHALL return the current fan control state including fanMode + +### Requirement: Python test fixture for Matter thermostat +A Python test fixture (`matter_thermostat.py`) SHALL be created as a `MatterDevice` subclass with: +- `device_class` set to `"thermostat"` +- `matterjs_entry_point` set to `"ThermostatDevice.js"` +- A pytest fixture function `matter_thermostat` that creates, starts, yields, and cleans up the device + +#### Scenario: Fixture creates and starts thermostat +- **WHEN** the `matter_thermostat` pytest fixture is used in a test +- **THEN** it SHALL yield a started `MatterThermostat` instance ready for commissioning + +### Requirement: Python test fixture for Matter thermostat with fan control +A Python test fixture (`matter_thermostat_with_fan.py`) SHALL be created by subclassing `MatterThermostat` to avoid duplicating common setup: +- `matterjs_entry_point` set to `"ThermostatWithFanDevice.js"` +- A pytest fixture function `matter_thermostat_with_fan` that creates, starts, yields, and cleans up the device + +#### Scenario: Fixture creates and starts thermostat with fan +- **WHEN** the `matter_thermostat_with_fan` pytest fixture is used in a test +- **THEN** it SHALL yield a started `MatterThermostatWithFan` instance ready for commissioning, with Fan Control cluster support + +### Requirement: Commission test +An integration test SHALL verify that a Matter thermostat can be commissioned and appears as a `thermostat` device class with the expected common resources. + +#### Scenario: Commission thermostat +- **WHEN** a virtual Matter thermostat is commissioned via Barton +- **THEN** the device SHALL appear with deviceClass `thermostat` and have common resources (firmwareVersionString, macAddress, networkType, serialNumber) + +### Requirement: Setpoint read/write test +Integration tests SHALL verify that heating and cooling setpoints can be read after commissioning and written via the Barton resource API. + +#### Scenario: Write heating setpoint via Barton +- **WHEN** a client writes a new value to the `heatSetpoint` resource +- **THEN** the virtual thermostat device SHALL reflect the new heating setpoint value via side-band query + +#### Scenario: Write cooling setpoint via Barton +- **WHEN** a client writes a new value to the `coolSetpoint` resource +- **THEN** the virtual thermostat device SHALL reflect the new cooling setpoint value via side-band query + +### Requirement: System mode read/write test +An integration test SHALL verify that the system mode can be read and changed via the Barton resource API. + +#### Scenario: Write system mode via Barton +- **WHEN** a client writes `"heat"` to the `systemMode` resource +- **THEN** the virtual thermostat device SHALL report its system mode as "heat" via side-band query + +### Requirement: Side-band temperature change triggers Barton update +An integration test SHALL verify that a temperature change initiated via the side-band interface triggers a resource update in Barton. + +#### Scenario: Side-band temperature change +- **WHEN** the virtual thermostat's local temperature is changed via the side-band interface +- **THEN** Barton SHALL receive a resource update for the `localTemperature` resource with the new value + +### Requirement: Base thermostat tests verify no fan resources +The base `thermostat_test.py` tests SHALL verify that `fanMode` and `fanOn` resources are NOT present on a thermostat without the Fan Control cluster, confirming the prerequisite gate works correctly. + +#### Scenario: No fan resources on base thermostat +- **WHEN** a virtual Matter thermostat without Fan Control cluster is commissioned +- **THEN** the `fanMode` and `fanOn` resources SHALL NOT be present on the thermostat endpoint + +### Requirement: Commission thermostat with fan control test +An integration test in `thermostat_with_fan_test.py` SHALL verify that a Matter thermostat with Fan Control cluster can be commissioned and includes both base thermostat resources and fan control resources (`fanMode`, `fanOn`). + +#### Scenario: Commission thermostat with fan control +- **WHEN** a virtual Matter thermostat with Fan Control cluster is commissioned via Barton +- **THEN** the device SHALL appear with deviceClass `thermostat` and have fan resources (`fanMode`, `fanOn`) in addition to all base thermostat resources + +### Requirement: Fan mode read test +An integration test SHALL verify that the `fanMode` resource can be read on a thermostat with Fan Control cluster support. + +#### Scenario: Read fan mode +- **WHEN** a thermostat with Fan Control cluster is commissioned +- **THEN** the `fanMode` resource SHALL be readable with an initial value of `"auto"` + +### Requirement: Fan on read test +An integration test SHALL verify that the `fanOn` resource can be read on a thermostat with Fan Control cluster support. + +#### Scenario: Read fan on +- **WHEN** a thermostat with Fan Control cluster is commissioned +- **THEN** the `fanOn` resource SHALL be readable with an initial value of `"false"` + +### Requirement: Sideband client class +A Python `SidebandClient` class SHALL provide a simple interface for sending side-band operations to matter.js virtual devices over HTTP. The client SHALL be initialized with the device's side-band host and port. + +#### Scenario: Client sends an operation +- **WHEN** `client.send("lock")` is called +- **THEN** the client SHALL send a POST request to `http://:/sideband` with body `{ "operation": "lock" }` +- **AND** the response SHALL be parsed as JSON and returned + +#### Scenario: Client sends an operation with parameters +- **WHEN** `client.send("setPin", {"pin": "1234", "userId": 1})` is called +- **THEN** the client SHALL send `{ "operation": "setPin", "params": {"pin": "1234", "userId": 1} }` +- **AND** the response SHALL be parsed as JSON and returned + +### Requirement: Convenience methods +The `SidebandClient` SHALL provide a `get_state()` convenience method that calls the `getState` side-band operation and returns the parsed result. + +#### Scenario: Get state returns device state +- **WHEN** `client.get_state()` is called +- **THEN** it SHALL send a `getState` operation and return the `result` field from the response + +### Requirement: Error handling +The `SidebandClient` SHALL raise a clear exception when a side-band operation fails or the device is unreachable. + +#### Scenario: Device unreachable +- **WHEN** a side-band operation is sent and the device process is not running +- **THEN** the client SHALL raise a `ConnectionError` (or subclass) with a descriptive message + +#### Scenario: Operation returns failure +- **WHEN** a side-band operation returns `{ "success": false, "error": "..." }` +- **THEN** the client SHALL raise a `SidebandOperationError` with the error message + +### Requirement: Timeout support +The `SidebandClient` SHALL support configurable timeouts for side-band operations, defaulting to a reasonable value (e.g., 5 seconds). + +#### Scenario: Operation times out +- **WHEN** a side-band operation does not receive a response within the timeout period +- **THEN** the client SHALL raise a timeout exception diff --git a/openspec/specs/matter-thermostat-testing/spec.md b/openspec/specs/matter-thermostat-testing/spec.md deleted file mode 100644 index 65edf17f..00000000 --- a/openspec/specs/matter-thermostat-testing/spec.md +++ /dev/null @@ -1,127 +0,0 @@ -# Matter Thermostat Testing - -## Purpose - -Specifies the matter.js virtual thermostat devices (with and without fan control) and the Python integration tests covering commissioning, setpoint and system-mode read/write, side-band temperature changes, and fan resources. - -## Requirements - -### Requirement: matter.js virtual thermostat device -A matter.js virtual thermostat device (`ThermostatDevice.js`) SHALL be created extending `VirtualDevice` with: -- Matter Thermostat device type (0x0301) on endpoint 1 -- Thermostat cluster (0x0201) with initial state: LocalTemperature=2100 (21.00°C), OccupiedHeatingSetpoint=2000, OccupiedCoolingSetpoint=2600, SystemMode=Off -- Side-band operations: `setTemperature`, `setSystemMode`, `getState` - -#### Scenario: Virtual thermostat starts with default state -- **WHEN** the virtual thermostat device is started -- **THEN** it SHALL be a Matter Thermostat device type (0x0301) with LocalTemperature=2100, OccupiedHeatingSetpoint=2000, OccupiedCoolingSetpoint=2600, SystemMode=Off - -#### Scenario: Side-band set temperature -- **WHEN** the `setTemperature` side-band operation is called with a temperature value -- **THEN** the device SHALL update its LocalTemperature attribute to the specified value and the change SHALL be reported to subscribed controllers - -#### Scenario: Side-band set system mode -- **WHEN** the `setSystemMode` side-band operation is called with a mode value (e.g., "heat") -- **THEN** the device SHALL update its SystemMode attribute accordingly - -#### Scenario: Side-band get state -- **WHEN** the `getState` side-band operation is called -- **THEN** it SHALL return the current thermostat state including localTemperature, occupiedHeatingSetpoint, occupiedCoolingSetpoint, and systemMode - -### Requirement: matter.js virtual thermostat with fan control device -A matter.js virtual thermostat with fan control device (`ThermostatWithFanDevice.js`) SHALL be created by subclassing `ThermostatDevice` to add: -- Fan Control cluster (0x0202) on the same thermostat endpoint, with initial FanMode=Auto -- Additional side-band operations: `setFanMode`, `getFanState` - -The subclass SHALL reuse the base thermostat endpoint setup (Thermostat cluster, initial state, core side-band operations) and only add the Fan Control cluster and fan-related side-band operations. - -#### Scenario: Virtual thermostat with fan starts with fan control cluster -- **WHEN** the virtual thermostat with fan device is started -- **THEN** it SHALL include both the Thermostat cluster (0x0201) and Fan Control cluster (0x0202) on the thermostat endpoint - -#### Scenario: Side-band set fan mode -- **WHEN** the `setFanMode` side-band operation is called with a fan mode value -- **THEN** the device SHALL update its FanMode attribute on the Fan Control cluster - -#### Scenario: Side-band get fan state -- **WHEN** the `getFanState` side-band operation is called -- **THEN** it SHALL return the current fan control state including fanMode - -### Requirement: Python test fixture for Matter thermostat -A Python test fixture (`matter_thermostat.py`) SHALL be created as a `MatterDevice` subclass with: -- `device_class` set to `"thermostat"` -- `matterjs_entry_point` set to `"ThermostatDevice.js"` -- A pytest fixture function `matter_thermostat` that creates, starts, yields, and cleans up the device - -#### Scenario: Fixture creates and starts thermostat -- **WHEN** the `matter_thermostat` pytest fixture is used in a test -- **THEN** it SHALL yield a started `MatterThermostat` instance ready for commissioning - -### Requirement: Python test fixture for Matter thermostat with fan control -A Python test fixture (`matter_thermostat_with_fan.py`) SHALL be created by subclassing `MatterThermostat` to avoid duplicating common setup: -- `matterjs_entry_point` set to `"ThermostatWithFanDevice.js"` -- A pytest fixture function `matter_thermostat_with_fan` that creates, starts, yields, and cleans up the device - -#### Scenario: Fixture creates and starts thermostat with fan -- **WHEN** the `matter_thermostat_with_fan` pytest fixture is used in a test -- **THEN** it SHALL yield a started `MatterThermostatWithFan` instance ready for commissioning, with Fan Control cluster support - -### Requirement: Commission test -An integration test SHALL verify that a Matter thermostat can be commissioned and appears as a `thermostat` device class with the expected common resources. - -#### Scenario: Commission thermostat -- **WHEN** a virtual Matter thermostat is commissioned via Barton -- **THEN** the device SHALL appear with deviceClass `thermostat` and have common resources (firmwareVersionString, macAddress, networkType, serialNumber) - -### Requirement: Setpoint read/write test -Integration tests SHALL verify that heating and cooling setpoints can be read after commissioning and written via the Barton resource API. - -#### Scenario: Write heating setpoint via Barton -- **WHEN** a client writes a new value to the `heatSetpoint` resource -- **THEN** the virtual thermostat device SHALL reflect the new heating setpoint value via side-band query - -#### Scenario: Write cooling setpoint via Barton -- **WHEN** a client writes a new value to the `coolSetpoint` resource -- **THEN** the virtual thermostat device SHALL reflect the new cooling setpoint value via side-band query - -### Requirement: System mode read/write test -An integration test SHALL verify that the system mode can be read and changed via the Barton resource API. - -#### Scenario: Write system mode via Barton -- **WHEN** a client writes `"heat"` to the `systemMode` resource -- **THEN** the virtual thermostat device SHALL report its system mode as "heat" via side-band query - -### Requirement: Side-band temperature change triggers Barton update -An integration test SHALL verify that a temperature change initiated via the side-band interface triggers a resource update in Barton. - -#### Scenario: Side-band temperature change -- **WHEN** the virtual thermostat's local temperature is changed via the side-band interface -- **THEN** Barton SHALL receive a resource update for the `localTemperature` resource with the new value - -### Requirement: Base thermostat tests verify no fan resources -The base `thermostat_test.py` tests SHALL verify that `fanMode` and `fanOn` resources are NOT present on a thermostat without the Fan Control cluster, confirming the prerequisite gate works correctly. - -#### Scenario: No fan resources on base thermostat -- **WHEN** a virtual Matter thermostat without Fan Control cluster is commissioned -- **THEN** the `fanMode` and `fanOn` resources SHALL NOT be present on the thermostat endpoint - -### Requirement: Commission thermostat with fan control test -An integration test in `thermostat_with_fan_test.py` SHALL verify that a Matter thermostat with Fan Control cluster can be commissioned and includes both base thermostat resources and fan control resources (`fanMode`, `fanOn`). - -#### Scenario: Commission thermostat with fan control -- **WHEN** a virtual Matter thermostat with Fan Control cluster is commissioned via Barton -- **THEN** the device SHALL appear with deviceClass `thermostat` and have fan resources (`fanMode`, `fanOn`) in addition to all base thermostat resources - -### Requirement: Fan mode read test -An integration test SHALL verify that the `fanMode` resource can be read on a thermostat with Fan Control cluster support. - -#### Scenario: Read fan mode -- **WHEN** a thermostat with Fan Control cluster is commissioned -- **THEN** the `fanMode` resource SHALL be readable with an initial value of `"auto"` - -### Requirement: Fan on read test -An integration test SHALL verify that the `fanOn` resource can be read on a thermostat with Fan Control cluster support. - -#### Scenario: Read fan on -- **WHEN** a thermostat with Fan Control cluster is commissioned -- **THEN** the `fanOn` resource SHALL be readable with an initial value of `"false"` diff --git a/openspec/specs/matterjs-door-lock-device/spec.md b/openspec/specs/matterjs-door-lock-device/spec.md deleted file mode 100644 index 98c994db..00000000 --- a/openspec/specs/matterjs-door-lock-device/spec.md +++ /dev/null @@ -1,99 +0,0 @@ -# matter.js Door Lock Device - -## Purpose - -Specifies the matter.js virtual door lock: its device type, Matter lock/unlock commands, side-band lock/unlock/getState operations, `LockOperation` event emission, initial lock state, and user and PIN-code management. - -## Requirements - -### Requirement: Door lock device type -The matter.js door lock virtual device SHALL present itself as a Matter Door Lock device type (Device Type ID `0x000A`) with a DoorLock cluster (Cluster ID `0x0101`) on endpoint 1. - -#### Scenario: Device advertises as door lock -- **WHEN** the door lock virtual device is started and commissioned -- **THEN** it SHALL be discoverable as a Matter Door Lock device type -- **AND** the DoorLock cluster SHALL be available on endpoint 1 - -### Requirement: Matter lock and unlock commands -The door lock virtual device SHALL respond to standard Matter DoorLock cluster `LockDoor` and `UnlockDoor` commands, updating its internal lock state accordingly. - -#### Scenario: Lock door via Matter command -- **WHEN** a Matter `LockDoor` command is sent to the device -- **THEN** the device's lock state SHALL change to locked -- **AND** the device SHALL report the updated `LockState` attribute as `Locked` (value 1) - -#### Scenario: Unlock door via Matter command -- **WHEN** a Matter `UnlockDoor` command is sent to the device -- **THEN** the device's lock state SHALL change to unlocked -- **AND** the device SHALL report the updated `LockState` attribute as `Unlocked` (value 2) - -### Requirement: Side-band lock operation -The door lock SHALL register a side-band operation `lock` that simulates a user manually locking the device (e.g., turning the thumb-turn). This operation SHALL update the Matter cluster state, triggering standard Matter notifications. - -#### Scenario: Side-band lock triggers Matter state change -- **WHEN** a side-band `lock` operation is sent to the device -- **THEN** the device's `LockState` attribute SHALL change to `Locked` -- **AND** connected Matter controllers SHALL receive a state change notification -- **AND** the side-band response SHALL be `{ "success": true, "result": { "lockState": "locked" } }` - -### Requirement: Side-band unlock operation -The door lock SHALL register a side-band operation `unlock` that simulates a user manually unlocking the device. This operation SHALL update the Matter cluster state, triggering standard Matter notifications. - -#### Scenario: Side-band unlock triggers Matter state change -- **WHEN** a side-band `unlock` operation is sent to the device -- **THEN** the device's `LockState` attribute SHALL change to `Unlocked` -- **AND** connected Matter controllers SHALL receive a state change notification -- **AND** the side-band response SHALL be `{ "success": true, "result": { "lockState": "unlocked" } }` - -### Requirement: Matter lock and unlock commands emit LockOperation events -The door lock virtual device SHALL emit a `LockOperation` event (DoorLock cluster `0x0101`, event `0x0002`) whenever the lock state changes via a Matter `LockDoor` or `UnlockDoor` command, in addition to updating the `LockState` attribute. The `LockOperationType` field SHALL be `0` (Lock) for `LockDoor` and `1` (Unlock) for `UnlockDoor`. This is required so that Matter-command-driven lock/unlock operations are reflected in event-driven SBMD resources. - -#### Scenario: LockDoor command emits LockOperation event -- **WHEN** a Matter `LockDoor` command is sent to the device -- **THEN** the device SHALL emit a `LockOperation` event with `LockOperationType` = 0 (Lock) -- **AND** the `LockState` attribute SHALL be updated to `Locked` - -#### Scenario: UnlockDoor command emits LockOperation event -- **WHEN** a Matter `UnlockDoor` command is sent to the device -- **THEN** the device SHALL emit a `LockOperation` event with `LockOperationType` = 1 (Unlock) -- **AND** the `LockState` attribute SHALL be updated to `Unlocked` - -### Requirement: Side-band operations emit LockOperation events -The door lock virtual device SHALL emit a `LockOperation` event (DoorLock cluster `0x0101`, event `0x0002`) whenever the lock state changes via a side-band operation, in addition to updating the `LockState` attribute. The `LockOperationType` field in the event SHALL be `0` (Lock) for side-band lock operations and `1` (Unlock) for side-band unlock operations. This is required for SBMD drivers that use event-driven resource updates rather than attribute subscription, such as the `door-lock.sbmd` spec with `mapper.event` on the `locked` resource. - -#### Scenario: Side-band lock emits LockOperation event -- **WHEN** a side-band `lock` operation is sent to the virtual door lock -- **THEN** the device SHALL emit a `LockOperation` event with `LockOperationType` = 0 (Lock) -- **AND** the `LockState` attribute SHALL be updated to `Locked` - -#### Scenario: Side-band unlock emits LockOperation event -- **WHEN** a side-band `unlock` operation is sent to the virtual door lock -- **THEN** the device SHALL emit a `LockOperation` event with `LockOperationType` = 1 (Unlock) -- **AND** the `LockState` attribute SHALL be updated to `Unlocked` - -### Requirement: Side-band getState operation -The door lock SHALL register a side-band operation `getState` that returns the current state of the device, including lock state and any configured users/pin codes. - -#### Scenario: Get state of locked device -- **WHEN** a side-band `getState` operation is sent and the device is locked -- **THEN** the response SHALL include `{ "success": true, "result": { "lockState": "locked", "users": [...], "pinCodes": [...] } }` - -#### Scenario: Get state of unlocked device -- **WHEN** a side-band `getState` operation is sent and the device is unlocked -- **THEN** the response SHALL include `{ "success": true, "result": { "lockState": "unlocked", "users": [...], "pinCodes": [...] } }` - -### Requirement: Initial lock state -The door lock virtual device SHALL start in the locked state by default. - -#### Scenario: Device starts locked -- **WHEN** the door lock virtual device is started -- **THEN** the initial `LockState` attribute SHALL be `Locked` -- **AND** a side-band `getState` SHALL return `lockState: "locked"` - -### Requirement: User and PIN code management via Matter -The door lock SHALL support basic user and PIN code management through the Matter DoorLock cluster, enabling tests to create users and set credentials. - -#### Scenario: Set user credential via Matter -- **WHEN** a `SetCredential` command is sent via Matter with a PIN code -- **THEN** the credential SHALL be stored on the device -- **AND** a subsequent `getState` side-band operation SHALL include the credential in the `pinCodes` array diff --git a/openspec/specs/matterjs-virtual-device-framework/spec.md b/openspec/specs/matterjs-virtual-device-framework/spec.md deleted file mode 100644 index 31c832f1..00000000 --- a/openspec/specs/matterjs-virtual-device-framework/spec.md +++ /dev/null @@ -1,69 +0,0 @@ -# matter.js Virtual Device Framework - -## Purpose - -Specifies the matter.js virtual device framework used for integration testing: the base class, the side-band HTTP server and operation registration, the stdout ready signal, graceful shutdown, and package configuration. - -## Requirements - -### Requirement: Virtual device base class initialization -The matter.js virtual device base class (`VirtualDevice`) SHALL initialize a Matter `ServerNode` with configurable vendor ID, product ID, device name, passcode, discriminator, and port. The class SHALL handle all common Matter device setup so that subclasses only need to define their device type and side-band operations. - -#### Scenario: Base class creates a Matter server node -- **WHEN** a `VirtualDevice` subclass is instantiated with device type, vendor ID, product ID, passcode, discriminator, and port parameters -- **THEN** a Matter `ServerNode` SHALL be created with those parameters and the device SHALL be ready to accept commissioning - -#### Scenario: Default values for optional parameters -- **WHEN** a `VirtualDevice` subclass is instantiated without specifying vendor ID, product ID, or port -- **THEN** the base class SHALL use sensible defaults (vendor ID `0xFFF1`, product ID `0x8000`, port `0` for dynamic assignment) - -### Requirement: Side-band HTTP server -The `VirtualDevice` base class SHALL start an HTTP server on a dynamically assigned port that accepts side-band operation requests from test drivers. The server SHALL listen on `127.0.0.1` and accept JSON-encoded POST requests. - -#### Scenario: Side-band server starts on dynamic port -- **WHEN** the virtual device starts -- **THEN** an HTTP server SHALL start on a dynamically assigned port (port 0) -- **AND** the actual assigned port SHALL be reported via stdout in a parseable format - -#### Scenario: Side-band server accepts JSON POST requests -- **WHEN** a POST request is sent to `/sideband` with a JSON body containing an `operation` field -- **THEN** the server SHALL dispatch to the registered handler for that operation -- **AND** the response SHALL be JSON with `success` (boolean) and `result` (object) fields - -#### Scenario: Side-band server rejects unknown operations -- **WHEN** a POST request is sent with an `operation` value that has no registered handler -- **THEN** the server SHALL respond with HTTP 400 and `{ "success": false, "error": "Unknown operation: " }` - -### Requirement: Side-band operation registration -The `VirtualDevice` base class SHALL provide a `registerOperation(name, handler)` method that subclasses use to register their device-specific side-band operations. Each handler SHALL be an async function that receives the request payload and returns a result object. - -#### Scenario: Subclass registers a custom operation -- **WHEN** a subclass calls `registerOperation("myOp", handler)` during construction -- **THEN** POST requests with `{ "operation": "myOp" }` SHALL be dispatched to that handler - -#### Scenario: Subclass registers multiple operations -- **WHEN** a subclass registers operations "opA" and "opB" -- **THEN** both operations SHALL be independently dispatchable via the side-band HTTP interface - -### Requirement: Ready signal on stdout -The virtual device process SHALL emit a JSON ready signal on stdout when both the Matter server node and the side-band HTTP server are fully initialized and operational. - -#### Scenario: Ready signal includes connection details -- **WHEN** the virtual device has completed initialization -- **THEN** it SHALL print a single JSON line to stdout containing `{ "ready": true, "sidebandPort": , "matterPort": , "passcode": , "discriminator": }` - -### Requirement: Graceful shutdown -The virtual device SHALL handle SIGTERM and SIGINT signals by cleanly shutting down the Matter server node and the side-band HTTP server. - -#### Scenario: Process terminated with SIGTERM -- **WHEN** the virtual device process receives SIGTERM -- **THEN** the Matter server node SHALL be closed -- **AND** the side-band HTTP server SHALL be closed -- **AND** the process SHALL exit with code 0 - -### Requirement: Package configuration -A `package.json` SHALL exist in the virtual device source directory that declares matter.js v0.16.10 as a dependency and configures the project as an ES module. - -#### Scenario: Package dependencies are correct -- **WHEN** `npm install` is run in the virtual device source directory -- **THEN** matter.js v0.16.10 SHALL be installed along with any required peer dependencies diff --git a/openspec/specs/python-sideband-client/spec.md b/openspec/specs/python-sideband-client/spec.md deleted file mode 100644 index 0394455c..00000000 --- a/openspec/specs/python-sideband-client/spec.md +++ /dev/null @@ -1,45 +0,0 @@ -# Python Side-band Client - -## Purpose - -Specifies the Python side-band client used by integration tests to drive virtual devices: the client class, convenience methods, error handling, and timeout support. - -## Requirements - -### Requirement: Sideband client class -A Python `SidebandClient` class SHALL provide a simple interface for sending side-band operations to matter.js virtual devices over HTTP. The client SHALL be initialized with the device's side-band host and port. - -#### Scenario: Client sends an operation -- **WHEN** `client.send("lock")` is called -- **THEN** the client SHALL send a POST request to `http://:/sideband` with body `{ "operation": "lock" }` -- **AND** the response SHALL be parsed as JSON and returned - -#### Scenario: Client sends an operation with parameters -- **WHEN** `client.send("setPin", {"pin": "1234", "userId": 1})` is called -- **THEN** the client SHALL send `{ "operation": "setPin", "params": {"pin": "1234", "userId": 1} }` -- **AND** the response SHALL be parsed as JSON and returned - -### Requirement: Convenience methods -The `SidebandClient` SHALL provide a `get_state()` convenience method that calls the `getState` side-band operation and returns the parsed result. - -#### Scenario: Get state returns device state -- **WHEN** `client.get_state()` is called -- **THEN** it SHALL send a `getState` operation and return the `result` field from the response - -### Requirement: Error handling -The `SidebandClient` SHALL raise a clear exception when a side-band operation fails or the device is unreachable. - -#### Scenario: Device unreachable -- **WHEN** a side-band operation is sent and the device process is not running -- **THEN** the client SHALL raise a `ConnectionError` (or subclass) with a descriptive message - -#### Scenario: Operation returns failure -- **WHEN** a side-band operation returns `{ "success": false, "error": "..." }` -- **THEN** the client SHALL raise a `SidebandOperationError` with the error message - -### Requirement: Timeout support -The `SidebandClient` SHALL support configurable timeouts for side-band operations, defaulting to a reasonable value (e.g., 5 seconds). - -#### Scenario: Operation times out -- **WHEN** a side-band operation does not receive a response within the timeout period -- **THEN** the client SHALL raise a timeout exception diff --git a/openspec/specs/release-workflow/spec.md b/openspec/specs/release-process/spec.md similarity index 52% rename from openspec/specs/release-workflow/spec.md rename to openspec/specs/release-process/spec.md index 38db122f..96d3a6c6 100644 --- a/openspec/specs/release-workflow/spec.md +++ b/openspec/specs/release-process/spec.md @@ -1,11 +1,33 @@ -# Release Workflow +# Release Process ## Purpose -Specifies release safety and mechanics: the branch guard that prevents non-main releases, annotated tagging, and pushing the version commit to main. +Specifies the project's release process: changelog generation across releases and the release-workflow safeguards (branch guard against non-main releases, annotated tags, and pushing the version commit to main). ## Requirements +### Requirement: Changelog entries are prepended across releases +The release workflow SHALL produce a `CHANGELOG.md` where each new release entry is prepended +above previous entries, preserving the full release history in the committed file. + +#### Scenario: Second release after initial +- **WHEN** a release is performed and `CHANGELOG.md` already contains entries from prior releases +- **THEN** the new version's entry SHALL appear above the existing entries +- **THEN** all prior entries SHALL remain intact + +### Requirement: Full historical changelog exists +The repository SHALL contain changelog entries for all semver releases from 1.0.0 through the +current version, generated from conventional commit history. + +#### Scenario: Historical entries present +- **WHEN** a user views `CHANGELOG.md` +- **THEN** there SHALL be a section for each semver tag (1.0.0, 1.1.0, 2.0.0, 2.1.0, 2.2.0, 2.3.0, 3.0.0, 3.1.0, 3.1.1, 4.0.0) +- **THEN** entries from 2.0.0 onward SHALL contain conventional commit details (type, scope, summary, hash, author) + +#### Scenario: Pre-conventional-commits releases +- **WHEN** a user views changelog entries for versions prior to 2.0.0 (1.0.0, 1.1.0) +- **THEN** those entries SHALL at minimum contain the version header and date + ### Requirement: Branch guard prevents non-main releases The release workflow SHALL refuse to run when triggered on any branch other than `main`. diff --git a/openspec/specs/vendor-product-claiming/spec.md b/openspec/specs/sbmd-claiming/spec.md similarity index 93% rename from openspec/specs/vendor-product-claiming/spec.md rename to openspec/specs/sbmd-claiming/spec.md index 3d71177a..689b1aaa 100644 --- a/openspec/specs/vendor-product-claiming/spec.md +++ b/openspec/specs/sbmd-claiming/spec.md @@ -1,8 +1,8 @@ -# Vendor and Product Claiming +# SBMD Vendor/Product Claiming ## Purpose -Specifies vendor and product ID claiming for SBMD drivers: declaring vendor/product IDs in specs, `DeviceDataCache` accessors, claim semantics, and vendor-specific driver priority. +Specifies how SBMD drivers claim devices by vendor and product ID: declaring vendor/product IDs in a driver spec, the `DeviceDataCache` accessors, claim semantics, and vendor-specific driver priority over generic drivers. ## Requirements diff --git a/openspec/specs/device-type-endpoint-resolution/spec.md b/openspec/specs/sbmd-endpoint-resolution/spec.md similarity index 63% rename from openspec/specs/device-type-endpoint-resolution/spec.md rename to openspec/specs/sbmd-endpoint-resolution/spec.md index 82de725f..3830b599 100644 --- a/openspec/specs/device-type-endpoint-resolution/spec.md +++ b/openspec/specs/sbmd-endpoint-resolution/spec.md @@ -1,8 +1,8 @@ -# Device Type Endpoint Resolution +# SBMD Endpoint Resolution ## Purpose -Specifies how SBMD device drivers resolve Matter endpoints by device-type matching, building an endpoint map during device initialization and binding resources to the Nth matching endpoint. +Specifies how SBMD drivers resolve which Matter endpoint backs each resource: device-type matching to build an endpoint map during initialization, and cluster-based fallback when device-type matching alone is insufficient. ## Requirements @@ -76,3 +76,37 @@ The system SHALL fail device initialization if no Matter endpoint matches any de #### Scenario: At least one match found - **WHEN** at least one Matter endpoint has a matching device type - **THEN** `ResolveEndpointMap()` SHALL return true and device initialization SHALL proceed + +### Requirement: Endpoint resolution with cluster-based fallback +`MatterDevice` SHALL provide a `ResolveEndpointForCluster` method that resolves a Matter endpoint for a given cluster ID. When an SBMD endpoint index is provided, it SHALL first try the SBMD-mapped endpoint. If that endpoint does not host the required cluster (verified via `DeviceDataCache::EndpointHasServerCluster`), it SHALL fall back to cluster-based lookup via `GetEndpointForCluster`. + +#### Scenario: Mapped endpoint hosts the cluster +- **WHEN** `ResolveEndpointForCluster` is called with cluster `0x0402` and SBMD endpoint index 0, and the mapped Matter endpoint hosts cluster `0x0402` +- **THEN** the method SHALL return the mapped endpoint ID + +#### Scenario: Mapped endpoint does not host the cluster — fallback +- **WHEN** `ResolveEndpointForCluster` is called with cluster `0x0405` and SBMD endpoint index 0, and the mapped Matter endpoint does NOT host cluster `0x0405` but another endpoint does +- **THEN** the method SHALL fall back to `GetEndpointForCluster` and return the endpoint that hosts `0x0405` + +#### Scenario: No SBMD endpoint index provided +- **WHEN** `ResolveEndpointForCluster` is called without an SBMD endpoint index (nullopt) +- **THEN** the method SHALL use `GetEndpointForCluster` directly + +#### Scenario: No endpoint hosts the cluster +- **WHEN** `ResolveEndpointForCluster` is called with a cluster ID that no endpoint hosts +- **THEN** the method SHALL return false + +#### Scenario: Cache data not yet available +- **WHEN** `ResolveEndpointForCluster` is called and `DeviceDataCache` is null or cannot verify cluster presence via Descriptor +- **THEN** the method SHALL use the SBMD-mapped endpoint directly without fallback + +### Requirement: All resource and event binding uses ResolveEndpointForCluster +All SBMD resource binding methods (`BindResourceReadInfo`, `BindWriteInfo`, `BindExecuteInfo`, `BindResourceEventInfo`) SHALL use `ResolveEndpointForCluster` instead of the previous inline if/else pattern for endpoint resolution. + +#### Scenario: Read binding on composite device +- **WHEN** `BindResourceReadInfo` is called for a resource whose cluster is on a different Matter endpoint than the SBMD-mapped one +- **THEN** the binding SHALL resolve to the correct endpoint via cluster-based fallback + +#### Scenario: Event binding on composite device +- **WHEN** `BindResourceEventInfo` is called for an event whose cluster is on a different Matter endpoint than the SBMD-mapped one +- **THEN** the binding SHALL resolve to the correct endpoint via cluster-based fallback diff --git a/openspec/specs/sbmd-resource-prerequisites/spec.md b/openspec/specs/sbmd-mappers/spec.md similarity index 53% rename from openspec/specs/sbmd-resource-prerequisites/spec.md rename to openspec/specs/sbmd-mappers/spec.md index 0667a2f0..db4b4242 100644 --- a/openspec/specs/sbmd-resource-prerequisites/spec.md +++ b/openspec/specs/sbmd-mappers/spec.md @@ -1,8 +1,8 @@ -# SBMD Resource Prerequisites +# SBMD Mappers ## Purpose -Specifies prerequisite gating for SBMD resources: alias definitions in `matterMeta`, prerequisite declarations on resources, alias-based mapper metadata, parser enforcement, and commissioning-time evaluation that leaves unsatisfied resources unregistered. +Specifies SBMD resource-mapper features: prerequisite gating of resources via cluster/attribute aliases, and the `seedFrom` mapper that seeds an event-backed resource from a Matter attribute at configure and synchronize time. ## Requirements @@ -126,3 +126,95 @@ Resources skipped due to unmet prerequisites SHALL be excluded from both the map #### Scenario: Prerequisite-skipped resource excluded from mapper bindings - **WHEN** a resource is skipped due to unmet prerequisites - **THEN** no mapper binding SHALL be established for it in the script engine + +### Requirement: seedFrom mapper — schema and structure +A resource's mapper MAY contain a `seedFrom` section with an `alias` (a string naming an **attribute** alias defined in `matterMeta.aliases`) and a `script` (JavaScript string). The `seedFrom` mapper SHALL only appear when the same resource also declares a `mapper.event` section. The `alias` SHALL resolve to an attribute alias; event aliases SHALL NOT be accepted. The `script` SHALL be required; absence of `script` SHALL be a parse error. + +#### Scenario: Valid seedFrom mapper +- **WHEN** a mapper declares `seedFrom.alias: lockState` and `lockState` is an attribute alias, and `seedFrom.script` is present, and `mapper.event` is also present +- **THEN** the parser SHALL populate `seedFromAttribute` with the resolved cluster/attribute/type and `seedFromScript` with the script text + +#### Scenario: seedFrom with event alias rejected +- **WHEN** a mapper declares `seedFrom.alias: lockOperation` and `lockOperation` is an event alias (not an attribute alias) +- **THEN** the parser SHALL reject the spec with an error + +#### Scenario: seedFrom without script rejected +- **WHEN** a mapper declares `seedFrom.alias: lockState` but omits `seedFrom.script` +- **THEN** the parser SHALL reject the spec with an error + +### Requirement: seedFrom mutual exclusion with read +A resource mapper SHALL NOT declare both `read` and `seedFrom`. The presence of both SHALL be a parse-time error. + +#### Scenario: seedFrom + read on same resource rejected +- **WHEN** a mapper declares both `read` and `seedFrom` +- **THEN** the parser SHALL reject the spec with an error + +### Requirement: seedFrom requires event +A `seedFrom` mapper SHALL NOT appear on a resource without a `mapper.event` section. Absence of `mapper.event` when `mapper.seedFrom` is present SHALL be a parse-time error. + +#### Scenario: seedFrom without event rejected +- **WHEN** a mapper declares `seedFrom` but does not declare `event` +- **THEN** the parser SHALL reject the spec with an error + +### Requirement: seedFrom attribute not subscribed for live updates +Attributes referenced by `mapper.seedFrom` SHALL NOT be registered in the device's live attribute subscription callback table. `seedFrom` attribute reads are one-shot operations that occur only at configure and synchronize time; they SHALL NOT update the resource in response to live attribute subscription callbacks. + +#### Scenario: seedFrom attribute does not update resource on subscription +- **WHEN** a resource declares only `event` and `seedFrom` (no `read`) +- **THEN** the driver SHALL NOT register the seedFrom attribute in `readableAttributeLookup` +- **THEN** a live attribute subscription update for the seedFrom attribute SHALL NOT update the resource value + +### Requirement: seedFrom seeds resource at configure time +When a device is configured (during commissioning), the driver SHALL compute each resource's `seedFrom` value from the device data cache during resource registration (`DoRegisterResources`) and pass it as the initial value to `createDeviceResource()`/`createEndpointResource()`. This SHALL occur on the Matter thread (which is where `DoRegisterResources` executes) and before the device is persisted or `DEVICE_ADDED` is emitted, so the resource SHALL have the correct initial value when `DEVICE_ADDED` fires. Resources in `skippedOptionalResources` SHALL be skipped. If the attribute is absent from the cache, the resource SHALL be created with a null initial value. + +#### Scenario: Locked resource seeded at commissioning +- **WHEN** a door lock device is commissioned and `LockState` (cluster `0x0101`, attribute `0x0000`) is present in the attribute cache +- **THEN** the `locked` resource SHALL have a non-null value when `DEVICE_ADDED` fires — no intermediate null state SHALL be visible to clients + +#### Scenario: Optional resource not seeded when skipped +- **WHEN** a resource is marked `optional: true` and was skipped due to absent cluster +- **THEN** `DoRegisterResources()` SHALL NOT attempt to read its seedFrom attribute + +#### Scenario: Cache miss — resource created with null value +- **WHEN** the seedFrom attribute is absent from the device data cache at configure time +- **THEN** `createDeviceResource()` SHALL be called with a null initial value for that resource + +### Requirement: seedFrom seeds resource at synchronize time +When a device is synchronized (at Barton restart or communication restoration), the driver SHALL re-read each resource's `seedFrom` attribute from the device data cache and update the resource value via `updateResource()`. This SHALL use the same `SeedInitialResourceValues()` helper as configure time and apply the same guards (skipped resources, cache miss). + +#### Scenario: Locked resource re-seeded at device synchronization +- **WHEN** `SynchronizeDevice()` is called for a commissioned door lock (e.g., after a comm-fail restore or Barton restart) +- **THEN** the `locked` resource SHALL be re-seeded from the `LockState` attribute cache before any event fires + +### Requirement: seedFrom script interface +The JavaScript script in `mapper.seedFrom` SHALL receive attribute value data via the same `sbmdReadArgs` interface used by `mapper.read` scripts: `sbmdReadArgs.tlvBase64` (base64-encoded TLV of the attribute value), `sbmdReadArgs.clusterId`, `sbmdReadArgs.attributeId`, `sbmdReadArgs.attributeName`, `sbmdReadArgs.attributeType`, `sbmdReadArgs.endpointId`, `sbmdReadArgs.deviceUuid`, and `sbmdReadArgs.clusterFeatureMaps`. The script SHALL return a JSON object of the form `{ output: }`, identical to the return contract of `mapper.read` scripts. + +#### Scenario: seedFrom script decodes uint8 to boolean +- **WHEN** a `seedFrom` script receives a `uint8` TLV for the `LockState` attribute (value `1` = locked) +- **THEN** the script SHALL decode the value and return `{ output: "true" }` for the `locked` resource + +#### Scenario: seedFrom script receives same args as read script +- **WHEN** a `seedFrom` script is invoked +- **THEN** `sbmdReadArgs` SHALL contain the same fields as a `read` mapper script invocation for the same attribute + +### Requirement: Event script partial handling of LockOperation types +The `event` script paired with `seedFrom` MUST handle all values of the event's discriminating field. For the `LockOperation` event on the DoorLock cluster, the script SHALL return a resource value only for `LockOperationType` values that represent a lock state transition (Lock = 0, Unlock = 1). For all other `LockOperationType` values (NonAccessUserEvent = 2, ForcedUserEvent = 3, Unlatch = 4), the script SHALL return no `output` key, leaving the resource value unchanged. + +#### Scenario: LockOperation event with Lock type updates resource +- **WHEN** a `LockOperation` event arrives with `LockOperationType` = 0 (Lock) +- **THEN** the event script SHALL return `{ output: "true" }` and the `locked` resource SHALL be updated + +#### Scenario: LockOperation event with non-state-change type does not update resource +- **WHEN** a `LockOperation` event arrives with `LockOperationType` = 2 (NonAccessUserEvent) +- **THEN** the event script SHALL return no `output` key and the `locked` resource SHALL NOT be updated + +### Requirement: seedFrom JSON schema entry +The SBMD JSON schema (`sbmd-spec-schema.json`) SHALL define a `seedFromMapper` type requiring `alias` (string) and `script` (string), and SHALL add `seedFrom` as an optional property of the `mapper` object. + +#### Scenario: seedFrom schema present in mapper +- **WHEN** a mapper contains a `seedFrom` object with `alias` and `script` +- **THEN** JSON schema validation SHALL accept the mapper + +#### Scenario: seedFrom missing required fields fails schema validation +- **WHEN** a mapper contains a `seedFrom` object that omits `script` +- **THEN** JSON schema validation SHALL reject the spec diff --git a/openspec/specs/sbmd-v4-runtime/spec.md b/openspec/specs/sbmd-runtime/spec.md similarity index 72% rename from openspec/specs/sbmd-v4-runtime/spec.md rename to openspec/specs/sbmd-runtime/spec.md index 272f53fd..c1baed3d 100644 --- a/openspec/specs/sbmd-v4-runtime/spec.md +++ b/openspec/specs/sbmd-runtime/spec.md @@ -1,11 +1,49 @@ -# SBMD v4 Runtime +# SBMD Runtime ## Purpose -Specifies the SBMD v4 embedded-JavaScript runtime: two-pass file evaluation with constants injection, the `SbmdDriver` capture function and registration extraction, result building, handler dispatch, supplements pre-loading, resource handler invocation, result-chain execution, deferred operations, driver lifecycle, and alias resolution. +Specifies the Spec-Based Matter Driver (SBMD) runtime engine: how driver files are loaded and claimed, the v4 embedded-JavaScript execution model (two-pass evaluation, handler dispatch, result chains, deferred operations, alias resolution, and lifecycle), and the safety limits (per-invocation script timeouts, an overall operation timeout for deferred chains, and a maximum deferral depth) that bound execution. ## Requirements +### Requirement: SBMD factory loads driver files +The SBMD factory SHALL scan configured directories for `.sbmd.js` files (instead of `.sbmd` YAML files). For each file, the factory SHALL evaluate it in the mquickjs context, extract metadata to C++ structures, and register the driver with `MatterDriverFactory`. The factory SHALL no longer use `SbmdParser` or yaml-cpp for driver loading. + +#### Scenario: Factory loads .sbmd.js files +- **WHEN** the SBMD factory scans the specs directory at startup +- **THEN** it finds and loads all files with the `.sbmd.js` extension + +#### Scenario: Factory ignores .sbmd files +- **WHEN** the specs directory contains both `.sbmd` and `.sbmd.js` files +- **THEN** only `.sbmd.js` files are loaded + +#### Scenario: Invalid .sbmd.js file rejected +- **WHEN** a `.sbmd.js` file contains a JavaScript syntax error +- **THEN** the factory logs an error and continues loading other files + +### Requirement: Driver claiming uses C++ metadata +The driver claiming process (vendor-specific pass, then generic device-type pass) SHALL use C++ metadata extracted at load time. Claiming SHALL NOT require the driver to be activated (handler JSValues rooted). + +#### Scenario: Inactive driver participates in claiming +- **WHEN** a new device is commissioned and matches an inactive driver's device types +- **THEN** the driver is identified as a candidate, activated, and claiming proceeds + +### Requirement: SpecBasedMatterDeviceDriver supports v4 handler model +The `SpecBasedMatterDeviceDriver` SHALL dispatch Barton resource operations to v4 handler functions (seed, read, write, execute) and device-initiated messages to attribute/event/command handlers. It SHALL execute result chains returned by handlers. + +#### Scenario: Resource read dispatches to read handler +- **WHEN** a Barton read operation is performed on a resource with a `read` handler +- **THEN** the driver resolves supplements, calls the handler, and returns the result value + +#### Scenario: Execute handler auto-sets EXECUTABLE mode +- **WHEN** a resource declares an `execute` handler +- **THEN** the runtime SHALL set `RESOURCE_MODE_EXECUTABLE` on that resource regardless of whether `"execute"` appears in its `modes` array +- **AND** explicitly including `"execute"` in `modes` SHALL be accepted and treated as equivalent + +#### Scenario: Attribute report dispatches to attribute handler +- **WHEN** a Matter attribute report arrives matching a registered `attributeHandler` +- **THEN** the driver calls the handler and executes the result chain (e.g., resource updates) + ### Requirement: Two-pass file evaluation with constants injection The runtime SHALL evaluate `.sbmd.js` files using a two-pass process. Pass 1 SHALL extract the `constants:` block from the source text by brace-matching, evaluate it as a JavaScript object literal, and produce a set of name→primitive-value pairs. Pass 2 SHALL prepend `var` declarations for each constant, wrap the entire file in an IIFE, and evaluate the result using `JS_EVAL_REPL`. @@ -159,6 +197,32 @@ Aliases declared in the `aliases` section SHALL be resolved to cluster+ID pairs - **WHEN** a resource has `prerequisites: ["lockOperation"]` and `lockOperation` is an event alias with `clusterId: 0x0101` - **THEN** the prerequisite is satisfied if cluster 0x0101 is present in the device's data cache +### Requirement: Script timeout enforcement for handler invocations +The mquickjs interrupt handler SHALL enforce per-invocation timeouts for v4 handler function calls, using the same `BARTON_CONFIG_SBMD_SCRIPT_TIMEOUT_MS` configuration as v3 mapper scripts. The deadline SHALL be set before each handler call and cleared immediately after. + +#### Scenario: Handler exceeds timeout +- **WHEN** a handler function runs longer than `BARTON_CONFIG_SBMD_SCRIPT_TIMEOUT_MS` +- **THEN** the mquickjs interrupt handler terminates execution and the runtime reports the operation as failed + + +### Requirement: Overall operation timeout for deferred chains +The runtime SHALL enforce an overall operation deadline for resource operations that involve deferred chains. The deadline SHALL be set when the first deferral occurs (from `matter.defaultTimeoutMs` or a system default) and SHALL NOT reset on subsequent deferrals. Per-hop `timeoutMs` values SHALL be capped at the remaining overall budget. + +#### Scenario: Overall timeout prevents runaway chains +- **WHEN** a deferred chain makes multiple successful hops but exceeds the overall deadline +- **THEN** the next deferral attempt triggers `onError` with `type: "timeout"` without sending the command + +#### Scenario: Per-hop timeout capped by overall budget +- **WHEN** a deferral specifies `timeoutMs: 30000` but only 5000ms remain in the overall budget +- **THEN** the effective per-hop timeout is 5000ms + +### Requirement: Maximum deferral depth +The runtime SHALL enforce a maximum deferral depth (configurable, default 10). When exceeded, the current hop's `onError` handler SHALL be called with an error indicating the depth limit was reached. + +#### Scenario: Depth limit exceeded +- **WHEN** a deferred chain reaches the maximum deferral depth +- **THEN** the `onError` handler is called with a message indicating deferral depth exceeded and the parked operation completes with failure + ### Requirement: volatile resource mode disables value caching The SBMD runtime SHALL support a resource mode `volatile`. A resource declared with the `volatile` mode SHALL be registered with `CACHING_POLICY_NEVER`, causing `updateResource` to emit a `resourceUpdated` event on every call (for a resource that emits events) regardless of whether the new value equals the currently stored value. The `volatile` mode SHALL NOT, by itself, add read, write, or execute access, and SHALL be accepted by mode-to-bitmask conversion without error. A resource is registered `CACHING_POLICY_NEVER` when it declares a read handler OR declares the `volatile` mode; otherwise it is registered `CACHING_POLICY_ALWAYS`. diff --git a/openspec/specs/sbmd-script-execution-limits/spec.md b/openspec/specs/sbmd-script-execution-limits/spec.md deleted file mode 100644 index 0434e9c3..00000000 --- a/openspec/specs/sbmd-script-execution-limits/spec.md +++ /dev/null @@ -1,33 +0,0 @@ -# SBMD Script Execution Limits - -## Purpose - -Specifies safety limits for SBMD embedded-JavaScript execution: per-invocation script timeouts, an overall operation timeout for deferred chains, and a maximum deferral depth. - -## Requirements - -### Requirement: Script timeout enforcement for handler invocations -The mquickjs interrupt handler SHALL enforce per-invocation timeouts for v4 handler function calls, using the same `BARTON_CONFIG_SBMD_SCRIPT_TIMEOUT_MS` configuration as v3 mapper scripts. The deadline SHALL be set before each handler call and cleared immediately after. - -#### Scenario: Handler exceeds timeout -- **WHEN** a handler function runs longer than `BARTON_CONFIG_SBMD_SCRIPT_TIMEOUT_MS` -- **THEN** the mquickjs interrupt handler terminates execution and the runtime reports the operation as failed - - -### Requirement: Overall operation timeout for deferred chains -The runtime SHALL enforce an overall operation deadline for resource operations that involve deferred chains. The deadline SHALL be set when the first deferral occurs (from `matter.defaultTimeoutMs` or a system default) and SHALL NOT reset on subsequent deferrals. Per-hop `timeoutMs` values SHALL be capped at the remaining overall budget. - -#### Scenario: Overall timeout prevents runaway chains -- **WHEN** a deferred chain makes multiple successful hops but exceeds the overall deadline -- **THEN** the next deferral attempt triggers `onError` with `type: "timeout"` without sending the command - -#### Scenario: Per-hop timeout capped by overall budget -- **WHEN** a deferral specifies `timeoutMs: 30000` but only 5000ms remain in the overall budget -- **THEN** the effective per-hop timeout is 5000ms - -### Requirement: Maximum deferral depth -The runtime SHALL enforce a maximum deferral depth (configurable, default 10). When exceeded, the current hop's `onError` handler SHALL be called with an error indicating the depth limit was reached. - -#### Scenario: Depth limit exceeded -- **WHEN** a deferred chain reaches the maximum deferral depth -- **THEN** the `onError` handler is called with a message indicating deferral depth exceeded and the parked operation completes with failure diff --git a/openspec/specs/sbmd-seed-from-attribute/spec.md b/openspec/specs/sbmd-seed-from-attribute/spec.md deleted file mode 100644 index e91df7e8..00000000 --- a/openspec/specs/sbmd-seed-from-attribute/spec.md +++ /dev/null @@ -1,99 +0,0 @@ -# SBMD seedFrom Attribute - -## Purpose - -Specifies the SBMD `seedFrom` mapper that seeds an event-backed resource from a Matter attribute at configure and synchronize time: its schema and structure, mutual exclusion with `read`, event requirement, non-subscription semantics, script interface, and JSON schema entry. - -## Requirements - -### Requirement: seedFrom mapper — schema and structure -A resource's mapper MAY contain a `seedFrom` section with an `alias` (a string naming an **attribute** alias defined in `matterMeta.aliases`) and a `script` (JavaScript string). The `seedFrom` mapper SHALL only appear when the same resource also declares a `mapper.event` section. The `alias` SHALL resolve to an attribute alias; event aliases SHALL NOT be accepted. The `script` SHALL be required; absence of `script` SHALL be a parse error. - -#### Scenario: Valid seedFrom mapper -- **WHEN** a mapper declares `seedFrom.alias: lockState` and `lockState` is an attribute alias, and `seedFrom.script` is present, and `mapper.event` is also present -- **THEN** the parser SHALL populate `seedFromAttribute` with the resolved cluster/attribute/type and `seedFromScript` with the script text - -#### Scenario: seedFrom with event alias rejected -- **WHEN** a mapper declares `seedFrom.alias: lockOperation` and `lockOperation` is an event alias (not an attribute alias) -- **THEN** the parser SHALL reject the spec with an error - -#### Scenario: seedFrom without script rejected -- **WHEN** a mapper declares `seedFrom.alias: lockState` but omits `seedFrom.script` -- **THEN** the parser SHALL reject the spec with an error - -### Requirement: seedFrom mutual exclusion with read -A resource mapper SHALL NOT declare both `read` and `seedFrom`. The presence of both SHALL be a parse-time error. - -#### Scenario: seedFrom + read on same resource rejected -- **WHEN** a mapper declares both `read` and `seedFrom` -- **THEN** the parser SHALL reject the spec with an error - -### Requirement: seedFrom requires event -A `seedFrom` mapper SHALL NOT appear on a resource without a `mapper.event` section. Absence of `mapper.event` when `mapper.seedFrom` is present SHALL be a parse-time error. - -#### Scenario: seedFrom without event rejected -- **WHEN** a mapper declares `seedFrom` but does not declare `event` -- **THEN** the parser SHALL reject the spec with an error - -### Requirement: seedFrom attribute not subscribed for live updates -Attributes referenced by `mapper.seedFrom` SHALL NOT be registered in the device's live attribute subscription callback table. `seedFrom` attribute reads are one-shot operations that occur only at configure and synchronize time; they SHALL NOT update the resource in response to live attribute subscription callbacks. - -#### Scenario: seedFrom attribute does not update resource on subscription -- **WHEN** a resource declares only `event` and `seedFrom` (no `read`) -- **THEN** the driver SHALL NOT register the seedFrom attribute in `readableAttributeLookup` -- **THEN** a live attribute subscription update for the seedFrom attribute SHALL NOT update the resource value - -### Requirement: seedFrom seeds resource at configure time -When a device is configured (during commissioning), the driver SHALL compute each resource's `seedFrom` value from the device data cache during resource registration (`DoRegisterResources`) and pass it as the initial value to `createDeviceResource()`/`createEndpointResource()`. This SHALL occur on the Matter thread (which is where `DoRegisterResources` executes) and before the device is persisted or `DEVICE_ADDED` is emitted, so the resource SHALL have the correct initial value when `DEVICE_ADDED` fires. Resources in `skippedOptionalResources` SHALL be skipped. If the attribute is absent from the cache, the resource SHALL be created with a null initial value. - -#### Scenario: Locked resource seeded at commissioning -- **WHEN** a door lock device is commissioned and `LockState` (cluster `0x0101`, attribute `0x0000`) is present in the attribute cache -- **THEN** the `locked` resource SHALL have a non-null value when `DEVICE_ADDED` fires — no intermediate null state SHALL be visible to clients - -#### Scenario: Optional resource not seeded when skipped -- **WHEN** a resource is marked `optional: true` and was skipped due to absent cluster -- **THEN** `DoRegisterResources()` SHALL NOT attempt to read its seedFrom attribute - -#### Scenario: Cache miss — resource created with null value -- **WHEN** the seedFrom attribute is absent from the device data cache at configure time -- **THEN** `createDeviceResource()` SHALL be called with a null initial value for that resource - -### Requirement: seedFrom seeds resource at synchronize time -When a device is synchronized (at Barton restart or communication restoration), the driver SHALL re-read each resource's `seedFrom` attribute from the device data cache and update the resource value via `updateResource()`. This SHALL use the same `SeedInitialResourceValues()` helper as configure time and apply the same guards (skipped resources, cache miss). - -#### Scenario: Locked resource re-seeded at device synchronization -- **WHEN** `SynchronizeDevice()` is called for a commissioned door lock (e.g., after a comm-fail restore or Barton restart) -- **THEN** the `locked` resource SHALL be re-seeded from the `LockState` attribute cache before any event fires - -### Requirement: seedFrom script interface -The JavaScript script in `mapper.seedFrom` SHALL receive attribute value data via the same `sbmdReadArgs` interface used by `mapper.read` scripts: `sbmdReadArgs.tlvBase64` (base64-encoded TLV of the attribute value), `sbmdReadArgs.clusterId`, `sbmdReadArgs.attributeId`, `sbmdReadArgs.attributeName`, `sbmdReadArgs.attributeType`, `sbmdReadArgs.endpointId`, `sbmdReadArgs.deviceUuid`, and `sbmdReadArgs.clusterFeatureMaps`. The script SHALL return a JSON object of the form `{ output: }`, identical to the return contract of `mapper.read` scripts. - -#### Scenario: seedFrom script decodes uint8 to boolean -- **WHEN** a `seedFrom` script receives a `uint8` TLV for the `LockState` attribute (value `1` = locked) -- **THEN** the script SHALL decode the value and return `{ output: "true" }` for the `locked` resource - -#### Scenario: seedFrom script receives same args as read script -- **WHEN** a `seedFrom` script is invoked -- **THEN** `sbmdReadArgs` SHALL contain the same fields as a `read` mapper script invocation for the same attribute - -### Requirement: Event script partial handling of LockOperation types -The `event` script paired with `seedFrom` MUST handle all values of the event's discriminating field. For the `LockOperation` event on the DoorLock cluster, the script SHALL return a resource value only for `LockOperationType` values that represent a lock state transition (Lock = 0, Unlock = 1). For all other `LockOperationType` values (NonAccessUserEvent = 2, ForcedUserEvent = 3, Unlatch = 4), the script SHALL return no `output` key, leaving the resource value unchanged. - -#### Scenario: LockOperation event with Lock type updates resource -- **WHEN** a `LockOperation` event arrives with `LockOperationType` = 0 (Lock) -- **THEN** the event script SHALL return `{ output: "true" }` and the `locked` resource SHALL be updated - -#### Scenario: LockOperation event with non-state-change type does not update resource -- **WHEN** a `LockOperation` event arrives with `LockOperationType` = 2 (NonAccessUserEvent) -- **THEN** the event script SHALL return no `output` key and the `locked` resource SHALL NOT be updated - -### Requirement: seedFrom JSON schema entry -The SBMD JSON schema (`sbmd-spec-schema.json`) SHALL define a `seedFromMapper` type requiring `alias` (string) and `script` (string), and SHALL add `seedFrom` as an optional property of the `mapper` object. - -#### Scenario: seedFrom schema present in mapper -- **WHEN** a mapper contains a `seedFrom` object with `alias` and `script` -- **THEN** JSON schema validation SHALL accept the mapper - -#### Scenario: seedFrom missing required fields fails schema validation -- **WHEN** a mapper contains a `seedFrom` object that omits `script` -- **THEN** JSON schema validation SHALL reject the spec diff --git a/openspec/specs/sbmd-system/spec.md b/openspec/specs/sbmd-system/spec.md deleted file mode 100644 index 2a6bfa90..00000000 --- a/openspec/specs/sbmd-system/spec.md +++ /dev/null @@ -1,45 +0,0 @@ -# SBMD System - -## Purpose - -Specifies the Spec-Based Matter Driver system: how the factory loads driver files, claims devices using C++ metadata, and supports the v4 handler model in `SpecBasedMatterDeviceDriver`. - -## Requirements - -### Requirement: SBMD factory loads driver files -The SBMD factory SHALL scan configured directories for `.sbmd.js` files (instead of `.sbmd` YAML files). For each file, the factory SHALL evaluate it in the mquickjs context, extract metadata to C++ structures, and register the driver with `MatterDriverFactory`. The factory SHALL no longer use `SbmdParser` or yaml-cpp for driver loading. - -#### Scenario: Factory loads .sbmd.js files -- **WHEN** the SBMD factory scans the specs directory at startup -- **THEN** it finds and loads all files with the `.sbmd.js` extension - -#### Scenario: Factory ignores .sbmd files -- **WHEN** the specs directory contains both `.sbmd` and `.sbmd.js` files -- **THEN** only `.sbmd.js` files are loaded - -#### Scenario: Invalid .sbmd.js file rejected -- **WHEN** a `.sbmd.js` file contains a JavaScript syntax error -- **THEN** the factory logs an error and continues loading other files - -### Requirement: Driver claiming uses C++ metadata -The driver claiming process (vendor-specific pass, then generic device-type pass) SHALL use C++ metadata extracted at load time. Claiming SHALL NOT require the driver to be activated (handler JSValues rooted). - -#### Scenario: Inactive driver participates in claiming -- **WHEN** a new device is commissioned and matches an inactive driver's device types -- **THEN** the driver is identified as a candidate, activated, and claiming proceeds - -### Requirement: SpecBasedMatterDeviceDriver supports v4 handler model -The `SpecBasedMatterDeviceDriver` SHALL dispatch Barton resource operations to v4 handler functions (seed, read, write, execute) and device-initiated messages to attribute/event/command handlers. It SHALL execute result chains returned by handlers. - -#### Scenario: Resource read dispatches to read handler -- **WHEN** a Barton read operation is performed on a resource with a `read` handler -- **THEN** the driver resolves supplements, calls the handler, and returns the result value - -#### Scenario: Execute handler auto-sets EXECUTABLE mode -- **WHEN** a resource declares an `execute` handler -- **THEN** the runtime SHALL set `RESOURCE_MODE_EXECUTABLE` on that resource regardless of whether `"execute"` appears in its `modes` array -- **AND** explicitly including `"execute"` in `modes` SHALL be accepted and treated as equivalent - -#### Scenario: Attribute report dispatches to attribute handler -- **WHEN** a Matter attribute report arrives matching a registered `attributeHandler` -- **THEN** the driver calls the handler and executes the result chain (e.g., resource updates) diff --git a/openspec/specs/sbmd-v4-light-driver/spec.md b/openspec/specs/sbmd-v4-light-driver/spec.md deleted file mode 100644 index b3f35ee7..00000000 --- a/openspec/specs/sbmd-v4-light-driver/spec.md +++ /dev/null @@ -1,63 +0,0 @@ -# SBMD v4 Light Driver - -## Purpose - -Specifies the v4 JavaScript SBMD light driver: on/off via attribute and write handlers, the optional current-level resource, and continued unchanged passing of existing integration tests. - -## Requirements - -### Requirement: Light driver as v4 JavaScript file -The light driver SHALL be implemented as a single `light.sbmd.js` file using the v4 `SbmdDriver({...})` registration format. It SHALL declare constants for all cluster, attribute, command, and resource IDs. It SHALL support the same device types as the v3 `light.sbmd` driver. - -#### Scenario: Light driver loads successfully -- **WHEN** the SBMD factory scans the specs directory at startup -- **THEN** `light.sbmd.js` is evaluated, metadata is extracted, and the driver is registered for device types 0x0100, 0x010a, 0x0101, 0x010b, 0x0102, 0x0200, 0x010d, 0x0210, 0x010c, 0x0220, 0x0103, 0x0104, 0x0105 - -### Requirement: Light on/off resource via attribute handler and write handler -The `isOn` resource on endpoint "1" SHALL be readable, writable, dynamic, and emit events. An `attributeHandler` for the OnOff attribute SHALL update the resource when attribute reports arrive. A `seed` handler SHALL read the initial value from supplements. A `write` handler SHALL send the On (0x0001) or Off (0x0000) command on the OnOff cluster (0x0006). - -#### Scenario: On/Off attribute report updates resource -- **WHEN** a Matter attribute report for cluster 0x0006, attribute 0x0000 arrives with value `true` -- **THEN** the `isOn` resource on endpoint "1" is updated to `"true"` - -#### Scenario: Write true sends On command -- **WHEN** a Barton write operation sets `isOn` to `"true"` -- **THEN** the driver sends Matter command 0x0001 (On) on cluster 0x0006 - -#### Scenario: Write false sends Off command -- **WHEN** a Barton write operation sets `isOn` to `"false"` -- **THEN** the driver sends Matter command 0x0000 (Off) on cluster 0x0006 - -#### Scenario: Seed handler reads initial value -- **WHEN** the device is commissioned or the service restarts -- **THEN** the seed handler reads the OnOff attribute from supplements and sets the initial `isOn` value - -### Requirement: Light current level resource (optional) -The `currentLevel` resource on endpoint "1" SHALL be optional (prerequisite: `currentLevel` alias). It SHALL map Matter level (0–254) to a percentage string (0–100). A `write` handler SHALL send the MoveToLevelWithOnOff command (0x0004) on the LevelControl cluster (0x0008). - -#### Scenario: Level attribute report updates resource as percentage -- **WHEN** a Matter attribute report for cluster 0x0008, attribute 0x0000 arrives with value 127 -- **THEN** the `currentLevel` resource is updated to `"50"` - -#### Scenario: Write percentage sends MoveToLevel command -- **WHEN** a Barton write sets `currentLevel` to `"75"` -- **THEN** the driver sends MoveToLevelWithOnOff with level 191 (round(75/100*254)), transition time 0 - -#### Scenario: Resource skipped when cluster absent -- **WHEN** a commissioned device does not have the LevelControl cluster (0x0008) -- **THEN** the `currentLevel` resource is not created and no error occurs - -### Requirement: Existing integration tests pass unchanged -All light integration tests (`testing/test/light_test.py`) SHALL pass against the v4 light driver without any modifications to the test code. - -#### Scenario: Commission and verify resources -- **WHEN** `test_commission_light` runs against the v4 driver -- **THEN** the test passes with the same resource set as v3 - -#### Scenario: On/off toggle via sideband -- **WHEN** `test_light_on_off` runs against the v4 driver -- **THEN** the test passes — toggling the sideband device updates the Barton resource - -#### Scenario: Attribute report for common clusters -- **WHEN** `test_light_common_cluster_attribute_report` runs against the v4 driver -- **THEN** the test passes — identifySeconds attribute reports are handled correctly diff --git a/openspec/specs/temperature-humidity-sbmd-drivers/spec.md b/openspec/specs/temperature-humidity-sbmd-drivers/spec.md deleted file mode 100644 index 22f4be54..00000000 --- a/openspec/specs/temperature-humidity-sbmd-drivers/spec.md +++ /dev/null @@ -1,81 +0,0 @@ -# Temperature and Humidity SBMD Drivers - -## Purpose - -Specifies the SBMD specs and tests for temperature and humidity sensors: the IKEA TIMMERFLOTTE spec and its resources, generic temperature and humidity sensor specs, virtual test devices, and integration tests. - -## Requirements - -### Requirement: IKEA TIMMERFLOTTE SBMD spec -The system SHALL include an SBMD spec file `ikea-timmerflotte.sbmd` that defines a TIMMERFLOTTE-specific driver claiming by vendor ID and product ID. The spec SHALL also list device types `0x0302` (Temperature Sensor) and `0x0307` (Humidity Sensor) for endpoint mapping purposes. - -#### Scenario: TIMMERFLOTTE device is claimed -- **WHEN** a Matter device reports VendorID matching IKEA and ProductID matching TIMMERFLOTTE -- **THEN** the `ikea-timmerflotte` driver SHALL claim the device - -#### Scenario: Device with same device types but different vendor is not claimed -- **WHEN** a Matter device has endpoints with device types `0x0302` and `0x0307` but a different vendor ID -- **THEN** the `ikea-timmerflotte` driver SHALL NOT claim the device (the generic temperature-sensor or humidity-sensor driver will claim it instead) - -### Requirement: IKEA TIMMERFLOTTE sensor resources -The `ikea-timmerflotte.sbmd` spec SHALL define a single SBMD endpoint with profile `sensor` containing two resources: `temperature` and `humidity`. - -#### Scenario: Temperature resource read -- **WHEN** the `temperature` resource is read from a composite temperature-humidity device -- **THEN** the driver SHALL read Matter cluster `0x0402` (Temperature Measurement), attribute `0x0000` (MeasuredValue, type `int16`), and the mapper script SHALL return the raw hundredths-of-a-degree value as a string (e.g., raw `2550` → `"2550"`) - -#### Scenario: Humidity resource read -- **WHEN** the `humidity` resource is read from a composite temperature-humidity device -- **THEN** the driver SHALL read Matter cluster `0x0405` (Relative Humidity Measurement), attribute `0x0000` (MeasuredValue, type `uint16`), and the mapper script SHALL convert the raw hundredths-of-a-percent value to whole percent as a string (e.g., raw `5000` → `"50"`) - -#### Scenario: Null measurement value -- **WHEN** the MeasuredValue attribute for temperature or humidity is null (0x8000 for int16 or 0xFFFF for uint16) -- **THEN** the mapper script SHALL return an empty string `""` - -### Requirement: Temperature sensor SBMD spec -The system SHALL include an SBMD spec file `temperature-sensor.sbmd` that defines a `temperatureSensor` device class matching Matter device type `0x0302` (Temperature Sensor) using the default `"any"` match semantics. - -#### Scenario: Temperature-only device is claimed -- **WHEN** a Matter device has an endpoint with device type `0x0302` -- **THEN** the `temperature-sensor` driver SHALL claim the device - -#### Scenario: Temperature resource read -- **WHEN** the `temperature` resource is read -- **THEN** the driver SHALL read Matter cluster `0x0402`, attribute `0x0000` (MeasuredValue, `int16`), and the mapper script SHALL return the raw hundredths-of-a-degree value as a string - -### Requirement: Humidity sensor SBMD spec -The system SHALL include an SBMD spec file `humidity-sensor.sbmd` that defines a `humiditySensor` device class matching Matter device type `0x0307` (Humidity Sensor) using the default `"any"` match semantics. - -#### Scenario: Humidity-only device is claimed -- **WHEN** a Matter device has an endpoint with device type `0x0307` -- **THEN** the `humidity-sensor` driver SHALL claim the device - -#### Scenario: Humidity resource read -- **WHEN** the `humidity` resource is read -- **THEN** the driver SHALL read Matter cluster `0x0405`, attribute `0x0000` (MeasuredValue, `uint16`), and the mapper script SHALL convert the raw hundredths-of-a-percent value to whole percent as a string - -### Requirement: Test devices for temperature and humidity sensors -The test infrastructure SHALL include Python virtual Matter devices for a TIMMERFLOTTE-like temperature-humidity sensor (with matching vendor/product ID), a standalone temperature sensor, and a standalone humidity sensor. These devices SHALL expose the appropriate Matter device types, clusters, and BasicInformation attributes for integration testing. - -#### Scenario: TIMMERFLOTTE test device exposes both device types and vendor info -- **WHEN** the TIMMERFLOTTE test device is instantiated -- **THEN** it SHALL expose endpoint 1 with device type `0x0302` and cluster `0x0402`, endpoint 2 with device type `0x0307` and cluster `0x0405`, and BasicInformation with the IKEA vendor ID and TIMMERFLOTTE product ID - -#### Scenario: Single-function test device exposes one device type -- **WHEN** the temperature-only test device is instantiated -- **THEN** it SHALL expose one endpoint with device type `0x0302` and cluster `0x0402` - -### Requirement: Integration tests for sensor drivers -Integration tests SHALL verify device discovery, claiming, resource reading, and dynamic updates for all three sensor driver types. - -#### Scenario: Discover and read TIMMERFLOTTE sensor -- **WHEN** a TIMMERFLOTTE temperature-humidity test device is commissioned -- **THEN** the device SHALL be claimed by the `ikea-timmerflotte` driver (via vendor/product ID match), and both `temperature` and `humidity` resources SHALL return correct values - -#### Scenario: Discover and read standalone temperature sensor -- **WHEN** a temperature-only test device is commissioned -- **THEN** the device SHALL be claimed by the `temperature-sensor` driver and the `temperature` resource SHALL return a correct value - -#### Scenario: Discover and read standalone humidity sensor -- **WHEN** a humidity-only test device is commissioned -- **THEN** the device SHALL be claimed by the `humidity-sensor` driver and the `humidity` resource SHALL return a correct value