Skip to content

fix(bridge-node): adopt a factory reset's own renumbering instead of alarming forever (#140) - #151

Merged
simons-plugins merged 2 commits into
mainfrom
fix/140-adopt-reset-renumbering
Aug 8, 2026
Merged

fix(bridge-node): adopt a factory reset's own renumbering instead of alarming forever (#140)#151
simons-plugins merged 2 commits into
mainfrom
fix/140-adopt-reset-renumbering

Conversation

@simons-plugins

@simons-plugins simons-plugins commented Aug 8, 2026

Copy link
Copy Markdown
Owner

Closes #140.

The defect: two deliberate designs colliding

A preserving factory reset keeps the endpoint map specifically to report the renumbering the reset causes, and its log line told the user to clear the report with the §3.11 rebuild — which the plugin's M11 gate correctly refuses on a healthy node. Permanent alarm, locked door. Live on jarvis at every attach: indigo-459564566: expected 5, got 2.

The fix — issue option (3), node-side only

At both reset sites — §3.10 factory_reset preserveEndpointNumbers: true and the last-fabric self-reset (noteLastFabricGone) — the node now voids every map entry's number (numberVoid: true, per entry). check() silently adopts the next live number for a void entry and clears the marker (even when the numbers happen to match — the marker is what must stop being true).

Safe by construction (now in the class comment): matter.js is always the allocator — the map is witness-only, even on the #141 restore path — and both reset sites are reached with an empty fabric set, so no paired ecosystem exists that could still hold the old numbers. Adoption is unobservable outside the node.

The never-auto-repaired rule survives: an entry that was never voided drifts exactly as before, and on a ≥0.8.0 node surviving drift is now a stronger signal. The plugin's drift error says so — naming the bridge-node version, not the plugin's: adoption is node-side, and a new plugin driving the published 0.7.0 node still gets reset drift; claiming it away would be #132's mistake over again.

Schema stays v2: every reader takes fields by name, so an old build reading a file carrying numberVoid never looks at the key — a version bump would guard nothing (documented at ENDPOINT_MAP_VERSION).

No wire change. Golden frames untouched; StatusReport.drift shape unchanged.

