@camstack/addon-provider-tuya 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
@@ -34313,6 +34313,53 @@ var BaseDeviceProvider = class extends BaseAddon {
34313
34313
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
34314
34314
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
34315
34315
  }
34316
+ /**
34317
+ * A top-level persisted row with NO configuration is a leftover, not a
34318
+ * device — quarantine it instead of dialling it.
34319
+ *
34320
+ * Device 614 was deleted, resurrected as a config-less shell by a write
34321
+ * already in flight (D376), and then dialled at every single boot: eight
34322
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
34323
+ * each one the same Zod refusal on `host` and `password`. The bound was
34324
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
34325
+ * about the row ever stopped costing attempts and log lines.
34326
+ *
34327
+ * No retry can add a configuration that is not there. The row is reported
34328
+ * once, with its `deviceId`, and set aside.
34329
+ *
34330
+ * Top-level only: an accessory child legitimately carries an empty blob (it
34331
+ * lives off its parent), and a hub-adopted child with an emptied blob is
34332
+ * repaired by `healSavedConfig` from its parent instead. Providers that
34333
+ * genuinely create configuration-free top-level devices turn this off.
34334
+ */
34335
+ quarantineConfiglessTopLevelRestores = true;
34336
+ isUnrestorableShell(saved) {
34337
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
34338
+ if (saved.parentDeviceId !== null) return false;
34339
+ const config = saved.config;
34340
+ if (config === void 0) return false;
34341
+ return Object.keys(config).length === 0;
34342
+ }
34343
+ quarantineShell(saved) {
34344
+ this._permanentRestoreFailures.set(saved.id, {
34345
+ deviceId: saved.id,
34346
+ stableId: saved.stableId,
34347
+ type: saved.type,
34348
+ attempts: 0,
34349
+ lastError: "persisted row has no configuration",
34350
+ failedAt: Date.now()
34351
+ });
34352
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
34353
+ tags: {
34354
+ deviceId: saved.id,
34355
+ stableId: saved.stableId
34356
+ },
34357
+ meta: {
34358
+ type: saved.type,
34359
+ provider: this.providerName
34360
+ }
34361
+ });
34362
+ }
34316
34363
  cancelRestoreRetries() {
34317
34364
  this._restoreRetryScheduler?.cancel();
34318
34365
  this._restoreRetryScheduler = null;
@@ -34497,6 +34544,10 @@ var BaseDeviceProvider = class extends BaseAddon {
34497
34544
  });
34498
34545
  return;
34499
34546
  }
34547
+ if (this.isUnrestorableShell(saved)) {
34548
+ this.quarantineShell(saved);
34549
+ return;
34550
+ }
34500
34551
  try {
34501
34552
  await attemptRestore(saved);
34502
34553
  } catch (err) {
package/dist/addon.mjs CHANGED
@@ -34312,6 +34312,53 @@ var BaseDeviceProvider = class extends BaseAddon {
34312
34312
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
34313
34313
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
34314
34314
  }
34315
+ /**
34316
+ * A top-level persisted row with NO configuration is a leftover, not a
34317
+ * device — quarantine it instead of dialling it.
34318
+ *
34319
+ * Device 614 was deleted, resurrected as a config-less shell by a write
34320
+ * already in flight (D376), and then dialled at every single boot: eight
34321
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
34322
+ * each one the same Zod refusal on `host` and `password`. The bound was
34323
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
34324
+ * about the row ever stopped costing attempts and log lines.
34325
+ *
34326
+ * No retry can add a configuration that is not there. The row is reported
34327
+ * once, with its `deviceId`, and set aside.
34328
+ *
34329
+ * Top-level only: an accessory child legitimately carries an empty blob (it
34330
+ * lives off its parent), and a hub-adopted child with an emptied blob is
34331
+ * repaired by `healSavedConfig` from its parent instead. Providers that
34332
+ * genuinely create configuration-free top-level devices turn this off.
34333
+ */
34334
+ quarantineConfiglessTopLevelRestores = true;
34335
+ isUnrestorableShell(saved) {
34336
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
34337
+ if (saved.parentDeviceId !== null) return false;
34338
+ const config = saved.config;
34339
+ if (config === void 0) return false;
34340
+ return Object.keys(config).length === 0;
34341
+ }
34342
+ quarantineShell(saved) {
34343
+ this._permanentRestoreFailures.set(saved.id, {
34344
+ deviceId: saved.id,
34345
+ stableId: saved.stableId,
34346
+ type: saved.type,
34347
+ attempts: 0,
34348
+ lastError: "persisted row has no configuration",
34349
+ failedAt: Date.now()
34350
+ });
34351
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
34352
+ tags: {
34353
+ deviceId: saved.id,
34354
+ stableId: saved.stableId
34355
+ },
34356
+ meta: {
34357
+ type: saved.type,
34358
+ provider: this.providerName
34359
+ }
34360
+ });
34361
+ }
34315
34362
  cancelRestoreRetries() {
34316
34363
  this._restoreRetryScheduler?.cancel();
34317
34364
  this._restoreRetryScheduler = null;
@@ -34496,6 +34543,10 @@ var BaseDeviceProvider = class extends BaseAddon {
34496
34543
  });
34497
34544
  return;
34498
34545
  }
34546
+ if (this.isUnrestorableShell(saved)) {
34547
+ this.quarantineShell(saved);
34548
+ return;
34549
+ }
34499
34550
  try {
34500
34551
  await attemptRestore(saved);
34501
34552
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-tuya",
3
- "version": "0.2.73",
3
+ "version": "0.2.75",
4
4
  "description": "Tuya / Smart Life device-provider addon for CamStack — account-onboarded (Tuya IoT cloud fetch of device localKeys) + LOCAL DP control via the @apocaliss92/nodetuya encrypted-LAN client, exposing switch / water-heater-family kettle entities",
5
5
  "keywords": [
6
6
  "camstack",