@camstack/addon-provider-tuya 0.2.125 → 0.2.127

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.
Files changed (3) hide show
  1. package/dist/addon.js +1404 -131
  2. package/dist/addon.mjs +1404 -131
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -819,7 +819,7 @@ async function discoverDevices(options = {}, binder = createDgramBinder()) {
819
819
  return [...byId.values()];
820
820
  }
821
821
  //#endregion
822
- //#region ../types/dist/event-category-C4hVnRo8.mjs
822
+ //#region ../types/dist/event-category-BvOFi6PY.mjs
823
823
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
824
824
  EventCategory["SystemBoot"] = "system.boot";
825
825
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -883,6 +883,24 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
883
883
  * consumes this category and maps it to the `addon-crash-loop` kind.
884
884
  */
885
885
  EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
886
+ /**
887
+ * A runner whose addon refused to start with a typed reason (D877) - parked
888
+ * in `refused`, never counted by the crash breaker, retried on operator
889
+ * action. One event per refusal.
890
+ */
891
+ EventCategory["AddonRunnerRefused"] = "addon.runner-refused";
892
+ /**
893
+ * A runner settled on a state (D877): `running` (READY), `crashed`, `failed` or
894
+ * `refused`. In-process telemetry for the boot check (re-evaluate while any
895
+ * runner is still pending) and the alert centre (close a recovered runner).
896
+ */
897
+ EventCategory["ProcessStateChanged"] = "process.state-changed";
898
+ /**
899
+ * The node's one-shot runner boot check (D877): planned runners against the
900
+ * process service, `degraded` when any is failed, refused or missing. The
901
+ * alert centre turns it into one alert and a direct critical push.
902
+ */
903
+ EventCategory["SystemRunnerCheck"] = "system.runner-check";
886
904
  EventCategory["AddonError"] = "addon.error";
887
905
  EventCategory["AddonPageReady"] = "addon.page-ready";
