@camstack/addon-provider-dreo 0.2.73 → 0.2.75

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.
package/dist/addon.js CHANGED
@@ -33529,6 +33529,53 @@ var BaseDeviceProvider = class extends BaseAddon {
33529
33529
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
33530
33530
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
33531
33531
  }
33532
+ /**
33533
+ * A top-level persisted row with NO configuration is a leftover, not a
33534
+ * device — quarantine it instead of dialling it.
33535
+ *
33536
+ * Device 614 was deleted, resurrected as a config-less shell by a write
33537
+ * already in flight (D376), and then dialled at every single boot: eight
33538
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
33539
+ * each one the same Zod refusal on `host` and `password`. The bound was
33540
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
33541
+ * about the row ever stopped costing attempts and log lines.
33542
+ *
33543
+ * No retry can add a configuration that is not there. The row is reported
33544
+ * once, with its `deviceId`, and set aside.
33545
+ *
33546
+ * Top-level only: an accessory child legitimately carries an empty blob (it
33547
+ * lives off its parent), and a hub-adopted child with an emptied blob is
33548
+ * repaired by `healSavedConfig` from its parent instead. Providers that
33549
+ * genuinely create configuration-free top-level devices turn this off.
33550
+ */
33551
+ quarantineConfiglessTopLevelRestores = true;
33552
+ isUnrestorableShell(saved) {
33553
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
33554
+ if (saved.parentDeviceId !== null) return false;
33555
+ const config = saved.config;
33556
+ if (config === void 0) return false;
33557
+ return Object.keys(config).length === 0;
33558
+ }
33559
+ quarantineShell(saved) {
33560
+ this._permanentRestoreFailures.set(saved.id, {
33561
+ deviceId: saved.id,
33562
+ stableId: saved.stableId,
33563
+ type: saved.type,
33564
+ attempts: 0,
33565
+ lastError: "persisted row has no configuration",
33566
+ failedAt: Date.now()
33567
+ });
33568
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
33569
+ tags: {
33570
+ deviceId: saved.id,
33571
+ stableId: saved.stableId
33572
+ },
33573
+ meta: {
33574
+ type: saved.type,
33575
+ provider: this.providerName
33576
+ }
33577
+ });
33578
+ }
33532
33579
  cancelRestoreRetries() {
33533
33580
  this._restoreRetryScheduler?.cancel();
33534
33581
  this._restoreRetryScheduler = null;
@@ -33713,6 +33760,10 @@ var BaseDeviceProvider = class extends BaseAddon {
33713
33760
  });
33714
33761
  return;
33715
33762
  }
33763
+ if (this.isUnrestorableShell(saved)) {
33764
+ this.quarantineShell(saved);
33765
+ return;
33766
+ }
33716
33767
  try {
33717
33768
  await attemptRestore(saved);
33718
33769
  } catch (err) {
package/dist/addon.mjs CHANGED
@@ -33530,6 +33530,53 @@ var BaseDeviceProvider = class extends BaseAddon {
33530
33530
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
33531
33531
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
33532
33532
  }
33533
+ /**
33534
+ * A top-level persisted row with NO configuration is a leftover, not a
33535
+ * device — quarantine it instead of dialling it.
33536
+ *
33537
+ * Device 614 was deleted, resurrected as a config-less shell by a write
33538
+ * already in flight (D376), and then dialled at every single boot: eight
33539
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
33540
+ * each one the same Zod refusal on `host` and `password`. The bound was
33541
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
33542
+ * about the row ever stopped costing attempts and log lines.
33543
+ *
33544
+ * No retry can add a configuration that is not there. The row is reported
33545
+ * once, with its `deviceId`, and set aside.
33546
+ *
33547
+ * Top-level only: an accessory child legitimately carries an empty blob (it
33548
+ * lives off its parent), and a hub-adopted child with an emptied blob is
33549
+ * repaired by `healSavedConfig` from its parent instead. Providers that
33550
+ * genuinely create configuration-free top-level devices turn this off.
33551
+ */
33552
+ quarantineConfiglessTopLevelRestores = true;
33553
+ isUnrestorableShell(saved) {
33554
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
33555
+ if (saved.parentDeviceId !== null) return false;
33556
+ const config = saved.config;
33557
+ if (config === void 0) return false;
33558
+ return Object.keys(config).length === 0;
33559
+ }
33560
+ quarantineShell(saved) {
33561
+ this._permanentRestoreFailures.set(saved.id, {
33562
+ deviceId: saved.id,
33563
+ stableId: saved.stableId,
33564
+ type: saved.type,
33565
+ attempts: 0,
33566
+ lastError: "persisted row has no configuration",
33567
+ failedAt: Date.now()
33568
+ });
33569
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
33570
+ tags: {
33571
+ deviceId: saved.id,
33572
+ stableId: saved.stableId
33573
+ },
33574
+ meta: {
33575
+ type: saved.type,
33576
+ provider: this.providerName
33577
+ }
33578
+ });
33579
+ }
33533
33580
  cancelRestoreRetries() {
33534
33581
  this._restoreRetryScheduler?.cancel();
33535
33582
  this._restoreRetryScheduler = null;
@@ -33714,6 +33761,10 @@ var BaseDeviceProvider = class extends BaseAddon {
33714
33761
  });
33715
33762
  return;
33716
33763
  }
33764
+ if (this.isUnrestorableShell(saved)) {
33765
+ this.quarantineShell(saved);
33766
+ return;
33767
+ }
33717
33768
  try {
33718
33769
  await attemptRestore(saved);
33719
33770
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-dreo",
3
- "version": "0.2.73",
3
+ "version": "0.2.75",
4
4
  "description": "Dreo smart-device (fan / air-circulator / purifier / heater / humidifier) device-provider addon for CamStack — wraps the @apocaliss92/nodedreo Dreo cloud client (REST + WebSocket)",
5
5
  "keywords": [
6
6
  "camstack",