@camstack/addon-training 0.1.112 → 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 (22) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/{MotionZonesSettings-1jL3cNrP.mjs → MotionZonesSettings-Fo1pD1Rt.mjs} +2 -2
  3. package/dist/{PrivacyMaskSettings-B5sDvjd1.mjs → PrivacyMaskSettings-CxJF-sR6.mjs} +2 -2
  4. package/dist/{SceneMonitorEditor-DUupJZPg.mjs → SceneMonitorEditor-BVjgmQ6j.mjs} +2 -2
  5. package/dist/_stub.js +5211 -5181
  6. package/dist/{_virtual_mf-localSharedImportMap___mfe_internal__addon_training_page-CcSHMntB.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-DP3QaLL9.mjs → dist-B7trMqj7.mjs} +901 -115
  9. package/dist/{dist-BWaaetIL.js → dist-j2JtuQ21.js} +901 -115
  10. package/dist/{hostInit-CL4HR5_U.mjs → hostInit-7-7XYgjM.mjs} +2 -2
  11. package/dist/index.js +1 -1
  12. package/dist/orchestrator/training.addon.js +1 -1
  13. package/dist/orchestrator/training.addon.mjs +1 -1
  14. package/dist/{player-overlays-Db2cJVG2.mjs → player-overlays-DVT_LAXt.mjs} +1 -1
  15. package/dist/providers/modal/training-modal.addon.js +1 -1
  16. package/dist/providers/modal/training-modal.addon.mjs +1 -1
  17. package/dist/remoteEntry.js +1 -1
  18. package/dist/{responsive-CqQQYlZD.mjs → responsive-DhFXZ4Mn.mjs} +1 -1
  19. package/dist/{scene-monitor-copy-D-mtbfGW.mjs → scene-monitor-copy-Busxg-ds.mjs} +1 -1
  20. package/dist/{virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-EkAqF5M1.mjs → virtual_mf-REMOTE_ENTRY_ID___mfe_internal__addon_training_page__remoteEntry_js-DlmMJ8_m.mjs} +1 -1
  21. package/package.json +1 -1
  22. package/dist/_virtual_mf___mfe_internal__addon_training_page__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-os6FZfGz.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) {
@@ -16331,8 +16437,172 @@ object({
16331
16437
  quantity: ReadingQuantitySchema,
16332
16438
  unit: string().min(1)
16333
16439
  });
16334
- /** How a leaf compares a device field to a value. Derived from the field's
16335
- * `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. */
16336
16606
  var AutomationConditionOperatorSchema = _enum([
16337
16607
  "eq",
16338
16608
  "ne",
@@ -16343,12 +16613,76 @@ var AutomationConditionOperatorSchema = _enum([
16343
16613
  "contains",
16344
16614
  "in"
16345
16615
  ]);
16346
- var AutomationConditionLeafSchema = object({
16347
- 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"),
16348
16676
  deviceId: number().int().nonnegative(),
16349
16677
  cap: string().min(1),
16350
16678
  fieldPath: string().min(1),
16351
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
+ */
16352
16686
  value: union([
16353
16687
  string(),
16354
16688
  number(),
@@ -16356,24 +16690,26 @@ var AutomationConditionLeafSchema = object({
16356
16690
  array(union([string(), number()])),
16357
16691
  QuantityValueSchema
16358
16692
  ]),
16693
+ /** The unit the person picked for `value` (display only; `value` is in the base unit). */
16359
16694
  unit: string().min(1).optional()
16360
16695
  });
16361
- 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({
16362
16702
  kind: literal("stationary-present"),
16363
16703
  deviceId: number().int().nonnegative(),
16364
- /** Class names; a macro class matches every raw class that maps to it. */
16365
- classes: array(string().min(1)).min(1).optional(),
16366
- /** The parked object must be in at least one of these zones. */
16704
+ subjects: array(StationarySubjectSchema).min(1).optional(),
16367
16705
  zones: array(string().min(1)).min(1).optional()
16368
16706
  });
16369
16707
  /**
16370
- * The expression leaf, declared as a plain object rather than an intersection
16371
- * with {@link ExpressionSourceSchema}: a discriminated union has to be able to
16372
- * read `kind` off each option, and an intersection hides it. The author-time
16373
- * validation is the SAME function `ExpressionSourceSchema` runs, so the two
16374
- * 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.
16375
16711
  */
16376
- var AutomationConditionExpressionSchema = object({
16712
+ var ExpressionLeafSchema = object({
16377
16713
  kind: literal("expression"),
16378
16714
  expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
16379
16715
  bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
@@ -16385,55 +16721,62 @@ var AutomationConditionExpressionSchema = object({
16385
16721
  path: ["expr"]
16386
16722
  });
16387
16723
  });
16388
- var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
16724
+ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
16389
16725
  object({
16390
16726
  kind: literal("all"),
16391
- children: array(AutomationConditionSchema)
16727
+ children: array(AutomationWhenSchema)
16392
16728
  }),
16393
16729
  object({
16394
16730
  kind: literal("any"),
16395
- children: array(AutomationConditionSchema)
16731
+ children: array(AutomationWhenSchema)
16396
16732
  }),
16397
16733
  object({
16398
16734
  kind: literal("not"),
16399
- child: AutomationConditionSchema
16735
+ child: AutomationWhenSchema
16400
16736
  }),
16401
- AutomationConditionLeafSchema,
16402
- AutomationConditionExpressionSchema,
16403
- AutomationConditionStationaryPresentSchema
16737
+ DeviceBecomesLeafSchema,
16738
+ DeviceEventLeafSchema,
16739
+ ScheduleLeafSchema,
16740
+ ManualLeafSchema,
16741
+ IntervalLeafSchema,
16742
+ DetectionLeafSchema,
16743
+ StationaryLeafSchema,
16744
+ DeviceIsLeafSchema,
16745
+ StationaryPresentLeafSchema,
16746
+ ExpressionLeafSchema
16404
16747
  ]));
16405
16748
  /**
16406
- * What starts a run.
16407
- *
16408
- * D8 compliance, and it is the reason `device-state` is not merely an event
16409
- * subscription: the trigger evaluates against the **state mirror**, which is
16410
- * reconciled, and an event only WAKES the evaluation. A dropped event therefore
16411
- * DELAYS a trigger; it does not lose it. `schedule` uses `croner` — the one
16412
- * already in the repo — because `setInterval(24h)` drifts and "at 23:30" does
16413
- * not.
16414
- */
16415
- 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", [
16416
16764
  object({
16417
16765
  kind: literal("device-state"),
16418
16766
  deviceId: number().int().nonnegative(),
16419
16767
  cap: string().min(1),
16420
16768
  fieldPath: string().min(1),
16421
- /** Fire when the field takes this value. Omit to fire on any change. */
16422
16769
  becomes: union([
16423
16770
  string(),
16424
16771
  number(),
16425
16772
  boolean()
16426
16773
  ]).optional(),
16427
- /** Only on a CHANGE of value, not on every re-report. */
16428
16774
  edge: boolean().optional(),
16429
- /** The condition must hold this long before the run starts. */
16430
16775
  forMs: number().int().min(0).max(864e5).optional(),
16431
- /** Collapse a burst into one run. */
16432
16776
  debounceMs: number().int().min(0).max(6e5).optional()
16433
16777
  }),
16434
16778
  object({
16435
16779
  kind: literal("device-event"),
16436
- /** An `EventCategory` value. */
16437
16780
  category: string().min(1),
16438
16781
  deviceId: number().int().nonnegative().optional()
16439
16782
  }),
@@ -16444,25 +16787,16 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
16444
16787
  object({ kind: literal("manual") }),
16445
16788
  object({
16446
16789
  kind: literal("detection"),
16447
- /** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
16448
- * matches every raw class that maps to it; any other name matches
16449
- * exactly. */
16450
16790
  classes: array(string().min(1)).min(1),
16451
16791
  deviceIds: array(number().int().nonnegative()).min(1),
16452
- /** The track must have visited at least one of these zones. */
16453
16792
  zones: array(string().min(1)).min(1).optional(),
16454
- /** Best-so-far confidence the track must reach. */
16455
16793
  minScore: number().min(0).max(1).optional()
16456
16794
  }),
16457
16795
  object({
16458
16796
  kind: literal("stationary"),
16459
16797
  deviceIds: array(number().int().nonnegative()).min(1),
16460
- /** Class names; a macro class matches like the detection trigger. */
16461
16798
  classes: array(string().min(1)).min(1).optional(),
16462
- /** The parked object must be in at least one of these zones. */
16463
16799
  zones: array(string().min(1)).min(1).optional(),
16464
- /** `appeared`: it became parked. `departed`: the registry retired it.
16465
- * The vocabulary of `PipelineAnalyticsStationaryChanged`. */
16466
16800
  phase: _enum(["appeared", "departed"])
16467
16801
  }),
16468
16802
  object({
@@ -16470,6 +16804,143 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
16470
16804
  minutes: number().int().min(1).max(1440)
16471
16805
  })
16472
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
+ */
16473
16944
  /**
16474
16945
  * One action step.
16475
16946
  *
@@ -16572,69 +17043,49 @@ var AutomationRunModeSchema = discriminatedUnion("mode", [
16572
17043
  minutes: number().int().min(1).max(1440)
16573
17044
  })
16574
17045
  ]);
16575
- var AutomationRecipeSchema = object({
16576
- triggers: array(AutomationTriggerSchema),
16577
- conditions: AutomationConditionSchema.optional(),
17046
+ var CurrentAutomationRecipeSchema = object({
17047
+ when: AutomationWhenSchema,
16578
17048
  actions: array(AutomationActionSchema),
16579
17049
  runMode: AutomationRunModeSchema.optional(),
16580
17050
  /** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
16581
17051
  keepTraces: number().int().min(1).max(100).optional()
16582
17052
  });
16583
- new Set([
16584
- {
16585
- id: "person",
16586
- name: "Person"
16587
- },
16588
- {
16589
- id: "vehicle",
16590
- name: "Vehicle"
16591
- },
16592
- {
16593
- id: "animal",
16594
- name: "Animal"
16595
- },
16596
- {
16597
- id: "package",
16598
- name: "Package"
16599
- }
16600
- ].map((l) => l.id));
16601
- var COCO_TO_MACRO = {
16602
- mapping: {
16603
- person: "person",
16604
- bicycle: "vehicle",
16605
- car: "vehicle",
16606
- motorcycle: "vehicle",
16607
- airplane: "vehicle",
16608
- bus: "vehicle",
16609
- train: "vehicle",
16610
- truck: "vehicle",
16611
- boat: "vehicle",
16612
- bird: "animal",
16613
- cat: "animal",
16614
- dog: "animal",
16615
- horse: "animal",
16616
- sheep: "animal",
16617
- cow: "animal",
16618
- elephant: "animal",
16619
- bear: "animal",
16620
- zebra: "animal",
16621
- giraffe: "animal",
16622
- suitcase: "package",
16623
- backpack: "package",
16624
- handbag: "package"
16625
- },
16626
- preserveOriginal: false
16627
- };
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)]);
16628
17078
  new Set(Object.values(COCO_TO_MACRO.mapping));
16629
17079
  /**
16630
17080
  * Run traces — what an automation (or, later, an Action run) DID, node by node,
16631
17081
  * so the editor can paint every block green or red with its reason.
16632
17082
  *
16633
- * A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
16634
- * every condition node (containers and leaves, including the ones a
16635
- * short-circuit never reached), the run-mode verdict and every action step —
16636
- * each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
16637
- * 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
16638
17089
  * evaluations are traced too, and identical consecutive skips collapse into a
16639
17090
  * repeat count so a trigger that fails every 5 minutes cannot flush real runs.
16640
17091
  *
@@ -16666,6 +17117,8 @@ var TraceNodeOutcomeSchema = _enum([
16666
17117
  ]);
16667
17118
  var AutomationTriggerTraceSchema = object({
16668
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(),
16669
17122
  outcome: _enum(["pass", "fail"]),
16670
17123
  /** Why an event was rejected ("class person not in [dog]") or what fired. */
16671
17124
  reason: string(),
@@ -16673,15 +17126,23 @@ var AutomationTriggerTraceSchema = object({
16673
17126
  vars: record(string(), TraceValueSchema)
16674
17127
  });
16675
17128
  var AutomationConditionTraceNodeSchema = object({
16676
- /** `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). */
16677
17130
  path: string().min(1),
16678
17131
  kind: _enum([
16679
17132
  "all",
16680
17133
  "any",
16681
17134
  "not",
16682
17135
  "condition",
17136
+ "device-is",
16683
17137
  "expression",
16684
- "stationary-present"
17138
+ "stationary-present",
17139
+ "device-becomes",
17140
+ "device-event",
17141
+ "schedule",
17142
+ "interval",
17143
+ "manual",
17144
+ "detection",
17145
+ "stationary"
16685
17146
  ]),
16686
17147
  outcome: TraceNodeOutcomeSchema,
16687
17148
  reason: string(),
@@ -16715,6 +17176,12 @@ var AutomationStepTraceSchema = object({
16715
17176
  deviceId: number().int().optional(),
16716
17177
  startedAt: number().optional(),
16717
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(),
16718
17185
  outcome: _enum([
16719
17186
  "ok",
16720
17187
  "error",
@@ -17464,6 +17931,23 @@ method(object({
17464
17931
  addonId: string(),
17465
17932
  nodeId: string().optional()
17466
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({
17467
17951
  addonId: string(),
17468
17952
  deviceId: number(),
17469
17953
  nodeId: string().optional()
@@ -21387,6 +21871,8 @@ var StorageKindSchema = object({
21387
21871
  "orphaned",
21388
21872
  "dropped"
21389
21873
  ]),
21874
+ /** An owner declared this kind since the engine started. */
21875
+ declaredThisBoot: boolean(),
21390
21876
  /** `null` when the caller asked for no counts (`counts: false`). */
21391
21877
  rows: number().int().nonnegative().nullable(),
21392
21878
  /** Rows x the average size of a sample; null for an empty kind. */
@@ -21398,6 +21884,51 @@ var StorageKindSchema = object({
21398
21884
  /** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
21399
21885
  declaration: RecordKindDeclarationSchema
21400
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
+ });
21401
21932
  method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
21402
21933
  owner: string().min(1).optional(),
21403
21934
  /** `false` skips the per-kind row count (a catalog read only). Default true. */
@@ -21405,7 +21936,30 @@ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: arr
21405
21936
  }), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
21406
21937
  owner: string().min(1),
21407
21938
  kind: RecordKindNameSchema
21408
- }), 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
+ });
21409
21963
  /**
21410
21964
  * record-entries - the ONE admin surface for every list of user-created entries
21411
21965
  * (D881): notification targets, brokers, LLM profiles, anything an addon
@@ -21544,6 +22098,12 @@ method(object({
21544
22098
  }), method(object({ deviceId: number().int() }), object({ refs: array(string()).readonly() }), {
21545
22099
  kind: "mutation",
21546
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"
21547
22107
  }), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
21548
22108
  /**
21549
22109
  * device-history — the ONE mechanism for numeric history (D850).
@@ -22333,7 +22893,9 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
22333
22893
  /** Upstream-system identity + rendering hints for the candidate. */
22334
22894
  sourceInfo: SourceInfoSchema.optional(),
22335
22895
  /** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
22336
- 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()
22337
22899
  }));
22338
22900
  /**
22339
22901
  * Status block — populated by the integration after each
@@ -42648,6 +43210,79 @@ object({
42648
43210
  metadata: record(string(), unknown()).optional()
42649
43211
  });
42650
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
+ /**
42651
43286
  * Candidate handed back from discovery and accepted by
42652
43287
  * `adoptDiscoveredDevice`. Shape mirrors the in-process
42653
43288
  * `DiscoveredDevice` interface used by `DeviceDiscovery`.
@@ -42682,7 +43317,17 @@ var DiscoveryCandidateSchema = object({
42682
43317
  /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
42683
43318
  onboardedDeviceId: number().optional(),
42684
43319
  /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
42685
- 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()
42686
43331
  });
42687
43332
  /**
42688
43333
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -42735,6 +43380,12 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
42735
43380
  }), method(object({}), boolean()), method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
42736
43381
  kind: "mutation",
42737
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"
42738
43389
  }), method(object({}), CreationSchemaOutputSchema), method(object({}), object({ deviceType: _enum(DeviceType).nullable() })), method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
42739
43390
  kind: "mutation",
42740
43391
  auth: "admin"
@@ -43462,17 +44113,23 @@ method(object({
43462
44113
  }), method(object({}), object({ providers: array(object({
43463
44114
  addonId: string(),
43464
44115
  label: string()
43465
- })).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
43466
- addonId: string(),
43467
- label: string(),
43468
- candidates: array(DiscoveryCandidateSchema).readonly(),
43469
- error: string().nullable()
43470
- })).readonly() }), {
43471
- kind: "mutation",
43472
- auth: "admin"
43473
- }), 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({
43474
44130
  addonId: string(),
43475
- params: record(string(), unknown()).optional()
44131
+ params: record(string(), unknown()).optional(),
44132
+ subnets: array(string()).optional()
43476
44133
  }), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
43477
44134
  kind: "mutation",
43478
44135
  auth: "admin"
@@ -61092,7 +61749,9 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
61092
61749
  id: string(),
61093
61750
  settings: record(string(), unknown()),
61094
61751
  /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
61095
- 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()
61096
61755
  }), object({ success: boolean() }), {
61097
61756
  kind: "mutation",
61098
61757
  auth: "admin"
@@ -61569,6 +62228,39 @@ var TlsStatusSchema = object({
61569
62228
  hasCa: boolean()
61570
62229
  }).nullable()
61571
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
+ });
61572
62264
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
61573
62265
  /**
61574
62266
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -61623,6 +62315,16 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
61623
62315
  }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
61624
62316
  kind: "mutation",
61625
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"
61626
62328
  }), method(_void(), TlsStatusSchema, {
61627
62329
  kind: "mutation",
61628
62330
  auth: "admin"
@@ -68566,6 +69268,13 @@ Object.freeze({
68566
69268
  access: "create",
68567
69269
  role: "admin"
68568
69270
  },
69271
+ "addonSettings.getDeclaredSettingsKeys": {
69272
+ capName: "addon-settings",
69273
+ capScope: "system",
69274
+ addonId: null,
69275
+ access: "view",
69276
+ role: "admin"
69277
+ },
68569
69278
  "addonSettings.getDeviceSettings": {
68570
69279
  capName: "addon-settings",
68571
69280
  capScope: "system",
@@ -68587,6 +69296,13 @@ Object.freeze({
68587
69296
  access: "view",
68588
69297
  role: "admin"
68589
69298
  },
69299
+ "addonSettings.unsetStoredSettingsKeys": {
69300
+ capName: "addon-settings",
69301
+ capScope: "system",
69302
+ addonId: null,
69303
+ access: "create",
69304
+ role: "admin"
69305
+ },
68590
69306
  "addonSettings.updateDeviceSettings": {
68591
69307
  capName: "addon-settings",
68592
69308
  capScope: "system",
@@ -71002,6 +71718,13 @@ Object.freeze({
71002
71718
  access: "create",
71003
71719
  role: "admin"
71004
71720
  },
71721
+ "deviceProvider.classifyDiscoveryHits": {
71722
+ capName: "device-provider",
71723
+ capScope: "system",
71724
+ addonId: null,
71725
+ access: "create",
71726
+ role: "admin"
71727
+ },
71005
71728
  "deviceProvider.createDevice": {
71006
71729
  capName: "device-provider",
71007
71730
  capScope: "system",
@@ -71030,6 +71753,13 @@ Object.freeze({
71030
71753
  access: "view",
71031
71754
  role: "admin"
71032
71755
  },
71756
+ "deviceProvider.getDiscoveryModes": {
71757
+ capName: "device-provider",
71758
+ capScope: "system",
71759
+ addonId: null,
71760
+ access: "view",
71761
+ role: "admin"
71762
+ },
71033
71763
  "deviceProvider.getDiscoveryParamsSchema": {
71034
71764
  capName: "device-provider",
71035
71765
  capScope: "system",
@@ -71807,6 +72537,13 @@ Object.freeze({
71807
72537
  access: "create",
71808
72538
  role: "admin"
71809
72539
  },
72540
+ "keychain.releaseRecordRefs": {
72541
+ capName: "keychain",
72542
+ capScope: "system",
72543
+ addonId: null,
72544
+ access: "create",
72545
+ role: "admin"
72546
+ },
71810
72547
  "keychain.resolve": {
71811
72548
  capName: "keychain",
71812
72549
  capScope: "system",
@@ -72227,6 +72964,13 @@ Object.freeze({
72227
72964
  access: "view",
72228
72965
  role: "admin"
72229
72966
  },
72967
+ "localNetwork.getScanSubnets": {
72968
+ capName: "local-network",
72969
+ capScope: "system",
72970
+ addonId: null,
72971
+ access: "view",
72972
+ role: "admin"
72973
+ },
72230
72974
  "localNetwork.getTlsStatus": {
72231
72975
  capName: "local-network",
72232
72976
  capScope: "system",
@@ -72276,6 +73020,13 @@ Object.freeze({
72276
73020
  access: "create",
72277
73021
  role: "admin"
72278
73022
  },
73023
+ "localNetwork.scanNetwork": {
73024
+ capName: "local-network",
73025
+ capScope: "system",
73026
+ addonId: null,
73027
+ access: "create",
73028
+ role: "admin"
73029
+ },
72279
73030
  "localNetwork.setAddressRole": {
72280
73031
  capName: "local-network",
72281
73032
  capScope: "system",
@@ -72297,6 +73048,13 @@ Object.freeze({
72297
73048
  access: "create",
72298
73049
  role: "admin"
72299
73050
  },
73051
+ "localNetwork.setScanSubnets": {
73052
+ capName: "local-network",
73053
+ capScope: "system",
73054
+ addonId: null,
73055
+ access: "create",
73056
+ role: "admin"
73057
+ },
72300
73058
  "localNetwork.setViewerEndpoints": {
72301
73059
  capName: "local-network",
72302
73060
  capScope: "system",
@@ -76518,6 +77276,13 @@ Object.freeze({
76518
77276
  access: "create",
76519
77277
  role: "admin"
76520
77278
  },
77279
+ "storageAdmin.dropOrphanedKind": {
77280
+ capName: "storage-admin",
77281
+ capScope: "system",
77282
+ addonId: null,
77283
+ access: "delete",
77284
+ role: "admin"
77285
+ },
76521
77286
  "storageAdmin.listKinds": {
76522
77287
  capName: "storage-admin",
76523
77288
  capScope: "system",
@@ -76539,6 +77304,27 @@ Object.freeze({
76539
77304
  access: "view",
76540
77305
  role: "admin"
76541
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
+ },
76542
77328
  "storageEvictable.evict": {
76543
77329
  capName: "storage-evictable",
76544
77330
  capScope: "system",