@camstack/addon-mqtt-broker 1.2.149 → 1.2.150

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.
@@ -6028,7 +6028,7 @@ var ZodIssueCode = {
6028
6028
  var ZodFirstPartyTypeKind;
6029
6029
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6030
6030
  //#endregion
6031
- //#region ../types/dist/sleep-BKClVk0K.mjs
6031
+ //#region ../types/dist/sleep-BaWhXZFm.mjs
6032
6032
  /**
6033
6033
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6034
6034
  * window to float samples (D455).
@@ -8165,6 +8165,10 @@ var BaseAddon = class {
8165
8165
  const stored = keychain ? await this.readAddonStoreWithRetry() : {};
8166
8166
  const patch = {};
8167
8167
  for (const key of keys) {
8168
+ if (tree.get(key)?.kind === "array") {
8169
+ patch[key] = [];
8170
+ continue;
8171
+ }
8168
8172
  const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
8169
8173
  patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
8170
8174
  }
@@ -8860,6 +8864,10 @@ var RECORD_SECRET_COLLECTION_PREFIX = "records:";
8860
8864
  * (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
8861
8865
  * (`auth.accessToken`, `headers[].value`).
8862
8866
  *
8867
+ * `{}` after a key means "each value of this map" (`applianceSecrets{}`): the map's own key
8868
+ * names the value (`applianceSecrets{fridge}`), so no id key is declared and a body that drops a
8869
+ * key releases exactly that key's secret.
8870
+ *
8863
8871
  * Array elements are matched by a DECLARED id key (`secretArrayKeys`:
8864
8872
  * `{ headers: 'name' }`), never by index, so reordering a list keeps every
8865
8873
  * keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
@@ -8872,11 +8880,13 @@ function parseSecretPath(path) {
8872
8880
  if (path.length === 0) throw new InvalidRecordError("an empty secret path");
8873
8881
  return path.split(".").map((raw) => {
8874
8882
  const each = raw.endsWith("[]");
8875
- const key = each ? raw.slice(0, -2) : raw;
8883
+ const map = raw.endsWith("{}");
8884
+ const key = each || map ? raw.slice(0, -2) : raw;
8876
8885
  if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
8877
8886
  return {
8878
8887
  key,
8879
- each
8888
+ each,
8889
+ map
8880
8890
  };
8881
8891
  });
8882
8892
  }
@@ -8928,6 +8938,22 @@ function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arra
8928
8938
  [segment.key]: mapped
8929
8939
  };
8930
8940
  }
8941
+ if (segment.map) {
8942
+ if (!isRecord$4(child)) return node;
8943
+ const kept = Object.entries(child).map(([mapKey, value]) => {
8944
+ const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}{${encodeURIComponent(mapKey)}}`;
8945
+ if (!last) return [mapKey, mapAt(value, segments, index + 1, keys, concrete, declared, arrayKeys, visit)];
8946
+ return [mapKey, visit({
8947
+ declared,
8948
+ concrete,
8949
+ value
8950
+ })];
8951
+ }).filter(([, value]) => value !== REMOVE_LEAF);
8952
+ return {
8953
+ ...node,
8954
+ [segment.key]: Object.fromEntries(kept)
8955
+ };
8956
+ }
8931
8957
  const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
8932
8958
  if (last) {
8933
8959
  const replacement = visit({
@@ -9069,7 +9095,8 @@ function isZodSecret(schema) {
9069
9095
  }
9070
9096
  /**
9071
9097
  * Every declared secret path in a config schema: dotted keys, `[]` for "each
9072
- * element of this array" (`connection.password`, `rows[].token`). A declared
9098
+ * element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
9099
+ * map" (`applianceSecrets{}`). A declared
9073
9100
  * object or array is reported as one path and not descended into — everything
9074
9101
  * under it is secret.
9075
9102
  */
@@ -9080,6 +9107,7 @@ function collectZodSecretPaths(schema, prefix = "") {
9080
9107
  const def = defOf(layer);
9081
9108
  if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
9082
9109
  else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
9110
+ else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
9083
9111
  else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
9084
9112
  else if (def.type === "intersection") {
9085
9113
  if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
@@ -9219,7 +9247,7 @@ function defineRecordKinds(specs) {
9219
9247
  retention: spec.retention,
9220
9248
  deviceRemoval: spec.deviceRemoval,
9221
9249
  maxDataBytes: spec.maxDataBytes,
9222
- secretPaths: [...secretPaths],
9250
+ secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
9223
9251
  secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
9224
9252
  transactFamily: spec.transactFamily,
9225
9253
  entry: spec.entry === void 0 ? void 0 : {
@@ -9264,6 +9292,7 @@ function defineEntryKind(name, spec) {
9264
9292
  ...spec.database === void 0 ? {} : { database: spec.database },
9265
9293
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9266
9294
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
9295
+ ...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
9267
9296
  entry: {
9268
9297
  title: spec.title,
9269
9298
  ...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
@@ -11925,7 +11954,7 @@ object({
11925
11954
  params: record(string(), union([string(), number()])).optional()
11926
11955
  });
11927
11956
  //#endregion
11928
- //#region ../types/dist/composition-BodkvOV0.mjs
11957
+ //#region ../types/dist/composition-CRNwcFYY.mjs
11929
11958
  /**
11930
11959
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11931
11960
  *
@@ -12688,38 +12717,6 @@ function validateExpressionSource(src) {
12688
12717
  return null;
12689
12718
  }
12690
12719
  /**
12691
- * One power reading becomes a washer, dryer or dishwasher (D944 helper).
12692
- *
12693
- * Standby of the control board is under 5 W; a motor or a heater is far
12694
- * above that, so 5 W is the line. A door lock or a short pump spike is not
12695
- * a cycle, so running starts only after 30 s above the line.
12696
- *
12697
- * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
12698
- * bursts) lasts several minutes. Ending the cycle on the first dip would
12699
- * report "finished" in the middle of the program, so the stop dwell is
12700
- * 10 minutes on every type. The number is stored per type so one machine
12701
- * can move later without moving the others.
12702
- */
12703
- var APPLIANCE_TEMPLATE_ID = "appliance";
12704
- var APPLIANCE_KINDS = [
12705
- "washer",
12706
- "dryer",
12707
- "dishwasher"
12708
- ];
12709
- DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
12710
- /**
12711
- * A dumb plug-in stove or heater, built from a smart plug (a `Heater`).
12712
- *
12713
- * The plug is the actuator: `switch.setState` is a FORWARD to the plug's own
12714
- * (D967), so the heater switches exactly as the plug does. Running is the
12715
- * plug being on; when a power reading is also named, running is the draw
12716
- * staying above the line instead (a plug left on with the stove's own
12717
- * thermostat open is not heating). 50 W is far above any standby and below
12718
- * the smallest radiator.
12719
- */
12720
- var HEATER_TEMPLATE_ID = "heater";
12721
- DeviceType.Heater;
12722
- /**
12723
12720
  * Statistics and integrals over a history read (D944).
12724
12721
  *
12725
12722
  * Pure: the same points always give the same answer, so a restart recomputes
@@ -12863,34 +12860,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
12863
12860
  /** Actuator groups only. Readings leave this unset. */
12864
12861
  active: ActiveCriterionSchema.optional()
12865
12862
  }),
12866
- object({
12867
- kind: literal("appliance"),
12868
- templateId: literal(APPLIANCE_TEMPLATE_ID),
12869
- appliance: _enum(APPLIANCE_KINDS),
12870
- thresholdWatts: number().positive(),
12871
- startDwellMs: number().int().nonnegative(),
12872
- stopDwellMs: number().int().nonnegative(),
12873
- member: HelperMemberSchema,
12874
- /**
12875
- * The plug that powers the appliance (`switch.on`). Optional: when set, the
12876
- * composition carries a `power-source` feature, so the hub can cut and restore
12877
- * that plug (power-source link of a NEW-target composition).
12878
- */
12879
- powerSwitch: HelperMemberSchema.optional(),
12880
- /** How long a forced restart keeps the plug off; the cap's default when unset. */
12881
- offSeconds: number().optional()
12882
- }),
12883
- object({
12884
- kind: literal("heater"),
12885
- templateId: literal(HEATER_TEMPLATE_ID),
12886
- /** The plug, read and commanded at `switch.on`. */
12887
- plug: HelperMemberSchema,
12888
- /** The plug's power reading, when it has one: running is then the draw, not the switch. */
12889
- meter: HelperMemberSchema.optional(),
12890
- thresholdWatts: number().positive(),
12891
- startDwellMs: number().int().nonnegative(),
12892
- stopDwellMs: number().int().nonnegative()
12893
- }),
12894
12863
  object({
12895
12864
  kind: literal("statistic"),
12896
12865
  stat: _enum(STATISTIC_STATS),
@@ -13114,7 +13083,7 @@ var CompositionSchema = object({
13114
13083
  kind: literal("existing"),
13115
13084
  device: CompositionSourceRefSchema
13116
13085
  })]),
13117
- features: array(CompositionFeatureSchema).min(1).max(16),
13086
+ features: array(CompositionFeatureSchema).min(0).max(16),
13118
13087
  /**
13119
13088
  * Present when the block is a helper template. Absent on a free
13120
13089
  * composition (the room example, device 4603). The editor reads this,
@@ -13332,11 +13301,49 @@ var IntegrationInstanceRefSchema = object({
13332
13301
  ]),
13333
13302
  candidateStableId: string().optional()
13334
13303
  });
13304
+ /**
13305
+ * `RecordIntegrationInstances` - the `integration-instances` provider over ONE
13306
+ * entry kind, so an addon that holds a connection serves it in ~30 lines.
13307
+ *
13308
+ * Every instance is one record of the addon's own kind, keyed by the
13309
+ * `integrations` registry id. The records door does the secret work: a field the
13310
+ * kind marks `.meta({ secret: true })` is a keychain marker bound to the row, so
13311
+ * deleting the row deletes the secret, and one row's value is never another's.
13312
+ *
13313
+ * Patch rules for a secret (`updateInstance`): omitted, `''`, the redaction
13314
+ * token or the view the form was given KEEP the stored secret;
13315
+ * `{ $secretClear: true }` clears it; any other string replaces it.
13316
+ */
13335
13317
  /** The most instances one addon lists (an integrations page, not a table). */
13336
13318
  var INSTANCES_LIST_MAX = 1e3;
13337
13319
  /** The keys of the kind's secret fields that sit at the top level of the body. */
13338
13320
  function topLevelSecretKeys(paths) {
13339
- return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[")));
13321
+ return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[") && !p.includes("{")));
13322
+ }
13323
+ /** The keys of the kind's top-level MAPS whose values are secrets (`applianceSecrets{}`). */
13324
+ function secretMapKeys(paths) {
13325
+ return new Set(paths.filter((p) => p.endsWith("{}") && !p.slice(0, -2).match(/[.[{]/)).map((p) => p.slice(0, -2)));
13326
+ }
13327
+ /**
13328
+ * A patch value for a map of secrets: an entry that is omitted, `''`, the redaction token or the
13329
+ * view the form was given KEEPS its stored secret (it is left out of the merge); `{ $secretClear }`
13330
+ * or `null` removes the entry; any other string replaces it. Null when nothing is left to merge.
13331
+ */
13332
+ function secretMapPatch(value) {
13333
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
13334
+ const out = {};
13335
+ for (const [key, entry] of Object.entries(value)) if (entry === null || isSecretClear(entry)) out[key] = null;
13336
+ else if (typeof entry === "string" && entry !== "" && entry !== "__camstack_redacted__" && !isSecretFieldView(entry)) out[key] = entry;
13337
+ return Object.keys(out).length > 0 ? out : null;
13338
+ }
13339
+ /** Does the kind's body schema carry an `enabled` field? */
13340
+ function declaresEnabled(kind) {
13341
+ const json = toJSONSchema(kind.schema, {
13342
+ unrepresentable: "any",
13343
+ io: "input"
13344
+ });
13345
+ const properties = Reflect.get(json, "properties");
13346
+ return typeof properties === "object" && properties !== null && "enabled" in properties;
13340
13347
  }
13341
13348
  var RecordIntegrationInstances = class {
13342
13349
  records;
@@ -13344,14 +13351,20 @@ var RecordIntegrationInstances = class {
13344
13351
  hooks;
13345
13352
  handle;
13346
13353
  secretKeys;
13354
+ secretMaps;
13347
13355
  nameField;
13356
+ /** The `integrations` registry owns the on/off switch (`role: devices`); the body has none. */
13357
+ registryOwnsEnabled;
13348
13358
  constructor(records, kind, hooks) {
13349
13359
  this.records = records;
13350
13360
  this.kind = kind;
13351
13361
  this.hooks = hooks;
13352
13362
  this.handle = records.kind(kind);
13353
13363
  this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
13364
+ this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
13354
13365
  this.nameField = kind.declaration.entry?.nameField ?? "name";
13366
+ this.registryOwnsEnabled = hooks.description.role === "devices";
13367
+ if (this.registryOwnsEnabled && declaresEnabled(kind)) throw new Error(`kind "${kind.name}": the integrations registry owns the on/off switch of a role "devices" addon, so the body must not declare "enabled"`);
13355
13368
  }
13356
13369
  /**
13357
13370
  * One instance read for the addon's own runtime; null when absent. When the keychain is
@@ -13396,15 +13409,23 @@ var RecordIntegrationInstances = class {
13396
13409
  viewOf(record) {
13397
13410
  const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
13398
13411
  const rawName = record.data[this.nameField];
13412
+ const kind = this.hooks.kindOf?.(record.data);
13413
+ const health = this.hooks.health?.(record.data, record.id);
13399
13414
  return {
13400
13415
  id: record.id,
13401
13416
  name: typeof rawName === "string" ? rawName : record.id,
13402
13417
  configured: this.hooks.configured(record.data),
13403
- enabled: record.data.enabled !== false,
13404
- formValues: shown
13418
+ ...this.registryOwnsEnabled ? {} : { enabled: record.data.enabled !== false },
13419
+ formValues: shown,
13420
+ ...kind !== void 0 ? { kind } : {},
13421
+ ...health?.status !== void 0 ? { status: health.status } : {},
13422
+ ...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
13405
13423
  };
13406
13424
  }
13407
- describe = async () => this.hooks.description;
13425
+ describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
13426
+ ...this.hooks.description,
13427
+ mintsId: true
13428
+ };
13408
13429
  instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13409
13430
  listInstances = async () => {
13410
13431
  return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
@@ -13414,37 +13435,46 @@ var RecordIntegrationInstances = class {
13414
13435
  return record === null ? null : this.viewOf(record);
13415
13436
  };
13416
13437
  /** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
13417
- createInstance = async ({ id, name }) => {
13438
+ createInstance = async ({ id, name, kind }) => {
13439
+ const minted = id ?? this.hooks.mintId?.({
13440
+ name,
13441
+ ...kind !== void 0 ? { kind } : {}
13442
+ });
13443
+ if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
13418
13444
  await this.handle.put({
13419
- id,
13420
- data: this.hooks.initialBody(name)
13445
+ id: minted,
13446
+ data: this.hooks.initialBody(name, kind)
13421
13447
  }, { ifRev: 0 });
13422
- await this.hooks.onChanged?.(id);
13423
- return { id };
13448
+ await this.hooks.onChanged?.(minted, "created");
13449
+ return { id: minted };
13424
13450
  };
13425
13451
  updateInstance = async ({ id, name, values }) => {
13426
13452
  const patch = {};
13427
13453
  for (const [key, value] of Object.entries(values)) {
13428
13454
  if (value === void 0) continue;
13429
- if (!this.secretKeys.has(key)) patch[key] = value;
13455
+ if (this.secretMaps.has(key)) {
13456
+ const entries = secretMapPatch(value);
13457
+ if (entries !== null) patch[key] = entries;
13458
+ } else if (!this.secretKeys.has(key)) patch[key] = value;
13430
13459
  else if (isSecretClear(value)) patch[key] = null;
13431
13460
  else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
13432
13461
  }
13433
13462
  if (name !== void 0) patch[this.nameField] = name;
13434
13463
  await this.handle.patch(id, patch);
13435
- await this.hooks.onChanged?.(id);
13464
+ await this.hooks.onChanged?.(id, "updated");
13436
13465
  return { ok: true };
13437
13466
  };
13438
13467
  /** The switch lives in the record body; the addon is told so it applies it (D996). */
13439
13468
  setInstanceEnabled = async ({ id, enabled }) => {
13469
+ if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
13440
13470
  await this.handle.patch(id, { enabled });
13441
- await this.hooks.onChanged?.(id);
13471
+ await this.hooks.onChanged?.(id, "enabled");
13442
13472
  return { ok: true };
13443
13473
  };
13444
13474
  /** The row and its keychain refs go together (the records door). */
13445
13475
  deleteInstance = async ({ id }) => {
13446
13476
  await this.handle.delete(id);
13447
- await this.hooks.onChanged?.(id);
13477
+ await this.hooks.onChanged?.(id, "deleted");
13448
13478
  return { ok: true };
13449
13479
  };
13450
13480
  testInstance = async (input) => {
@@ -48894,49 +48924,6 @@ function actuator(input) {
48894
48924
  };
48895
48925
  }
48896
48926
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
48897
- var PowerSourceRefusalReasonSchema = _enum([
48898
- "no-power-source",
48899
- "switch-missing",
48900
- "switch-offline",
48901
- "switch-unknown",
48902
- "restart-in-progress",
48903
- "switch-call-failed"
48904
- ]);
48905
- var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
48906
- accepted: literal(false),
48907
- reason: PowerSourceRefusalReasonSchema,
48908
- /** One line for the operator, naming the switch. */
48909
- message: string()
48910
- })]);
48911
- var PowerSourceInfoSchema = object({
48912
- /** False when the device has no power source linked; the rest is then empty. */
48913
- linked: boolean(),
48914
- switchDeviceId: number().nullable(),
48915
- switchName: string().nullable(),
48916
- /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
48917
- switchOn: boolean().nullable(),
48918
- switchOnline: boolean().nullable(),
48919
- restartInProgress: boolean(),
48920
- offSeconds: number().int()
48921
- });
48922
- object({
48923
- /** Null while the switch has not reported: unknown is never `false` (D393). */
48924
- switchOn: boolean().nullable(),
48925
- offSeconds: number().int(),
48926
- lastFetchedAt: number()
48927
- });
48928
- DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater;
48929
- var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
48930
- systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
48931
- kind: "mutation",
48932
- auth: "admin"
48933
- }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
48934
- kind: "mutation",
48935
- auth: "admin"
48936
- }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
48937
- kind: "mutation",
48938
- auth: "admin"
48939
- });
48940
48927
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
48941
48928
  DeviceType.Camera;
48942
48929
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
@@ -48964,6 +48951,165 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
48964
48951
  }).optional()
48965
48952
  })) });
