@camstack/addon-provider-unifi 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
@@ -33485,6 +33485,53 @@ var BaseDeviceProvider = class extends BaseAddon {
33485
33485
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
33486
33486
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
33487
33487
  }
33488
+ /**
33489
+ * A top-level persisted row with NO configuration is a leftover, not a
33490
+ * device — quarantine it instead of dialling it.
33491
+ *
33492
+ * Device 614 was deleted, resurrected as a config-less shell by a write
33493
+ * already in flight (D376), and then dialled at every single boot: eight
33494
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
33495
+ * each one the same Zod refusal on `host` and `password`. The bound was
33496
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
33497
+ * about the row ever stopped costing attempts and log lines.
33498
+ *
33499
+ * No retry can add a configuration that is not there. The row is reported
33500
+ * once, with its `deviceId`, and set aside.
33501
+ *
33502
+ * Top-level only: an accessory child legitimately carries an empty blob (it
33503
+ * lives off its parent), and a hub-adopted child with an emptied blob is
33504
+ * repaired by `healSavedConfig` from its parent instead. Providers that
33505
+ * genuinely create configuration-free top-level devices turn this off.
33506
+ */
33507
+ quarantineConfiglessTopLevelRestores = true;
33508
+ isUnrestorableShell(saved) {
33509
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
33510
+ if (saved.parentDeviceId !== null) return false;
33511
+ const config = saved.config;
33512
+ if (config === void 0) return false;
33513
+ return Object.keys(config).length === 0;
33514
+ }
33515
+ quarantineShell(saved) {
33516
+ this._permanentRestoreFailures.set(saved.id, {
33517
+ deviceId: saved.id,
33518
+ stableId: saved.stableId,
33519
+ type: saved.type,
33520
+ attempts: 0,
33521
+ lastError: "persisted row has no configuration",
33522
+ failedAt: Date.now()
33523
+ });
33524
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
33525
+ tags: {
33526
+ deviceId: saved.id,
33527
+ stableId: saved.stableId
33528
+ },
33529
+ meta: {
33530
+ type: saved.type,
33531
+ provider: this.providerName
33532
+ }
33533
+ });
33534
+ }
33488
33535
  cancelRestoreRetries() {
33489
33536
  this._restoreRetryScheduler?.cancel();
33490
33537
  this._restoreRetryScheduler = null;
@@ -33669,6 +33716,10 @@ var BaseDeviceProvider = class extends BaseAddon {
33669
33716
  });
33670
33717
  return;
33671
33718
  }
33719
+ if (this.isUnrestorableShell(saved)) {
33720
+ this.quarantineShell(saved);
33721
+ return;
33722
+ }
33672
33723
  try {
33673
33724
  await attemptRestore(saved);
33674
33725
  } catch (err) {
package/dist/addon.mjs CHANGED
@@ -33484,6 +33484,53 @@ var BaseDeviceProvider = class extends BaseAddon {
33484
33484
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
33485
33485
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
33486
33486
  }
33487
+ /**
33488
+ * A top-level persisted row with NO configuration is a leftover, not a
33489
+ * device — quarantine it instead of dialling it.
33490
+ *
33491
+ * Device 614 was deleted, resurrected as a config-less shell by a write
33492
+ * already in flight (D376), and then dialled at every single boot: eight
33493
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
33494
+ * each one the same Zod refusal on `host` and `password`. The bound was
33495
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
33496
+ * about the row ever stopped costing attempts and log lines.
33497
+ *
33498
+ * No retry can add a configuration that is not there. The row is reported
33499
+ * once, with its `deviceId`, and set aside.
33500
+ *
33501
+ * Top-level only: an accessory child legitimately carries an empty blob (it
33502
+ * lives off its parent), and a hub-adopted child with an emptied blob is
33503
+ * repaired by `healSavedConfig` from its parent instead. Providers that
33504
+ * genuinely create configuration-free top-level devices turn this off.
33505
+ */
33506
+ quarantineConfiglessTopLevelRestores = true;
33507
+ isUnrestorableShell(saved) {
33508
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
33509
+ if (saved.parentDeviceId !== null) return false;
33510
+ const config = saved.config;
33511
+ if (config === void 0) return false;
33512
+ return Object.keys(config).length === 0;
33513
+ }
33514
+ quarantineShell(saved) {
33515
+ this._permanentRestoreFailures.set(saved.id, {
33516
+ deviceId: saved.id,
33517
+ stableId: saved.stableId,
33518
+ type: saved.type,
33519
+ attempts: 0,
33520
+ lastError: "persisted row has no configuration",
33521
+ failedAt: Date.now()
33522
+ });
33523
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
33524
+ tags: {
33525
+ deviceId: saved.id,
33526
+ stableId: saved.stableId
33527
+ },
33528
+ meta: {
33529
+ type: saved.type,
33530
+ provider: this.providerName
33531
+ }
33532
+ });
33533
+ }
33487
33534
  cancelRestoreRetries() {
33488
33535
  this._restoreRetryScheduler?.cancel();
33489
33536
  this._restoreRetryScheduler = null;
@@ -33668,6 +33715,10 @@ var BaseDeviceProvider = class extends BaseAddon {
33668
33715
  });
33669
33716
  return;
33670
33717
  }
33718
+ if (this.isUnrestorableShell(saved)) {
33719
+ this.quarantineShell(saved);
33720
+ return;
33721
+ }
33671
33722
  try {
33672
33723
  await attemptRestore(saved);
33673
33724
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-unifi",
3
- "version": "0.2.73",
3
+ "version": "0.2.75",
4
4
  "description": "UniFi Network controller device-provider addon for CamStack — local-controller infra switches/APs (as containers) + network-client presence. NO cameras/Protect.",
5
5
  "keywords": [
6
6
  "camstack",