@camstack/addon-provider-amcrest 0.2.136 → 0.2.138

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 +1317 -124
  2. package/dist/addon.mjs +1317 -124
  3. package/package.json +1 -1
package/dist/addon.mjs CHANGED
@@ -14,7 +14,7 @@ import { networkInterfaces } from "node:os";
14
14
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
15
15
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
16
16
  //#endregion
17
- //#region ../types/dist/event-category-C4hVnRo8.mjs
17
+ //#region ../types/dist/event-category-BvOFi6PY.mjs
18
18
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
19
19
  EventCategory["SystemBoot"] = "system.boot";
20
20
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -78,6 +78,24 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
78
78
  * consumes this category and maps it to the `addon-crash-loop` kind.
79
79
  */
80
80
  EventCategory["AddonRunnerFailed"] = "addon.runner-failed";
81
+ /**
82
+ * A runner whose addon refused to start with a typed reason (D877) - parked
83
+ * in `refused`, never counted by the crash breaker, retried on operator
84
+ * action. One event per refusal.
85
+ */
86
+ EventCategory["AddonRunnerRefused"] = "addon.runner-refused";
87
+ /**
88
+ * A runner settled on a state (D877): `running` (READY), `crashed`, `failed` or
89
+ * `refused`. In-process telemetry for the boot check (re-evaluate while any
90
+ * runner is still pending) and the alert centre (close a recovered runner).
91
+ */
92
+ EventCategory["ProcessStateChanged"] = "process.state-changed";
93
+ /**
94
+ * The node's one-shot runner boot check (D877): planned runners against the
95
+ * process service, `degraded` when any is failed, refused or missing. The
96
+ * alert centre turns it into one alert and a direct critical push.
97
+ */
98
+ EventCategory["SystemRunnerCheck"] = "system.runner-check";
81
99
  EventCategory["AddonError"] = "addon.error";
82
100
  EventCategory["AddonPageReady"] = "addon.page-ready";
83
101
  EventCategory["AddonWidgetReady"] = "addon.widget-ready";
