@camstack/addon-matter-broker 0.2.96 → 0.2.98

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +151 -22
  2. package/dist/addon.mjs +151 -22
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -7317,6 +7317,23 @@ function errMsg(err) {
7317
7317
  if (typeof err === "string") return err;
7318
7318
  return String(err);
7319
7319
  }
7320
+ new Set([
7321
+ "track",
7322
+ "summary",
7323
+ "face",
7324
+ "identity",
7325
+ "plate",
7326
+ "vehicle",
7327
+ "scene",
7328
+ "motion",
7329
+ "object",
7330
+ "audio"
7331
+ ]);
7332
+ new Set([
7333
+ "motion",
7334
+ "object",
7335
+ "audio"
7336
+ ]);
7320
7337
  var EncodeProfileSchema = object({
7321
7338
  video: object({
7322
7339
  codec: _enum([
@@ -12566,8 +12583,17 @@ method(object({
12566
12583
  }), array(SettingsRecordSchema).readonly()), method(object({
12567
12584
  namespace: string$2().optional(),
12568
12585
  collection: string$2(),
12569
- record: SettingsRecordSchema
12570
- }), _void(), { kind: "mutation" }), method(object({
12586
+ record: object({
12587
+ id: string$2().optional(),
12588
+ data: record(string$2(), unknown())
12589
+ })
12590
+ }), object({
12591
+ /**
12592
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12593
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12594
+ * primary key — which is the only place an auto key is knowable.
12595
+ */
12596
+ id: union([string$2(), number()]) }), { kind: "mutation" }), method(object({
12571
12597
  namespace: string$2().optional(),
12572
12598
  collection: string$2(),
12573
12599
  records: array(BulkRecordSchema).readonly()
@@ -12676,8 +12702,17 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
12676
12702
  }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
12677
12703
  namespace: string$2().optional(),
12678
12704
  collection: string$2(),
12679
- record: SettingsRecordSchema
12680
- }), _void(), {
12705
+ record: object({
12706
+ id: string$2().optional(),
12707
+ data: record(string$2(), unknown())
12708
+ })
12709
+ }), object({
12710
+ /**
12711
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12712
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12713
+ * primary key — which is the only place an auto key is knowable.
12714
+ */
12715
+ id: union([string$2(), number()]) }), {
12681
12716
  kind: "mutation",
12682
12717
  auth: "admin"
12683
12718
  }), method(object({
@@ -19602,7 +19637,20 @@ var TrackSchema = object({
19602
19637
  ...TrackRetrainFields
19603
19638
  });
19604
19639
  var BaseEventFields = {
19605
- id: string$2(),
19640
+ /**
19641
+ * A SQLite ROWID, assigned by the database (D474).
19642
+ *
19643
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
19644
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
19645
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
19646
+ * the rowid: the table itself is that B-tree, so the index stops existing
19647
+ * rather than getting smaller. No shorter string does that.
19648
+ *
19649
+ * Defined once here for all three event kinds, which is why they move
19650
+ * together: a per-table migration would have forked this and
19651
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
19652
+ */
19653
+ id: number().int(),
19606
19654
  deviceId: number(),
19607
19655
  timestamp: number()
19608
19656
  };
@@ -19621,7 +19669,34 @@ var MotionEventSchema = object({
19621
19669
  /** Omitted in slim projection. */
19622
19670
  frameHeight: number().optional(),
19623
19671
  /** Populated by B5 (recording playback URL for this event). */
19624
- mediaUrl: string$2().optional()
19672
+ mediaUrl: string$2().optional(),
19673
+ /**
19674
+ * One row per motion EPISODE, not one per push (D475). `null` while the
19675
+ * episode is still open — a further rising edge extends it in place rather
19676
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
19677
+ * (the span from the first rising edge to the LAST one, deliberately NOT
19678
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
19679
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
19680
+ * motion for an instantaneous trigger).
19681
+ *
19682
+ * **Absent** (not merely `null`) on a row written before D475 — that means
19683
+ * "closed the old way, before this column existed", never "still open".
19684
+ * Nothing in this codebase may read an absent `durationMs` as an open
19685
+ * episode; only `null` means open.
19686
+ */
19687
+ durationMs: number().nullable().optional(),
19688
+ /**
19689
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
19690
+ * first entry is always `0`) of every genuine off→on transition the
19691
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
19692
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
19693
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
19694
+ * explicit `false` mid-episode and then resumes before the quiet window
19695
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
19696
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
19697
+ * which must never be read as "no episode happened here".
19698
+ */
19699
+ edges: array(number()).readonly().optional()
19625
19700
  });
19626
19701
  /**
19627
19702
  * Which detection SOURCE produced an object event. `pipeline` = the ML
@@ -19676,6 +19751,23 @@ var ObjectEventSchema = object({
19676
19751
  * includes it (it is light). Absent on rows written before this field.
19677
19752
  */
19678
19753
  frameId: string$2().optional(),
19754
+ /**
19755
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
19756
+ * (D474).
19757
+ *
19758
+ * Only the package detector writes it, and it exists because the event id
19759
+ * stopped being choosable: the delivery and pick-up rows used to BE their
19760
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
19761
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
19762
+ * assigned by SQLite, so that key had to move off the primary key rather
19763
+ * than be dropped — a detector that cannot recognise its own row re-delivers
19764
+ * every parcel on every boot.
19765
+ *
19766
+ * Absent on every other object event, and on every row written before this
19767
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
19768
+ * not per row, and nothing addresses a row by it.
19769
+ */
19770
+ idempotencyKey: string$2().optional(),
19679
19771
  /** Omitted in slim projection. */
19680
19772
  trackId: string$2().optional(),
19681
19773
  className: string$2(),
@@ -27942,26 +28034,59 @@ authKey: string$2().optional() }), object({
27942
28034
  /** Human-readable error when `ok: false`. */
27943
28035
  error: string$2().optional()
27944
28036
  }), { kind: "mutation" });
27945
- /**
27946
- * Hardware / firmware motion sensor cap — binary detected state plus
27947
- * a timestamp of the last observation. Distinct from
27948
- * `motion-detection.cap.ts` which owns the LOCAL ML motion pipeline;
27949
- * `motion` is the lightweight readout from on-camera motion (Reolink
27950
- * `GetMdState`, Baichuan push `type: motion`, ONVIF analytics).
27951
- *
27952
- * Native-motion providers also fan out to `detection.camera-native`
27953
- * with `source: 'onboard'` so cross-cutting system services
27954
- * (alert-center, advanced-notifier) can subscribe once and receive
27955
- * motion from every camera.
27956
- */
27957
28037
  var MotionStatusSchema = object({
27958
28038
  detected: boolean(),
27959
28039
  /** Ms epoch of the last detected-true observation. Null if never detected. */
27960
28040
  lastDetectedAt: number().nullable(),
27961
28041
  /**
27962
- * Ms after which `detected` auto-reverts to false if no fresh push
27963
- * arrives. Null means the provider leaves detected state until a
27964
- * native "clear" event.
28042
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
28043
+ * `null` while false and on every `Sensor` device (D475) — see that
28044
+ * constant's doc for the one-authority rule.
28045
+ *
28046
+ * ## Reading this field still arms nothing
28047
+ *
28048
+ * It reads like an instruction to the consumer ("revert after N ms if
28049
+ * no fresh push arrives") and it is not one: nothing in this repo reads
28050
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
28051
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
28052
+ * imported constant, not as a read of `device.state.motion.value`, so this
28053
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
28054
+ * computed elsewhere. Building a self-clear timer out of a READ of this
28055
+ * field would add a second falling-edge authority beside whichever one
28056
+ * already owns the device, and two that can disagree are worse than one.
28057
+ * Consumers that need a falling edge SHAPED differently — held open across
28058
+ * a flapping source — debounce on their own side and say so, as
28059
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
28060
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
28061
+ *
28062
+ * ## Who writes it
28063
+ *
28064
+ * - **Cameras** — the runner's phase machine, `active → watching` on
28065
+ * `cooldown_expired`, which then writes this slice with
28066
+ * `detected: false` (`handlePhaseChanged` in
28067
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
28068
+ * matters most for the sources that only ever push a rising one:
28069
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
28070
+ * a false.
28071
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
28072
+ * provider pushes the false itself, from the upstream system's own
28073
+ * state change. No phase machine is involved.
28074
+ *
28075
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
28076
+ *
28077
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
28078
+ * phase machine is the sole writer for a camera. It is not.
28079
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
28080
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
28081
+ * Hikvision's comment says why: it read THIS docblock, agreed the
28082
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
28083
+ * two authorities can disagree about one slice, which this repo
28084
+ * forbids, and the doc said otherwise — which is worse than saying
28085
+ * nothing, because it reads as verification.
28086
+ *
28087
+ * This predates D475 and is not fixed there: the fix touches every
28088
+ * camera provider. Recorded in D475's Consequences. Do not restore the
28089
+ * "sole writer" wording without also removing the other writers.
27965
28090
  */
27966
28091
  autoClearAfterMs: number().nullable()
27967
28092
  });
@@ -28031,7 +28156,11 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
28031
28156
  */
28032
28157
  runtimeState: MotionStatusSchema,
28033
28158
  /**
28034
- * Runtime-state durability: **session** — self-clearing by construction (`autoClearAfterMs`); a restored `detected: true` is a frozen event, and the next frame re-publishes the real one.
28159
+ * Runtime-state durability: **session** — every writer of this slice
28160
+ * writes only on an EDGE, so a restored `detected: true` would stay
28161
+ * frozen until the next one instead of being corrected. The next edge
28162
+ * re-publishes the real state. (On who the writers are, and why there
28163
+ * is more than one, see `autoClearAfterMs` above.)
28035
28164
  *
28036
28165
  * See `RuntimeStateDurability`. Enforced by
28037
28166
  * `scripts/check-runtime-state-durability.ts`.
package/dist/addon.mjs CHANGED
@@ -7315,6 +7315,23 @@ function errMsg(err) {
7315
7315
  if (typeof err === "string") return err;
7316
7316
  return String(err);
7317
7317
  }
7318
+ new Set([
7319
+ "track",
7320
+ "summary",
7321
+ "face",
7322
+ "identity",
7323
+ "plate",
7324
+ "vehicle",
7325
+ "scene",
7326
+ "motion",
7327
+ "object",
7328
+ "audio"
7329
+ ]);
7330
+ new Set([
7331
+ "motion",
7332
+ "object",
7333
+ "audio"
7334
+ ]);
7318
7335
  var EncodeProfileSchema = object({
7319
7336
  video: object({
7320
7337
  codec: _enum([
@@ -12564,8 +12581,17 @@ method(object({
12564
12581
  }), array(SettingsRecordSchema).readonly()), method(object({
12565
12582
  namespace: string$2().optional(),
12566
12583
  collection: string$2(),
12567
- record: SettingsRecordSchema
12568
- }), _void(), { kind: "mutation" }), method(object({
12584
+ record: object({
12585
+ id: string$2().optional(),
12586
+ data: record(string$2(), unknown())
12587
+ })
12588
+ }), object({
12589
+ /**
12590
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12591
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12592
+ * primary key — which is the only place an auto key is knowable.
12593
+ */
12594
+ id: union([string$2(), number()]) }), { kind: "mutation" }), method(object({
12569
12595
  namespace: string$2().optional(),
12570
12596
  collection: string$2(),
12571
12597
  records: array(BulkRecordSchema).readonly()
@@ -12674,8 +12700,17 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
12674
12700
  }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
12675
12701
  namespace: string$2().optional(),
12676
12702
  collection: string$2(),
12677
- record: SettingsRecordSchema
12678
- }), _void(), {
12703
+ record: object({
12704
+ id: string$2().optional(),
12705
+ data: record(string$2(), unknown())
12706
+ })
12707
+ }), object({
12708
+ /**
12709
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
12710
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
12711
+ * primary key — which is the only place an auto key is knowable.
12712
+ */
12713
+ id: union([string$2(), number()]) }), {
12679
12714
  kind: "mutation",
12680
12715
  auth: "admin"
12681
12716
  }), method(object({
@@ -19600,7 +19635,20 @@ var TrackSchema = object({
19600
19635
  ...TrackRetrainFields
19601
19636
  });
19602
19637
  var BaseEventFields = {
19603
- id: string$2(),
19638
+ /**
19639
+ * A SQLite ROWID, assigned by the database (D474).
19640
+ *
19641
+ * Was a 36-character UUID and cost 263 MB of a 1 117 MB database — paid
19642
+ * TWICE per row, in the row and in the primary-key index, across 2.1 million
19643
+ * motion, audio and object events. An `INTEGER PRIMARY KEY` in SQLite **is**
19644
+ * the rowid: the table itself is that B-tree, so the index stops existing
19645
+ * rather than getting smaller. No shorter string does that.
19646
+ *
19647
+ * Defined once here for all three event kinds, which is why they move
19648
+ * together: a per-table migration would have forked this and
19649
+ * `COMMON_BASE_COLUMNS` and reunited them two stages later.
19650
+ */
19651
+ id: number().int(),
19604
19652
  deviceId: number(),
19605
19653
  timestamp: number()
19606
19654
  };
@@ -19619,7 +19667,34 @@ var MotionEventSchema = object({
19619
19667
  /** Omitted in slim projection. */
19620
19668
  frameHeight: number().optional(),
19621
19669
  /** Populated by B5 (recording playback URL for this event). */
19622
- mediaUrl: string$2().optional()
19670
+ mediaUrl: string$2().optional(),
19671
+ /**
19672
+ * One row per motion EPISODE, not one per push (D475). `null` while the
19673
+ * episode is still open — a further rising edge extends it in place rather
19674
+ * than inserting a new row. Set once, at close, to `lastOnAt - startedAt`
19675
+ * (the span from the first rising edge to the LAST one, deliberately NOT
19676
+ * `closedAt - startedAt` — the close delay is a quiet CONFIRMATION, not
19677
+ * movement, and folding it in would report `MOTION_CLOSE_AFTER_MS` of
19678
+ * motion for an instantaneous trigger).
19679
+ *
19680
+ * **Absent** (not merely `null`) on a row written before D475 — that means
19681
+ * "closed the old way, before this column existed", never "still open".
19682
+ * Nothing in this codebase may read an absent `durationMs` as an open
19683
+ * episode; only `null` means open.
19684
+ */
19685
+ durationMs: number().nullable().optional(),
19686
+ /**
19687
+ * Ms offsets from `timestamp` (the episode's own first rising edge, so the
19688
+ * first entry is always `0`) of every genuine off→on transition the
19689
+ * episode saw — "ogni evento on si deve salvare" (D475). NOT one entry per
19690
+ * push: a firmware source that keepalives at ~1 Hz for the whole burst
19691
+ * (Reolink, Hikvision) produces exactly one edge; a source that reports an
19692
+ * explicit `false` mid-episode and then resumes before the quiet window
19693
+ * elapses produces another. Stored compactly — see `motion-edge-codec.ts`
19694
+ * — and decoded back to this shape on read. Absent/empty on a legacy row,
19695
+ * which must never be read as "no episode happened here".
19696
+ */
19697
+ edges: array(number()).readonly().optional()
19623
19698
  });
19624
19699
  /**
19625
19700
  * Which detection SOURCE produced an object event. `pipeline` = the ML
@@ -19674,6 +19749,23 @@ var ObjectEventSchema = object({
19674
19749
  * includes it (it is light). Absent on rows written before this field.
19675
19750
  */
19676
19751
  frameId: string$2().optional(),
19752
+ /**
19753
+ * A PRODUCER-chosen key that makes a synthetic event's emission idempotent
19754
+ * (D474).
19755
+ *
19756
+ * Only the package detector writes it, and it exists because the event id
19757
+ * stopped being choosable: the delivery and pick-up rows used to BE their
19758
+ * dedupe key (`pa-pkg-<entryId>-delivered`), which is how "never emit a
19759
+ * second delivery for this entry" survived a restart. An `INTEGER` rowid is
19760
+ * assigned by SQLite, so that key had to move off the primary key rather
19761
+ * than be dropped — a detector that cannot recognise its own row re-delivers
19762
+ * every parcel on every boot.
19763
+ *
19764
+ * Absent on every other object event, and on every row written before this
19765
+ * field. Never a substitute for `id`: it is unique per (producer, occasion),
19766
+ * not per row, and nothing addresses a row by it.
19767
+ */
19768
+ idempotencyKey: string$2().optional(),
19677
19769
  /** Omitted in slim projection. */
19678
19770
  trackId: string$2().optional(),
19679
19771
  className: string$2(),
@@ -27940,26 +28032,59 @@ authKey: string$2().optional() }), object({
27940
28032
  /** Human-readable error when `ok: false`. */
27941
28033
  error: string$2().optional()
27942
28034
  }), { kind: "mutation" });
