@camstack/addon-provider-velux 0.2.73 → 0.2.74

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
@@ -33468,6 +33468,53 @@ var BaseDeviceProvider = class extends BaseAddon {
33468
33468
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
33469
33469
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
33470
33470
  }
33471
+ /**
33472
+ * A top-level persisted row with NO configuration is a leftover, not a
33473
+ * device — quarantine it instead of dialling it.
33474
+ *
33475
+ * Device 614 was deleted, resurrected as a config-less shell by a write
33476
+ * already in flight (D376), and then dialled at every single boot: eight
33477
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
33478
+ * each one the same Zod refusal on `host` and `password`. The bound was
33479
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
33480
+ * about the row ever stopped costing attempts and log lines.
33481
+ *
33482
+ * No retry can add a configuration that is not there. The row is reported
33483
+ * once, with its `deviceId`, and set aside.
33484
+ *
33485
+ * Top-level only: an accessory child legitimately carries an empty blob (it
33486
+ * lives off its parent), and a hub-adopted child with an emptied blob is
33487
+ * repaired by `healSavedConfig` from its parent instead. Providers that
33488
+ * genuinely create configuration-free top-level devices turn this off.
33489
+ */
33490
+ quarantineConfiglessTopLevelRestores = true;
33491
+ isUnrestorableShell(saved) {
33492
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
33493
+ if (saved.parentDeviceId !== null) return false;
33494
+ const config = saved.config;
33495
+ if (config === void 0) return false;
33496
+ return Object.keys(config).length === 0;
33497
+ }
33498
+ quarantineShell(saved) {
33499
+ this._permanentRestoreFailures.set(saved.id, {
33500
+ deviceId: saved.id,
33501
+ stableId: saved.stableId,
33502
+ type: saved.type,
33503
+ attempts: 0,
33504
+ lastError: "persisted row has no configuration",
33505
+ failedAt: Date.now()
33506
+ });
33507
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
33508
+ tags: {
33509
+ deviceId: saved.id,
33510
+ stableId: saved.stableId
33511
+ },
33512
+ meta: {
33513
+ type: saved.type,
33514
+ provider: this.providerName
33515
+ }
33516
+ });
33517
+ }
33471
33518
  cancelRestoreRetries() {
33472
33519
  this._restoreRetryScheduler?.cancel();
33473
33520
  this._restoreRetryScheduler = null;
@@ -33652,6 +33699,10 @@ var BaseDeviceProvider = class extends BaseAddon {
33652
33699
  });
33653
33700
  return;
33654
33701
  }
33702
+ if (this.isUnrestorableShell(saved)) {
33703
+ this.quarantineShell(saved);
33704
+ return;
33705
+ }
33655
33706
  try {
33656
33707
  await attemptRestore(saved);
33657
33708
  } catch (err) {
package/dist/addon.mjs CHANGED
@@ -33467,6 +33467,53 @@ var BaseDeviceProvider = class extends BaseAddon {
33467
33467
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
33468
33468
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
33469
33469
  }
33470
+ /**
33471
+ * A top-level persisted row with NO configuration is a leftover, not a
33472
+ * device — quarantine it instead of dialling it.
33473
+ *
33474
+ * Device 614 was deleted, resurrected as a config-less shell by a write
33475
+ * already in flight (D376), and then dialled at every single boot: eight
33476
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
33477
+ * each one the same Zod refusal on `host` and `password`. The bound was
33478
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
33479
+ * about the row ever stopped costing attempts and log lines.
33480
+ *
33481
+ * No retry can add a configuration that is not there. The row is reported
33482
+ * once, with its `deviceId`, and set aside.
33483
+ *
33484
+ * Top-level only: an accessory child legitimately carries an empty blob (it
33485
+ * lives off its parent), and a hub-adopted child with an emptied blob is
33486
+ * repaired by `healSavedConfig` from its parent instead. Providers that
33487
+ * genuinely create configuration-free top-level devices turn this off.
33488
+ */
33489
+ quarantineConfiglessTopLevelRestores = true;
33490
+ isUnrestorableShell(saved) {
33491
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
33492
+ if (saved.parentDeviceId !== null) return false;
33493
+ const config = saved.config;
33494
+ if (config === void 0) return false;
33495
+ return Object.keys(config).length === 0;
33496
+ }
33497
+ quarantineShell(saved) {
33498
+ this._permanentRestoreFailures.set(saved.id, {
33499
+ deviceId: saved.id,
33500
+ stableId: saved.stableId,
33501
+ type: saved.type,
33502
+ attempts: 0,
33503
+ lastError: "persisted row has no configuration",
33504
+ failedAt: Date.now()
33505
+ });
33506
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
33507
+ tags: {
33508
+ deviceId: saved.id,
33509
+ stableId: saved.stableId
33510
+ },
33511
+ meta: {
33512
+ type: saved.type,
33513
+ provider: this.providerName
33514
+ }
33515
+ });
33516
+ }
33470
33517
  cancelRestoreRetries() {
33471
33518
  this._restoreRetryScheduler?.cancel();
33472
33519
  this._restoreRetryScheduler = null;
@@ -33651,6 +33698,10 @@ var BaseDeviceProvider = class extends BaseAddon {
33651
33698
  });
33652
33699
  return;
33653
33700
  }
33701
+ if (this.isUnrestorableShell(saved)) {
33702
+ this.quarantineShell(saved);
33703
+ return;
33704
+ }
33654
33705
  try {
33655
33706
  await attemptRestore(saved);
33656
33707
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-velux",
3
- "version": "0.2.73",
3
+ "version": "0.2.74",
4
4
  "description": "Velux KLF-200 device-provider addon for CamStack — local-gateway window + shutter covers (positional, no tilt)",
5
5
  "keywords": [
6
6
  "camstack",