NOT in this PR (by the #141 precedent — never pin an unpublished version)

The bridge-node 0.7.0 → 0.8.0 bump, npm publish, and the DEFAULT_INSTALL_SPEC pin — the step that actually ships this behaviour to installs. jarvis also needs a one-time map edit for its pre-existing stale entry (predates the marker); recipe in HANDOVER §#140 — the safer variant is adding "numberVoid": true to the stale entries and letting the new node adopt.

Tests

  • TS: 13 new (396 total) — void/adopt/steady-state/marker-clears-on-match/non-void-still-drifts (the critical regression test)/persist-round-trip/legacy-file/duplicate-fingerprint-heals/restorable-while-void/both reset sites/log line.
  • Python: 1 new (2265 total) — drift message pins "0.8.0" + "outside any reset".
  • Mutation-verified: adopt-regardless-of-marker → killed; skip voidNumbers at noteLastFabricGone → killed.
  • One pre-existing test updated (persistence.test.ts "keeps endpoint-map.json by default" — entries now gain the marker after a preserving reset).

Version 2026.8.8 → 2026.8.9.

🤖 Generated with Claude Code

https://claude.ai/code/session_018Unpt5UPRdLoZkahH6a4gL

Summary by CodeRabbit

  • New Features

    • Factory resets can automatically adopt newly assigned endpoint numbers while preserving endpoint roles and labels.
    • Endpoint numbers are safely invalidated when the last fabric is removed or reset preservation is used.
  • Bug Fixes

    • Prevented expected post-reset renumbering from being reported as storage drift.
    • Unexplained endpoint changes continue to be reported for investigation.
  • Documentation

    • Updated drift handling, reset behavior, and compatibility guidance.
  • Chores

    • Updated the Indigo Matter plugin version to 2026.8.9.

…alarming forever (#140)

Two deliberate designs collided: a preserving factory reset kept the
endpoint map specifically to REPORT the renumbering the reset causes,
and its log line told the user to clear the report with the §3.11
rebuild — which the plugin's M11 gate correctly refuses on a healthy
node. Permanent alarm, locked door. Live on jarvis at every attach
(indigo-459564566: expected 5, got 2).

Fix is issue option (3), node-side only. Both reset sites — §3.10
factory_reset preserveEndpointNumbers:true and the last-fabric
self-reset — now void every map entry's number (numberVoid: true);
check() silently adopts the next live number for a void entry and
clears the marker (even when the numbers happen to match: the marker is
what must stop being true). Safe by construction: matter.js is always
the allocator (the map is witness-only, even on restore) and both reset
sites are reached with an empty fabric set, so no paired ecosystem
exists that could hold the old numbers. A never-voided entry still
drifts exactly as before — on a >=0.8.0 node surviving drift is a
STRONGER signal, and the plugin's drift error now says so, naming the
bridge-node version rather than the plugin's (adoption is node-side; a
new plugin driving an old node still gets reset drift, and claiming it
away would be #132 again).

Schema stays v2: every reader takes fields by name, so an old build
never looks at the new key — a version bump would guard nothing.

No wire change; golden frames untouched. NOT in this PR, by the #141
precedent (never pin an unpublished version): the bridge-node
0.7.0->0.8.0 bump + npm publish + DEFAULT_INSTALL_SPEC pin, which is
what actually ships the behaviour to installs.

Mutation-verified: adopt-regardless-of-marker fails the
non-void-still-drifts test; skipping voidNumbers at noteLastFabricGone
fails its test. The reset's duplicate-number fingerprint heals under
adoption (tested).

Suites: 2265 Python (+1), 396 TS (+13). Version 2026.8.8 -> 2026.8.9.

Closes #140

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Unpt5UPRdLoZkahH6a4gL
@coderabbitai

coderabbitai Bot commented Aug 8, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2f717f63-de72-49d0-abd1-2600f13ffd13

📥 Commits

Reviewing files that changed from the base of the PR and between cbaa20a and 8566e68.

📒 Files selected for processing (5)
  • bridge-node/src/endpoint-map.ts
  • bridge-node/src/node.ts
  • bridge-node/test/endpoint-map.test.ts
  • bridge-node/test/persistence.test.ts
  • docs/BRIDGE_PROTOCOL.md
🚧 Files skipped from review as they are similar to previous changes (4)
  • bridge-node/src/node.ts
  • bridge-node/test/endpoint-map.test.ts
  • docs/BRIDGE_PROTOCOL.md
  • bridge-node/src/endpoint-map.ts

📝 Walkthrough

Walkthrough

Changes

Reset-aware endpoint renumbering

Layer / File(s) Summary
Endpoint-map voiding and adoption
bridge-node/src/endpoint-map.ts, bridge-node/test/endpoint-map.test.ts
Endpoint records support numberVoid. Voided records adopt live numbers during check(), clear the marker, persist changes, and avoid drift reports.
Reset-path map invalidation
bridge-node/src/node.ts, bridge-node/test/persistence.test.ts
Last-fabric removal and preserving factory resets call voidNumbers() and retain endpoint metadata.
Protocol and drift diagnostics
docs/BRIDGE_PROTOCOL.md, docs/PRD-indigo-matter-export.md, indigo-matter.indigoPlugin/Contents/Server Plugin/export_bridge.py, tests/test_export_bridge.py
Documentation and diagnostics distinguish reset-induced adoption from unexplained drift.
Release and handover updates
docs/HANDOVER.md, indigo-matter.indigoPlugin/Contents/Info.plist
The handover records the implementation state, and the plugin version changes to 2026.8.9.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant BridgeNode
  participant MatterJS
  participant EndpointMapStore
  BridgeNode->>MatterJS: clear endpoint allocation during reset
  BridgeNode->>EndpointMapStore: mark persisted numbers as void
  MatterJS->>BridgeNode: recreate endpoints with new numbers
  BridgeNode->>EndpointMapStore: reconcile live endpoints
  EndpointMapStore->>EndpointMapStore: adopt numbers and clear markers
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly identifies the bridge-node fix: adopting endpoint renumbering caused by a factory reset.
Linked Issues check ✅ Passed The PR implements issue #140 option 3 for both reset paths and preserves drift reporting for non-void entries.
Out of Scope Changes check ✅ Passed The code, tests, logging, and documentation changes support issue #140; no unrelated code changes are evident.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/140-adopt-reset-renumbering

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@bridge-node/src/node.ts`:
- Around line 665-668: Update the last-fabric handling around the commissioning
witness guard so the `#endpointMap.voidNumbers()` call and `#drift` reset
execute even when `identity.commissionedAt` is undefined. Preserve the existing
witness-clear guard for clearing commissioning data, but move only the
endpoint-number invalidation and drift reset after that guard and before
returning from the transition.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: f2633922-74f5-4152-8e1a-6371ba7fd65f

📥 Commits

Reviewing files that changed from the base of the PR and between 298ee6e and cbaa20a.

📒 Files selected for processing (10)
  • bridge-node/src/endpoint-map.ts
  • bridge-node/src/node.ts
  • bridge-node/test/endpoint-map.test.ts
  • bridge-node/test/persistence.test.ts
  • docs/BRIDGE_PROTOCOL.md
  • docs/HANDOVER.md
  • docs/PRD-indigo-matter-export.md
  • indigo-matter.indigoPlugin/Contents/Info.plist
  • indigo-matter.indigoPlugin/Contents/Server Plugin/export_bridge.py
  • tests/test_export_bridge.py

Comment thread bridge-node/src/node.ts Outdated
… a void baseline (#140)

The review pass found the two claims-around-the-mechanism defects:

- factoryReset discarded voidNumbers()'s return and unconditionally
  logged "…numbers are now VOID… §3.11 rebuild is not needed" — over a
  write that may have failed, leaving RAM-only markers that a restart
  loses, bringing #140's forever-drift back after the user was told
  otherwise. Same bug class the same function fixed for discard().
  Both call sites now capture the result and branch the log; the
  empty-map no-op (also false) is resolved by gating on size > 0 —
  nothing to void is not a failure, and a bridge that never exported
  gets neither line. persist() already leaves #dirty set, so the retry
  is automatic and the failure line says so.

- voidNumbers left #checked true, so between the reset and the first
  reconcile StatusReport claimed driftChecked: true ("checked, nothing
  moved" per §4.3) over a baseline the node itself just declared void —
  the exact over-claim the #dirty doc comment exists to prevent. Every
  sibling baseline-invalidating op recomputes it; now this one does
  too, and the plugin's "not checked yet" note becomes the honest
  post-reset rendering.

Also: class comment bounds the marker's lifetime honestly (only LIVE
entries adopt; a device offline through the first reconcile keeps its
marker until it next exports — still safe, the argument anchors on "no
fabric survived the reset", not "at the moment of adoption");
BRIDGE_PROTOCOL's three #140 passages now say "since bridge-node
0.8.0" per the doc's own convention, matching the plugin's message.

Three new tests (399 TS): checked-drops-on-void,
void-write-failure-reports-not-a-no-op, and the factoryReset
truth-telling branch under the suite's chmod fault-injection pattern.
Mutation-verified both important fixes fail-before/pass-after.

Suites: 2265 Python, 399 TS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018Unpt5UPRdLoZkahH6a4gL
@simons-plugins
simons-plugins merged commit 650cc60 into main Aug 8, 2026
3 checks passed
@simons-plugins
simons-plugins deleted the fix/140-adopt-reset-renumbering branch August 8, 2026 22:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

No way to clear a drift report on a healthy node — Rebuild is gated on the refusal state

1 participant