27943
- /**
27944
- * Hardware / firmware motion sensor cap — binary detected state plus
27945
- * a timestamp of the last observation. Distinct from
27946
- * `motion-detection.cap.ts` which owns the LOCAL ML motion pipeline;
27947
- * `motion` is the lightweight readout from on-camera motion (Reolink
27948
- * `GetMdState`, Baichuan push `type: motion`, ONVIF analytics).
27949
- *
27950
- * Native-motion providers also fan out to `detection.camera-native`
27951
- * with `source: 'onboard'` so cross-cutting system services
27952
- * (alert-center, advanced-notifier) can subscribe once and receive
27953
- * motion from every camera.
27954
- */
27955
28035
  var MotionStatusSchema = object({
27956
28036
  detected: boolean(),
27957
28037
  /** Ms epoch of the last detected-true observation. Null if never detected. */
27958
28038
  lastDetectedAt: number().nullable(),
27959
28039
  /**
27960
- * Ms after which `detected` auto-reverts to false if no fresh push
27961
- * arrives. Null means the provider leaves detected state until a
27962
- * native "clear" event.
28040
+ * `MOTION_CLOSE_AFTER_MS` while `detected: true` on a `Camera` device,
28041
+ * `null` while false and on every `Sensor` device (D475) — see that
28042
+ * constant's doc for the one-authority rule.
28043
+ *
28044
+ * ## Reading this field still arms nothing
28045
+ *
28046
+ * It reads like an instruction to the consumer ("revert after N ms if
28047
+ * no fresh push arrives") and it is not one: nothing in this repo reads
28048
+ * the LIVE cap value to drive a timer. `pipeline-analytics`'s motion-episode
28049
+ * close DOES now use the same number — `MOTION_CLOSE_AFTER_MS` — but as an
28050
+ * imported constant, not as a read of `device.state.motion.value`, so this
28051
+ * field stays what it always was: DESCRIPTIVE output, mirroring an answer
28052
+ * computed elsewhere. Building a self-clear timer out of a READ of this
28053
+ * field would add a second falling-edge authority beside whichever one
28054
+ * already owns the device, and two that can disagree are worse than one.
28055
+ * Consumers that need a falling edge SHAPED differently — held open across
28056
+ * a flapping source — debounce on their own side and say so, as
28057
+ * `addon-export-alexa/src/motion-clear-hold.ts` and
28058
+ * `addon-export-hap`'s `RESET_DEBOUNCE_MS` both do.
28059
+ *
28060
+ * ## Who writes it
28061
+ *
28062
+ * - **Cameras** — the runner's phase machine, `active → watching` on
28063
+ * `cooldown_expired`, which then writes this slice with
28064
+ * `detected: false` (`handlePhaseChanged` in
28065
+ * `pipeline-runner/index.ts`). It produces the FALLING edge, which
28066
+ * matters most for the sources that only ever push a rising one:
28067
+ * Reolink emits `MotionOnMotionChanged { detected: true }` and never
28068
+ * a false.
28069
+ * - **Sensors** (Home Assistant binary sensors, Homematic) — the
28070
+ * provider pushes the false itself, from the upstream system's own
28071
+ * state change. No phase machine is involved.
28072
+ *
28073
+ * ### The phase machine is CANONICAL, not sole — and that is a defect
28074
+ *
28075
+ * An earlier revision of this docblock (mine, 2026-09-12) claimed the
28076
+ * phase machine is the sole writer for a camera. It is not.
28077
+ * `hikvision-camera.ts:3464` and `amcrest-camera.ts:445` both call
28078
+ * `setCapSlice(motionCapability, …)` on their own rising edge, and
28079
+ * Hikvision's comment says why: it read THIS docblock, agreed the
28080
+ * runner is canonical, and wrote anyway to avoid per-tick churn. So
28081
+ * two authorities can disagree about one slice, which this repo
28082
+ * forbids, and the doc said otherwise — which is worse than saying
28083
+ * nothing, because it reads as verification.
28084
+ *
28085
+ * This predates D475 and is not fixed there: the fix touches every
28086
+ * camera provider. Recorded in D475's Consequences. Do not restore the
28087
+ * "sole writer" wording without also removing the other writers.
27963
28088
  */
27964
28089
  autoClearAfterMs: number().nullable()
27965
28090
  });
@@ -28029,7 +28154,11 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
28029
28154
  */
28030
28155
  runtimeState: MotionStatusSchema,
28031
28156
  /**
28032
- * Runtime-state durability: **session** — self-clearing by construction (`autoClearAfterMs`); a restored `detected: true` is a frozen event, and the next frame re-publishes the real one.
28157
+ * Runtime-state durability: **session** — every writer of this slice
28158
+ * writes only on an EDGE, so a restored `detected: true` would stay
28159
+ * frozen until the next one instead of being corrected. The next edge
28160
+ * re-publishes the real state. (On who the writers are, and why there
28161
+ * is more than one, see `autoClearAfterMs` above.)
28033
28162
  *
28034
28163
  * See `RuntimeStateDurability`. Enforced by
28035
28164
  * `scripts/check-runtime-state-durability.ts`.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.2.96",
3
+ "version": "0.2.98",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",