@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.
@@ -6021,7 +6021,7 @@ var ZodIssueCode = {
6021
6021
  var ZodFirstPartyTypeKind;
6022
6022
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6023
6023
  //#endregion
6024
- //#region ../types/dist/sleep-BKClVk0K.mjs
6024
+ //#region ../types/dist/sleep-BaWhXZFm.mjs
6025
6025
  /**
6026
6026
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6027
6027
  * window to float samples (D455).
@@ -8158,6 +8158,10 @@ var BaseAddon = class {
8158
8158
  const stored = keychain ? await this.readAddonStoreWithRetry() : {};
8159
8159
  const patch = {};
8160
8160
  for (const key of keys) {
8161
+ if (tree.get(key)?.kind === "array") {
8162
+ patch[key] = [];
8163
+ continue;
8164
+ }
8161
8165
  const keychainStored = tree.has(key) || collectSecretMarkerRefs(stored[key]).length > 0;
8162
8166
  patch[key] = keychain && keychainStored ? SECRET_CLEAR : "";
8163
8167
  }
@@ -8853,6 +8857,10 @@ var RECORD_SECRET_COLLECTION_PREFIX = "records:";
8853
8857
  * (`collectZodSecretPaths`): dotted keys, `[]` for "each element of this array"
8854
8858
  * (`auth.accessToken`, `headers[].value`).
8855
8859
  *
8860
+ * `{}` after a key means "each value of this map" (`applianceSecrets{}`): the map's own key
8861
+ * names the value (`applianceSecrets{fridge}`), so no id key is declared and a body that drops a
8862
+ * key releases exactly that key's secret.
8863
+ *
8856
8864
  * Array elements are matched by a DECLARED id key (`secretArrayKeys`:
8857
8865
  * `{ headers: 'name' }`), never by index, so reordering a list keeps every
8858
8866
  * keychain ref (the same rule as `arrayItemFieldPath`). The concrete path of a
@@ -8865,11 +8873,13 @@ function parseSecretPath(path) {
8865
8873
  if (path.length === 0) throw new InvalidRecordError("an empty secret path");
8866
8874
  return path.split(".").map((raw) => {
8867
8875
  const each = raw.endsWith("[]");
8868
- const key = each ? raw.slice(0, -2) : raw;
8876
+ const map = raw.endsWith("{}");
8877
+ const key = each || map ? raw.slice(0, -2) : raw;
8869
8878
  if (key.length === 0) throw new InvalidRecordError(`secret path "${path}" has an empty segment`);
8870
8879
  return {
8871
8880
  key,
8872
- each
8881
+ each,
8882
+ map
8873
8883
  };
8874
8884
  });
8875
8885
  }
@@ -8921,6 +8931,22 @@ function mapAt(node, segments, index, walkedKeys, concretePrefix, declared, arra
8921
8931
  [segment.key]: mapped
8922
8932
  };
8923
8933
  }
8934
+ if (segment.map) {
8935
+ if (!isRecord$4(child)) return node;
8936
+ const kept = Object.entries(child).map(([mapKey, value]) => {
8937
+ const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}{${encodeURIComponent(mapKey)}}`;
8938
+ if (!last) return [mapKey, mapAt(value, segments, index + 1, keys, concrete, declared, arrayKeys, visit)];
8939
+ return [mapKey, visit({
8940
+ declared,
8941
+ concrete,
8942
+ value
8943
+ })];
8944
+ }).filter(([, value]) => value !== REMOVE_LEAF);
8945
+ return {
8946
+ ...node,
8947
+ [segment.key]: Object.fromEntries(kept)
8948
+ };
8949
+ }
8924
8950
  const concrete = `${concretePrefix}${concretePrefix.length > 0 ? "." : ""}${segment.key}`;
8925
8951
  if (last) {
8926
8952
  const replacement = visit({
@@ -9062,7 +9088,8 @@ function isZodSecret(schema) {
9062
9088
  }
9063
9089
  /**
9064
9090
  * Every declared secret path in a config schema: dotted keys, `[]` for "each
9065
- * element of this array" (`connection.password`, `rows[].token`). A declared
9091
+ * element of this array" (`connection.password`, `rows[].token`), `{}` for "each value of this
9092
+ * map" (`applianceSecrets{}`). A declared
9066
9093
  * object or array is reported as one path and not descended into — everything
9067
9094
  * under it is secret.
9068
9095
  */
@@ -9073,6 +9100,7 @@ function collectZodSecretPaths(schema, prefix = "") {
9073
9100
  const def = defOf(layer);
9074
9101
  if (def.type === "object" && def.shape) for (const [key, child] of Object.entries(def.shape)) paths.push(...collectZodSecretPaths(child, prefix ? `${prefix}.${key}` : key));
9075
9102
  else if (def.type === "array" && def.element) paths.push(...collectZodSecretPaths(def.element, `${prefix}[]`));
9103
+ else if (def.type === "record" && def.valueType) paths.push(...collectZodSecretPaths(def.valueType, `${prefix}{}`));
9076
9104
  else if (def.type === "union" && def.options) for (const option of def.options) paths.push(...collectZodSecretPaths(option, prefix));
9077
9105
  else if (def.type === "intersection") {
9078
9106
  if (def.left) paths.push(...collectZodSecretPaths(def.left, prefix));
@@ -9212,7 +9240,7 @@ function defineRecordKinds(specs) {
9212
9240
  retention: spec.retention,
9213
9241
  deviceRemoval: spec.deviceRemoval,
9214
9242
  maxDataBytes: spec.maxDataBytes,
9215
- secretPaths: [...secretPaths],
9243
+ secretPaths: [...new Set([...secretPaths, ...spec.retiredSecretPaths ?? []])],
9216
9244
  secretArrayKeys: spec.secretArrayKeys === void 0 ? void 0 : { ...spec.secretArrayKeys },
9217
9245
  transactFamily: spec.transactFamily,
9218
9246
  entry: spec.entry === void 0 ? void 0 : {
@@ -9257,6 +9285,7 @@ function defineEntryKind(name, spec) {
9257
9285
  ...spec.database === void 0 ? {} : { database: spec.database },
9258
9286
  ...spec.indexes === void 0 ? {} : { indexes: spec.indexes },
9259
9287
  ...spec.secretArrayKeys === void 0 ? {} : { secretArrayKeys: spec.secretArrayKeys },
9288
+ ...spec.retiredSecretPaths === void 0 ? {} : { retiredSecretPaths: spec.retiredSecretPaths },
9260
9289
  entry: {
9261
9290
  title: spec.title,
9262
9291
  ...spec.nameField === void 0 ? {} : { nameField: spec.nameField }
@@ -11918,7 +11947,7 @@ object({
11918
11947
  params: record(string(), union([string(), number()])).optional()
11919
11948
  });
11920
11949
  //#endregion
11921
- //#region ../types/dist/composition-BodkvOV0.mjs
11950
+ //#region ../types/dist/composition-CRNwcFYY.mjs
11922
11951
  /**
11923
11952
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11924
11953
  *
@@ -12681,38 +12710,6 @@ function validateExpressionSource(src) {
12681
12710
  return null;
12682
12711
  }
12683
12712
  /**
12684
- * One power reading becomes a washer, dryer or dishwasher (D944 helper).
12685
- *
12686
- * Standby of the control board is under 5 W; a motor or a heater is far
12687
- * above that, so 5 W is the line. A door lock or a short pump spike is not
12688
- * a cycle, so running starts only after 30 s above the line.
12689
- *
12690
- * A wash, dry or dishwasher pause (soak, heat-wait, heater off between
12691
- * bursts) lasts several minutes. Ending the cycle on the first dip would
12692
- * report "finished" in the middle of the program, so the stop dwell is
12693
- * 10 minutes on every type. The number is stored per type so one machine
12694
- * can move later without moving the others.
12695
- */
12696
- var APPLIANCE_TEMPLATE_ID = "appliance";
12697
- var APPLIANCE_KINDS = [
12698
- "washer",
12699
- "dryer",
12700
- "dishwasher"
12701
- ];
12702
- DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
12703
- /**
12704
- * A dumb plug-in stove or heater, built from a smart plug (a `Heater`).
12705
- *
12706
- * The plug is the actuator: `switch.setState` is a FORWARD to the plug's own
12707
- * (D967), so the heater switches exactly as the plug does. Running is the
12708
- * plug being on; when a power reading is also named, running is the draw
12709
- * staying above the line instead (a plug left on with the stove's own
12710
- * thermostat open is not heating). 50 W is far above any standby and below
12711
- * the smallest radiator.
12712
- */
12713
- var HEATER_TEMPLATE_ID = "heater";
12714
- DeviceType.Heater;
12715
- /**
12716
12713
  * Statistics and integrals over a history read (D944).
12717
12714
  *
12718
12715
  * Pure: the same points always give the same answer, so a restart recomputes
@@ -12856,34 +12853,6 @@ var HelperSpecSchema = discriminatedUnion("kind", [
12856
12853
  /** Actuator groups only. Readings leave this unset. */
12857
12854
  active: ActiveCriterionSchema.optional()
12858
12855
  }),
12859
- object({
12860
- kind: literal("appliance"),
12861
- templateId: literal(APPLIANCE_TEMPLATE_ID),
12862
- appliance: _enum(APPLIANCE_KINDS),
12863
- thresholdWatts: number().positive(),
12864
- startDwellMs: number().int().nonnegative(),
12865
- stopDwellMs: number().int().nonnegative(),
12866
- member: HelperMemberSchema,
12867
- /**
12868
- * The plug that powers the appliance (`switch.on`). Optional: when set, the
12869
- * composition carries a `power-source` feature, so the hub can cut and restore
12870
- * that plug (power-source link of a NEW-target composition).
12871
- */
12872
- powerSwitch: HelperMemberSchema.optional(),
12873
- /** How long a forced restart keeps the plug off; the cap's default when unset. */
12874
- offSeconds: number().optional()
12875
- }),
12876
- object({
12877
- kind: literal("heater"),
12878
- templateId: literal(HEATER_TEMPLATE_ID),
12879
- /** The plug, read and commanded at `switch.on`. */
12880
- plug: HelperMemberSchema,
12881
- /** The plug's power reading, when it has one: running is then the draw, not the switch. */
12882
- meter: HelperMemberSchema.optional(),
12883
- thresholdWatts: number().positive(),
12884
- startDwellMs: number().int().nonnegative(),
12885
- stopDwellMs: number().int().nonnegative()
12886
- }),
12887
12856
  object({
12888
12857
  kind: literal("statistic"),
12889
12858
  stat: _enum(STATISTIC_STATS),
@@ -13107,7 +13076,7 @@ var CompositionSchema = object({
13107
13076
  kind: literal("existing"),
13108
13077
  device: CompositionSourceRefSchema
13109
13078
  })]),
13110
- features: array(CompositionFeatureSchema).min(1).max(16),
13079
+ features: array(CompositionFeatureSchema).min(0).max(16),
13111
13080
  /**
13112
13081
  * Present when the block is a helper template. Absent on a free
13113
13082
  * composition (the room example, device 4603). The editor reads this,
@@ -13325,11 +13294,49 @@ var IntegrationInstanceRefSchema = object({
13325
13294
  ]),
13326
13295
  candidateStableId: string().optional()
13327
13296
  });
13297
+ /**
13298
+ * `RecordIntegrationInstances` - the `integration-instances` provider over ONE
13299
+ * entry kind, so an addon that holds a connection serves it in ~30 lines.
13300
+ *
13301
+ * Every instance is one record of the addon's own kind, keyed by the
13302
+ * `integrations` registry id. The records door does the secret work: a field the
13303
+ * kind marks `.meta({ secret: true })` is a keychain marker bound to the row, so
13304
+ * deleting the row deletes the secret, and one row's value is never another's.
13305
+ *
13306
+ * Patch rules for a secret (`updateInstance`): omitted, `''`, the redaction
13307
+ * token or the view the form was given KEEP the stored secret;
13308
+ * `{ $secretClear: true }` clears it; any other string replaces it.
13309
+ */
13328
13310
  /** The most instances one addon lists (an integrations page, not a table). */
13329
13311
  var INSTANCES_LIST_MAX = 1e3;
13330
13312
  /** The keys of the kind's secret fields that sit at the top level of the body. */
13331
13313
  function topLevelSecretKeys(paths) {
13332
- return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[")));
13314
+ return new Set(paths.filter((p) => !p.includes(".") && !p.includes("[") && !p.includes("{")));
13315
+ }
13316
+ /** The keys of the kind's top-level MAPS whose values are secrets (`applianceSecrets{}`). */
13317
+ function secretMapKeys(paths) {
13318
+ return new Set(paths.filter((p) => p.endsWith("{}") && !p.slice(0, -2).match(/[.[{]/)).map((p) => p.slice(0, -2)));
13319
+ }
13320
+ /**
13321
+ * A patch value for a map of secrets: an entry that is omitted, `''`, the redaction token or the
13322
+ * view the form was given KEEPS its stored secret (it is left out of the merge); `{ $secretClear }`
13323
+ * or `null` removes the entry; any other string replaces it. Null when nothing is left to merge.
13324
+ */
13325
+ function secretMapPatch(value) {
13326
+ if (typeof value !== "object" || value === null || Array.isArray(value)) return null;
13327
+ const out = {};
13328
+ for (const [key, entry] of Object.entries(value)) if (entry === null || isSecretClear(entry)) out[key] = null;
13329
+ else if (typeof entry === "string" && entry !== "" && entry !== "__camstack_redacted__" && !isSecretFieldView(entry)) out[key] = entry;
13330
+ return Object.keys(out).length > 0 ? out : null;
13331
+ }
13332
+ /** Does the kind's body schema carry an `enabled` field? */
13333
+ function declaresEnabled(kind) {
13334
+ const json = toJSONSchema(kind.schema, {
13335
+ unrepresentable: "any",
13336
+ io: "input"
13337
+ });
13338
+ const properties = Reflect.get(json, "properties");
13339
+ return typeof properties === "object" && properties !== null && "enabled" in properties;
13333
13340
  }
13334
13341
  var RecordIntegrationInstances = class {
13335
13342
  records;
@@ -13337,14 +13344,20 @@ var RecordIntegrationInstances = class {
13337
13344
  hooks;
13338
13345
  handle;
13339
13346
  secretKeys;
13347
+ secretMaps;
13340
13348
  nameField;
13349
+ /** The `integrations` registry owns the on/off switch (`role: devices`); the body has none. */
13350
+ registryOwnsEnabled;
13341
13351
  constructor(records, kind, hooks) {
13342
13352
  this.records = records;
13343
13353
  this.kind = kind;
13344
13354
  this.hooks = hooks;
13345
13355
  this.handle = records.kind(kind);
13346
13356
  this.secretKeys = topLevelSecretKeys(kind.declaration.secretPaths);
13357
+ this.secretMaps = secretMapKeys(kind.declaration.secretPaths);
13347
13358
  this.nameField = kind.declaration.entry?.nameField ?? "name";
13359
+ this.registryOwnsEnabled = hooks.description.role === "devices";
13360
+ 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"`);
13348
13361
  }
13349
13362
  /**
13350
13363
  * One instance read for the addon's own runtime; null when absent. When the keychain is
@@ -13389,15 +13402,23 @@ var RecordIntegrationInstances = class {
13389
13402
  viewOf(record) {
13390
13403
  const shown = secretViewsOf(this.records.owner, this.kind.name, record.id, this.kind.declaration, record.data);
13391
13404
  const rawName = record.data[this.nameField];
13405
+ const kind = this.hooks.kindOf?.(record.data);
13406
+ const health = this.hooks.health?.(record.data, record.id);
13392
13407
  return {
13393
13408
  id: record.id,
13394
13409
  name: typeof rawName === "string" ? rawName : record.id,
13395
13410
  configured: this.hooks.configured(record.data),
13396
- enabled: record.data.enabled !== false,
13397
- formValues: shown
13411
+ ...this.registryOwnsEnabled ? {} : { enabled: record.data.enabled !== false },
13412
+ formValues: shown,
13413
+ ...kind !== void 0 ? { kind } : {},
13414
+ ...health?.status !== void 0 ? { status: health.status } : {},
13415
+ ...health?.lastCheckedAt !== void 0 ? { lastCheckedAt: health.lastCheckedAt } : {}
13398
13416
  };
13399
13417
  }
13400
- describe = async () => this.hooks.description;
13418
+ describe = async () => this.hooks.mintId === void 0 ? this.hooks.description : {
13419
+ ...this.hooks.description,
13420
+ mintsId: true
13421
+ };
13401
13422
  instanceFormSchema = async ({ id }) => this.hooks.formSchema(id);
13402
13423
  listInstances = async () => {
13403
13424
  return (await this.handle.query({ limit: INSTANCES_LIST_MAX })).records.map((record) => this.viewOf(record));
@@ -13407,37 +13428,46 @@ var RecordIntegrationInstances = class {
13407
13428
  return record === null ? null : this.viewOf(record);
13408
13429
  };
13409
13430
  /** Insert ONE row (`ifRev: 0`: an existing id is refused, never overwritten). */
13410
- createInstance = async ({ id, name }) => {
13431
+ createInstance = async ({ id, name, kind }) => {
13432
+ const minted = id ?? this.hooks.mintId?.({
13433
+ name,
13434
+ ...kind !== void 0 ? { kind } : {}
13435
+ });
13436
+ if (minted === void 0) throw new Error("createInstance needs an id: this provider does not mint one");
13411
13437
  await this.handle.put({
13412
- id,
13413
- data: this.hooks.initialBody(name)
13438
+ id: minted,
13439
+ data: this.hooks.initialBody(name, kind)
13414
13440
  }, { ifRev: 0 });
13415
- await this.hooks.onChanged?.(id);
13416
- return { id };
13441
+ await this.hooks.onChanged?.(minted, "created");
13442
+ return { id: minted };
13417
13443
  };
13418
13444
  updateInstance = async ({ id, name, values }) => {
13419
13445
  const patch = {};
13420
13446
  for (const [key, value] of Object.entries(values)) {
13421
13447
  if (value === void 0) continue;
13422
- if (!this.secretKeys.has(key)) patch[key] = value;
13448
+ if (this.secretMaps.has(key)) {
13449
+ const entries = secretMapPatch(value);
13450
+ if (entries !== null) patch[key] = entries;
13451
+ } else if (!this.secretKeys.has(key)) patch[key] = value;
13423
13452
  else if (isSecretClear(value)) patch[key] = null;
13424
13453
  else if (typeof value === "string" && value !== "" && value !== "__camstack_redacted__" && !isSecretFieldView(value)) patch[key] = value;
13425
13454
  }
13426
13455
  if (name !== void 0) patch[this.nameField] = name;
13427
13456
  await this.handle.patch(id, patch);
13428
- await this.hooks.onChanged?.(id);
13457
+ await this.hooks.onChanged?.(id, "updated");
13429
13458
  return { ok: true };
13430
13459
  };
13431
13460
  /** The switch lives in the record body; the addon is told so it applies it (D996). */
13432
13461
  setInstanceEnabled = async ({ id, enabled }) => {
13462
+ if (this.registryOwnsEnabled) throw new Error(`instance "${id}": the integrations registry owns the on/off switch of this addon; set it with integrations.update`);
13433
13463
  await this.handle.patch(id, { enabled });
13434
- await this.hooks.onChanged?.(id);
13464
+ await this.hooks.onChanged?.(id, "enabled");
13435
13465
  return { ok: true };
13436
13466
  };
13437
13467
  /** The row and its keychain refs go together (the records door). */
13438
13468
  deleteInstance = async ({ id }) => {
13439
13469
  await this.handle.delete(id);
13440
- await this.hooks.onChanged?.(id);
13470
+ await this.hooks.onChanged?.(id, "deleted");
13441
13471
  return { ok: true };
13442
13472
  };
13443
13473
  testInstance = async (input) => {
@@ -48887,49 +48917,6 @@ function actuator(input) {
48887
48917
  };
48888
48918
  }
48889
48919
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
48890
- var PowerSourceRefusalReasonSchema = _enum([
48891
- "no-power-source",
48892
- "switch-missing",
48893
- "switch-offline",
48894
- "switch-unknown",
48895
- "restart-in-progress",
48896
- "switch-call-failed"
48897
- ]);
48898
- var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
48899
- accepted: literal(false),
48900
- reason: PowerSourceRefusalReasonSchema,
48901
- /** One line for the operator, naming the switch. */
48902
- message: string()
48903
- })]);
48904
- var PowerSourceInfoSchema = object({
48905
- /** False when the device has no power source linked; the rest is then empty. */
48906
- linked: boolean(),
48907
- switchDeviceId: number().nullable(),
48908
- switchName: string().nullable(),
48909
- /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
48910
- switchOn: boolean().nullable(),
48911
- switchOnline: boolean().nullable(),
48912
- restartInProgress: boolean(),
48913
- offSeconds: number().int()
48914
- });
48915
- object({
48916
- /** Null while the switch has not reported: unknown is never `false` (D393). */
48917
- switchOn: boolean().nullable(),
48918
- offSeconds: number().int(),
48919
- lastFetchedAt: number()
48920
- });
48921
- 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;
48922
- var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
48923
- systemMethod(deviceInput$2, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
48924
- kind: "mutation",
48925
- auth: "admin"
48926
- }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
48927
- kind: "mutation",
48928
- auth: "admin"
48929
- }), systemMethod(deviceInput$2, PowerSourceActionResultSchema, {
48930
- kind: "mutation",
48931
- auth: "admin"
48932
- });
48933
48920
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
48934
48921
  DeviceType.Camera;
48935
48922
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
@@ -48957,6 +48944,165 @@ var ComposableCapabilitiesSchema = object({ caps: array(object({
48957
48944
  }).optional()
48958
48945
  })) });
48959
48946
  /**
48947
+ * A saved Customize assignment (D936). The schema is the type: callers use
48948
+ * `z.infer`, they do not restate the shape.
48949
+ */
48950
+ var OverrideSourceSchema = object({
48951
+ addonId: string().min(1),
48952
+ stableId: string().min(1),
48953
+ cap: string().min(1),
48954
+ fieldPath: string().min(1)
48955
+ });
48956
+ var IdentityTransformDraftSchema = object({ kind: literal("identity") });
48957
+ var InvertTransformDraftSchema = object({ kind: literal("invert") });
48958
+ var ThresholdTransformDraftSchema = object({
48959
+ kind: literal("threshold"),
48960
+ comparison: _enum(["gte", "lte"]),
48961
+ threshold: number(),
48962
+ startDwellMs: number().nonnegative(),
48963
+ stopDwellMs: number().nonnegative()
48964
+ });
48965
+ var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
48966
+ IdentityTransformDraftSchema,
48967
+ InvertTransformDraftSchema,
48968
+ ThresholdTransformDraftSchema,
48969
+ object({
48970
+ kind: literal("linear"),
48971
+ scale: number(),
48972
+ offset: number(),
48973
+ clampMin: number().optional(),
48974
+ clampMax: number().optional()
48975
+ }),
48976
+ object({
48977
+ kind: literal("enum-map"),
48978
+ mapping: record(string(), string().nullable())
48979
+ }),
48980
+ object({ kind: literal("number") })
48981
+ ]);
48982
+ var ScalarOverrideDraftSchema = object({
48983
+ kind: literal("scalar"),
48984
+ cap: string().min(1),
48985
+ targetId: string().min(1),
48986
+ source: OverrideSourceSchema,
48987
+ transform: CustomizeTransformDraftSchema
48988
+ });
48989
+ var KeyedArrayOverrideDraftSchema = object({
48990
+ kind: literal("keyedArray"),
48991
+ cap: string().min(1),
48992
+ targetId: string().min(1),
48993
+ key: string().min(1),
48994
+ label: string().min(1),
48995
+ fieldPath: string().min(1),
48996
+ source: OverrideSourceSchema,
48997
+ transform: CustomizeTransformDraftSchema
48998
+ });
48999
+ var PulseOverrideDraftSchema = object({
49000
+ kind: literal("pulse"),
49001
+ cap: string().min(1),
49002
+ targetId: string().min(1),
49003
+ enabled: boolean(),
49004
+ debounceMs: number().nonnegative(),
49005
+ edges: array(_enum([
49006
+ "rising",
49007
+ "button",
49008
+ "native-event"
49009
+ ])).min(1).optional(),
49010
+ sources: array(OverrideSourceSchema).min(1)
49011
+ });
49012
+ var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
49013
+ "in-addition",
49014
+ "confirm",
49015
+ "replace"
49016
+ ]).optional() });
49017
+ discriminatedUnion("kind", [
49018
+ ScalarOverrideDraftSchema,
49019
+ KeyedArrayOverrideDraftSchema,
49020
+ PulseOverrideDraftSchema,
49021
+ object({
49022
+ kind: literal("span"),
49023
+ cap: string().min(1),
49024
+ targetId: string().min(1),
49025
+ timeoutMs: number().nonnegative(),
49026
+ cooldownMs: number().nonnegative().optional(),
49027
+ sources: array(SpanSourceDraftSchema)
49028
+ })
49029
+ ]);
49030
+ object({
49031
+ targetId: string().min(1),
49032
+ configured: boolean(),
49033
+ source: _enum(["native", "assigned"]),
49034
+ state: _enum([
49035
+ "active",
49036
+ "stale",
49037
+ "released",
49038
+ "unavailable"
49039
+ ]),
49040
+ reason: string().nullable()
49041
+ });
49042
+ /**
49043
+ * What a running-threshold proposal says (planning#405). The schema is the
49044
+ * type: the algorithm, the capability method and the UI all read `z.infer`.
49045
+ *
49046
+ * A proposal is read-only advice: it carries the numbers it was derived from
49047
+ * (`evidence`) so the operator can judge it, and an `insufficient` answer names
49048
+ * WHY with a typed reason and the catalog defaults, never an invented number.
49049
+ */
49050
+ var RunningProposalTierSchema = _enum(["raw", "5m"]);
49051
+ var RunningProposalConfidenceSchema = _enum([
49052
+ "high",
49053
+ "medium",
49054
+ "low"
49055
+ ]);
49056
+ var RunningProposalInsufficientReasonSchema = _enum([
49057
+ "no-history",
49058
+ "span-too-short",
49059
+ "truncated",
49060
+ "no-idle-floor",
49061
+ "no-runs",
49062
+ "no-separation"
49063
+ ]);
49064
+ var RunningProposalReplaySchema = object({
49065
+ /** Intervals the real dwell expression held true over the window. */
49066
+ intervals: number().int().nonnegative(),
49067
+ /** Intervals outside every run, or a second interval inside one. */
49068
+ extra: number().int().nonnegative(),
49069
+ /** Runs without an interval of their own (none, or merged into a neighbour). */
49070
+ missed: number().int().nonnegative()
49071
+ });
49072
+ var RunningProposalEvidenceSchema = object({
49073
+ tier: RunningProposalTierSchema,
49074
+ spanMs: number().nonnegative(),
49075
+ from: number(),
49076
+ to: number(),
49077
+ points: number().int().nonnegative(),
49078
+ runsFound: number().int().nonnegative(),
49079
+ pausesFound: number().int().nonnegative(),
49080
+ longestPauseMs: number().nonnegative(),
49081
+ idleFloorW: number(),
49082
+ noiseCeilingW: number(),
49083
+ plateauW: number(),
49084
+ replay: RunningProposalReplaySchema
49085
+ });
49086
+ var RunningProposalInsufficientEvidenceSchema = object({
49087
+ spanMs: number().nonnegative(),
49088
+ points: number().int().nonnegative(),
49089
+ runsFound: number().int().nonnegative()
49090
+ });
49091
+ var RunningThresholdProposalSchema = discriminatedUnion("state", [object({
49092
+ state: literal("proposal"),
49093
+ transform: ThresholdTransformDraftSchema,
49094
+ evidence: RunningProposalEvidenceSchema,
49095
+ confidence: RunningProposalConfidenceSchema,
49096
+ /** Why the confidence is not higher; empty when nothing held it back. */
49097
+ notes: array(string())
49098
+ }), object({
49099
+ state: literal("insufficient"),
49100
+ reason: RunningProposalInsufficientReasonSchema,
49101
+ evidence: RunningProposalInsufficientEvidenceSchema,
49102
+ /** The catalog defaults, offered as what stands when nothing better is known. */
49103
+ fallback: ThresholdTransformDraftSchema
49104
+ })]);
49105
+ /**
48960
49106
  * The report of a one-shot "move this owner's plaintext secrets into the
48961
49107
  * keychain" run (D878). COUNTS only - never a value, never a ref.
48962
49108
  *
@@ -49246,6 +49392,14 @@ method(object({}), object({ blocks: array(CoreBlockSchema) }), { auth: "admin" }
49246
49392
  kind: "mutation",
49247
49393
  auth: "admin",
49248
49394
  access: "view"
49395
+ }), method(object({
49396
+ deviceId: number().int(),
49397
+ cap: string().min(1),
49398
+ fieldPath: string().min(1),
49399
+ deviceType: _enum(DeviceType).optional()
49400
+ }), RunningThresholdProposalSchema, {
49401
+ auth: "admin",
49402
+ access: "view"
49249
49403
  }), method(object({
49250
49404
  blockId: string().min(1),
49251
49405
  trace: AutomationRunTraceSchema.optional(),
@@ -50054,7 +50208,27 @@ method(object({
50054
50208
  * exposes only the COMMON surface: link state, supported device
50055
50209
  * kinds (so the UI can filter the device picker), and per-device
50056
50210
  * expose/unexpose.
50057
- */
50211
+ *
50212
+ * INSTANCES. One export addon can serve several independent targets (a
50213
+ * HomeKit bridge, a Home Assistant broker). `listInstances` names them.
50214
+ * `instanceId` on a READ is optional: absent = the whole addon (linked if any
50215
+ * enabled instance is, `exposedDeviceCount` = distinct devices, `setup` = the
50216
+ * default instance's); present = that instance; an id the provider does not
50217
+ * serve is refused with `ExportInstanceUnknownError` (`export-instance-unknown`),
50218
+ * never answered empty. `instanceId` on a WRITE is REQUIRED: a membership
50219
+ * change always names the target it changes. A single-instance provider
50220
+ * accepts an absent id on a read or its own id, and refuses any other.
50221
+ */
50222
+ /** Which instance of an export addon a call addresses. Absent on a read = the whole addon. */
50223
+ var InstanceIdSchema$1 = string().min(1);
50224
+ var InstancePinSchema = object({ instanceId: InstanceIdSchema$1.optional() });
50225
+ /** One independent export target of an addon. */
50226
+ var ExportInstanceSchema = object({
50227
+ instanceId: InstanceIdSchema$1,
50228
+ name: string(),
50229
+ enabled: boolean(),
50230
+ kind: string().optional()
50231
+ });
50058
50232
  var LinkStateSchema = _enum([
50059
50233
  "unlinked",
50060
50234
  "linked",
@@ -50105,10 +50279,14 @@ var ExposedDeviceSchema = object({
50105
50279
  capabilities: array(string()).optional()
50106
50280
  });
50107
50281
  var ExposeInputSchema = object({
50282
+ instanceId: InstanceIdSchema$1,
50108
50283
  deviceId: string(),
50109
50284
  capabilities: array(string()).optional()
50110
50285
  });
50111
- var UnexposeInputSchema = object({ deviceId: string() });
50286
+ var UnexposeInputSchema = object({
50287
+ instanceId: InstanceIdSchema$1,
50288
+ deviceId: string()
50289
+ });
50112
50290
  /**
50113
50291
  * What an exporter may expose besides devices (D968). An action and a
50114
50292
  * notification target are LIST ENTRIES, never devices (D954): they enter an
@@ -50137,15 +50315,21 @@ var ExposedEntitySchema = object({
50137
50315
  entity: ExportEntityRefSchema,
50138
50316
  exposedAs: string().optional()
50139
50317
  });
50140
- var ExposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
50141
- var UnexposeEntityInputSchema = object({ entity: ExportEntityRefSchema });
50142
- method(_void(), DeviceExportStatusSchema), method(_void(), array(DeviceKindSchema)), method(_void(), array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
50318
+ var ExposeEntityInputSchema = object({
50319
+ instanceId: InstanceIdSchema$1,
50320
+ entity: ExportEntityRefSchema
50321
+ });
50322
+ var UnexposeEntityInputSchema = object({
50323
+ instanceId: InstanceIdSchema$1,
50324
+ entity: ExportEntityRefSchema
50325
+ });
50326
+ method(InstancePinSchema, DeviceExportStatusSchema), method(_void(), array(ExportInstanceSchema)), method(InstancePinSchema, array(DeviceKindSchema)), method(InstancePinSchema, array(ExposedDeviceSchema)), method(ExposeInputSchema, _void(), {
50143
50327
  kind: "mutation",
50144
50328
  auth: "admin"
50145
50329
  }), method(UnexposeInputSchema, _void(), {
50146
50330
  kind: "mutation",
50147
50331
  auth: "admin"
50148
- }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(_void(), array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
50332
+ }), optionalMethod(_void(), array(ExportEntityKindSchema)), optionalMethod(InstancePinSchema, array(ExposedEntitySchema)), optionalMethod(ExposeEntityInputSchema, _void(), {
50149
50333
  kind: "mutation",
50150
50334
  auth: "admin"
50151
50335
  }), optionalMethod(UnexposeEntityInputSchema, _void(), {
@@ -53346,15 +53530,15 @@ var FanPresetCaptureResultSchema = object({
53346
53530
  items: array(FanCaptureItemSchema),
53347
53531
  omitted: array(FanCaptureOmissionSchema)
53348
53532
  });
53349
- var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
53533
+ var deviceInput$2 = object({ deviceId: number().int().nonnegative() });
53350
53534
  DeviceType.Fan;
53351
- method(deviceInput$1, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$1.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
53535
+ method(deviceInput$2, array(FanPresetSchema), { auth: "protected" }), method(deviceInput$2.extend({ presets: array(FanPresetSchema).max(12) }), FanPresetsSetResultSchema, {
53352
53536
  kind: "mutation",
53353
53537
  auth: "admin"
53354
- }), method(deviceInput$1.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
53538
+ }), method(deviceInput$2.extend({ presetId: string().min(1) }), FanPresetApplyResultSchema, {
53355
53539
  kind: "mutation",
53356
53540
  auth: "protected"
53357
- }), method(deviceInput$1.extend({
53541
+ }), method(deviceInput$2.extend({
53358
53542
  fields: array(FanCaptureFieldSchema).optional(),
53359
53543
  steps: array(ActionStepSchema).max(20).default([])
53360
53544
  }), FanPresetCaptureResultSchema, { auth: "protected" });
@@ -53878,13 +54062,65 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
53878
54062
  */
53879
54063
  var ConfigSchemaPassthrough$3 = custom();
53880
54064
  var IntegrationInstanceIdSchema = string().min(1);
54065
+ var IntegrationInstanceModeSchema = _enum(["single", "multiple"]);
54066
+ /**
54067
+ * One kind of instance a provider can create (an adapter, an account type). The sign-in and
54068
+ * connect fields follow the `broker` contract (`broker.cap.ts`): `signIn.basePath` is a
54069
+ * hub-relative route set speaking the sign-in contract, `connectPath` / `disconnectPath` open an
54070
+ * interactive account link (popup) and may carry `{{brokerId}}`, which the UI replaces with the
54071
+ * instance id.
54072
+ */
54073
+ var IntegrationInstanceKindSchema = object({
54074
+ /** Stable id of the kind, sent back as `createInstance.kind`. */
54075
+ id: string().min(1),
54076
+ label: string().min(1),
54077
+ /** The kind signs in through a server-held interactive session. */
54078
+ signIn: object({
54079
+ basePath: string().min(1),
54080
+ methods: array(_enum(["device-code", "proxy"])).optional()
54081
+ }).optional(),
54082
+ /** Relative hub path to start an interactive account link of an instance of this kind. */
54083
+ connectPath: string().min(1).optional(),
54084
+ /** Relative hub path to disconnect that account. */
54085
+ disconnectPath: string().min(1).optional()
54086
+ });
53881
54087
  var IntegrationInstanceDescriptionSchema = object({
54088
+ /** What the instances are for the Integrations page; see {@link IntegrationInstanceRoleSchema}. */
54089
+ role: _enum([
54090
+ "devices",
54091
+ "services",
54092
+ "both"
54093
+ ]),
54094
+ /** How the devices of an instance are found; absent means `{ mode: 'none' }`. */
54095
+ discovery: object({
54096
+ mode: _enum([
54097
+ "none",
54098
+ "lan",
54099
+ "account"
54100
+ ]),
54101
+ scopedToInstance: boolean()
54102
+ }).optional(),
53882
54103
  /** `single`: at most one instance can exist (Loki). `multiple`: an account each. */
53883
- instanceMode: _enum(["single", "multiple"]),
54104
+ instanceMode: IntegrationInstanceModeSchema,
53884
54105
  /** What the instance is, for the UI (`destination`, `relay`, `account`). */
53885
54106
  kind: string(),
53886
54107
  /** True when `testInstance` really probes the remote. */
53887
- canTest: boolean()
54108
+ canTest: boolean(),
54109
+ /**
54110
+ * The kinds an instance can be of. Absent or one entry: nothing to choose; more than one: the
54111
+ * Add flow asks which first and sends it as `createInstance.kind`.
54112
+ */
54113
+ kinds: array(IntegrationInstanceKindSchema).optional(),
54114
+ /**
54115
+ * The provider accepts `createInstance.id` for an id that once existed (an adopted id, because
54116
+ * devices carry it). The UI then offers "restore under its old id" on Add. Absent means no.
54117
+ */
54118
+ allowRestoreId: boolean().optional(),
54119
+ /**
54120
+ * `createInstance` without an `id` works: the provider mints it. Absent means the caller must
54121
+ * send an id (the UI then mints an opaque one).
54122
+ */
54123
+ mintsId: boolean().optional()
53888
54124
  });
53889
54125
  var IntegrationInstanceStatusSchema = object({
53890
54126
  state: _enum([
@@ -53893,7 +54129,9 @@ var IntegrationInstanceStatusSchema = object({
53893
54129
  "error",
53894
54130
  "unknown"
53895
54131
  ]),
53896
- detail: string().optional()
54132
+ detail: string().optional(),
54133
+ /** A short human word for the state ("Signed in", "Token expired"); shown instead of `state`. */
54134
+ label: string().optional()
53897
54135
  });
53898
54136
  var IntegrationInstanceSchema = object({
53899
54137
  id: IntegrationInstanceIdSchema,
@@ -53901,13 +54139,18 @@ var IntegrationInstanceSchema = object({
53901
54139
  /** The addon's own rule (an URL is present, a client id is present...). */
53902
54140
  configured: boolean(),
53903
54141
  /**
53904
- * The instance's own on/off switch (D996): always present, derived from the record body
53905
- * (`enabled !== false`, so a row written before the field existed is on).
54142
+ * The instance's own on/off switch (D996), derived from the record body (`enabled !== false`,
54143
+ * so a row written before the field existed is on). Absent when the `integrations` registry
54144
+ * owns the switch (`describe().role` is `devices`): read it from the registry row.
53906
54145
  */
53907
- enabled: boolean(),
54146
+ enabled: boolean().optional(),
54147
+ /** The kind of this instance (one of `describe().kinds[].id`), when the provider has kinds. */
54148
+ kind: string().optional(),
53908
54149
  /** Values for the instance form; a secret is a `SecretFieldView`, never text. */
53909
54150
  formValues: record(string(), unknown()),
53910
- status: IntegrationInstanceStatusSchema.optional()
54151
+ status: IntegrationInstanceStatusSchema.optional(),
54152
+ /** Epoch ms of the last probe of the remote that produced `status`; absent when never checked. */
54153
+ lastCheckedAt: number().optional()
53911
54154
  });
53912
54155
  var IntegrationInstancePatchSchema = object({
53913
54156
  id: IntegrationInstanceIdSchema,
@@ -53925,10 +54168,14 @@ var integrationInstancesCapability = {
53925
54168
  instanceFormSchema: method(object({ id: IntegrationInstanceIdSchema.nullable() }), ConfigSchemaPassthrough$3, { auth: "admin" }),
53926
54169
  listInstances: method(_void(), array(IntegrationInstanceSchema).readonly(), { auth: "admin" }),
53927
54170
  getInstance: method(object({ id: IntegrationInstanceIdSchema }), IntegrationInstanceSchema.nullable(), { auth: "admin" }),
53928
- /** Insert the typed record under the given id (a duplicate id is refused). */
54171
+ /**
54172
+ * Insert the typed record. With `id` it is stored under that id (a duplicate id is refused);
54173
+ * without one the PROVIDER mints it and answers it. `kind` picks one of `describe().kinds`.
54174
+ */
53929
54175
  createInstance: method(object({
53930
- id: IntegrationInstanceIdSchema,
53931
- name: string().min(1)
54176
+ id: IntegrationInstanceIdSchema.optional(),
54177
+ name: string().min(1),
54178
+ kind: string().min(1).optional()
53932
54179
  }), object({ id: string() }), {
53933
54180
  kind: "mutation",
53934
54181
  auth: "admin"
@@ -55760,6 +56007,11 @@ var LogEntrySchema = object({
55760
56007
  * flight, and not asking for the next slice ends the scan.
55761
56008
  */
55762
56009
  var LogReadQuerySchema = object({
56010
+ /**
56011
+ * Which destination INSTANCE answers (a destination addon may hold several records). Absent ⇒
56012
+ * the provider's first readable one, so a caller written before instances existed is unchanged.
56013
+ */
56014
+ instanceId: string().min(1).max(200).optional(),
55763
56015
  /** Correlates `cancelRead`. Caller-chosen, unique per in-flight read. */
55764
56016
  requestId: string().min(1).max(64).optional(),
55765
56017
  /** The device the line is about (`tags.deviceId`, numeric). */
@@ -55822,6 +56074,8 @@ var LogReadResultSchema = object({
55822
56074
  /** True when `cancelRead` ended this slice early. */
55823
56075
  cancelled: boolean().optional()
55824
56076
  });
56077
+ /** Names one destination instance; absent ⇒ the provider's first readable one. */
56078
+ var LogInstanceSelectorSchema = object({ instanceId: string().min(1).max(200).optional() });
55825
56079
  var LogReadCapabilitiesSchema = object({
55826
56080
  /** False ⇒ this destination only ships lines (console, forwarder, an unconfigured Loki). */
55827
56081
  readable: boolean(),
@@ -55840,7 +56094,7 @@ var LogReadCapabilitiesSchema = object({
55840
56094
  /** How far back the store reaches, when the provider knows (days). */
55841
56095
  retentionDays: number().nullable()
55842
56096
  });
55843
- 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() }), {
56097
+ 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() }), {
55844
56098
  kind: "mutation",
55845
56099
  auth: "admin"
55846
56100
  });
@@ -67431,6 +67685,49 @@ object({
67431
67685
  DeviceType.PetFeeder,
67432
67686
  DeviceType.Heater
67433
67687
  ].filter((type) => type !== DeviceType.Sensor);
67688
+ var PowerSourceRefusalReasonSchema = _enum([
67689
+ "no-power-source",
67690
+ "switch-missing",
67691
+ "switch-offline",
67692
+ "switch-unknown",
67693
+ "restart-in-progress",
67694
+ "switch-call-failed"
67695
+ ]);
67696
+ var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
67697
+ accepted: literal(false),
67698
+ reason: PowerSourceRefusalReasonSchema,
67699
+ /** One line for the operator, naming the switch. */
67700
+ message: string()
67701
+ })]);
67702
+ var PowerSourceInfoSchema = object({
67703
+ /** False when the device has no power source linked; the rest is then empty. */
67704
+ linked: boolean(),
67705
+ switchDeviceId: number().nullable(),
67706
+ switchName: string().nullable(),
67707
+ /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
67708
+ switchOn: boolean().nullable(),
67709
+ switchOnline: boolean().nullable(),
67710
+ restartInProgress: boolean(),
67711
+ offSeconds: number().int()
67712
+ });
67713
+ object({
67714
+ /** Null while the switch has not reported: unknown is never `false` (D393). */
67715
+ switchOn: boolean().nullable(),
67716
+ offSeconds: number().int(),
67717
+ lastFetchedAt: number()
67718
+ });
67719
+ 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;
67720
+ var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
67721
+ systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
67722
+ kind: "mutation",
67723
+ auth: "admin"
67724
+ }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
67725
+ kind: "mutation",
67726
+ auth: "admin"
67727
+ }), systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
67728
+ kind: "mutation",
67729
+ auth: "admin"
67730
+ });
67434
67731
  /**
67435
67732
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
67436
67733
  * on `DeviceType.Presence`. Read-only — no setters: presence is
@@ -75484,6 +75781,25 @@ function buildDescriptor(capName, kind) {
75484
75781
  * contributes one entry per bound cap present in this map.
75485
75782
  */
75486
75783
  var EVENT_KIND_BY_CAP = Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
75784
+ /**
75785
+ * What a remote key is CALLED and what it LOOKS like, for every role (D963),
75786
+ * shared by the admin UI and the viewer. A role is data on the wire; each
75787
+ * client resolves its name from `label` (English and Italian side by side) and
75788
+ * its glyph from `icon`, a lucide id from the shared icon catalogue — the
75789
+ * server sends an id, a client maps it to its own icon set.
75790
+ *
75791
+ * `legend` is text printed on the key INSTEAD of a glyph (a digit has no
75792
+ * pictogram): a client that can draw text shows it, one that cannot falls back
75793
+ * to `icon`.
75794
+ */
75795
+ function digit(n) {
75796
+ return {
75797
+ label: t(n, n),
75798
+ icon: "hash",
75799
+ legend: n
75800
+ };
75801
+ }
75802
+ digit("0"), digit("1"), digit("2"), digit("3"), digit("4"), digit("5"), digit("6"), digit("7"), digit("8"), digit("9");
75487
75803
  DeviceFeature.MediaPlayerPower, DeviceFeature.MediaPlayerVolumeStep, DeviceFeature.MediaPlayerChannelStep, DeviceFeature.MediaPlayerMute, DeviceFeature.MediaPlayerStop, DeviceFeature.MediaPlayerNext, DeviceFeature.MediaPlayerPrevious;
75488
75804
  var btn = (idSuffix, row, col, init) => ({
75489
75805
  idSuffix,
@@ -75611,6 +75927,34 @@ discriminatedUnion("kind", [object({
75611
75927
  kind: literal("pronto"),
75612
75928
  code: string()
75613
75929
  })]);
75930
+ /**
75931
+ * Propose a running threshold from a device's own power history (planning#405).
75932
+ *
75933
+ * Pure and deterministic: the same series gives the same answer. The series is
75934
+ * a HELD-VALUE step series — a point's value stands until the next point — which
75935
+ * is how device-history stores a gauge, so "time at a level" is exact, not a
75936
+ * count of samples.
75937
+ *
75938
+ * 1. idle floor F = time-weighted median; noise ceiling N = highest value seen
75939
+ * near F. A device that is not near one level most of the time has no floor.
75940
+ * 2. coarse activity = above max(2N, N + 5 W); consecutive stretches are episodes.
75941
+ * 3. gaps between episodes split in two by the largest log-scale jump (>= 3x):
75942
+ * the short ones are pauses INSIDE a run (a soak), the long ones separate runs.
75943
+ * 4. T = geometric mean of N and the plateau's 10th percentile, snapped to a
75944
+ * grid a person would type; stop dwell covers the longest pause; start dwell
75945
+ * covers the longest standby blip above T.
75946
+ * 5. the numbers are proved by REPLAY through the real `dwell` evaluation: each
75947
+ * run must hold exactly one true interval and nothing may flap outside.
75948
+ */
75949
+ var SECOND_MS = 1e3;
75950
+ var MINUTE_MS$1 = 60 * SECOND_MS;
75951
+ 24 * (60 * MINUTE_MS$1);
75952
+ 10 * MINUTE_MS$1;
75953
+ 30 * SECOND_MS;
75954
+ 60 * SECOND_MS;
75955
+ 20 * MINUTE_MS$1;
75956
+ 10 * SECOND_MS;
75957
+ 10 * SECOND_MS;
75614
75958
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverStoppable, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
75615
75959
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
75616
75960
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
@@ -77152,6 +77496,13 @@ Object.freeze({
77152
77496
  access: "create",
77153
77497
  role: "admin"
77154
77498
  },
77499
+ "coreBlocks.proposeRunningThreshold": {
77500
+ capName: "core-blocks",
77501
+ capScope: "system",
77502
+ addonId: null,
77503
+ access: "view",
77504
+ role: "admin"
77505
+ },
77155
77506
  "coreBlocks.reportAutomationRun": {
77156
77507
  capName: "core-blocks",
77157
77508
  capScope: "system",
@@ -77663,6 +78014,13 @@ Object.freeze({
77663
78014
  access: "view",
77664
78015
  role: "admin"
77665
78016
  },
78017
+ "deviceExport.listInstances": {
78018
+ capName: "device-export",
78019
+ capScope: "system",
78020
+ addonId: null,
78021
+ access: "view",
78022
+ role: "admin"
78023
+ },
77666
78024
  "deviceExport.listSupportedDeviceKinds": {
77667
78025
  capName: "device-export",
77668
78026
  capScope: "system",
@@ -86610,6 +86968,11 @@ Object.freeze({
86610
86968
  form: "single",
86611
86969
  optional: false
86612
86970
  }],
86971
+ "coreBlocks.proposeRunningThreshold": [{
86972
+ name: "deviceId",
86973
+ form: "single",
86974
+ optional: false
86975
+ }],
86613
86976
  "cover.close": [{
86614
86977
  name: "deviceId",
86615
86978
  form: "single",
@@ -89273,99 +89636,6 @@ Object.freeze({
89273
89636
  "network-access": "ingress",
89274
89637
  "smtp-provider": "email"
89275
89638
  });
89276
- /**
89277
- * A saved Customize assignment (D936). The schema is the type: callers use
89278
- * `z.infer`, they do not restate the shape.
89279
- */
89280
- var OverrideSourceSchema = object({
89281
- addonId: string().min(1),
89282
- stableId: string().min(1),
89283
- cap: string().min(1),
89284
- fieldPath: string().min(1)
89285
- });
89286
- var CustomizeTransformDraftSchema = discriminatedUnion("kind", [
89287
- object({ kind: literal("identity") }),
89288
- object({ kind: literal("invert") }),
89289
- object({
89290
- kind: literal("threshold"),
89291
- comparison: _enum(["gte", "lte"]),
89292
- threshold: number(),
89293
- startDwellMs: number().nonnegative(),
89294
- stopDwellMs: number().nonnegative()
89295
- }),
89296
- object({
89297
- kind: literal("linear"),
89298
- scale: number(),
89299
- offset: number(),
89300
- clampMin: number().optional(),
89301
- clampMax: number().optional()
89302
- }),
89303
- object({
89304
- kind: literal("enum-map"),
89305
- mapping: record(string(), string().nullable())
89306
- }),
89307
- object({ kind: literal("number") })
89308
- ]);
89309
- var ScalarOverrideDraftSchema = object({
89310
- kind: literal("scalar"),
89311
- cap: string().min(1),
89312
- targetId: string().min(1),
89313
- source: OverrideSourceSchema,
89314
- transform: CustomizeTransformDraftSchema
89315
- });
89316
- var KeyedArrayOverrideDraftSchema = object({
89317
- kind: literal("keyedArray"),
89318
- cap: string().min(1),
89319
- targetId: string().min(1),
89320
- key: string().min(1),
89321
- label: string().min(1),
89322
- fieldPath: string().min(1),
89323
- source: OverrideSourceSchema,
89324
- transform: CustomizeTransformDraftSchema
89325
- });
89326
- var PulseOverrideDraftSchema = object({
89327
- kind: literal("pulse"),
89328
- cap: string().min(1),
89329
- targetId: string().min(1),
89330
- enabled: boolean(),
89331
- debounceMs: number().nonnegative(),
89332
- edges: array(_enum([
89333
- "rising",
89334
- "button",
89335
- "native-event"
89336
- ])).min(1).optional(),
89337
- sources: array(OverrideSourceSchema).min(1)
89338
- });
89339
- var SpanSourceDraftSchema = OverrideSourceSchema.extend({ mode: _enum([
89340
- "in-addition",
89341
- "confirm",
89342
- "replace"
89343
- ]).optional() });
89344
- discriminatedUnion("kind", [
89345
- ScalarOverrideDraftSchema,
89346
- KeyedArrayOverrideDraftSchema,
89347
- PulseOverrideDraftSchema,
89348
- object({
89349
- kind: literal("span"),
89350
- cap: string().min(1),
89351
- targetId: string().min(1),
89352
- timeoutMs: number().nonnegative(),
89353
- cooldownMs: number().nonnegative().optional(),
89354
- sources: array(SpanSourceDraftSchema)
89355
- })
89356
- ]);
89357
- object({
89358
- targetId: string().min(1),
89359
- configured: boolean(),
89360
- source: _enum(["native", "assigned"]),
89361
- state: _enum([
89362
- "active",
89363
- "stale",
89364
- "released",
89365
- "unavailable"
89366
- ]),
89367
- reason: string().nullable()
89368
- });
89369
89639
  var G711_SCALE_CORRECTION_DB = {
89370
89640
  PCMU: 20 * Math.log10(4),
89371
89641
  PCMA: 20 * Math.log10(8)
@@ -123377,6 +123647,7 @@ var MqttBrokerAddon = class extends BaseAddon {
123377
123647
  this.aclStore = MqttAclStore.of(records);
123378
123648
  this.instances = new RecordIntegrationInstances(records, mqttBrokersEntryKind, {
123379
123649
  description: {
123650
+ role: "services",
123380
123651
  instanceMode: "multiple",
123381
123652
  kind: "brokers",
123382
123653
  canTest: false