@camstack/types 1.2.156 → 1.2.157

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.
@@ -190,6 +190,28 @@ export declare abstract class BaseDeviceProvider<TConfig extends object = Record
190
190
  /** One-line operator-facing summary for `getStatus().error`, or
191
191
  * `null` when every device restored. */
192
192
  protected restoreFailureSummary(): string | null;
193
+ /**
194
+ * A top-level persisted row with NO configuration is a leftover, not a
195
+ * device — quarantine it instead of dialling it.
196
+ *
197
+ * Device 614 was deleted, resurrected as a config-less shell by a write
198
+ * already in flight (D376), and then dialled at every single boot: eight
199
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
200
+ * each one the same Zod refusal on `host` and `password`. The bound was
201
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
202
+ * about the row ever stopped costing attempts and log lines.
203
+ *
204
+ * No retry can add a configuration that is not there. The row is reported
205
+ * once, with its `deviceId`, and set aside.
206
+ *
207
+ * Top-level only: an accessory child legitimately carries an empty blob (it
208
+ * lives off its parent), and a hub-adopted child with an emptied blob is
209
+ * repaired by `healSavedConfig` from its parent instead. Providers that
210
+ * genuinely create configuration-free top-level devices turn this off.
211
+ */
212
+ protected readonly quarantineConfiglessTopLevelRestores: boolean;
213
+ private isUnrestorableShell;
214
+ private quarantineShell;
193
215
  private cancelRestoreRetries;
194
216
  private recordPermanentRestoreFailure;
195
217
  private scheduleRestoreRetries;
package/dist/index.js CHANGED
@@ -37567,6 +37567,53 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
37567
37567
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
37568
37568
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
37569
37569
  }
37570
+ /**
37571
+ * A top-level persisted row with NO configuration is a leftover, not a
37572
+ * device — quarantine it instead of dialling it.
37573
+ *
37574
+ * Device 614 was deleted, resurrected as a config-less shell by a write
37575
+ * already in flight (D376), and then dialled at every single boot: eight
37576
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
37577
+ * each one the same Zod refusal on `host` and `password`. The bound was
37578
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
37579
+ * about the row ever stopped costing attempts and log lines.
37580
+ *
37581
+ * No retry can add a configuration that is not there. The row is reported
37582
+ * once, with its `deviceId`, and set aside.
37583
+ *
37584
+ * Top-level only: an accessory child legitimately carries an empty blob (it
37585
+ * lives off its parent), and a hub-adopted child with an emptied blob is
37586
+ * repaired by `healSavedConfig` from its parent instead. Providers that
37587
+ * genuinely create configuration-free top-level devices turn this off.
37588
+ */
37589
+ quarantineConfiglessTopLevelRestores = true;
37590
+ isUnrestorableShell(saved) {
37591
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
37592
+ if (saved.parentDeviceId !== null) return false;
37593
+ const config = saved.config;
37594
+ if (config === void 0) return false;
37595
+ return Object.keys(config).length === 0;
37596
+ }
37597
+ quarantineShell(saved) {
37598
+ this._permanentRestoreFailures.set(saved.id, {
37599
+ deviceId: saved.id,
37600
+ stableId: saved.stableId,
37601
+ type: saved.type,
37602
+ attempts: 0,
37603
+ lastError: "persisted row has no configuration",
37604
+ failedAt: Date.now()
37605
+ });
37606
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
37607
+ tags: {
37608
+ deviceId: saved.id,
37609
+ stableId: saved.stableId
37610
+ },
37611
+ meta: {
37612
+ type: saved.type,
37613
+ provider: this.providerName
37614
+ }
37615
+ });
37616
+ }
37570
37617
  cancelRestoreRetries() {
37571
37618
  this._restoreRetryScheduler?.cancel();
37572
37619
  this._restoreRetryScheduler = null;
@@ -37751,6 +37798,10 @@ var BaseDeviceProvider = class extends require_sleep.BaseAddon {
37751
37798
  });