@@ -5402,7 +5420,7 @@ var ZodIssueCode = {
5402
5420
  var ZodFirstPartyTypeKind;
5403
5421
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5404
5422
  //#endregion
5405
- //#region ../types/dist/sleep-DRqxtc0C.mjs
5423
+ //#region ../types/dist/sleep-BOjuyoPC.mjs
5406
5424
  /**
5407
5425
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5408
5426
  * window to float samples (D455).
@@ -5519,7 +5537,7 @@ function isKeychainStoredField(field) {
5519
5537
  var SECRET_CLEAR = { $secretClear: true };
5520
5538
  /** The `'***'` token D634 redaction writes for non-admins. */
5521
5539
  var LEGACY_DEVICE_REDACTION = "***";
5522
- function isRecord$2(value) {
5540
+ function isRecord$5(value) {
5523
5541
  return typeof value === "object" && value !== null && !Array.isArray(value);
5524
5542
  }
5525
5543
  function makeSecretMarker(ref) {
@@ -5530,12 +5548,12 @@ function makeSecretMarker(ref) {
5530
5548
  }
5531
5549
  /** A stored marker: an object whose `$secret` is a non-empty string. */
5532
5550
  function isSecretMarker(value) {
5533
- if (!isRecord$2(value)) return false;
5551
+ if (!isRecord$5(value)) return false;
5534
5552
  const ref = value["$secret"];
5535
5553
  return typeof ref === "string" && ref.length > 0;
5536
5554
  }
5537
5555
  function isSecretClear(value) {
5538
- return isRecord$2(value) && value["$secretClear"] === true;
5556
+ return isRecord$5(value) && value["$secretClear"] === true;
5539
5557
  }
5540
5558
  /**
5541
5559
  * The UI view of a stored secret field value: a marker becomes "set", inline
@@ -6145,7 +6163,7 @@ var KeychainWriteRefusedError = class extends Error {
6145
6163
  }
6146
6164
  };
6147
6165
  var KEEP = { kind: "keep" };
6148
- function isRecord$1$1(value) {
6166
+ function isRecord$4(value) {
6149
6167
  return typeof value === "object" && value !== null && !Array.isArray(value);
6150
6168
  }
6151
6169
  function defaultMintId() {
@@ -6158,7 +6176,7 @@ function collectSecretMarkerRefs(value, out = []) {
6158
6176
  return out;
6159
6177
  }
6160
6178
  if (Array.isArray(value)) for (const item of value) collectSecretMarkerRefs(item, out);
6161
- else if (isRecord$1$1(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6179
+ else if (isRecord$4(value)) for (const child of Object.values(value)) collectSecretMarkerRefs(child, out);
6162
6180
  return out;
6163
6181
  }
6164
6182
  async function storedValueEquals(secrets, marker, candidate) {
@@ -6213,7 +6231,7 @@ function rowId(row, idKey) {
6213
6231
  return null;
6214
6232
  }
6215
6233
  async function writeArray(ctx, node, incoming, stored, fieldPath) {
6216
- const storedRows = Array.isArray(stored) ? stored.filter(isRecord$1$1) : [];
6234
+ const storedRows = Array.isArray(stored) ? stored.filter(isRecord$4) : [];
6217
6235
  if (!Array.isArray(incoming)) {
6218
6236
  ctx.deletes.push(...collectSecretMarkerRefs(storedRows));
6219
6237
  return {
@@ -6229,7 +6247,7 @@ async function writeArray(ctx, node, incoming, stored, fieldPath) {
6229
6247
  const kept = /* @__PURE__ */ new Set();
6230
6248
  const rows = [];
6231
6249
  for (const row of incoming) {
6232
- if (!isRecord$1$1(row)) {
6250
+ if (!isRecord$4(row)) {
6233
6251
  rows.push(row);
6234
6252
  continue;
6235
6253
  }
@@ -6254,7 +6272,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
6254
6272
  case "leaf": return writeLeaf(ctx, incoming, stored, fieldPath);
6255
6273
  case "array": return writeArray(ctx, node, incoming, stored, fieldPath);
6256
6274
  case "object": {
6257
- if (!isRecord$1$1(incoming)) {
6275
+ if (!isRecord$4(incoming)) {
6258
6276
  if (incoming === void 0) return KEEP;
6259
6277
  ctx.deletes.push(...collectSecretMarkerRefs(stored));
6260
6278
  return {
@@ -6262,7 +6280,7 @@ async function writeNode(ctx, node, incoming, stored, fieldPath) {
6262
6280
  value: incoming
6263
6281
  };
6264
6282
  }
6265
- const storedObj = isRecord$1$1(stored) ? stored : {};
6283
+ const storedObj = isRecord$4(stored) ? stored : {};
6266
6284
  return {
6267
6285
  kind: "value",
6268
6286
  value: await writeMembers(ctx, node.children, incoming, storedObj, (m) => `${fieldPath}.${m}`)
@@ -6385,7 +6403,7 @@ async function resolveSecretMarkers(value, secrets, options = {}) {
6385
6403
  value: items
6386
6404
  };
6387
6405
  }
6388
- if (isRecord$1$1(v)) {
6406
+ if (isRecord$4(v)) {
6389
6407
  const out = {};
6390
6408
  for (const [k, child] of Object.entries(v)) {
6391
6409
  const s = substitute(child);
@@ -6653,7 +6671,6 @@ function createHandle(raw, opts) {
6653
6671
  const writeAddon = (patch) => write("addon", opts.addonTree(), void 0, patch, () => readStoredAddonBlob(raw), (p) => raw.writeAddonStore(p));
6654
6672
  const readAddonStoreResult = raw.readAddonStoreResult;
6655
6673
  const readAddonStoreKeyResult = raw.readAddonStoreKeyResult;
6656
- const readDeviceStoreBatch = raw.readDeviceStoreBatch;
6657
6674
  const overrides = {
6658
6675
  readAddonStore: async () => resolveRead("addon", await raw.readAddonStore()),
6659
6676
  ...readAddonStoreResult !== void 0 ? { readAddonStoreResult: async () => {
@@ -6677,12 +6694,12 @@ function createHandle(raw, opts) {
6677
6694
  };
6678
6695
  } } : {},
6679
6696
  readDeviceStore: async (deviceId) => resolveRead(`device:${deviceId}`, await raw.readDeviceStore(deviceId)),
6680
- ...readDeviceStoreBatch !== void 0 ? { readDeviceStoreBatch: async (deviceIds) => {
6681
- const rows = await readDeviceStoreBatch.call(raw, deviceIds);
6697
+ readDeviceStoreBatch: async (deviceIds) => {
6698
+ const rows = await raw.readDeviceStoreBatch(deviceIds);
6682
6699
  const out = /* @__PURE__ */ new Map();
6683
6700
  for (const [deviceId, blob] of rows) out.set(deviceId, await resolveRead(`device:${deviceId}`, blob));
6684
6701
  return out;
6685
- } } : {},
6702
+ },
6686
6703
  writeAddonStore: async (patch) => {
6687
6704
  await writeAddon(patch);
6688
6705
  },
@@ -7309,7 +7326,7 @@ var BaseAddon = class {
7309
7326
  if (!ctx) return;
7310
7327
  const addonId = ctx.id;
7311
7328
  setImmediate(() => {
7312
- ctx.api.addons?.restartAddon?.mutate({ addonId }).then(() => {
7329
+ ctx.api.addons.restartAddon.mutate({ addonId }).then(() => {
7313
7330
  ctx.logger.info("addon auto-restart triggered by restart-required setting change", { meta: { changedFields: patchKeys.filter((k) => restartKeys.has(k)) } });
7314
7331
  }).catch((err) => {
7315
7332
  ctx.logger.error("addon auto-restart failed", { meta: { error: err instanceof Error ? err.message : String(err) } });
@@ -7509,7 +7526,7 @@ var BaseAddon = class {
7509
7526
  return this._addonSecretTree;
7510
7527
  }
7511
7528
  deviceSecretTree() {
7512
- this._deviceSecretTree ??= secretTreeFromSchema(this.deviceSettingsSchema());
7529
+ this._deviceSecretTree ??= mergeSecretStatePaths(secretTreeFromSchema(this.deviceSettingsSchema()), this.secretStatePaths);
7513
7530
  return this._deviceSecretTree;
7514
7531
  }
7515
7532
  /**
@@ -7585,42 +7602,135 @@ var BaseAddon = class {
7585
7602
  this[timerOf] = timer;
7586
7603
  }
7587
7604
  /**
7588
- * Wrap `ctx.settings.readAddonStore()` with a short retry budget so a
7589
- * transient settings-store outage (mid-restart of sqlite-settings, tsx-watch
7590
- * swap, or any race where the SqliteSettingsBackend is between shutdown and
7591
- * re-initialize) doesn't propagate up into `initialize()` and leave the
7605
+ * Wrap `ctx.settings.readAddonStore()` with a retry budget so a transient
7606
+ * settings-store outage doesn't propagate up into `initialize()` and leave the
7592
7607
  * addon permanently broken.
7593
7608
  *
7594
- * Retry only the two known infra fingerprints. Anything else propagates so
7595
- * real bugs surface immediately. After the budget expires we fall back to
7596
- * `{}` (defaults) — the addon's first successful patch will rehydrate.
7609
+ * Two kinds of outage, two budgets:
7610
+ * - the store's own restart fingerprints (`SqliteSettingsBackend not
7611
+ * initialized`, `provider not available`): a short budget, then the addon
7612
+ * falls back to `{}` (defaults) - its first successful patch rehydrates;
7613
+ * - a TRANSPORT failure (the hub is restarting while this runner boots:
7614
+ * `discovery deadline exceeded`, `parent could not route`, a refused or
7615
+ * timed-out connection). Since D870 a runner's settings read THROWS instead
7616
+ * of answering `{}`, so without a budget a hub restart ends in a respawn
7617
+ * loop. A long, bounded budget; only after it the error propagates - the
7618
+ * addon never runs on defaults for a store it could not ask.
7619
+ * Anything else propagates immediately so real bugs surface.
7597
7620
  */
7598
7621
  async readAddonStoreWithRetry() {
7599
7622
  const settings = this.rawSettings();
7600
7623
  if (!settings) return {};
7601
- const delaysMs = [
7602
- 150,
7603
- 350,
7604
- 600,
7605
- 900
7606
- ];
7607
7624
  let lastErr;
7608
- for (let attempt = 0; attempt <= delaysMs.length; attempt++) try {
7625
+ for (let attempt = 0;; attempt++) try {
7609
7626
  const stored = await settings.readAddonStore() ?? {};
7610
7627
  this.settingsStoreReady = true;
7611
7628
  return stored;
7612
7629
  } catch (err) {
7613
7630
  lastErr = err;
7614
- const msg = err instanceof Error ? err.message : String(err);
7615
- if (!(msg.includes("SqliteSettingsBackend not initialized") || msg.includes("provider not available"))) throw err;
7616
- if (attempt === delaysMs.length) break;
7617
- await new Promise((r) => setTimeout(r, delaysMs[attempt]));
7631
+ const kind = classifyStoreError(err);
7632
+ if (kind === "permanent") throw err;
7633
+ const delay = (kind === "transport" ? TRANSPORT_RETRY_DELAYS_MS : RESTART_RETRY_DELAYS_MS)[attempt];
7634
+ if (delay === void 0) {
7635
+ if (kind === "transport") throw err;
7636
+ break;
7637
+ }
7638
+ await new Promise((r) => setTimeout(r, delay));
7618
7639
  }
7619
7640
  this.settingsStoreReady = false;
7620
7641
  this._ctx?.logger?.warn?.("readAddonStore: settings-store unavailable after retries — using defaults", { meta: { error: lastErr instanceof Error ? lastErr.message : String(lastErr) } });
7621
7642
  return {};
7622
7643
  }
7623
7644
  };
7645
+ /** The settings store's own restart fingerprints. */
7646
+ var RESTART_RETRY_DELAYS_MS = [
7647
+ 150,
7648
+ 350,
7649
+ 600,
7650
+ 900
7651
+ ];
7652
+ /** A hub restarting under a booting runner: ~100 s in total. */
7653
+ var TRANSPORT_RETRY_DELAYS_MS = [
7654
+ 150,
7655
+ 350,
7656
+ 600,
7657
+ 900,
7658
+ 2e3,
7659
+ 5e3,
7660
+ 1e4,
7661
+ 2e4,
7662
+ 3e4,
7663
+ 3e4
7664
+ ];
7665
+ var RESTART_FINGERPRINTS = ["SqliteSettingsBackend not initialized", "provider not available"];
7666
+ /** Node / socket error codes of a connection that did not happen or broke. */
7667
+ var TRANSPORT_CODES = new Set([
7668
+ "ECONNREFUSED",
7669
+ "ECONNRESET",
7670
+ "ETIMEDOUT",
7671
+ "EPIPE",
7672
+ "EHOSTUNREACH",
7673
+ "ENETUNREACH"
7674
+ ]);
7675
+ /** tRPC error codes that mean "the other side is not answering now", not "your call is wrong". */
7676
+ var UNAVAILABLE_TRPC_CODES = new Set([
7677
+ "TIMEOUT",
7678
+ "SERVICE_UNAVAILABLE",
7679
+ "BAD_GATEWAY",
7680
+ "GATEWAY_TIMEOUT"
7681
+ ]);
7682
+ /** The exact phrases OUR transport raises when it cannot reach the hub. */
7683
+ var TRANSPORT_PHRASES = [
7684
+ "discovery deadline exceeded",
7685
+ "parent could not route child unowned cap call",
7686
+ "UDS request timed out after",
7687
+ "channel closed"
7688
+ ];
7689
+ /** Moleculer error `type`s of a call that could not be served right now. */
7690
+ var MOLECULER_TRANSIENT_TYPES = new Set([
7691
+ "REQUEST_TIMEOUT",
7692
+ "SERVICE_NOT_FOUND",
7693
+ "SERVICE_NOT_AVAILABLE",
7694
+ "REQUEST_REJECTED",
7695
+ "BROKER_DISCONNECTED"
7696
+ ]);
7697
+ function stringProp(value, key) {
7698
+ if (typeof value !== "object" || value === null) return void 0;
7699
+ const found = Reflect.get(value, key);
7700
+ return typeof found === "string" ? found : void 0;
7701
+ }
7702
+ /** The code an error carries: its own, its cause's, or a tRPC `data.code`. */
7703
+ function errorCode(err) {
7704
+ const out = {};
7705
+ const cause = typeof err === "object" && err !== null ? Reflect.get(err, "cause") : void 0;
7706
+ const data = typeof err === "object" && err !== null ? Reflect.get(err, "data") : void 0;
7707
+ const node = stringProp(err, "code") ?? stringProp(cause, "code");
7708
+ if (node !== void 0) out.node = node;
7709
+ const trpc = stringProp(data, "code");
7710
+ if (trpc !== void 0) out.trpc = trpc;
7711
+ return out;
7712
+ }
7713
+ /**
7714
+ * Is a settings read failure worth retrying, and with which budget? Only
7715
+ * EXPLICIT signals count: a socket error code, a tRPC "unavailable" code, or
7716
+ * one of the exact phrases our own transport raises. A message that merely
7717
+ * contains "timed out" or "unreachable" is not evidence of anything, and
7718
+ * `settings store not wired` is a wiring bug that retrying cannot fix.
7719
+ */
7720
+ function classifyStoreError(err) {
7721
+ const message = err instanceof Error ? err.message : String(err);
7722
+ if (RESTART_FINGERPRINTS.some((f) => message.includes(f))) return "restart";
7723
+ const code = errorCode(err);
7724
+ if (code.node !== void 0 && TRANSPORT_CODES.has(code.node)) return "transport";
7725
+ if (code.trpc !== void 0 && UNAVAILABLE_TRPC_CODES.has(code.trpc)) return "transport";
7726
+ if (typeof err === "object" && err !== null) {
7727
+ if (Reflect.get(err, "retryable") === true) return "transport";
7728
+ const type = stringProp(err, "type");
7729
+ if (type !== void 0 && MOLECULER_TRANSIENT_TYPES.has(type)) return "transport";
7730
+ }
7731
+ if (TRANSPORT_PHRASES.some((f) => message.includes(f))) return "transport";
7732
+ return "permanent";
7733
+ }
7624
7734
  /**
7625
7735
  * Collect the keys of every field marked `perNode: true`, recursing into
7626
7736
  * layout containers (`group` fields and `sub-tabs` tabs) the same way
@@ -7710,6 +7820,156 @@ var PeerBytesTicketSchema = object({
7710
7820
  */
7711
7821
  declaredBytes: number().int().nonnegative().nullable()
7712
7822
  });
7823
+ /** Limits and names of `ctx.records` (D881). One authority; the engine and the facade read it. */
7824
+ /** The files a kind may live in. `history` is device-history's (D850), never records. */
7825
+ var RECORD_DATABASES = ["settings", "events"];
7826
+ /** The classes of a kind: the `@durable` vocabulary, one authority (replaces the annotation). */
7827
+ var RECORD_CLASSES = [
7828
+ "config",
7829
+ "ledger",
7830
+ "audit",
7831
+ "mirror",
7832
+ "ephemeral"
7833
+ ];
7834
+ var RECORD_DEFAULT_MAX_DATA_BYTES = 64 * 1024;
7835
+ var RECORD_MAX_DATA_BYTES = 1024 * 1024;
7836
+ var RECORD_BATCH_MAX = 1e3;
7837
+ var RECORD_QUERY_MAX = 1e3;
7838
+ function defOf(schema) {
7839
+ const zodBag = Reflect.get(schema, "_zod");
7840
+ if (typeof zodBag !== "object" || zodBag === null) return {};
7841
+ const def = Reflect.get(zodBag, "def");
7842
+ if (typeof def !== "object" || def === null) return {};
7843
+ return def;
7844
+ }
7845
+ function declaredHere(schema) {
7846
+ return globalRegistry.get(schema)?.secret === true;
7847
+ }
7848
+ /** Wrapper kinds whose `innerType` is the field itself. */
7849
+ var WRAPPERS = new Set([
7850
+ "optional",
7851
+ "nullable",
7852
+ "default",
7853
+ "prefault",
7854
+ "catch",
7855
+ "readonly",
7856
+ "nonoptional"
7857
+ ]);
7858
+ /**
7859
+ * The schema layers that make up ONE field: the schema itself and every
7860
+ * wrapper below it (optional / default / nullable / readonly / catch), plus
7861
+ * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
7862
+ * `.transform` on `in`).
7863
+ */
7864
+ function layersOf(schema) {
7865
+ const out = [];
7866
+ const queue = [schema];
7867
+ while (queue.length > 0) {
7868
+ const s = queue.shift();
7869
+ if (!s || out.includes(s)) continue;
7870
+ out.push(s);
7871
+ const def = defOf(s);
7872
+ if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
7873
+ if (def.type === "pipe") {
7874
+ if (def.in) queue.push(def.in);
7875
+ if (def.out) queue.push(def.out);
7876
+ }
7877
+ }
7878
+ return out;
7879
+ }
7880
+ /** True when any layer of this field declares `.meta({ secret: true })`. */
7881
+ function isZodSecret(schema) {
7882
+ return layersOf(schema).some(declaredHere);
7883
+ }
7884
+ /**
7885
+ * Every declared secret path in a config schema: dotted keys, `[]` for "each
7886
+ * element of this array" (`connection.password`, `rows[].token`). A declared
7887
+ * object or array is reported as one path and not descended into — everything
7888
+ * under it is secret.
7889
+ */
7890
+ function collectZodSecretPaths(schema, prefix = "") {
7891
+ if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
7892
+ const paths = [];
7893
+ for (const layer of layersOf(schema)) {
7894
+ const def = defOf(layer);
7895
+ if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
7896
+ else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
7897
+ else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
7898
+ else if (def.type === "intersection") {
7899
+ if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
7900
+ if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
7901
+ }
7902
+ }
7903
+ return [...new Set(paths)];
7904
+ }
7905
+ /**
7906
+ * Record kinds (D881): the declaration an addon makes of one kind of fact, the
7907
+ * Zod row schema that describes it, and the helper that turns the two into the
7908
+ * JSON the engine stores.
7909
+ *
7910
+ * A kind is declared ONCE, next to the code that owns it, and the engine's
7911
+ * reconciler creates its indexes - nothing creates DDL at runtime from a write.
7912
+ * Everything the generic machinery needs (secret fields, the entry descriptor)
7913
+ * is DERIVED from the Zod schema: `.meta({ secret: true })` marks a credential,
7914
+ * never a field name (D879).
7915
+ */
7916
+ /** A JSON field path: `trackId`, `auth.accessToken`. Charset-restricted: it is rendered into DDL. */
7917
+ var RecordFieldPathSchema = string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}(\.[A-Za-z_][A-Za-z0-9_]{0,63}){0,3}$/);
7918
+ var RecordIndexSchema = object({
7919
+ name: string().regex(/^[a-z][a-z0-9_]{0,31}$/),
7920
+ fields: array(RecordFieldPathSchema).min(1).max(4),
7921
+ unique: boolean().optional()
7922
+ }).strict();
7923
+ var RecordRetentionSchema = union([
7924
+ object({
7925
+ ttlMs: number().int().positive(),
7926
+ from: _enum(["at", "write"])
7927
+ }).strict(),
7928
+ object({
7929
+ maxRows: number().int().positive().optional(),
7930
+ maxRowsPerDevice: number().int().positive().optional()
7931
+ }).strict().refine((r) => r.maxRows !== void 0 || r.maxRowsPerDevice !== void 0, { message: "name maxRows, maxRowsPerDevice or both" }),
7932
+ object({
7933
+ owner: string().min(1),
7934
+ reason: string().min(1)
7935
+ }).strict(),
7936
+ object({ none: string().min(1) }).strict()
7937
+ ]);
7938
+ /** The descriptor a generic entries UI reads (D881 "generic entries"). */
7939
+ var EntryDescriptorSchema = object({
7940
+ title: string().min(1),
7941
+ /** The record body field shown as the entry's name. */
7942
+ nameField: string().min(1),
7943
+ /** JSON Schema of the entry body; `secret: true` marks a password field. */
7944
+ fields: record(string(), unknown())
7945
+ }).strict();
7946
+ /** What crosses the wire and sits in the engine's catalog: JSON only, no Zod. */
7947
+ var RecordKindDeclarationSchema = object({
7948
+ database: _enum(RECORD_DATABASES).default("settings"),
7949
+ class: _enum(RECORD_CLASSES),
7950
+ writeTrigger: string().min(1),
7951
+ device: _enum([
7952
+ "required",
7953
+ "optional",
7954
+ "none"
7955
+ ]).default("none"),
7956
+ at: _enum(["required", "none"]).default("none"),
7957
+ indexes: array(RecordIndexSchema).max(8).default([]),
7958
+ retention: RecordRetentionSchema,
7959
+ deviceRemoval: _enum(["delete", "none"]).default("none"),
7960
+ maxDataBytes: number().int().positive().max(RECORD_MAX_DATA_BYTES).default(RECORD_DEFAULT_MAX_DATA_BYTES),
7961
+ /** Derived from the Zod schema; never written by hand. */
7962
+ secretPaths: array(string().min(1)).default([]),
7963
+ /** The id key of each array that carries a secret (`{ headers: 'name' }`). */
7964
+ secretArrayKeys: record(string(), string()).default({}),
7965
+ /** The legacy table this kind adopts from; the boot guard reads it (D853: no migration code). */
7966
+ adoptsFrom: string().min(1).optional(),
7967
+ /** Kinds that must agree through `transact` sit in one file under one family name. */
7968
+ transactFamily: string().min(1).optional(),
7969
+ entry: EntryDescriptorSchema.optional()
7970
+ }).strict();
7971
+ /** The kind names: lower-case, dash separated, short enough for an index name. */
7972
+ var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
7713
7973
  /** Shared Zod schemas used across streaming capabilities. */
7714
7974
  var CamProfileSchema = _enum([
7715
7975
  "high",
@@ -8174,69 +8434,6 @@ method(object({ deviceId: number() }), array(StreamSourceEntrySchema)), method(o
8174
8434
  kind: "mutation",
8175
8435
  auth: "admin"
8176
8436
  }), method(object({ deviceId: number() }), unknown().nullable()), method(object({ deviceId: number() }), RawStateResultSchema.nullable(), { auth: "protected" });
8177
- function defOf(schema) {
8178
- return schema._zod?.def ?? {};
8179
- }
8180
- function declaredHere(schema) {
8181
- return globalRegistry.get(schema)?.secret === true;
8182
- }
8183
- /** Wrapper kinds whose `innerType` is the field itself. */
8184
- var WRAPPERS = new Set([
8185
- "optional",
8186
- "nullable",
8187
- "default",
8188
- "prefault",
8189
- "catch",
8190
- "readonly",
8191
- "nonoptional"
8192
- ]);
8193
- /**
8194
- * The schema layers that make up ONE field: the schema itself and every
8195
- * wrapper below it (optional / default / nullable / readonly / catch), plus
8196
- * both sides of a pipe (`z.preprocess` puts the real schema on `out`,
8197
- * `.transform` on `in`).
8198
- */
8199
- function layersOf(schema) {
8200
- const out = [];
8201
- const queue = [schema];
8202
- while (queue.length > 0) {
8203
- const s = queue.shift();
8204
- if (!s || out.includes(s)) continue;
8205
- out.push(s);
8206
- const def = defOf(s);
8207
- if (def.type !== void 0 && WRAPPERS.has(def.type) && def.innerType) queue.push(def.innerType);
8208
- if (def.type === "pipe") {
8209
- if (def.in) queue.push(def.in);
8210
- if (def.out) queue.push(def.out);
8211
- }
8212
- }
8213
- return out;
8214
- }
8215
- /** True when any layer of this field declares `.meta({ secret: true })`. */
8216
- function isZodSecret(schema) {
8217
- return layersOf(schema).some(declaredHere);
8218
- }
8219
- /**
8220
- * Every declared secret path in a config schema: dotted keys, `[]` for "each
8221
- * element of this array" (`connection.password`, `rows[].token`). A declared
8222
- * object or array is reported as one path and not descended into — everything
8223
- * under it is secret.
8224
- */
8225
- function collectZodSecretPaths(schema, prefix = "") {
8226
- if (isZodSecret(schema)) return prefix.length > 0 ? [prefix] : [];
8227
- const paths = [];
8228
- for (const layer of layersOf(schema)) {
8229
- const def = defOf(layer);
8230
- if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
8231
- else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
8232
- else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
8233
- else if (def.type === "intersection") {
8234
- if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
8235
- if (def.right) paths.push(...collectZodSecretPaths(def.right, prefix));
8236
- }
8237
- }
8238
- return [...new Set(paths)];
8239
- }
8240
8437
  object({
8241
8438
  code: string().regex(/^[a-z][a-z0-9-]*(\.[a-z0-9-]+)+$/),
8242
8439
  params: record(string(), union([string(), number()])).optional()
@@ -8256,7 +8453,7 @@ function userError(code, params, englishFallback) {
8256
8453
  return new CamstackError(code, params, englishFallback);
8257
8454
  }
8258
8455
  //#endregion
8259
- //#region ../types/dist/err-msg-DLerKiVO.mjs
8456
+ //#region ../types/dist/err-msg-BXKzRBwg.mjs
8260
8457
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
8261
8458
  DeviceType["Camera"] = "camera";
8262
8459
  DeviceType["Hub"] = "hub";
@@ -8348,6 +8545,10 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
8348
8545
  * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
8349
8546
  * integrations sharing the same food/desiccant/hopper surface. */
8350
8547
  DeviceType["PetFeeder"] = "pet-feeder";
8548
+ /** Built-in dishwasher — program lifecycle, door / remote-start gates,
8549
+ * salt and rinse-aid levels. Installed with the `dishwasher` cap.
8550
+ * Sources: native Bosch Home Connect, reusable by similar integrations. */
8551
+ DeviceType["Dishwasher"] = "dishwasher";
8351
8552
  return DeviceType;
8352
8553
  }({});
8353
8554
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -8525,6 +8726,9 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
8525
8726
  /** Virtual HA toggle (input_boolean.*) — distinguishable from a
8526
8727
  * real Switch device for UI rendering / export adapters. */
8527
8728
  DeviceRole["BinaryHelper"] = "binary-helper";
8729
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
8730
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
8731
+ DeviceRole["Outlet"] = "outlet";
8528
8732
  /** Generic motion / occupancy / moving event source. Distinct from
8529
8733
  * the camera accessory PirSensor role: that one is a camera child;
8530
8734
  * this is a standalone HA / 3rd-party motion sensor. */
@@ -8592,7 +8796,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
8592
8796
  return DeviceRole;
8593
8797
  }({});
8594
8798
  //#endregion
8595
- //#region ../types/dist/composition-EQ9Ok0Ee.mjs
8799
+ //#region ../types/dist/composition-CB6PMT9p.mjs
8596
8800
  /**
8597
8801
  * The ffmpeg build every CamStack node downloads, per platform and arch.
8598
8802
  *
@@ -11816,11 +12020,10 @@ var ClipModelMetaSchema = object({
11816
12020
  /**
11817
12021
  * The `vector-store` index this model's vectors live in. ONE feature space
11818
12022
  * per index: two models share an index only when their vectors are ranked
11819
- * against each other, which for CLIP means never across families. MobileCLIP
11820
- * S1 and S2 share `object-clip` for history (separated by the row's
11821
- * `modelId`); any other model gets its own.
12023
+ * against each other, which for CLIP means never across families. Every
12024
+ * model gets its own.
11822
12025
  */
11823
- vectorIndex: string().regex(/^object-clip(-[a-z0-9][a-z0-9-]*)?$/),
12026
+ vectorIndex: string().regex(/^object-clip-[a-z0-9][a-z0-9-]*$/),
11824
12027
  /**
11825
12028
  * The text encoder's tokenizer, a declared SIBLING file of its onnx. The name
11826
12029
  * must be unique per tokenizer: every model's siblings land in ONE flat
@@ -12999,8 +13202,13 @@ var AlertSeveritySchema = _enum([
12999
13202
  "info",
13000
13203
  "success",
13001
13204
  "warning",
13002
- "error"
13205
+ "error",
13206
+ "critical"
13003
13207
  ]);
13208
+ var AlertActionSchema = object({
13209
+ label: string(),
13210
+ href: string().optional()
13211
+ });
13004
13212
  var AlertStatusSchema = _enum([
13005
13213
  "active",
13006
13214
  "in-progress",
@@ -13018,6 +13226,7 @@ var AlertSchema = object({
13018
13226
  severity: AlertSeveritySchema,
13019
13227
  title: string(),
13020
13228
  message: string(),
13229
+ action: AlertActionSchema.optional(),
13021
13230
  status: AlertStatusSchema,
13022
13231
  progress: number().optional(),
13023
13232
  read: boolean(),
@@ -15553,6 +15762,41 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
15553
15762
  keyField: string()
15554
15763
  }).optional()
15555
15764
  })) });
15765
+ /**
15766
+ * The report of a one-shot "move this owner's plaintext secrets into the
15767
+ * keychain" run (D878). COUNTS only - never a value, never a ref.
15768
+ *
15769
+ * The owner runs it on its own rows, through its own write path; an operator
15770
+ * script (`scripts/hub/migrate-raw-secrets.ts`) calls the owner's cap method.
15771
+ * Dry run (`apply: false`) reports what WOULD move and changes nothing.
15772
+ */
15773
+ var RawSecretMigrationInputSchema = object({
15774
+ /** `false` (the default of the script) = report only. */
15775
+ apply: boolean() });
15776
+ var RawSecretMigrationReportSchema = object({
15777
+ apply: boolean(),
15778
+ /** Rows that can carry a secret (a profile, an enrolment). */
15779
+ rows: number().int(),
15780
+ /** Rows whose secret was plaintext when the run started. */
15781
+ raw: number().int(),
15782
+ /** Rows that already held a keychain marker when the run started. */
15783
+ markers: number().int(),
15784
+ /** Plaintext rows moved by THIS run (always 0 on a dry run). */
15785
+ migrated: number().int(),
15786
+ /** Plaintext rows left in place because the move or its read-back failed. */
15787
+ failed: number().int(),
15788
+ /** After the run: rows still holding plaintext. */
15789
+ remainingRaw: number().int(),
15790
+ /** After the run: markers the keychain could not resolve right now. */
15791
+ unresolvedMarkers: number().int(),
15792
+ /**
15793
+ * A second, redundant copy of the secrets' rows that the run found (and, on
15794
+ * `apply`, deleted after verifying the first). Owners with a single copy omit it.
15795
+ */
15796
+ redundantCopies: number().int().optional(),
15797
+ /** True when nothing is raw and every marker resolves. */
15798
+ verified: boolean()
15799
+ });
15556
15800
  /** Where a block runs. The operator chooses — a block driving a device on an
15557
15801
  * agent is the reason placement is not fixed to the hub. */
15558
15802
  var CoreBlockPlacementSchema = union([literal("hub"), string().min(1)]);
@@ -15779,6 +16023,9 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
15779
16023
  }), SettingsUpdateResultSchema, {
15780
16024
  kind: "mutation",
15781
16025
  auth: "admin"
16026
+ }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
16027
+ kind: "mutation",
16028
+ auth: "admin"
15782
16029
  }), method(object({}), object({ libs: array(object({
15783
16030
  filePath: string(),
15784
16031
  content: string()
@@ -16042,6 +16289,347 @@ id: union([string(), number()]) }), {
16042
16289
  auth: "admin"
16043
16290
  });
16044
16291
  /**
16292
+ * records - an addon's own facts and documents (D881): ledgers, audit rows,
16293
+ * config documents, mirrors. The third storage API next to `ctx.settings`
16294
+ * (operator intent, D870) and device-history (numbers over time, D850).
16295
+ *
16296
+ * Who the OWNER is: `callerAddonId`, stamped by the transport on a call that
16297
+ * crossed a process and stated by a hub in-process caller (`ctx.records` does
16298
+ * that with the addon id closed over). It is deliberately NOT in any input
16299
+ * schema, so a session can never supply it, and a call with no stamp is refused
16300
+ * `records/no-caller`. A kind is readable and writable only by its owner; other
16301
+ * addons go through the owner's capability.
16302
+ *
16303
+ * Every read is three-way: a row, `null`, or the call THROWS. A failed read is
16304
+ * never `[]`. Secret fields are never on the wire: a read shows the
16305
+ * redaction token, `secretRefs` hands the owner's `ctx.secrets` the refs it
16306
+ * resolves itself, and a write may carry only a keychain marker bound to the
16307
+ * row's own ref.
16308
+ *
16309
+ * Internal: addons reach it through `ctx.records`, never directly.
16310
+ */
16311
+ var RecordIdSchema = string().min(1).max(256);
16312
+ /** A JSON object body. Size is the engine's check (`maxDataBytes`), not a schema's. */
16313
+ var RecordBodySchema = record(string(), unknown());
16314
+ var RecordInputSchema = object({
16315
+ id: RecordIdSchema,
16316
+ /** The device this row belongs to (D865 removal, per-device reads). */
16317
+ deviceId: number().int().optional(),
16318
+ /** The domain clock, ms: observed / created / opened. */
16319
+ at: number().int().optional(),
16320
+ /** Producer-set expiry, ms. Refused on a kind whose retention is an engine TTL. */
16321
+ expiresAt: number().int().optional(),
16322
+ data: RecordBodySchema
16323
+ });
16324
+ var RecordViewSchema = object({
16325
+ id: string(),
16326
+ deviceId: number().int().nullable(),
16327
+ at: number().nullable(),
16328
+ expiresAt: number().nullable(),
16329
+ rev: number().int(),
16330
+ updatedAt: number(),
16331
+ data: RecordBodySchema
16332
+ });
16333
+ /** Declared index fields only. Anything else does not compile, by design. */
16334
+ var RecordWhereSchema = record(RecordFieldPathSchema, union([
16335
+ string(),
16336
+ number(),
16337
+ boolean(),
16338
+ object({ in: array(union([
16339
+ string(),
16340
+ number(),
16341
+ boolean()
16342
+ ])).min(1).max(RECORD_BATCH_MAX) }),
16343
+ object({
16344
+ gt: union([string(), number()]).optional(),
16345
+ gte: union([string(), number()]).optional(),
16346
+ lt: union([string(), number()]).optional(),
16347
+ lte: union([string(), number()]).optional()
16348
+ }).refine((r) => Object.keys(r).length > 0, { message: "name at least one bound" })
16349
+ ]));
16350
+ var KindShape = { kind: RecordKindNameSchema };
16351
+ var TimeWindowShape = {
16352
+ deviceId: number().int().optional(),
16353
+ atFrom: number().optional(),
16354
+ atTo: number().optional()
16355
+ };
16356
+ var RecordQueryInputSchema = object({
16357
+ ...KindShape,
16358
+ ...TimeWindowShape,
16359
+ where: RecordWhereSchema.optional(),
16360
+ /** Without `where`: `at` (default for a kind with a clock) or `id`. With `where` the order is the index's. */
16361
+ order: _enum(["at", "id"]).optional(),
16362
+ desc: boolean().optional(),
16363
+ limit: number().int().min(1).max(RECORD_QUERY_MAX).default(100),
16364
+ /** Opaque keyset cursor from a previous answer. There is no offset. */
16365
+ cursor: string().min(1).optional()
16366
+ });
16367
+ var RecordQueryResultSchema = object({
16368
+ records: array(RecordViewSchema),
16369
+ nextCursor: string().nullable()
16370
+ });
16371
+ var RecordKindStateSchema = _enum([
16372
+ "created",
16373
+ "unchanged",
16374
+ "updated"
16375
+ ]);
16376
+ var DeclareKindResultSchema = object({
16377
+ kindId: number().int(),
16378
+ state: RecordKindStateSchema,
16379
+ createdIndexes: array(string()),
16380
+ /** The boot guard: the kind is empty while its legacy table still holds rows. */
16381
+ adoptionPending: object({
16382
+ legacyTable: string(),
16383
+ legacyRows: number().int()
16384
+ }).nullable()
16385
+ });
16386
+ /** Keychain refs a write released (the old marker is gone from the row): the owner deletes the values. */
16387
+ var SecretRefsList = array(string());
16388
+ var RecordSecretRefsSchema = record(string(), record(string(), string()));
16389
+ method(object({ kinds: record(RecordKindNameSchema, RecordKindDeclarationSchema) }), object({ kinds: record(string(), DeclareKindResultSchema) }), {
16390
+ kind: "mutation",
16391
+ auth: "admin"
16392
+ }), method(object({
16393
+ ...KindShape,
16394
+ id: RecordIdSchema
16395
+ }), RecordViewSchema.nullable(), { auth: "admin" }), method(object({
16396
+ ...KindShape,
16397
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
16398
+ }), array(RecordViewSchema), { auth: "admin" }), method(object({
16399
+ ...KindShape,
16400
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
16401
+ }), RecordSecretRefsSchema, { auth: "admin" }), method(object({
16402
+ ...KindShape,
16403
+ record: RecordInputSchema,
16404
+ /** Compare-and-set. `0` means "the row must not exist". */
16405
+ ifRev: number().int().min(0).optional()
16406
+ }), object({
16407
+ rev: number().int(),
16408
+ releasedRefs: SecretRefsList
16409
+ }), {
16410
+ kind: "mutation",
16411
+ auth: "admin"
16412
+ }), method(object({
16413
+ ...KindShape,
16414
+ records: array(RecordInputSchema).min(1).max(RECORD_BATCH_MAX)
16415
+ }), object({
16416
+ count: number().int(),
16417
+ releasedRefs: SecretRefsList
16418
+ }), {
16419
+ kind: "mutation",
16420
+ auth: "admin"
16421
+ }), method(object({
16422
+ ...KindShape,
16423
+ id: RecordIdSchema,
16424
+ data: RecordBodySchema,
16425
+ at: number().int().nullable().optional(),
16426
+ expiresAt: number().int().nullable().optional(),
16427
+ ifRev: number().int().min(1).optional()
16428
+ }), object({
16429
+ rev: number().int(),
16430
+ releasedRefs: SecretRefsList
16431
+ }), {
16432
+ kind: "mutation",
16433
+ auth: "admin"
16434
+ }), method(object({
16435
+ ...KindShape,
16436
+ id: RecordIdSchema
16437
+ }), object({
16438
+ deleted: boolean(),
16439
+ secretRefs: SecretRefsList
16440
+ }), {
16441
+ kind: "mutation",
16442
+ auth: "admin"
16443
+ }), method(object({
16444
+ ...KindShape,
16445
+ ids: array(RecordIdSchema).min(1).max(RECORD_BATCH_MAX)
16446
+ }), object({
16447
+ deleted: number().int(),
16448
+ secretRefs: SecretRefsList
16449
+ }), {
16450
+ kind: "mutation",
16451
+ auth: "admin"
16452
+ }), method(RecordQueryInputSchema, RecordQueryResultSchema, { auth: "admin" }), method(object({
16453
+ ...KindShape,
16454
+ ...TimeWindowShape,
16455
+ where: RecordWhereSchema.optional()
16456
+ }), object({ count: number().int() }), { auth: "admin" }), method(object({
16457
+ ...KindShape,
16458
+ deviceId: number().int().optional(),
16459
+ from: number(),
16460
+ to: number(),
16461
+ bucketMs: number().int().positive()
16462
+ }), object({ buckets: array(object({
16463
+ bucketStart: number(),
16464
+ count: number().int()
16465
+ })) }), { auth: "admin" }), method(object({
16466
+ ...KindShape,
16467
+ op: _enum([
16468
+ "sum",
16469
+ "min",
16470
+ "max",
16471
+ "count"
16472
+ ]),
16473
+ field: RecordFieldPathSchema.optional(),
16474
+ groupBy: RecordFieldPathSchema.optional(),
16475
+ where: RecordWhereSchema.optional()
16476
+ }), object({ rows: array(object({
16477
+ group: union([
16478
+ string(),
16479
+ number(),
16480
+ _null()
16481
+ ]),
16482
+ value: number().nullable()
16483
+ })) }), { auth: "admin" }), method(object({
16484
+ ...KindShape,
16485
+ deviceId: number().int().optional(),
16486
+ atBefore: number().optional(),
16487
+ where: RecordWhereSchema.optional()
16488
+ }), object({
16489
+ deleted: number().int(),
16490
+ secretRefs: SecretRefsList
16491
+ }), {
16492
+ kind: "mutation",
16493
+ auth: "admin"
16494
+ }), method(object({
16495
+ deviceId: number().int(),
16496
+ kinds: array(RecordKindNameSchema).optional()
16497
+ }), object({
16498
+ deleted: record(string(), number().int()),
16499
+ secretRefs: SecretRefsList
16500
+ }), {
16501
+ kind: "mutation",
16502
+ auth: "admin"
16503
+ }), method(object(KindShape), object({ id: number().int() }), {
16504
+ kind: "mutation",
16505
+ auth: "admin"
16506
+ });
16507
+ /** One table of the settings database. */
16508
+ var StorageTableSchema = object({
16509
+ /** The table's full name, e.g. `recorder:addon-settings` or `addon-device-settings`. */
16510
+ name: string(),
16511
+ /** The owner prefix of a scoped name (`recorder` for `recorder:addon-settings`), else null. */
16512
+ owner: string().nullable(),
16513
+ rows: number().int().nonnegative(),
16514
+ /** Bytes the table's pages occupy (`dbstat`); null when the engine cannot measure. */
16515
+ bytes: number().int().nonnegative().nullable(),
16516
+ /** Declared by an addon since this engine process started (a running addon claims it). */
16517
+ declaredThisBoot: boolean()
16518
+ });
16519
+ /** One declared record kind (D881) as System -> Data shows it. */
16520
+ var StorageKindSchema = object({
16521
+ owner: string(),
16522
+ kind: string(),
16523
+ database: _enum(["settings", "events"]),
16524
+ class: string(),
16525
+ state: _enum([
16526
+ "declared",
16527
+ "orphaned",
16528
+ "dropped"
16529
+ ]),
16530
+ /** `null` when the caller asked for no counts (`counts: false`). */
16531
+ rows: number().int().nonnegative().nullable(),
16532
+ /** Rows x the average size of a sample; null for an empty kind. */
16533
+ bytesEstimate: number().int().nonnegative().nullable(),
16534
+ writeTrigger: string(),
16535
+ retention: RecordRetentionSchema,
16536
+ /** `true` when the kind is an entry kind (the generic entries UI lists it). */
16537
+ entry: boolean(),
16538
+ /** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
16539
+ declaration: RecordKindDeclarationSchema
16540
+ });
16541
+ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
16542
+ collection: string().min(1),
16543
+ expectRows: number().int().nonnegative()
16544
+ }), object({
16545
+ dropped: literal(true),
16546
+ rows: number().int().nonnegative()
16547
+ }), {
16548
+ kind: "mutation",
16549
+ auth: "admin"
16550
+ }), method(object({
16551
+ owner: string().min(1).optional(),
16552
+ /** `false` skips the per-kind row count (a catalog read only). Default true. */
16553
+ counts: boolean().optional()
16554
+ }), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
16555
+ owner: string().min(1),
16556
+ kind: RecordKindNameSchema
16557
+ }), RecordQueryResultSchema, { auth: "admin" }), method(object({
16558
+ owner: string().min(1),
16559
+ kind: RecordKindNameSchema,
16560
+ records: array(RecordInputSchema).max(RECORD_BATCH_MAX),
16561
+ /** The LAST batch: the copy is complete, the boot guard stops asking. */
16562
+ complete: boolean().optional()
16563
+ }).refine((input) => input.records.length > 0 || input.complete === true, { message: "records must be non-empty unless complete is true" }), object({ adopted: number().int() }), {
16564
+ kind: "mutation",
16565
+ auth: "admin"
16566
+ }), method(object({
16567
+ collection: string().min(1),
16568
+ cursor: string().optional(),
16569
+ limit: number().int().min(1).max(1e3).optional()
16570
+ }), object({
16571
+ rows: array(object({
16572
+ id: string(),
16573
+ data: record(string(), unknown())
16574
+ })),
16575
+ nextCursor: string().nullable()
16576
+ }), { auth: "admin" }), method(object({ collection: string().min(1) }), object({ adopted: boolean() }), { auth: "admin" });
16577
+ /**
16578
+ * record-entries - the ONE admin surface for every list of user-created entries
16579
+ * (D881): notification targets, brokers, LLM profiles, anything an addon
16580
+ * declares as an ENTRY kind (`entry: { title }` in `defineRecordKinds`).
16581
+ *
16582
+ * The UI is generic. `listEntryKinds` hands it, per kind, the JSON Schema of
16583
+ * the entry body with `secret: true` on every credential field, so a password
16584
+ * field is derived from the schema and never from a name. `putEntry` takes the
16585
+ * redaction token for an untouched secret (the stored value is kept), a string
16586
+ * for a new one, and the clear instruction to remove one. A read never carries
16587
+ * a secret value, and a locked keychain refuses the write with
16588
+ * `records/secret-unavailable` instead of storing plaintext.
16589
+ *
16590
+ * Hub-only, served in-process (it acts for each entry's owner on the keychain).
16591
+ */
16592
+ var EntryKindInfoSchema = object({
16593
+ owner: string(),
16594
+ kind: RecordKindNameSchema,
16595
+ title: string(),
16596
+ nameField: string(),
16597
+ /** JSON Schema of the body; `secret: true` marks a password field. */
16598
+ fields: record(string(), unknown()),
16599
+ secretPaths: array(string()),
16600
+ rows: number().int().nonnegative()
16601
+ });
16602
+ var EntryViewSchema = object({
16603
+ id: RecordIdSchema,
16604
+ name: string(),
16605
+ rev: number().int(),
16606
+ updatedAt: number(),
16607
+ /** Secret fields hold the redaction token. */
16608
+ data: RecordBodySchema
16609
+ });
16610
+ var EntryAddress = {
16611
+ owner: string().min(1),
16612
+ kind: RecordKindNameSchema
16613
+ };
16614
+ method(object({}), object({ kinds: array(EntryKindInfoSchema) }), { auth: "admin" }), method(object({ ...EntryAddress }), object({ entries: array(EntryViewSchema) }), { auth: "admin" }), method(object({
16615
+ ...EntryAddress,
16616
+ id: RecordIdSchema.optional(),
16617
+ data: RecordBodySchema,
16618
+ ifRev: number().int().min(1).optional()
16619
+ }), object({
16620
+ id: string(),
16621
+ rev: number().int()
16622
+ }), {
16623
+ kind: "mutation",
16624
+ auth: "admin"
16625
+ }), method(object({
16626
+ ...EntryAddress,
16627
+ id: RecordIdSchema
16628
+ }), object({ deleted: boolean() }), {
16629
+ kind: "mutation",
16630
+ auth: "admin"
16631
+ });
16632
+ /**
16045
16633
  * `keychain` — the hub's encrypted store of secret values (D787).
16046
16634
  *
16047
16635
  * Settings blobs never hold a secret: they hold a marker
@@ -16582,6 +17170,16 @@ method(object({}), KeychainStatusSchema, { auth: "admin" }), method(object({}),
16582
17170
  kind: "mutation",
16583
17171
  auth: "admin",
16584
17172
  caller: "required"
17173
+ }), method(object({
17174
+ ref: string().min(1),
17175
+ value: string().min(1)
17176
+ }), object({
17177
+ ref: string(),
17178
+ version: number().int()
17179
+ }), {
17180
+ kind: "mutation",
17181
+ auth: "admin",
17182
+ caller: "required"
16585
17183
  }), method(object({ refs: array(string().min(1)).max(1e3) }), object({
16586
17184
  deleted: array(string()).readonly(),
16587
17185
  kept: array(object({
@@ -39356,6 +39954,9 @@ method(LlmGenerateBaseInputSchema, LlmGenerateResultSchema, {
39356
39954
  }), _void(), { kind: "mutation" }), method(object({}), array(LlmProfileKindDescriptorSchema)), method(object({}), array(LlmProfileSchema)), method(object({ profile: LlmProfileSchema }), LlmProfileSchema, {
39357
39955
  kind: "mutation",
39358
39956
  auth: "admin"
39957
+ }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
39958
+ kind: "mutation",
39959
+ auth: "admin"
39359
39960
  }), method(ProfileRefInputSchema, _void(), {
39360
39961
  kind: "mutation",
39361
39962
  auth: "admin"
@@ -49865,6 +50466,41 @@ var ServerRollbackInfoSchema = object({
49865
50466
  atMs: number(),
49866
50467
  reason: string()
49867
50468
  });
50469
+ /**
50470
+ * The runner boot check (D877): the runners this node PLANNED (hub: one per
50471
+ * `resolveRunnerId` over its forked addons; agent: the runners its process
50472
+ * service holds, because only the hub plans) against the states the process
50473
+ * service reports. Kept in memory, recomputed by the same one-shot check after
50474
+ * every boot, served on `getServerPackageStatus`.
50475
+ *
50476
+ * `failed` is the D6 breaker's terminal state, `refused` a deterministic Init
50477
+ * refusal (an addon that said "I will not start, and here is why"), `missing` a
50478
+ * planned runner the process service has no entry for.
50479
+ */
50480
+ var RunnerRefusalSchema = object({
50481
+ runnerId: string(),
50482
+ addonIds: array(string()),
50483
+ reason: string()
50484
+ });
50485
+ var RunnerBootCheckSchema = object({
50486
+ checkedAtMs: number(),
50487
+ /** Runners planned for this node. */
50488
+ expected: number(),
50489
+ /** Planned runners whose process is `running`. */
50490
+ running: number(),
50491
+ failed: array(string()),
50492
+ refused: array(RunnerRefusalSchema),
50493
+ missing: array(string()),
50494
+ /**
50495
+ * Runners still on their way: `starting`, or `crashed` waiting on a backoff
50496
+ * respawn. The check is FINAL only when this is empty; until then nothing is
50497
+ * pushed and the verdict is re-evaluated on the next runner state change.
50498
+ * Optional for version skew (absent = settled).
50499
+ */
50500
+ pending: array(string()).optional(),
50501
+ /** True when the plan is the hub's (`missing` is then meaningful); false on an agent. */
50502
+ planKnown: boolean()
50503
+ });
49868
50504
  var ServerPackageStatusSchema = object({
49869
50505
  /** Root package name (`@camstack/server` on the hub). */
49870
50506
  packageName: string(),
@@ -49927,7 +50563,14 @@ var ServerPackageStatusSchema = object({
49927
50563
  * for version skew: an older provider's payload simply omits it, and an
49928
50564
  * absent wrapper is unknown — never `native`.
49929
50565
  */
49930
- wrapper: WrapperIdentitySchema.optional()
50566
+ wrapper: WrapperIdentitySchema.optional(),
50567
+ /**
50568
+ * What this node PLANNED to run against what its process service reports
50569
+ * (D877). Absent until the one-shot boot check has run (~60 s after boot) and
50570
+ * on a provider that predates it. Absent is "not checked yet", never "all
50571
+ * well": a consumer that needs the answer treats it as pending.
50572
+ */
50573
+ runners: RunnerBootCheckSchema.optional()
49931
50574
  });
49932
50575
  var ServerUpdateCheckResultSchema = object({
49933
50576
  packageName: string(),
@@ -49978,6 +50621,13 @@ var ServerUpdateOutcomeSchema = _enum([
49978
50621
  "refused",
49979
50622
  "failed"
49980
50623
  ]);
50624
+ /** One addon the pre-flight found the target version would refuse (D877). */
50625
+ var UpdateBlockerSchema = object({
50626
+ addonId: string(),
50627
+ reason: string(),
50628
+ /** What the operator does about it. */
50629
+ remedy: string()
50630
+ });
49981
50631
  var ServerUpdateActionResultSchema = object({
49982
50632
  accepted: boolean(),
49983
50633
  targetVersion: string().nullable(),
@@ -49995,14 +50645,27 @@ var ServerUpdateActionResultSchema = object({
49995
50645
  * The failure itself, verbatim enough to act on (the npm output for a
49996
50646
  * staging install). Non-null only for `failed`.
49997
50647
  */
49998
- error: string().nullable().optional()
50648
+ error: string().nullable().optional(),
50649
+ /**
50650
+ * The addons the target version would refuse to start on this node's live
50651
+ * store (D877), present when the pre-flight refused the update
50652
+ * (`reason: 'addon-would-refuse'`). Optional for version skew.
50653
+ */
50654
+ blockers: array(UpdateBlockerSchema).optional()
49999
50655
  });
50000
50656
  method(_void(), ServerPackageStatusSchema, { auth: "admin" }), method(_void(), ServerUpdateCheckResultSchema, {
50001
50657
  kind: "mutation",
50002
50658
  auth: "admin"
50003
- }), method(object({
50004
- /** Explicit target version; omitted = latest from the registry. */
50005
- version: string().optional() }), ServerUpdateActionResultSchema, {
50659
+ }), method(object({
50660
+ /** Explicit target version; omitted = latest from the registry. */
50661
+ version: string().optional(),
50662
+ /**
50663
+ * Apply even though the pre-flight (D877) found addons the target would
50664
+ * refuse to start. Off by default: those addons stay down until the
50665
+ * remedy is run, which is the incident this gate exists to prevent.
50666
+ */
50667
+ acknowledgeRefusals: boolean().optional()
50668
+ }), ServerUpdateActionResultSchema, {
50006
50669
  kind: "mutation",
50007
50670
  auth: "admin",
50008
50671
  timeoutMs: 16 * 6e4
@@ -52961,7 +53624,9 @@ var ManifestShapeSchema = object({
52961
53624
  var AddonProcessSchema = object({
52962
53625
  pid: number().optional(),
52963
53626
  mode: string(),
52964
- state: string()
53627
+ state: string(),
53628
+ /** Why the runner is `refused` (an addon's own words), when it is (D877). */
53629
+ reason: string().optional()
52965
53630
  });
52966
53631
  /**
52967
53632
  * Per-addon health snapshot from the kernel's AddonHealthMonitor.
@@ -53729,7 +54394,8 @@ var batteryCapability = {
53729
54394
  DeviceType.Button,
53730
54395
  DeviceType.Switch,
53731
54396
  DeviceType.PetFeeder,
53732
- DeviceType.Container
54397
+ DeviceType.Container,
54398
+ DeviceType.Thermostat
53733
54399
  ],
53734
54400
  methods: {
53735
54401
  /**
@@ -54350,6 +55016,12 @@ var ClimateControlStatusSchema = object({
54350
55016
  /** Horizontal louver swing on/off. Null when the device has no
54351
55017
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
54352
55018
  swingHorizontal: boolean().nullable(),
55019
+ /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
55020
+ minTemp: number().nullable().optional(),
55021
+ /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
55022
+ maxTemp: number().nullable().optional(),
55023
+ /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
55024
+ tempStep: number().positive().nullable().optional(),
54353
55025
  /** Ms epoch when the slice was last updated (push or command). */
54354
55026
  lastFetchedAt: number()
54355
55027
  });
@@ -55196,6 +55868,125 @@ onStatusChanged: { data: object({
55196
55868
  * whether persisting is worth a SQLite commit. */
55197
55869
  volatileStateFields: ["lastChangedAt"]
55198
55870
  };
55871
+ /**
55872
+ * Smart-dishwasher cap — program lifecycle, door / remote-start gates,
55873
+ * consumable levels (salt, rinse aid) and common program options.
55874
+ *
55875
+ * Sources: native Bosch Home Connect (`dishwasher` device type). Reusable
55876
+ * by any integration that exposes the same program / option surface.
55877
+ *
55878
+ * `kind: 'poll'` — cloud REST APIs typically have no push channel; the
55879
+ * provider refreshes the slice on an interval and eagerly after commands.
55880
+ */
55881
+ var DishwasherStateSchema = _enum([
55882
+ "idle",
55883
+ "running",
55884
+ "paused",
55885
+ "finished",
55886
+ "error",
55887
+ "unavailable"
55888
+ ]);
55889
+ var DishwasherConsumableLevelSchema = _enum([
55890
+ "ok",
55891
+ "low",
55892
+ "empty"
55893
+ ]);
55894
+ var DishwasherStatusSchema = object({
55895
+ state: DishwasherStateSchema,
55896
+ /** Active program id/name, or null when none selected / unknown. */
55897
+ program: string().nullable(),
55898
+ /** Program tokens the hardware accepts — drives the UI selector. */
55899
+ availablePrograms: array(string()),
55900
+ /** Minutes remaining on the active cycle, or null when not running. */
55901
+ remainingMinutes: number().int().nonnegative().nullable(),
55902
+ /** Cycle completion 0..100, or null when not applicable. */
55903
+ progressPercent: number().min(0).max(100).nullable(),
55904
+ /** True when the door is reported open. */
55905
+ doorOpen: boolean(),
55906
+ /** True when remote start is allowed (door closed, power on, etc.). */
55907
+ remoteStartAllowed: boolean(),
55908
+ /** Main power / switched-on state. */
55909
+ powerOn: boolean(),
55910
+ saltLevel: DishwasherConsumableLevelSchema.nullable(),
55911
+ rinseAidLevel: DishwasherConsumableLevelSchema.nullable(),
55912
+ halfLoad: boolean(),
55913
+ hygienePlus: boolean(),
55914
+ errorCode: number().nullable(),
55915
+ errorLabel: string().nullable(),
55916
+ lastFetchedAt: number()
55917
+ });
55918
+ var dishwasherCapability = {
55919
+ name: "dishwasher",
55920
+ scope: "device",
55921
+ deviceNative: true,
55922
+ mode: "singleton",
55923
+ deviceTypes: [DeviceType.Dishwasher],
55924
+ methods: {
55925
+ start: method(object({ deviceId: number().int().nonnegative() }), _void(), {
55926
+ kind: "mutation",
55927
+ auth: "protected"
55928
+ }),
55929
+ pause: method(object({ deviceId: number().int().nonnegative() }), _void(), {
55930
+ kind: "mutation",
55931
+ auth: "protected"
55932
+ }),
55933
+ stop: method(object({ deviceId: number().int().nonnegative() }), _void(), {
55934
+ kind: "mutation",
55935
+ auth: "protected"
55936
+ }),
55937
+ setProgram: method(object({
55938
+ deviceId: number().int().nonnegative(),
55939
+ program: string().min(1)
55940
+ }), _void(), {
55941
+ kind: "mutation",
55942
+ auth: "protected"
55943
+ }),
55944
+ setPower: method(object({
55945
+ deviceId: number().int().nonnegative(),
55946
+ on: boolean()
55947
+ }), _void(), {
55948
+ kind: "mutation",
55949
+ auth: "protected"
55950
+ }),
55951
+ setHalfLoad: method(object({
55952
+ deviceId: number().int().nonnegative(),
55953
+ on: boolean()
55954
+ }), _void(), {
55955
+ kind: "mutation",
55956
+ auth: "protected"
55957
+ }),
55958
+ setHygienePlus: method(object({
55959
+ deviceId: number().int().nonnegative(),
55960
+ on: boolean()
55961
+ }), _void(), {
55962
+ kind: "mutation",
55963
+ auth: "protected"
55964
+ }),
55965
+ setOption: method(object({
55966
+ deviceId: number().int().nonnegative(),
55967
+ key: string().min(1),
55968
+ value: union([
55969
+ boolean(),
55970
+ string(),
55971
+ number()
55972
+ ])
55973
+ }), _void(), {
55974
+ kind: "mutation",
55975
+ auth: "protected"
55976
+ })
55977
+ },
55978
+ status: {
55979
+ schema: DishwasherStatusSchema,
55980
+ kind: "poll"
55981
+ },
55982
+ runtimeState: DishwasherStatusSchema,
55983
+ durability: "session",
55984
+ history: { fields: { progressPercent: {
55985
+ kind: "gauge",
55986
+ unit: "%",
55987
+ deadband: 2
55988
+ } } }
55989
+ };
55199
55990
  var DoorbellStatusSchema = object({
55200
55991
  /** Ms epoch of the last press. null = never observed since this provider started. */
55201
55992
  lastPressedAt: number().nullable(),
@@ -55998,6 +56789,32 @@ var gasCapability = {
55998
56789
  * whether persisting is worth a SQLite commit. */
55999
56790
  volatileStateFields: ["lastChangedAt"]
56000
56791
  };
56792
+ var HeatingValveStatusSchema = object({
56793
+ /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
56794
+ position: number().min(0).max(100).nullable(),
56795
+ /** Ms epoch when the slice was last updated (push or poll). */
56796
+ lastFetchedAt: number()
56797
+ });
56798
+ var heatingValveCapability = {
56799
+ name: "heating-valve",
56800
+ scope: "device",
56801
+ deviceNative: true,
56802
+ mode: "singleton",
56803
+ deviceTypes: [DeviceType.Thermostat],
56804
+ methods: {},
56805
+ status: {
56806
+ schema: HeatingValveStatusSchema,
56807
+ kind: "push"
56808
+ },
56809
+ runtimeState: HeatingValveStatusSchema,
56810
+ durability: "session",
56811
+ history: { fields: { position: {
56812
+ kind: "gauge",
56813
+ unit: "%",
56814
+ deadband: 1
56815
+ } } },
56816
+ volatileStateFields: ["lastFetchedAt"]
56817
+ };
56001
56818
  /**
56002
56819
  * Humidifier / dehumidifier cap. Models HA `humidifier.*` entities —
56003
56820
  * an on/off actuator with an optional target-humidity setpoint and an
@@ -57130,7 +57947,19 @@ var TlsStatusSchema = object({
57130
57947
  sans: array(string()),
57131
57948
  caCertPem: string().nullable(),
57132
57949
  reissueError: string().nullable(),
57133
- restartRequired: boolean()
57950
+ restartRequired: boolean(),
57951
+ /** An uploaded certificate is served but no CA is kept for it and it is not publicly signed (D873). */
57952
+ caMissing: boolean(),
57953
+ /** Does the hub's own generated CA carry a critical name constraint? `null` while it has none. */
57954
+ localCaConstrained: boolean().nullable(),
57955
+ /** ISO time the generated CA was replaced by a constrained one; devices must re-download it. */
57956
+ caReplacedAt: string().nullable(),
57957
+ /** The uploaded certificate kept on disk, selectable again after "Revert to generated". */
57958
+ storedUpload: object({
57959
+ leafFingerprintSha256: string(),
57960
+ validTo: string(),
57961
+ hasCa: boolean()
57962
+ }).nullable()
57134
57963
  });
57135
57964
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
57136
57965
  /**
@@ -57186,6 +58015,9 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
57186
58015
  }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
57187
58016
  kind: "mutation",
57188
58017
  auth: "admin"
58018
+ }), method(_void(), TlsStatusSchema, {
58019
+ kind: "mutation",
58020
+ auth: "admin"
57189
58021
  });
57190
58022
  var LockControlStatusSchema = object({
57191
58023
  /** Lifecycle state of the lock. `jammed` means the motor reported
@@ -64141,7 +64973,11 @@ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserI
64141
64973
  kind: "mutation",
64142
64974
  auth: "admin",
64143
64975
  access: "delete"
64144
- }), method(object({ userId: string() }), TotpStatusSchema, { auth: "admin" }), method(object({
64976
+ }), method(object({ userId: string() }), TotpStatusSchema, { auth: "admin" }), method(RawSecretMigrationInputSchema, RawSecretMigrationReportSchema, {
64977
+ kind: "mutation",
64978
+ auth: "admin",
64979
+ access: "create"
64980
+ }), method(object({
64145
64981
  userId: string(),
64146
64982
  code: string()
64147
64983
  }), object({ valid: boolean() }), {
@@ -65861,6 +66697,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
65861
66697
  dayNight: dayNightCapability,
65862
66698
  deviceDiscovery: deviceDiscoveryCapability,
65863
66699
  deviceStatus: deviceStatusCapability,
66700
+ dishwasher: dishwasherCapability,
65864
66701
  doorbell: doorbellCapability,
65865
66702
  enumSensor: enumSensorCapability,
65866
66703
  eventEmitter: eventEmitterCapability,
@@ -65868,6 +66705,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
65868
66705
  featureProbe: featureProbeCapability,
65869
66706
  flood: floodCapability,
65870
66707
  gas: gasCapability,
66708
+ heatingValve: heatingValveCapability,
65871
66709
  humidifier: humidifierCapability,
65872
66710
  humiditySensor: humiditySensorCapability,
65873
66711
  image: imageCapability,
@@ -65953,13 +66791,14 @@ var BaseDevice = class {
65953
66791
  get state() {
65954
66792
  if (!this._stateProxyCache) {
65955
66793
  const cache = {};
65956
- const handler = { get: (_target, key) => {
66794
+ const handler = { get: (target, key) => {
65957
66795
  const k = key;
65958
- if (k in cache) return cache[k];
66796
+ const existing = Reflect.get(target, k);
66797
+ if (existing !== void 0) return existing;
65959
66798
  const cap = DEVICE_LOCAL_STATE_CAPS[k];
65960
66799
  if (!cap) return void 0;
65961
66800
  const proxy = this.sliceProxy(cap);
65962
- cache[k] = proxy;
66801
+ Reflect.set(target, k, proxy);
65963
66802
  return proxy;
65964
66803
  } };
65965
66804
  this._stateProxyCache = new Proxy(cache, handler);
@@ -66158,7 +66997,7 @@ var BaseDevice = class {
66158
66997
  async updateSourceInfo(patch) {
66159
66998
  const next = mergeSourceInfo(this.sourceInfo, patch);
66160
66999
  this._sourceInfoCache = Object.freeze({ ...next });
66161
- const action = this.ctx.api?.deviceManager?.setMetadata;
67000
+ const action = this.ctx.api.deviceManager?.setMetadata;
66162
67001
  if (action) try {
66163
67002
  await action.mutate({
66164
67003
  deviceId: this.id,
@@ -66193,7 +67032,7 @@ var BaseDevice = class {
66193
67032
  * device boot on a meta refresh.
66194
67033
  */
66195
67034
  async refreshFeatures() {
66196
- const action = this.ctx.api?.deviceManager?.registerDevice;
67035
+ const action = this.ctx.api.deviceManager?.registerDevice;
66197
67036
  if (!action) return;
66198
67037
  try {
66199
67038
  await action.mutate({
@@ -66592,6 +67431,12 @@ var BaseDeviceProvider = class extends BaseAddon {
66592
67431
  provider: this
66593
67432
  }];
66594
67433
  }
67434
+ /** Flatten `super.onInitialize()` whether it returned an array or an envelope. */
67435
+ providerRegistrationsFromSuper(result) {
67436
+ if (Array.isArray(result)) return result;
67437
+ if (result !== void 0 && result.providers !== void 0) return [...result.providers];
67438
+ return [];
67439
+ }
66595
67440
  async onShutdown() {
66596
67441
  this._restoreShuttingDown = true;
66597
67442
  this.cancelRestoreRetries();
@@ -67118,7 +67963,11 @@ var BaseDeviceProvider = class extends BaseAddon {
67118
67963
  return toDeviceSummary(device, this.addonId);
67119
67964
  }
67120
67965
  };
67121
- 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;
67966
+ /** Map entry helper — concrete camera/hub classes widen to `DeviceConstructor<IDevice>`. */
67967
+ function widenDeviceConstructor(ctor) {
67968
+ return ctor;
67969
+ }
67970
+ 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;
67122
67971
  new Set(Object.values(DeviceType));
67123
67972
  Object.freeze({
67124
67973
  "accessories.getStatus": {
@@ -68267,6 +69116,12 @@ Object.freeze({
68267
69116
  addonId: null,
68268
69117
  access: "view"
68269
69118
  },
69119
+ "coreBlocks.migrateRawSecrets": {
69120
+ capName: "core-blocks",
69121
+ capScope: "system",
69122
+ addonId: null,
69123
+ access: "create"
69124
+ },
68270
69125
  "coreBlocks.restart": {
68271
69126
  capName: "core-blocks",
68272
69127
  capScope: "system",
@@ -69533,6 +70388,60 @@ Object.freeze({
69533
70388
  addonId: null,
69534
70389
  access: "view"
69535
70390
  },
70391
+ "dishwasher.getStatus": {
70392
+ capName: "dishwasher",
70393
+ capScope: "device",
70394
+ addonId: null,
70395
+ access: "view"
70396
+ },
70397
+ "dishwasher.pause": {
70398
+ capName: "dishwasher",
70399
+ capScope: "device",
70400
+ addonId: null,
70401
+ access: "create"
70402
+ },
70403
+ "dishwasher.setHalfLoad": {
70404
+ capName: "dishwasher",
70405
+ capScope: "device",
70406
+ addonId: null,
70407
+ access: "create"
70408
+ },
70409
+ "dishwasher.setHygienePlus": {
70410
+ capName: "dishwasher",
70411
+ capScope: "device",
70412
+ addonId: null,
70413
+ access: "create"
70414
+ },
70415
+ "dishwasher.setOption": {
70416
+ capName: "dishwasher",
70417
+ capScope: "device",
70418
+ addonId: null,
70419
+ access: "create"
70420
+ },
70421
+ "dishwasher.setPower": {
70422
+ capName: "dishwasher",
70423
+ capScope: "device",
70424
+ addonId: null,
70425
+ access: "create"
70426
+ },
70427
+ "dishwasher.setProgram": {
70428
+ capName: "dishwasher",
70429
+ capScope: "device",
70430
+ addonId: null,
70431
+ access: "create"
70432
+ },
70433
+ "dishwasher.start": {
70434
+ capName: "dishwasher",
70435
+ capScope: "device",
70436
+ addonId: null,
70437
+ access: "create"
70438
+ },
70439
+ "dishwasher.stop": {
70440
+ capName: "dishwasher",
70441
+ capScope: "device",
70442
+ addonId: null,
70443
+ access: "create"
70444
+ },
69536
70445
  "doorbell.getStatus": {
69537
70446
  capName: "doorbell",
69538
70447
  capScope: "device",
@@ -69743,6 +70652,12 @@ Object.freeze({
69743
70652
  addonId: null,
69744
70653
  access: "view"
69745
70654
  },
70655
+ "heatingValve.getStatus": {
70656
+ capName: "heating-valve",
70657
+ capScope: "device",
70658
+ addonId: null,
70659
+ access: "view"
70660
+ },
69746
70661
  "humidifier.getStatus": {
69747
70662
  capName: "humidifier",
69748
70663
  capScope: "device",
@@ -69953,6 +70868,12 @@ Object.freeze({
69953
70868
  addonId: null,
69954
70869
  access: "create"
69955
70870
  },
70871
+ "keychainAdmin.importSecret": {
70872
+ capName: "keychain-admin",
70873
+ capScope: "system",
70874
+ addonId: null,
70875
+ access: "create"
70876
+ },
69956
70877
  "keychainAdmin.listAudit": {
69957
70878
  capName: "keychain-admin",
69958
70879
  capScope: "system",
@@ -70121,6 +71042,12 @@ Object.freeze({
70121
71042
  addonId: null,
70122
71043
  access: "view"
70123
71044
  },
71045
+ "llm.migrateRawSecrets": {
71046
+ capName: "llm",
71047
+ capScope: "system",
71048
+ addonId: null,
71049
+ access: "create"
71050
+ },
70124
71051
  "llm.resolveModelRef": {
70125
71052
  capName: "llm",
70126
71053
  capScope: "system",
@@ -70349,6 +71276,12 @@ Object.freeze({
70349
71276
  addonId: null,
70350
71277
  access: "create"
70351
71278
  },
71279
+ "localNetwork.useStoredUploadedCertificate": {
71280
+ capName: "local-network",
71281
+ capScope: "system",
71282
+ addonId: null,
71283
+ access: "create"
71284
+ },
70352
71285
  "lockControl.getStatus": {
70353
71286
  capName: "lock-control",
70354
71287
  capScope: "device",
@@ -73001,6 +73934,30 @@ Object.freeze({
73001
73934
  addonId: null,
73002
73935
  access: "create"
73003
73936
  },
73937
+ "recordEntries.deleteEntry": {
73938
+ capName: "record-entries",
73939
+ capScope: "system",
73940
+ addonId: null,
73941
+ access: "delete"
73942
+ },
73943
+ "recordEntries.listEntries": {
73944
+ capName: "record-entries",
73945
+ capScope: "system",
73946
+ addonId: null,
73947
+ access: "view"
73948
+ },
73949
+ "recordEntries.listEntryKinds": {
73950
+ capName: "record-entries",
73951
+ capScope: "system",
73952
+ addonId: null,
73953
+ access: "view"
73954
+ },
73955
+ "recordEntries.putEntry": {
73956
+ capName: "record-entries",
73957
+ capScope: "system",
73958
+ addonId: null,
73959
+ access: "create"
73960
+ },
73004
73961
  "recording.getAvailability": {
73005
73962
  capName: "recording",
73006
73963
  capScope: "device",
@@ -73301,6 +74258,102 @@ Object.freeze({
73301
74258
  addonId: null,
73302
74259
  access: "view"
73303
74260
  },
74261
+ "records.aggregate": {
74262
+ capName: "records",
74263
+ capScope: "system",
74264
+ addonId: null,
74265
+ access: "view"
74266
+ },
74267
+ "records.allocateId": {
74268
+ capName: "records",
74269
+ capScope: "system",
74270
+ addonId: null,
74271
+ access: "create"
74272
+ },
74273
+ "records.count": {
74274
+ capName: "records",
74275
+ capScope: "system",
74276
+ addonId: null,
74277
+ access: "view"
74278
+ },
74279
+ "records.declareKinds": {
74280
+ capName: "records",
74281
+ capScope: "system",
74282
+ addonId: null,
74283
+ access: "create"
74284
+ },
74285
+ "records.delete": {
74286
+ capName: "records",
74287
+ capScope: "system",
74288
+ addonId: null,
74289
+ access: "delete"
74290
+ },
74291
+ "records.deleteDevice": {
74292
+ capName: "records",
74293
+ capScope: "system",
74294
+ addonId: null,
74295
+ access: "delete"
74296
+ },
74297
+ "records.deleteMany": {
74298
+ capName: "records",
74299
+ capScope: "system",
74300
+ addonId: null,
74301
+ access: "delete"
74302
+ },
74303
+ "records.deleteWhere": {
74304
+ capName: "records",
74305
+ capScope: "system",
74306
+ addonId: null,
74307
+ access: "delete"
74308
+ },
74309
+ "records.get": {
74310
+ capName: "records",
74311
+ capScope: "system",
74312
+ addonId: null,
74313
+ access: "view"
74314
+ },
74315
+ "records.getMany": {
74316
+ capName: "records",
74317
+ capScope: "system",
74318
+ addonId: null,
74319
+ access: "view"
74320
+ },
74321
+ "records.histogram": {
74322
+ capName: "records",
74323
+ capScope: "system",
74324
+ addonId: null,
74325
+ access: "view"
74326
+ },
74327
+ "records.patch": {
74328
+ capName: "records",
74329
+ capScope: "system",
74330
+ addonId: null,
74331
+ access: "create"
74332
+ },
74333
+ "records.put": {
74334
+ capName: "records",
74335
+ capScope: "system",
74336
+ addonId: null,
74337
+ access: "create"
74338
+ },
74339
+ "records.putMany": {
74340
+ capName: "records",
74341
+ capScope: "system",
74342
+ addonId: null,
74343
+ access: "create"
74344
+ },
74345
+ "records.query": {
74346
+ capName: "records",
74347
+ capScope: "system",
74348
+ addonId: null,
74349
+ access: "view"
74350
+ },
74351
+ "records.secretRefs": {
74352
+ capName: "records",
74353
+ capScope: "system",
74354
+ addonId: null,
74355
+ access: "view"
74356
+ },
73304
74357
  "sceneMonitor.captureReference": {
73305
74358
  capName: "scene-monitor",
73306
74359
  capScope: "device",
@@ -73733,6 +74786,48 @@ Object.freeze({
73733
74786
  addonId: null,
73734
74787
  access: "create"
73735
74788
  },
74789
+ "storageAdmin.adoptRecords": {
74790
+ capName: "storage-admin",
74791
+ capScope: "system",
74792
+ addonId: null,
74793
+ access: "create"
74794
+ },
74795
+ "storageAdmin.dropCollection": {
74796
+ capName: "storage-admin",
74797
+ capScope: "system",
74798
+ addonId: null,
74799
+ access: "delete"
74800
+ },
74801
+ "storageAdmin.isAdoptedLegacyTable": {
74802
+ capName: "storage-admin",
74803
+ capScope: "system",
74804
+ addonId: null,
74805
+ access: "view"
74806
+ },
74807
+ "storageAdmin.listKinds": {
74808
+ capName: "storage-admin",
74809
+ capScope: "system",
74810
+ addonId: null,
74811
+ access: "view"
74812
+ },
74813
+ "storageAdmin.listTables": {
74814
+ capName: "storage-admin",
74815
+ capScope: "system",
74816
+ addonId: null,
74817
+ access: "view"
74818
+ },
74819
+ "storageAdmin.readLegacyCollection": {
74820
+ capName: "storage-admin",
74821
+ capScope: "system",
74822
+ addonId: null,
74823
+ access: "view"
74824
+ },
74825
+ "storageAdmin.readRecords": {
74826
+ capName: "storage-admin",
74827
+ capScope: "system",
74828
+ addonId: null,
74829
+ access: "view"
74830
+ },
73736
74831
  "storageEvictable.evict": {
73737
74832
  capName: "storage-evictable",
73738
74833
  capScope: "system",
@@ -74645,6 +75740,12 @@ Object.freeze({
74645
75740
  addonId: null,
74646
75741
  access: "view"
74647
75742
  },
75743
+ "userManagement.migrateRawSecrets": {
75744
+ capName: "user-management",
75745
+ capScope: "system",
75746
+ addonId: null,
75747
+ access: "create"
75748
+ },
74648
75749
  "userManagement.oauthExchangeCode": {
74649
75750
  capName: "user-management",
74650
75751
  capScope: "system",
@@ -75144,7 +76245,7 @@ Object.freeze({
75144
76245
  access: "create"
75145
76246
  }
75146
76247
  });
75147
- 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;
76248
+ 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;
75148
76249
  DeviceFeature.BatteryOperated;
75149
76250
  /** Reject after `ms`; always clears its own timer. */
75150
76251
  async function withTimeout$1(promise, ms, label) {
@@ -76174,6 +77275,51 @@ Object.freeze({
76174
77275
  form: "single",
76175
77276
  optional: false
76176
77277
  }],
77278
+ "dishwasher.getStatus": [{
77279
+ name: "deviceId",
77280
+ form: "single",
77281
+ optional: false
77282
+ }],
77283
+ "dishwasher.pause": [{
77284
+ name: "deviceId",
77285
+ form: "single",
77286
+ optional: false
77287
+ }],
77288
+ "dishwasher.setHalfLoad": [{
77289
+ name: "deviceId",
77290
+ form: "single",
77291
+ optional: false
77292
+ }],
77293
+ "dishwasher.setHygienePlus": [{
77294
+ name: "deviceId",
77295
+ form: "single",
77296
+ optional: false
77297
+ }],
77298
+ "dishwasher.setOption": [{
77299
+ name: "deviceId",
77300
+ form: "single",
77301
+ optional: false
77302
+ }],
77303
+ "dishwasher.setPower": [{
77304
+ name: "deviceId",
77305
+ form: "single",
77306
+ optional: false
77307
+ }],
77308
+ "dishwasher.setProgram": [{
77309
+ name: "deviceId",
77310
+ form: "single",
77311
+ optional: false
77312
+ }],
77313
+ "dishwasher.start": [{
77314
+ name: "deviceId",
77315
+ form: "single",
77316
+ optional: false
77317
+ }],
77318
+ "dishwasher.stop": [{
77319
+ name: "deviceId",
77320
+ form: "single",
77321
+ optional: false
77322
+ }],
76177
77323
  "doorbell.getStatus": [{
76178
77324
  name: "deviceId",
76179
77325
  form: "single",
@@ -76243,6 +77389,11 @@ Object.freeze({
76243
77389
  form: "single",
76244
77390
  optional: false
76245
77391
  }],
77392
+ "heatingValve.getStatus": [{
77393
+ name: "deviceId",
77394
+ form: "single",
77395
+ optional: false
77396
+ }],
76246
77397
  "humidifier.getStatus": [{
76247
77398
  name: "deviceId",
76248
77399
  form: "single",
@@ -77527,6 +78678,37 @@ Object.freeze({
77527
78678
  form: "single",
77528
78679
  optional: false
77529
78680
  }],
78681
+ "records.count": [{
78682
+ name: "deviceId",
78683
+ form: "single",
78684
+ optional: true
78685
+ }],
78686
+ "records.deleteDevice": [{
78687
+ name: "deviceId",
78688
+ form: "single",
78689
+ optional: false
78690
+ }],
78691
+ "records.deleteWhere": [{
78692
+ name: "deviceId",
78693
+ form: "single",
78694
+ optional: true
78695
+ }],
78696
+ "records.histogram": [{
78697
+ name: "deviceId",
78698
+ form: "single",
78699
+ optional: true
78700
+ }],
78701
+ "records.putMany": [{
78702
+ name: "records",
78703
+ form: "object-array",
78704
+ optional: false,
78705
+ itemField: "deviceId"
78706
+ }],
78707
+ "records.query": [{
78708
+ name: "deviceId",
78709
+ form: "single",
78710
+ optional: true
78711
+ }],
77530
78712
  "sceneMonitor.captureReference": [{
77531
78713
  name: "deviceId",
77532
78714
  form: "single",
@@ -77643,6 +78825,17 @@ Object.freeze({
77643
78825
  form: "single",
77644
78826
  optional: false
77645
78827
  }],
78828
+ "storageAdmin.adoptRecords": [{
78829
+ name: "records",
78830
+ form: "object-array",
78831
+ optional: false,
78832
+ itemField: "deviceId"
78833
+ }],
78834
+ "storageAdmin.readRecords": [{
78835
+ name: "deviceId",
78836
+ form: "single",
78837
+ optional: true
78838
+ }],
77646
78839
  "streamBroker.acquireEgressTranscode": [{
77647
78840
  name: "deviceId",
77648
78841
  form: "single",
@@ -95981,7 +97174,7 @@ function isMeaningfulIdentifier(s) {
95981
97174
  var AmcrestProviderAddon = class extends BaseDeviceProvider {
95982
97175
  addonId = AMCREST_ADDON_ID;
95983
97176
  providerName = "Amcrest";
95984
- deviceClasses = { [DeviceType.Camera]: AmcrestCamera };
97177
+ deviceClasses = { [DeviceType.Camera]: widenDeviceConstructor(AmcrestCamera) };
95985
97178
  constructor() {
95986
97179
  super({});
95987
97180
  }
@@ -96004,7 +97197,7 @@ var AmcrestProviderAddon = class extends BaseDeviceProvider {
96004
97197
  throw new Error(`Amcrest: probe on ${host || "(unknown host)"} resolved neither mac nor host address — cannot persist a stable row key. Verify network reachability + credentials, then retry.`);
96005
97198
  }
96006
97199
  async onInitialize() {
96007
- const regs = await super.onInitialize();
97200
+ const regs = this.providerRegistrationsFromSuper(await super.onInitialize());
96008
97201
  this.clipService = createClipService({
96009
97202
  dataDir: this.ctx.dataDir,
96010
97203
  logger: this.ctx.logger,