@camstack/addon-training 0.1.111 → 0.1.113

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (24) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{MotionZonesSettings-D4s4e-dn.mjs → MotionZonesSettings-Fo1pD1Rt.mjs} +2 -2
  3. package/dist/{PrivacyMaskSettings-BtdwrmHQ.mjs → PrivacyMaskSettings-CxJF-sR6.mjs} +2 -2
  4. package/dist/{SceneMonitorEditor-DOX6WQaZ.mjs → SceneMonitorEditor-BVjgmQ6j.mjs} +2 -2
  5. package/dist/_stub.js +6084 -6049
  6. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-wgqnvZam.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-Bya5bIs3.mjs} +3 -3
  7. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CMfDB80q.mjs +26 -0
  8. package/dist/{dist-BY4SxNbQ.mjs → dist-B7trMqj7.mjs} +927 -122
  9. package/dist/{dist-BsIozugK.js → dist-j2JtuQ21.js} +927 -122
  10. package/dist/{hostInit-COVVezu5.mjs → hostInit-7-7XYgjM.mjs} +2 -2
  11. package/dist/{icons-tE-y9KlM.mjs → icons-BAJBs2k2.mjs} +1124 -1153
  12. package/dist/index.js +1 -1
  13. package/dist/orchestrator/training.addon.js +1 -1
  14. package/dist/orchestrator/training.addon.mjs +1 -1
  15. package/dist/{player-overlays-C_B-yc3g.mjs → player-overlays-DVT_LAXt.mjs} +1 -1
  16. package/dist/providers/modal/training-modal.addon.js +1 -1
  17. package/dist/providers/modal/training-modal.addon.mjs +1 -1
  18. package/dist/remoteEntry.js +1 -1
  19. package/dist/{responsive-YANRyril.mjs → responsive-DhFXZ4Mn.mjs} +1 -1
  20. package/dist/{scene-monitor-copy-B0w92y1z.mjs → scene-monitor-copy-Busxg-ds.mjs} +1 -1
  21. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-Xpg6ATFx.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-DlmMJ8_m.mjs} +1 -1
  22. package/dist/{zoom-out-CWDOvUsr.mjs → zoom-out-Bc8fFbXe.mjs} +88 -59
  23. package/package.json +1 -1
  24. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-CmVZHQ-k.mjs +0 -26
@@ -5621,7 +5621,7 @@ var ZodIssueCode = {
5621
5621
  var ZodFirstPartyTypeKind;
5622
5622
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5623
5623
  //#endregion
5624
- //#region ../types/dist/sleep-63gM_1JR.mjs
5624
+ //#region ../types/dist/sleep-CeyhrcOk.mjs
5625
5625
  /**
5626
5626
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5627
5627
  * window to float samples (D455).
@@ -7688,6 +7688,7 @@ var BaseAddon = class {
7688
7688
  * a warning and falls back rather than crashing boot.
7689
7689
  */
7690
7690
  state(key, schema, fallback) {
7691
+ this._declaredStateKeys.add(key);
7691
7692
  return createDurableState({
7692
7693
  key,
7693
7694
  schema,
@@ -7703,6 +7704,96 @@ var BaseAddon = class {
7703
7704
  } })
7704
7705
  });
7705
7706
  }
