@camstack/addon-provider-tuya 0.2.125 → 0.2.126

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 +1318 -129
  2. package/dist/addon.mjs +1318 -129
  3. package/package.json +1 -1
package/dist/addon.mjs CHANGED
@@ -818,7 +818,7 @@ async function discoverDevices(options = {}, binder = createDgramBinder()) {
818
818
  return [...byId.values()];
819
819
  }
820
820
  //#endregion
821
- //#region ../types/dist/event-category-C4hVnRo8.mjs
821
+ //#region ../types/dist/event-category-BvOFi6PY.mjs
822
822
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
823
823
  EventCategory["SystemBoot"] = "system.boot";
824
824
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -882,6 +882,24 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
882
882
  * consumes this category and maps it to the `addon-crash-loop` kind.
883
883
  */
884
884
  EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
885
+ /**
886
+ * A runner whose addon refused to start with a typed reason (D877) - parked
887
+ * in `refused`, never counted by the crash breaker, retried on operator
888
+ * action. One event per refusal.
889
+ */
890
+ EventCategory["AddonRunnerRefused"] = "addon.runner-refused";
891
+ /**
892
+ * A runner settled on a state (D877): `running` (READY), `crashed`, `failed` or
893
+ * `refused`. In-process telemetry for the boot check (re-evaluate while any
894
+ * runner is still pending) and the alert centre (close a recovered runner).
895
+ */
896
+ EventCategory["ProcessStateChanged"] = "process.state-changed";
897
+ /**
898
+ * The node's one-shot runner boot check (D877): planned runners against the
899
+ * process service, `degraded` when any is failed, refused or missing. The
900
+ * alert centre turns it into one alert and a direct critical push.
901
+ */
902
+ EventCategory["SystemRunnerCheck"] = "system.runner-check";
885
903
  EventCategory["AddonError"] = "addon.error";
886
904
  EventCategory["AddonPageReady"] = "addon.page-ready";
