@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
@@ -5588,7 +5588,7 @@ var ZodIssueCode = {
5588
5588
  var ZodFirstPartyTypeKind;
5589
5589
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5590
5590
  //#endregion
5591
- //#region ../types/dist/sleep-63gM_1JR.mjs
5591
+ //#region ../types/dist/sleep-CeyhrcOk.mjs
5592
5592
  /**
5593
5593
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5594
5594
  * window to float samples (D455).
@@ -7655,6 +7655,7 @@ var BaseAddon = class {
7655
7655
  * a warning and falls back rather than crashing boot.
7656
7656
  */
7657
7657
  state(key, schema, fallback) {
7658
+ this._declaredStateKeys.add(key);
7658
7659
  return createDurableState({
7659
7660
  key,
7660
7661
  schema,
@@ -7670,6 +7671,96 @@ var BaseAddon = class {
7670
7671
  } })
7671
7672
  });
7672
7673
  }
7674
+ /** Durable-state keys this process registered through {@link state}. */
7675
+ _declaredStateKeys = /* @__PURE__ */ new Set();
7676
+ /**
7677
+ * Stale-data audit: every global-settings key this addon still declares —
7678
+ * constructor defaults, settings-schema fields (layout containers included),
7679
+ * durable-state keys and the root of each `secretStatePaths` entry. Answered
7680
+ * here, in the addon's own process, so the hub keeps no copy of it.
7681
+ */
7682
+ async getDeclaredSettingsKeys() {
7683
+ return { keys: [...this.declaredKeySet()].toSorted() };
7684
+ }
7685
+ declaredKeySet() {
7686
+ const keys = new Set(Object.keys(this.defaults));
7687
+ const schema = this.globalSettingsSchema();
7688
+ if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
7689
+ for (const key of this._declaredStateKeys) keys.add(key);
7690
+ for (const path of this.secretStatePaths) {
7691
+ const root = path.split(/[.[]/, 1)[0];
7692
+ if (root) keys.add(root);
7693
+ }
7694
+ return keys;
7695
+ }
7696
+ /**
7697
+ * Stale-data audit: remove stored global-settings keys the addon no longer
7698
+ * declares, and clear the keychain values their markers held — except a value
7699
+ * a surviving key still points at. A declared or absent key is refused.
7700
+ */
7701
+ async unsetStoredSettingsKeys(keys) {
7702
+ const settings = this._ctx?.settings;
7703
+ const raw = this.rawSettings();
7704
+ if (!settings || !raw) return {
7705
+ removed: [],
7706
+ refused: keys.map((key) => ({
7707
+ key,
7708
+ reason: "no settings store"
7709
+ })),
7710
+ secretsCleared: 0
7711
+ };
7712
+ let stored;
7713
+ try {
7714
+ stored = await raw.readAddonStore();
7715
+ } catch (error) {
7716
+ const reason = `store unreadable: ${error instanceof Error ? error.message : "unknown"}`;
7717
+ return {
7718
+ removed: [],
7719
+ refused: keys.map((key) => ({
7720
+ key,
7721
+ reason
7722
+ })),
7723
+ secretsCleared: 0
7724
+ };
7725
+ }
7726
+ const declared = this.declaredKeySet();
7727
+ const removed = [];
7728
+ const refused = [];
7729
+ for (const key of keys) {
7730
+ const base = key.split("@", 1)[0] ?? key;
7731
+ if (declared.has(base)) refused.push({
7732
+ key,
7733
+ reason: "still declared by the addon"
7734
+ });
7735
+ else if (!(key in stored)) refused.push({
7736
+ key,
7737
+ reason: "not stored"
7738
+ });
7739
+ else removed.push(key);
7740
+ }
7741
+ if (removed.length === 0) return {
7742
+ removed,
7743
+ refused,
7744
+ secretsCleared: 0
7745
+ };
7746
+ const kept = /* @__PURE__ */ new Set();
7747
+ const held = /* @__PURE__ */ new Set();
7748
+ for (const [key, value] of Object.entries(stored)) {
7749
+ const target = removed.includes(key) ? held : kept;
7750
+ for (const ref of collectSecretMarkerRefs(value)) target.add(ref);
7751
+ }
7752
+ const refsToClear = [...held].filter((ref) => !kept.has(ref));
7753
+ const patch = {};
7754
+ for (const key of removed) patch[key] = void 0;
7755
+ await settings.writeAddonStore(patch);
7756
+ const secretsCleared = refsToClear.length > 0 && this._ctx?.secrets ? await this._ctx.secrets.delete(refsToClear) : 0;
7757
+ await this.resolveConfig();
7758
+ return {
7759
+ removed,
7760
+ refused,
7761
+ secretsCleared
7762
+ };
7763
+ }
7673
7764
  /** Per-device variant of {@link state}, backed by the per-device store. */
7674
7765
  deviceState(deviceId, key, schema, fallback) {
7675
7766
  return createDurableState({
@@ -7940,6 +8031,21 @@ function dropPerNodeFields(fields) {
7940
8031
  }
7941
8032
  return kept;
7942
8033
  }
8034
+ function collectFieldKeys(fields) {
8035
+ const collected = [];
8036
+ for (const field of fields) {
8037
+ if (field.type === "group") {
8038
+ collected.push(...collectFieldKeys(field.fields));
8039
+ continue;
8040
+ }
8041
+ if (field.type === "sub-tabs") {
8042
+ for (const tab of field.tabs) collected.push(...collectFieldKeys(tab.fields));
8043
+ continue;
8044
+ }
8045
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8046
+ }
8047
+ return collected;
8048
+ }
7943
8049
  function collectPerNodeFieldKeys(fields) {
7944
8050
  const collected = [];
7945
8051
  for (const field of fields) {
@@ -10962,7 +11068,7 @@ object({
10962
11068
  params: record(string(), union([string(), number()])).optional()
10963
11069
  });
10964
11070
  //#endregion
10965
- //#region ../types/dist/err-msg-DWXWc0Di.mjs
11071
+ //#region ../types/dist/err-msg-BD5buxvT.mjs
10966
11072
  var DeviceType = /* @__PURE__ */ function(DeviceType) {
10967
11073
  DeviceType["Camera"] = "camera";
10968
11074
  DeviceType["Hub"] = "hub";
@@ -11068,6 +11174,12 @@ var DeviceType = /* @__PURE__ */ function(DeviceType) {
11068
11174
  DeviceType["Washer"] = "washer";
11069
11175
  /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
11070
11176
  DeviceType["Dryer"] = "dryer";
11177
+ /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
11178
+ * other radio-to-IP bridges). It carries the box's own facts (firmware,
11179
+ * address, connection state) and the actions the box really supports
11180
+ * (`reboot`, `device-admin-link`). Products are NOT its children — they are
11181
+ * adopted beside it — and its connection settings stay on the integration. */
11182
+ DeviceType["Gateway"] = "gateway";
11071
11183
  return DeviceType;
11072
11184
  }({});
11073
11185
  var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
@@ -11318,7 +11430,7 @@ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11318
11430
  return DeviceRole;
11319
11431
  }({});
11320
11432
  //#endregion
11321
- //#region ../types/dist/composition-kSuOifuG.mjs
11433
+ //#region ../types/dist/composition-C7WdThmP.mjs
11322
11434
  /**
11323
11435
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11324
11436
  *
@@ -16325,8 +16437,172 @@ object({
16325
16437
  quantity: ReadingQuantitySchema,
16326
16438
  unit: string().min(1)
16327
16439
  });
16328
- /** How a leaf compares a device field to a value. Derived from the field's
16329
- * `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
16440
+ new Set([
16441
+ {
16442
+ id: "person",
16443
+ name: "Person"
16444
+ },
16445
+ {
16446
+ id: "vehicle",
16447
+ name: "Vehicle"
16448
+ },
16449
+ {
16450
+ id: "animal",
16451
+ name: "Animal"
16452
+ },
16453
+ {
16454
+ id: "package",
16455
+ name: "Package"
16456
+ }
16457
+ ].map((l) => l.id));
16458
+ var COCO_TO_MACRO = {
16459
+ mapping: {
16460
+ person: "person",
16461
+ bicycle: "vehicle",
16462
+ car: "vehicle",
16463
+ motorcycle: "vehicle",
16464
+ airplane: "vehicle",
16465
+ bus: "vehicle",
16466
+ train: "vehicle",
16467
+ truck: "vehicle",
16468
+ boat: "vehicle",
16469
+ bird: "animal",
16470
+ cat: "animal",
16471
+ dog: "animal",
16472
+ horse: "animal",
16473
+ sheep: "animal",
16474
+ cow: "animal",
16475
+ elephant: "animal",
16476
+ bear: "animal",
16477
+ zebra: "animal",
16478
+ giraffe: "animal",
16479
+ suitcase: "package",
16480
+ backpack: "package",
16481
+ handbag: "package"
16482
+ },
16483
+ preserveOriginal: false
16484
+ };
16485
+ /**
16486
+ * WHAT a detection-shaped leaf is about: one MACRO class (person, vehicle,
16487
+ * animal, package) plus an OPTIONAL label that narrows it.
16488
+ *
16489
+ * The taxonomy is `COCO_TO_MACRO` (and `CLASS_MAP_MACRO_TARGETS`) — never a
16490
+ * second list. The label that fits depends on the macro class:
16491
+ *
16492
+ * - `animal` → a sub-class (`dog`, `cat`, …);
16493
+ * - `person` → a known face (an identity of the `face-gallery`);
16494
+ * - `vehicle` → a plate (its text) or a sub-class (`car`, `truck`, …);
16495
+ * - `package` → a sub-class.
16496
+ *
16497
+ * `other` exists only so a class the taxonomy does not know (a custom model's
16498
+ * `forklift`) survives the conversion of a stored recipe without widening.
16499
+ *
16500
+ * A label is decided LATE (D683): face recognition and LPR land after the
16501
+ * detection starts. The track-lifecycle `update` event carries the newly
16502
+ * resolved `label` / `plateText`, which is why a leaf with a label fires when
16503
+ * the subject GETS it — not at the first detection.
16504
+ */
16505
+ var SubClassLabelSchema = object({
16506
+ kind: literal("sub-class"),
16507
+ className: string().min(1)
16508
+ });
16509
+ var IdentityLabelSchema = object({
16510
+ kind: literal("identity"),
16511
+ /** The `face-gallery` identity id (stable across a rename). */
16512
+ identityId: string().min(1),
16513
+ /** The identity's name — what a track-lifecycle event carries as `label`. */
16514
+ name: string().min(1)
16515
+ });
16516
+ var PlateLabelSchema = object({
16517
+ kind: literal("plate"),
16518
+ text: string().min(1)
16519
+ });
16520
+ function macroOf$1(className) {
16521
+ return COCO_TO_MACRO.mapping[className.toLowerCase()] ?? className.toLowerCase();
16522
+ }
16523
+ /** A sub-class label must belong to the macro it is under. */
16524
+ function subClassBelongs(macro, label) {
16525
+ return label === void 0 || macroOf$1(label.className) === macro;
16526
+ }
16527
+ var subClassOf = (macro) => SubClassLabelSchema.refine((l) => subClassBelongs(macro, l), { message: `that class is not a kind of ${macro} in the detection taxonomy` });
16528
+ var DetectionSubjectSchema = discriminatedUnion("macro", [
16529
+ object({
16530
+ macro: literal("animal"),
16531
+ label: subClassOf("animal").optional()
16532
+ }),
16533
+ object({
16534
+ macro: literal("person"),
16535
+ label: IdentityLabelSchema.optional()
16536
+ }),
16537
+ object({
16538
+ macro: literal("vehicle"),
16539
+ label: discriminatedUnion("kind", [SubClassLabelSchema.refine((l) => subClassBelongs("vehicle", l), { message: "that class is not a kind of vehicle in the detection taxonomy" }), PlateLabelSchema]).optional()
16540
+ }),
16541
+ object({
16542
+ macro: literal("package"),
16543
+ label: subClassOf("package").optional()
16544
+ }),
16545
+ object({
16546
+ macro: literal("other"),
16547
+ className: string().min(1)
16548
+ })
16549
+ ]);
16550
+ /**
16551
+ * The subject of a PARKED object. The stationary registry's event carries a
16552
+ * class and a zone, no face and no plate — an identity or plate label there
16553
+ * could never match, so the schema refuses it instead of letting it sit dead.
16554
+ */
16555
+ 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" });
16556
+ /**
16557
+ * A stored class string as a subject: a macro name stays a macro; a raw class
16558
+ * the taxonomy files under a macro becomes that macro plus the sub-class label
16559
+ * (`dog` → animal / dog); anything else is `other`, kept verbatim.
16560
+ */
16561
+ function subjectFromClassName(className) {
16562
+ const name = className.trim().toLowerCase();
16563
+ const macro = COCO_TO_MACRO.mapping[name];
16564
+ if (new Set(Object.values(COCO_TO_MACRO.mapping)).has(name)) return macroOnly(name);
16565
+ if (macro === void 0) return {
16566
+ macro: "other",
16567
+ className
16568
+ };
16569
+ const label = {
16570
+ kind: "sub-class",
16571
+ className: name
16572
+ };
16573
+ switch (macro) {
16574
+ case "animal": return {
16575
+ macro: "animal",
16576
+ label
16577
+ };
16578
+ case "vehicle": return {
16579
+ macro: "vehicle",
16580
+ label
16581
+ };
16582
+ case "package": return {
16583
+ macro: "package",
16584
+ label
16585
+ };
16586
+ default: return {
16587
+ macro: "other",
16588
+ className
16589
+ };
16590
+ }
16591
+ }
16592
+ function macroOnly(macro) {
16593
+ switch (macro) {
16594
+ case "animal": return { macro: "animal" };
16595
+ case "person": return { macro: "person" };
16596
+ case "vehicle": return { macro: "vehicle" };
16597
+ case "package": return { macro: "package" };
16598
+ default: return {
16599
+ macro: "other",
16600
+ className: macro
16601
+ };
16602
+ }
16603
+ }
16604
+ /** How a state leaf compares a device field to a value. Derived from the
16605
+ * field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
16330
16606
  var AutomationConditionOperatorSchema = _enum([
16331
16607
  "eq",
16332
16608
  "ne",
@@ -16337,12 +16613,76 @@ var AutomationConditionOperatorSchema = _enum([
16337
16613
  "contains",
16338
16614
  "in"
16339
16615
  ]);
16340
- var AutomationConditionLeafSchema = object({
16341
- kind: literal("condition"),
16616
+ /**
16617
+ * D8 compliance, and it is the reason a "becomes" leaf is not merely an event
16618
+ * subscription: it evaluates against the **state mirror**, which is reconciled,
16619
+ * and an event only WAKES the evaluation. A dropped event therefore DELAYS a
16620
+ * fire; it does not lose it.
16621
+ */
16622
+ var DeviceBecomesLeafSchema = object({
16623
+ kind: literal("device-becomes"),
16624
+ deviceId: number().int().nonnegative(),
16625
+ cap: string().min(1),
16626
+ fieldPath: string().min(1),
16627
+ /** Fire when the field takes this value. Omit to fire on any change. */
16628
+ becomes: union([
16629
+ string(),
16630
+ number(),
16631
+ boolean()
16632
+ ]).optional(),
16633
+ /** Only on a CHANGE of value, not on every re-report. */
16634
+ edge: boolean().optional(),
16635
+ /** The new value must hold this long before it counts. */
16636
+ forMs: number().int().min(0).max(864e5).optional(),
16637
+ /** Collapse a burst into one occurrence. */
16638
+ debounceMs: number().int().min(0).max(6e5).optional()
16639
+ });
16640
+ var DeviceEventLeafSchema = object({
16641
+ kind: literal("device-event"),
16642
+ /** An `EventCategory` value. */
16643
+ category: string().min(1),
16644
+ deviceId: number().int().nonnegative().optional()
16645
+ });
16646
+ var ScheduleLeafSchema = object({
16647
+ kind: literal("schedule"),
16648
+ cron: string().min(1).max(120)
16649
+ });
16650
+ var ManualLeafSchema = object({ kind: literal("manual") });
16651
+ var IntervalLeafSchema = object({
16652
+ kind: literal("interval"),
16653
+ minutes: number().int().min(1).max(1440)
16654
+ });
16655
+ var DetectionLeafSchema = object({
16656
+ kind: literal("detection"),
16657
+ /** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
16658
+ subjects: array(DetectionSubjectSchema).min(1),
16659
+ deviceIds: array(number().int().nonnegative()).min(1),
16660
+ /** The track must have visited at least one of these zones. */
16661
+ zones: array(string().min(1)).min(1).optional(),
16662
+ /** Best-so-far confidence the track must reach. */
16663
+ minScore: number().min(0).max(1).optional()
16664
+ });
16665
+ var StationaryLeafSchema = object({
16666
+ kind: literal("stationary"),
16667
+ deviceIds: array(number().int().nonnegative()).min(1),
16668
+ subjects: array(StationarySubjectSchema).min(1).optional(),
16669
+ zones: array(string().min(1)).min(1).optional(),
16670
+ /** `appeared`: it became parked. `departed`: the registry retired it. */
16671
+ phase: _enum(["appeared", "departed"])
16672
+ });
16673
+ /** "is": one device field compared to one value, NOW. */
16674
+ var DeviceIsLeafSchema = object({
16675
+ kind: literal("device-is"),
16342
16676
  deviceId: number().int().nonnegative(),
16343
16677
  cap: string().min(1),
16344
16678
  fieldPath: string().min(1),
16345
16679
  operator: AutomationConditionOperatorSchema,
16680
+ /**
16681
+ * The value. A number on a quantity field is stored in the field's base unit
16682
+ * (D944); a quantity value is the WRITE form, which the save boundary
16683
+ * replaces with the converted number and `unit`. A leaf that still carries
16684
+ * one is never evaluated (it fails).
16685
+ */
16346
16686
  value: union([
16347
16687
  string(),
16348
16688
  number(),
@@ -16350,24 +16690,26 @@ var AutomationConditionLeafSchema = object({
16350
16690
  array(union([string(), number()])),
16351
16691
  QuantityValueSchema
16352
16692
  ]),
16693
+ /** The unit the person picked for `value` (display only; `value` is in the base unit). */
16353
16694
  unit: string().min(1).optional()
16354
16695
  });
16355
- var AutomationConditionStationaryPresentSchema = object({
16696
+ /**
16697
+ * True when the camera currently has a PARKED object matching `subjects` in at
16698
+ * least one of `zones`. Reads the zone-analytics snapshot through the same
16699
+ * mirror as every other state leaf, so a dropped event never decides it (D49).
16700
+ */
16701
+ var StationaryPresentLeafSchema = object({
16356
16702
  kind: literal("stationary-present"),
16357
16703
  deviceId: number().int().nonnegative(),
16358
- /** Class names; a macro class matches every raw class that maps to it. */
16359
- classes: array(string().min(1)).min(1).optional(),
16360
- /** The parked object must be in at least one of these zones. */
16704
+ subjects: array(StationarySubjectSchema).min(1).optional(),
16361
16705
  zones: array(string().min(1)).min(1).optional()
16362
16706
  });
16363
16707
  /**
16364
- * The expression leaf, declared as a plain object rather than an intersection
16365
- * with {@link ExpressionSourceSchema}: a discriminated union has to be able to
16366
- * read `kind` off each option, and an intersection hides it. The author-time
16367
- * validation is the SAME function `ExpressionSourceSchema` runs, so the two
16368
- * cannot drift — an expression that one accepts, the other accepts.
16708
+ * The escape hatch: the bounded expression engine over named device bindings.
16709
+ * Declared as a plain object so a discriminated union can read `kind`; the
16710
+ * author-time validation is the SAME function `ExpressionSourceSchema` runs.
16369
16711
  */
16370
- var AutomationConditionExpressionSchema = object({
16712
+ var ExpressionLeafSchema = object({
16371
16713
  kind: literal("expression"),
16372
16714
  expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
16373
16715
  bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
@@ -16379,55 +16721,62 @@ var AutomationConditionExpressionSchema = object({
16379
16721
  path: ["expr"]
16380
16722
  });
16381
16723
  });
16382
- var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
16724
+ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
16383
16725
  object({
16384
16726
  kind: literal("all"),
16385
- children: array(AutomationConditionSchema)
16727
+ children: array(AutomationWhenSchema)
16386
16728
  }),
16387
16729
  object({
16388
16730
  kind: literal("any"),
16389
- children: array(AutomationConditionSchema)
16731
+ children: array(AutomationWhenSchema)
16390
16732
  }),
16391
16733
  object({
16392
16734
  kind: literal("not"),
16393
- child: AutomationConditionSchema
16735
+ child: AutomationWhenSchema
16394
16736
  }),
16395
- AutomationConditionLeafSchema,
16396
- AutomationConditionExpressionSchema,
16397
- AutomationConditionStationaryPresentSchema
16737
+ DeviceBecomesLeafSchema,
16738
+ DeviceEventLeafSchema,
16739
+ ScheduleLeafSchema,
16740
+ ManualLeafSchema,
16741
+ IntervalLeafSchema,
16742
+ DetectionLeafSchema,
16743
+ StationaryLeafSchema,
16744
+ DeviceIsLeafSchema,
16745
+ StationaryPresentLeafSchema,
16746
+ ExpressionLeafSchema
16398
16747
  ]));
16399
16748
  /**
16400
- * What starts a run.
16401
- *
16402
- * D8 compliance, and it is the reason `device-state` is not merely an event
16403
- * subscription: the trigger evaluates against the **state mirror**, which is
16404
- * reconciled, and an event only WAKES the evaluation. A dropped event therefore
16405
- * DELAYS a trigger; it does not lose it. `schedule` uses `croner` — the one
16406
- * already in the repo — because `setInterval(24h)` drifts and "at 23:30" does
16407
- * not.
16408
- */
16409
- var AutomationTriggerSchema = discriminatedUnion("kind", [
16749
+ * The recipe shape before the single `when` tree: a flat list of triggers
16750
+ * (OR'd, each an instant) and an optional condition tree. Stored recipes —
16751
+ * the `AUTOMATION_RECIPE` literal of every block saved so far, and whatever an
16752
+ * older client still posts — are in this shape, so it stays READABLE here, and
16753
+ * {@link convertLegacyRecipe} turns it into the new one, losslessly and one
16754
+ * way. Nothing writes this shape any more.
16755
+ *
16756
+ * `{ triggers: [t1…tn], conditions?: C }`
16757
+ * → `when = all(any(t1…tn), C)`, or `any(t1…tn)` without `C`;
16758
+ * a single trigger is not wrapped in `any`.
16759
+ * a `condition` leaf → a `device-is` leaf (the "is" verb);
16760
+ * a `device-state` trigger → a `device-becomes` leaf (the "becomes" verb);
16761
+ * `classes: ['dog']` → the macro `animal` with the label `dog`.
16762
+ */
16763
+ var LegacyTriggerSchema = discriminatedUnion("kind", [
16410
16764
  object({
16411
16765
  kind: literal("device-state"),
16412
16766
  deviceId: number().int().nonnegative(),
16413
16767
  cap: string().min(1),
16414
16768
  fieldPath: string().min(1),
16415
- /** Fire when the field takes this value. Omit to fire on any change. */
16416
16769
  becomes: union([
16417
16770
  string(),
16418
16771
  number(),
16419
16772
  boolean()
16420
16773
  ]).optional(),
16421
- /** Only on a CHANGE of value, not on every re-report. */
16422
16774
  edge: boolean().optional(),
16423
- /** The condition must hold this long before the run starts. */
16424
16775
  forMs: number().int().min(0).max(864e5).optional(),
16425
- /** Collapse a burst into one run. */
16426
16776
  debounceMs: number().int().min(0).max(6e5).optional()
16427
16777
  }),
16428
16778
  object({
16429
16779
  kind: literal("device-event"),
16430
- /** An `EventCategory` value. */
16431
16780
  category: string().min(1),
16432
16781
  deviceId: number().int().nonnegative().optional()
16433
16782
  }),
@@ -16438,25 +16787,16 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
16438
16787
  object({ kind: literal("manual") }),
16439
16788
  object({
16440
16789
  kind: literal("detection"),
16441
- /** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
16442
- * matches every raw class that maps to it; any other name matches
16443
- * exactly. */
16444
16790
  classes: array(string().min(1)).min(1),
16445
16791
  deviceIds: array(number().int().nonnegative()).min(1),
16446
- /** The track must have visited at least one of these zones. */
16447
16792
  zones: array(string().min(1)).min(1).optional(),
16448
- /** Best-so-far confidence the track must reach. */
16449
16793
  minScore: number().min(0).max(1).optional()
16450
16794
  }),
16451
16795
  object({
16452
16796
  kind: literal("stationary"),
16453
16797
  deviceIds: array(number().int().nonnegative()).min(1),
16454
- /** Class names; a macro class matches like the detection trigger. */
16455
16798
  classes: array(string().min(1)).min(1).optional(),
16456
- /** The parked object must be in at least one of these zones. */
16457
16799
  zones: array(string().min(1)).min(1).optional(),
16458
- /** `appeared`: it became parked. `departed`: the registry retired it.
16459
- * The vocabulary of `PipelineAnalyticsStationaryChanged`. */
16460
16800
  phase: _enum(["appeared", "departed"])
16461
16801
  }),
16462
16802
  object({
@@ -16464,6 +16804,143 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
16464
16804
  minutes: number().int().min(1).max(1440)
16465
16805
  })
16466
16806
  ]);
16807
+ var LegacyConditionLeafSchema = object({
16808
+ kind: literal("condition"),
16809
+ deviceId: number().int().nonnegative(),
16810
+ cap: string().min(1),
16811
+ fieldPath: string().min(1),
16812
+ operator: AutomationConditionOperatorSchema,
16813
+ value: union([
16814
+ string(),
16815
+ number(),
16816
+ boolean(),
16817
+ array(union([string(), number()])),
16818
+ QuantityValueSchema
16819
+ ]),
16820
+ unit: string().min(1).optional()
16821
+ });
16822
+ var LegacyStationaryPresentSchema = object({
16823
+ kind: literal("stationary-present"),
16824
+ deviceId: number().int().nonnegative(),
16825
+ classes: array(string().min(1)).min(1).optional(),
16826
+ zones: array(string().min(1)).min(1).optional()
16827
+ });
16828
+ var LegacyExpressionSchema = object({
16829
+ kind: literal("expression"),
16830
+ expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
16831
+ bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
16832
+ }).superRefine((src, ctx) => {
16833
+ const err = validateExpressionSource(src);
16834
+ if (err !== null) ctx.addIssue({
16835
+ code: "custom",
16836
+ message: err,
16837
+ path: ["expr"]
16838
+ });
16839
+ });
16840
+ var LegacyConditionSchema = lazy(() => discriminatedUnion("kind", [
16841
+ object({
16842
+ kind: literal("all"),
16843
+ children: array(LegacyConditionSchema)
16844
+ }),
16845
+ object({
16846
+ kind: literal("any"),
16847
+ children: array(LegacyConditionSchema)
16848
+ }),
16849
+ object({
16850
+ kind: literal("not"),
16851
+ child: LegacyConditionSchema
16852
+ }),
16853
+ LegacyConditionLeafSchema,
16854
+ LegacyExpressionSchema,
16855
+ LegacyStationaryPresentSchema
16856
+ ]));
16857
+ function convertTrigger(trigger) {
16858
+ switch (trigger.kind) {
16859
+ case "device-state": return {
16860
+ ...trigger,
16861
+ kind: "device-becomes"
16862
+ };
16863
+ case "detection": {
16864
+ const { classes, ...rest } = trigger;
16865
+ return {
16866
+ ...rest,
16867
+ subjects: classes.map(subjectFromClassName)
16868
+ };
16869
+ }
16870
+ case "stationary": {
16871
+ const { classes, ...rest } = trigger;
16872
+ return {
16873
+ ...rest,
16874
+ ...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
16875
+ };
16876
+ }
16877
+ default: return trigger;
16878
+ }
16879
+ }
16880
+ /** An old condition tree as a `when` subtree: `condition` → "is", classes → subjects. */
16881
+ function convertLegacyCondition(condition) {
16882
+ switch (condition.kind) {
16883
+ case "condition": return {
16884
+ ...condition,
16885
+ kind: "device-is"
16886
+ };
16887
+ case "stationary-present": {
16888
+ const { classes, ...rest } = condition;
16889
+ return {
16890
+ ...rest,
16891
+ ...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
16892
+ };
16893
+ }
16894
+ case "expression": return condition;
16895
+ case "not": return {
16896
+ kind: "not",
16897
+ child: convertLegacyCondition(condition.child)
16898
+ };
16899
+ case "all":
16900
+ case "any": return {
16901
+ kind: condition.kind,
16902
+ children: condition.children.map(convertLegacyCondition)
16903
+ };
16904
+ }
16905
+ }
16906
+ /** `all(any(t1…tn), C)`, or `any(t1…tn)`; a single trigger is not wrapped in `any`. */
16907
+ function convertLegacyWhen(half) {
16908
+ const leaves = half.triggers.map(convertTrigger);
16909
+ const first = leaves[0];
16910
+ const triggers = leaves.length === 1 && first !== void 0 ? first : {
16911
+ kind: "any",
16912
+ children: leaves
16913
+ };
16914
+ if (half.conditions === void 0) return triggers;
16915
+ return {
16916
+ kind: "all",
16917
+ children: [triggers, convertLegacyCondition(half.conditions)]
16918
+ };
16919
+ }
16920
+ /**
16921
+ * The automation RECIPE — what a visually-authored automation is, before it
16922
+ * becomes code.
16923
+ *
16924
+ * This is the data half of `docs/design/2026-08-08-automation-scripts-custom-devices.md`
16925
+ * Phase 6. A recipe is not an engine and never evaluates anything: it is a
16926
+ * declarative description that {@link generateAutomationBlock} compiles to the
16927
+ * TypeScript of an ordinary core block. There is therefore no second rule
16928
+ * engine, no second store and no second runtime — the block runner is the
16929
+ * runtime, `core_blocks` is the store (§2.1).
16930
+ *
16931
+ * Two parts:
16932
+ *
16933
+ * - **`when`** — ONE tree (`./when.ts`) of all/any/not over event leaves
16934
+ * (instants) and state leaves (durations). It replaces the flat trigger list
16935
+ * plus the separate condition tree; the firing rule is in that file and in
16936
+ * ADR "one tree".
16937
+ * - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
16938
+ * fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
16939
+ * this had to diverge from "verbatim", and why.
16940
+ *
16941
+ * A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
16942
+ * {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
16943
+ */
16467
16944
  /**
16468
16945
  * One action step.
16469
16946
  *
@@ -16566,69 +17043,49 @@ var AutomationRunModeSchema = discriminatedUnion("mode", [
16566
17043
  minutes: number().int().min(1).max(1440)
16567
17044
  })
16568
17045
  ]);
16569
- var AutomationRecipeSchema = object({
16570
- triggers: array(AutomationTriggerSchema),
16571
- conditions: AutomationConditionSchema.optional(),
17046
+ var CurrentAutomationRecipeSchema = object({
17047
+ when: AutomationWhenSchema,
16572
17048
  actions: array(AutomationActionSchema),
16573
17049
  runMode: AutomationRunModeSchema.optional(),
16574
17050
  /** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
16575
17051
  keepTraces: number().int().min(1).max(100).optional()
16576
17052
  });
16577
- new Set([
16578
- {
16579
- id: "person",
16580
- name: "Person"
16581
- },
16582
- {
16583
- id: "vehicle",
16584
- name: "Vehicle"
16585
- },
16586
- {
16587
- id: "animal",
16588
- name: "Animal"
16589
- },
16590
- {
16591
- id: "package",
16592
- name: "Package"
16593
- }
16594
- ].map((l) => l.id));
16595
- var COCO_TO_MACRO = {
16596
- mapping: {
16597
- person: "person",
16598
- bicycle: "vehicle",
16599
- car: "vehicle",
16600
- motorcycle: "vehicle",
16601
- airplane: "vehicle",
16602
- bus: "vehicle",
16603
- train: "vehicle",
16604
- truck: "vehicle",
16605
- boat: "vehicle",
16606
- bird: "animal",
16607
- cat: "animal",
16608
- dog: "animal",
16609
- horse: "animal",
16610
- sheep: "animal",
16611
- cow: "animal",
16612
- elephant: "animal",
16613
- bear: "animal",
16614
- zebra: "animal",
16615
- giraffe: "animal",
16616
- suitcase: "package",
16617
- backpack: "package",
16618
- handbag: "package"
16619
- },
16620
- preserveOriginal: false
16621
- };
17053
+ /** A recipe as it was stored before the single tree. Read-only: nothing writes this. */
17054
+ var LegacyAutomationRecipeSchema = object({
17055
+ triggers: array(LegacyTriggerSchema),
17056
+ conditions: LegacyConditionSchema.optional(),
17057
+ actions: array(AutomationActionSchema),
17058
+ runMode: AutomationRunModeSchema.optional(),
17059
+ keepTraces: number().int().min(1).max(100).optional()
17060
+ });
17061
+ /** The old shape as the new one, lossless. */
17062
+ function convertLegacyRecipe(legacy) {
17063
+ const { triggers, conditions, ...rest } = legacy;
17064
+ return {
17065
+ when: convertLegacyWhen({
17066
+ triggers,
17067
+ conditions
17068
+ }),
17069
+ ...rest
17070
+ };
17071
+ }
17072
+ /**
17073
+ * Where recipes are PARSED (the core-blocks cap, the MCP tool, the literal a
17074
+ * saved block carries): the new shape as it is, or the old shape converted to
17075
+ * it. The output type is always the new one.
17076
+ */
17077
+ var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
16622
17078
  new Set(Object.values(COCO_TO_MACRO.mapping));
16623
17079
  /**
16624
17080
  * Run traces — what an automation (or, later, an Action run) DID, node by node,
16625
17081
  * so the editor can paint every block green or red with its reason.
16626
17082
  *
16627
- * A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
16628
- * every condition node (containers and leaves, including the ones a
16629
- * short-circuit never reached), the run-mode verdict and every action step —
16630
- * each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
16631
- * for an Action run). Skipped
17083
+ * A trace is data, not a log: it covers the WHOLE tree — the event leaf that
17084
+ * fired, every node of the `when` tree (containers and leaves, including the
17085
+ * ones a short-circuit never reached), the run-mode verdict and every action
17086
+ * step — each addressed by a stable path (`when.0.1`, `actions.2`, or `steps.N`
17087
+ * for an Action run). Traces written before the single tree carry
17088
+ * `conditions.*` paths and still parse. Skipped
16632
17089
  * evaluations are traced too, and identical consecutive skips collapse into a
16633
17090
  * repeat count so a trigger that fails every 5 minutes cannot flush real runs.
16634
17091
  *
@@ -16660,6 +17117,8 @@ var TraceNodeOutcomeSchema = _enum([
16660
17117
  ]);
16661
17118
  var AutomationTriggerTraceSchema = object({
16662
17119
  kind: string().min(1),
17120
+ /** The event leaf that fired (`when`, `when.0.1`); absent on traces written before the single tree. */
17121
+ path: string().min(1).optional(),
16663
17122
  outcome: _enum(["pass", "fail"]),
16664
17123
  /** Why an event was rejected ("class person not in [dog]") or what fired. */
16665
17124
  reason: string(),
@@ -16667,15 +17126,23 @@ var AutomationTriggerTraceSchema = object({
16667
17126
  vars: record(string(), TraceValueSchema)
16668
17127
  });
16669
17128
  var AutomationConditionTraceNodeSchema = object({
16670
- /** `conditions` for the root, `conditions.<i>` for its i-th child, … */
17129
+ /** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
16671
17130
  path: string().min(1),
16672
17131
  kind: _enum([
16673
17132
  "all",
16674
17133
  "any",
16675
17134
  "not",
16676
17135
  "condition",
17136
+ "device-is",
16677
17137
  "expression",
16678
- "stationary-present"
17138
+ "stationary-present",
17139
+ "device-becomes",
17140
+ "device-event",
17141
+ "schedule",
17142
+ "interval",
17143
+ "manual",
17144
+ "detection",
17145
+ "stationary"
16679
17146
  ]),
16680
17147
  outcome: TraceNodeOutcomeSchema,
16681
17148
  reason: string(),
@@ -16709,6 +17176,12 @@ var AutomationStepTraceSchema = object({
16709
17176
  deviceId: number().int().optional(),
16710
17177
  startedAt: number().optional(),
16711
17178
  endedAt: number().optional(),
17179
+ /**
17180
+ * The parameters the step SENT, after `{{vars.x}}` substitution — what the
17181
+ * device was actually asked. Bounded like trigger `vars`; a secret parameter
17182
+ * is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
17183
+ */
17184
+ args: record(string(), TraceValueSchema).optional(),
16712
17185
  outcome: _enum([
16713
17186
  "ok",
16714
17187
  "error",
@@ -17458,6 +17931,23 @@ method(object({
17458
17931
  addonId: string(),
17459
17932
  nodeId: string().optional()
17460
17933
  }), SettingsSchemaWithValuesSchema.nullable()), method(object({
17934
+ addonId: string(),
17935
+ nodeId: string().optional()
17936
+ }), object({ keys: array(string()) }).nullable(), { auth: "admin" }), method(object({
17937
+ addonId: string(),
17938
+ nodeId: string().optional(),
17939
+ keys: array(string())
17940
+ }), object({
17941
+ removed: array(string()),
17942
+ refused: array(object({
17943
+ key: string(),
17944
+ reason: string()
17945
+ })),
17946
+ secretsCleared: number()
17947
+ }), {
17948
+ kind: "mutation",
17949
+ auth: "admin"
17950
+ }), method(object({
17461
17951
  addonId: string(),
17462
17952
  deviceId: number(),
17463
17953
  nodeId: string().optional()
@@ -18893,8 +19383,21 @@ var BrokerProviderInfoSchema = object({
18893
19383
  * (`{ sessionId }`) drops the session. The dialog that edits a broker
18894
19384
  * offers it as the default path; the fields the session fills are
18895
19385
  * marked `filledBySignIn` in the kind's settings schema.
19386
+ *
19387
+ * `methods` lists the ways the session can sign in, recommended first:
19388
+ * `'device-code'` adds `POST {basePath}/code/start` (body: the form's
19389
+ * settings) answering `{ sessionId, userCode, verificationUrl,
19390
+ * expiresAt }` — the operator types `userCode` at `verificationUrl` on
19391
+ * any device, and the same `status` / `commit` / `cancel` routes follow
19392
+ * (status then carries `method`, `userCode`, `verificationUrl`,
19393
+ * `expiresAt` and the phases `'awaiting-code'` / `'expired'`);
19394
+ * `'proxy'` is the `{basePath}/start` flow above. Absent means
19395
+ * `['proxy']`.
18896
19396
  */
18897
- signIn: object({ basePath: string().min(1) }).optional()
19397
+ signIn: object({
19398
+ basePath: string().min(1),
19399
+ methods: array(_enum(["device-code", "proxy"])).optional()
19400
+ }).optional()
18898
19401
  }))
18899
19402
  });
18900
19403
  var ListInputSchema = object({
@@ -21368,6 +21871,8 @@ var StorageKindSchema = object({
21368
21871
  "orphaned",
21369
21872
  "dropped"
21370
21873
  ]),
21874
+ /** An owner declared this kind since the engine started. */
21875
+ declaredThisBoot: boolean(),
21371
21876
  /** `null` when the caller asked for no counts (`counts: false`). */
21372
21877
  rows: number().int().nonnegative().nullable(),
21373
21878
  /** Rows x the average size of a sample; null for an empty kind. */
@@ -21379,6 +21884,51 @@ var StorageKindSchema = object({
21379
21884
  /** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
21380
21885
  declaration: RecordKindDeclarationSchema
21381
21886
  });
21887
+ _enum([
21888
+ "orphaned",
21889
+ "stale",
21890
+ "unknown"
21891
+ ]);
21892
+ /** A record kind nobody declared since its owner started, or whose owner is gone. */
21893
+ var StaleKindFindingSchema = object({
21894
+ owner: string(),
21895
+ kind: string(),
21896
+ /** `unknown`: the owner could not be judged (not running, or its state unreadable) - never dropped. */
21897
+ verdict: _enum(["orphaned", "unknown"]),
21898
+ reason: string(),
21899
+ rows: number().int().nonnegative().nullable(),
21900
+ /** local-auth's tables are never dropped by this audit. */
21901
+ protected: boolean()
21902
+ });
21903
+ /** A stored global-settings key the running addon no longer declares. */
21904
+ var StaleSettingsKeyFindingSchema = object({
21905
+ addonId: string(),
21906
+ /** The stored key as written, a per-node `<key>@<nodeId>` suffix included. */
21907
+ key: string(),
21908
+ /**
21909
+ * Keychain refs whose ONLY marker sits under this key: in use by stale data,
21910
+ * not orphans yet. Removing the key releases them.
21911
+ */
21912
+ secretRefs: array(string())
21913
+ });
21914
+ /** An addon the audit could not judge, with the reason. Never reported as stale. */
21915
+ var StaleUnknownSchema = object({
21916
+ subject: string(),
21917
+ reason: string()
21918
+ });
21919
+ var StaleDataReportSchema = object({
21920
+ /** Epoch ms of the last finished scan; `null` before the first. */
21921
+ scannedAt: number().nullable(),
21922
+ scanning: boolean(),
21923
+ trigger: _enum([
21924
+ "boot",
21925
+ "interval",
21926
+ "manual"
21927
+ ]).nullable(),
21928
+ kinds: array(StaleKindFindingSchema),
21929
+ settingsKeys: array(StaleSettingsKeyFindingSchema),
21930
+ unknown: array(StaleUnknownSchema)
21931
+ });
21382
21932
  method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
21383
21933
  owner: string().min(1).optional(),
21384
21934
  /** `false` skips the per-kind row count (a catalog read only). Default true. */
@@ -21386,7 +21936,30 @@ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: arr
21386
21936
  }), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
21387
21937
  owner: string().min(1),
21388
21938
  kind: RecordKindNameSchema
21389
- }), RecordQueryResultSchema, { auth: "admin" });
21939
+ }), RecordQueryResultSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, {
21940
+ kind: "mutation",
21941
+ auth: "admin"
21942
+ }), method(object({
21943
+ owner: string().min(1),
21944
+ kind: RecordKindNameSchema
21945
+ }), object({
21946
+ deleted: number().int().nonnegative(),
21947
+ secretsReleased: number().int().nonnegative()
21948
+ }), {
21949
+ kind: "mutation",
21950
+ auth: "admin",
21951
+ caller: "required"
21952
+ }), method(object({
21953
+ addonId: string().min(1),
21954
+ key: string().min(1)
21955
+ }), object({
21956
+ removed: boolean(),
21957
+ secretsCleared: number().int().nonnegative()
21958
+ }), {
21959
+ kind: "mutation",
21960
+ auth: "admin",
21961
+ caller: "required"
21962
+ });
21390
21963
  /**
21391
21964
  * record-entries - the ONE admin surface for every list of user-created entries
21392
21965
  * (D881): notification targets, brokers, LLM profiles, anything an addon
@@ -21525,6 +22098,12 @@ method(object({
21525
22098
  }), method(object({ deviceId: number().int() }), object({ refs: array(string()).readonly() }), {
21526
22099
  kind: "mutation",
21527
22100
  auth: "admin"
22101
+ }), method(object({
22102
+ refs: RefListSchema,
22103
+ onBehalfOf: string().min(1)
22104
+ }), object({ released: number().int() }), {
22105
+ kind: "mutation",
22106
+ auth: "admin"
21528
22107
  }), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
21529
22108
  /**
21530
22109
  * device-history — the ONE mechanism for numeric history (D850).
@@ -22314,7 +22893,9 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
22314
22893
  /** Upstream-system identity + rendering hints for the candidate. */
22315
22894
  sourceInfo: SourceInfoSchema.optional(),
22316
22895
  /** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
22317
- children: array(DiscoveredChildDeviceSchema).readonly().optional()
22896
+ children: array(DiscoveredChildDeviceSchema).readonly().optional(),
22897
+ /** `childNativeId` of the gateway candidate in the same list this device sits behind. */
22898
+ parentStableId: string().optional()
22318
22899
  }));
22319
22900
  /**
22320
22901
  * Status block — populated by the integration after each
@@ -42629,6 +43210,79 @@ object({
42629
43210
  metadata: record(string(), unknown()).optional()
42630
43211
  });
42631
43212
  /**
43213
+ * HOW a provider can be found on the network. The provider only DECLARES the
43214
+ * mode; the `local-network` scan engine runs it once for every provider that
43215
+ * asked (one multicast browse, one host sweep) and hands the raw hits back to
43216
+ * `classifyDiscoveryHits`. A provider never opens a discovery socket itself,
43217
+ * except through a `custom` mode.
43218
+ */
43219
+ var DiscoveryModeSchema = discriminatedUnion("kind", [
43220
+ object({
43221
+ kind: literal("mdns"),
43222
+ /** DNS-SD service type, e.g. `_esphomelib._tcp`. */
43223
+ serviceType: string().min(1),
43224
+ /** Regular expression an instance name must match (case-insensitive). */
43225
+ instancePattern: string().optional()
43226
+ }),
43227
+ object({
43228
+ kind: literal("ws-discovery"),
43229
+ /** Probe types (e.g. `dn:NetworkVideoTransmitter`); omitted = any device. */
43230
+ types: array(string()).optional()
43231
+ }),
43232
+ object({
43233
+ kind: literal("ssdp"),
43234
+ searchTarget: string().min(1)
43235
+ }),
43236
+ object({
43237
+ kind: literal("udp-broadcast"),
43238
+ port: number().int().min(1).max(65535),
43239
+ payloadHex: string().regex(/^([0-9a-fA-F]{2})*$/),
43240
+ /** Port replies arrive on, when the device answers somewhere other than the source port. */
43241
+ listenPort: number().int().min(1).max(65535).optional()
43242
+ }),
43243
+ object({
43244
+ kind: literal("http-probe"),
43245
+ port: number().int().min(1).max(65535),
43246
+ path: string().startsWith("/"),
43247
+ method: _enum(["GET", "POST"]).default("GET"),
43248
+ bodyText: string().optional(),
43249
+ contentType: string().optional(),
43250
+ tls: boolean().default(false),
43251
+ /** Status codes that count as a hit. Omitted = any HTTP answer. */
43252
+ acceptStatus: array(number().int()).optional()
43253
+ }),
43254
+ object({
43255
+ kind: literal("custom"),
43256
+ /** Why the engine cannot do it (an allowlisted exception, e.g. a vendor UDP protocol). */
43257
+ reason: string().min(1)
43258
+ })
43259
+ ]);
43260
+ /** What the engine saw on the wire for ONE declared mode; the provider classifies it. */
43261
+ var DiscoveryHitSchema = object({
43262
+ /** Index into the provider's own `getDiscoveryModes()` array. */
43263
+ modeIndex: number().int().min(0),
43264
+ host: string(),
43265
+ port: number().int(),
43266
+ http: object({
43267
+ status: number().int(),
43268
+ headers: record(string(), string()),
43269
+ /** At most 16 KiB. */
43270
+ bodyText: string()
43271
+ }).optional(),
43272
+ mdns: object({
43273
+ instance: string(),
43274
+ host: string(),
43275
+ port: number().int(),
43276
+ txt: record(string(), string())
43277
+ }).optional(),
43278
+ udp: object({ payloadHex: string() }).optional(),
43279
+ wsd: object({
43280
+ xaddrs: array(string()),
43281
+ types: array(string()),
43282
+ scopes: array(string())
43283
+ }).optional()
43284
+ });
43285
+ /**
42632
43286
  * Candidate handed back from discovery and accepted by
42633
43287
  * `adoptDiscoveredDevice`. Shape mirrors the in-process
42634
43288
  * `DiscoveredDevice` interface used by `DeviceDiscovery`.
@@ -42663,7 +43317,17 @@ var DiscoveryCandidateSchema = object({
42663
43317
  /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
42664
43318
  onboardedDeviceId: number().optional(),
42665
43319
  /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
42666
- onboardedName: string().optional()
43320
+ onboardedName: string().optional(),
43321
+ /**
43322
+ * This candidate is an INTEGRATION to set up (a gateway, a hub, a controller),
43323
+ * not a device to adopt. Replaces the `prefilledConfig.setupIntegration` marker,
43324
+ * which is still read for one release.
43325
+ */
43326
+ setupIntegration: boolean().optional(),
43327
+ /** `stableId` of the gateway candidate this device was found BEHIND. */
43328
+ parentStableId: string().optional(),
43329
+ /** `false` = shown for context but not pickable. Default true. */
43330
+ selectable: boolean().optional()
42667
43331
  });
42668
43332
  /**
42669
43333
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -42716,6 +43380,12 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
42716
43380
  }), method(object({}), boolean()), method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
42717
43381
  kind: "mutation",
42718
43382
  auth: "admin"
43383
+ }), method(object({}), array(DiscoveryModeSchema)), method(object({
43384
+ hits: array(DiscoveryHitSchema),
43385
+ params: record(string(), unknown()).optional()
43386
+ }), array(DiscoveryCandidateSchema), {
43387
+ kind: "mutation",
43388
+ auth: "admin"
42719
43389
  }), method(object({}), CreationSchemaOutputSchema), method(object({}), object({ deviceType: _enum(DeviceType).nullable() })), method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
42720
43390
  kind: "mutation",
42721
43391
  auth: "admin"
@@ -43443,17 +44113,23 @@ method(object({
43443
44113
  }), method(object({}), object({ providers: array(object({
43444
44114
  addonId: string(),
43445
44115
  label: string()
43446
- })).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
43447
- addonId: string(),
43448
- label: string(),
43449
- candidates: array(DiscoveryCandidateSchema).readonly(),
43450
- error: string().nullable()
43451
- })).readonly() }), {
43452
- kind: "mutation",
43453
- auth: "admin"
43454
- }), method(object({
44116
+ })).readonly() }), { auth: "admin" }), method(
44117
+ /** `subnets`: extra CIDRs for THIS scan, on top of the hub's own and the saved list. */
44118
+ object({ subnets: array(string()).optional() }),
44119
+ object({ groups: array(object({
44120
+ addonId: string(),
44121
+ label: string(),
44122
+ candidates: array(DiscoveryCandidateSchema).readonly(),
44123
+ error: string().nullable()
44124
+ })).readonly() }),
44125
+ {
44126
+ kind: "mutation",
44127
+ auth: "admin"
44128
+ }
44129
+ ), method(object({
43455
44130
  addonId: string(),
43456
- params: record(string(), unknown()).optional()
44131
+ params: record(string(), unknown()).optional(),
44132
+ subnets: array(string()).optional()
43457
44133
  }), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
43458
44134
  kind: "mutation",
43459
44135
  auth: "admin"
@@ -61073,7 +61749,9 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
61073
61749
  id: string(),
61074
61750
  settings: record(string(), unknown()),
61075
61751
  /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
61076
- secretKeys: array(string()).optional()
61752
+ secretKeys: array(string()).optional(),
61753
+ /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
61754
+ skipRestart: boolean().optional()
61077
61755
  }), object({ success: boolean() }), {
61078
61756
  kind: "mutation",
61079
61757
  auth: "admin"
@@ -61550,6 +62228,39 @@ var TlsStatusSchema = object({
61550
62228
  hasCa: boolean()
61551
62229
  }).nullable()
61552
62230
  });
62231
+ /** One provider's ask of the scan engine: the modes it declared. */
62232
+ var ScanRequestSchema = object({
62233
+ providerId: string().min(1),
62234
+ modes: array(DiscoveryModeSchema)
62235
+ });
62236
+ var ScanErrorSchema = object({
62237
+ /** Absent for a failure that is the scan's own (a bad subnet, a socket that would not open). */
62238
+ providerId: string().optional(),
62239
+ /** The mode family that failed (`mdns`, `http-probe`, …). */
62240
+ mode: string().optional(),
62241
+ message: string()
62242
+ });
62243
+ /** What one scan did, per mode family; the engine logs the same numbers once. */
62244
+ var ScanStatsSchema = object({
62245
+ hostsProbed: number().int(),
62246
+ hits: number().int(),
62247
+ timeouts: number().int(),
62248
+ byMode: record(string(), object({
62249
+ probed: number().int(),
62250
+ hits: number().int(),
62251
+ timeouts: number().int()
62252
+ }))
62253
+ });
62254
+ var ScanNetworkResultSchema = object({
62255
+ hits: array(object({
62256
+ providerId: string(),
62257
+ hit: DiscoveryHitSchema
62258
+ })),
62259
+ /** CIDRs actually covered: the hub's own /24s, the saved list and the per-call list. */
62260
+ subnetsScanned: array(string()),
62261
+ errors: array(ScanErrorSchema),
62262
+ stats: ScanStatsSchema
62263
+ });
61553
62264
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
61554
62265
  /**
61555
62266
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -61604,6 +62315,16 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
61604
62315
  }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
61605
62316
  kind: "mutation",
61606
62317
  auth: "admin"
62318
+ }), method(object({
62319
+ requests: array(ScanRequestSchema),
62320
+ subnets: array(string()).optional()
62321
+ }), ScanNetworkResultSchema, {
62322
+ kind: "mutation",
62323
+ auth: "admin",
62324
+ timeoutMs: 9e4
62325
+ }), method(_void(), object({ subnets: array(string()) }), { auth: "admin" }), method(object({ subnets: array(string()) }), object({ subnets: array(string()) }), {
62326
+ kind: "mutation",
62327
+ auth: "admin"
61607
62328
  }), method(_void(), TlsStatusSchema, {
61608
62329
  kind: "mutation",
61609
62330
  auth: "admin"
@@ -64146,7 +64867,7 @@ object({
64146
64867
  });
64147
64868
  Object.values(DeviceType);
64148
64869
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
64149
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
64870
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Switch, DeviceType.Gateway, method(object({ deviceId: number().int().nonnegative() }), object({ success: literal(true) }), {
64150
64871
  kind: "mutation",
64151
64872
  auth: "admin"
64152
64873
  });
@@ -68106,7 +68827,7 @@ discriminatedUnion("kind", [object({
68106
68827
  })]);
68107
68828
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
68108
68829
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
68109
- 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;
68830
+ 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;
68110
68831
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
68111
68832
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
68112
68833
  new Set(CAP_NAMES);
@@ -68159,7 +68880,7 @@ var AccessoryKind = {
68159
68880
  PrivacyMask: DeviceRole.PrivacyMask
68160
68881
  };
68161
68882
  AccessoryKind.Siren, AccessoryKind.Floodlight, AccessoryKind.Spotlight, AccessoryKind.PirSensor, AccessoryKind.Chime, AccessoryKind.Autotrack, AccessoryKind.Nightvision, AccessoryKind.PrivacyMask;
68162
- 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;
68883
+ 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;
68163
68884
  new Set(Object.values(DeviceType));
68164
68885
  Object.freeze({
68165
68886
  "accessories.getStatus": {
@@ -68547,6 +69268,13 @@ Object.freeze({
68547
69268
  access: "create",
68548
69269
  role: "admin"
68549
69270
  },
69271
+ "addonSettings.getDeclaredSettingsKeys": {
69272
+ capName: "addon-settings",
69273
+ capScope: "system",
69274
+ addonId: null,
69275
+ access: "view",
69276
+ role: "admin"
69277
+ },
68550
69278
  "addonSettings.getDeviceSettings": {
68551
69279
  capName: "addon-settings",
68552
69280
  capScope: "system",
@@ -68568,6 +69296,13 @@ Object.freeze({
68568
69296
  access: "view",
68569
69297
  role: "admin"
68570
69298
  },
69299
+ "addonSettings.unsetStoredSettingsKeys": {
69300
+ capName: "addon-settings",
69301
+ capScope: "system",
69302
+ addonId: null,
69303
+ access: "create",
69304
+ role: "admin"
69305
+ },
68571
69306
  "addonSettings.updateDeviceSettings": {
68572
69307
  capName: "addon-settings",
68573
69308
  capScope: "system",
@@ -70983,6 +71718,13 @@ Object.freeze({
70983
71718
  access: "create",
70984
71719
  role: "admin"
70985
71720
  },
71721
+ "deviceProvider.classifyDiscoveryHits": {
71722
+ capName: "device-provider",
71723
+ capScope: "system",
71724
+ addonId: null,
71725
+ access: "create",
71726
+ role: "admin"
71727
+ },
70986
71728
  "deviceProvider.createDevice": {
70987
71729
  capName: "device-provider",
70988
71730
  capScope: "system",
@@ -71011,6 +71753,13 @@ Object.freeze({
71011
71753
  access: "view",
71012
71754
  role: "admin"
71013
71755
  },
71756
+ "deviceProvider.getDiscoveryModes": {
71757
+ capName: "device-provider",
71758
+ capScope: "system",
71759
+ addonId: null,
71760
+ access: "view",
71761
+ role: "admin"
71762
+ },
71014
71763
  "deviceProvider.getDiscoveryParamsSchema": {
71015
71764
  capName: "device-provider",
71016
71765
  capScope: "system",
@@ -71788,6 +72537,13 @@ Object.freeze({
71788
72537
  access: "create",
71789
72538
  role: "admin"
71790
72539
  },
72540
+ "keychain.releaseRecordRefs": {
72541
+ capName: "keychain",
72542
+ capScope: "system",
72543
+ addonId: null,
72544
+ access: "create",
72545
+ role: "admin"
72546
+ },
71791
72547
  "keychain.resolve": {
71792
72548
  capName: "keychain",
71793
72549
  capScope: "system",
@@ -72208,6 +72964,13 @@ Object.freeze({
72208
72964
  access: "view",
72209
72965
  role: "admin"
72210
72966
  },
72967
+ "localNetwork.getScanSubnets": {
72968
+ capName: "local-network",
72969
+ capScope: "system",
72970
+ addonId: null,
72971
+ access: "view",
72972
+ role: "admin"
72973
+ },
72211
72974
  "localNetwork.getTlsStatus": {
72212
72975
  capName: "local-network",
72213
72976
  capScope: "system",
@@ -72257,6 +73020,13 @@ Object.freeze({
72257
73020
  access: "create",
72258
73021
  role: "admin"
72259
73022
  },
73023
+ "localNetwork.scanNetwork": {
73024
+ capName: "local-network",
73025
+ capScope: "system",
73026
+ addonId: null,
73027
+ access: "create",
73028
+ role: "admin"
73029
+ },
72260
73030
  "localNetwork.setAddressRole": {
72261
73031
  capName: "local-network",
72262
73032
  capScope: "system",
@@ -72278,6 +73048,13 @@ Object.freeze({
72278
73048
  access: "create",
72279
73049
  role: "admin"
72280
73050
  },
73051
+ "localNetwork.setScanSubnets": {
73052
+ capName: "local-network",
73053
+ capScope: "system",
73054
+ addonId: null,
73055
+ access: "create",
73056
+ role: "admin"
73057
+ },
72281
73058
  "localNetwork.setViewerEndpoints": {
72282
73059
  capName: "local-network",
72283
73060
  capScope: "system",
@@ -76499,6 +77276,13 @@ Object.freeze({
76499
77276
  access: "create",
76500
77277
  role: "admin"
76501
77278
  },
77279
+ "storageAdmin.dropOrphanedKind": {
77280
+ capName: "storage-admin",
77281
+ capScope: "system",
77282
+ addonId: null,
77283
+ access: "delete",
77284
+ role: "admin"
77285
+ },
76502
77286
  "storageAdmin.listKinds": {
76503
77287
  capName: "storage-admin",
76504
77288
  capScope: "system",
@@ -76520,6 +77304,27 @@ Object.freeze({
76520
77304
  access: "view",
76521
77305
  role: "admin"
76522
77306
  },
77307
+ "storageAdmin.scanStaleData": {
77308
+ capName: "storage-admin",
77309
+ capScope: "system",
77310
+ addonId: null,
77311
+ access: "create",
77312
+ role: "admin"
77313
+ },
77314
+ "storageAdmin.staleDataReport": {
77315
+ capName: "storage-admin",
77316
+ capScope: "system",
77317
+ addonId: null,
77318
+ access: "view",
77319
+ role: "admin"
77320
+ },
77321
+ "storageAdmin.unsetStaleSettingsKey": {
77322
+ capName: "storage-admin",
77323
+ capScope: "system",
77324
+ addonId: null,
77325
+ access: "create",
77326
+ role: "admin"
77327
+ },
76523
77328
  "storageEvictable.evict": {
76524
77329
  capName: "storage-evictable",
76525
77330
  capScope: "system",
@@ -82216,7 +83021,7 @@ function projectAction(cap, action) {
82216
83021
  };
82217
83022
  }
82218
83023
  Object.entries(CAP_VOCABULARY_BY_NAME).flatMap(([cap, entry]) => entry.scope === "device" ? (entry.actions ?? []).map((a) => projectAction(cap, a)) : []);
82219
- 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;
83024
+ 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;
82220
83025
  DeviceFeature.BatteryOperated;
82221
83026
  DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher;
82222
83027
  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.";