7707
+ /** Durable-state keys this process registered through {@link state}. */
7708
+ _declaredStateKeys = /* @__PURE__ */ new Set();
7709
+ /**
7710
+ * Stale-data audit: every global-settings key this addon still declares —
7711
+ * constructor defaults, settings-schema fields (layout containers included),
7712
+ * durable-state keys and the root of each `secretStatePaths` entry. Answered
7713
+ * here, in the addon's own process, so the hub keeps no copy of it.
7714
+ */
7715
+ async getDeclaredSettingsKeys() {
7716
+ return { keys: [...this.declaredKeySet()].toSorted() };
7717
+ }
7718
+ declaredKeySet() {
7719
+ const keys = new Set(Object.keys(this.defaults));
7720
+ const schema = this.globalSettingsSchema();
7721
+ if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
7722
+ for (const key of this._declaredStateKeys) keys.add(key);
7723
+ for (const path of this.secretStatePaths) {
7724
+ const root = path.split(/[.[]/, 1)[0];
7725
+ if (root) keys.add(root);
7726
+ }
7727
+ return keys;
7728
+ }
7729
+ /**
7730
+ * Stale-data audit: remove stored global-settings keys the addon no longer
7731
+ * declares, and clear the keychain values their markers held — except a value
7732
+ * a surviving key still points at. A declared or absent key is refused.
7733
+ */
7734
+ async unsetStoredSettingsKeys(keys) {
7735
+ const settings = this._ctx?.settings;
7736
+ const raw = this.rawSettings();
7737
+ if (!settings || !raw) return {
7738
+ removed: [],
7739
+ refused: keys.map((key) => ({
7740
+ key,
7741
+ reason: "no settings store"
7742
+ })),
7743
+ secretsCleared: 0
7744
+ };
7745
+ let stored;
7746
+ try {
7747
+ stored = await raw.readAddonStore();
7748
+ } catch (error) {
7749
+ const reason = `store unreadable: ${error instanceof Error ? error.message : "unknown"}`;
7750
+ return {
7751
+ removed: [],
7752
+ refused: keys.map((key) => ({
7753
+ key,
7754
+ reason
7755
+ })),
7756
+ secretsCleared: 0
7757
+ };
7758
+ }
7759
+ const declared = this.declaredKeySet();
7760
+ const removed = [];
7761
+ const refused = [];
7762
+ for (const key of keys) {
7763
+ const base = key.split("@", 1)[0] ?? key;
7764
+ if (declared.has(base)) refused.push({
7765
+ key,
7766
+ reason: "still declared by the addon"
7767
+ });
7768
+ else if (!(key in stored)) refused.push({
7769
+ key,
7770
+ reason: "not stored"
7771
+ });
7772
+ else removed.push(key);
7773
+ }
7774
+ if (removed.length === 0) return {
7775
+ removed,
7776
+ refused,
7777
+ secretsCleared: 0
7778
+ };
7779
+ const kept = /* @__PURE__ */ new Set();
7780
+ const held = /* @__PURE__ */ new Set();
7781
+ for (const [key, value] of Object.entries(stored)) {
7782
+ const target = removed.includes(key) ? held : kept;
7783
+ for (const ref of collectSecretMarkerRefs(value)) target.add(ref);
7784
+ }
7785
+ const refsToClear = [...held].filter((ref) => !kept.has(ref));
7786
+ const patch = {};
7787
+ for (const key of removed) patch[key] = void 0;
7788
+ await settings.writeAddonStore(patch);
7789
+ const secretsCleared = refsToClear.length > 0 && this._ctx?.secrets ? await this._ctx.secrets.delete(refsToClear) : 0;
7790
+ await this.resolveConfig();
7791
+ return {
7792
+ removed,
7793
+ refused,
7794
+ secretsCleared
7795
+ };
7796
+ }
7706
7797
  /** Per-device variant of {@link state}, backed by the per-device store. */
7707
7798
  deviceState(deviceId, key, schema, fallback) {
7708
7799
  return createDurableState({
@@ -7973,6 +8064,21 @@ function dropPerNodeFields(fields) {
7973
8064
  }
7974
8065
  return kept;
7975
8066
  }
8067
+ function collectFieldKeys(fields) {
8068
+ const collected = [];
8069
+ for (const field of fields) {
8070
+ if (field.type === "group") {
8071
+ collected.push(...collectFieldKeys(field.fields));
8072
+ continue;
8073
+ }
8074
+ if (field.type === "sub-tabs") {
8075
+ for (const tab of field.tabs) collected.push(...collectFieldKeys(tab.fields));
8076
+ continue;
8077
+ }
8078
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8079
+ }
8080
+ return collected;
8081
+ }
7976
8082
  function collectPerNodeFieldKeys(fields) {
7977
8083
  const collected = [];
7978
8084
  for (const field of fields) {
@@ -10995,7 +11101,7 @@ object({
10995
11101
  params: record(string(), union([string(), number()])).optional()
10996
11102
  });
10997
11103
  //#endregion
10998
- //#region ../types/dist/err-msg-DWXWc0Di.mjs
11104
+ //#region ../types/dist/err-msg-BD5buxvT.mjs
10999
11105
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
11000
11106
  DeviceType["Camera"] = "camera";
11001
11107
  DeviceType["Hub"] = "hub";
@@ -11101,6 +11207,12 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
11101
11207
  DeviceType["Washer"] = "washer";
11102
11208
  /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
11103
11209
  DeviceType["Dryer"] = "dryer";
11210
+ /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
11211
+ * other radio-to-IP bridges). It carries the box's own facts (firmware,
11212
+ * address, connection state) and the actions the box really supports
11213
+ * (`reboot`, `device-admin-link`). Products are NOT its children — they are
11214
+ * adopted beside it — and its connection settings stay on the integration. */
11215
+ DeviceType["Gateway"] = "gateway";
11104
11216
  return DeviceType;
11105
11217
  }({});
11106
11218
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -11351,7 +11463,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11351
11463
  return DeviceRole;
11352
11464
  }({});
11353
11465
  //#endregion
11354
- //#region ../types/dist/composition-kSuOifuG.mjs
11466
+ //#region ../types/dist/composition-C7WdThmP.mjs
11355
11467
  /**
11356
11468
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11357
11469
  *
@@ -16358,8 +16470,172 @@ object({
16358
16470
  quantity: ReadingQuantitySchema,
16359
16471
  unit: string().min(1)
16360
16472
  });
16361
- /** How a leaf compares a device field to a value. Derived from the field's
16362
- * `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
16473
+ new Set([
16474
+ {
16475
+ id: "person",
16476
+ name: "Person"
16477
+ },
16478
+ {
16479
+ id: "vehicle",
16480
+ name: "Vehicle"
16481
+ },
16482
+ {
16483
+ id: "animal",
16484
+ name: "Animal"
16485
+ },
16486
+ {
16487
+ id: "package",
16488
+ name: "Package"
16489
+ }
16490
+ ].map((l) => l.id));
16491
+ var COCO_TO_MACRO = {
16492
+ mapping: {
16493
+ person: "person",
16494
+ bicycle: "vehicle",
16495
+ car: "vehicle",
16496
+ motorcycle: "vehicle",
16497
+ airplane: "vehicle",
16498
+ bus: "vehicle",
16499
+ train: "vehicle",
16500
+ truck: "vehicle",
16501
+ boat: "vehicle",
16502
+ bird: "animal",
16503
+ cat: "animal",
16504
+ dog: "animal",
16505
+ horse: "animal",
16506
+ sheep: "animal",
16507
+ cow: "animal",
16508
+ elephant: "animal",
16509
+ bear: "animal",
16510
+ zebra: "animal",
16511
+ giraffe: "animal",
16512
+ suitcase: "package",
16513
+ backpack: "package",
16514
+ handbag: "package"
16515
+ },
16516
+ preserveOriginal: false
16517
+ };
16518
+ /**
16519
+ * WHAT a detection-shaped leaf is about: one MACRO class (person, vehicle,
16520
+ * animal, package) plus an OPTIONAL label that narrows it.
16521
+ *
16522
+ * The taxonomy is `COCO_TO_MACRO` (and `CLASS_MAP_MACRO_TARGETS`) — never a
16523
+ * second list. The label that fits depends on the macro class:
16524
+ *
16525
+ * - `animal` → a sub-class (`dog`, `cat`, …);
16526
+ * - `person` → a known face (an identity of the `face-gallery`);
16527
+ * - `vehicle` → a plate (its text) or a sub-class (`car`, `truck`, …);
16528
+ * - `package` → a sub-class.
16529
+ *
16530
+ * `other` exists only so a class the taxonomy does not know (a custom model's
16531
+ * `forklift`) survives the conversion of a stored recipe without widening.
16532
+ *
16533
+ * A label is decided LATE (D683): face recognition and LPR land after the
16534
+ * detection starts. The track-lifecycle `update` event carries the newly
16535
+ * resolved `label` / `plateText`, which is why a leaf with a label fires when
16536
+ * the subject GETS it — not at the first detection.
16537
+ */
16538
+ var SubClassLabelSchema = object({
16539
+ kind: literal("sub-class"),
16540
+ className: string().min(1)
16541
+ });
16542
+ var IdentityLabelSchema = object({
16543
+ kind: literal("identity"),
16544
+ /** The `face-gallery` identity id (stable across a rename). */
16545
+ identityId: string().min(1),
16546
+ /** The identity's name — what a track-lifecycle event carries as `label`. */
16547
+ name: string().min(1)
16548
+ });
16549
+ var PlateLabelSchema = object({
16550
+ kind: literal("plate"),
16551
+ text: string().min(1)
16552
+ });
16553
+ function macroOf$1(className) {
16554
+ return COCO_TO_MACRO.mapping[className.toLowerCase()] ?? className.toLowerCase();
16555
+ }
16556
+ /** A sub-class label must belong to the macro it is under. */
16557
+ function subClassBelongs(macro, label) {
16558
+ return label === void 0 || macroOf$1(label.className) === macro;
16559
+ }
16560
+ var subClassOf = (macro) => SubClassLabelSchema.refine((l) => subClassBelongs(macro, l), { message: `that class is not a kind of ${macro} in the detection taxonomy` });
16561
+ var DetectionSubjectSchema = discriminatedUnion("macro", [
16562
+ object({
16563
+ macro: literal("animal"),
16564
+ label: subClassOf("animal").optional()
16565
+ }),
16566
+ object({
16567
+ macro: literal("person"),
16568
+ label: IdentityLabelSchema.optional()
16569
+ }),
16570
+ object({
16571
+ macro: literal("vehicle"),
16572
+ label: discriminatedUnion("kind", [SubClassLabelSchema.refine((l) => subClassBelongs("vehicle", l), { message: "that class is not a kind of vehicle in the detection taxonomy" }), PlateLabelSchema]).optional()
16573
+ }),
16574
+ object({
16575
+ macro: literal("package"),
16576
+ label: subClassOf("package").optional()
16577
+ }),
16578
+ object({
16579
+ macro: literal("other"),
16580
+ className: string().min(1)
16581
+ })
16582
+ ]);
16583
+ /**
16584
+ * The subject of a PARKED object. The stationary registry's event carries a
16585
+ * class and a zone, no face and no plate — an identity or plate label there
16586
+ * could never match, so the schema refuses it instead of letting it sit dead.
16587
+ */
16588
+ var StationarySubjectSchema = DetectionSubjectSchema.refine((s) => s.macro === "other" || s.label === void 0 || s.label.kind === "sub-class", { message: "a parked object carries no face or plate — only a class can narrow it" });
16589
+ /**
16590
+ * A stored class string as a subject: a macro name stays a macro; a raw class
16591
+ * the taxonomy files under a macro becomes that macro plus the sub-class label
16592
+ * (`dog` → animal / dog); anything else is `other`, kept verbatim.
16593
+ */
16594
+ function subjectFromClassName(className) {
16595
+ const name = className.trim().toLowerCase();
16596
+ const macro = COCO_TO_MACRO.mapping[name];
16597
+ if (new Set(Object.values(COCO_TO_MACRO.mapping)).has(name)) return macroOnly(name);
16598
+ if (macro === void 0) return {
16599
+ macro: "other",
16600
+ className
16601
+ };
16602
+ const label = {
16603
+ kind: "sub-class",
16604
+ className: name
16605
+ };
16606
+ switch (macro) {
16607
+ case "animal": return {
16608
+ macro: "animal",
16609
+ label
16610
+ };
16611
+ case "vehicle": return {
16612
+ macro: "vehicle",
16613
+ label
16614
+ };
16615
+ case "package": return {
16616
+ macro: "package",
16617
+ label
16618
+ };
16619
+ default: return {
16620
+ macro: "other",
16621
+ className
16622
+ };
16623
+ }
16624
+ }
16625
+ function macroOnly(macro) {
16626
+ switch (macro) {
16627
+ case "animal": return { macro: "animal" };
16628
+ case "person": return { macro: "person" };
16629
+ case "vehicle": return { macro: "vehicle" };
16630
+ case "package": return { macro: "package" };
16631
+ default: return {
16632
+ macro: "other",
16633
+ className: macro
16634
+ };
16635
+ }
16636
+ }
16637
+ /** How a state leaf compares a device field to a value. Derived from the
16638
+ * field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
16363
16639
  var AutomationConditionOperatorSchema = _enum([
16364
16640
  "eq",
16365
16641
  "ne",
@@ -16370,12 +16646,76 @@ var AutomationConditionOperatorSchema = _enum([
16370
16646
  "contains",
16371
16647
  "in"
16372
16648
  ]);
16373
- var AutomationConditionLeafSchema = object({
16374
- kind: literal("condition"),
16649
+ /**
16650
+ * D8 compliance, and it is the reason a "becomes" leaf is not merely an event
16651
+ * subscription: it evaluates against the **state mirror**, which is reconciled,
16652
+ * and an event only WAKES the evaluation. A dropped event therefore DELAYS a
16653
+ * fire; it does not lose it.
16654
+ */
16655
+ var DeviceBecomesLeafSchema = object({
16656
+ kind: literal("device-becomes"),
16657
+ deviceId: number().int().nonnegative(),
16658
+ cap: string().min(1),
16659
+ fieldPath: string().min(1),
16660
+ /** Fire when the field takes this value. Omit to fire on any change. */
16661
+ becomes: union([
16662
+ string(),
16663
+ number(),
16664
+ boolean()
16665
+ ]).optional(),
16666
+ /** Only on a CHANGE of value, not on every re-report. */
16667
+ edge: boolean().optional(),
16668
+ /** The new value must hold this long before it counts. */
16669
+ forMs: number().int().min(0).max(864e5).optional(),
16670
+ /** Collapse a burst into one occurrence. */
16671
+ debounceMs: number().int().min(0).max(6e5).optional()
16672
+ });
16673
+ var DeviceEventLeafSchema = object({
16674
+ kind: literal("device-event"),
16675
+ /** An `EventCategory` value. */
16676
+ category: string().min(1),
16677
+ deviceId: number().int().nonnegative().optional()
16678
+ });
16679
+ var ScheduleLeafSchema = object({
16680
+ kind: literal("schedule"),
16681
+ cron: string().min(1).max(120)
16682
+ });
16683
+ var ManualLeafSchema = object({ kind: literal("manual") });
16684
+ var IntervalLeafSchema = object({
16685
+ kind: literal("interval"),
16686
+ minutes: number().int().min(1).max(1440)
16687
+ });
16688
+ var DetectionLeafSchema = object({
16689
+ kind: literal("detection"),
16690
+ /** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
16691
+ subjects: array(DetectionSubjectSchema).min(1),
16692
+ deviceIds: array(number().int().nonnegative()).min(1),
16693
+ /** The track must have visited at least one of these zones. */
16694
+ zones: array(string().min(1)).min(1).optional(),
16695
+ /** Best-so-far confidence the track must reach. */
16696
+ minScore: number().min(0).max(1).optional()
16697
+ });
16698
+ var StationaryLeafSchema = object({
16699
+ kind: literal("stationary"),
16700
+ deviceIds: array(number().int().nonnegative()).min(1),
16701
+ subjects: array(StationarySubjectSchema).min(1).optional(),
16702
+ zones: array(string().min(1)).min(1).optional(),
16703
+ /** `appeared`: it became parked. `departed`: the registry retired it. */
16704
+ phase: _enum(["appeared", "departed"])
16705
+ });
16706
+ /** "is": one device field compared to one value, NOW. */
16707
+ var DeviceIsLeafSchema = object({
16708
+ kind: literal("device-is"),
16375
16709
  deviceId: number().int().nonnegative(),
16376
16710
  cap: string().min(1),
16377
16711
  fieldPath: string().min(1),
16378
16712
  operator: AutomationConditionOperatorSchema,
16713
+ /**
16714
+ * The value. A number on a quantity field is stored in the field's base unit
16715
+ * (D944); a quantity value is the WRITE form, which the save boundary
16716
+ * replaces with the converted number and `unit`. A leaf that still carries
16717
+ * one is never evaluated (it fails).
16718
+ */
16379
16719
  value: union([
16380
16720
  string(),
16381
16721
  number(),
@@ -16383,24 +16723,26 @@ var AutomationConditionLeafSchema = object({
16383
16723
  array(union([string(), number()])),
16384
16724
  QuantityValueSchema
16385
16725
  ]),
16726
+ /** The unit the person picked for `value` (display only; `value` is in the base unit). */
16386
16727
  unit: string().min(1).optional()
16387
16728
  });
16388
- var AutomationConditionStationaryPresentSchema = object({
16729
+ /**
16730
+ * True when the camera currently has a PARKED object matching `subjects` in at
16731
+ * least one of `zones`. Reads the zone-analytics snapshot through the same
16732
+ * mirror as every other state leaf, so a dropped event never decides it (D49).
16733
+ */
16734
+ var StationaryPresentLeafSchema = object({
16389
16735
  kind: literal("stationary-present"),
16390
16736
  deviceId: number().int().nonnegative(),
16391
- /** Class names; a macro class matches every raw class that maps to it. */
16392
- classes: array(string().min(1)).min(1).optional(),
16393
- /** The parked object must be in at least one of these zones. */
16737
+ subjects: array(StationarySubjectSchema).min(1).optional(),
16394
16738
  zones: array(string().min(1)).min(1).optional()
16395
16739
  });
16396
16740
  /**
16397
- * The expression leaf, declared as a plain object rather than an intersection
16398
- * with {@link ExpressionSourceSchema}: a discriminated union has to be able to
16399
- * read `kind` off each option, and an intersection hides it. The author-time
16400
- * validation is the SAME function `ExpressionSourceSchema` runs, so the two
16401
- * cannot drift — an expression that one accepts, the other accepts.
16741
+ * The escape hatch: the bounded expression engine over named device bindings.
16742
+ * Declared as a plain object so a discriminated union can read `kind`; the
16743
+ * author-time validation is the SAME function `ExpressionSourceSchema` runs.
16402
16744
  */
16403
- var AutomationConditionExpressionSchema = object({
16745
+ var ExpressionLeafSchema = object({
16404
16746
  kind: literal("expression"),
16405
16747
  expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
16406
16748
  bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
@@ -16412,55 +16754,62 @@ var AutomationConditionExpressionSchema = object({
16412
16754
  path: ["expr"]
16413
16755
  });
16414
16756
  });
16415
- var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
16757
+ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
16416
16758
  object({
16417
16759
  kind: literal("all"),
16418
- children: array(AutomationConditionSchema)
16760
+ children: array(AutomationWhenSchema)
16419
16761
  }),
16420
16762
  object({
16421
16763
  kind: literal("any"),
16422
- children: array(AutomationConditionSchema)
16764
+ children: array(AutomationWhenSchema)
16423
16765
  }),
16424
16766
  object({
16425
16767
  kind: literal("not"),
16426
- child: AutomationConditionSchema
16768
+ child: AutomationWhenSchema
16427
16769
  }),
16428
- AutomationConditionLeafSchema,
16429
- AutomationConditionExpressionSchema,
16430
- AutomationConditionStationaryPresentSchema
16770
+ DeviceBecomesLeafSchema,
16771
+ DeviceEventLeafSchema,
16772
+ ScheduleLeafSchema,
16773
+ ManualLeafSchema,
16774
+ IntervalLeafSchema,
16775
+ DetectionLeafSchema,
16776
+ StationaryLeafSchema,
16777
+ DeviceIsLeafSchema,
16778
+ StationaryPresentLeafSchema,
16779
+ ExpressionLeafSchema
16431
16780
  ]));
16432
16781
  /**
16433
- * What starts a run.
16434
- *
16435
- * D8 compliance, and it is the reason `device-state` is not merely an event
16436
- * subscription: the trigger evaluates against the **state mirror**, which is
16437
- * reconciled, and an event only WAKES the evaluation. A dropped event therefore
16438
- * DELAYS a trigger; it does not lose it. `schedule` uses `croner` — the one
16439
- * already in the repo — because `setInterval(24h)` drifts and "at 23:30" does
16440
- * not.
16441
- */
16442
- var AutomationTriggerSchema = discriminatedUnion("kind", [
16782
+ * The recipe shape before the single `when` tree: a flat list of triggers
16783
+ * (OR'd, each an instant) and an optional condition tree. Stored recipes —
16784
+ * the `AUTOMATION_RECIPE` literal of every block saved so far, and whatever an
16785
+ * older client still posts — are in this shape, so it stays READABLE here, and
16786
+ * {@link convertLegacyRecipe} turns it into the new one, losslessly and one
16787
+ * way. Nothing writes this shape any more.
16788
+ *
16789
+ * `{ triggers: [t1…tn], conditions?: C }`
16790
+ * → `when = all(any(t1…tn), C)`, or `any(t1…tn)` without `C`;
16791
+ * a single trigger is not wrapped in `any`.
16792
+ * a `condition` leaf → a `device-is` leaf (the "is" verb);
16793
+ * a `device-state` trigger → a `device-becomes` leaf (the "becomes" verb);
16794
+ * `classes: ['dog']` → the macro `animal` with the label `dog`.
16795
+ */
16796
+ var LegacyTriggerSchema = discriminatedUnion("kind", [
16443
16797
  object({
16444
16798
  kind: literal("device-state"),
16445
16799
  deviceId: number().int().nonnegative(),
16446
16800
  cap: string().min(1),
16447
16801
  fieldPath: string().min(1),
16448
- /** Fire when the field takes this value. Omit to fire on any change. */
16449
16802
  becomes: union([
16450
16803
  string(),
16451
16804
  number(),
16452
16805
  boolean()
16453
16806
  ]).optional(),
16454
- /** Only on a CHANGE of value, not on every re-report. */
16455
16807
  edge: boolean().optional(),
16456
- /** The condition must hold this long before the run starts. */
16457
16808
  forMs: number().int().min(0).max(864e5).optional(),
16458
- /** Collapse a burst into one run. */
16459
16809
  debounceMs: number().int().min(0).max(6e5).optional()
16460
16810
  }),
16461
16811
  object({
16462
16812
  kind: literal("device-event"),
16463
- /** An `EventCategory` value. */
16464
16813
  category: string().min(1),
16465
16814
  deviceId: number().int().nonnegative().optional()
16466
16815
  }),
@@ -16471,25 +16820,16 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
16471
16820
  object({ kind: literal("manual") }),
16472
16821
  object({
16473
16822
  kind: literal("detection"),
16474
- /** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
16475
- * matches every raw class that maps to it; any other name matches
16476
- * exactly. */
16477
16823
  classes: array(string().min(1)).min(1),
16478
16824
  deviceIds: array(number().int().nonnegative()).min(1),
16479
- /** The track must have visited at least one of these zones. */
16480
16825
  zones: array(string().min(1)).min(1).optional(),
16481
- /** Best-so-far confidence the track must reach. */
16482
16826
  minScore: number().min(0).max(1).optional()
16483
16827
  }),
16484
16828
  object({
16485
16829
  kind: literal("stationary"),
16486
16830
  deviceIds: array(number().int().nonnegative()).min(1),
16487
- /** Class names; a macro class matches like the detection trigger. */
16488
16831
  classes: array(string().min(1)).min(1).optional(),
16489
- /** The parked object must be in at least one of these zones. */
16490
16832
  zones: array(string().min(1)).min(1).optional(),
16491
- /** `appeared`: it became parked. `departed`: the registry retired it.
16492
- * The vocabulary of `PipelineAnalyticsStationaryChanged`. */
16493
16833
  phase: _enum(["appeared", "departed"])
16494
16834
  }),
16495
16835
  object({
@@ -16497,6 +16837,143 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
16497
16837
  minutes: number().int().min(1).max(1440)
16498
16838
  })
16499
16839
  ]);
16840
+ var LegacyConditionLeafSchema = object({
16841
+ kind: literal("condition"),
16842
+ deviceId: number().int().nonnegative(),
16843
+ cap: string().min(1),
16844
+ fieldPath: string().min(1),
16845
+ operator: AutomationConditionOperatorSchema,
16846
+ value: union([
16847
+ string(),
16848
+ number(),
16849
+ boolean(),
16850
+ array(union([string(), number()])),
16851
+ QuantityValueSchema
16852
+ ]),
16853
+ unit: string().min(1).optional()
16854
+ });
16855
+ var LegacyStationaryPresentSchema = object({
16856
+ kind: literal("stationary-present"),
16857
+ deviceId: number().int().nonnegative(),
16858
+ classes: array(string().min(1)).min(1).optional(),
16859
+ zones: array(string().min(1)).min(1).optional()
16860
+ });
16861
+ var LegacyExpressionSchema = object({
16862
+ kind: literal("expression"),
16863
+ expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
16864
+ bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
16865
+ }).superRefine((src, ctx) => {
16866
+ const err = validateExpressionSource(src);
16867
+ if (err !== null) ctx.addIssue({
16868
+ code: "custom",
16869
+ message: err,
16870
+ path: ["expr"]
16871
+ });
16872
+ });
16873
+ var LegacyConditionSchema = lazy(() => discriminatedUnion("kind", [
16874
+ object({
16875
+ kind: literal("all"),
16876
+ children: array(LegacyConditionSchema)
16877
+ }),
16878
+ object({
16879
+ kind: literal("any"),
16880
+ children: array(LegacyConditionSchema)
16881
+ }),
16882
+ object({
16883
+ kind: literal("not"),
16884
+ child: LegacyConditionSchema
16885
+ }),
16886
+ LegacyConditionLeafSchema,
16887
+ LegacyExpressionSchema,
16888
+ LegacyStationaryPresentSchema
16889
+ ]));
16890
+ function convertTrigger(trigger) {
16891
+ switch (trigger.kind) {
16892
+ case "device-state": return {
16893
+ ...trigger,
16894
+ kind: "device-becomes"
16895
+ };
16896
+ case "detection": {
16897
+ const { classes, ...rest } = trigger;
16898
+ return {
16899
+ ...rest,
16900
+ subjects: classes.map(subjectFromClassName)
16901
+ };
16902
+ }
16903
+ case "stationary": {
16904
+ const { classes, ...rest } = trigger;
16905
+ return {
16906
+ ...rest,
16907
+ ...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
16908
+ };
16909
+ }
16910
+ default: return trigger;
16911
+ }
16912
+ }
16913
+ /** An old condition tree as a `when` subtree: `condition` → "is", classes → subjects. */
16914
+ function convertLegacyCondition(condition) {
16915
+ switch (condition.kind) {
16916
+ case "condition": return {
16917
+ ...condition,
16918
+ kind: "device-is"
16919
+ };
16920
+ case "stationary-present": {
16921
+ const { classes, ...rest } = condition;
16922
+ return {
16923
+ ...rest,
16924
+ ...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
16925
+ };
16926
+ }
16927
+ case "expression": return condition;
16928
+ case "not": return {
16929
+ kind: "not",
16930
+ child: convertLegacyCondition(condition.child)
16931
+ };
16932
+ case "all":
16933
+ case "any": return {
16934
+ kind: condition.kind,
16935
+ children: condition.children.map(convertLegacyCondition)
16936
+ };
16937
+ }
16938
+ }
16939
+ /** `all(any(t1…tn), C)`, or `any(t1…tn)`; a single trigger is not wrapped in `any`. */
16940
+ function convertLegacyWhen(half) {
16941
+ const leaves = half.triggers.map(convertTrigger);
16942
+ const first = leaves[0];
16943
+ const triggers = leaves.length === 1 && first !== void 0 ? first : {
16944
+ kind: "any",
16945
+ children: leaves
16946
+ };
16947
+ if (half.conditions === void 0) return triggers;
16948
+ return {
16949
+ kind: "all",
16950
+ children: [triggers, convertLegacyCondition(half.conditions)]
16951
+ };
16952
+ }
16953
+ /**
16954
+ * The automation RECIPE — what a visually-authored automation is, before it
16955
+ * becomes code.
16956
+ *
16957
+ * This is the data half of `docs/design/2026-08-08-automation-scripts-custom-devices.md`
16958
+ * Phase 6. A recipe is not an engine and never evaluates anything: it is a
16959
+ * declarative description that {@link generateAutomationBlock} compiles to the
16960
+ * TypeScript of an ordinary core block. There is therefore no second rule
16961
+ * engine, no second store and no second runtime — the block runner is the
16962
+ * runtime, `core_blocks` is the store (§2.1).
16963
+ *
16964
+ * Two parts:
16965
+ *
16966
+ * - **`when`** — ONE tree (`./when.ts`) of all/any/not over event leaves
16967
+ * (instants) and state leaves (durations). It replaces the flat trigger list
16968
+ * plus the separate condition tree; the firing rule is in that file and in
16969
+ * ADR "one tree".
16970
+ * - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
16971
+ * fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
16972
+ * this had to diverge from "verbatim", and why.
16973
+ *
16974
+ * A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
16975
+ * {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
16976
+ */
16500
16977
  /**
16501
16978
  * One action step.
16502
16979
  *
@@ -16599,69 +17076,49 @@ var AutomationRunModeSchema = discriminatedUnion("mode", [
16599
17076
  minutes: number().int().min(1).max(1440)
16600
17077
  })
16601
17078
  ]);
16602
- var AutomationRecipeSchema = object({
16603
- triggers: array(AutomationTriggerSchema),
16604
- conditions: AutomationConditionSchema.optional(),
17079
+ var CurrentAutomationRecipeSchema = object({
17080
+ when: AutomationWhenSchema,
16605
17081
  actions: array(AutomationActionSchema),
16606
17082
  runMode: AutomationRunModeSchema.optional(),
16607
17083
  /** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
16608
17084
  keepTraces: number().int().min(1).max(100).optional()
16609
17085
  });
16610
- new Set([
16611
- {
16612
- id: "person",
16613
- name: "Person"
16614
- },
16615
- {
16616
- id: "vehicle",
16617
- name: "Vehicle"
16618
- },
16619
- {
16620
- id: "animal",
16621
- name: "Animal"
16622
- },
16623
- {
16624
- id: "package",
16625
- name: "Package"
16626
- }
16627
- ].map((l) => l.id));
16628
- var COCO_TO_MACRO = {
16629
- mapping: {
16630
- person: "person",
16631
- bicycle: "vehicle",
16632
- car: "vehicle",
16633
- motorcycle: "vehicle",
16634
- airplane: "vehicle",
16635
- bus: "vehicle",
16636
- train: "vehicle",
16637
- truck: "vehicle",
16638
- boat: "vehicle",
16639
- bird: "animal",
16640
- cat: "animal",
16641
- dog: "animal",
16642
- horse: "animal",
16643
- sheep: "animal",
16644
- cow: "animal",
16645
- elephant: "animal",
16646
- bear: "animal",
16647
- zebra: "animal",
16648
- giraffe: "animal",
16649
- suitcase: "package",
16650
- backpack: "package",
16651
- handbag: "package"
16652
- },
16653
- preserveOriginal: false
16654
- };
17086
+ /** A recipe as it was stored before the single tree. Read-only: nothing writes this. */
17087
+ var LegacyAutomationRecipeSchema = object({
17088
+ triggers: array(LegacyTriggerSchema),
17089
+ conditions: LegacyConditionSchema.optional(),
17090
+ actions: array(AutomationActionSchema),
17091
+ runMode: AutomationRunModeSchema.optional(),
17092
+ keepTraces: number().int().min(1).max(100).optional()
17093
+ });
17094
+ /** The old shape as the new one, lossless. */
17095
+ function convertLegacyRecipe(legacy) {
17096
+ const { triggers, conditions, ...rest } = legacy;
17097
+ return {
17098
+ when: convertLegacyWhen({
17099
+ triggers,
17100
+ conditions
17101
+ }),
17102
+ ...rest
17103
+ };
17104
+ }
17105
+ /**
17106
+ * Where recipes are PARSED (the core-blocks cap, the MCP tool, the literal a
17107
+ * saved block carries): the new shape as it is, or the old shape converted to
17108
+ * it. The output type is always the new one.
17109
+ */
17110
+ var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
16655
17111
  new Set(Object.values(COCO_TO_MACRO.mapping));
16656
17112
  /**
16657
17113
  * Run traces — what an automation (or, later, an Action run) DID, node by node,
16658
17114
  * so the editor can paint every block green or red with its reason.
16659
17115
  *
16660
- * A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
16661
- * every condition node (containers and leaves, including the ones a
16662
- * short-circuit never reached), the run-mode verdict and every action step —
16663
- * each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
16664
- * for an Action run). Skipped
17116
+ * A trace is data, not a log: it covers the WHOLE tree — the event leaf that
17117
+ * fired, every node of the `when` tree (containers and leaves, including the
17118
+ * ones a short-circuit never reached), the run-mode verdict and every action
17119
+ * step — each addressed by a stable path (`when.0.1`, `actions.2`, or `steps.N`
17120
+ * for an Action run). Traces written before the single tree carry
17121
+ * `conditions.*` paths and still parse. Skipped
16665
17122
  * evaluations are traced too, and identical consecutive skips collapse into a
16666
17123
  * repeat count so a trigger that fails every 5 minutes cannot flush real runs.
16667
17124
  *
@@ -16693,6 +17150,8 @@ var TraceNodeOutcomeSchema = _enum([
16693
17150
  ]);
16694
17151
  var AutomationTriggerTraceSchema = object({
16695
17152
  kind: string().min(1),
17153
+ /** The event leaf that fired (`when`, `when.0.1`); absent on traces written before the single tree. */
17154
+ path: string().min(1).optional(),
16696
17155
  outcome: _enum(["pass", "fail"]),
16697
17156
  /** Why an event was rejected ("class person not in [dog]") or what fired. */
16698
17157
  reason: string(),
@@ -16700,15 +17159,23 @@ var AutomationTriggerTraceSchema = object({
16700
17159
  vars: record(string(), TraceValueSchema)
16701
17160
  });
16702
17161
  var AutomationConditionTraceNodeSchema = object({
16703
- /** `conditions` for the root, `conditions.<i>` for its i-th child, … */
17162
+ /** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
16704
17163
  path: string().min(1),
16705
17164
  kind: _enum([
16706
17165
  "all",
16707
17166
  "any",
16708
17167
  "not",
16709
17168
  "condition",
17169
+ "device-is",
16710
17170
  "expression",
16711
- "stationary-present"
17171
+ "stationary-present",
17172
+ "device-becomes",
17173
+ "device-event",
17174
+ "schedule",
17175
+ "interval",
17176
+ "manual",
17177
+ "detection",
17178
+ "stationary"
16712
17179
  ]),
16713
17180
  outcome: TraceNodeOutcomeSchema,
16714
17181
  reason: string(),
@@ -16742,6 +17209,12 @@ var AutomationStepTraceSchema = object({
16742
17209
  deviceId: number().int().optional(),
16743
17210
  startedAt: number().optional(),
16744
17211
  endedAt: number().optional(),
17212
+ /**
17213
+ * The parameters the step SENT, after `{{vars.x}}` substitution — what the
17214
+ * device was actually asked. Bounded like trigger `vars`; a secret parameter
17215
+ * is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
17216
+ */
17217
+ args: record(string(), TraceValueSchema).optional(),
16745
17218
  outcome: _enum([
16746
17219
  "ok",
16747
17220
  "error",
@@ -17491,6 +17964,23 @@ method(object({
17491
17964
  addonId: string(),
17492
17965
  nodeId: string().optional()
17493
17966
  }), SettingsSchemaWithValuesSchema.nullable()), method(object({
17967
+ addonId: string(),
17968
+ nodeId: string().optional()
17969
+ }), object({ keys: array(string()) }).nullable(), { auth: "admin" }), method(object({
17970
+ addonId: string(),
17971
+ nodeId: string().optional(),
17972
+ keys: array(string())
17973
+ }), object({
17974
+ removed: array(string()),
17975
+ refused: array(object({
17976
+ key: string(),
17977
+ reason: string()
17978
+ })),
17979
+ secretsCleared: number()
17980
+ }), {
17981
+ kind: "mutation",
17982
+ auth: "admin"
17983
+ }), method(object({
17494
17984
  addonId: string(),
17495
17985
  deviceId: number(),
17496
17986
  nodeId: string().optional()
@@ -18926,8 +19416,21 @@ var BrokerProviderInfoSchema = object({
18926
19416
  * (`{ sessionId }`) drops the session. The dialog that edits a broker
18927
19417
  * offers it as the default path; the fields the session fills are
18928
19418
  * marked `filledBySignIn` in the kind's settings schema.
19419
+ *
19420
+ * `methods` lists the ways the session can sign in, recommended first:
19421
+ * `'device-code'` adds `POST {basePath}/code/start` (body: the form's
19422
+ * settings) answering `{ sessionId, userCode, verificationUrl,
19423
+ * expiresAt }` — the operator types `userCode` at `verificationUrl` on
19424
+ * any device, and the same `status` / `commit` / `cancel` routes follow
19425
+ * (status then carries `method`, `userCode`, `verificationUrl`,
19426
+ * `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
19427
+ * `'proxy'` is the `{basePath}/start` flow above. Absent means
19428
+ * `['proxy']`.
18929
19429
  */
18930
- signIn: object({ basePath: string().min(1) }).optional()
19430
+ signIn: object({
19431
+ basePath: string().min(1),
19432
+ methods: array(_enum(["device-code", "proxy"])).optional()
19433
+ }).optional()
18931
19434
  }))
18932
19435
  });
18933
19436
  var ListInputSchema = object({
@@ -21401,6 +21904,8 @@ var StorageKindSchema = object({
21401
21904
  "orphaned",
21402
21905
  "dropped"
21403
21906
  ]),
21907
+ /** An owner declared this kind since the engine started. */
21908
+ declaredThisBoot: boolean(),
21404
21909
  /** `null` when the caller asked for no counts (`counts: false`). */
21405
21910
  rows: number().int().nonnegative().nullable(),
21406
21911
  /** Rows x the average size of a sample; null for an empty kind. */
@@ -21412,6 +21917,51 @@ var StorageKindSchema = object({
21412
21917
  /** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
21413
21918
  declaration: RecordKindDeclarationSchema
21414
21919
  });
21920
+ _enum([
21921
+ "orphaned",
21922
+ "stale",
21923
+ "unknown"
21924
+ ]);
21925
+ /** A record kind nobody declared since its owner started, or whose owner is gone. */
21926
+ var StaleKindFindingSchema = object({
21927
+ owner: string(),
21928
+ kind: string(),
21929
+ /** `unknown`: the owner could not be judged (not running, or its state unreadable) - never dropped. */
21930
+ verdict: _enum(["orphaned", "unknown"]),
21931
+ reason: string(),
21932
+ rows: number().int().nonnegative().nullable(),
21933
+ /** local-auth's tables are never dropped by this audit. */
21934
+ protected: boolean()
21935
+ });
21936
+ /** A stored global-settings key the running addon no longer declares. */
21937
+ var StaleSettingsKeyFindingSchema = object({
21938
+ addonId: string(),
21939
+ /** The stored key as written, a per-node `<key>@<nodeId>` suffix included. */
21940
+ key: string(),
21941
+ /**
21942
+ * Keychain refs whose ONLY marker sits under this key: in use by stale data,
21943
+ * not orphans yet. Removing the key releases them.
21944
+ */
21945
+ secretRefs: array(string())
21946
+ });
21947
+ /** An addon the audit could not judge, with the reason. Never reported as stale. */
21948
+ var StaleUnknownSchema = object({
21949
+ subject: string(),
21950
+ reason: string()
21951
+ });
21952
+ var StaleDataReportSchema = object({
21953
+ /** Epoch ms of the last finished scan; `null` before the first. */
21954
+ scannedAt: number().nullable(),
21955
+ scanning: boolean(),
21956
+ trigger: _enum([
21957
+ "boot",
21958
+ "interval",
21959
+ "manual"
21960
+ ]).nullable(),
21961
+ kinds: array(StaleKindFindingSchema),
21962
+ settingsKeys: array(StaleSettingsKeyFindingSchema),
21963
+ unknown: array(StaleUnknownSchema)
21964
+ });
21415
21965
  method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
21416
21966
  owner: string().min(1).optional(),
21417
21967
  /** `false` skips the per-kind row count (a catalog read only). Default true. */
@@ -21419,7 +21969,30 @@ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: arr
21419
21969
  }), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
21420
21970
  owner: string().min(1),
21421
21971
  kind: RecordKindNameSchema
21422
- }), RecordQueryResultSchema, { auth: "admin" });
21972
+ }), RecordQueryResultSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, {
21973
+ kind: "mutation",
21974
+ auth: "admin"
21975
+ }), method(object({
21976
+ owner: string().min(1),
21977
+ kind: RecordKindNameSchema
21978
+ }), object({
21979
+ deleted: number().int().nonnegative(),
21980
+ secretsReleased: number().int().nonnegative()
21981
+ }), {
21982
+ kind: "mutation",
21983
+ auth: "admin",
21984
+ caller: "required"
21985
+ }), method(object({
21986
+ addonId: string().min(1),
21987
+ key: string().min(1)
21988
+ }), object({
21989
+ removed: boolean(),
21990
+ secretsCleared: number().int().nonnegative()
21991
+ }), {
21992
+ kind: "mutation",
21993
+ auth: "admin",
21994
+ caller: "required"
21995
+ });
21423
21996
  /**
21424
21997
  * record-entries - the ONE admin surface for every list of user-created entries
21425
21998
  * (D881): notification targets, brokers, LLM profiles, anything an addon
@@ -21558,6 +22131,12 @@ method(object({
21558
22131
  }), method(object({ deviceId: number().int() }), object({ refs: array(string()).readonly() }), {
21559
22132
  kind: "mutation",
21560
22133
  auth: "admin"
22134
+ }), method(object({
22135
+ refs: RefListSchema,
22136
+ onBehalfOf: string().min(1)
22137
+ }), object({ released: number().int() }), {
22138
+ kind: "mutation",
22139
+ auth: "admin"
21561
22140
  }), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
21562
22141
  /**
21563
22142
  * device-history — the ONE mechanism for numeric history (D850).
@@ -22347,7 +22926,9 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
22347
22926
  /** Upstream-system identity + rendering hints for the candidate. */
22348
22927
  sourceInfo: SourceInfoSchema.optional(),
22349
22928
  /** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
22350
- children: array(DiscoveredChildDeviceSchema).readonly().optional()
22929
+ children: array(DiscoveredChildDeviceSchema).readonly().optional(),
22930
+ /** `childNativeId` of the gateway candidate in the same list this device sits behind. */
22931
+ parentStableId: string().optional()
22351
22932
  }));
22352
22933
  /**
22353
22934
  * Status block — populated by the integration after each
@@ -42662,6 +43243,79 @@ object({
42662
43243
  metadata: record(string(), unknown()).optional()
42663
43244
  });
42664
43245
  /**
43246
+ * HOW a provider can be found on the network. The provider only DECLARES the
43247
+ * mode; the `local-network` scan engine runs it once for every provider that
43248
+ * asked (one multicast browse, one host sweep) and hands the raw hits back to
43249
+ * `classifyDiscoveryHits`. A provider never opens a discovery socket itself,
43250
+ * except through a `custom` mode.
43251
+ */
43252
+ var DiscoveryModeSchema = discriminatedUnion("kind", [
43253
+ object({
43254
+ kind: literal("mdns"),
43255
+ /** DNS-SD service type, e.g. `_esphomelib._tcp`. */
43256
+ serviceType: string().min(1),
43257
+ /** Regular expression an instance name must match (case-insensitive). */
43258
+ instancePattern: string().optional()
43259
+ }),
43260
+ object({
43261
+ kind: literal("ws-discovery"),
43262
+ /** Probe types (e.g. `dn:NetworkVideoTransmitter`); omitted = any device. */
43263
+ types: array(string()).optional()
43264
+ }),
43265
+ object({
43266
+ kind: literal("ssdp"),
43267
+ searchTarget: string().min(1)
43268
+ }),
43269
+ object({
43270
+ kind: literal("udp-broadcast"),
43271
+ port: number().int().min(1).max(65535),
43272
+ payloadHex: string().regex(/^([0-9a-fA-F]{2})*$/),
43273
+ /** Port replies arrive on, when the device answers somewhere other than the source port. */
43274
+ listenPort: number().int().min(1).max(65535).optional()
43275
+ }),
43276
+ object({
43277
+ kind: literal("http-probe"),
43278
+ port: number().int().min(1).max(65535),
43279
+ path: string().startsWith("/"),
43280
+ method: _enum(["GET", "POST"]).default("GET"),
43281
+ bodyText: string().optional(),
43282
+ contentType: string().optional(),
43283
+ tls: boolean().default(false),
43284
+ /** Status codes that count as a hit. Omitted = any HTTP answer. */
43285
+ acceptStatus: array(number().int()).optional()
43286
+ }),
43287
+ object({
43288
+ kind: literal("custom"),
43289
+ /** Why the engine cannot do it (an allowlisted exception, e.g. a vendor UDP protocol). */
43290
+ reason: string().min(1)
43291
+ })
43292
+ ]);
43293
+ /** What the engine saw on the wire for ONE declared mode; the provider classifies it. */
43294
+ var DiscoveryHitSchema = object({
43295
+ /** Index into the provider's own `getDiscoveryModes()` array. */
43296
+ modeIndex: number().int().min(0),
43297
+ host: string(),
43298
+ port: number().int(),
43299
+ http: object({
43300
+ status: number().int(),
43301
+ headers: record(string(), string()),
43302
+ /** At most 16 KiB. */
43303
+ bodyText: string()
43304
+ }).optional(),
43305
+ mdns: object({
43306
+ instance: string(),
43307
+ host: string(),
43308
+ port: number().int(),
43309
+ txt: record(string(), string())
43310
+ }).optional(),
43311
+ udp: object({ payloadHex: string() }).optional(),
43312
+ wsd: object({
43313
+ xaddrs: array(string()),
43314
+ types: array(string()),
43315
+ scopes: array(string())
43316
+ }).optional()
43317
+ });
43318
+ /**
42665
43319
  * Candidate handed back from discovery and accepted by
42666
43320
  * `adoptDiscoveredDevice`. Shape mirrors the in-process
42667
43321
  * `DiscoveredDevice` interface used by `DeviceDiscovery`.
@@ -42696,7 +43350,17 @@ var DiscoveryCandidateSchema = object({
42696
43350
  /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
42697
43351
  onboardedDeviceId: number().optional(),
42698
43352
  /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
42699
- onboardedName: string().optional()
43353
+ onboardedName: string().optional(),
43354
+ /**
43355
+ * This candidate is an INTEGRATION to set up (a gateway, a hub, a controller),
43356
+ * not a device to adopt. Replaces the `prefilledConfig.setupIntegration` marker,
43357
+ * which is still read for one release.
43358
+ */
43359
+ setupIntegration: boolean().optional(),
43360
+ /** `stableId` of the gateway candidate this device was found BEHIND. */
43361
+ parentStableId: string().optional(),
43362
+ /** `false` = shown for context but not pickable. Default true. */
43363
+ selectable: boolean().optional()
42700
43364
  });
42701
43365
  /**
42702
43366
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -42749,6 +43413,12 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
42749
43413
  }), method(object({}), boolean()), method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
42750
43414
  kind: "mutation",
42751
43415
  auth: "admin"
43416
+ }), method(object({}), array(DiscoveryModeSchema)), method(object({
43417
+ hits: array(DiscoveryHitSchema),
43418
+ params: record(string(), unknown()).optional()
43419
+ }), array(DiscoveryCandidateSchema), {
43420
+ kind: "mutation",
43421
+ auth: "admin"
42752
43422
  }), method(object({}), CreationSchemaOutputSchema), method(object({}), object({ deviceType: _enum(DeviceType).nullable() })), method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
42753
43423
  kind: "mutation",
42754
43424
  auth: "admin"
@@ -43476,17 +44146,23 @@ method(object({
43476
44146
  }), method(object({}), object({ providers: array(object({
43477
44147
  addonId: string(),
43478
44148
  label: string()
43479
- })).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
43480
- addonId: string(),
43481
- label: string(),
43482
- candidates: array(DiscoveryCandidateSchema).readonly(),
43483
- error: string().nullable()
43484
- })).readonly() }), {
43485
- kind: "mutation",
43486
- auth: "admin"
43487
- }), method(object({
44149
+ })).readonly() }), { auth: "admin" }), method(
44150
+ /** `subnets`: extra CIDRs for THIS scan, on top of the hub's own and the saved list. */
44151
+ object({ subnets: array(string()).optional() }),
44152
+ object({ groups: array(object({
44153
+ addonId: string(),
44154
+ label: string(),
44155
+ candidates: array(DiscoveryCandidateSchema).readonly(),
44156
+ error: string().nullable()
44157
+ })).readonly() }),
44158
+ {
44159
+ kind: "mutation",
44160
+ auth: "admin"
44161
+ }
44162
+ ), method(object({
43488
44163
  addonId: string(),
43489
- params: record(string(), unknown()).optional()
44164
+ params: record(string(), unknown()).optional(),
44165
+ subnets: array(string()).optional()
43490
44166
  }), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
43491
44167
  kind: "mutation",
43492
44168
  auth: "admin"
@@ -61106,7 +61782,9 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
61106
61782
  id: string(),
61107
61783
  settings: record(string(), unknown()),
61108
61784
  /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
61109
- secretKeys: array(string()).optional()
61785
+ secretKeys: array(string()).optional(),
61786
+ /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
61787
+ skipRestart: boolean().optional()
61110
61788
  }), object({ success: boolean() }), {
61111
61789
  kind: "mutation",
61112
61790
  auth: "admin"
@@ -61583,6 +62261,39 @@ var TlsStatusSchema = object({
61583
62261
  hasCa: boolean()
61584
62262
  }).nullable()
61585
62263
  });
62264
+ /** One provider's ask of the scan engine: the modes it declared. */
62265
+ var ScanRequestSchema = object({
62266
+ providerId: string().min(1),
62267
+ modes: array(DiscoveryModeSchema)
62268
+ });
62269
+ var ScanErrorSchema = object({
62270
+ /** Absent for a failure that is the scan's own (a bad subnet, a socket that would not open). */
62271
+ providerId: string().optional(),
62272
+ /** The mode family that failed (`mdns`, `http-probe`, …). */
62273
+ mode: string().optional(),
62274
+ message: string()
62275
+ });
62276
+ /** What one scan did, per mode family; the engine logs the same numbers once. */
62277
+ var ScanStatsSchema = object({
62278
+ hostsProbed: number().int(),
62279
+ hits: number().int(),
62280
+ timeouts: number().int(),
62281
+ byMode: record(string(), object({
62282
+ probed: number().int(),
62283
+ hits: number().int(),
62284
+ timeouts: number().int()
62285
+ }))
62286
+ });
62287
+ var ScanNetworkResultSchema = object({
62288
+ hits: array(object({
62289
+ providerId: string(),
62290
+ hit: DiscoveryHitSchema
62291
+ })),
62292
+ /** CIDRs actually covered: the hub's own /24s, the saved list and the per-call list. */
62293
+ subnetsScanned: array(string()),
62294
+ errors: array(ScanErrorSchema),
62295
+ stats: ScanStatsSchema
62296
+ });
61586
62297
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
61587
62298
  /**
61588
62299
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -61637,6 +62348,16 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
61637
62348
  }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
61638
62349
  kind: "mutation",
61639
62350
  auth: "admin"
62351
+ }), method(object({
62352
+ requests: array(ScanRequestSchema),
62353
+ subnets: array(string()).optional()
62354
+ }), ScanNetworkResultSchema, {
62355
+ kind: "mutation",
62356
+ auth: "admin",
62357
+ timeoutMs: 9e4
62358
+ }), method(_void(), object({ subnets: array(string()) }), { auth: "admin" }), method(object({ subnets: array(string()) }), object({ subnets: array(string()) }), {
62359
+ kind: "mutation",
62360
+ auth: "admin"
61640
62361
  }), method(_void(), TlsStatusSchema, {
61641
62362
  kind: "mutation",
61642
62363
  auth: "admin"
@@ -64179,7 +64900,7 @@ object({
64179
64900
  });
64180
64901
  Object.values(DeviceType);
64181
64902
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
64182
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
64903
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
64183
64904
  kind: "mutation",
64184
64905
  auth: "admin"
64185
64906
  });
@@ -68139,7 +68860,7 @@ discriminatedUnion("kind", [object({
68139
68860
  })]);
68140
68861
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
68141
68862
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
68142
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
68863
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
68143
68864
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
68144
68865
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
68145
68866
  new Set(CAP_NAMES);
@@ -68192,7 +68913,7 @@ var AccessoryKind = {
68192
68913
  PrivacyMask: DeviceRole.PrivacyMask
68193
68914
  };
68194
68915
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
68195
- DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
68916
+ DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Camera, DeviceType.Hub, DeviceType.Switch, DeviceType.Siren, DeviceType.Light, DeviceType.Fan, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
68196
68917
  new Set(Object.values(DeviceType));
68197
68918
  Object.freeze({
68198
68919
  "accessories.getStatus": {
@@ -68580,6 +69301,13 @@ Object.freeze({
68580
69301
  access: "create",
68581
69302
  role: "admin"
68582
69303
  },
69304
+ "addonSettings.getDeclaredSettingsKeys": {
69305
+ capName: "addon-settings",
69306
+ capScope: "system",
69307
+ addonId: null,
69308
+ access: "view",
69309
+ role: "admin"
69310
+ },
68583
69311
  "addonSettings.getDeviceSettings": {
68584
69312
  capName: "addon-settings",
68585
69313
  capScope: "system",
@@ -68601,6 +69329,13 @@ Object.freeze({
68601
69329
  access: "view",
68602
69330
  role: "admin"
68603
69331
  },
69332
+ "addonSettings.unsetStoredSettingsKeys": {
69333
+ capName: "addon-settings",
69334
+ capScope: "system",
69335
+ addonId: null,
69336
+ access: "create",
69337
+ role: "admin"
69338
+ },
68604
69339
  "addonSettings.updateDeviceSettings": {
68605
69340
  capName: "addon-settings",
68606
69341
  capScope: "system",
@@ -71016,6 +71751,13 @@ Object.freeze({
71016
71751
  access: "create",
71017
71752
  role: "admin"
71018
71753
  },
71754
+ "deviceProvider.classifyDiscoveryHits": {
71755
+ capName: "device-provider",
71756
+ capScope: "system",
71757
+ addonId: null,
71758
+ access: "create",
71759
+ role: "admin"
71760
+ },
71019
71761
  "deviceProvider.createDevice": {
71020
71762
  capName: "device-provider",
71021
71763
  capScope: "system",
@@ -71044,6 +71786,13 @@ Object.freeze({
71044
71786
  access: "view",
71045
71787
  role: "admin"
71046
71788
  },
71789
+ "deviceProvider.getDiscoveryModes": {
71790
+ capName: "device-provider",
71791
+ capScope: "system",
71792
+ addonId: null,
71793
+ access: "view",
71794
+ role: "admin"
71795
+ },
71047
71796
  "deviceProvider.getDiscoveryParamsSchema": {
71048
71797
  capName: "device-provider",
71049
71798
  capScope: "system",
@@ -71821,6 +72570,13 @@ Object.freeze({
71821
72570
  access: "create",
71822
72571
  role: "admin"
71823
72572
  },
72573
+ "keychain.releaseRecordRefs": {
72574
+ capName: "keychain",
72575
+ capScope: "system",
72576
+ addonId: null,
72577
+ access: "create",
72578
+ role: "admin"
72579
+ },
71824
72580
  "keychain.resolve": {
71825
72581
  capName: "keychain",
71826
72582
  capScope: "system",
@@ -72241,6 +72997,13 @@ Object.freeze({
72241
72997
  access: "view",
72242
72998
  role: "admin"
72243
72999
  },
73000
+ "localNetwork.getScanSubnets": {
73001
+ capName: "local-network",
73002
+ capScope: "system",
73003
+ addonId: null,
73004
+ access: "view",
73005
+ role: "admin"
73006
+ },
72244
73007
  "localNetwork.getTlsStatus": {
72245
73008
  capName: "local-network",
72246
73009
  capScope: "system",
@@ -72290,6 +73053,13 @@ Object.freeze({
72290
73053
  access: "create",
72291
73054
  role: "admin"
72292
73055
  },
73056
+ "localNetwork.scanNetwork": {
73057
+ capName: "local-network",
73058
+ capScope: "system",
73059
+ addonId: null,
73060
+ access: "create",
73061
+ role: "admin"
73062
+ },
72293
73063
  "localNetwork.setAddressRole": {
72294
73064
  capName: "local-network",
72295
73065
  capScope: "system",
@@ -72311,6 +73081,13 @@ Object.freeze({
72311
73081
  access: "create",
72312
73082
  role: "admin"
72313
73083
  },
73084
+ "localNetwork.setScanSubnets": {
73085
+ capName: "local-network",
73086
+ capScope: "system",
73087
+ addonId: null,
73088
+ access: "create",
73089
+ role: "admin"
73090
+ },
72314
73091
  "localNetwork.setViewerEndpoints": {
72315
73092
  capName: "local-network",
72316
73093
  capScope: "system",
@@ -76532,6 +77309,13 @@ Object.freeze({
76532
77309
  access: "create",
76533
77310
  role: "admin"
76534
77311
  },
77312
+ "storageAdmin.dropOrphanedKind": {
77313
+ capName: "storage-admin",
77314
+ capScope: "system",
77315
+ addonId: null,
77316
+ access: "delete",
77317
+ role: "admin"
77318
+ },
76535
77319
  "storageAdmin.listKinds": {
76536
77320
  capName: "storage-admin",
76537
77321
  capScope: "system",
@@ -76553,6 +77337,27 @@ Object.freeze({
76553
77337
  access: "view",
76554
77338
  role: "admin"
76555
77339
  },
77340
+ "storageAdmin.scanStaleData": {
77341
+ capName: "storage-admin",
77342
+ capScope: "system",
77343
+ addonId: null,
77344
+ access: "create",
77345
+ role: "admin"
77346
+ },
77347
+ "storageAdmin.staleDataReport": {
77348
+ capName: "storage-admin",
77349
+ capScope: "system",
77350
+ addonId: null,
77351
+ access: "view",
77352
+ role: "admin"
77353
+ },
77354
+ "storageAdmin.unsetStaleSettingsKey": {
77355
+ capName: "storage-admin",
77356
+ capScope: "system",
77357
+ addonId: null,
77358
+ access: "create",
77359
+ role: "admin"
77360
+ },
76556
77361
  "storageEvictable.evict": {
76557
77362
  capName: "storage-evictable",
76558
77363
  capScope: "system",
@@ -82249,7 +83054,7 @@ function projectAction(cap, action) {
82249
83054
  };
82250
83055
  }
82251
83056
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
82252
- DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer;
83057
+ DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway;
82253
83058
  DeviceFeature.BatteryOperated;
82254
83059
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
82255
83060
  var HA_AS = "a power sensor (device_class power). Home Assistant has no laundry domain. running-state stays unexported until a null reading can be published as unavailable.";