48966
48953
  /**
48954
+ * A saved Customize assignment (D936). The schema is the type: callers use
48955
+ * `z.infer`, they do not restate the shape.
48956
+ */
48957
+ var OverrideSourceSchema = object({
48958
+ addonId: string().min(1),
48959
+ stableId: string().min(1),
48960
+ cap: string().min(1),
48961
+ fieldPath: string().min(1)
48962
+ });
48963
+ var IdentityTransformDraftSchema = object({ kind: literal("identity") });
48964
+ var InvertTransformDraftSchema = object({ kind: literal("invert") });
48965
+ var ThresholdTransformDraftSchema = object({
48966
+ kind: literal("threshold"),
48967
+ comparison: _enum(["gte", "lte"]),
48968
+ threshold: number(),
48969
+ startDwellMs: number().nonnegative(),
48970
+ stopDwellMs: number().nonnegative()
48971
+ });
48972
+ var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
48973
+ IdentityTransformDraftSchema,
48974
+ InvertTransformDraftSchema,
48975
+ ThresholdTransformDraftSchema,
48976
+ object({
48977
+ kind: literal("linear"),
48978
+ scale: number(),
48979
+ offset: number(),
48980
+ clampMin: number().optional(),
48981
+ clampMax: number().optional()
48982
+ }),
48983
+ object({
48984
+ kind: literal("enum-map"),
48985
+ mapping: record(string(), string().nullable())
48986
+ }),
48987
+ object({ kind: literal("number") })
48988
+ ]);
48989
+ var ScalarOverrideDraftSchema = object({
48990
+ kind: literal("scalar"),
48991
+ cap: string().min(1),
48992
+ targetId: string().min(1),
48993
+ source: OverrideSourceSchema,
48994
+ transform: CustomizeTransformDraftSchema
48995
+ });
48996
+ var KeyedArrayOverrideDraftSchema = object({
48997
+ kind: literal("keyedArray"),
48998
+ cap: string().min(1),
48999
+ targetId: string().min(1),
49000
+ key: string().min(1),
49001
+ label: string().min(1),
49002
+ fieldPath: string().min(1),
49003
+ source: OverrideSourceSchema,
49004
+ transform: CustomizeTransformDraftSchema
49005
+ });
49006
+ var PulseOverrideDraftSchema = object({
49007
+ kind: literal("pulse"),
49008
+ cap: string().min(1),
49009
+ targetId: string().min(1),
49010
+ enabled: boolean(),
49011
+ debounceMs: number().nonnegative(),
49012
+ edges: array(_enum([
49013
+ "rising",
49014
+ "button",
49015
+ "native-event"
49016
+ ])).min(1).optional(),
49017
+ sources: array(OverrideSourceSchema).min(1)
49018
+ });
49019
+ var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
49020
+ "in-addition",
49021
+ "confirm",
49022
+ "replace"
49023
+ ]).optional() });
49024
+ discriminatedUnion("kind", [
49025
+ ScalarOverrideDraftSchema,
49026
+ KeyedArrayOverrideDraftSchema,
49027
+ PulseOverrideDraftSchema,
49028
+ object({
49029
+ kind: literal("span"),
49030
+ cap: string().min(1),
49031
+ targetId: string().min(1),
49032
+ timeoutMs: number().nonnegative(),
49033
+ cooldownMs: number().nonnegative().optional(),
49034
+ sources: array(SpanSourceDraftSchema)
49035
+ })
49036
+ ]);
49037
+ object({
49038
+ targetId: string().min(1),
49039
+ configured: boolean(),
49040
+ source: _enum(["native", "assigned"]),
49041
+ state: _enum([
49042
+ "active",
49043
+ "stale",
49044
+ "released",
49045
+ "unavailable"
49046
+ ]),
49047
+ reason: string().nullable()
49048
+ });
49049
+ /**
49050
+ * What a running-threshold proposal says (planning#405). The schema is the
49051
+ * type: the algorithm, the capability method and the UI all read `z.infer`.
49052
+ *
49053
+ * A proposal is read-only advice: it carries the numbers it was derived from
49054
+ * (`evidence`) so the operator can judge it, and an `insufficient` answer names
49055
+ * WHY with a typed reason and the catalog defaults, never an invented number.
49056
+ */
49057
+ var RunningProposalTierSchema = _enum(["raw", "5m"]);
49058
+ var RunningProposalConfidenceSchema = _enum([
49059
+ "high",
49060
+ "medium",
49061
+ "low"
49062
+ ]);
49063
+ var RunningProposalInsufficientReasonSchema = _enum([
49064
+ "no-history",
49065
+ "span-too-short",
49066
+ "truncated",
49067
+ "no-idle-floor",
49068
+ "no-runs",
49069
+ "no-separation"
49070
+ ]);
49071
+ var RunningProposalReplaySchema = object({
49072
+ /** Intervals the real dwell expression held true over the window. */
49073
+ intervals: number().int().nonnegative(),
49074
+ /** Intervals outside every run, or a second interval inside one. */
49075
+ extra: number().int().nonnegative(),
49076
+ /** Runs without an interval of their own (none, or merged into a neighbour). */
49077
+ missed: number().int().nonnegative()
49078
+ });
49079
+ var RunningProposalEvidenceSchema = object({
49080
+ tier: RunningProposalTierSchema,
49081
+ spanMs: number().nonnegative(),
49082
+ from: number(),
49083
+ to: number(),
49084
+ points: number().int().nonnegative(),
49085
+ runsFound: number().int().nonnegative(),
49086
+ pausesFound: number().int().nonnegative(),
49087
+ longestPauseMs: number().nonnegative(),
49088
+ idleFloorW: number(),
49089
+ noiseCeilingW: number(),
49090
+ plateauW: number(),
49091
+ replay: RunningProposalReplaySchema
49092
+ });
49093
+ var RunningProposalInsufficientEvidenceSchema = object({
49094
+ spanMs: number().nonnegative(),
49095
+ points: number().int().nonnegative(),
49096
+ runsFound: number().int().nonnegative()
49097
+ });
49098
+ var RunningThresholdProposalSchema = discriminatedUnion("state", [object({
49099
+ state: literal("proposal"),
49100
+ transform: ThresholdTransformDraftSchema,
49101
+ evidence: RunningProposalEvidenceSchema,
49102
+ confidence: RunningProposalConfidenceSchema,
49103
+ /** Why the confidence is not higher; empty when nothing held it back. */
49104
+ notes: array(string())
49105
+ }), object({
49106
+ state: literal("insufficient"),
49107
+ reason: RunningProposalInsufficientReasonSchema,
49108
+ evidence: RunningProposalInsufficientEvidenceSchema,
49109
+ /** The catalog defaults, offered as what stands when nothing better is known. */
49110
+ fallback: ThresholdTransformDraftSchema
49111
+ })]);
49112
+ /**
48967
49113
  * The report of a one-shot "move this owner's plaintext secrets into the
48968
49114
  * keychain" run (D878). COUNTS only - never a value, never a ref.
48969
49115
  *
@@ -49253,6 +49399,14 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
49253
49399
  kind: "mutation",
49254
49400
  auth: "admin",
49255
49401
  access: "view"
49402
+ }), method(object({
49403
+ deviceId: number().int(),
49404
+ cap: string().min(1),
49405
+ fieldPath: string().min(1),
49406
+ deviceType: _enum(DeviceType).optional()
49407
+ }), RunningThresholdProposalSchema, {
49408
+ auth: "admin",
49409
+ access: "view"
49256
49410
  }), method(object({
49257
49411
  blockId: string().min(1),
49258
49412
  trace: AutomationRunTraceSchema.optional(),
@@ -50061,7 +50215,27 @@ method(object({
50061
50215
  * exposes only the COMMON surface: link state, supported device
50062
50216
  * kinds (so the UI can filter the device picker), and per-device
50063
50217
  * expose/unexpose.
50064
- */
50218
+ *
50219
+ * INSTANCES. One export addon can serve several independent targets (a
50220
+ * HomeKit bridge, a Home Assistant broker). `listInstances` names them.
50221
+ * `instanceId` on a READ is optional: absent = the whole addon (linked if any
50222
+ * enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
50223
+ * default instance's); present = that instance; an id the provider does not
50224
+ * serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
50225
+ * never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
50226
+ * change always names the target it changes. A single-instance provider
50227
+ * accepts an absent id on a read or its own id, and refuses any other.
50228
+ */
50229
+ /** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
50230
+ var InstanceIdSchema$1 = string().min(1);
50231
+ var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
50232
+ /** One independent export target of an addon. */
50233
+ var ExportInstanceSchema = object({
50234
+ instanceId: InstanceIdSchema$1,
50235
+ name: string(),
50236
+ enabled: boolean(),
50237
+ kind: string().optional()
50238
+ });
50065
50239
  var LinkStateSchema = _enum([
50066
50240
  "unlinked",
50067
50241
  "linked",
@@ -50112,10 +50286,14 @@ var ExposedDeviceSchema = object({
50112
50286
  capabilities: array(string()).optional()
50113
50287
  });
50114
50288
  var ExposeInputSchema = object({
50289
+ instanceId: InstanceIdSchema$1,
50115
50290
  deviceId: string(),
50116
50291
  capabilities: array(string()).optional()
50117
50292
  });
50118
- var UnexposeInputSchema = object({ deviceId: string() });
50293
+ var UnexposeInputSchema = object({
50294
+ instanceId: InstanceIdSchema$1,
50295
+ deviceId: string()
50296
+ });
50119
50297
  /**
50120
50298
  * What an exporter may expose besides devices (D968). An action and a
50121
50299
  * notification target are LIST ENTRIES, never devices (D954): they enter an
@@ -50144,15 +50322,21 @@ var ExposedEntitySchema = object({
50144
50322
  entity: ExportEntityRefSchema,
50145
50323
  exposedAs: string().optional()
50146
50324
  });
50147
- var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
50148
- var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
50149
- method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
50325
+ var ExposeEntityInputSchema = object({
50326
+ instanceId: InstanceIdSchema$1,
50327
+ entity: ExportEntityRefSchema
50328
+ });
50329
+ var UnexposeEntityInputSchema = object({
50330
+ instanceId: InstanceIdSchema$1,
50331
+ entity: ExportEntityRefSchema
50332
+ });
50333
+ method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
50150
50334
  kind: "mutation",
50151
50335
  auth: "admin"
50152
50336
  }), method(UnexposeInputSchema, _void(), {
50153
50337
  kind: "mutation",
50154
50338
  auth: "admin"
50155
- }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
50339
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
50156
50340
  kind: "mutation",
50157
50341
  auth: "admin"
50158
50342
  }), optionalMethod(UnexposeEntityInputSchema, _void(), {
@@ -53353,15 +53537,15 @@ var FanPresetCaptureResultSchema = object({
53353
53537
  items: array(FanCaptureItemSchema),
53354
53538
  omitted: array(FanCaptureOmissionSchema)
53355
53539
  });
53356
- var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
53540
+ var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
53357
53541
  DeviceType.Fan;
53358
- method(deviceInput$1, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$1.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
53542
+ method(deviceInput$2, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$2.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
53359
53543
  kind: "mutation",
53360
53544
  auth: "admin"
53361
- }), method(deviceInput$1.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
53545
+ }), method(deviceInput$2.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
53362
53546
  kind: "mutation",
53363
53547
  auth: "protected"
53364
- }), method(deviceInput$1.extend({
53548
+ }), method(deviceInput$2.extend({
53365
53549
  fields: array(FanCaptureFieldSchema).optional(),
53366
53550
  steps: array(ActionStepSchema).max(20).default([])
53367
53551
  }), FanPresetCaptureResultSchema, { auth: "protected" });
@@ -53885,13 +54069,65 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
53885
54069
  */
53886
54070
  var ConfigSchemaPassthrough$3 = custom();
53887
54071
  var IntegrationInstanceIdSchema = string().min(1);
54072
+ var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
54073
+ /**
54074
+ * One kind of instance a provider can create (an adapter, an account type). The sign-in and
54075
+ * connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
54076
+ * hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
54077
+ * interactive account link (popup) and may carry `{{brokerId}}`, which the UI replaces with the
54078
+ * instance id.
54079
+ */
54080
+ var IntegrationInstanceKindSchema = object({
54081
+ /** Stable id of the kind, sent back as `createInstance.kind`. */
54082
+ id: string().min(1),
54083
+ label: string().min(1),
54084
+ /** The kind signs in through a server-held interactive session. */
54085
+ signIn: object({
54086
+ basePath: string().min(1),
54087
+ methods: array(_enum(["device-code", "proxy"])).optional()
54088
+ }).optional(),
54089
+ /** Relative hub path to start an interactive account link of an instance of this kind. */
54090
+ connectPath: string().min(1).optional(),
54091
+ /** Relative hub path to disconnect that account. */
54092
+ disconnectPath: string().min(1).optional()
54093
+ });
53888
54094
  var IntegrationInstanceDescriptionSchema = object({
54095
+ /** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
54096
+ role: _enum([
54097
+ "devices",
54098
+ "services",
54099
+ "both"
54100
+ ]),
54101
+ /** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
54102
+ discovery: object({
54103
+ mode: _enum([
54104
+ "none",
54105
+ "lan",
54106
+ "account"
54107
+ ]),
54108
+ scopedToInstance: boolean()
54109
+ }).optional(),
53889
54110
  /** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
53890
- instanceMode: _enum(["single", "multiple"]),
54111
+ instanceMode: IntegrationInstanceModeSchema,
53891
54112
  /** What the instance is, for the UI (`destination`, `relay`, `account`). */
53892
54113
  kind: string(),
53893
54114
  /** True when `testInstance` really probes the remote. */
53894
- canTest: boolean()
54115
+ canTest: boolean(),
54116
+ /**
54117
+ * The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
54118
+ * Add flow asks which first and sends it as `createInstance.kind`.
54119
+ */
54120
+ kinds: array(IntegrationInstanceKindSchema).optional(),
54121
+ /**
54122
+ * The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
54123
+ * devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
54124
+ */
54125
+ allowRestoreId: boolean().optional(),
54126
+ /**
54127
+ * `createInstance` without an `id` works: the provider mints it. Absent means the caller must
54128
+ * send an id (the UI then mints an opaque one).
54129
+ */
54130
+ mintsId: boolean().optional()
53895
54131
  });
53896
54132
  var IntegrationInstanceStatusSchema = object({
53897
54133
  state: _enum([
@@ -53900,7 +54136,9 @@ var IntegrationInstanceStatusSchema = object({
53900
54136
  "error",
53901
54137
  "unknown"
53902
54138
  ]),
53903
- detail: string().optional()
54139
+ detail: string().optional(),
54140
+ /** A short human word for the state ("Signed in", "Token expired"); shown instead of `state`. */
54141
+ label: string().optional()
53904
54142
  });
53905
54143
  var IntegrationInstanceSchema = object({
53906
54144
  id: IntegrationInstanceIdSchema,
@@ -53908,13 +54146,18 @@ var IntegrationInstanceSchema = object({
53908
54146
  /** The addon's own rule (an URL is present, a client id is present...). */
53909
54147
  configured: boolean(),
53910
54148
  /**
53911
- * The instance's own on/off switch (D996): always present, derived from the record body
53912
- * (`enabled !== false`, so a row written before the field existed is on).
54149
+ * The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
54150
+ * so a row written before the field existed is on). Absent when the `integrations` registry
54151
+ * owns the switch (`describe().role` is `devices`): read it from the registry row.
53913
54152
  */
53914
- enabled: boolean(),
54153
+ enabled: boolean().optional(),
54154
+ /** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
54155
+ kind: string().optional(),
53915
54156
  /** Values for the instance form; a secret is a `SecretFieldView`, never text. */
53916
54157
  formValues: record(string(), unknown()),
53917
- status: IntegrationInstanceStatusSchema.optional()
54158
+ status: IntegrationInstanceStatusSchema.optional(),
54159
+ /** Epoch ms of the last probe of the remote that produced `status`; absent when never checked. */
54160
+ lastCheckedAt: number().optional()
53918
54161
  });
53919
54162
  var IntegrationInstancePatchSchema = object({
53920
54163
  id: IntegrationInstanceIdSchema,
@@ -53932,10 +54175,14 @@ var integrationInstancesCapability = {
53932
54175
  instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
53933
54176
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
53934
54177
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
53935
- /** Insert the typed record under the given id (a duplicate id is refused). */
54178
+ /**
54179
+ * Insert the typed record. With `id` it is stored under that id (a duplicate id is refused);
54180
+ * without one the PROVIDER mints it and answers it. `kind` picks one of `describe().kinds`.
54181
+ */
53936
54182
  createInstance: method(object({
53937
- id: IntegrationInstanceIdSchema,
53938
- name: string().min(1)
54183
+ id: IntegrationInstanceIdSchema.optional(),
54184
+ name: string().min(1),
54185
+ kind: string().min(1).optional()
53939
54186
  }), object({ id: string() }), {
53940
54187
  kind: "mutation",
53941
54188
  auth: "admin"
@@ -55767,6 +56014,11 @@ var LogEntrySchema = object({
55767
56014
  * flight, and not asking for the next slice ends the scan.
55768
56015
  */
55769
56016
  var LogReadQuerySchema = object({
56017
+ /**
56018
+ * Which destination INSTANCE answers (a destination addon may hold several records). Absent ⇒
56019
+ * the provider's first readable one, so a caller written before instances existed is unchanged.
56020
+ */
56021
+ instanceId: string().min(1).max(200).optional(),
55770
56022
  /** Correlates `cancelRead`. Caller-chosen, unique per in-flight read. */
55771
56023
  requestId: string().min(1).max(64).optional(),
55772
56024
  /** The device the line is about (`tags.deviceId`, numeric). */
@@ -55829,6 +56081,8 @@ var LogReadResultSchema = object({
55829
56081
  /** True when `cancelRead` ended this slice early. */
55830
56082
  cancelled: boolean().optional()
55831
56083
  });
56084
+ /** Names one destination instance; absent ⇒ the provider's first readable one. */
56085
+ var LogInstanceSelectorSchema = object({ instanceId: string().min(1).max(200).optional() });
55832
56086
  var LogReadCapabilitiesSchema = object({
55833
56087
  /** False ⇒ this destination only ships lines (console, forwarder, an unconfigured Loki). */
55834
56088
  readable: boolean(),
@@ -55847,7 +56101,7 @@ var LogReadCapabilitiesSchema = object({
55847
56101
  /** How far back the store reaches, when the provider knows (days). */
55848
56102
  retentionDays: number().nullable()
55849
56103
  });
55850
- method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(_void(), LogReadCapabilitiesSchema, { auth: "admin" }), optionalMethod(LogReadQuerySchema, LogReadResultSchema, { auth: "admin" }), optionalMethod(object({ requestId: string().min(1).max(64) }), object({ cancelled: boolean() }), {
56104
+ method(LogEntrySchema, _void(), { kind: "mutation" }), optionalMethod(LogInstanceSelectorSchema, LogReadCapabilitiesSchema, { auth: "admin" }), optionalMethod(LogReadQuerySchema, LogReadResultSchema, { auth: "admin" }), optionalMethod(object({ requestId: string().min(1).max(64) }), object({ cancelled: boolean() }), {
55851
56105
  kind: "mutation",
55852
56106
  auth: "admin"
55853
56107
  });
@@ -67438,6 +67692,49 @@ object({
67438
67692
  DeviceType.PetFeeder,
67439
67693
  DeviceType.Heater
67440
67694
  ].filter((type) => type !== DeviceType.Sensor);
67695
+ var PowerSourceRefusalReasonSchema = _enum([
67696
+ "no-power-source",
67697
+ "switch-missing",
67698
+ "switch-offline",
67699
+ "switch-unknown",
67700
+ "restart-in-progress",
67701
+ "switch-call-failed"
67702
+ ]);
67703
+ var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
67704
+ accepted: literal(false),
67705
+ reason: PowerSourceRefusalReasonSchema,
67706
+ /** One line for the operator, naming the switch. */
67707
+ message: string()
67708
+ })]);
67709
+ var PowerSourceInfoSchema = object({
67710
+ /** False when the device has no power source linked; the rest is then empty. */
67711
+ linked: boolean(),
67712
+ switchDeviceId: number().nullable(),
67713
+ switchName: string().nullable(),
67714
+ /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
67715
+ switchOn: boolean().nullable(),
67716
+ switchOnline: boolean().nullable(),
67717
+ restartInProgress: boolean(),
67718
+ offSeconds: number().int()
67719
+ });
67720
+ object({
67721
+ /** Null while the switch has not reported: unknown is never `false` (D393). */
67722
+ switchOn: boolean().nullable(),
67723
+ offSeconds: number().int(),
67724
+ lastFetchedAt: number()
67725
+ });
67726
+ DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater;
67727
+ var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
67728
+ systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
67729
+ kind: "mutation",
67730
+ auth: "admin"
67731
+ }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
67732
+ kind: "mutation",
67733
+ auth: "admin"
67734
+ }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
67735
+ kind: "mutation",
67736
+ auth: "admin"
67737
+ });
67441
67738
  /**
67442
67739
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
67443
67740
  * on `DeviceType.Presence`. Read-only — no setters: presence is
@@ -75491,6 +75788,25 @@ function buildDescriptor(capName, kind) {
75491
75788
  * contributes one entry per bound cap present in this map.
75492
75789
  */
75493
75790
  var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
75791
+ /**
75792
+ * What a remote key is CALLED and what it LOOKS like, for every role (D963),
75793
+ * shared by the admin UI and the viewer. A role is data on the wire; each
75794
+ * client resolves its name from `label` (English and Italian side by side) and
75795
+ * its glyph from `icon`, a lucide id from the shared icon catalogue — the
75796
+ * server sends an id, a client maps it to its own icon set.
75797
+ *
75798
+ * `legend` is text printed on the key INSTEAD of a glyph (a digit has no
75799
+ * pictogram): a client that can draw text shows it, one that cannot falls back
75800
+ * to `icon`.
75801
+ */
75802
+ function digit(n) {
75803
+ return {
75804
+ label: t(n, n),
75805
+ icon: "hash",
75806
+ legend: n
75807
+ };
75808
+ }
75809
+ digit("0"), digit("1"), digit("2"), digit("3"), digit("4"), digit("5"), digit("6"), digit("7"), digit("8"), digit("9");
75494
75810
  DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
75495
75811
  var btn = (idSuffix, row, col, init) => ({
75496
75812
  idSuffix,
@@ -75618,6 +75934,34 @@ discriminatedUnion("kind", [object({
75618
75934
  kind: literal("pronto"),
75619
75935
  code: string()
75620
75936
  })]);
75937
+ /**
75938
+ * Propose a running threshold from a device's own power history (planning#405).
75939
+ *
75940
+ * Pure and deterministic: the same series gives the same answer. The series is
75941
+ * a HELD-VALUE step series — a point's value stands until the next point — which
75942
+ * is how device-history stores a gauge, so "time at a level" is exact, not a
75943
+ * count of samples.
75944
+ *
75945
+ * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
75946
+ * near F. A device that is not near one level most of the time has no floor.
75947
+ * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
75948
+ * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
75949
+ * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
75950
+ * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
75951
+ * grid a person would type; stop dwell covers the longest pause; start dwell
75952
+ * covers the longest standby blip above T.
75953
+ * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
75954
+ * run must hold exactly one true interval and nothing may flap outside.
75955
+ */
75956
+ var SECOND_MS = 1e3;
75957
+ var MINUTE_MS$1 = 60 * SECOND_MS;
75958
+ 24 * (60 * MINUTE_MS$1);
75959
+ 10 * MINUTE_MS$1;
75960
+ 30 * SECOND_MS;
75961
+ 60 * SECOND_MS;
75962
+ 20 * MINUTE_MS$1;
75963
+ 10 * SECOND_MS;
75964
+ 10 * SECOND_MS;
75621
75965
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
75622
75966
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
75623
75967
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
@@ -77159,6 +77503,13 @@ Object.freeze({
77159
77503
  access: "create",
77160
77504
  role: "admin"
77161
77505
  },
77506
+ "coreBlocks.proposeRunningThreshold": {
77507
+ capName: "core-blocks",
77508
+ capScope: "system",
77509
+ addonId: null,
77510
+ access: "view",
77511
+ role: "admin"
77512
+ },
77162
77513
  "coreBlocks.reportAutomationRun": {
77163
77514
  capName: "core-blocks",
77164
77515
  capScope: "system",
@@ -77670,6 +78021,13 @@ Object.freeze({
77670
78021
  access: "view",
77671
78022
  role: "admin"
77672
78023
  },
78024
+ "deviceExport.listInstances": {
78025
+ capName: "device-export",
78026
+ capScope: "system",
78027
+ addonId: null,
78028
+ access: "view",
78029
+ role: "admin"
78030
+ },
77673
78031
  "deviceExport.listSupportedDeviceKinds": {
77674
78032
  capName: "device-export",
77675
78033
  capScope: "system",
@@ -86617,6 +86975,11 @@ Object.freeze({
86617
86975
  form: "single",
86618
86976
  optional: false
86619
86977
  }],
86978
+ "coreBlocks.proposeRunningThreshold": [{
86979
+ name: "deviceId",
86980
+ form: "single",
86981
+ optional: false
86982
+ }],
86620
86983
  "cover.close": [{
86621
86984
  name: "deviceId",
86622
86985
  form: "single",
@@ -89280,99 +89643,6 @@ Object.freeze({
89280
89643
  "network-access": "ingress",
89281
89644
  "smtp-provider": "email"
89282
89645
  });
89283
- /**
89284
- * A saved Customize assignment (D936). The schema is the type: callers use
89285
- * `z.infer`, they do not restate the shape.
89286
- */
89287
- var OverrideSourceSchema = object({
89288
- addonId: string().min(1),
89289
- stableId: string().min(1),
89290
- cap: string().min(1),
89291
- fieldPath: string().min(1)
89292
- });
89293
- var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
89294
- object({ kind: literal("identity") }),
89295
- object({ kind: literal("invert") }),
89296
- object({
89297
- kind: literal("threshold"),
89298
- comparison: _enum(["gte", "lte"]),
89299
- threshold: number(),
89300
- startDwellMs: number().nonnegative(),
89301
- stopDwellMs: number().nonnegative()
89302
- }),
89303
- object({
89304
- kind: literal("linear"),
89305
- scale: number(),
89306
- offset: number(),
89307
- clampMin: number().optional(),
89308
- clampMax: number().optional()
89309
- }),
89310
- object({
89311
- kind: literal("enum-map"),
89312
- mapping: record(string(), string().nullable())
89313
- }),
89314
- object({ kind: literal("number") })
89315
- ]);
89316
- var ScalarOverrideDraftSchema = object({
89317
- kind: literal("scalar"),
89318
- cap: string().min(1),
89319
- targetId: string().min(1),
89320
- source: OverrideSourceSchema,
89321
- transform: CustomizeTransformDraftSchema
89322
- });
89323
- var KeyedArrayOverrideDraftSchema = object({
89324
- kind: literal("keyedArray"),
89325
- cap: string().min(1),
89326
- targetId: string().min(1),
89327
- key: string().min(1),
89328
- label: string().min(1),
89329
- fieldPath: string().min(1),
89330
- source: OverrideSourceSchema,
89331
- transform: CustomizeTransformDraftSchema
89332
- });
89333
- var PulseOverrideDraftSchema = object({
89334
- kind: literal("pulse"),
89335
- cap: string().min(1),
89336
- targetId: string().min(1),
89337
- enabled: boolean(),
89338
- debounceMs: number().nonnegative(),
89339
- edges: array(_enum([
89340
- "rising",
89341
- "button",
89342
- "native-event"
89343
- ])).min(1).optional(),
89344
- sources: array(OverrideSourceSchema).min(1)
89345
- });
89346
- var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
89347
- "in-addition",
89348
- "confirm",
89349
- "replace"
89350
- ]).optional() });
89351
- discriminatedUnion("kind", [
89352
- ScalarOverrideDraftSchema,
89353
- KeyedArrayOverrideDraftSchema,
89354
- PulseOverrideDraftSchema,
89355
- object({
89356
- kind: literal("span"),
89357
- cap: string().min(1),
89358
- targetId: string().min(1),
89359
- timeoutMs: number().nonnegative(),
89360
- cooldownMs: number().nonnegative().optional(),
89361
- sources: array(SpanSourceDraftSchema)
89362
- })
89363
- ]);
89364
- object({
89365
- targetId: string().min(1),
89366
- configured: boolean(),
89367
- source: _enum(["native", "assigned"]),
89368
- state: _enum([
89369
- "active",
89370
- "stale",
89371
- "released",
89372
- "unavailable"
89373
- ]),
89374
- reason: string().nullable()
89375
- });
89376
89646
  var G711_SCALE_CORRECTION_DB = {
89377
89647
  PCMU: 20 * Math.log10(4),
89378
89648
  PCMA: 20 * Math.log10(8)
@@ -123384,6 +123654,7 @@ var MqttBrokerAddon = class extends BaseAddon {
123384
123654
  this.aclStore = MqttAclStore.of(records);
123385
123655
  this.instances = new RecordIntegrationInstances(records, mqttBrokersEntryKind, {
123386
123656
  description: {
123657
+ role: "services",
123387
123658
  instanceMode: "multiple",
123388
123659
  kind: "brokers",
123389
123660
  canTest: false