37752
37799
  return;
37753
37800
  }
37801
+ if (this.isUnrestorableShell(saved)) {
37802
+ this.quarantineShell(saved);
37803
+ return;
37804
+ }
37754
37805
  try {
37755
37806
  await attemptRestore(saved);
37756
37807
  } catch (err) {
package/dist/index.mjs CHANGED
@@ -37559,6 +37559,53 @@ var BaseDeviceProvider = class extends BaseAddon {
37559
37559
  const ids = [...this._permanentRestoreFailures.keys()].join(", ");
37560
37560
  return `${this._permanentRestoreFailures.size} device(s) permanently failed restore (deviceIds: ${ids}) — restart the ${this.providerName} provider to retry`;
37561
37561
  }
37562
+ /**
37563
+ * A top-level persisted row with NO configuration is a leftover, not a
37564
+ * device — quarantine it instead of dialling it.
37565
+ *
37566
+ * Device 614 was deleted, resurrected as a config-less shell by a write
37567
+ * already in flight (D376), and then dialled at every single boot: eight
37568
+ * `Failed to restore device — bounded retry scheduled` lines in one day,
37569
+ * each one the same Zod refusal on `host` and `password`. The bound was
37570
+ * real, but it is a bound PER PROCESS, and the runner restarts — so nothing
37571
+ * about the row ever stopped costing attempts and log lines.
37572
+ *
37573
+ * No retry can add a configuration that is not there. The row is reported
37574
+ * once, with its `deviceId`, and set aside.
37575
+ *
37576
+ * Top-level only: an accessory child legitimately carries an empty blob (it
37577
+ * lives off its parent), and a hub-adopted child with an emptied blob is
37578
+ * repaired by `healSavedConfig` from its parent instead. Providers that
37579
+ * genuinely create configuration-free top-level devices turn this off.
37580
+ */
37581
+ quarantineConfiglessTopLevelRestores = true;
37582
+ isUnrestorableShell(saved) {
37583
+ if (!this.quarantineConfiglessTopLevelRestores) return false;
37584
+ if (saved.parentDeviceId !== null) return false;
37585
+ const config = saved.config;
37586
+ if (config === void 0) return false;
37587
+ return Object.keys(config).length === 0;
37588
+ }
37589
+ quarantineShell(saved) {
37590
+ this._permanentRestoreFailures.set(saved.id, {
37591
+ deviceId: saved.id,
37592
+ stableId: saved.stableId,
37593
+ type: saved.type,
37594
+ attempts: 0,
37595
+ lastError: "persisted row has no configuration",
37596
+ failedAt: Date.now()
37597
+ });
37598
+ this.ctx.logger.error("Persisted device row has NO configuration — quarantined: not restored, not retried. Remove it (deviceManager.removeDevice) or re-add the device.", {
37599
+ tags: {
37600
+ deviceId: saved.id,
37601
+ stableId: saved.stableId
37602
+ },
37603
+ meta: {
37604
+ type: saved.type,
37605
+ provider: this.providerName
37606
+ }
37607
+ });
37608
+ }
37562
37609
  cancelRestoreRetries() {
37563
37610
  this._restoreRetryScheduler?.cancel();
37564
37611
  this._restoreRetryScheduler = null;
@@ -37743,6 +37790,10 @@ var BaseDeviceProvider = class extends BaseAddon {
37743
37790
  });
37744
37791
  return;
37745
37792
  }
37793
+ if (this.isUnrestorableShell(saved)) {
37794
+ this.quarantineShell(saved);
37795
+ return;
37796
+ }
37746
37797
  try {
37747
37798
  await attemptRestore(saved);
37748
37799
  } catch (err) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/types",
3
- "version": "1.2.156",
3
+ "version": "1.2.157",
4
4
  "description": "Shared types, interfaces, and model catalogs for the CamStack detection ecosystem",
5
5
  "keywords": [
6
6
  "camstack",