888
906
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -6207,7 +6225,7 @@ var ZodIssueCode = {
6207
6225
  var ZodFirstPartyTypeKind;
6208
6226
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6209
6227
  //#endregion
6210
- //#region ../types/dist/sleep-DRqxtc0C.mjs
6228
+ //#region ../types/dist/sleep-BegKjwUu.mjs
6211
6229
  /**
6212
6230
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6213
6231
  * window to float samples (D455).
@@ -6324,7 +6342,7 @@ function isKeychainStoredField(field) {
6324
6342
  var SECRET_CLEAR = { $secretClear: true };
6325
6343
  /** The `'***'` token D634 redaction writes for non-admins. */
6326
6344
  var LEGACY_DEVICE_REDACTION = "***";
6327
- function isRecord$2(value) {
6345
+ function isRecord$5(value) {
6328
6346
  return typeof value === "object" && value !== null && !Array.isArray(value);
6329
6347
  }
6330
6348
  function makeSecretMarker(ref) {
@@ -6335,12 +6353,12 @@ function makeSecretMarker(ref) {
6335
6353
  }
6336
6354
  /** A stored marker: an object whose `$secret` is a non-empty string. */
6337
6355
  function isSecretMarker(value) {
6338
- if (!isRecord$2(value)) return false;
6356
+ if (!isRecord$5(value)) return false;
6339
6357
  const ref = value["$secret"];
6340
6358
  return typeof ref === "string" && ref.length > 0;
6341
6359
  }
6342
6360
  function isSecretClear(value) {
6343
- return isRecord$2(value) && value["$secretClear"] === true;
6361
+ return isRecord$5(value) && value["$secretClear"] === true;
6344
6362
  }
6345
6363
  /**
6346
6364
  * The UI view of a stored secret field value: a marker becomes "set", inline
@@ -6950,7 +6968,7 @@ var KeychainWriteRefusedError = class extends Error {
6950
6968
  }
6951
6969
  };
6952
6970
  var KEEP = { kind: "keep" };
6953
- function isRecord$1(value) {
6971
+ function isRecord$4(value) {
6954
6972
  return typeof value === "object" && value !== null && !Array.isArray(value);
6955
6973
  }
6956
6974
  function defaultMintId() {
@@ -6963,7 +6981,7 @@ function collectSecretMarkerRefs(value, out = []) {
6963
6981
  return out;
6964
6982
  }
6965
6983
  if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
6966
- else if (isRecord$1(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6984
+ else if (isRecord$4(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6967
6985
  return out;
6968
6986
  }
6969
6987
  async function storedValueEquals(secrets, marker, candidate) {
@@ -7018,7 +7036,7 @@ function rowId(row, idKey) {
7018
7036
  return null;
7019
7037
  }
7020
7038
  async function writeArray(ctx, node, incoming, stored, fieldPath) {
7021
- const storedRows = Array.isArray(stored) ? stored.filter(isRecord$1) : [];
7039
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord$4) : [];
7022
7040
  if (!Array.isArray(incoming)) {
7023
7041
  ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
7024
7042
  return {
@@ -7034,7 +7052,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
7034
7052
  const kept = /* @__PURE__ */ new Set();
7035
7053
  const rows = [];
7036
7054
  for (const row of incoming) {
7037
- if (!isRecord$1(row)) {
7055
+ if (!isRecord$4(row)) {
7038
7056
  rows.push(row);
7039
7057
  continue;
7040
7058
  }
@@ -7059,7 +7077,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
7059
7077
  case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
7060
7078
  case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
7061
7079
  case "object": {
7062
- if (!isRecord$1(incoming)) {
7080
+ if (!isRecord$4(incoming)) {
7063
7081
  if (incoming === void 0) return KEEP;
7064
7082
  ctx.deletes.push(...collectSecretMarkerRefs(stored));
7065
7083
  return {
@@ -7067,7 +7085,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
7067
7085
  value: incoming
7068
7086
  };
7069
7087
  }
7070
- const storedObj = isRecord$1(stored) ? stored : {};
7088
+ const storedObj = isRecord$4(stored) ? stored : {};
7071
7089
  return {
7072
7090
  kind: "value",
7073
7091
  value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
@@ -7190,7 +7208,7 @@ async function resolveSecretMarkers(value, secrets, options = {}) {
7190
7208
  value: items
7191
7209
  };
7192
7210
  }
7193
- if (isRecord$1(v)) {
7211
+ if (isRecord$4(v)) {
7194
7212
  const out = {};
7195
7213
  for (const [k, child] of Object.entries(v)) {
7196
7214
  const s = substitute(child);
@@ -7222,7 +7240,7 @@ var SecretOverwriteRefusedError = class extends Error {
7222
7240
  this.name = "SecretOverwriteRefusedError";
7223
7241
  }
7224
7242
  };
7225
- function isRecord(value) {
7243
+ function isRecord$3(value) {
7226
7244
  return typeof value === "object" && value !== null && !Array.isArray(value);
7227
7245
  }
7228
7246
  function resolvedValue(pass, marker) {
@@ -7251,7 +7269,7 @@ function inArray(pass, incoming, stored) {
7251
7269
  return incoming.map((item, index) => {
7252
7270
  const twin = stored[index];
7253
7271
  if (isSecretMarker(twin)) return typeof item === "string" && item === resolvedValue(pass, twin) ? twin : item;
7254
- if (isRecord(item) && isRecord(twin)) return inArrayRow(pass, item, twin);
7272
+ if (isRecord$3(item) && isRecord$3(twin)) return inArrayRow(pass, item, twin);
7255
7273
  if (Array.isArray(item) && Array.isArray(twin)) return inArray(pass, item, twin);
7256
7274
  return item;
7257
7275
  });
@@ -7262,7 +7280,7 @@ function inArrayRow(pass, incoming, stored) {
7262
7280
  const twin = stored[key];
7263
7281
  if (isSecretMarker(twin)) {
7264
7282
  if (typeof value === "string" && value === resolvedValue(pass, twin)) out[key] = twin;
7265
- } else if (isRecord(value) && isRecord(twin)) out[key] = inArrayRow(pass, value, twin);
7283
+ } else if (isRecord$3(value) && isRecord$3(twin)) out[key] = inArrayRow(pass, value, twin);
7266
7284
  else if (Array.isArray(value) && Array.isArray(twin)) out[key] = inArray(pass, value, twin);
7267
7285
  }
7268
7286
  return out;
@@ -7270,7 +7288,7 @@ function inArrayRow(pass, incoming, stored) {
7270
7288
  async function walk(pass, incoming, stored) {
7271
7289
  if (isSecretMarker(stored)) return atMarker(pass, incoming, stored);
7272
7290
  if (Array.isArray(incoming) && Array.isArray(stored)) return inArray(pass, incoming, stored);
7273
- if (!isRecord(incoming) || !isRecord(stored)) return incoming;
7291
+ if (!isRecord$3(incoming) || !isRecord$3(stored)) return incoming;
7274
7292
  const out = { ...incoming };
7275
7293
  for (const [key, value] of Object.entries(incoming)) out[key] = await walk(pass, value, stored[key]);
7276
7294
  return out;
@@ -7301,7 +7319,7 @@ async function restoreUnchangedSecrets(input) {
7301
7319
  function isJsonEqual(a, b) {
7302
7320
  if (Object.is(a, b)) return true;
7303
7321
  if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((v, i) => isJsonEqual(v, b[i]));
7304
- if (isRecord(a) && isRecord(b)) {
7322
+ if (isRecord$3(a) && isRecord$3(b)) {
7305
7323
  const keys = Object.keys(a);
7306
7324
  if (keys.length !== Object.keys(b).length) return false;
7307
7325
  return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isJsonEqual(a[k], b[k]));
@@ -7325,7 +7343,7 @@ function markerKeysOf(blob, owner) {
7325
7343
  }
7326
7344
  function nodeFromStored(value, owner) {
7327
7345
  if (isSecretMarker(value)) return isSecretRefOwnedBy(value.$secret, owner) ? { kind: "leaf" } : null;
7328
- if (!isRecord(value)) return null;
7346
+ if (!isRecord$3(value)) return null;
7329
7347
  const children = /* @__PURE__ */ new Map();
7330
7348
  for (const [key, child] of Object.entries(value)) {
7331
7349
  const node = nodeFromStored(child, owner);
@@ -7458,7 +7476,6 @@ function createHandle(raw, opts) {
7458
7476
  const writeAddon = (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p));
7459
7477
  const readAddonStoreResult = raw.readAddonStoreResult;
7460
7478
  const readAddonStoreKeyResult = raw.readAddonStoreKeyResult;
7461
- const readDeviceStoreBatch = raw.readDeviceStoreBatch;
7462
7479
  const overrides = {
7463
7480
  readAddonStore: async () => resolveRead("addon", await raw.readAddonStore()),
7464
7481
  ...readAddonStoreResult !== void 0 ? { readAddonStoreResult: async () => {
@@ -7482,12 +7499,12 @@ function createHandle(raw, opts) {
7482
7499
  };
7483
7500
  } } : {},
7484
7501
  readDeviceStore: async (deviceId) => resolveRead(`device:${deviceId}`, await raw.readDeviceStore(deviceId)),
7485
- ...readDeviceStoreBatch !== void 0 ? { readDeviceStoreBatch: async (deviceIds) => {
7486
- const rows = await readDeviceStoreBatch.call(raw, deviceIds);
7502
+ readDeviceStoreBatch: async (deviceIds) => {
7503
+ const rows = await raw.readDeviceStoreBatch(deviceIds);
7487
7504
  const out = /* @__PURE__ */ new Map();
7488
7505
  for (const [deviceId, blob] of rows) out.set(deviceId, await resolveRead(`device:${deviceId}`, blob));
7489
7506
  return out;
7490
- } } : {},
7507
+ },
7491
7508
  writeAddonStore: async (patch) => {
7492
7509
  await writeAddon(patch);
7493
7510
  },
@@ -8114,7 +8131,7 @@ var BaseAddon = class {
8114
8131
  if (!ctx) return;
8115
8132
  const addonId = ctx.id;
8116
8133
  setImmediate(() => {
8117
- ctx.api.addons?.restartAddon?.mutate({ addonId }).then(() => {
8134
+ ctx.api.addons.restartAddon.mutate({ addonId }).then(() => {
8118
8135
  ctx.logger.info("addon auto-restart triggered by restart-required setting change", { meta: { changedFields: patchKeys.filter((k) => restartKeys.has(k)) } });
8119
8136
  }).catch((err) => {
8120
8137
  ctx.logger.error("addon auto-restart failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
@@ -8314,7 +8331,7 @@ var BaseAddon = class {
8314
8331
  return this._addonSecretTree;
8315
8332
  }
8316
8333
  deviceSecretTree() {
8317
- this._deviceSecretTree ??= secretTreeFromSchema(this.deviceSettingsSchema());
8334
+ this._deviceSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.deviceSettingsSchema()), this.secretStatePaths);
8318
8335
  return this._deviceSecretTree;
8319
8336
  }
8320
8337
  /**
@@ -8390,42 +8407,135 @@ var BaseAddon = class {
8390
8407
  this[timerOf] = timer;
8391
8408
  }
8392
8409
  /**
8393
- * Wrap `ctx.settings.readAddonStore()` with a short retry budget so a
8394
- * transient settings-store outage (mid-restart of sqlite-settings, tsx-watch
8395
- * swap, or any race where the SqliteSettingsBackend is between shutdown and
8396
- * re-initialize) doesn't propagate up into `initialize()` and leave the
8410
+ * Wrap `ctx.settings.readAddonStore()` with a retry budget so a transient
8411
+ * settings-store outage doesn't propagate up into `initialize()` and leave the
8397
8412
  * addon permanently broken.
8398
8413
  *
8399
- * Retry only the two known infra fingerprints. Anything else propagates so
8400
- * real bugs surface immediately. After the budget expires we fall back to
8401
- * `{}` (defaults) — the addon's first successful patch will rehydrate.
8414
+ * Two kinds of outage, two budgets:
8415
+ * - the store's own restart fingerprints (`SqliteSettingsBackend not
8416
+ * initialized`, `provider not available`): a short budget, then the addon
8417
+ * falls back to `{}` (defaults) - its first successful patch rehydrates;
8418
+ * - a TRANSPORT failure (the hub is restarting while this runner boots:
8419
+ * `discovery deadline exceeded`, `parent could not route`, a refused or
8420
+ * timed-out connection). Since D870 a runner's settings read THROWS instead
8421
+ * of answering `{}`, so without a budget a hub restart ends in a respawn
8422
+ * loop. A long, bounded budget; only after it the error propagates - the
8423
+ * addon never runs on defaults for a store it could not ask.
8424
+ * Anything else propagates immediately so real bugs surface.
8402
8425
  */
8403
8426
  async readAddonStoreWithRetry() {
8404
8427
  const settings = this.rawSettings();
8405
8428
  if (!settings) return {};
8406
- const delaysMs = [
8407
- 150,
8408
- 350,
8409
- 600,
8410
- 900
8411
- ];
8412
8429
  let lastErr;
8413
- for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
8430
+ for (let attempt = 0;; attempt++) try {
8414
8431
  const stored = await settings.readAddonStore() ?? {};
8415
8432
  this.settingsStoreReady = true;
8416
8433
  return stored;
8417
8434
  } catch (err) {
8418
8435
  lastErr = err;
8419
- const msg = err instanceof Error ? err.message : String(err);
8420
- if (!(msg.includes("SqliteSettingsBackend not initialized") || msg.includes("provider not available"))) throw err;
8421
- if (attempt === delaysMs.length) break;
8422
- await new Promise((r) => setTimeout(r, delaysMs[attempt]));
8436
+ const kind = classifyStoreError(err);
8437
+ if (kind === "permanent") throw err;
8438
+ const delay = (kind === "transport" ? TRANSPORT_RETRY_DELAYS_MS : RESTART_RETRY_DELAYS_MS)[attempt];
8439
+ if (delay === void 0) {
8440
+ if (kind === "transport") throw err;
8441
+ break;
8442
+ }
8443
+ await new Promise((r) => setTimeout(r, delay));
8423
8444
  }
8424
8445
  this.settingsStoreReady = false;
8425
8446
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
8426
8447
  return {};
8427
8448
  }
8428
8449
  };
8450
+ /** The settings store's own restart fingerprints. */
8451
+ var RESTART_RETRY_DELAYS_MS = [
8452
+ 150,
8453
+ 350,
8454
+ 600,
8455
+ 900
8456
+ ];
8457
+ /** A hub restarting under a booting runner: ~100 s in total. */
8458
+ var TRANSPORT_RETRY_DELAYS_MS = [
8459
+ 150,
8460
+ 350,
8461
+ 600,
8462
+ 900,
8463
+ 2e3,
8464
+ 5e3,
8465
+ 1e4,
8466
+ 2e4,
8467
+ 3e4,
8468
+ 3e4
8469
+ ];
8470
+ var RESTART_FINGERPRINTS = ["SqliteSettingsBackend not initialized", "provider not available"];
8471
+ /** Node / socket error codes of a connection that did not happen or broke. */
8472
+ var TRANSPORT_CODES = new Set([
8473
+ "ECONNREFUSED",
8474
+ "ECONNRESET",
8475
+ "ETIMEDOUT",
8476
+ "EPIPE",
8477
+ "EHOSTUNREACH",
8478
+ "ENETUNREACH"
8479
+ ]);
8480
+ /** tRPC error codes that mean "the other side is not answering now", not "your call is wrong". */
8481
+ var UNAVAILABLE_TRPC_CODES = new Set([
8482
+ "TIMEOUT",
8483
+ "SERVICE_UNAVAILABLE",
8484
+ "BAD_GATEWAY",
8485
+ "GATEWAY_TIMEOUT"
8486
+ ]);
8487
+ /** The exact phrases OUR transport raises when it cannot reach the hub. */
8488
+ var TRANSPORT_PHRASES = [
8489
+ "discovery deadline exceeded",
8490
+ "parent could not route child unowned cap call",
8491
+ "UDS request timed out after",
8492
+ "channel closed"
8493
+ ];
8494
+ /** Moleculer error `type`s of a call that could not be served right now. */
8495
+ var MOLECULER_TRANSIENT_TYPES = new Set([
8496
+ "REQUEST_TIMEOUT",
8497
+ "SERVICE_NOT_FOUND",
8498
+ "SERVICE_NOT_AVAILABLE",
8499
+ "REQUEST_REJECTED",
8500
+ "BROKER_DISCONNECTED"
8501
+ ]);
8502
+ function stringProp(value, key) {
8503
+ if (typeof value !== "object" || value === null) return void 0;
8504
+ const found = Reflect.get(value, key);
8505
+ return typeof found === "string" ? found : void 0;
8506
+ }
8507
+ /** The code an error carries: its own, its cause's, or a tRPC `data.code`. */
8508
+ function errorCode(err) {
8509
+ const out = {};
8510
+ const cause = typeof err === "object" && err !== null ? Reflect.get(err, "cause") : void 0;
8511
+ const data = typeof err === "object" && err !== null ? Reflect.get(err, "data") : void 0;
8512
+ const node = stringProp(err, "code") ?? stringProp(cause, "code");
8513
+ if (node !== void 0) out.node = node;
8514
+ const trpc = stringProp(data, "code");
8515
+ if (trpc !== void 0) out.trpc = trpc;
8516
+ return out;
8517
+ }
8518
+ /**
8519
+ * Is a settings read failure worth retrying, and with which budget? Only
8520
+ * EXPLICIT signals count: a socket error code, a tRPC "unavailable" code, or
8521
+ * one of the exact phrases our own transport raises. A message that merely
8522
+ * contains "timed out" or "unreachable" is not evidence of anything, and
8523
+ * `settings store not wired` is a wiring bug that retrying cannot fix.
8524
+ */
8525
+ function classifyStoreError(err) {
8526
+ const message = err instanceof Error ? err.message : String(err);
8527
+ if (RESTART_FINGERPRINTS.some((f) => message.includes(f))) return "restart";
8528
+ const code = errorCode(err);
8529
+ if (code.node !== void 0 && TRANSPORT_CODES.has(code.node)) return "transport";
8530
+ if (code.trpc !== void 0 && UNAVAILABLE_TRPC_CODES.has(code.trpc)) return "transport";
8531
+ if (typeof err === "object" && err !== null) {
8532
+ if (Reflect.get(err, "retryable") === true) return "transport";
8533
+ const type = stringProp(err, "type");
8534
+ if (type !== void 0 && MOLECULER_TRANSIENT_TYPES.has(type)) return "transport";
8535
+ }
8536
+ if (TRANSPORT_PHRASES.some((f) => message.includes(f))) return "transport";
8537
+ return "permanent";
8538
+ }
8429
8539
  /**
8430
8540
  * Collect the keys of every field marked `perNode: true`, recursing into
8431
8541
  * layout containers (`group` fields and `sub-tabs` tabs) the same way
@@ -8515,6 +8625,156 @@ var PeerBytesTicketSchema = object({
8515
8625
  */
8516
8626
  declaredBytes: number().int().nonnegative().nullable()
8517
8627
  });
8628
+ /** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
8629
+ /** The files a kind may live in. `history` is device-history's (D850), never records. */
8630
+ var RECORD_DATABASES = ["settings", "events"];
8631
+ /** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
8632
+ var RECORD_CLASSES = [
8633
+ "config",
8634
+ "ledger",
8635
+ "audit",
8636
+ "mirror",
8637
+ "ephemeral"
8638
+ ];
8639
+ var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
8640
+ var RECORD_MAX_DATA_BYTES = 1024 * 1024;
8641
+ var RECORD_BATCH_MAX = 1e3;
8642
+ var RECORD_QUERY_MAX = 1e3;
8643
+ function defOf(schema) {
8644
+ const zodBag = Reflect.get(schema, "_zod");
8645
+ if (typeof zodBag !== "object" || zodBag === null) return {};
8646
+ const def = Reflect.get(zodBag, "def");
8647
+ if (typeof def !== "object" || def === null) return {};
8648
+ return def;
8649
+ }
8650
+ function declaredHere(schema) {
8651
+ return globalRegistry.get(schema)?.secret === true;
8652
+ }
8653
+ /** Wrapper kinds whose `innerType` is the field itself. */
8654
+ var WRAPPERS = new Set([
8655
+ "optional",
8656
+ "nullable",
8657
+ "default",
8658
+ "prefault",
8659
+ "catch",
8660
+ "readonly",
8661
+ "nonoptional"
8662
+ ]);
8663
+ /**
8664
+ * The schema layers that make up ONE field: the schema itself and every
8665
+ * wrapper below it (optional / default / nullable / readonly / catch), plus
8666
+ * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
8667
+ * `.transform` on `in`).
8668
+ */
8669
+ function layersOf(schema) {
8670
+ const out = [];
8671
+ const queue = [schema];
8672
+ while (queue.length > 0) {
8673
+ const s = queue.shift();
8674
+ if (!s || out.includes(s)) continue;
8675
+ out.push(s);
8676
+ const def = defOf(s);
8677
+ if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
8678
+ if (def.type === "pipe") {
8679
+ if (def.in) queue.push(def.in);
8680
+ if (def.out) queue.push(def.out);
8681
+ }
8682
+ }
8683
+ return out;
8684
+ }
8685
+ /** True when any layer of this field declares `.meta({ secret: true })`. */
8686
+ function isZodSecret(schema) {
8687
+ return layersOf(schema).some(declaredHere);
8688
+ }
8689
+ /**
8690
+ * Every declared secret path in a config schema: dotted keys, `[]` for "each
8691
+ * element of this array" (`connection.password`, `rows[].token`). A declared
8692
+ * object or array is reported as one path and not descended into — everything
8693
+ * under it is secret.
8694
+ */
8695
+ function collectZodSecretPaths(schema, prefix = "") {
8696
+ if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
8697
+ const paths = [];
8698
+ for (const layer of layersOf(schema)) {
8699
+ const def = defOf(layer);
8700
+ if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
8701
+ else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
8702
+ else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
8703
+ else if (def.type === "intersection") {
8704
+ if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
8705
+ if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
8706
+ }
8707
+ }
8708
+ return [...new Set(paths)];
8709
+ }
8710
+ /**
8711
+ * Record kinds (D881): the declaration an addon makes of one kind of fact, the
8712
+ * Zod row schema that describes it, and the helper that turns the two into the
8713
+ * JSON the engine stores.
8714
+ *
8715
+ * A kind is declared ONCE, next to the code that owns it, and the engine's
8716
+ * reconciler creates its indexes - nothing creates DDL at runtime from a write.
8717
+ * Everything the generic machinery needs (secret fields, the entry descriptor)
8718
+ * is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
8719
+ * never a field name (D879).
8720
+ */
8721
+ /** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
8722
+ var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
8723
+ var RecordIndexSchema = object({
8724
+ name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
8725
+ fields: array(RecordFieldPathSchema).min(1).max(4),
8726
+ unique: boolean().optional()
8727
+ }).strict();
8728
+ var RecordRetentionSchema = union([
8729
+ object({
8730
+ ttlMs: number().int().positive(),
8731
+ from: _enum(["at", "write"])
8732
+ }).strict(),
8733
+ object({
8734
+ maxRows: number().int().positive().optional(),
8735
+ maxRowsPerDevice: number().int().positive().optional()
8736
+ }).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
8737
+ object({
8738
+ owner: string().min(1),
8739
+ reason: string().min(1)
8740
+ }).strict(),
8741
+ object({ none: string().min(1) }).strict()
8742
+ ]);
8743
+ /** The descriptor a generic entries UI reads (D881 "generic entries"). */
8744
+ var EntryDescriptorSchema = object({
8745
+ title: string().min(1),
8746
+ /** The record body field shown as the entry's name. */
8747
+ nameField: string().min(1),
8748
+ /** JSON Schema of the entry body; `secret: true` marks a password field. */
8749
+ fields: record(string(), unknown())
8750
+ }).strict();
8751
+ /** What crosses the wire and sits in the engine's catalog: JSON only, no Zod. */
8752
+ var RecordKindDeclarationSchema = object({
8753
+ database: _enum(RECORD_DATABASES).default("settings"),
8754
+ class: _enum(RECORD_CLASSES),
8755
+ writeTrigger: string().min(1),
8756
+ device: _enum([
8757
+ "required",
8758
+ "optional",
8759
+ "none"
8760
+ ]).default("none"),
8761
+ at: _enum(["required", "none"]).default("none"),
8762
+ indexes: array(RecordIndexSchema).max(8).default([]),
8763
+ retention: RecordRetentionSchema,
8764
+ deviceRemoval: _enum(["delete", "none"]).default("none"),
8765
+ maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
8766
+ /** Derived from the Zod schema; never written by hand. */
8767
+ secretPaths: array(string().min(1)).default([]),
8768
+ /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
8769
+ secretArrayKeys: record(string(), string()).default({}),
8770
+ /** The legacy table this kind adopts from; the boot guard reads it (D853: no migration code). */
8771
+ adoptsFrom: string().min(1).optional(),
8772
+ /** Kinds that must agree through `transact` sit in one file under one family name. */
8773
+ transactFamily: string().min(1).optional(),
8774
+ entry: EntryDescriptorSchema.optional()
8775
+ }).strict();
8776
+ /** The kind names: lower-case, dash separated, short enough for an index name. */
8777
+ var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
8518
8778
  /** Shared Zod schemas used across streaming capabilities. */
8519
8779
  var CamProfileSchema = _enum([
8520
8780
  "high",
@@ -8979,75 +9239,12 @@ method(object({ deviceId: number() }), array(StreamSourceEntrySchema)), method(o
8979
9239
  kind: "mutation",
8980
9240
  auth: "admin"
8981
9241
  }), method(object({ deviceId: number() }), unknown().nullable()), method(object({ deviceId: number() }), RawStateResultSchema.nullable(), { auth: "protected" });
8982
- function defOf(schema) {
8983
- return schema._zod?.def ?? {};
8984
- }
8985
- function declaredHere(schema) {
8986
- return globalRegistry.get(schema)?.secret === true;
8987
- }
8988
- /** Wrapper kinds whose `innerType` is the field itself. */
8989
- var WRAPPERS = new Set([
8990
- "optional",
8991
- "nullable",
8992
- "default",
8993
- "prefault",
8994
- "catch",
8995
- "readonly",
8996
- "nonoptional"
8997
- ]);
8998
- /**
8999
- * The schema layers that make up ONE field: the schema itself and every
9000
- * wrapper below it (optional / default / nullable / readonly / catch), plus
9001
- * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
9002
- * `.transform` on `in`).
9003
- */
9004
- function layersOf(schema) {
9005
- const out = [];
9006
- const queue = [schema];
9007
- while (queue.length > 0) {
9008
- const s = queue.shift();
9009
- if (!s || out.includes(s)) continue;
9010
- out.push(s);
9011
- const def = defOf(s);
9012
- if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
9013
- if (def.type === "pipe") {
9014
- if (def.in) queue.push(def.in);
9015
- if (def.out) queue.push(def.out);
9016
- }
9017
- }
9018
- return out;
9019
- }
9020
- /** True when any layer of this field declares `.meta({ secret: true })`. */
9021
- function isZodSecret(schema) {
9022
- return layersOf(schema).some(declaredHere);
9023
- }
9024
- /**
9025
- * Every declared secret path in a config schema: dotted keys, `[]` for "each
9026
- * element of this array" (`connection.password`, `rows[].token`). A declared
9027
- * object or array is reported as one path and not descended into — everything
9028
- * under it is secret.
9029
- */
9030
- function collectZodSecretPaths(schema, prefix = "") {
9031
- if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
9032
- const paths = [];
9033
- for (const layer of layersOf(schema)) {
9034
- const def = defOf(layer);
9035
- if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
9036
- else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
9037
- else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
9038
- else if (def.type === "intersection") {
9039
- if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
9040
- if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
9041
- }
9042
- }
9043
- return [...new Set(paths)];
9044
- }
9045
9242
  object({
9046
9243
  code: string().regex(/^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/),
9047
9244
  params: record(string(), union([string(), number()])).optional()
9048
9245
  });
9049
9246
  //#endregion
9050
- //#region ../types/dist/err-msg-DLerKiVO.mjs
9247
+ //#region ../types/dist/err-msg-BXKzRBwg.mjs
9051
9248
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
9052
9249
  DeviceType["Camera"] = "camera";
9053
9250
  DeviceType["Hub"] = "hub";
@@ -9139,6 +9336,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
9139
9336
  * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
9140
9337
  * integrations sharing the same food/desiccant/hopper surface. */
9141
9338
  DeviceType["PetFeeder"] = "pet-feeder";
9339
+ /** Built-in dishwasher — program lifecycle, door / remote-start gates,
9340
+ * salt and rinse-aid levels. Installed with the `dishwasher` cap.
9341
+ * Sources: native Bosch Home Connect, reusable by similar integrations. */
9342
+ DeviceType["Dishwasher"] = "dishwasher";
9142
9343
  return DeviceType;
9143
9344
  }({});
9144
9345
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -9316,6 +9517,9 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
9316
9517
  /** Virtual HA toggle (input_boolean.*) — distinguishable from a
9317
9518
  * real Switch device for UI rendering / export adapters. */
9318
9519
  DeviceRole["BinaryHelper"] = "binary-helper";
9520
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
9521
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
9522
+ DeviceRole["Outlet"] = "outlet";
9319
9523
  /** Generic motion / occupancy / moving event source. Distinct from
9320
9524
  * the camera accessory PirSensor role: that one is a camera child;
9321
9525
  * this is a standalone HA / 3rd-party motion sensor. */
@@ -9393,7 +9597,7 @@ function errMsg(err) {
9393
9597
  return String(err);
9394
9598
  }
9395
9599
  //#endregion
9396
- //#region ../types/dist/composition-EQ9Ok0Ee.mjs
9600
+ //#region ../types/dist/composition-CB6PMT9p.mjs
9397
9601
  /**
9398
9602
  * The ffmpeg build every CamStack node downloads, per platform and arch.
9399
9603
  *
@@ -12597,11 +12801,10 @@ var ClipModelMetaSchema = object({
12597
12801
  /**
12598
12802
  * The `vector-store` index this model's vectors live in. ONE feature space
12599
12803
  * per index: two models share an index only when their vectors are ranked
12600
- * against each other, which for CLIP means never across families. MobileCLIP
12601
- * S1 and S2 share `object-clip` for history (separated by the row's
12602
- * `modelId`); any other model gets its own.
12804
+ * against each other, which for CLIP means never across families. Every
12805
+ * model gets its own.
12603
12806
  */
12604
- vectorIndex: string().regex(/^object-clip(-[a-z0-9][a-z0-9-]*)?$/),
12807
+ vectorIndex: string().regex(/^object-clip-[a-z0-9][a-z0-9-]*$/),
12605
12808
  /**
12606
12809
  * The text encoder's tokenizer, a declared SIBLING file of its onnx. The name
12607
12810
  * must be unique per tokenizer: every model's siblings land in ONE flat
@@ -13780,8 +13983,13 @@ var AlertSeveritySchema = _enum([
13780
13983
  "info",
13781
13984
  "success",
13782
13985
  "warning",
13783
- "error"
13986
+ "error",
13987
+ "critical"
13784
13988
  ]);
13989
+ var AlertActionSchema = object({
13990
+ label: string(),
13991
+ href: string().optional()
13992
+ });
13785
13993
  var AlertStatusSchema = _enum([
13786
13994
  "active",
13787
13995
  "in-progress",
@@ -13799,6 +14007,7 @@ var AlertSchema = object({
13799
14007
  severity: AlertSeveritySchema,
13800
14008
  title: string(),
13801
14009
  message: string(),
14010
+ action: AlertActionSchema.optional(),
13802
14011
  status: AlertStatusSchema,
13803
14012
  progress: number().optional(),
13804
14013
  read: boolean(),
@@ -16334,6 +16543,41 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
16334
16543
  keyField: string()
16335
16544
  }).optional()
16336
16545
  })) });
16546
+ /**
16547
+ * The report of a one-shot "move this owner's plaintext secrets into the
16548
+ * keychain" run (D878). COUNTS only - never a value, never a ref.
16549
+ *
16550
+ * The owner runs it on its own rows, through its own write path; an operator
16551
+ * script (`scripts/hub/migrate-raw-secrets.ts`) calls the owner's cap method.
16552
+ * Dry run (`apply: false`) reports what WOULD move and changes nothing.
16553
+ */
16554
+ var RawSecretMigrationInputSchema = object({
16555
+ /** `false` (the default of the script) = report only. */
16556
+ apply: boolean() });
16557
+ var RawSecretMigrationReportSchema = object({
16558
+ apply: boolean(),
16559
+ /** Rows that can carry a secret (a profile, an enrolment). */
16560
+ rows: number().int(),
16561
+ /** Rows whose secret was plaintext when the run started. */
16562
+ raw: number().int(),
16563
+ /** Rows that already held a keychain marker when the run started. */
16564
+ markers: number().int(),
16565
+ /** Plaintext rows moved by THIS run (always 0 on a dry run). */
16566
+ migrated: number().int(),
16567
+ /** Plaintext rows left in place because the move or its read-back failed. */
16568
+ failed: number().int(),
16569
+ /** After the run: rows still holding plaintext. */
16570
+ remainingRaw: number().int(),
16571
+ /** After the run: markers the keychain could not resolve right now. */
16572
+ unresolvedMarkers: number().int(),
16573
+ /**
16574
+ * A second, redundant copy of the secrets' rows that the run found (and, on
16575
+ * `apply`, deleted after verifying the first). Owners with a single copy omit it.
16576
+ */
16577
+ redundantCopies: number().int().optional(),
16578
+ /** True when nothing is raw and every marker resolves. */
16579
+ verified: boolean()
16580
+ });
16337
16581
  /** Where a block runs. The operator chooses — a block driving a device on an
16338
16582
  * agent is the reason placement is not fixed to the hub. */
16339
16583
  var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
@@ -16560,6 +16804,9 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
16560
16804
  }), SettingsUpdateResultSchema, {
16561
16805
  kind: "mutation",
16562
16806
  auth: "admin"
16807
+ }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
16808
+ kind: "mutation",
16809
+ auth: "admin"
16563
16810
  }), method(object({}), object({ libs: array(object({
16564
16811
  filePath: string(),
16565
16812
  content: string()
@@ -16823,6 +17070,347 @@ id: union([string(), number()]) }), {
16823
17070
  auth: "admin"
16824
17071
  });
16825
17072
  /**
17073
+ * records - an addon's own facts and documents (D881): ledgers, audit rows,
17074
+ * config documents, mirrors. The third storage API next to `ctx.settings`
17075
+ * (operator intent, D870) and device-history (numbers over time, D850).
17076
+ *
17077
+ * Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
17078
+ * crossed a process and stated by a hub in-process caller (`ctx.records` does
17079
+ * that with the addon id closed over). It is deliberately NOT in any input
17080
+ * schema, so a session can never supply it, and a call with no stamp is refused
17081
+ * `records/no-caller`. A kind is readable and writable only by its owner; other
17082
+ * addons go through the owner's capability.
17083
+ *
17084
+ * Every read is three-way: a row, `null`, or the call THROWS. A failed read is
17085
+ * never `[]`. Secret fields are never on the wire: a read shows the
17086
+ * redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
17087
+ * resolves itself, and a write may carry only a keychain marker bound to the
17088
+ * row's own ref.
17089
+ *
17090
+ * Internal: addons reach it through `ctx.records`, never directly.
17091
+ */
17092
+ var RecordIdSchema = string().min(1).max(256);
17093
+ /** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
17094
+ var RecordBodySchema = record(string(), unknown());
17095
+ var RecordInputSchema = object({
17096
+ id: RecordIdSchema,
17097
+ /** The device this row belongs to (D865 removal, per-device reads). */
17098
+ deviceId: number().int().optional(),
17099
+ /** The domain clock, ms: observed / created / opened. */
17100
+ at: number().int().optional(),
17101
+ /** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
17102
+ expiresAt: number().int().optional(),
17103
+ data: RecordBodySchema
17104
+ });
17105
+ var RecordViewSchema = object({
17106
+ id: string(),
17107
+ deviceId: number().int().nullable(),
17108
+ at: number().nullable(),
17109
+ expiresAt: number().nullable(),
17110
+ rev: number().int(),
17111
+ updatedAt: number(),
17112
+ data: RecordBodySchema
17113
+ });
17114
+ /** Declared index fields only. Anything else does not compile, by design. */
17115
+ var RecordWhereSchema = record(RecordFieldPathSchema, union([
17116
+ string(),
17117
+ number(),
17118
+ boolean(),
17119
+ object({ in: array(union([
17120
+ string(),
17121
+ number(),
17122
+ boolean()
17123
+ ])).min(1).max(RECORD_BATCH_MAX) }),
17124
+ object({
17125
+ gt: union([string(), number()]).optional(),
17126
+ gte: union([string(), number()]).optional(),
17127
+ lt: union([string(), number()]).optional(),
17128
+ lte: union([string(), number()]).optional()
17129
+ }).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
17130
+ ]));
17131
+ var KindShape = { kind: RecordKindNameSchema };
17132
+ var TimeWindowShape = {
17133
+ deviceId: number().int().optional(),
17134
+ atFrom: number().optional(),
17135
+ atTo: number().optional()
17136
+ };
17137
+ var RecordQueryInputSchema = object({
17138
+ ...KindShape,
17139
+ ...TimeWindowShape,
17140
+ where: RecordWhereSchema.optional(),
17141
+ /** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
17142
+ order: _enum(["at", "id"]).optional(),
17143
+ desc: boolean().optional(),
17144
+ limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
17145
+ /** Opaque keyset cursor from a previous answer. There is no offset. */
17146
+ cursor: string().min(1).optional()
17147
+ });
17148
+ var RecordQueryResultSchema = object({
17149
+ records: array(RecordViewSchema),
17150
+ nextCursor: string().nullable()
17151
+ });
17152
+ var RecordKindStateSchema = _enum([
17153
+ "created",
17154
+ "unchanged",
17155
+ "updated"
17156
+ ]);
17157
+ var DeclareKindResultSchema = object({
17158
+ kindId: number().int(),
17159
+ state: RecordKindStateSchema,
17160
+ createdIndexes: array(string()),
17161
+ /** The boot guard: the kind is empty while its legacy table still holds rows. */
17162
+ adoptionPending: object({
17163
+ legacyTable: string(),
17164
+ legacyRows: number().int()
17165
+ }).nullable()
17166
+ });
17167
+ /** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
17168
+ var SecretRefsList = array(string());
17169
+ var RecordSecretRefsSchema = record(string(), record(string(), string()));
17170
+ method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
17171
+ kind: "mutation",
17172
+ auth: "admin"
17173
+ }), method(object({
17174
+ ...KindShape,
17175
+ id: RecordIdSchema
17176
+ }), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
17177
+ ...KindShape,
17178
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
17179
+ }), array(RecordViewSchema), { auth: "admin" }), method(object({
17180
+ ...KindShape,
17181
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
17182
+ }), RecordSecretRefsSchema, { auth: "admin" }), method(object({
17183
+ ...KindShape,
17184
+ record: RecordInputSchema,
17185
+ /** Compare-and-set. `0` means "the row must not exist". */
17186
+ ifRev: number().int().min(0).optional()
17187
+ }), object({
17188
+ rev: number().int(),
17189
+ releasedRefs: SecretRefsList
17190
+ }), {
17191
+ kind: "mutation",
17192
+ auth: "admin"
17193
+ }), method(object({
17194
+ ...KindShape,
17195
+ records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
17196
+ }), object({
17197
+ count: number().int(),
17198
+ releasedRefs: SecretRefsList
17199
+ }), {
17200
+ kind: "mutation",
17201
+ auth: "admin"
17202
+ }), method(object({
17203
+ ...KindShape,
17204
+ id: RecordIdSchema,
17205
+ data: RecordBodySchema,
17206
+ at: number().int().nullable().optional(),
17207
+ expiresAt: number().int().nullable().optional(),
17208
+ ifRev: number().int().min(1).optional()
17209
+ }), object({
17210
+ rev: number().int(),
17211
+ releasedRefs: SecretRefsList
17212
+ }), {
17213
+ kind: "mutation",
17214
+ auth: "admin"
17215
+ }), method(object({
17216
+ ...KindShape,
17217
+ id: RecordIdSchema
17218
+ }), object({
17219
+ deleted: boolean(),
17220
+ secretRefs: SecretRefsList
17221
+ }), {
17222
+ kind: "mutation",
17223
+ auth: "admin"
17224
+ }), method(object({
17225
+ ...KindShape,
17226
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
17227
+ }), object({
17228
+ deleted: number().int(),
17229
+ secretRefs: SecretRefsList
17230
+ }), {
17231
+ kind: "mutation",
17232
+ auth: "admin"
17233
+ }), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
17234
+ ...KindShape,
17235
+ ...TimeWindowShape,
17236
+ where: RecordWhereSchema.optional()
17237
+ }), object({ count: number().int() }), { auth: "admin" }), method(object({
17238
+ ...KindShape,
17239
+ deviceId: number().int().optional(),
17240
+ from: number(),
17241
+ to: number(),
17242
+ bucketMs: number().int().positive()
17243
+ }), object({ buckets: array(object({
17244
+ bucketStart: number(),
17245
+ count: number().int()
17246
+ })) }), { auth: "admin" }), method(object({
17247
+ ...KindShape,
17248
+ op: _enum([
17249
+ "sum",
17250
+ "min",
17251
+ "max",
17252
+ "count"
17253
+ ]),
17254
+ field: RecordFieldPathSchema.optional(),
17255
+ groupBy: RecordFieldPathSchema.optional(),
17256
+ where: RecordWhereSchema.optional()
17257
+ }), object({ rows: array(object({
17258
+ group: union([
17259
+ string(),
17260
+ number(),
17261
+ _null()
17262
+ ]),
17263
+ value: number().nullable()
17264
+ })) }), { auth: "admin" }), method(object({
17265
+ ...KindShape,
17266
+ deviceId: number().int().optional(),
17267
+ atBefore: number().optional(),
17268
+ where: RecordWhereSchema.optional()
17269
+ }), object({
17270
+ deleted: number().int(),
17271
+ secretRefs: SecretRefsList
17272
+ }), {
17273
+ kind: "mutation",
17274
+ auth: "admin"
17275
+ }), method(object({
17276
+ deviceId: number().int(),
17277
+ kinds: array(RecordKindNameSchema).optional()
17278
+ }), object({
17279
+ deleted: record(string(), number().int()),
17280
+ secretRefs: SecretRefsList
17281
+ }), {
17282
+ kind: "mutation",
17283
+ auth: "admin"
17284
+ }), method(object(KindShape), object({ id: number().int() }), {
17285
+ kind: "mutation",
17286
+ auth: "admin"
17287
+ });
17288
+ /** One table of the settings database. */
17289
+ var StorageTableSchema = object({
17290
+ /** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
17291
+ name: string(),
17292
+ /** The owner prefix of a scoped name (`recorder` for `recorder:addon-settings`), else null. */
17293
+ owner: string().nullable(),
17294
+ rows: number().int().nonnegative(),
17295
+ /** Bytes the table's pages occupy (`dbstat`); null when the engine cannot measure. */
17296
+ bytes: number().int().nonnegative().nullable(),
17297
+ /** Declared by an addon since this engine process started (a running addon claims it). */
17298
+ declaredThisBoot: boolean()
17299
+ });
17300
+ /** One declared record kind (D881) as System -> Data shows it. */
17301
+ var StorageKindSchema = object({
17302
+ owner: string(),
17303
+ kind: string(),
17304
+ database: _enum(["settings", "events"]),
17305
+ class: string(),
17306
+ state: _enum([
17307
+ "declared",
17308
+ "orphaned",
17309
+ "dropped"
17310
+ ]),
17311
+ /** `null` when the caller asked for no counts (`counts: false`). */
17312
+ rows: number().int().nonnegative().nullable(),
17313
+ /** Rows x the average size of a sample; null for an empty kind. */
17314
+ bytesEstimate: number().int().nonnegative().nullable(),
17315
+ writeTrigger: string(),
17316
+ retention: RecordRetentionSchema,
17317
+ /** `true` when the kind is an entry kind (the generic entries UI lists it). */
17318
+ entry: boolean(),
17319
+ /** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
17320
+ declaration: RecordKindDeclarationSchema
17321
+ });
17322
+ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
17323
+ collection: string().min(1),
17324
+ expectRows: number().int().nonnegative()
17325
+ }), object({
17326
+ dropped: literal(true),
17327
+ rows: number().int().nonnegative()
17328
+ }), {
17329
+ kind: "mutation",
17330
+ auth: "admin"
17331
+ }), method(object({
17332
+ owner: string().min(1).optional(),
17333
+ /** `false` skips the per-kind row count (a catalog read only). Default true. */
17334
+ counts: boolean().optional()
17335
+ }), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
17336
+ owner: string().min(1),
17337
+ kind: RecordKindNameSchema
17338
+ }), RecordQueryResultSchema, { auth: "admin" }), method(object({
17339
+ owner: string().min(1),
17340
+ kind: RecordKindNameSchema,
17341
+ records: array(RecordInputSchema).max(RECORD_BATCH_MAX),
17342
+ /** The LAST batch: the copy is complete, the boot guard stops asking. */
17343
+ complete: boolean().optional()
17344
+ }).refine((input) => input.records.length > 0 || input.complete === true, { message: "records must be non-empty unless complete is true" }), object({ adopted: number().int() }), {
17345
+ kind: "mutation",
17346
+ auth: "admin"
17347
+ }), method(object({
17348
+ collection: string().min(1),
17349
+ cursor: string().optional(),
17350
+ limit: number().int().min(1).max(1e3).optional()
17351
+ }), object({
17352
+ rows: array(object({
17353
+ id: string(),
17354
+ data: record(string(), unknown())
17355
+ })),
17356
+ nextCursor: string().nullable()
17357
+ }), { auth: "admin" }), method(object({ collection: string().min(1) }), object({ adopted: boolean() }), { auth: "admin" });
17358
+ /**
17359
+ * record-entries - the ONE admin surface for every list of user-created entries
17360
+ * (D881): notification targets, brokers, LLM profiles, anything an addon
17361
+ * declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
17362
+ *
17363
+ * The UI is generic. `listEntryKinds` hands it, per kind, the JSON Schema of
17364
+ * the entry body with `secret: true` on every credential field, so a password
17365
+ * field is derived from the schema and never from a name. `putEntry` takes the
17366
+ * redaction token for an untouched secret (the stored value is kept), a string
17367
+ * for a new one, and the clear instruction to remove one. A read never carries
17368
+ * a secret value, and a locked keychain refuses the write with
17369
+ * `records/secret-unavailable` instead of storing plaintext.
17370
+ *
17371
+ * Hub-only, served in-process (it acts for each entry's owner on the keychain).
17372
+ */
17373
+ var EntryKindInfoSchema = object({
17374
+ owner: string(),
17375
+ kind: RecordKindNameSchema,
17376
+ title: string(),
17377
+ nameField: string(),
17378
+ /** JSON Schema of the body; `secret: true` marks a password field. */
17379
+ fields: record(string(), unknown()),
17380
+ secretPaths: array(string()),
17381
+ rows: number().int().nonnegative()
17382
+ });
17383
+ var EntryViewSchema = object({
17384
+ id: RecordIdSchema,
17385
+ name: string(),
17386
+ rev: number().int(),
17387
+ updatedAt: number(),
17388
+ /** Secret fields hold the redaction token. */
17389
+ data: RecordBodySchema
17390
+ });
17391
+ var EntryAddress = {
17392
+ owner: string().min(1),
17393
+ kind: RecordKindNameSchema
17394
+ };
17395
+ method(object({}), object({ kinds: array(EntryKindInfoSchema) }), { auth: "admin" }), method(object({ ...EntryAddress }), object({ entries: array(EntryViewSchema) }), { auth: "admin" }), method(object({
17396
+ ...EntryAddress,
17397
+ id: RecordIdSchema.optional(),
17398
+ data: RecordBodySchema,
17399
+ ifRev: number().int().min(1).optional()
17400
+ }), object({
17401
+ id: string(),
17402
+ rev: number().int()
17403
+ }), {
17404
+ kind: "mutation",
17405
+ auth: "admin"
17406
+ }), method(object({
17407
+ ...EntryAddress,
17408
+ id: RecordIdSchema
17409
+ }), object({ deleted: boolean() }), {
17410
+ kind: "mutation",
17411
+ auth: "admin"
17412
+ });
17413
+ /**
16826
17414
  * `keychain` — the hub's encrypted store of secret values (D787).
16827
17415
  *
16828
17416
  * Settings blobs never hold a secret: they hold a marker
@@ -17363,6 +17951,16 @@ method(object({}), KeychainStatusSchema, { auth: "admin" }), method(object({}),
17363
17951
  kind: "mutation",
17364
17952
  auth: "admin",
17365
17953
  caller: "required"
17954
+ }), method(object({
17955
+ ref: string().min(1),
17956
+ value: string().min(1)
17957
+ }), object({
17958
+ ref: string(),
17959
+ version: number().int()
17960
+ }), {
17961
+ kind: "mutation",
17962
+ auth: "admin",
17963
+ caller: "required"
17366
17964
  }), method(object({ refs: array(string().min(1)).max(1e3) }), object({
17367
17965
  deleted: array(string()).readonly(),
17368
17966
  kept: array(object({
@@ -17771,7 +18369,11 @@ var deviceDiscoveryCapability = {
17771
18369
  };
17772
18370
  /**
17773
18371
  * `device-adoption` — generic discovery + adoption surface,
17774
- * system-scoped singleton. The de-HA-ified successor to `ha-discovery`:
18372
+ * system-scoped collection (one provider per integration addon), routed
18373
+ * by explicit `{ addonId }` on direct `deviceAdoption.*` calls — the same
18374
+ * pin model as `connection-test`. The admin UI keeps using
18375
+ * `deviceManager.adoption*` (which already threads `addonId`). The de-HA-ified
18376
+ * successor to `ha-discovery`:
17775
18377
  * keyed by `integrationId` (the addon resolves integrationId → broker
17776
18378
  * internally) rather than a connection-specific `brokerId`, and it
17777
18379
  * REUSES the shared `DiscoveredChildDevice` candidate type from
@@ -17936,7 +18538,7 @@ var ResyncResultSchema = object({
17936
18538
  var deviceAdoptionCapability = {
17937
18539
  name: "device-adoption",
17938
18540
  scope: "system",
17939
- mode: "singleton",
18541
+ mode: "collection",
17940
18542
  status: {
17941
18543
  schema: AdoptionStatusSchema,
17942
18544
  kind: "poll"
@@ -40041,6 +40643,9 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
40041
40643
  }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
40042
40644
  kind: "mutation",
40043
40645
  auth: "admin"
40646
+ }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
40647
+ kind: "mutation",
40648
+ auth: "admin"
40044
40649
  }), method(ProfileRefInputSchema, _void(), {
40045
40650
  kind: "mutation",
40046
40651
  auth: "admin"
@@ -50550,6 +51155,41 @@ var ServerRollbackInfoSchema = object({
50550
51155
  atMs: number(),
50551
51156
  reason: string()
50552
51157
  });
51158
+ /**
51159
+ * The runner boot check (D877): the runners this node PLANNED (hub: one per
51160
+ * `resolveRunnerId` over its forked addons; agent: the runners its process
51161
+ * service holds, because only the hub plans) against the states the process
51162
+ * service reports. Kept in memory, recomputed by the same one-shot check after
51163
+ * every boot, served on `getServerPackageStatus`.
51164
+ *
51165
+ * `failed` is the D6 breaker's terminal state, `refused` a deterministic Init
51166
+ * refusal (an addon that said "I will not start, and here is why"), `missing` a
51167
+ * planned runner the process service has no entry for.
51168
+ */
51169
+ var RunnerRefusalSchema = object({
51170
+ runnerId: string(),
51171
+ addonIds: array(string()),
51172
+ reason: string()
51173
+ });
51174
+ var RunnerBootCheckSchema = object({
51175
+ checkedAtMs: number(),
51176
+ /** Runners planned for this node. */
51177
+ expected: number(),
51178
+ /** Planned runners whose process is `running`. */
51179
+ running: number(),
51180
+ failed: array(string()),
51181
+ refused: array(RunnerRefusalSchema),
51182
+ missing: array(string()),
51183
+ /**
51184
+ * Runners still on their way: `starting`, or `crashed` waiting on a backoff
51185
+ * respawn. The check is FINAL only when this is empty; until then nothing is
51186
+ * pushed and the verdict is re-evaluated on the next runner state change.
51187
+ * Optional for version skew (absent = settled).
51188
+ */
51189
+ pending: array(string()).optional(),
51190
+ /** True when the plan is the hub's (`missing` is then meaningful); false on an agent. */
51191
+ planKnown: boolean()
51192
+ });
50553
51193
  var ServerPackageStatusSchema = object({
50554
51194
  /** Root package name (`@camstack/server` on the hub). */
50555
51195
  packageName: string(),
@@ -50612,7 +51252,14 @@ var ServerPackageStatusSchema = object({
50612
51252
  * for version skew: an older provider's payload simply omits it, and an
50613
51253
  * absent wrapper is unknown — never `native`.
50614
51254
  */
50615
- wrapper: WrapperIdentitySchema.optional()
51255
+ wrapper: WrapperIdentitySchema.optional(),
51256
+ /**
51257
+ * What this node PLANNED to run against what its process service reports
51258
+ * (D877). Absent until the one-shot boot check has run (~60 s after boot) and
51259
+ * on a provider that predates it. Absent is "not checked yet", never "all
51260
+ * well": a consumer that needs the answer treats it as pending.
51261
+ */
51262
+ runners: RunnerBootCheckSchema.optional()
50616
51263
  });
50617
51264
  var ServerUpdateCheckResultSchema = object({
50618
51265
  packageName: string(),
@@ -50663,6 +51310,13 @@ var ServerUpdateOutcomeSchema = _enum([
50663
51310
  "refused",
50664
51311
  "failed"
50665
51312
  ]);
51313
+ /** One addon the pre-flight found the target version would refuse (D877). */
51314
+ var UpdateBlockerSchema = object({
51315
+ addonId: string(),
51316
+ reason: string(),
51317
+ /** What the operator does about it. */
51318
+ remedy: string()
51319
+ });
50666
51320
  var ServerUpdateActionResultSchema = object({
50667
51321
  accepted: boolean(),
50668
51322
  targetVersion: string().nullable(),
@@ -50680,14 +51334,27 @@ var ServerUpdateActionResultSchema = object({
50680
51334
  * The failure itself, verbatim enough to act on (the npm output for a
50681
51335
  * staging install). Non-null only for `failed`.
50682
51336
  */
50683
- error: string().nullable().optional()
51337
+ error: string().nullable().optional(),
51338
+ /**
51339
+ * The addons the target version would refuse to start on this node's live
51340
+ * store (D877), present when the pre-flight refused the update
51341
+ * (`reason: 'addon-would-refuse'`). Optional for version skew.
51342
+ */
51343
+ blockers: array(UpdateBlockerSchema).optional()
50684
51344
  });
50685
51345
  method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
50686
51346
  kind: "mutation",
50687
51347
  auth: "admin"
50688
- }), method(object({
50689
- /** Explicit target version; omitted = latest from the registry. */
50690
- version: string().optional() }), ServerUpdateActionResultSchema, {
51348
+ }), method(object({
51349
+ /** Explicit target version; omitted = latest from the registry. */
51350
+ version: string().optional(),
51351
+ /**
51352
+ * Apply even though the pre-flight (D877) found addons the target would
51353
+ * refuse to start. Off by default: those addons stay down until the
51354
+ * remedy is run, which is the incident this gate exists to prevent.
51355
+ */
51356
+ acknowledgeRefusals: boolean().optional()
51357
+ }), ServerUpdateActionResultSchema, {
50691
51358
  kind: "mutation",
50692
51359
  auth: "admin",
50693
51360
  timeoutMs: 16 * 6e4
@@ -53133,7 +53800,9 @@ var ManifestShapeSchema = object({
53133
53800
  var AddonProcessSchema = object({
53134
53801
  pid: number().optional(),
53135
53802
  mode: string(),
53136
- state: string()
53803
+ state: string(),
53804
+ /** Why the runner is `refused` (an addon's own words), when it is (D877). */
53805
+ reason: string().optional()
53137
53806
  });
53138
53807
  /**
53139
53808
  * Per-addon health snapshot from the kernel's AddonHealthMonitor.
@@ -53901,7 +54570,8 @@ var batteryCapability = {
53901
54570
  DeviceType.Button,
53902
54571
  DeviceType.Switch,
53903
54572
  DeviceType.PetFeeder,
53904
- DeviceType.Container
54573
+ DeviceType.Container,
54574
+ DeviceType.Thermostat
53905
54575
  ],
53906
54576
  methods: {
53907
54577
  /**
@@ -54522,6 +55192,12 @@ var ClimateControlStatusSchema = object({
54522
55192
  /** Horizontal louver swing on/off. Null when the device has no
54523
55193
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
54524
55194
  swingHorizontal: boolean().nullable(),
55195
+ /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
55196
+ minTemp: number().nullable().optional(),
55197
+ /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
55198
+ maxTemp: number().nullable().optional(),
55199
+ /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
55200
+ tempStep: number().positive().nullable().optional(),
54525
55201
  /** Ms epoch when the slice was last updated (push or command). */
54526
55202
  lastFetchedAt: number()
54527
55203
  });
@@ -55376,6 +56052,161 @@ onStatusChanged: { data: object({
55376
56052
  * whether persisting is worth a SQLite commit. */
55377
56053
  volatileStateFields: ["lastChangedAt"]
55378
56054
  };
56055
+ /**
56056
+ * Smart-dishwasher cap — program lifecycle, door / remote-start gates,
56057
+ * consumable levels (salt, rinse aid) and common program options.
56058
+ *
56059
+ * Sources: native Bosch Home Connect (`dishwasher` device type). Reusable
56060
+ * by any integration that exposes the same program / option surface.
56061
+ *
56062
+ * `kind: 'poll'` — cloud REST APIs typically have no push channel; the
56063
+ * provider refreshes the slice on an interval and eagerly after commands.
56064
+ */
56065
+ var DishwasherStateSchema = _enum([
56066
+ "idle",
56067
+ "running",
56068
+ "paused",
56069
+ "finished",
56070
+ "error",
56071
+ "unavailable"
56072
+ ]);
56073
+ var DishwasherConsumableLevelSchema = _enum([
56074
+ "ok",
56075
+ "low",
56076
+ "empty"
56077
+ ]);
56078
+ var DishwasherStatusSchema = object({
56079
+ state: DishwasherStateSchema,
56080
+ /** Active program id/name, or null when none selected / unknown. */
56081
+ program: string().nullable(),
56082
+ /** Program tokens the hardware accepts — drives the UI selector. */
56083
+ availablePrograms: array(string()),
56084
+ /** Minutes remaining on the active cycle, or null when not running. */
56085
+ remainingMinutes: number().int().nonnegative().nullable(),
56086
+ /** Cycle completion 0..100, or null when not applicable. */
56087
+ progressPercent: number().min(0).max(100).nullable(),
56088
+ /** True when the door is reported open. */
56089
+ doorOpen: boolean(),
56090
+ /** True when remote start is allowed (door closed, power on, etc.). */
56091
+ remoteStartAllowed: boolean(),
56092
+ /** Main power / switched-on state. */
56093
+ powerOn: boolean(),
56094
+ saltLevel: DishwasherConsumableLevelSchema.nullable(),
56095
+ rinseAidLevel: DishwasherConsumableLevelSchema.nullable(),
56096
+ halfLoad: boolean(),
56097
+ hygienePlus: boolean(),
56098
+ /** Extra dry option when the appliance exposes it. */
56099
+ extraDry: boolean(),
56100
+ /** Intensive zone (lower basket) when the appliance exposes it. */
56101
+ intensiveZone: boolean(),
56102
+ /** VarioSpeed / VarioSpeed+ when the appliance exposes it. */
56103
+ varioSpeedPlus: boolean(),
56104
+ /** Child lock (controls disabled on the appliance). */
56105
+ childLock: boolean(),
56106
+ errorCode: number().nullable(),
56107
+ errorLabel: string().nullable(),
56108
+ lastFetchedAt: number()
56109
+ });
56110
+ var dishwasherCapability = {
56111
+ name: "dishwasher",
56112
+ scope: "device",
56113
+ deviceNative: true,
56114
+ mode: "singleton",
56115
+ deviceTypes: [DeviceType.Dishwasher],
56116
+ methods: {
56117
+ start: method(object({ deviceId: number().int().nonnegative() }), _void(), {
56118
+ kind: "mutation",
56119
+ auth: "protected"
56120
+ }),
56121
+ pause: method(object({ deviceId: number().int().nonnegative() }), _void(), {
56122
+ kind: "mutation",
56123
+ auth: "protected"
56124
+ }),
56125
+ stop: method(object({ deviceId: number().int().nonnegative() }), _void(), {
56126
+ kind: "mutation",
56127
+ auth: "protected"
56128
+ }),
56129
+ setProgram: method(object({
56130
+ deviceId: number().int().nonnegative(),
56131
+ program: string().min(1)
56132
+ }), _void(), {
56133
+ kind: "mutation",
56134
+ auth: "protected"
56135
+ }),
56136
+ setPower: method(object({
56137
+ deviceId: number().int().nonnegative(),
56138
+ on: boolean()
56139
+ }), _void(), {
56140
+ kind: "mutation",
56141
+ auth: "protected"
56142
+ }),
56143
+ setHalfLoad: method(object({
56144
+ deviceId: number().int().nonnegative(),
56145
+ on: boolean()
56146
+ }), _void(), {
56147
+ kind: "mutation",
56148
+ auth: "protected"
56149
+ }),
56150
+ setHygienePlus: method(object({
56151
+ deviceId: number().int().nonnegative(),
56152
+ on: boolean()
56153
+ }), _void(), {
56154
+ kind: "mutation",
56155
+ auth: "protected"
56156
+ }),
56157
+ setExtraDry: method(object({
56158
+ deviceId: number().int().nonnegative(),
56159
+ on: boolean()
56160
+ }), _void(), {
56161
+ kind: "mutation",
56162
+ auth: "protected"
56163
+ }),
56164
+ setIntensiveZone: method(object({
56165
+ deviceId: number().int().nonnegative(),
56166
+ on: boolean()
56167
+ }), _void(), {
56168
+ kind: "mutation",
56169
+ auth: "protected"
56170
+ }),
56171
+ setVarioSpeedPlus: method(object({
56172
+ deviceId: number().int().nonnegative(),
56173
+ on: boolean()
56174
+ }), _void(), {
56175
+ kind: "mutation",
56176
+ auth: "protected"
56177
+ }),
56178
+ setChildLock: method(object({
56179
+ deviceId: number().int().nonnegative(),
56180
+ on: boolean()
56181
+ }), _void(), {
56182
+ kind: "mutation",
56183
+ auth: "protected"
56184
+ }),
56185
+ setOption: method(object({
56186
+ deviceId: number().int().nonnegative(),
56187
+ key: string().min(1),
56188
+ value: union([
56189
+ boolean(),
56190
+ string(),
56191
+ number()
56192
+ ])
56193
+ }), _void(), {
56194
+ kind: "mutation",
56195
+ auth: "protected"
56196
+ })
56197
+ },
56198
+ status: {
56199
+ schema: DishwasherStatusSchema,
56200
+ kind: "poll"
56201
+ },
56202
+ runtimeState: DishwasherStatusSchema,
56203
+ durability: "session",
56204
+ history: { fields: { progressPercent: {
56205
+ kind: "gauge",
56206
+ unit: "%",
56207
+ deadband: 2
56208
+ } } }
56209
+ };
55379
56210
  var DoorbellStatusSchema = object({
55380
56211
  /** Ms epoch of the last press. null = never observed since this provider started. */
55381
56212
  lastPressedAt: number().nullable(),
@@ -56178,6 +57009,32 @@ var gasCapability = {
56178
57009
  * whether persisting is worth a SQLite commit. */
56179
57010
  volatileStateFields: ["lastChangedAt"]
56180
57011
  };
57012
+ var HeatingValveStatusSchema = object({
57013
+ /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
57014
+ position: number().min(0).max(100).nullable(),
57015
+ /** Ms epoch when the slice was last updated (push or poll). */
57016
+ lastFetchedAt: number()
57017
+ });
57018
+ var heatingValveCapability = {
57019
+ name: "heating-valve",
57020
+ scope: "device",
57021
+ deviceNative: true,
57022
+ mode: "singleton",
57023
+ deviceTypes: [DeviceType.Thermostat],
57024
+ methods: {},
57025
+ status: {
57026
+ schema: HeatingValveStatusSchema,
57027
+ kind: "push"
57028
+ },
57029
+ runtimeState: HeatingValveStatusSchema,
57030
+ durability: "session",
57031
+ history: { fields: { position: {
57032
+ kind: "gauge",
57033
+ unit: "%",
57034
+ deadband: 1
57035
+ } } },
57036
+ volatileStateFields: ["lastFetchedAt"]
57037
+ };
56181
57038
  /**
56182
57039
  * Humidifier / dehumidifier cap. Models HA `humidifier.*` entities —
56183
57040
  * an on/off actuator with an optional target-humidity setpoint and an
@@ -57310,7 +58167,19 @@ var TlsStatusSchema = object({
57310
58167
  sans: array(string()),
57311
58168
  caCertPem: string().nullable(),
57312
58169
  reissueError: string().nullable(),
57313
- restartRequired: boolean()
58170
+ restartRequired: boolean(),
58171
+ /** An uploaded certificate is served but no CA is kept for it and it is not publicly signed (D873). */
58172
+ caMissing: boolean(),
58173
+ /** Does the hub's own generated CA carry a critical name constraint? `null` while it has none. */
58174
+ localCaConstrained: boolean().nullable(),
58175
+ /** ISO time the generated CA was replaced by a constrained one; devices must re-download it. */
58176
+ caReplacedAt: string().nullable(),
58177
+ /** The uploaded certificate kept on disk, selectable again after "Revert to generated". */
58178
+ storedUpload: object({
58179
+ leafFingerprintSha256: string(),
58180
+ validTo: string(),
58181
+ hasCa: boolean()
58182
+ }).nullable()
57314
58183
  });
57315
58184
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
57316
58185
  /**
@@ -57366,6 +58235,9 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
57366
58235
  }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
57367
58236
  kind: "mutation",
57368
58237
  auth: "admin"
58238
+ }), method(_void(), TlsStatusSchema, {
58239
+ kind: "mutation",
58240
+ auth: "admin"
57369
58241
  });
57370
58242
  var LockControlStatusSchema = object({
57371
58243
  /** Lifecycle state of the lock. `jammed` means the motor reported
@@ -63859,7 +64731,11 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
63859
64731
  kind: "mutation",
63860
64732
  auth: "admin",
63861
64733
  access: "delete"
63862
- }), method(object({ userId: string() }), TotpStatusSchema, { auth: "admin" }), method(object({
64734
+ }), method(object({ userId: string() }), TotpStatusSchema, { auth: "admin" }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
64735
+ kind: "mutation",
64736
+ auth: "admin",
64737
+ access: "create"
64738
+ }), method(object({
63863
64739
  userId: string(),
63864
64740
  code: string()
63865
64741
  }), object({ valid: boolean() }), {
@@ -65579,6 +66455,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
65579
66455
  dayNight: dayNightCapability,
65580
66456
  deviceDiscovery: deviceDiscoveryCapability,
65581
66457
  deviceStatus: deviceStatusCapability,
66458
+ dishwasher: dishwasherCapability,
65582
66459
  doorbell: doorbellCapability,
65583
66460
  enumSensor: enumSensorCapability,
65584
66461
  eventEmitter: eventEmitterCapability,
@@ -65586,6 +66463,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
65586
66463
  featureProbe: featureProbeCapability,
65587
66464
  flood: floodCapability,
65588
66465
  gas: gasCapability,
66466
+ heatingValve: heatingValveCapability,
65589
66467
  humidifier: humidifierCapability,
65590
66468
  humiditySensor: humiditySensorCapability,
65591
66469
  image: imageCapability,
@@ -65671,13 +66549,14 @@ var BaseDevice = class {
65671
66549
  get state() {
65672
66550
  if (!this._stateProxyCache) {
65673
66551
  const cache = {};
65674
- const handler = { get: (_target, key) => {
66552
+ const handler = { get: (target, key) => {
65675
66553
  const k = key;
65676
- if (k in cache) return cache[k];
66554
+ const existing = Reflect.get(target, k);
66555
+ if (existing !== void 0) return existing;
65677
66556
  const cap = DEVICE_LOCAL_STATE_CAPS[k];
65678
66557
  if (!cap) return void 0;
65679
66558
  const proxy = this.sliceProxy(cap);
65680
- cache[k] = proxy;
66559
+ Reflect.set(target, k, proxy);
65681
66560
  return proxy;
65682
66561
  } };
65683
66562
  this._stateProxyCache = new Proxy(cache, handler);
@@ -65876,7 +66755,7 @@ var BaseDevice = class {
65876
66755
  async updateSourceInfo(patch) {
65877
66756
  const next = mergeSourceInfo(this.sourceInfo, patch);
65878
66757
  this._sourceInfoCache = Object.freeze({ ...next });
65879
- const action = this.ctx.api?.deviceManager?.setMetadata;
66758
+ const action = this.ctx.api.deviceManager?.setMetadata;
65880
66759
  if (action) try {
65881
66760
  await action.mutate({
65882
66761
  deviceId: this.id,
@@ -65911,7 +66790,7 @@ var BaseDevice = class {
65911
66790
  * device boot on a meta refresh.
65912
66791
  */
65913
66792
  async refreshFeatures() {
65914
- const action = this.ctx.api?.deviceManager?.registerDevice;
66793
+ const action = this.ctx.api.deviceManager?.registerDevice;
65915
66794
  if (!action) return;
65916
66795
  try {
65917
66796
  await action.mutate({
@@ -66310,6 +67189,12 @@ var BaseDeviceProvider = class extends BaseAddon {
66310
67189
  provider: this
66311
67190
  }];
66312
67191
  }
67192
+ /** Flatten `super.onInitialize()` whether it returned an array or an envelope. */
67193
+ providerRegistrationsFromSuper(result) {
67194
+ if (Array.isArray(result)) return result;
67195
+ if (result !== void 0 && result.providers !== void 0) return [...result.providers];
67196
+ return [];
67197
+ }
66313
67198
  async onShutdown() {
66314
67199
  this._restoreShuttingDown = true;
66315
67200
  this.cancelRestoreRetries();
@@ -66836,7 +67721,7 @@ var BaseDeviceProvider = class extends BaseAddon {
66836
67721
  return toDeviceSummary(device, this.addonId);
66837
67722
  }
66838
67723
  };
66839
- DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
67724
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher;
66840
67725
  new Set(Object.values(DeviceType));
66841
67726
  Object.freeze({
66842
67727
  "accessories.getStatus": {
@@ -67985,6 +68870,12 @@ Object.freeze({
67985
68870
  addonId: null,
67986
68871
  access: "view"
67987
68872
  },
68873
+ "coreBlocks.migrateRawSecrets": {
68874
+ capName: "core-blocks",
68875
+ capScope: "system",
68876
+ addonId: null,
68877
+ access: "create"
68878
+ },
67988
68879
  "coreBlocks.restart": {
67989
68880
  capName: "core-blocks",
67990
68881
  capScope: "system",
@@ -69251,6 +70142,84 @@ Object.freeze({
69251
70142
  addonId: null,
69252
70143
  access: "view"
69253
70144
  },
70145
+ "dishwasher.getStatus": {
70146
+ capName: "dishwasher",
70147
+ capScope: "device",
70148
+ addonId: null,
70149
+ access: "view"
70150
+ },
70151
+ "dishwasher.pause": {
70152
+ capName: "dishwasher",
70153
+ capScope: "device",
70154
+ addonId: null,
70155
+ access: "create"
70156
+ },
70157
+ "dishwasher.setChildLock": {
70158
+ capName: "dishwasher",
70159
+ capScope: "device",
70160
+ addonId: null,
70161
+ access: "create"
70162
+ },
70163
+ "dishwasher.setExtraDry": {
70164
+ capName: "dishwasher",
70165
+ capScope: "device",
70166
+ addonId: null,
70167
+ access: "create"
70168
+ },
70169
+ "dishwasher.setHalfLoad": {
70170
+ capName: "dishwasher",
70171
+ capScope: "device",
70172
+ addonId: null,
70173
+ access: "create"
70174
+ },
70175
+ "dishwasher.setHygienePlus": {
70176
+ capName: "dishwasher",
70177
+ capScope: "device",
70178
+ addonId: null,
70179
+ access: "create"
70180
+ },
70181
+ "dishwasher.setIntensiveZone": {
70182
+ capName: "dishwasher",
70183
+ capScope: "device",
70184
+ addonId: null,
70185
+ access: "create"
70186
+ },
70187
+ "dishwasher.setOption": {
70188
+ capName: "dishwasher",
70189
+ capScope: "device",
70190
+ addonId: null,
70191
+ access: "create"
70192
+ },
70193
+ "dishwasher.setPower": {
70194
+ capName: "dishwasher",
70195
+ capScope: "device",
70196
+ addonId: null,
70197
+ access: "create"
70198
+ },
70199
+ "dishwasher.setProgram": {
70200
+ capName: "dishwasher",
70201
+ capScope: "device",
70202
+ addonId: null,
70203
+ access: "create"
70204
+ },
70205
+ "dishwasher.setVarioSpeedPlus": {
70206
+ capName: "dishwasher",
70207
+ capScope: "device",
70208
+ addonId: null,
70209
+ access: "create"
70210
+ },
70211
+ "dishwasher.start": {
70212
+ capName: "dishwasher",
70213
+ capScope: "device",
70214
+ addonId: null,
70215
+ access: "create"
70216
+ },
70217
+ "dishwasher.stop": {
70218
+ capName: "dishwasher",
70219
+ capScope: "device",
70220
+ addonId: null,
70221
+ access: "create"
70222
+ },
69254
70223
  "doorbell.getStatus": {
69255
70224
  capName: "doorbell",
69256
70225
  capScope: "device",
@@ -69461,6 +70430,12 @@ Object.freeze({
69461
70430
  addonId: null,
69462
70431
  access: "view"
69463
70432
  },
70433
+ "heatingValve.getStatus": {
70434
+ capName: "heating-valve",
70435
+ capScope: "device",
70436
+ addonId: null,
70437
+ access: "view"
70438
+ },
69464
70439
  "humidifier.getStatus": {
69465
70440
  capName: "humidifier",
69466
70441
  capScope: "device",
@@ -69671,6 +70646,12 @@ Object.freeze({
69671
70646
  addonId: null,
69672
70647
  access: "create"
69673
70648
  },
70649
+ "keychainAdmin.importSecret": {
70650
+ capName: "keychain-admin",
70651
+ capScope: "system",
70652
+ addonId: null,
70653
+ access: "create"
70654
+ },
69674
70655
  "keychainAdmin.listAudit": {
69675
70656
  capName: "keychain-admin",
69676
70657
  capScope: "system",
@@ -69839,6 +70820,12 @@ Object.freeze({
69839
70820
  addonId: null,
69840
70821
  access: "view"
69841
70822
  },
70823
+ "llm.migrateRawSecrets": {
70824
+ capName: "llm",
70825
+ capScope: "system",
70826
+ addonId: null,
70827
+ access: "create"
70828
+ },
69842
70829
  "llm.resolveModelRef": {
69843
70830
  capName: "llm",
69844
70831
  capScope: "system",
@@ -70067,6 +71054,12 @@ Object.freeze({
70067
71054
  addonId: null,
70068
71055
  access: "create"
70069
71056
  },
71057
+ "localNetwork.useStoredUploadedCertificate": {
71058
+ capName: "local-network",
71059
+ capScope: "system",
71060
+ addonId: null,
71061
+ access: "create"
71062
+ },
70070
71063
  "lockControl.getStatus": {
70071
71064
  capName: "lock-control",
70072
71065
  capScope: "device",
@@ -72719,6 +73712,30 @@ Object.freeze({
72719
73712
  addonId: null,
72720
73713
  access: "create"
72721
73714
  },
73715
+ "recordEntries.deleteEntry": {
73716
+ capName: "record-entries",
73717
+ capScope: "system",
73718
+ addonId: null,
73719
+ access: "delete"
73720
+ },
73721
+ "recordEntries.listEntries": {
73722
+ capName: "record-entries",
73723
+ capScope: "system",
73724
+ addonId: null,
73725
+ access: "view"
73726
+ },
73727
+ "recordEntries.listEntryKinds": {
73728
+ capName: "record-entries",
73729
+ capScope: "system",
73730
+ addonId: null,
73731
+ access: "view"
73732
+ },
73733
+ "recordEntries.putEntry": {
73734
+ capName: "record-entries",
73735
+ capScope: "system",
73736
+ addonId: null,
73737
+ access: "create"
73738
+ },
72722
73739
  "recording.getAvailability": {
72723
73740
  capName: "recording",
72724
73741
  capScope: "device",
@@ -73019,6 +74036,102 @@ Object.freeze({
73019
74036
  addonId: null,
73020
74037
  access: "view"
73021
74038
  },
74039
+ "records.aggregate": {
74040
+ capName: "records",
74041
+ capScope: "system",
74042
+ addonId: null,
74043
+ access: "view"
74044
+ },
74045
+ "records.allocateId": {
74046
+ capName: "records",
74047
+ capScope: "system",
74048
+ addonId: null,
74049
+ access: "create"
74050
+ },
74051
+ "records.count": {
74052
+ capName: "records",
74053
+ capScope: "system",
74054
+ addonId: null,
74055
+ access: "view"
74056
+ },
74057
+ "records.declareKinds": {
74058
+ capName: "records",
74059
+ capScope: "system",
74060
+ addonId: null,
74061
+ access: "create"
74062
+ },
74063
+ "records.delete": {
74064
+ capName: "records",
74065
+ capScope: "system",
74066
+ addonId: null,
74067
+ access: "delete"
74068
+ },
74069
+ "records.deleteDevice": {
74070
+ capName: "records",
74071
+ capScope: "system",
74072
+ addonId: null,
74073
+ access: "delete"
74074
+ },
74075
+ "records.deleteMany": {
74076
+ capName: "records",
74077
+ capScope: "system",
74078
+ addonId: null,
74079
+ access: "delete"
74080
+ },
74081
+ "records.deleteWhere": {
74082
+ capName: "records",
74083
+ capScope: "system",
74084
+ addonId: null,
74085
+ access: "delete"
74086
+ },
74087
+ "records.get": {
74088
+ capName: "records",
74089
+ capScope: "system",
74090
+ addonId: null,
74091
+ access: "view"
74092
+ },
74093
+ "records.getMany": {
74094
+ capName: "records",
74095
+ capScope: "system",
74096
+ addonId: null,
74097
+ access: "view"
74098
+ },
74099
+ "records.histogram": {
74100
+ capName: "records",
74101
+ capScope: "system",
74102
+ addonId: null,
74103
+ access: "view"
74104
+ },
74105
+ "records.patch": {
74106
+ capName: "records",
74107
+ capScope: "system",
74108
+ addonId: null,
74109
+ access: "create"
74110
+ },
74111
+ "records.put": {
74112
+ capName: "records",
74113
+ capScope: "system",
74114
+ addonId: null,
74115
+ access: "create"
74116
+ },
74117
+ "records.putMany": {
74118
+ capName: "records",
74119
+ capScope: "system",
74120
+ addonId: null,
74121
+ access: "create"
74122
+ },
74123
+ "records.query": {
74124
+ capName: "records",
74125
+ capScope: "system",
74126
+ addonId: null,
74127
+ access: "view"
74128
+ },
74129
+ "records.secretRefs": {
74130
+ capName: "records",
74131
+ capScope: "system",
74132
+ addonId: null,
74133
+ access: "view"
74134
+ },
73022
74135
  "sceneMonitor.captureReference": {
73023
74136
  capName: "scene-monitor",
73024
74137
  capScope: "device",
@@ -73451,6 +74564,48 @@ Object.freeze({
73451
74564
  addonId: null,
73452
74565
  access: "create"
73453
74566
  },
74567
+ "storageAdmin.adoptRecords": {
74568
+ capName: "storage-admin",
74569
+ capScope: "system",
74570
+ addonId: null,
74571
+ access: "create"
74572
+ },
74573
+ "storageAdmin.dropCollection": {
74574
+ capName: "storage-admin",
74575
+ capScope: "system",
74576
+ addonId: null,
74577
+ access: "delete"
74578
+ },
74579
+ "storageAdmin.isAdoptedLegacyTable": {
74580
+ capName: "storage-admin",
74581
+ capScope: "system",
74582
+ addonId: null,
74583
+ access: "view"
74584
+ },
74585
+ "storageAdmin.listKinds": {
74586
+ capName: "storage-admin",
74587
+ capScope: "system",
74588
+ addonId: null,
74589
+ access: "view"
74590
+ },
74591
+ "storageAdmin.listTables": {
74592
+ capName: "storage-admin",
74593
+ capScope: "system",
74594
+ addonId: null,
74595
+ access: "view"
74596
+ },
74597
+ "storageAdmin.readLegacyCollection": {
74598
+ capName: "storage-admin",
74599
+ capScope: "system",
74600
+ addonId: null,
74601
+ access: "view"
74602
+ },
74603
+ "storageAdmin.readRecords": {
74604
+ capName: "storage-admin",
74605
+ capScope: "system",
74606
+ addonId: null,
74607
+ access: "view"
74608
+ },
73454
74609
  "storageEvictable.evict": {
73455
74610
  capName: "storage-evictable",
73456
74611
  capScope: "system",
@@ -74363,6 +75518,12 @@ Object.freeze({
74363
75518
  addonId: null,
74364
75519
  access: "view"
74365
75520
  },
75521
+ "userManagement.migrateRawSecrets": {
75522
+ capName: "user-management",
75523
+ capScope: "system",
75524
+ addonId: null,
75525
+ access: "create"
75526
+ },
74366
75527
  "userManagement.oauthExchangeCode": {
74367
75528
  capName: "user-management",
74368
75529
  capScope: "system",
@@ -74862,7 +76023,7 @@ Object.freeze({
74862
76023
  access: "create"
74863
76024
  }
74864
76025
  });
74865
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder;
76026
+ DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.Dishwasher;
74866
76027
  DeviceFeature.BatteryOperated;
74867
76028
  Object.freeze({
74868
76029
  "accessories.getStatus": [{
@@ -75686,6 +76847,71 @@ Object.freeze({
75686
76847
  form: "single",
75687
76848
  optional: false
75688
76849
  }],
76850
+ "dishwasher.getStatus": [{
76851
+ name: "deviceId",
76852
+ form: "single",
76853
+ optional: false
76854
+ }],
76855
+ "dishwasher.pause": [{
76856
+ name: "deviceId",
76857
+ form: "single",
76858
+ optional: false
76859
+ }],
76860
+ "dishwasher.setChildLock": [{
76861
+ name: "deviceId",
76862
+ form: "single",
76863
+ optional: false
76864
+ }],
76865
+ "dishwasher.setExtraDry": [{
76866
+ name: "deviceId",
76867
+ form: "single",
76868
+ optional: false
76869
+ }],
76870
+ "dishwasher.setHalfLoad": [{
76871
+ name: "deviceId",
76872
+ form: "single",
76873
+ optional: false
76874
+ }],
76875
+ "dishwasher.setHygienePlus": [{
76876
+ name: "deviceId",
76877
+ form: "single",
76878
+ optional: false
76879
+ }],
76880
+ "dishwasher.setIntensiveZone": [{
76881
+ name: "deviceId",
76882
+ form: "single",
76883
+ optional: false
76884
+ }],
76885
+ "dishwasher.setOption": [{
76886
+ name: "deviceId",
76887
+ form: "single",
76888
+ optional: false
76889
+ }],
76890
+ "dishwasher.setPower": [{
76891
+ name: "deviceId",
76892
+ form: "single",
76893
+ optional: false
76894
+ }],
76895
+ "dishwasher.setProgram": [{
76896
+ name: "deviceId",
76897
+ form: "single",
76898
+ optional: false
76899
+ }],
76900
+ "dishwasher.setVarioSpeedPlus": [{
76901
+ name: "deviceId",
76902
+ form: "single",
76903
+ optional: false
76904
+ }],
76905
+ "dishwasher.start": [{
76906
+ name: "deviceId",
76907
+ form: "single",
76908
+ optional: false
76909
+ }],
76910
+ "dishwasher.stop": [{
76911
+ name: "deviceId",
76912
+ form: "single",
76913
+ optional: false
76914
+ }],
75689
76915
  "doorbell.getStatus": [{
75690
76916
  name: "deviceId",
75691
76917
  form: "single",
@@ -75755,6 +76981,11 @@ Object.freeze({
75755
76981
  form: "single",
75756
76982
  optional: false
75757
76983
  }],
76984
+ "heatingValve.getStatus": [{
76985
+ name: "deviceId",
76986
+ form: "single",
76987
+ optional: false
76988
+ }],
75758
76989
  "humidifier.getStatus": [{
75759
76990
  name: "deviceId",
75760
76991
  form: "single",
@@ -77039,6 +78270,37 @@ Object.freeze({
77039
78270
  form: "single",
77040
78271
  optional: false
77041
78272
  }],
78273
+ "records.count": [{
78274
+ name: "deviceId",
78275
+ form: "single",
78276
+ optional: true
78277
+ }],
78278
+ "records.deleteDevice": [{
78279
+ name: "deviceId",
78280
+ form: "single",
78281
+ optional: false
78282
+ }],
78283
+ "records.deleteWhere": [{
78284
+ name: "deviceId",
78285
+ form: "single",
78286
+ optional: true
78287
+ }],
78288
+ "records.histogram": [{
78289
+ name: "deviceId",
78290
+ form: "single",
78291
+ optional: true
78292
+ }],
78293
+ "records.putMany": [{
78294
+ name: "records",
78295
+ form: "object-array",
78296
+ optional: false,
78297
+ itemField: "deviceId"
78298
+ }],
78299
+ "records.query": [{
78300
+ name: "deviceId",
78301
+ form: "single",
78302
+ optional: true
78303
+ }],
77042
78304
  "sceneMonitor.captureReference": [{
77043
78305
  name: "deviceId",
77044
78306
  form: "single",
@@ -77155,6 +78417,17 @@ Object.freeze({
77155
78417
  form: "single",
77156
78418
  optional: false
77157
78419
  }],
78420
+ "storageAdmin.adoptRecords": [{
78421
+ name: "records",
78422
+ form: "object-array",
78423
+ optional: false,
78424
+ itemField: "deviceId"
78425
+ }],
78426
+ "storageAdmin.readRecords": [{
78427
+ name: "deviceId",
78428
+ form: "single",
78429
+ optional: true
78430
+ }],
77158
78431
  "streamBroker.acquireEgressTranscode": [{
77159
78432
  name: "deviceId",
77160
78433
  form: "single",
@@ -80021,7 +81294,7 @@ var TuyaProviderAddon = class extends BaseDeviceProvider {
80021
81294
  super({});
80022
81295
  }
80023
81296
  async onInitialize() {
80024
- const regs = await super.onInitialize();
81297
+ const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
80025
81298
  this.clients = new TuyaClientRegistry(this.ctx.logger, tuyaConnections);
80026
81299
  this.clients.setOnConnected((integrationId) => {
80027
81300
  this.ctx.logger.info("Tuya: integration connected", { meta: { integrationId } });