887
905
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -6206,7 +6224,7 @@ var ZodIssueCode = {
6206
6224
  var ZodFirstPartyTypeKind;
6207
6225
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6208
6226
  //#endregion
6209
- //#region ../types/dist/sleep-DRqxtc0C.mjs
6227
+ //#region ../types/dist/sleep-BOjuyoPC.mjs
6210
6228
  /**
6211
6229
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6212
6230
  * window to float samples (D455).
@@ -6323,7 +6341,7 @@ function isKeychainStoredField(field) {
6323
6341
  var SECRET_CLEAR = { $secretClear: true };
6324
6342
  /** The `'***'` token D634 redaction writes for non-admins. */
6325
6343
  var LEGACY_DEVICE_REDACTION = "***";
6326
- function isRecord$2(value) {
6344
+ function isRecord$5(value) {
6327
6345
  return typeof value === "object" && value !== null && !Array.isArray(value);
6328
6346
  }
6329
6347
  function makeSecretMarker(ref) {
@@ -6334,12 +6352,12 @@ function makeSecretMarker(ref) {
6334
6352
  }
6335
6353
  /** A stored marker: an object whose `$secret` is a non-empty string. */
6336
6354
  function isSecretMarker(value) {
6337
- if (!isRecord$2(value)) return false;
6355
+ if (!isRecord$5(value)) return false;
6338
6356
  const ref = value["$secret"];
6339
6357
  return typeof ref === "string" && ref.length > 0;
6340
6358
  }
6341
6359
  function isSecretClear(value) {
6342
- return isRecord$2(value) && value["$secretClear"] === true;
6360
+ return isRecord$5(value) && value["$secretClear"] === true;
6343
6361
  }
6344
6362
  /**
6345
6363
  * The UI view of a stored secret field value: a marker becomes "set", inline
@@ -6949,7 +6967,7 @@ var KeychainWriteRefusedError = class extends Error {
6949
6967
  }
6950
6968
  };
6951
6969
  var KEEP = { kind: "keep" };
6952
- function isRecord$1(value) {
6970
+ function isRecord$4(value) {
6953
6971
  return typeof value === "object" && value !== null && !Array.isArray(value);
6954
6972
  }
6955
6973
  function defaultMintId() {
@@ -6962,7 +6980,7 @@ function collectSecretMarkerRefs(value, out = []) {
6962
6980
  return out;
6963
6981
  }
6964
6982
  if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
6965
- else if (isRecord$1(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6983
+ else if (isRecord$4(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6966
6984
  return out;
6967
6985
  }
6968
6986
  async function storedValueEquals(secrets, marker, candidate) {
@@ -7017,7 +7035,7 @@ function rowId(row, idKey) {
7017
7035
  return null;
7018
7036
  }
7019
7037
  async function writeArray(ctx, node, incoming, stored, fieldPath) {
7020
- const storedRows = Array.isArray(stored) ? stored.filter(isRecord$1) : [];
7038
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord$4) : [];
7021
7039
  if (!Array.isArray(incoming)) {
7022
7040
  ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
7023
7041
  return {
@@ -7033,7 +7051,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
7033
7051
  const kept = /* @__PURE__ */ new Set();
7034
7052
  const rows = [];
7035
7053
  for (const row of incoming) {
7036
- if (!isRecord$1(row)) {
7054
+ if (!isRecord$4(row)) {
7037
7055
  rows.push(row);
7038
7056
  continue;
7039
7057
  }
@@ -7058,7 +7076,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
7058
7076
  case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
7059
7077
  case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
7060
7078
  case "object": {
7061
- if (!isRecord$1(incoming)) {
7079
+ if (!isRecord$4(incoming)) {
7062
7080
  if (incoming === void 0) return KEEP;
7063
7081
  ctx.deletes.push(...collectSecretMarkerRefs(stored));
7064
7082
  return {
@@ -7066,7 +7084,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
7066
7084
  value: incoming
7067
7085
  };
7068
7086
  }
7069
- const storedObj = isRecord$1(stored) ? stored : {};
7087
+ const storedObj = isRecord$4(stored) ? stored : {};
7070
7088
  return {
7071
7089
  kind: "value",
7072
7090
  value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
@@ -7189,7 +7207,7 @@ async function resolveSecretMarkers(value, secrets, options = {}) {
7189
7207
  value: items
7190
7208
  };
7191
7209
  }
7192
- if (isRecord$1(v)) {
7210
+ if (isRecord$4(v)) {
7193
7211
  const out = {};
7194
7212
  for (const [k, child] of Object.entries(v)) {
7195
7213
  const s = substitute(child);
@@ -7221,7 +7239,7 @@ var SecretOverwriteRefusedError = class extends Error {
7221
7239
  this.name = "SecretOverwriteRefusedError";
7222
7240
  }
7223
7241
  };
7224
- function isRecord(value) {
7242
+ function isRecord$3(value) {
7225
7243
  return typeof value === "object" && value !== null && !Array.isArray(value);
7226
7244
  }
7227
7245
  function resolvedValue(pass, marker) {
@@ -7250,7 +7268,7 @@ function inArray(pass, incoming, stored) {
7250
7268
  return incoming.map((item, index) => {
7251
7269
  const twin = stored[index];
7252
7270
  if (isSecretMarker(twin)) return typeof item === "string" && item === resolvedValue(pass, twin) ? twin : item;
7253
- if (isRecord(item) && isRecord(twin)) return inArrayRow(pass, item, twin);
7271
+ if (isRecord$3(item) && isRecord$3(twin)) return inArrayRow(pass, item, twin);
7254
7272
  if (Array.isArray(item) && Array.isArray(twin)) return inArray(pass, item, twin);
7255
7273
  return item;
7256
7274
  });
@@ -7261,7 +7279,7 @@ function inArrayRow(pass, incoming, stored) {
7261
7279
  const twin = stored[key];
7262
7280
  if (isSecretMarker(twin)) {
7263
7281
  if (typeof value === "string" && value === resolvedValue(pass, twin)) out[key] = twin;
7264
- } else if (isRecord(value) && isRecord(twin)) out[key] = inArrayRow(pass, value, twin);
7282
+ } else if (isRecord$3(value) && isRecord$3(twin)) out[key] = inArrayRow(pass, value, twin);
7265
7283
  else if (Array.isArray(value) && Array.isArray(twin)) out[key] = inArray(pass, value, twin);
7266
7284
  }
7267
7285
  return out;
@@ -7269,7 +7287,7 @@ function inArrayRow(pass, incoming, stored) {
7269
7287
  async function walk(pass, incoming, stored) {
7270
7288
  if (isSecretMarker(stored)) return atMarker(pass, incoming, stored);
7271
7289
  if (Array.isArray(incoming) && Array.isArray(stored)) return inArray(pass, incoming, stored);
7272
- if (!isRecord(incoming) || !isRecord(stored)) return incoming;
7290
+ if (!isRecord$3(incoming) || !isRecord$3(stored)) return incoming;
7273
7291
  const out = { ...incoming };
7274
7292
  for (const [key, value] of Object.entries(incoming)) out[key] = await walk(pass, value, stored[key]);
7275
7293
  return out;
@@ -7300,7 +7318,7 @@ async function restoreUnchangedSecrets(input) {
7300
7318
  function isJsonEqual(a, b) {
7301
7319
  if (Object.is(a, b)) return true;
7302
7320
  if (Array.isArray(a)) return Array.isArray(b) && a.length === b.length && a.every((v, i) => isJsonEqual(v, b[i]));
7303
- if (isRecord(a) && isRecord(b)) {
7321
+ if (isRecord$3(a) && isRecord$3(b)) {
7304
7322
  const keys = Object.keys(a);
7305
7323
  if (keys.length !== Object.keys(b).length) return false;
7306
7324
  return keys.every((k) => Object.prototype.hasOwnProperty.call(b, k) && isJsonEqual(a[k], b[k]));
@@ -7324,7 +7342,7 @@ function markerKeysOf(blob, owner) {
7324
7342
  }
7325
7343
  function nodeFromStored(value, owner) {
7326
7344
  if (isSecretMarker(value)) return isSecretRefOwnedBy(value.$secret, owner) ? { kind: "leaf" } : null;
7327
- if (!isRecord(value)) return null;
7345
+ if (!isRecord$3(value)) return null;
7328
7346
  const children = /* @__PURE__ */ new Map();
7329
7347
  for (const [key, child] of Object.entries(value)) {
7330
7348
  const node = nodeFromStored(child, owner);
@@ -7457,7 +7475,6 @@ function createHandle(raw, opts) {
7457
7475
  const writeAddon = (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p));
7458
7476
  const readAddonStoreResult = raw.readAddonStoreResult;
7459
7477
  const readAddonStoreKeyResult = raw.readAddonStoreKeyResult;
7460
- const readDeviceStoreBatch = raw.readDeviceStoreBatch;
7461
7478
  const overrides = {
7462
7479
  readAddonStore: async () => resolveRead("addon", await raw.readAddonStore()),
7463
7480
  ...readAddonStoreResult !== void 0 ? { readAddonStoreResult: async () => {
@@ -7481,12 +7498,12 @@ function createHandle(raw, opts) {
7481
7498
  };
7482
7499
  } } : {},
7483
7500
  readDeviceStore: async (deviceId) => resolveRead(`device:${deviceId}`, await raw.readDeviceStore(deviceId)),
7484
- ...readDeviceStoreBatch !== void 0 ? { readDeviceStoreBatch: async (deviceIds) => {
7485
- const rows = await readDeviceStoreBatch.call(raw, deviceIds);
7501
+ readDeviceStoreBatch: async (deviceIds) => {
7502
+ const rows = await raw.readDeviceStoreBatch(deviceIds);
7486
7503
  const out = /* @__PURE__ */ new Map();
7487
7504
  for (const [deviceId, blob] of rows) out.set(deviceId, await resolveRead(`device:${deviceId}`, blob));
7488
7505
  return out;
7489
- } } : {},
7506
+ },
7490
7507
  writeAddonStore: async (patch) => {
7491
7508
  await writeAddon(patch);
7492
7509
  },
@@ -8113,7 +8130,7 @@ var BaseAddon = class {
8113
8130
  if (!ctx) return;
8114
8131
  const addonId = ctx.id;
8115
8132
  setImmediate(() => {
8116
- ctx.api.addons?.restartAddon?.mutate({ addonId }).then(() => {
8133
+ ctx.api.addons.restartAddon.mutate({ addonId }).then(() => {
8117
8134
  ctx.logger.info("addon auto-restart triggered by restart-required setting change", { meta: { changedFields: patchKeys.filter((k) => restartKeys.has(k)) } });
8118
8135
  }).catch((err) => {
8119
8136
  ctx.logger.error("addon auto-restart failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
@@ -8313,7 +8330,7 @@ var BaseAddon = class {
8313
8330
  return this._addonSecretTree;
8314
8331
  }
8315
8332
  deviceSecretTree() {
8316
- this._deviceSecretTree ??= secretTreeFromSchema(this.deviceSettingsSchema());
8333
+ this._deviceSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.deviceSettingsSchema()), this.secretStatePaths);
8317
8334
  return this._deviceSecretTree;
8318
8335
  }
8319
8336
  /**
@@ -8389,42 +8406,135 @@ var BaseAddon = class {
8389
8406
  this[timerOf] = timer;
8390
8407
  }
8391
8408
  /**
8392
- * Wrap `ctx.settings.readAddonStore()` with a short retry budget so a
8393
- * transient settings-store outage (mid-restart of sqlite-settings, tsx-watch
8394
- * swap, or any race where the SqliteSettingsBackend is between shutdown and
8395
- * re-initialize) doesn't propagate up into `initialize()` and leave the
8409
+ * Wrap `ctx.settings.readAddonStore()` with a retry budget so a transient
8410
+ * settings-store outage doesn't propagate up into `initialize()` and leave the
8396
8411
  * addon permanently broken.
8397
8412
  *
8398
- * Retry only the two known infra fingerprints. Anything else propagates so
8399
- * real bugs surface immediately. After the budget expires we fall back to
8400
- * `{}` (defaults) — the addon's first successful patch will rehydrate.
8413
+ * Two kinds of outage, two budgets:
8414
+ * - the store's own restart fingerprints (`SqliteSettingsBackend not
8415
+ * initialized`, `provider not available`): a short budget, then the addon
8416
+ * falls back to `{}` (defaults) - its first successful patch rehydrates;
8417
+ * - a TRANSPORT failure (the hub is restarting while this runner boots:
8418
+ * `discovery deadline exceeded`, `parent could not route`, a refused or
8419
+ * timed-out connection). Since D870 a runner's settings read THROWS instead
8420
+ * of answering `{}`, so without a budget a hub restart ends in a respawn
8421
+ * loop. A long, bounded budget; only after it the error propagates - the
8422
+ * addon never runs on defaults for a store it could not ask.
8423
+ * Anything else propagates immediately so real bugs surface.
8401
8424
  */
8402
8425
  async readAddonStoreWithRetry() {
8403
8426
  const settings = this.rawSettings();
8404
8427
  if (!settings) return {};
8405
- const delaysMs = [
8406
- 150,
8407
- 350,
8408
- 600,
8409
- 900
8410
- ];
8411
8428
  let lastErr;
8412
- for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
8429
+ for (let attempt = 0;; attempt++) try {
8413
8430
  const stored = await settings.readAddonStore() ?? {};
8414
8431
  this.settingsStoreReady = true;
8415
8432
  return stored;
8416
8433
  } catch (err) {
8417
8434
  lastErr = err;
8418
- const msg = err instanceof Error ? err.message : String(err);
8419
- if (!(msg.includes("SqliteSettingsBackend not initialized") || msg.includes("provider not available"))) throw err;
8420
- if (attempt === delaysMs.length) break;
8421
- await new Promise((r) => setTimeout(r, delaysMs[attempt]));
8435
+ const kind = classifyStoreError(err);
8436
+ if (kind === "permanent") throw err;
8437
+ const delay = (kind === "transport" ? TRANSPORT_RETRY_DELAYS_MS : RESTART_RETRY_DELAYS_MS)[attempt];
8438
+ if (delay === void 0) {
8439
+ if (kind === "transport") throw err;
8440
+ break;
8441
+ }
8442
+ await new Promise((r) => setTimeout(r, delay));
8422
8443
  }
8423
8444
  this.settingsStoreReady = false;
8424
8445
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
8425
8446
  return {};
8426
8447
  }
8427
8448
  };
8449
+ /** The settings store's own restart fingerprints. */
8450
+ var RESTART_RETRY_DELAYS_MS = [
8451
+ 150,
8452
+ 350,
8453
+ 600,
8454
+ 900
8455
+ ];
8456
+ /** A hub restarting under a booting runner: ~100 s in total. */
8457
+ var TRANSPORT_RETRY_DELAYS_MS = [
8458
+ 150,
8459
+ 350,
8460
+ 600,
8461
+ 900,
8462
+ 2e3,
8463
+ 5e3,
8464
+ 1e4,
8465
+ 2e4,
8466
+ 3e4,
8467
+ 3e4
8468
+ ];
8469
+ var RESTART_FINGERPRINTS = ["SqliteSettingsBackend not initialized", "provider not available"];
8470
+ /** Node / socket error codes of a connection that did not happen or broke. */
8471
+ var TRANSPORT_CODES = new Set([
8472
+ "ECONNREFUSED",
8473
+ "ECONNRESET",
8474
+ "ETIMEDOUT",
8475
+ "EPIPE",
8476
+ "EHOSTUNREACH",
8477
+ "ENETUNREACH"
8478
+ ]);
8479
+ /** tRPC error codes that mean "the other side is not answering now", not "your call is wrong". */
8480
+ var UNAVAILABLE_TRPC_CODES = new Set([
8481
+ "TIMEOUT",
8482
+ "SERVICE_UNAVAILABLE",
8483
+ "BAD_GATEWAY",
8484
+ "GATEWAY_TIMEOUT"
8485
+ ]);
8486
+ /** The exact phrases OUR transport raises when it cannot reach the hub. */
8487
+ var TRANSPORT_PHRASES = [
8488
+ "discovery deadline exceeded",
8489
+ "parent could not route child unowned cap call",
8490
+ "UDS request timed out after",
8491
+ "channel closed"
8492
+ ];
8493
+ /** Moleculer error `type`s of a call that could not be served right now. */
8494
+ var MOLECULER_TRANSIENT_TYPES = new Set([
8495
+ "REQUEST_TIMEOUT",
8496
+ "SERVICE_NOT_FOUND",
8497
+ "SERVICE_NOT_AVAILABLE",
8498
+ "REQUEST_REJECTED",
8499
+ "BROKER_DISCONNECTED"
8500
+ ]);
8501
+ function stringProp(value, key) {
8502
+ if (typeof value !== "object" || value === null) return void 0;
8503
+ const found = Reflect.get(value, key);
8504
+ return typeof found === "string" ? found : void 0;
8505
+ }
8506
+ /** The code an error carries: its own, its cause's, or a tRPC `data.code`. */
8507
+ function errorCode(err) {
8508
+ const out = {};
8509
+ const cause = typeof err === "object" && err !== null ? Reflect.get(err, "cause") : void 0;
8510
+ const data = typeof err === "object" && err !== null ? Reflect.get(err, "data") : void 0;
8511
+ const node = stringProp(err, "code") ?? stringProp(cause, "code");
8512
+ if (node !== void 0) out.node = node;
8513
+ const trpc = stringProp(data, "code");
8514
+ if (trpc !== void 0) out.trpc = trpc;
8515
+ return out;
8516
+ }
8517
+ /**
8518
+ * Is a settings read failure worth retrying, and with which budget? Only
8519
+ * EXPLICIT signals count: a socket error code, a tRPC "unavailable" code, or
8520
+ * one of the exact phrases our own transport raises. A message that merely
8521
+ * contains "timed out" or "unreachable" is not evidence of anything, and
8522
+ * `settings store not wired` is a wiring bug that retrying cannot fix.
8523
+ */
8524
+ function classifyStoreError(err) {
8525
+ const message = err instanceof Error ? err.message : String(err);
8526
+ if (RESTART_FINGERPRINTS.some((f) => message.includes(f))) return "restart";
8527
+ const code = errorCode(err);
8528
+ if (code.node !== void 0 && TRANSPORT_CODES.has(code.node)) return "transport";
8529
+ if (code.trpc !== void 0 && UNAVAILABLE_TRPC_CODES.has(code.trpc)) return "transport";
8530
+ if (typeof err === "object" && err !== null) {
8531
+ if (Reflect.get(err, "retryable") === true) return "transport";
8532
+ const type = stringProp(err, "type");
8533
+ if (type !== void 0 && MOLECULER_TRANSIENT_TYPES.has(type)) return "transport";
8534
+ }
8535
+ if (TRANSPORT_PHRASES.some((f) => message.includes(f))) return "transport";
8536
+ return "permanent";
8537
+ }
8428
8538
  /**
8429
8539
  * Collect the keys of every field marked `perNode: true`, recursing into
8430
8540
  * layout containers (`group` fields and `sub-tabs` tabs) the same way
@@ -8514,6 +8624,156 @@ var PeerBytesTicketSchema = object({
8514
8624
  */
8515
8625
  declaredBytes: number().int().nonnegative().nullable()
8516
8626
  });
8627
+ /** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
8628
+ /** The files a kind may live in. `history` is device-history's (D850), never records. */
8629
+ var RECORD_DATABASES = ["settings", "events"];
8630
+ /** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
8631
+ var RECORD_CLASSES = [
8632
+ "config",
8633
+ "ledger",
8634
+ "audit",
8635
+ "mirror",
8636
+ "ephemeral"
8637
+ ];
8638
+ var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
8639
+ var RECORD_MAX_DATA_BYTES = 1024 * 1024;
8640
+ var RECORD_BATCH_MAX = 1e3;
8641
+ var RECORD_QUERY_MAX = 1e3;
8642
+ function defOf(schema) {
8643
+ const zodBag = Reflect.get(schema, "_zod");
8644
+ if (typeof zodBag !== "object" || zodBag === null) return {};
8645
+ const def = Reflect.get(zodBag, "def");
8646
+ if (typeof def !== "object" || def === null) return {};
8647
+ return def;
8648
+ }
8649
+ function declaredHere(schema) {
8650
+ return globalRegistry.get(schema)?.secret === true;
8651
+ }
8652
+ /** Wrapper kinds whose `innerType` is the field itself. */
8653
+ var WRAPPERS = new Set([
8654
+ "optional",
8655
+ "nullable",
8656
+ "default",
8657
+ "prefault",
8658
+ "catch",
8659
+ "readonly",
8660
+ "nonoptional"
8661
+ ]);
8662
+ /**
8663
+ * The schema layers that make up ONE field: the schema itself and every
8664
+ * wrapper below it (optional / default / nullable / readonly / catch), plus
8665
+ * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
8666
+ * `.transform` on `in`).
8667
+ */
8668
+ function layersOf(schema) {
8669
+ const out = [];
8670
+ const queue = [schema];
8671
+ while (queue.length > 0) {
8672
+ const s = queue.shift();
8673
+ if (!s || out.includes(s)) continue;
8674
+ out.push(s);
8675
+ const def = defOf(s);
8676
+ if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
8677
+ if (def.type === "pipe") {
8678
+ if (def.in) queue.push(def.in);
8679
+ if (def.out) queue.push(def.out);
8680
+ }
8681
+ }
8682
+ return out;
8683
+ }
8684
+ /** True when any layer of this field declares `.meta({ secret: true })`. */
8685
+ function isZodSecret(schema) {
8686
+ return layersOf(schema).some(declaredHere);
8687
+ }
8688
+ /**
8689
+ * Every declared secret path in a config schema: dotted keys, `[]` for "each
8690
+ * element of this array" (`connection.password`, `rows[].token`). A declared
8691
+ * object or array is reported as one path and not descended into — everything
8692
+ * under it is secret.
8693
+ */
8694
+ function collectZodSecretPaths(schema, prefix = "") {
8695
+ if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
8696
+ const paths = [];
8697
+ for (const layer of layersOf(schema)) {
8698
+ const def = defOf(layer);
8699
+ if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
8700
+ else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
8701
+ else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
8702
+ else if (def.type === "intersection") {
8703
+ if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
8704
+ if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
8705
+ }
8706
+ }
8707
+ return [...new Set(paths)];
8708
+ }
8709
+ /**
8710
+ * Record kinds (D881): the declaration an addon makes of one kind of fact, the
8711
+ * Zod row schema that describes it, and the helper that turns the two into the
8712
+ * JSON the engine stores.
8713
+ *
8714
+ * A kind is declared ONCE, next to the code that owns it, and the engine's
8715
+ * reconciler creates its indexes - nothing creates DDL at runtime from a write.
8716
+ * Everything the generic machinery needs (secret fields, the entry descriptor)
8717
+ * is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
8718
+ * never a field name (D879).
8719
+ */
8720
+ /** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
8721
+ var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
8722
+ var RecordIndexSchema = object({
8723
+ name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
8724
+ fields: array(RecordFieldPathSchema).min(1).max(4),
8725
+ unique: boolean().optional()
8726
+ }).strict();
8727
+ var RecordRetentionSchema = union([
8728
+ object({
8729
+ ttlMs: number().int().positive(),
8730
+ from: _enum(["at", "write"])
8731
+ }).strict(),
8732
+ object({
8733
+ maxRows: number().int().positive().optional(),
8734
+ maxRowsPerDevice: number().int().positive().optional()
8735
+ }).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
8736
+ object({
8737
+ owner: string().min(1),
8738
+ reason: string().min(1)
8739
+ }).strict(),
8740
+ object({ none: string().min(1) }).strict()
8741
+ ]);
8742
+ /** The descriptor a generic entries UI reads (D881 "generic entries"). */
8743
+ var EntryDescriptorSchema = object({
8744
+ title: string().min(1),
8745
+ /** The record body field shown as the entry's name. */
8746
+ nameField: string().min(1),
8747
+ /** JSON Schema of the entry body; `secret: true` marks a password field. */
8748
+ fields: record(string(), unknown())
8749
+ }).strict();
8750
+ /** What crosses the wire and sits in the engine's catalog: JSON only, no Zod. */
8751
+ var RecordKindDeclarationSchema = object({
8752
+ database: _enum(RECORD_DATABASES).default("settings"),
8753
+ class: _enum(RECORD_CLASSES),
8754
+ writeTrigger: string().min(1),
8755
+ device: _enum([
8756
+ "required",
8757
+ "optional",
8758
+ "none"
8759
+ ]).default("none"),
8760
+ at: _enum(["required", "none"]).default("none"),
8761
+ indexes: array(RecordIndexSchema).max(8).default([]),
8762
+ retention: RecordRetentionSchema,
8763
+ deviceRemoval: _enum(["delete", "none"]).default("none"),
8764
+ maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
8765
+ /** Derived from the Zod schema; never written by hand. */
8766
+ secretPaths: array(string().min(1)).default([]),
8767
+ /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
8768
+ secretArrayKeys: record(string(), string()).default({}),
8769
+ /** The legacy table this kind adopts from; the boot guard reads it (D853: no migration code). */
8770
+ adoptsFrom: string().min(1).optional(),
8771
+ /** Kinds that must agree through `transact` sit in one file under one family name. */
8772
+ transactFamily: string().min(1).optional(),
8773
+ entry: EntryDescriptorSchema.optional()
8774
+ }).strict();
8775
+ /** The kind names: lower-case, dash separated, short enough for an index name. */
8776
+ var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
8517
8777
  /** Shared Zod schemas used across streaming capabilities. */
8518
8778
  var CamProfileSchema = _enum([
8519
8779
  "high",
@@ -8978,75 +9238,12 @@ method(object({ deviceId: number() }), array(StreamSourceEntrySchema)), method(o
8978
9238
  kind: "mutation",
8979
9239
  auth: "admin"
8980
9240
  }), method(object({ deviceId: number() }), unknown().nullable()), method(object({ deviceId: number() }), RawStateResultSchema.nullable(), { auth: "protected" });
8981
- function defOf(schema) {
8982
- return schema._zod?.def ?? {};
8983
- }
8984
- function declaredHere(schema) {
8985
- return globalRegistry.get(schema)?.secret === true;
8986
- }
8987
- /** Wrapper kinds whose `innerType` is the field itself. */
8988
- var WRAPPERS = new Set([
8989
- "optional",
8990
- "nullable",
8991
- "default",
8992
- "prefault",
8993
- "catch",
8994
- "readonly",
8995
- "nonoptional"
8996
- ]);
8997
- /**
8998
- * The schema layers that make up ONE field: the schema itself and every
8999
- * wrapper below it (optional / default / nullable / readonly / catch), plus
9000
- * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
9001
- * `.transform` on `in`).
9002
- */
9003
- function layersOf(schema) {
9004
- const out = [];
9005
- const queue = [schema];
9006
- while (queue.length > 0) {
9007
- const s = queue.shift();
9008
- if (!s || out.includes(s)) continue;
9009
- out.push(s);
9010
- const def = defOf(s);
9011
- if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
9012
- if (def.type === "pipe") {
9013
- if (def.in) queue.push(def.in);
9014
- if (def.out) queue.push(def.out);
9015
- }
9016
- }
9017
- return out;
9018
- }
9019
- /** True when any layer of this field declares `.meta({ secret: true })`. */
9020
- function isZodSecret(schema) {
9021
- return layersOf(schema).some(declaredHere);
9022
- }
9023
- /**
9024
- * Every declared secret path in a config schema: dotted keys, `[]` for "each
9025
- * element of this array" (`connection.password`, `rows[].token`). A declared
9026
- * object or array is reported as one path and not descended into — everything
9027
- * under it is secret.
9028
- */
9029
- function collectZodSecretPaths(schema, prefix = "") {
9030
- if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
9031
- const paths = [];
9032
- for (const layer of layersOf(schema)) {
9033
- const def = defOf(layer);
9034
- if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
9035
- else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
9036
- else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
9037
- else if (def.type === "intersection") {
9038
- if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
9039
- if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
9040
- }
9041
- }
9042
- return [...new Set(paths)];
9043
- }
9044
9241
  object({
9045
9242
  code: string().regex(/^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/),
9046
9243
  params: record(string(), union([string(), number()])).optional()
9047
9244
  });
9048
9245
  //#endregion
9049
- //#region ../types/dist/err-msg-DLerKiVO.mjs
9246
+ //#region ../types/dist/err-msg-BXKzRBwg.mjs
9050
9247
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
9051
9248
  DeviceType["Camera"] = "camera";
9052
9249
  DeviceType["Hub"] = "hub";
@@ -9138,6 +9335,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
9138
9335
  * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
9139
9336
  * integrations sharing the same food/desiccant/hopper surface. */
9140
9337
  DeviceType["PetFeeder"] = "pet-feeder";
9338
+ /** Built-in dishwasher — program lifecycle, door / remote-start gates,
9339
+ * salt and rinse-aid levels. Installed with the `dishwasher` cap.
9340
+ * Sources: native Bosch Home Connect, reusable by similar integrations. */
9341
+ DeviceType["Dishwasher"] = "dishwasher";
9141
9342
  return DeviceType;
9142
9343
  }({});
9143
9344
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -9315,6 +9516,9 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
9315
9516
  /** Virtual HA toggle (input_boolean.*) — distinguishable from a
9316
9517
  * real Switch device for UI rendering / export adapters. */
9317
9518
  DeviceRole["BinaryHelper"] = "binary-helper";
9519
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
9520
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
9521
+ DeviceRole["Outlet"] = "outlet";
9318
9522
  /** Generic motion / occupancy / moving event source. Distinct from
9319
9523
  * the camera accessory PirSensor role: that one is a camera child;
9320
9524
  * this is a standalone HA / 3rd-party motion sensor. */
@@ -9392,7 +9596,7 @@ function errMsg(err) {
9392
9596
  return String(err);
9393
9597
  }
9394
9598
  //#endregion
9395
- //#region ../types/dist/composition-EQ9Ok0Ee.mjs
9599
+ //#region ../types/dist/composition-CB6PMT9p.mjs
9396
9600
  /**
9397
9601
  * The ffmpeg build every CamStack node downloads, per platform and arch.
9398
9602
  *
@@ -12596,11 +12800,10 @@ var ClipModelMetaSchema = object({
12596
12800
  /**
12597
12801
  * The `vector-store` index this model's vectors live in. ONE feature space
12598
12802
  * per index: two models share an index only when their vectors are ranked
12599
- * against each other, which for CLIP means never across families. MobileCLIP
12600
- * S1 and S2 share `object-clip` for history (separated by the row's
12601
- * `modelId`); any other model gets its own.
12803
+ * against each other, which for CLIP means never across families. Every
12804
+ * model gets its own.
12602
12805
  */
12603
- vectorIndex: string().regex(/^object-clip(-[a-z0-9][a-z0-9-]*)?$/),
12806
+ vectorIndex: string().regex(/^object-clip-[a-z0-9][a-z0-9-]*$/),
12604
12807
  /**
12605
12808
  * The text encoder's tokenizer, a declared SIBLING file of its onnx. The name
12606
12809
  * must be unique per tokenizer: every model's siblings land in ONE flat
@@ -13779,8 +13982,13 @@ var AlertSeveritySchema = _enum([
13779
13982
  "info",
13780
13983
  "success",
13781
13984
  "warning",
13782
- "error"
13985
+ "error",
13986
+ "critical"
13783
13987
  ]);
13988
+ var AlertActionSchema = object({
13989
+ label: string(),
13990
+ href: string().optional()
13991
+ });
13784
13992
  var AlertStatusSchema = _enum([
13785
13993
  "active",
13786
13994
  "in-progress",
@@ -13798,6 +14006,7 @@ var AlertSchema = object({
13798
14006
  severity: AlertSeveritySchema,
13799
14007
  title: string(),
13800
14008
  message: string(),
14009
+ action: AlertActionSchema.optional(),
13801
14010
  status: AlertStatusSchema,
13802
14011
  progress: number().optional(),
13803
14012
  read: boolean(),
@@ -16333,6 +16542,41 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
16333
16542
  keyField: string()
16334
16543
  }).optional()
16335
16544
  })) });
16545
+ /**
16546
+ * The report of a one-shot "move this owner's plaintext secrets into the
16547
+ * keychain" run (D878). COUNTS only - never a value, never a ref.
16548
+ *
16549
+ * The owner runs it on its own rows, through its own write path; an operator
16550
+ * script (`scripts/hub/migrate-raw-secrets.ts`) calls the owner's cap method.
16551
+ * Dry run (`apply: false`) reports what WOULD move and changes nothing.
16552
+ */
16553
+ var RawSecretMigrationInputSchema = object({
16554
+ /** `false` (the default of the script) = report only. */
16555
+ apply: boolean() });
16556
+ var RawSecretMigrationReportSchema = object({
16557
+ apply: boolean(),
16558
+ /** Rows that can carry a secret (a profile, an enrolment). */
16559
+ rows: number().int(),
16560
+ /** Rows whose secret was plaintext when the run started. */
16561
+ raw: number().int(),
16562
+ /** Rows that already held a keychain marker when the run started. */
16563
+ markers: number().int(),
16564
+ /** Plaintext rows moved by THIS run (always 0 on a dry run). */
16565
+ migrated: number().int(),
16566
+ /** Plaintext rows left in place because the move or its read-back failed. */
16567
+ failed: number().int(),
16568
+ /** After the run: rows still holding plaintext. */
16569
+ remainingRaw: number().int(),
16570
+ /** After the run: markers the keychain could not resolve right now. */
16571
+ unresolvedMarkers: number().int(),
16572
+ /**
16573
+ * A second, redundant copy of the secrets' rows that the run found (and, on
16574
+ * `apply`, deleted after verifying the first). Owners with a single copy omit it.
16575
+ */
16576
+ redundantCopies: number().int().optional(),
16577
+ /** True when nothing is raw and every marker resolves. */
16578
+ verified: boolean()
16579
+ });
16336
16580
  /** Where a block runs. The operator chooses — a block driving a device on an
16337
16581
  * agent is the reason placement is not fixed to the hub. */
16338
16582
  var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
@@ -16559,6 +16803,9 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
16559
16803
  }), SettingsUpdateResultSchema, {
16560
16804
  kind: "mutation",
16561
16805
  auth: "admin"
16806
+ }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
16807
+ kind: "mutation",
16808
+ auth: "admin"
16562
16809
  }), method(object({}), object({ libs: array(object({
16563
16810
  filePath: string(),
16564
16811
  content: string()
@@ -16822,6 +17069,347 @@ id: union([string(), number()]) }), {
16822
17069
  auth: "admin"
16823
17070
  });
16824
17071
  /**
17072
+ * records - an addon's own facts and documents (D881): ledgers, audit rows,
17073
+ * config documents, mirrors. The third storage API next to `ctx.settings`
17074
+ * (operator intent, D870) and device-history (numbers over time, D850).
17075
+ *
17076
+ * Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
17077
+ * crossed a process and stated by a hub in-process caller (`ctx.records` does
17078
+ * that with the addon id closed over). It is deliberately NOT in any input
17079
+ * schema, so a session can never supply it, and a call with no stamp is refused
17080
+ * `records/no-caller`. A kind is readable and writable only by its owner; other
17081
+ * addons go through the owner's capability.
17082
+ *
17083
+ * Every read is three-way: a row, `null`, or the call THROWS. A failed read is
17084
+ * never `[]`. Secret fields are never on the wire: a read shows the
17085
+ * redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
17086
+ * resolves itself, and a write may carry only a keychain marker bound to the
17087
+ * row's own ref.
17088
+ *
17089
+ * Internal: addons reach it through `ctx.records`, never directly.
17090
+ */
17091
+ var RecordIdSchema = string().min(1).max(256);
17092
+ /** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
17093
+ var RecordBodySchema = record(string(), unknown());
17094
+ var RecordInputSchema = object({
17095
+ id: RecordIdSchema,
17096
+ /** The device this row belongs to (D865 removal, per-device reads). */
17097
+ deviceId: number().int().optional(),
17098
+ /** The domain clock, ms: observed / created / opened. */
17099
+ at: number().int().optional(),
17100
+ /** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
17101
+ expiresAt: number().int().optional(),
17102
+ data: RecordBodySchema
17103
+ });
17104
+ var RecordViewSchema = object({
17105
+ id: string(),
17106
+ deviceId: number().int().nullable(),
17107
+ at: number().nullable(),
17108
+ expiresAt: number().nullable(),
17109
+ rev: number().int(),
17110
+ updatedAt: number(),
17111
+ data: RecordBodySchema
17112
+ });
17113
+ /** Declared index fields only. Anything else does not compile, by design. */
17114
+ var RecordWhereSchema = record(RecordFieldPathSchema, union([
17115
+ string(),
17116
+ number(),
17117
+ boolean(),
17118
+ object({ in: array(union([
17119
+ string(),
17120
+ number(),
17121
+ boolean()
17122
+ ])).min(1).max(RECORD_BATCH_MAX) }),
17123
+ object({
17124
+ gt: union([string(), number()]).optional(),
17125
+ gte: union([string(), number()]).optional(),
17126
+ lt: union([string(), number()]).optional(),
17127
+ lte: union([string(), number()]).optional()
17128
+ }).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
17129
+ ]));
17130
+ var KindShape = { kind: RecordKindNameSchema };
17131
+ var TimeWindowShape = {
17132
+ deviceId: number().int().optional(),
17133
+ atFrom: number().optional(),
17134
+ atTo: number().optional()
17135
+ };
17136
+ var RecordQueryInputSchema = object({
17137
+ ...KindShape,
17138
+ ...TimeWindowShape,
17139
+ where: RecordWhereSchema.optional(),
17140
+ /** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
17141
+ order: _enum(["at", "id"]).optional(),
17142
+ desc: boolean().optional(),
17143
+ limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
17144
+ /** Opaque keyset cursor from a previous answer. There is no offset. */
17145
+ cursor: string().min(1).optional()
17146
+ });
17147
+ var RecordQueryResultSchema = object({
17148
+ records: array(RecordViewSchema),
17149
+ nextCursor: string().nullable()
17150
+ });
17151
+ var RecordKindStateSchema = _enum([
17152
+ "created",
17153
+ "unchanged",
17154
+ "updated"
17155
+ ]);
17156
+ var DeclareKindResultSchema = object({
17157
+ kindId: number().int(),
17158
+ state: RecordKindStateSchema,
17159
+ createdIndexes: array(string()),
17160
+ /** The boot guard: the kind is empty while its legacy table still holds rows. */
17161
+ adoptionPending: object({
17162
+ legacyTable: string(),
17163
+ legacyRows: number().int()
17164
+ }).nullable()
17165
+ });
17166
+ /** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
17167
+ var SecretRefsList = array(string());
17168
+ var RecordSecretRefsSchema = record(string(), record(string(), string()));
17169
+ method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
17170
+ kind: "mutation",
17171
+ auth: "admin"
17172
+ }), method(object({
17173
+ ...KindShape,
17174
+ id: RecordIdSchema
17175
+ }), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
17176
+ ...KindShape,
17177
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
17178
+ }), array(RecordViewSchema), { auth: "admin" }), method(object({
17179
+ ...KindShape,
17180
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
17181
+ }), RecordSecretRefsSchema, { auth: "admin" }), method(object({
17182
+ ...KindShape,
17183
+ record: RecordInputSchema,
17184
+ /** Compare-and-set. `0` means "the row must not exist". */
17185
+ ifRev: number().int().min(0).optional()
17186
+ }), object({
17187
+ rev: number().int(),
17188
+ releasedRefs: SecretRefsList
17189
+ }), {
17190
+ kind: "mutation",
17191
+ auth: "admin"
17192
+ }), method(object({
17193
+ ...KindShape,
17194
+ records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
17195
+ }), object({
17196
+ count: number().int(),
17197
+ releasedRefs: SecretRefsList
17198
+ }), {
17199
+ kind: "mutation",
17200
+ auth: "admin"
17201
+ }), method(object({
17202
+ ...KindShape,
17203
+ id: RecordIdSchema,
17204
+ data: RecordBodySchema,
17205
+ at: number().int().nullable().optional(),
17206
+ expiresAt: number().int().nullable().optional(),
17207
+ ifRev: number().int().min(1).optional()
17208
+ }), object({
17209
+ rev: number().int(),
17210
+ releasedRefs: SecretRefsList
17211
+ }), {
17212
+ kind: "mutation",
17213
+ auth: "admin"
17214
+ }), method(object({
17215
+ ...KindShape,
17216
+ id: RecordIdSchema
17217
+ }), object({
17218
+ deleted: boolean(),
17219
+ secretRefs: SecretRefsList
17220
+ }), {
17221
+ kind: "mutation",
17222
+ auth: "admin"
17223
+ }), method(object({
17224
+ ...KindShape,
17225
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
17226
+ }), object({
17227
+ deleted: number().int(),
17228
+ secretRefs: SecretRefsList
17229
+ }), {
17230
+ kind: "mutation",
17231
+ auth: "admin"
17232
+ }), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
17233
+ ...KindShape,
17234
+ ...TimeWindowShape,
17235
+ where: RecordWhereSchema.optional()
17236
+ }), object({ count: number().int() }), { auth: "admin" }), method(object({
17237
+ ...KindShape,
17238
+ deviceId: number().int().optional(),
17239
+ from: number(),
17240
+ to: number(),
17241
+ bucketMs: number().int().positive()
17242
+ }), object({ buckets: array(object({
17243
+ bucketStart: number(),
17244
+ count: number().int()
17245
+ })) }), { auth: "admin" }), method(object({
17246
+ ...KindShape,
17247
+ op: _enum([
17248
+ "sum",
17249
+ "min",
17250
+ "max",
17251
+ "count"
17252
+ ]),
17253
+ field: RecordFieldPathSchema.optional(),
17254
+ groupBy: RecordFieldPathSchema.optional(),
17255
+ where: RecordWhereSchema.optional()
17256
+ }), object({ rows: array(object({
17257
+ group: union([
17258
+ string(),
17259
+ number(),
17260
+ _null()
17261
+ ]),
17262
+ value: number().nullable()
17263
+ })) }), { auth: "admin" }), method(object({
17264
+ ...KindShape,
17265
+ deviceId: number().int().optional(),
17266
+ atBefore: number().optional(),
17267
+ where: RecordWhereSchema.optional()
17268
+ }), object({
17269
+ deleted: number().int(),
17270
+ secretRefs: SecretRefsList
17271
+ }), {
17272
+ kind: "mutation",
17273
+ auth: "admin"
17274
+ }), method(object({
17275
+ deviceId: number().int(),
17276
+ kinds: array(RecordKindNameSchema).optional()
17277
+ }), object({
17278
+ deleted: record(string(), number().int()),
17279
+ secretRefs: SecretRefsList
17280
+ }), {
17281
+ kind: "mutation",
17282
+ auth: "admin"
17283
+ }), method(object(KindShape), object({ id: number().int() }), {
17284
+ kind: "mutation",
17285
+ auth: "admin"
17286
+ });
17287
+ /** One table of the settings database. */
17288
+ var StorageTableSchema = object({
17289
+ /** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
17290
+ name: string(),
17291
+ /** The owner prefix of a scoped name (`recorder` for `recorder:addon-settings`), else null. */
17292
+ owner: string().nullable(),
17293
+ rows: number().int().nonnegative(),
17294
+ /** Bytes the table's pages occupy (`dbstat`); null when the engine cannot measure. */
17295
+ bytes: number().int().nonnegative().nullable(),
17296
+ /** Declared by an addon since this engine process started (a running addon claims it). */
17297
+ declaredThisBoot: boolean()
17298
+ });
17299
+ /** One declared record kind (D881) as System -> Data shows it. */
17300
+ var StorageKindSchema = object({
17301
+ owner: string(),
17302
+ kind: string(),
17303
+ database: _enum(["settings", "events"]),
17304
+ class: string(),
17305
+ state: _enum([
17306
+ "declared",
17307
+ "orphaned",
17308
+ "dropped"
17309
+ ]),
17310
+ /** `null` when the caller asked for no counts (`counts: false`). */
17311
+ rows: number().int().nonnegative().nullable(),
17312
+ /** Rows x the average size of a sample; null for an empty kind. */
17313
+ bytesEstimate: number().int().nonnegative().nullable(),
17314
+ writeTrigger: string(),
17315
+ retention: RecordRetentionSchema,
17316
+ /** `true` when the kind is an entry kind (the generic entries UI lists it). */
17317
+ entry: boolean(),
17318
+ /** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
17319
+ declaration: RecordKindDeclarationSchema
17320
+ });
17321
+ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
17322
+ collection: string().min(1),
17323
+ expectRows: number().int().nonnegative()
17324
+ }), object({
17325
+ dropped: literal(true),
17326
+ rows: number().int().nonnegative()
17327
+ }), {
17328
+ kind: "mutation",
17329
+ auth: "admin"
17330
+ }), method(object({
17331
+ owner: string().min(1).optional(),
17332
+ /** `false` skips the per-kind row count (a catalog read only). Default true. */
17333
+ counts: boolean().optional()
17334
+ }), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
17335
+ owner: string().min(1),
17336
+ kind: RecordKindNameSchema
17337
+ }), RecordQueryResultSchema, { auth: "admin" }), method(object({
17338
+ owner: string().min(1),
17339
+ kind: RecordKindNameSchema,
17340
+ records: array(RecordInputSchema).max(RECORD_BATCH_MAX),
17341
+ /** The LAST batch: the copy is complete, the boot guard stops asking. */
17342
+ complete: boolean().optional()
17343
+ }).refine((input) => input.records.length > 0 || input.complete === true, { message: "records must be non-empty unless complete is true" }), object({ adopted: number().int() }), {
17344
+ kind: "mutation",
17345
+ auth: "admin"
17346
+ }), method(object({
17347
+ collection: string().min(1),
17348
+ cursor: string().optional(),
17349
+ limit: number().int().min(1).max(1e3).optional()
17350
+ }), object({
17351
+ rows: array(object({
17352
+ id: string(),
17353
+ data: record(string(), unknown())
17354
+ })),
17355
+ nextCursor: string().nullable()
17356
+ }), { auth: "admin" }), method(object({ collection: string().min(1) }), object({ adopted: boolean() }), { auth: "admin" });
17357
+ /**
17358
+ * record-entries - the ONE admin surface for every list of user-created entries
17359
+ * (D881): notification targets, brokers, LLM profiles, anything an addon
17360
+ * declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
17361
+ *
17362
+ * The UI is generic. `listEntryKinds` hands it, per kind, the JSON Schema of
17363
+ * the entry body with `secret: true` on every credential field, so a password
17364
+ * field is derived from the schema and never from a name. `putEntry` takes the
17365
+ * redaction token for an untouched secret (the stored value is kept), a string
17366
+ * for a new one, and the clear instruction to remove one. A read never carries
17367
+ * a secret value, and a locked keychain refuses the write with
17368
+ * `records/secret-unavailable` instead of storing plaintext.
17369
+ *
17370
+ * Hub-only, served in-process (it acts for each entry's owner on the keychain).
17371
+ */
17372
+ var EntryKindInfoSchema = object({
17373
+ owner: string(),
17374
+ kind: RecordKindNameSchema,
17375
+ title: string(),
17376
+ nameField: string(),
17377
+ /** JSON Schema of the body; `secret: true` marks a password field. */
17378
+ fields: record(string(), unknown()),
17379
+ secretPaths: array(string()),
17380
+ rows: number().int().nonnegative()
17381
+ });
17382
+ var EntryViewSchema = object({
17383
+ id: RecordIdSchema,
17384
+ name: string(),
17385
+ rev: number().int(),
17386
+ updatedAt: number(),
17387
+ /** Secret fields hold the redaction token. */
17388
+ data: RecordBodySchema
17389
+ });
17390
+ var EntryAddress = {
17391
+ owner: string().min(1),
17392
+ kind: RecordKindNameSchema
17393
+ };
17394
+ method(object({}), object({ kinds: array(EntryKindInfoSchema) }), { auth: "admin" }), method(object({ ...EntryAddress }), object({ entries: array(EntryViewSchema) }), { auth: "admin" }), method(object({
17395
+ ...EntryAddress,
17396
+ id: RecordIdSchema.optional(),
17397
+ data: RecordBodySchema,
17398
+ ifRev: number().int().min(1).optional()
17399
+ }), object({
17400
+ id: string(),
17401
+ rev: number().int()
17402
+ }), {
17403
+ kind: "mutation",
17404
+ auth: "admin"
17405
+ }), method(object({
17406
+ ...EntryAddress,
17407
+ id: RecordIdSchema
17408
+ }), object({ deleted: boolean() }), {
17409
+ kind: "mutation",
17410
+ auth: "admin"
17411
+ });
17412
+ /**
16825
17413
  * `keychain` — the hub's encrypted store of secret values (D787).
16826
17414
  *
16827
17415
  * Settings blobs never hold a secret: they hold a marker
@@ -17362,6 +17950,16 @@ method(object({}), KeychainStatusSchema, { auth: "admin" }), method(object({}),
17362
17950
  kind: "mutation",
17363
17951
  auth: "admin",
17364
17952
  caller: "required"
17953
+ }), method(object({
17954
+ ref: string().min(1),
17955
+ value: string().min(1)
17956
+ }), object({
17957
+ ref: string(),
17958
+ version: number().int()
17959
+ }), {
17960
+ kind: "mutation",
17961
+ auth: "admin",
17962
+ caller: "required"
17365
17963
  }), method(object({ refs: array(string().min(1)).max(1e3) }), object({
17366
17964
  deleted: array(string()).readonly(),
17367
17965
  kept: array(object({
@@ -40040,6 +40638,9 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
40040
40638
  }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
40041
40639
  kind: "mutation",
40042
40640
  auth: "admin"
40641
+ }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
40642
+ kind: "mutation",
40643
+ auth: "admin"
40043
40644
  }), method(ProfileRefInputSchema, _void(), {
40044
40645
  kind: "mutation",
40045
40646
  auth: "admin"
@@ -50549,6 +51150,41 @@ var ServerRollbackInfoSchema = object({
50549
51150
  atMs: number(),
50550
51151
  reason: string()
50551
51152
  });
51153
+ /**
51154
+ * The runner boot check (D877): the runners this node PLANNED (hub: one per
51155
+ * `resolveRunnerId` over its forked addons; agent: the runners its process
51156
+ * service holds, because only the hub plans) against the states the process
51157
+ * service reports. Kept in memory, recomputed by the same one-shot check after
51158
+ * every boot, served on `getServerPackageStatus`.
51159
+ *
51160
+ * `failed` is the D6 breaker's terminal state, `refused` a deterministic Init
51161
+ * refusal (an addon that said "I will not start, and here is why"), `missing` a
51162
+ * planned runner the process service has no entry for.
51163
+ */
51164
+ var RunnerRefusalSchema = object({
51165
+ runnerId: string(),
51166
+ addonIds: array(string()),
51167
+ reason: string()
51168
+ });
51169
+ var RunnerBootCheckSchema = object({
51170
+ checkedAtMs: number(),
51171
+ /** Runners planned for this node. */
51172
+ expected: number(),
51173
+ /** Planned runners whose process is `running`. */
51174
+ running: number(),
51175
+ failed: array(string()),
51176
+ refused: array(RunnerRefusalSchema),
51177
+ missing: array(string()),
51178
+ /**
51179
+ * Runners still on their way: `starting`, or `crashed` waiting on a backoff
51180
+ * respawn. The check is FINAL only when this is empty; until then nothing is
51181
+ * pushed and the verdict is re-evaluated on the next runner state change.
51182
+ * Optional for version skew (absent = settled).
51183
+ */
51184
+ pending: array(string()).optional(),
51185
+ /** True when the plan is the hub's (`missing` is then meaningful); false on an agent. */
51186
+ planKnown: boolean()
51187
+ });
50552
51188
  var ServerPackageStatusSchema = object({
50553
51189
  /** Root package name (`@camstack/server` on the hub). */
50554
51190
  packageName: string(),
@@ -50611,7 +51247,14 @@ var ServerPackageStatusSchema = object({
50611
51247
  * for version skew: an older provider's payload simply omits it, and an
50612
51248
  * absent wrapper is unknown — never `native`.
50613
51249
  */
50614
- wrapper: WrapperIdentitySchema.optional()
51250
+ wrapper: WrapperIdentitySchema.optional(),
51251
+ /**
51252
+ * What this node PLANNED to run against what its process service reports
51253
+ * (D877). Absent until the one-shot boot check has run (~60 s after boot) and
51254
+ * on a provider that predates it. Absent is "not checked yet", never "all
51255
+ * well": a consumer that needs the answer treats it as pending.
51256
+ */
51257
+ runners: RunnerBootCheckSchema.optional()
50615
51258
  });
50616
51259
  var ServerUpdateCheckResultSchema = object({
50617
51260
  packageName: string(),
@@ -50662,6 +51305,13 @@ var ServerUpdateOutcomeSchema = _enum([
50662
51305
  "refused",
50663
51306
  "failed"
50664
51307
  ]);
51308
+ /** One addon the pre-flight found the target version would refuse (D877). */
51309
+ var UpdateBlockerSchema = object({
51310
+ addonId: string(),
51311
+ reason: string(),
51312
+ /** What the operator does about it. */
51313
+ remedy: string()
51314
+ });
50665
51315
  var ServerUpdateActionResultSchema = object({
50666
51316
  accepted: boolean(),
50667
51317
  targetVersion: string().nullable(),
@@ -50679,14 +51329,27 @@ var ServerUpdateActionResultSchema = object({
50679
51329
  * The failure itself, verbatim enough to act on (the npm output for a
50680
51330
  * staging install). Non-null only for `failed`.
50681
51331
  */
50682
- error: string().nullable().optional()
51332
+ error: string().nullable().optional(),
51333
+ /**
51334
+ * The addons the target version would refuse to start on this node's live
51335
+ * store (D877), present when the pre-flight refused the update
51336
+ * (`reason: 'addon-would-refuse'`). Optional for version skew.
51337
+ */
51338
+ blockers: array(UpdateBlockerSchema).optional()
50683
51339
  });
50684
51340
  method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
50685
51341
  kind: "mutation",
50686
51342
  auth: "admin"
50687
- }), method(object({
50688
- /** Explicit target version; omitted = latest from the registry. */
50689
- version: string().optional() }), ServerUpdateActionResultSchema, {
51343
+ }), method(object({
51344
+ /** Explicit target version; omitted = latest from the registry. */
51345
+ version: string().optional(),
51346
+ /**
51347
+ * Apply even though the pre-flight (D877) found addons the target would
51348
+ * refuse to start. Off by default: those addons stay down until the
51349
+ * remedy is run, which is the incident this gate exists to prevent.
51350
+ */
51351
+ acknowledgeRefusals: boolean().optional()
51352
+ }), ServerUpdateActionResultSchema, {
50690
51353
  kind: "mutation",
50691
51354
  auth: "admin",
50692
51355
  timeoutMs: 16 * 6e4
@@ -53132,7 +53795,9 @@ var ManifestShapeSchema = object({
53132
53795
  var AddonProcessSchema = object({
53133
53796
  pid: number().optional(),
53134
53797
  mode: string(),
53135
- state: string()
53798
+ state: string(),
53799
+ /** Why the runner is `refused` (an addon's own words), when it is (D877). */
53800
+ reason: string().optional()
53136
53801
  });
53137
53802
  /**
53138
53803
  * Per-addon health snapshot from the kernel's AddonHealthMonitor.
@@ -53900,7 +54565,8 @@ var batteryCapability = {
53900
54565
  DeviceType.Button,
53901
54566
  DeviceType.Switch,
53902
54567
  DeviceType.PetFeeder,
53903
- DeviceType.Container
54568
+ DeviceType.Container,
54569
+ DeviceType.Thermostat
53904
54570
  ],
53905
54571
  methods: {
53906
54572
  /**
@@ -54521,6 +55187,12 @@ var ClimateControlStatusSchema = object({
54521
55187
  /** Horizontal louver swing on/off. Null when the device has no
54522
55188
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
54523
55189
  swingHorizontal: boolean().nullable(),
55190
+ /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
55191
+ minTemp: number().nullable().optional(),
55192
+ /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
55193
+ maxTemp: number().nullable().optional(),
55194
+ /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
55195
+ tempStep: number().positive().nullable().optional(),
54524
55196
  /** Ms epoch when the slice was last updated (push or command). */
54525
55197
  lastFetchedAt: number()
54526
55198
  });
@@ -55375,6 +56047,125 @@ onStatusChanged: { data: object({
55375
56047
  * whether persisting is worth a SQLite commit. */
55376
56048
  volatileStateFields: ["lastChangedAt"]
55377
56049
  };
56050
+ /**
56051
+ * Smart-dishwasher cap — program lifecycle, door / remote-start gates,
56052
+ * consumable levels (salt, rinse aid) and common program options.
56053
+ *
56054
+ * Sources: native Bosch Home Connect (`dishwasher` device type). Reusable
56055
+ * by any integration that exposes the same program / option surface.
56056
+ *
56057
+ * `kind: 'poll'` — cloud REST APIs typically have no push channel; the
56058
+ * provider refreshes the slice on an interval and eagerly after commands.
56059
+ */
56060
+ var DishwasherStateSchema = _enum([
56061
+ "idle",
56062
+ "running",
56063
+ "paused",
56064
+ "finished",
56065
+ "error",
56066
+ "unavailable"
56067
+ ]);
56068
+ var DishwasherConsumableLevelSchema = _enum([
56069
+ "ok",
56070
+ "low",
56071
+ "empty"
56072
+ ]);
56073
+ var DishwasherStatusSchema = object({
56074
+ state: DishwasherStateSchema,
56075
+ /** Active program id/name, or null when none selected / unknown. */
56076
+ program: string().nullable(),
56077
+ /** Program tokens the hardware accepts — drives the UI selector. */
56078
+ availablePrograms: array(string()),
56079
+ /** Minutes remaining on the active cycle, or null when not running. */
56080
+ remainingMinutes: number().int().nonnegative().nullable(),
56081
+ /** Cycle completion 0..100, or null when not applicable. */
56082
+ progressPercent: number().min(0).max(100).nullable(),
56083
+ /** True when the door is reported open. */
56084
+ doorOpen: boolean(),
56085
+ /** True when remote start is allowed (door closed, power on, etc.). */
56086
+ remoteStartAllowed: boolean(),
56087
+ /** Main power / switched-on state. */
56088
+ powerOn: boolean(),
56089
+ saltLevel: DishwasherConsumableLevelSchema.nullable(),
56090
+ rinseAidLevel: DishwasherConsumableLevelSchema.nullable(),
56091
+ halfLoad: boolean(),
56092
+ hygienePlus: boolean(),
56093
+ errorCode: number().nullable(),
56094
+ errorLabel: string().nullable(),
56095
+ lastFetchedAt: number()
56096
+ });
56097
+ var dishwasherCapability = {
56098
+ name: "dishwasher",
56099
+ scope: "device",
56100
+ deviceNative: true,
56101
+ mode: "singleton",
56102
+ deviceTypes: [DeviceType.Dishwasher],
56103
+ methods: {
56104
+ start: method(object({ deviceId: number().int().nonnegative() }), _void(), {
56105
+ kind: "mutation",
56106
+ auth: "protected"
56107
+ }),
56108
+ pause: method(object({ deviceId: number().int().nonnegative() }), _void(), {
56109
+ kind: "mutation",
56110
+ auth: "protected"
56111
+ }),
56112
+ stop: method(object({ deviceId: number().int().nonnegative() }), _void(), {
56113
+ kind: "mutation",
56114
+ auth: "protected"
56115
+ }),
56116
+ setProgram: method(object({
56117
+ deviceId: number().int().nonnegative(),
56118
+ program: string().min(1)
56119
+ }), _void(), {
56120
+ kind: "mutation",
56121
+ auth: "protected"
56122
+ }),
56123
+ setPower: method(object({
56124
+ deviceId: number().int().nonnegative(),
56125
+ on: boolean()
56126
+ }), _void(), {
56127
+ kind: "mutation",
56128
+ auth: "protected"
56129
+ }),
56130
+ setHalfLoad: method(object({
56131
+ deviceId: number().int().nonnegative(),
56132
+ on: boolean()
56133
+ }), _void(), {
56134
+ kind: "mutation",
56135
+ auth: "protected"
56136
+ }),
56137
+ setHygienePlus: method(object({
56138
+ deviceId: number().int().nonnegative(),
56139
+ on: boolean()
56140
+ }), _void(), {
56141
+ kind: "mutation",
56142
+ auth: "protected"
56143
+ }),
56144
+ setOption: method(object({
56145
+ deviceId: number().int().nonnegative(),
56146
+ key: string().min(1),
56147
+ value: union([
56148
+ boolean(),
56149
+ string(),
56150
+ number()
56151
+ ])
56152
+ }), _void(), {
56153
+ kind: "mutation",
56154
+ auth: "protected"
56155
+ })
56156
+ },
56157
+ status: {
56158
+ schema: DishwasherStatusSchema,
56159
+ kind: "poll"
56160
+ },
56161
+ runtimeState: DishwasherStatusSchema,
56162
+ durability: "session",
56163
+ history: { fields: { progressPercent: {
56164
+ kind: "gauge",
56165
+ unit: "%",
56166
+ deadband: 2
56167
+ } } }
56168
+ };
55378
56169
  var DoorbellStatusSchema = object({
55379
56170
  /** Ms epoch of the last press. null = never observed since this provider started. */
55380
56171
  lastPressedAt: number().nullable(),
@@ -56177,6 +56968,32 @@ var gasCapability = {
56177
56968
  * whether persisting is worth a SQLite commit. */
56178
56969
  volatileStateFields: ["lastChangedAt"]
56179
56970
  };
56971
+ var HeatingValveStatusSchema = object({
56972
+ /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
56973
+ position: number().min(0).max(100).nullable(),
56974
+ /** Ms epoch when the slice was last updated (push or poll). */
56975
+ lastFetchedAt: number()
56976
+ });
56977
+ var heatingValveCapability = {
56978
+ name: "heating-valve",
56979
+ scope: "device",
56980
+ deviceNative: true,
56981
+ mode: "singleton",
56982
+ deviceTypes: [DeviceType.Thermostat],
56983
+ methods: {},
56984
+ status: {
56985
+ schema: HeatingValveStatusSchema,
56986
+ kind: "push"
56987
+ },
56988
+ runtimeState: HeatingValveStatusSchema,
56989
+ durability: "session",
56990
+ history: { fields: { position: {
56991
+ kind: "gauge",
56992
+ unit: "%",
56993
+ deadband: 1
56994
+ } } },
56995
+ volatileStateFields: ["lastFetchedAt"]
56996
+ };
56180
56997
  /**
56181
56998
  * Humidifier / dehumidifier cap. Models HA `humidifier.*` entities —
56182
56999
  * an on/off actuator with an optional target-humidity setpoint and an
@@ -57309,7 +58126,19 @@ var TlsStatusSchema = object({
57309
58126
  sans: array(string()),
57310
58127
  caCertPem: string().nullable(),
57311
58128
  reissueError: string().nullable(),
57312
- restartRequired: boolean()
58129
+ restartRequired: boolean(),
58130
+ /** An uploaded certificate is served but no CA is kept for it and it is not publicly signed (D873). */
58131
+ caMissing: boolean(),
58132
+ /** Does the hub's own generated CA carry a critical name constraint? `null` while it has none. */
58133
+ localCaConstrained: boolean().nullable(),
58134
+ /** ISO time the generated CA was replaced by a constrained one; devices must re-download it. */
58135
+ caReplacedAt: string().nullable(),
58136
+ /** The uploaded certificate kept on disk, selectable again after "Revert to generated". */
58137
+ storedUpload: object({
58138
+ leafFingerprintSha256: string(),
58139
+ validTo: string(),
58140
+ hasCa: boolean()
58141
+ }).nullable()
57313
58142
  });
57314
58143
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
57315
58144
  /**
@@ -57365,6 +58194,9 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
57365
58194
  }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
57366
58195
  kind: "mutation",
57367
58196
  auth: "admin"
58197
+ }), method(_void(), TlsStatusSchema, {
58198
+ kind: "mutation",
58199
+ auth: "admin"
57368
58200
  });
57369
58201
  var LockControlStatusSchema = object({
57370
58202
  /** Lifecycle state of the lock. `jammed` means the motor reported
@@ -63858,7 +64690,11 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
63858
64690
  kind: "mutation",
63859
64691
  auth: "admin",
63860
64692
  access: "delete"
63861
- }), method(object({ userId: string() }), TotpStatusSchema, { auth: "admin" }), method(object({
64693
+ }), method(object({ userId: string() }), TotpStatusSchema, { auth: "admin" }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
64694
+ kind: "mutation",
64695
+ auth: "admin",
64696
+ access: "create"
64697
+ }), method(object({
63862
64698
  userId: string(),
63863
64699
  code: string()
63864
64700
  }), object({ valid: boolean() }), {
@@ -65578,6 +66414,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
65578
66414
  dayNight: dayNightCapability,
65579
66415
  deviceDiscovery: deviceDiscoveryCapability,
65580
66416
  deviceStatus: deviceStatusCapability,
66417
+ dishwasher: dishwasherCapability,
65581
66418
  doorbell: doorbellCapability,
65582
66419
  enumSensor: enumSensorCapability,
65583
66420
  eventEmitter: eventEmitterCapability,
@@ -65585,6 +66422,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
65585
66422
  featureProbe: featureProbeCapability,
65586
66423
  flood: floodCapability,
65587
66424
  gas: gasCapability,
66425
+ heatingValve: heatingValveCapability,
65588
66426
  humidifier: humidifierCapability,
65589
66427
  humiditySensor: humiditySensorCapability,
65590
66428
  image: imageCapability,
@@ -65670,13 +66508,14 @@ var BaseDevice = class {
65670
66508
  get state() {
65671
66509
  if (!this._stateProxyCache) {
65672
66510
  const cache = {};
65673
- const handler = { get: (_target, key) => {
66511
+ const handler = { get: (target, key) => {
65674
66512
  const k = key;
65675
- if (k in cache) return cache[k];
66513
+ const existing = Reflect.get(target, k);
66514
+ if (existing !== void 0) return existing;
65676
66515
  const cap = DEVICE_LOCAL_STATE_CAPS[k];
65677
66516
  if (!cap) return void 0;
65678
66517
  const proxy = this.sliceProxy(cap);
65679
- cache[k] = proxy;
66518
+ Reflect.set(target, k, proxy);
65680
66519
  return proxy;
65681
66520
  } };
65682
66521
  this._stateProxyCache = new Proxy(cache, handler);
@@ -65875,7 +66714,7 @@ var BaseDevice = class {
65875
66714
  async updateSourceInfo(patch) {
65876
66715
  const next = mergeSourceInfo(this.sourceInfo, patch);
65877
66716
  this._sourceInfoCache = Object.freeze({ ...next });
65878
- const action = this.ctx.api?.deviceManager?.setMetadata;
66717
+ const action = this.ctx.api.deviceManager?.setMetadata;
65879
66718
  if (action) try {
65880
66719
  await action.mutate({
65881
66720
  deviceId: this.id,
@@ -65910,7 +66749,7 @@ var BaseDevice = class {
65910
66749
  * device boot on a meta refresh.
65911
66750
  */
65912
66751
  async refreshFeatures() {
65913
- const action = this.ctx.api?.deviceManager?.registerDevice;
66752
+ const action = this.ctx.api.deviceManager?.registerDevice;
65914
66753
  if (!action) return;
65915
66754
  try {
65916
66755
  await action.mutate({
@@ -66309,6 +67148,12 @@ var BaseDeviceProvider = class extends BaseAddon {
66309
67148
  provider: this
66310
67149
  }];
66311
67150
  }
67151
+ /** Flatten `super.onInitialize()` whether it returned an array or an envelope. */
67152
+ providerRegistrationsFromSuper(result) {
67153
+ if (Array.isArray(result)) return result;
67154
+ if (result !== void 0 && result.providers !== void 0) return [...result.providers];
67155
+ return [];
67156
+ }
66312
67157
  async onShutdown() {
66313
67158
  this._restoreShuttingDown = true;
66314
67159
  this.cancelRestoreRetries();
@@ -66835,7 +67680,7 @@ var BaseDeviceProvider = class extends BaseAddon {
66835
67680
  return toDeviceSummary(device, this.addonId);
66836
67681
  }
66837
67682
  };
66838
- 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;
67683
+ 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;
66839
67684
  new Set(Object.values(DeviceType));
66840
67685
  Object.freeze({
66841
67686
  "accessories.getStatus": {
@@ -67984,6 +68829,12 @@ Object.freeze({
67984
68829
  addonId: null,
67985
68830
  access: "view"
67986
68831
  },
68832
+ "coreBlocks.migrateRawSecrets": {
68833
+ capName: "core-blocks",
68834
+ capScope: "system",
68835
+ addonId: null,
68836
+ access: "create"
68837
+ },
67987
68838
  "coreBlocks.restart": {
67988
68839
  capName: "core-blocks",
67989
68840
  capScope: "system",
@@ -69250,6 +70101,60 @@ Object.freeze({
69250
70101
  addonId: null,
69251
70102
  access: "view"
69252
70103
  },
70104
+ "dishwasher.getStatus": {
70105
+ capName: "dishwasher",
70106
+ capScope: "device",
70107
+ addonId: null,
70108
+ access: "view"
70109
+ },
70110
+ "dishwasher.pause": {
70111
+ capName: "dishwasher",
70112
+ capScope: "device",
70113
+ addonId: null,
70114
+ access: "create"
70115
+ },
70116
+ "dishwasher.setHalfLoad": {
70117
+ capName: "dishwasher",
70118
+ capScope: "device",
70119
+ addonId: null,
70120
+ access: "create"
70121
+ },
70122
+ "dishwasher.setHygienePlus": {
70123
+ capName: "dishwasher",
70124
+ capScope: "device",
70125
+ addonId: null,
70126
+ access: "create"
70127
+ },
70128
+ "dishwasher.setOption": {
70129
+ capName: "dishwasher",
70130
+ capScope: "device",
70131
+ addonId: null,
70132
+ access: "create"
70133
+ },
70134
+ "dishwasher.setPower": {
70135
+ capName: "dishwasher",
70136
+ capScope: "device",
70137
+ addonId: null,
70138
+ access: "create"
70139
+ },
70140
+ "dishwasher.setProgram": {
70141
+ capName: "dishwasher",
70142
+ capScope: "device",
70143
+ addonId: null,
70144
+ access: "create"
70145
+ },
70146
+ "dishwasher.start": {
70147
+ capName: "dishwasher",
70148
+ capScope: "device",
70149
+ addonId: null,
70150
+ access: "create"
70151
+ },
70152
+ "dishwasher.stop": {
70153
+ capName: "dishwasher",
70154
+ capScope: "device",
70155
+ addonId: null,
70156
+ access: "create"
70157
+ },
69253
70158
  "doorbell.getStatus": {
69254
70159
  capName: "doorbell",
69255
70160
  capScope: "device",
@@ -69460,6 +70365,12 @@ Object.freeze({
69460
70365
  addonId: null,
69461
70366
  access: "view"
69462
70367
  },
70368
+ "heatingValve.getStatus": {
70369
+ capName: "heating-valve",
70370
+ capScope: "device",
70371
+ addonId: null,
70372
+ access: "view"
70373
+ },
69463
70374
  "humidifier.getStatus": {
69464
70375
  capName: "humidifier",
69465
70376
  capScope: "device",
@@ -69670,6 +70581,12 @@ Object.freeze({
69670
70581
  addonId: null,
69671
70582
  access: "create"
69672
70583
  },
70584
+ "keychainAdmin.importSecret": {
70585
+ capName: "keychain-admin",
70586
+ capScope: "system",
70587
+ addonId: null,
70588
+ access: "create"
70589
+ },
69673
70590
  "keychainAdmin.listAudit": {
69674
70591
  capName: "keychain-admin",
69675
70592
  capScope: "system",
@@ -69838,6 +70755,12 @@ Object.freeze({
69838
70755
  addonId: null,
69839
70756
  access: "view"
69840
70757
  },
70758
+ "llm.migrateRawSecrets": {
70759
+ capName: "llm",
70760
+ capScope: "system",
70761
+ addonId: null,
70762
+ access: "create"
70763
+ },
69841
70764
  "llm.resolveModelRef": {
69842
70765
  capName: "llm",
69843
70766
  capScope: "system",
@@ -70066,6 +70989,12 @@ Object.freeze({
70066
70989
  addonId: null,
70067
70990
  access: "create"
70068
70991
  },
70992
+ "localNetwork.useStoredUploadedCertificate": {
70993
+ capName: "local-network",
70994
+ capScope: "system",
70995
+ addonId: null,
70996
+ access: "create"
70997
+ },
70069
70998
  "lockControl.getStatus": {
70070
70999
  capName: "lock-control",
70071
71000
  capScope: "device",
@@ -72718,6 +73647,30 @@ Object.freeze({
72718
73647
  addonId: null,
72719
73648
  access: "create"
72720
73649
  },
73650
+ "recordEntries.deleteEntry": {
73651
+ capName: "record-entries",
73652
+ capScope: "system",
73653
+ addonId: null,
73654
+ access: "delete"
73655
+ },
73656
+ "recordEntries.listEntries": {
73657
+ capName: "record-entries",
73658
+ capScope: "system",
73659
+ addonId: null,
73660
+ access: "view"
73661
+ },
73662
+ "recordEntries.listEntryKinds": {
73663
+ capName: "record-entries",
73664
+ capScope: "system",
73665
+ addonId: null,
73666
+ access: "view"
73667
+ },
73668
+ "recordEntries.putEntry": {
73669
+ capName: "record-entries",
73670
+ capScope: "system",
73671
+ addonId: null,
73672
+ access: "create"
73673
+ },
72721
73674
  "recording.getAvailability": {
72722
73675
  capName: "recording",
72723
73676
  capScope: "device",
@@ -73018,6 +73971,102 @@ Object.freeze({
73018
73971
  addonId: null,
73019
73972
  access: "view"
73020
73973
  },
73974
+ "records.aggregate": {
73975
+ capName: "records",
73976
+ capScope: "system",
73977
+ addonId: null,
73978
+ access: "view"
73979
+ },
73980
+ "records.allocateId": {
73981
+ capName: "records",
73982
+ capScope: "system",
73983
+ addonId: null,
73984
+ access: "create"
73985
+ },
73986
+ "records.count": {
73987
+ capName: "records",
73988
+ capScope: "system",
73989
+ addonId: null,
73990
+ access: "view"
73991
+ },
73992
+ "records.declareKinds": {
73993
+ capName: "records",
73994
+ capScope: "system",
73995
+ addonId: null,
73996
+ access: "create"
73997
+ },
73998
+ "records.delete": {
73999
+ capName: "records",
74000
+ capScope: "system",
74001
+ addonId: null,
74002
+ access: "delete"
74003
+ },
74004
+ "records.deleteDevice": {
74005
+ capName: "records",
74006
+ capScope: "system",
74007
+ addonId: null,
74008
+ access: "delete"
74009
+ },
74010
+ "records.deleteMany": {
74011
+ capName: "records",
74012
+ capScope: "system",
74013
+ addonId: null,
74014
+ access: "delete"
74015
+ },
74016
+ "records.deleteWhere": {
74017
+ capName: "records",
74018
+ capScope: "system",
74019
+ addonId: null,
74020
+ access: "delete"
74021
+ },
74022
+ "records.get": {
74023
+ capName: "records",
74024
+ capScope: "system",
74025
+ addonId: null,
74026
+ access: "view"
74027
+ },
74028
+ "records.getMany": {
74029
+ capName: "records",
74030
+ capScope: "system",
74031
+ addonId: null,
74032
+ access: "view"
74033
+ },
74034
+ "records.histogram": {
74035
+ capName: "records",
74036
+ capScope: "system",
74037
+ addonId: null,
74038
+ access: "view"
74039
+ },
74040
+ "records.patch": {
74041
+ capName: "records",
74042
+ capScope: "system",
74043
+ addonId: null,
74044
+ access: "create"
74045
+ },
74046
+ "records.put": {
74047
+ capName: "records",
74048
+ capScope: "system",
74049
+ addonId: null,
74050
+ access: "create"
74051
+ },
74052
+ "records.putMany": {
74053
+ capName: "records",
74054
+ capScope: "system",
74055
+ addonId: null,
74056
+ access: "create"
74057
+ },
74058
+ "records.query": {
74059
+ capName: "records",
74060
+ capScope: "system",
74061
+ addonId: null,
74062
+ access: "view"
74063
+ },
74064
+ "records.secretRefs": {
74065
+ capName: "records",
74066
+ capScope: "system",
74067
+ addonId: null,
74068
+ access: "view"
74069
+ },
73021
74070
  "sceneMonitor.captureReference": {
73022
74071
  capName: "scene-monitor",
73023
74072
  capScope: "device",
@@ -73450,6 +74499,48 @@ Object.freeze({
73450
74499
  addonId: null,
73451
74500
  access: "create"
73452
74501
  },
74502
+ "storageAdmin.adoptRecords": {
74503
+ capName: "storage-admin",
74504
+ capScope: "system",
74505
+ addonId: null,
74506
+ access: "create"
74507
+ },
74508
+ "storageAdmin.dropCollection": {
74509
+ capName: "storage-admin",
74510
+ capScope: "system",
74511
+ addonId: null,
74512
+ access: "delete"
74513
+ },
74514
+ "storageAdmin.isAdoptedLegacyTable": {
74515
+ capName: "storage-admin",
74516
+ capScope: "system",
74517
+ addonId: null,
74518
+ access: "view"
74519
+ },
74520
+ "storageAdmin.listKinds": {
74521
+ capName: "storage-admin",
74522
+ capScope: "system",
74523
+ addonId: null,
74524
+ access: "view"
74525
+ },
74526
+ "storageAdmin.listTables": {
74527
+ capName: "storage-admin",
74528
+ capScope: "system",
74529
+ addonId: null,
74530
+ access: "view"
74531
+ },
74532
+ "storageAdmin.readLegacyCollection": {
74533
+ capName: "storage-admin",
74534
+ capScope: "system",
74535
+ addonId: null,
74536
+ access: "view"
74537
+ },
74538
+ "storageAdmin.readRecords": {
74539
+ capName: "storage-admin",
74540
+ capScope: "system",
74541
+ addonId: null,
74542
+ access: "view"
74543
+ },
73453
74544
  "storageEvictable.evict": {
73454
74545
  capName: "storage-evictable",
73455
74546
  capScope: "system",
@@ -74362,6 +75453,12 @@ Object.freeze({
74362
75453
  addonId: null,
74363
75454
  access: "view"
74364
75455
  },
75456
+ "userManagement.migrateRawSecrets": {
75457
+ capName: "user-management",
75458
+ capScope: "system",
75459
+ addonId: null,
75460
+ access: "create"
75461
+ },
74365
75462
  "userManagement.oauthExchangeCode": {
74366
75463
  capName: "user-management",
74367
75464
  capScope: "system",
@@ -74861,7 +75958,7 @@ Object.freeze({
74861
75958
  access: "create"
74862
75959
  }
74863
75960
  });
74864
- 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;
75961
+ 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;
74865
75962
  DeviceFeature.BatteryOperated;
74866
75963
  Object.freeze({
74867
75964
  "accessories.getStatus": [{
@@ -75685,6 +76782,51 @@ Object.freeze({
75685
76782
  form: "single",
75686
76783
  optional: false
75687
76784
  }],
76785
+ "dishwasher.getStatus": [{
76786
+ name: "deviceId",
76787
+ form: "single",
76788
+ optional: false
76789
+ }],
76790
+ "dishwasher.pause": [{
76791
+ name: "deviceId",
76792
+ form: "single",
76793
+ optional: false
76794
+ }],
76795
+ "dishwasher.setHalfLoad": [{
76796
+ name: "deviceId",
76797
+ form: "single",
76798
+ optional: false
76799
+ }],
76800
+ "dishwasher.setHygienePlus": [{
76801
+ name: "deviceId",
76802
+ form: "single",
76803
+ optional: false
76804
+ }],
76805
+ "dishwasher.setOption": [{
76806
+ name: "deviceId",
76807
+ form: "single",
76808
+ optional: false
76809
+ }],
76810
+ "dishwasher.setPower": [{
76811
+ name: "deviceId",
76812
+ form: "single",
76813
+ optional: false
76814
+ }],
76815
+ "dishwasher.setProgram": [{
76816
+ name: "deviceId",
76817
+ form: "single",
76818
+ optional: false
76819
+ }],
76820
+ "dishwasher.start": [{
76821
+ name: "deviceId",
76822
+ form: "single",
76823
+ optional: false
76824
+ }],
76825
+ "dishwasher.stop": [{
76826
+ name: "deviceId",
76827
+ form: "single",
76828
+ optional: false
76829
+ }],
75688
76830
  "doorbell.getStatus": [{
75689
76831
  name: "deviceId",
75690
76832
  form: "single",
@@ -75754,6 +76896,11 @@ Object.freeze({
75754
76896
  form: "single",
75755
76897
  optional: false
75756
76898
  }],
76899
+ "heatingValve.getStatus": [{
76900
+ name: "deviceId",
76901
+ form: "single",
76902
+ optional: false
76903
+ }],
75757
76904
  "humidifier.getStatus": [{
75758
76905
  name: "deviceId",
75759
76906
  form: "single",
@@ -77038,6 +78185,37 @@ Object.freeze({
77038
78185
  form: "single",
77039
78186
  optional: false
77040
78187
  }],
78188
+ "records.count": [{
78189
+ name: "deviceId",
78190
+ form: "single",
78191
+ optional: true
78192
+ }],
78193
+ "records.deleteDevice": [{
78194
+ name: "deviceId",
78195
+ form: "single",
78196
+ optional: false
78197
+ }],
78198
+ "records.deleteWhere": [{
78199
+ name: "deviceId",
78200
+ form: "single",
78201
+ optional: true
78202
+ }],
78203
+ "records.histogram": [{
78204
+ name: "deviceId",
78205
+ form: "single",
78206
+ optional: true
78207
+ }],
78208
+ "records.putMany": [{
78209
+ name: "records",
78210
+ form: "object-array",
78211
+ optional: false,
78212
+ itemField: "deviceId"
78213
+ }],
78214
+ "records.query": [{
78215
+ name: "deviceId",
78216
+ form: "single",
78217
+ optional: true
78218
+ }],
77041
78219
  "sceneMonitor.captureReference": [{
77042
78220
  name: "deviceId",
77043
78221
  form: "single",
@@ -77154,6 +78332,17 @@ Object.freeze({
77154
78332
  form: "single",
77155
78333
  optional: false
77156
78334
  }],
78335
+ "storageAdmin.adoptRecords": [{
78336
+ name: "records",
78337
+ form: "object-array",
78338
+ optional: false,
78339
+ itemField: "deviceId"
78340
+ }],
78341
+ "storageAdmin.readRecords": [{
78342
+ name: "deviceId",
78343
+ form: "single",
78344
+ optional: true
78345
+ }],
77157
78346
  "streamBroker.acquireEgressTranscode": [{
77158
78347
  name: "deviceId",
77159
78348
  form: "single",
@@ -80020,7 +81209,7 @@ var TuyaProviderAddon = class extends BaseDeviceProvider {
80020
81209
  super({});
80021
81210
  }
80022
81211
  async onInitialize() {
80023
- const regs = await super.onInitialize();
81212
+ const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
80024
81213
  this.clients = new TuyaClientRegistry(this.ctx.logger, tuyaConnections);
80025
81214
  this.clients.setOnConnected((integrationId) => {
80026
81215
  this.ctx.logger.info("Tuya: integration connected", { meta: { integrationId } });