@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
@@ -5621,7 +5621,7 @@ var ZodIssueCode = {
5621
5621
  var ZodFirstPartyTypeKind;
5622
5622
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5623
5623
  //#endregion
5624
- //#region ../types/dist/sleep-63gM_1JR.mjs
5624
+ //#region ../types/dist/sleep-CeyhrcOk.mjs
5625
5625
  /**
5626
5626
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5627
5627
  * window to float samples (D455).
@@ -7688,6 +7688,7 @@ var BaseAddon = class {
7688
7688
  * a warning and falls back rather than crashing boot.
7689
7689
  */
7690
7690
  state(key, schema, fallback) {
7691
+ this._declaredStateKeys.add(key);
7691
7692
  return createDurableState({
7692
7693
  key,
7693
7694
  schema,
@@ -7703,6 +7704,96 @@ var BaseAddon = class {
7703
7704
  } })
7704
7705
  });
7705
7706
  }
7707
+ /** Durable-state keys this process registered through {@link state}. */
7708
+ _declaredStateKeys = /* @__PURE__ */ new Set();
7709
+ /**
7710
+ * Stale-data audit: every global-settings key this addon still declares —
7711
+ * constructor defaults, settings-schema fields (layout containers included),
7712
+ * durable-state keys and the root of each `secretStatePaths` entry. Answered
7713
+ * here, in the addon's own process, so the hub keeps no copy of it.
7714
+ */
7715
+ async getDeclaredSettingsKeys() {
7716
+ return { keys: [...this.declaredKeySet()].toSorted() };
7717
+ }
7718
+ declaredKeySet() {
7719
+ const keys = new Set(Object.keys(this.defaults));
7720
+ const schema = this.globalSettingsSchema();
7721
+ if (schema) for (const section of schema.sections) for (const key of collectFieldKeys(section.fields)) keys.add(key);
7722
+ for (const key of this._declaredStateKeys) keys.add(key);
7723
+ for (const path of this.secretStatePaths) {
7724
+ const root = path.split(/[.[]/, 1)[0];
7725
+ if (root) keys.add(root);
7726
+ }
7727
+ return keys;
7728
+ }
7729
+ /**
7730
+ * Stale-data audit: remove stored global-settings keys the addon no longer
7731
+ * declares, and clear the keychain values their markers held — except a value
7732
+ * a surviving key still points at. A declared or absent key is refused.
7733
+ */
7734
+ async unsetStoredSettingsKeys(keys) {
7735
+ const settings = this._ctx?.settings;
7736
+ const raw = this.rawSettings();
7737
+ if (!settings || !raw) return {
7738
+ removed: [],
7739
+ refused: keys.map((key) => ({
7740
+ key,
7741
+ reason: "no settings store"
7742
+ })),
7743
+ secretsCleared: 0
7744
+ };
7745
+ let stored;
7746
+ try {
7747
+ stored = await raw.readAddonStore();
7748
+ } catch (error) {
7749
+ const reason = `store unreadable: ${error instanceof Error ? error.message : "unknown"}`;
7750
+ return {
7751
+ removed: [],
7752
+ refused: keys.map((key) => ({
7753
+ key,
7754
+ reason
7755
+ })),
7756
+ secretsCleared: 0
7757
+ };
7758
+ }
7759
+ const declared = this.declaredKeySet();
7760
+ const removed = [];
7761
+ const refused = [];
7762
+ for (const key of keys) {
7763
+ const base = key.split("@", 1)[0] ?? key;
7764
+ if (declared.has(base)) refused.push({
7765
+ key,
7766
+ reason: "still declared by the addon"
7767
+ });
7768
+ else if (!(key in stored)) refused.push({
7769
+ key,
7770
+ reason: "not stored"
7771
+ });
7772
+ else removed.push(key);
7773
+ }
7774
+ if (removed.length === 0) return {
7775
+ removed,
7776
+ refused,
7777
+ secretsCleared: 0
7778
+ };
7779
+ const kept = /* @__PURE__ */ new Set();
7780
+ const held = /* @__PURE__ */ new Set();
7781
+ for (const [key, value] of Object.entries(stored)) {
7782
+ const target = removed.includes(key) ? held : kept;
7783
+ for (const ref of collectSecretMarkerRefs(value)) target.add(ref);
7784
+ }
7785
+ const refsToClear = [...held].filter((ref) => !kept.has(ref));
7786
+ const patch = {};
7787
+ for (const key of removed) patch[key] = void 0;
7788
+ await settings.writeAddonStore(patch);
7789
+ const secretsCleared = refsToClear.length > 0 && this._ctx?.secrets ? await this._ctx.secrets.delete(refsToClear) : 0;
7790
+ await this.resolveConfig();
7791
+ return {
7792
+ removed,
7793
+ refused,
7794
+ secretsCleared
7795
+ };
7796
+ }
7706
7797
  /** Per-device variant of {@link state}, backed by the per-device store. */
7707
7798
  deviceState(deviceId, key, schema, fallback) {
7708
7799
  return createDurableState({
@@ -7973,6 +8064,21 @@ function dropPerNodeFields(fields) {
7973
8064
  }
7974
8065
  return kept;
7975
8066
  }
8067
+ function collectFieldKeys(fields) {
8068
+ const collected = [];
8069
+ for (const field of fields) {
8070
+ if (field.type === "group") {
8071
+ collected.push(...collectFieldKeys(field.fields));
8072
+ continue;
8073
+ }
8074
+ if (field.type === "sub-tabs") {
8075
+ for (const tab of field.tabs) collected.push(...collectFieldKeys(tab.fields));
8076
+ continue;
8077
+ }
8078
+ if ("key" in field && typeof field.key === "string") collected.push(field.key);
8079
+ }
8080
+ return collected;
8081
+ }
7976
8082
  function collectPerNodeFieldKeys(fields) {
7977
8083
  const collected = [];
7978
8084
  for (const field of fields) {
@@ -16364,8 +16470,172 @@ object({
16364
16470
  quantity: ReadingQuantitySchema,
16365
16471
  unit: string().min(1)
16366
16472
  });
16367
- /** How a leaf compares a device field to a value. Derived from the field's
16368
- * `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
16473
+ new Set([
16474
+ {
16475
+ id: "person",
16476
+ name: "Person"
16477
+ },
16478
+ {
16479
+ id: "vehicle",
16480
+ name: "Vehicle"
16481
+ },
16482
+ {
16483
+ id: "animal",
16484
+ name: "Animal"
16485
+ },
16486
+ {
16487
+ id: "package",
16488
+ name: "Package"
16489
+ }
16490
+ ].map((l) => l.id));
16491
+ var COCO_TO_MACRO = {
16492
+ mapping: {
16493
+ person: "person",
16494
+ bicycle: "vehicle",
16495
+ car: "vehicle",
16496
+ motorcycle: "vehicle",
16497
+ airplane: "vehicle",
16498
+ bus: "vehicle",
16499
+ train: "vehicle",
16500
+ truck: "vehicle",
16501
+ boat: "vehicle",
16502
+ bird: "animal",
16503
+ cat: "animal",
16504
+ dog: "animal",
16505
+ horse: "animal",
16506
+ sheep: "animal",
16507
+ cow: "animal",
16508
+ elephant: "animal",
16509
+ bear: "animal",
16510
+ zebra: "animal",
16511
+ giraffe: "animal",
16512
+ suitcase: "package",
16513
+ backpack: "package",
16514
+ handbag: "package"
16515
+ },
16516
+ preserveOriginal: false
16517
+ };
16518
+ /**
16519
+ * WHAT a detection-shaped leaf is about: one MACRO class (person, vehicle,
16520
+ * animal, package) plus an OPTIONAL label that narrows it.
16521
+ *
16522
+ * The taxonomy is `COCO_TO_MACRO` (and `CLASS_MAP_MACRO_TARGETS`) — never a
16523
+ * second list. The label that fits depends on the macro class:
16524
+ *
16525
+ * - `animal` → a sub-class (`dog`, `cat`, …);
16526
+ * - `person` → a known face (an identity of the `face-gallery`);
16527
+ * - `vehicle` → a plate (its text) or a sub-class (`car`, `truck`, …);
16528
+ * - `package` → a sub-class.
16529
+ *
16530
+ * `other` exists only so a class the taxonomy does not know (a custom model's
16531
+ * `forklift`) survives the conversion of a stored recipe without widening.
16532
+ *
16533
+ * A label is decided LATE (D683): face recognition and LPR land after the
16534
+ * detection starts. The track-lifecycle `update` event carries the newly
16535
+ * resolved `label` / `plateText`, which is why a leaf with a label fires when
16536
+ * the subject GETS it — not at the first detection.
16537
+ */
16538
+ var SubClassLabelSchema = object({
16539
+ kind: literal("sub-class"),
16540
+ className: string().min(1)
16541
+ });
16542
+ var IdentityLabelSchema = object({
16543
+ kind: literal("identity"),
16544
+ /** The `face-gallery` identity id (stable across a rename). */
16545
+ identityId: string().min(1),
16546
+ /** The identity's name — what a track-lifecycle event carries as `label`. */
16547
+ name: string().min(1)
16548
+ });
16549
+ var PlateLabelSchema = object({
16550
+ kind: literal("plate"),
16551
+ text: string().min(1)
16552
+ });
16553
+ function macroOf$1(className) {
16554
+ return COCO_TO_MACRO.mapping[className.toLowerCase()] ?? className.toLowerCase();
16555
+ }
16556
+ /** A sub-class label must belong to the macro it is under. */
16557
+ function subClassBelongs(macro, label) {
16558
+ return label === void 0 || macroOf$1(label.className) === macro;
16559
+ }
16560
+ var subClassOf = (macro) => SubClassLabelSchema.refine((l) => subClassBelongs(macro, l), { message: `that class is not a kind of ${macro} in the detection taxonomy` });
16561
+ var DetectionSubjectSchema = discriminatedUnion("macro", [
16562
+ object({
16563
+ macro: literal("animal"),
16564
+ label: subClassOf("animal").optional()
16565
+ }),
16566
+ object({
16567
+ macro: literal("person"),
16568
+ label: IdentityLabelSchema.optional()
16569
+ }),
16570
+ object({
16571
+ macro: literal("vehicle"),
16572
+ label: discriminatedUnion("kind", [SubClassLabelSchema.refine((l) => subClassBelongs("vehicle", l), { message: "that class is not a kind of vehicle in the detection taxonomy" }), PlateLabelSchema]).optional()
16573
+ }),
16574
+ object({
16575
+ macro: literal("package"),
16576
+ label: subClassOf("package").optional()
16577
+ }),
16578
+ object({
16579
+ macro: literal("other"),
16580
+ className: string().min(1)
16581
+ })
16582
+ ]);
16583
+ /**
16584
+ * The subject of a PARKED object. The stationary registry's event carries a
16585
+ * class and a zone, no face and no plate — an identity or plate label there
16586
+ * could never match, so the schema refuses it instead of letting it sit dead.
16587
+ */
16588
+ var StationarySubjectSchema = DetectionSubjectSchema.refine((s) => s.macro === "other" || s.label === void 0 || s.label.kind === "sub-class", { message: "a parked object carries no face or plate — only a class can narrow it" });
16589
+ /**
16590
+ * A stored class string as a subject: a macro name stays a macro; a raw class
16591
+ * the taxonomy files under a macro becomes that macro plus the sub-class label
16592
+ * (`dog` → animal / dog); anything else is `other`, kept verbatim.
16593
+ */
16594
+ function subjectFromClassName(className) {
16595
+ const name = className.trim().toLowerCase();
16596
+ const macro = COCO_TO_MACRO.mapping[name];
16597
+ if (new Set(Object.values(COCO_TO_MACRO.mapping)).has(name)) return macroOnly(name);
16598
+ if (macro === void 0) return {
16599
+ macro: "other",
16600
+ className
16601
+ };
16602
+ const label = {
16603
+ kind: "sub-class",
16604
+ className: name
16605
+ };
16606
+ switch (macro) {
16607
+ case "animal": return {
16608
+ macro: "animal",
16609
+ label
16610
+ };
16611
+ case "vehicle": return {
16612
+ macro: "vehicle",
16613
+ label
16614
+ };
16615
+ case "package": return {
16616
+ macro: "package",
16617
+ label
16618
+ };
16619
+ default: return {
16620
+ macro: "other",
16621
+ className
16622
+ };
16623
+ }
16624
+ }
16625
+ function macroOnly(macro) {
16626
+ switch (macro) {
16627
+ case "animal": return { macro: "animal" };
16628
+ case "person": return { macro: "person" };
16629
+ case "vehicle": return { macro: "vehicle" };
16630
+ case "package": return { macro: "package" };
16631
+ default: return {
16632
+ macro: "other",
16633
+ className: macro
16634
+ };
16635
+ }
16636
+ }
16637
+ /** How a state leaf compares a device field to a value. Derived from the
16638
+ * field's `kind` in `deviceManager.getWireableFields`, never hand-maintained. */
16369
16639
  var AutomationConditionOperatorSchema = _enum([
16370
16640
  "eq",
16371
16641
  "ne",
@@ -16376,12 +16646,76 @@ var AutomationConditionOperatorSchema = _enum([
16376
16646
  "contains",
16377
16647
  "in"
16378
16648
  ]);
16379
- var AutomationConditionLeafSchema = object({
16380
- kind: literal("condition"),
16649
+ /**
16650
+ * D8 compliance, and it is the reason a "becomes" leaf is not merely an event
16651
+ * subscription: it evaluates against the **state mirror**, which is reconciled,
16652
+ * and an event only WAKES the evaluation. A dropped event therefore DELAYS a
16653
+ * fire; it does not lose it.
16654
+ */
16655
+ var DeviceBecomesLeafSchema = object({
16656
+ kind: literal("device-becomes"),
16657
+ deviceId: number().int().nonnegative(),
16658
+ cap: string().min(1),
16659
+ fieldPath: string().min(1),
16660
+ /** Fire when the field takes this value. Omit to fire on any change. */
16661
+ becomes: union([
16662
+ string(),
16663
+ number(),
16664
+ boolean()
16665
+ ]).optional(),
16666
+ /** Only on a CHANGE of value, not on every re-report. */
16667
+ edge: boolean().optional(),
16668
+ /** The new value must hold this long before it counts. */
16669
+ forMs: number().int().min(0).max(864e5).optional(),
16670
+ /** Collapse a burst into one occurrence. */
16671
+ debounceMs: number().int().min(0).max(6e5).optional()
16672
+ });
16673
+ var DeviceEventLeafSchema = object({
16674
+ kind: literal("device-event"),
16675
+ /** An `EventCategory` value. */
16676
+ category: string().min(1),
16677
+ deviceId: number().int().nonnegative().optional()
16678
+ });
16679
+ var ScheduleLeafSchema = object({
16680
+ kind: literal("schedule"),
16681
+ cron: string().min(1).max(120)
16682
+ });
16683
+ var ManualLeafSchema = object({ kind: literal("manual") });
16684
+ var IntervalLeafSchema = object({
16685
+ kind: literal("interval"),
16686
+ minutes: number().int().min(1).max(1440)
16687
+ });
16688
+ var DetectionLeafSchema = object({
16689
+ kind: literal("detection"),
16690
+ /** One macro class each, optionally narrowed by a label; a track of any listed subject fires. */
16691
+ subjects: array(DetectionSubjectSchema).min(1),
16692
+ deviceIds: array(number().int().nonnegative()).min(1),
16693
+ /** The track must have visited at least one of these zones. */
16694
+ zones: array(string().min(1)).min(1).optional(),
16695
+ /** Best-so-far confidence the track must reach. */
16696
+ minScore: number().min(0).max(1).optional()
16697
+ });
16698
+ var StationaryLeafSchema = object({
16699
+ kind: literal("stationary"),
16700
+ deviceIds: array(number().int().nonnegative()).min(1),
16701
+ subjects: array(StationarySubjectSchema).min(1).optional(),
16702
+ zones: array(string().min(1)).min(1).optional(),
16703
+ /** `appeared`: it became parked. `departed`: the registry retired it. */
16704
+ phase: _enum(["appeared", "departed"])
16705
+ });
16706
+ /** "is": one device field compared to one value, NOW. */
16707
+ var DeviceIsLeafSchema = object({
16708
+ kind: literal("device-is"),
16381
16709
  deviceId: number().int().nonnegative(),
16382
16710
  cap: string().min(1),
16383
16711
  fieldPath: string().min(1),
16384
16712
  operator: AutomationConditionOperatorSchema,
16713
+ /**
16714
+ * The value. A number on a quantity field is stored in the field's base unit
16715
+ * (D944); a quantity value is the WRITE form, which the save boundary
16716
+ * replaces with the converted number and `unit`. A leaf that still carries
16717
+ * one is never evaluated (it fails).
16718
+ */
16385
16719
  value: union([
16386
16720
  string(),
16387
16721
  number(),
@@ -16389,24 +16723,26 @@ var AutomationConditionLeafSchema = object({
16389
16723
  array(union([string(), number()])),
16390
16724
  QuantityValueSchema
16391
16725
  ]),
16726
+ /** The unit the person picked for `value` (display only; `value` is in the base unit). */
16392
16727
  unit: string().min(1).optional()
16393
16728
  });
16394
- var AutomationConditionStationaryPresentSchema = object({
16729
+ /**
16730
+ * True when the camera currently has a PARKED object matching `subjects` in at
16731
+ * least one of `zones`. Reads the zone-analytics snapshot through the same
16732
+ * mirror as every other state leaf, so a dropped event never decides it (D49).
16733
+ */
16734
+ var StationaryPresentLeafSchema = object({
16395
16735
  kind: literal("stationary-present"),
16396
16736
  deviceId: number().int().nonnegative(),
16397
- /** Class names; a macro class matches every raw class that maps to it. */
16398
- classes: array(string().min(1)).min(1).optional(),
16399
- /** The parked object must be in at least one of these zones. */
16737
+ subjects: array(StationarySubjectSchema).min(1).optional(),
16400
16738
  zones: array(string().min(1)).min(1).optional()
16401
16739
  });
16402
16740
  /**
16403
- * The expression leaf, declared as a plain object rather than an intersection
16404
- * with {@link ExpressionSourceSchema}: a discriminated union has to be able to
16405
- * read `kind` off each option, and an intersection hides it. The author-time
16406
- * validation is the SAME function `ExpressionSourceSchema` runs, so the two
16407
- * cannot drift — an expression that one accepts, the other accepts.
16741
+ * The escape hatch: the bounded expression engine over named device bindings.
16742
+ * Declared as a plain object so a discriminated union can read `kind`; the
16743
+ * author-time validation is the SAME function `ExpressionSourceSchema` runs.
16408
16744
  */
16409
- var AutomationConditionExpressionSchema = object({
16745
+ var ExpressionLeafSchema = object({
16410
16746
  kind: literal("expression"),
16411
16747
  expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
16412
16748
  bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
@@ -16418,55 +16754,62 @@ var AutomationConditionExpressionSchema = object({
16418
16754
  path: ["expr"]
16419
16755
  });
16420
16756
  });
16421
- var AutomationConditionSchema = lazy(() => discriminatedUnion("kind", [
16757
+ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
16422
16758
  object({
16423
16759
  kind: literal("all"),
16424
- children: array(AutomationConditionSchema)
16760
+ children: array(AutomationWhenSchema)
16425
16761
  }),
16426
16762
  object({
16427
16763
  kind: literal("any"),
16428
- children: array(AutomationConditionSchema)
16764
+ children: array(AutomationWhenSchema)
16429
16765
  }),
16430
16766
  object({
16431
16767
  kind: literal("not"),
16432
- child: AutomationConditionSchema
16768
+ child: AutomationWhenSchema
16433
16769
  }),
16434
- AutomationConditionLeafSchema,
16435
- AutomationConditionExpressionSchema,
16436
- AutomationConditionStationaryPresentSchema
16770
+ DeviceBecomesLeafSchema,
16771
+ DeviceEventLeafSchema,
16772
+ ScheduleLeafSchema,
16773
+ ManualLeafSchema,
16774
+ IntervalLeafSchema,
16775
+ DetectionLeafSchema,
16776
+ StationaryLeafSchema,
16777
+ DeviceIsLeafSchema,
16778
+ StationaryPresentLeafSchema,
16779
+ ExpressionLeafSchema
16437
16780
  ]));
16438
16781
  /**
16439
- * What starts a run.
16440
- *
16441
- * D8 compliance, and it is the reason `device-state` is not merely an event
16442
- * subscription: the trigger evaluates against the **state mirror**, which is
16443
- * reconciled, and an event only WAKES the evaluation. A dropped event therefore
16444
- * DELAYS a trigger; it does not lose it. `schedule` uses `croner` — the one
16445
- * already in the repo — because `setInterval(24h)` drifts and "at 23:30" does
16446
- * not.
16447
- */
16448
- var AutomationTriggerSchema = discriminatedUnion("kind", [
16782
+ * The recipe shape before the single `when` tree: a flat list of triggers
16783
+ * (OR'd, each an instant) and an optional condition tree. Stored recipes —
16784
+ * the `AUTOMATION_RECIPE` literal of every block saved so far, and whatever an
16785
+ * older client still posts — are in this shape, so it stays READABLE here, and
16786
+ * {@link convertLegacyRecipe} turns it into the new one, losslessly and one
16787
+ * way. Nothing writes this shape any more.
16788
+ *
16789
+ * `{ triggers: [t1…tn], conditions?: C }`
16790
+ * → `when = all(any(t1…tn), C)`, or `any(t1…tn)` without `C`;
16791
+ * a single trigger is not wrapped in `any`.
16792
+ * a `condition` leaf → a `device-is` leaf (the "is" verb);
16793
+ * a `device-state` trigger → a `device-becomes` leaf (the "becomes" verb);
16794
+ * `classes: ['dog']` → the macro `animal` with the label `dog`.
16795
+ */
16796
+ var LegacyTriggerSchema = discriminatedUnion("kind", [
16449
16797
  object({
16450
16798
  kind: literal("device-state"),
16451
16799
  deviceId: number().int().nonnegative(),
16452
16800
  cap: string().min(1),
16453
16801
  fieldPath: string().min(1),
16454
- /** Fire when the field takes this value. Omit to fire on any change. */
16455
16802
  becomes: union([
16456
16803
  string(),
16457
16804
  number(),
16458
16805
  boolean()
16459
16806
  ]).optional(),
16460
- /** Only on a CHANGE of value, not on every re-report. */
16461
16807
  edge: boolean().optional(),
16462
- /** The condition must hold this long before the run starts. */
16463
16808
  forMs: number().int().min(0).max(864e5).optional(),
16464
- /** Collapse a burst into one run. */
16465
16809
  debounceMs: number().int().min(0).max(6e5).optional()
16466
16810
  }),
16467
16811
  object({
16468
16812
  kind: literal("device-event"),
16469
- /** An `EventCategory` value. */
16470
16813
  category: string().min(1),
16471
16814
  deviceId: number().int().nonnegative().optional()
16472
16815
  }),
@@ -16477,25 +16820,16 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
16477
16820
  object({ kind: literal("manual") }),
16478
16821
  object({
16479
16822
  kind: literal("detection"),
16480
- /** Class names. A macro class (`animal`, `vehicle`, `person`, `package`)
16481
- * matches every raw class that maps to it; any other name matches
16482
- * exactly. */
16483
16823
  classes: array(string().min(1)).min(1),
16484
16824
  deviceIds: array(number().int().nonnegative()).min(1),
16485
- /** The track must have visited at least one of these zones. */
16486
16825
  zones: array(string().min(1)).min(1).optional(),
16487
- /** Best-so-far confidence the track must reach. */
16488
16826
  minScore: number().min(0).max(1).optional()
16489
16827
  }),
16490
16828
  object({
16491
16829
  kind: literal("stationary"),
16492
16830
  deviceIds: array(number().int().nonnegative()).min(1),
16493
- /** Class names; a macro class matches like the detection trigger. */
16494
16831
  classes: array(string().min(1)).min(1).optional(),
16495
- /** The parked object must be in at least one of these zones. */
16496
16832
  zones: array(string().min(1)).min(1).optional(),
16497
- /** `appeared`: it became parked. `departed`: the registry retired it.
16498
- * The vocabulary of `PipelineAnalyticsStationaryChanged`. */
16499
16833
  phase: _enum(["appeared", "departed"])
16500
16834
  }),
16501
16835
  object({
@@ -16503,6 +16837,143 @@ var AutomationTriggerSchema = discriminatedUnion("kind", [
16503
16837
  minutes: number().int().min(1).max(1440)
16504
16838
  })
16505
16839
  ]);
16840
+ var LegacyConditionLeafSchema = object({
16841
+ kind: literal("condition"),
16842
+ deviceId: number().int().nonnegative(),
16843
+ cap: string().min(1),
16844
+ fieldPath: string().min(1),
16845
+ operator: AutomationConditionOperatorSchema,
16846
+ value: union([
16847
+ string(),
16848
+ number(),
16849
+ boolean(),
16850
+ array(union([string(), number()])),
16851
+ QuantityValueSchema
16852
+ ]),
16853
+ unit: string().min(1).optional()
16854
+ });
16855
+ var LegacyStationaryPresentSchema = object({
16856
+ kind: literal("stationary-present"),
16857
+ deviceId: number().int().nonnegative(),
16858
+ classes: array(string().min(1)).min(1).optional(),
16859
+ zones: array(string().min(1)).min(1).optional()
16860
+ });
16861
+ var LegacyExpressionSchema = object({
16862
+ kind: literal("expression"),
16863
+ expr: string().min(1).max(MAX_EXPRESSION_SOURCE_LENGTH),
16864
+ bindings: record(string().regex(EXPRESSION_IDENTIFIER_RE), ExpressionBindingSourceSchema)
16865
+ }).superRefine((src, ctx) => {
16866
+ const err = validateExpressionSource(src);
16867
+ if (err !== null) ctx.addIssue({
16868
+ code: "custom",
16869
+ message: err,
16870
+ path: ["expr"]
16871
+ });
16872
+ });
16873
+ var LegacyConditionSchema = lazy(() => discriminatedUnion("kind", [
16874
+ object({
16875
+ kind: literal("all"),
16876
+ children: array(LegacyConditionSchema)
16877
+ }),
16878
+ object({
16879
+ kind: literal("any"),
16880
+ children: array(LegacyConditionSchema)
16881
+ }),
16882
+ object({
16883
+ kind: literal("not"),
16884
+ child: LegacyConditionSchema
16885
+ }),
16886
+ LegacyConditionLeafSchema,
16887
+ LegacyExpressionSchema,
16888
+ LegacyStationaryPresentSchema
16889
+ ]));
16890
+ function convertTrigger(trigger) {
16891
+ switch (trigger.kind) {
16892
+ case "device-state": return {
16893
+ ...trigger,
16894
+ kind: "device-becomes"
16895
+ };
16896
+ case "detection": {
16897
+ const { classes, ...rest } = trigger;
16898
+ return {
16899
+ ...rest,
16900
+ subjects: classes.map(subjectFromClassName)
16901
+ };
16902
+ }
16903
+ case "stationary": {
16904
+ const { classes, ...rest } = trigger;
16905
+ return {
16906
+ ...rest,
16907
+ ...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
16908
+ };
16909
+ }
16910
+ default: return trigger;
16911
+ }
16912
+ }
16913
+ /** An old condition tree as a `when` subtree: `condition` → "is", classes → subjects. */
16914
+ function convertLegacyCondition(condition) {
16915
+ switch (condition.kind) {
16916
+ case "condition": return {
16917
+ ...condition,
16918
+ kind: "device-is"
16919
+ };
16920
+ case "stationary-present": {
16921
+ const { classes, ...rest } = condition;
16922
+ return {
16923
+ ...rest,
16924
+ ...classes === void 0 ? {} : { subjects: classes.map(subjectFromClassName) }
16925
+ };
16926
+ }
16927
+ case "expression": return condition;
16928
+ case "not": return {
16929
+ kind: "not",
16930
+ child: convertLegacyCondition(condition.child)
16931
+ };
16932
+ case "all":
16933
+ case "any": return {
16934
+ kind: condition.kind,
16935
+ children: condition.children.map(convertLegacyCondition)
16936
+ };
16937
+ }
16938
+ }
16939
+ /** `all(any(t1…tn), C)`, or `any(t1…tn)`; a single trigger is not wrapped in `any`. */
16940
+ function convertLegacyWhen(half) {
16941
+ const leaves = half.triggers.map(convertTrigger);
16942
+ const first = leaves[0];
16943
+ const triggers = leaves.length === 1 && first !== void 0 ? first : {
16944
+ kind: "any",
16945
+ children: leaves
16946
+ };
16947
+ if (half.conditions === void 0) return triggers;
16948
+ return {
16949
+ kind: "all",
16950
+ children: [triggers, convertLegacyCondition(half.conditions)]
16951
+ };
16952
+ }
16953
+ /**
16954
+ * The automation RECIPE — what a visually-authored automation is, before it
16955
+ * becomes code.
16956
+ *
16957
+ * This is the data half of `docs/design/2026-08-08-automation-scripts-custom-devices.md`
16958
+ * Phase 6. A recipe is not an engine and never evaluates anything: it is a
16959
+ * declarative description that {@link generateAutomationBlock} compiles to the
16960
+ * TypeScript of an ordinary core block. There is therefore no second rule
16961
+ * engine, no second store and no second runtime — the block runner is the
16962
+ * runtime, `core_blocks` is the store (§2.1).
16963
+ *
16964
+ * Two parts:
16965
+ *
16966
+ * - **`when`** — ONE tree (`./when.ts`) of all/any/not over event leaves
16967
+ * (instants) and state leaves (durations). It replaces the flat trigger list
16968
+ * plus the separate condition tree; the firing rule is in that file and in
16969
+ * ADR "one tree".
16970
+ * - **Actions** — `NcRuleActionSchema`'s two steps, plus the `{kind:'code'}`
16971
+ * fragment of §3.2.2. See {@link AutomationActionSchema} for the ONE place
16972
+ * this had to diverge from "verbatim", and why.
16973
+ *
16974
+ * A recipe stored in the old shape (`triggers` + `conditions`) still LOADS:
16975
+ * {@link AutomationRecipeSchema} (the parse-anywhere schema) converts it on parse. Nothing writes it.
16976
+ */
16506
16977
  /**
16507
16978
  * One action step.
16508
16979
  *
@@ -16605,69 +17076,49 @@ var AutomationRunModeSchema = discriminatedUnion("mode", [
16605
17076
  minutes: number().int().min(1).max(1440)
16606
17077
  })
16607
17078
  ]);
16608
- var AutomationRecipeSchema = object({
16609
- triggers: array(AutomationTriggerSchema),
16610
- conditions: AutomationConditionSchema.optional(),
17079
+ var CurrentAutomationRecipeSchema = object({
17080
+ when: AutomationWhenSchema,
16611
17081
  actions: array(AutomationActionSchema),
16612
17082
  runMode: AutomationRunModeSchema.optional(),
16613
17083
  /** How many run traces the automation keeps (default {@link DEFAULT_TRACE_KEEP}). */
16614
17084
  keepTraces: number().int().min(1).max(100).optional()
16615
17085
  });
16616
- new Set([
16617
- {
16618
- id: "person",
16619
- name: "Person"
16620
- },
16621
- {
16622
- id: "vehicle",
16623
- name: "Vehicle"
16624
- },
16625
- {
16626
- id: "animal",
16627
- name: "Animal"
16628
- },
16629
- {
16630
- id: "package",
16631
- name: "Package"
16632
- }
16633
- ].map((l) => l.id));
16634
- var COCO_TO_MACRO = {
16635
- mapping: {
16636
- person: "person",
16637
- bicycle: "vehicle",
16638
- car: "vehicle",
16639
- motorcycle: "vehicle",
16640
- airplane: "vehicle",
16641
- bus: "vehicle",
16642
- train: "vehicle",
16643
- truck: "vehicle",
16644
- boat: "vehicle",
16645
- bird: "animal",
16646
- cat: "animal",
16647
- dog: "animal",
16648
- horse: "animal",
16649
- sheep: "animal",
16650
- cow: "animal",
16651
- elephant: "animal",
16652
- bear: "animal",
16653
- zebra: "animal",
16654
- giraffe: "animal",
16655
- suitcase: "package",
16656
- backpack: "package",
16657
- handbag: "package"
16658
- },
16659
- preserveOriginal: false
16660
- };
17086
+ /** A recipe as it was stored before the single tree. Read-only: nothing writes this. */
17087
+ var LegacyAutomationRecipeSchema = object({
17088
+ triggers: array(LegacyTriggerSchema),
17089
+ conditions: LegacyConditionSchema.optional(),
17090
+ actions: array(AutomationActionSchema),
17091
+ runMode: AutomationRunModeSchema.optional(),
17092
+ keepTraces: number().int().min(1).max(100).optional()
17093
+ });
17094
+ /** The old shape as the new one, lossless. */
17095
+ function convertLegacyRecipe(legacy) {
17096
+ const { triggers, conditions, ...rest } = legacy;
17097
+ return {
17098
+ when: convertLegacyWhen({
17099
+ triggers,
17100
+ conditions
17101
+ }),
17102
+ ...rest
17103
+ };
17104
+ }
17105
+ /**
17106
+ * Where recipes are PARSED (the core-blocks cap, the MCP tool, the literal a
17107
+ * saved block carries): the new shape as it is, or the old shape converted to
17108
+ * it. The output type is always the new one.
17109
+ */
17110
+ var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
16661
17111
  new Set(Object.values(COCO_TO_MACRO.mapping));
16662
17112
  /**
16663
17113
  * Run traces — what an automation (or, later, an Action run) DID, node by node,
16664
17114
  * so the editor can paint every block green or red with its reason.
16665
17115
  *
16666
- * A trace is data, not a log: it covers the WHOLE tree — the trigger stage,
16667
- * every condition node (containers and leaves, including the ones a
16668
- * short-circuit never reached), the run-mode verdict and every action step —
16669
- * each addressed by a stable path (`conditions.0.1`, `actions.2`, or `steps.N`
16670
- * for an Action run). Skipped
17116
+ * A trace is data, not a log: it covers the WHOLE tree — the event leaf that
17117
+ * fired, every node of the `when` tree (containers and leaves, including the
17118
+ * ones a short-circuit never reached), the run-mode verdict and every action
17119
+ * step — each addressed by a stable path (`when.0.1`, `actions.2`, or `steps.N`
17120
+ * for an Action run). Traces written before the single tree carry
17121
+ * `conditions.*` paths and still parse. Skipped
16671
17122
  * evaluations are traced too, and identical consecutive skips collapse into a
16672
17123
  * repeat count so a trigger that fails every 5 minutes cannot flush real runs.
16673
17124
  *
@@ -16699,6 +17150,8 @@ var TraceNodeOutcomeSchema = _enum([
16699
17150
  ]);
16700
17151
  var AutomationTriggerTraceSchema = object({
16701
17152
  kind: string().min(1),
17153
+ /** The event leaf that fired (`when`, `when.0.1`); absent on traces written before the single tree. */
17154
+ path: string().min(1).optional(),
16702
17155
  outcome: _enum(["pass", "fail"]),
16703
17156
  /** Why an event was rejected ("class person not in [dog]") or what fired. */
16704
17157
  reason: string(),
@@ -16706,15 +17159,23 @@ var AutomationTriggerTraceSchema = object({
16706
17159
  vars: record(string(), TraceValueSchema)
16707
17160
  });
16708
17161
  var AutomationConditionTraceNodeSchema = object({
16709
- /** `conditions` for the root, `conditions.<i>` for its i-th child, … */
17162
+ /** `when` for the root, `when.<i>` for its i-th child, … (`conditions…` on an old trace). */
16710
17163
  path: string().min(1),
16711
17164
  kind: _enum([
16712
17165
  "all",
16713
17166
  "any",
16714
17167
  "not",
16715
17168
  "condition",
17169
+ "device-is",
16716
17170
  "expression",
16717
- "stationary-present"
17171
+ "stationary-present",
17172
+ "device-becomes",
17173
+ "device-event",
17174
+ "schedule",
17175
+ "interval",
17176
+ "manual",
17177
+ "detection",
17178
+ "stationary"
16718
17179
  ]),
16719
17180
  outcome: TraceNodeOutcomeSchema,
16720
17181
  reason: string(),
@@ -16748,6 +17209,12 @@ var AutomationStepTraceSchema = object({
16748
17209
  deviceId: number().int().optional(),
16749
17210
  startedAt: number().optional(),
16750
17211
  endedAt: number().optional(),
17212
+ /**
17213
+ * The parameters the step SENT, after `{{vars.x}}` substitution — what the
17214
+ * device was actually asked. Bounded like trigger `vars`; a secret parameter
17215
+ * is shown as {@link REDACTED_ARG}. Absent on traces written before it existed.
17216
+ */
17217
+ args: record(string(), TraceValueSchema).optional(),
16751
17218
  outcome: _enum([
16752
17219
  "ok",
16753
17220
  "error",
@@ -17497,6 +17964,23 @@ method(object({
17497
17964
  addonId: string(),
17498
17965
  nodeId: string().optional()
17499
17966
  }), SettingsSchemaWithValuesSchema.nullable()), method(object({
17967
+ addonId: string(),
17968
+ nodeId: string().optional()
17969
+ }), object({ keys: array(string()) }).nullable(), { auth: "admin" }), method(object({
17970
+ addonId: string(),
17971
+ nodeId: string().optional(),
17972
+ keys: array(string())
17973
+ }), object({
17974
+ removed: array(string()),
17975
+ refused: array(object({
17976
+ key: string(),
17977
+ reason: string()
17978
+ })),
17979
+ secretsCleared: number()
17980
+ }), {
17981
+ kind: "mutation",
17982
+ auth: "admin"
17983
+ }), method(object({
17500
17984
  addonId: string(),
17501
17985
  deviceId: number(),
17502
17986
  nodeId: string().optional()
@@ -21420,6 +21904,8 @@ var StorageKindSchema = object({
21420
21904
  "orphaned",
21421
21905
  "dropped"
21422
21906
  ]),
21907
+ /** An owner declared this kind since the engine started. */
21908
+ declaredThisBoot: boolean(),
21423
21909
  /** `null` when the caller asked for no counts (`counts: false`). */
21424
21910
  rows: number().int().nonnegative().nullable(),
21425
21911
  /** Rows x the average size of a sample; null for an empty kind. */
@@ -21431,6 +21917,51 @@ var StorageKindSchema = object({
21431
21917
  /** The whole declaration, secret paths and entry descriptor included (the entries service builds its handle from it). */
21432
21918
  declaration: RecordKindDeclarationSchema
21433
21919
  });
21920
+ _enum([
21921
+ "orphaned",
21922
+ "stale",
21923
+ "unknown"
21924
+ ]);
21925
+ /** A record kind nobody declared since its owner started, or whose owner is gone. */
21926
+ var StaleKindFindingSchema = object({
21927
+ owner: string(),
21928
+ kind: string(),
21929
+ /** `unknown`: the owner could not be judged (not running, or its state unreadable) - never dropped. */
21930
+ verdict: _enum(["orphaned", "unknown"]),
21931
+ reason: string(),
21932
+ rows: number().int().nonnegative().nullable(),
21933
+ /** local-auth's tables are never dropped by this audit. */
21934
+ protected: boolean()
21935
+ });
21936
+ /** A stored global-settings key the running addon no longer declares. */
21937
+ var StaleSettingsKeyFindingSchema = object({
21938
+ addonId: string(),
21939
+ /** The stored key as written, a per-node `<key>@<nodeId>` suffix included. */
21940
+ key: string(),
21941
+ /**
21942
+ * Keychain refs whose ONLY marker sits under this key: in use by stale data,
21943
+ * not orphans yet. Removing the key releases them.
21944
+ */
21945
+ secretRefs: array(string())
21946
+ });
21947
+ /** An addon the audit could not judge, with the reason. Never reported as stale. */
21948
+ var StaleUnknownSchema = object({
21949
+ subject: string(),
21950
+ reason: string()
21951
+ });
21952
+ var StaleDataReportSchema = object({
21953
+ /** Epoch ms of the last finished scan; `null` before the first. */
21954
+ scannedAt: number().nullable(),
21955
+ scanning: boolean(),
21956
+ trigger: _enum([
21957
+ "boot",
21958
+ "interval",
21959
+ "manual"
21960
+ ]).nullable(),
21961
+ kinds: array(StaleKindFindingSchema),
21962
+ settingsKeys: array(StaleSettingsKeyFindingSchema),
21963
+ unknown: array(StaleUnknownSchema)
21964
+ });
21434
21965
  method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: array(StorageTableSchema) }), { auth: "admin" }), method(object({
21435
21966
  owner: string().min(1).optional(),
21436
21967
  /** `false` skips the per-kind row count (a catalog read only). Default true. */
@@ -21438,7 +21969,30 @@ method(object({ ownerPrefix: string().min(1).optional() }), object({ tables: arr
21438
21969
  }), object({ kinds: array(StorageKindSchema) }), { auth: "admin" }), method(RecordQueryInputSchema.extend({
21439
21970
  owner: string().min(1),
21440
21971
  kind: RecordKindNameSchema
21441
- }), RecordQueryResultSchema, { auth: "admin" });
21972
+ }), RecordQueryResultSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, { auth: "admin" }), method(object({}), StaleDataReportSchema, {
21973
+ kind: "mutation",
21974
+ auth: "admin"
21975
+ }), method(object({
21976
+ owner: string().min(1),
21977
+ kind: RecordKindNameSchema
21978
+ }), object({
21979
+ deleted: number().int().nonnegative(),
21980
+ secretsReleased: number().int().nonnegative()
21981
+ }), {
21982
+ kind: "mutation",
21983
+ auth: "admin",
21984
+ caller: "required"
21985
+ }), method(object({
21986
+ addonId: string().min(1),
21987
+ key: string().min(1)
21988
+ }), object({
21989
+ removed: boolean(),
21990
+ secretsCleared: number().int().nonnegative()
21991
+ }), {
21992
+ kind: "mutation",
21993
+ auth: "admin",
21994
+ caller: "required"
21995
+ });
21442
21996
  /**
21443
21997
  * record-entries - the ONE admin surface for every list of user-created entries
21444
21998
  * (D881): notification targets, brokers, LLM profiles, anything an addon
@@ -21577,6 +22131,12 @@ method(object({
21577
22131
  }), method(object({ deviceId: number().int() }), object({ refs: array(string()).readonly() }), {
21578
22132
  kind: "mutation",
21579
22133
  auth: "admin"
22134
+ }), method(object({
22135
+ refs: RefListSchema,
22136
+ onBehalfOf: string().min(1)
22137
+ }), object({ released: number().int() }), {
22138
+ kind: "mutation",
22139
+ auth: "admin"
21580
22140
  }), method(object({}), array(KeychainSecretMetaSchema).readonly(), { auth: "admin" }), method(object({}), KeychainStateSchema, { auth: "admin" });
21581
22141
  /**
21582
22142
  * device-history — the ONE mechanism for numeric history (D850).
@@ -22366,7 +22926,9 @@ var DiscoveredChildDeviceSchema = lazy(() => object({
22366
22926
  /** Upstream-system identity + rendering hints for the candidate. */
22367
22927
  sourceInfo: SourceInfoSchema.optional(),
22368
22928
  /** Nested entity-children for accordion preview — integrations populate, Hubs omit. */
22369
- children: array(DiscoveredChildDeviceSchema).readonly().optional()
22929
+ children: array(DiscoveredChildDeviceSchema).readonly().optional(),
22930
+ /** `childNativeId` of the gateway candidate in the same list this device sits behind. */
22931
+ parentStableId: string().optional()
22370
22932
  }));
22371
22933
  /**
22372
22934
  * Status block — populated by the integration after each
@@ -42681,6 +43243,79 @@ object({
42681
43243
  metadata: record(string(), unknown()).optional()
42682
43244
  });
42683
43245
  /**
43246
+ * HOW a provider can be found on the network. The provider only DECLARES the
43247
+ * mode; the `local-network` scan engine runs it once for every provider that
43248
+ * asked (one multicast browse, one host sweep) and hands the raw hits back to
43249
+ * `classifyDiscoveryHits`. A provider never opens a discovery socket itself,
43250
+ * except through a `custom` mode.
43251
+ */
43252
+ var DiscoveryModeSchema = discriminatedUnion("kind", [
43253
+ object({
43254
+ kind: literal("mdns"),
43255
+ /** DNS-SD service type, e.g. `_esphomelib._tcp`. */
43256
+ serviceType: string().min(1),
43257
+ /** Regular expression an instance name must match (case-insensitive). */
43258
+ instancePattern: string().optional()
43259
+ }),
43260
+ object({
43261
+ kind: literal("ws-discovery"),
43262
+ /** Probe types (e.g. `dn:NetworkVideoTransmitter`); omitted = any device. */
43263
+ types: array(string()).optional()
43264
+ }),
43265
+ object({
43266
+ kind: literal("ssdp"),
43267
+ searchTarget: string().min(1)
43268
+ }),
43269
+ object({
43270
+ kind: literal("udp-broadcast"),
43271
+ port: number().int().min(1).max(65535),
43272
+ payloadHex: string().regex(/^([0-9a-fA-F]{2})*$/),
43273
+ /** Port replies arrive on, when the device answers somewhere other than the source port. */
43274
+ listenPort: number().int().min(1).max(65535).optional()
43275
+ }),
43276
+ object({
43277
+ kind: literal("http-probe"),
43278
+ port: number().int().min(1).max(65535),
43279
+ path: string().startsWith("/"),
43280
+ method: _enum(["GET", "POST"]).default("GET"),
43281
+ bodyText: string().optional(),
43282
+ contentType: string().optional(),
43283
+ tls: boolean().default(false),
43284
+ /** Status codes that count as a hit. Omitted = any HTTP answer. */
43285
+ acceptStatus: array(number().int()).optional()
43286
+ }),
43287
+ object({
43288
+ kind: literal("custom"),
43289
+ /** Why the engine cannot do it (an allowlisted exception, e.g. a vendor UDP protocol). */
43290
+ reason: string().min(1)
43291
+ })
43292
+ ]);
43293
+ /** What the engine saw on the wire for ONE declared mode; the provider classifies it. */
43294
+ var DiscoveryHitSchema = object({
43295
+ /** Index into the provider's own `getDiscoveryModes()` array. */
43296
+ modeIndex: number().int().min(0),
43297
+ host: string(),
43298
+ port: number().int(),
43299
+ http: object({
43300
+ status: number().int(),
43301
+ headers: record(string(), string()),
43302
+ /** At most 16 KiB. */
43303
+ bodyText: string()
43304
+ }).optional(),
43305
+ mdns: object({
43306
+ instance: string(),
43307
+ host: string(),
43308
+ port: number().int(),
43309
+ txt: record(string(), string())
43310
+ }).optional(),
43311
+ udp: object({ payloadHex: string() }).optional(),
43312
+ wsd: object({
43313
+ xaddrs: array(string()),
43314
+ types: array(string()),
43315
+ scopes: array(string())
43316
+ }).optional()
43317
+ });
43318
+ /**
42684
43319
  * Candidate handed back from discovery and accepted by
42685
43320
  * `adoptDiscoveredDevice`. Shape mirrors the in-process
42686
43321
  * `DiscoveredDevice` interface used by `DeviceDiscovery`.
@@ -42715,7 +43350,17 @@ var DiscoveryCandidateSchema = object({
42715
43350
  /** Numeric id of the device this candidate was matched to. Set with `alreadyOnboarded`. */
42716
43351
  onboardedDeviceId: number().optional(),
42717
43352
  /** Operator-facing name of the matched device, so the UI can say WHICH one it is. */
42718
- onboardedName: string().optional()
43353
+ onboardedName: string().optional(),
43354
+ /**
43355
+ * This candidate is an INTEGRATION to set up (a gateway, a hub, a controller),
43356
+ * not a device to adopt. Replaces the `prefilledConfig.setupIntegration` marker,
43357
+ * which is still read for one release.
43358
+ */
43359
+ setupIntegration: boolean().optional(),
43360
+ /** `stableId` of the gateway candidate this device was found BEHIND. */
43361
+ parentStableId: string().optional(),
43362
+ /** `false` = shown for context but not pickable. Default true. */
43363
+ selectable: boolean().optional()
42719
43364
  });
42720
43365
  /**
42721
43366
  * Flat device summary returned by `createDevice` / `adoptDiscoveredDevice`.
@@ -42768,6 +43413,12 @@ method(_void(), _void(), { kind: "mutation" }), method(_void(), _void(), { kind:
42768
43413
  }), method(object({}), boolean()), method(object({ params: record(string(), unknown()).optional() }), array(DiscoveryCandidateSchema), {
42769
43414
  kind: "mutation",
42770
43415
  auth: "admin"
43416
+ }), method(object({}), array(DiscoveryModeSchema)), method(object({
43417
+ hits: array(DiscoveryHitSchema),
43418
+ params: record(string(), unknown()).optional()
43419
+ }), array(DiscoveryCandidateSchema), {
43420
+ kind: "mutation",
43421
+ auth: "admin"
42771
43422
  }), method(object({}), CreationSchemaOutputSchema), method(object({}), object({ deviceType: _enum(DeviceType).nullable() })), method(object({ candidate: DiscoveryCandidateSchema }), DeviceSummarySchema, {
42772
43423
  kind: "mutation",
42773
43424
  auth: "admin"
@@ -43495,17 +44146,23 @@ method(object({
43495
44146
  }), method(object({}), object({ providers: array(object({
43496
44147
  addonId: string(),
43497
44148
  label: string()
43498
- })).readonly() }), { auth: "admin" }), method(object({}), object({ groups: array(object({
43499
- addonId: string(),
43500
- label: string(),
43501
- candidates: array(DiscoveryCandidateSchema).readonly(),
43502
- error: string().nullable()
43503
- })).readonly() }), {
43504
- kind: "mutation",
43505
- auth: "admin"
43506
- }), method(object({
44149
+ })).readonly() }), { auth: "admin" }), method(
44150
+ /** `subnets`: extra CIDRs for THIS scan, on top of the hub's own and the saved list. */
44151
+ object({ subnets: array(string()).optional() }),
44152
+ object({ groups: array(object({
44153
+ addonId: string(),
44154
+ label: string(),
44155
+ candidates: array(DiscoveryCandidateSchema).readonly(),
44156
+ error: string().nullable()
44157
+ })).readonly() }),
44158
+ {
44159
+ kind: "mutation",
44160
+ auth: "admin"
44161
+ }
44162
+ ), method(object({
43507
44163
  addonId: string(),
43508
- params: record(string(), unknown()).optional()
44164
+ params: record(string(), unknown()).optional(),
44165
+ subnets: array(string()).optional()
43509
44166
  }), object({ candidates: array(DiscoveryCandidateSchema).readonly() }), {
43510
44167
  kind: "mutation",
43511
44168
  auth: "admin"
@@ -61125,7 +61782,9 @@ method(_void(), array(IntegrationWithStateSchema)), method(object({ id: string()
61125
61782
  id: string(),
61126
61783
  settings: record(string(), unknown()),
61127
61784
  /** Keys the form's schema declares secret (by type) — keychain-stored (D787). */
61128
- secretKeys: array(string()).optional()
61785
+ secretKeys: array(string()).optional(),
61786
+ /** The integration's own addon applies the change in-process and asks the hub not to respawn it. */
61787
+ skipRestart: boolean().optional()
61129
61788
  }), object({ success: boolean() }), {
61130
61789
  kind: "mutation",
61131
61790
  auth: "admin"
@@ -61602,6 +62261,39 @@ var TlsStatusSchema = object({
61602
62261
  hasCa: boolean()
61603
62262
  }).nullable()
61604
62263
  });
62264
+ /** One provider's ask of the scan engine: the modes it declared. */
62265
+ var ScanRequestSchema = object({
62266
+ providerId: string().min(1),
62267
+ modes: array(DiscoveryModeSchema)
62268
+ });
62269
+ var ScanErrorSchema = object({
62270
+ /** Absent for a failure that is the scan's own (a bad subnet, a socket that would not open). */
62271
+ providerId: string().optional(),
62272
+ /** The mode family that failed (`mdns`, `http-probe`, …). */
62273
+ mode: string().optional(),
62274
+ message: string()
62275
+ });
62276
+ /** What one scan did, per mode family; the engine logs the same numbers once. */
62277
+ var ScanStatsSchema = object({
62278
+ hostsProbed: number().int(),
62279
+ hits: number().int(),
62280
+ timeouts: number().int(),
62281
+ byMode: record(string(), object({
62282
+ probed: number().int(),
62283
+ hits: number().int(),
62284
+ timeouts: number().int()
62285
+ }))
62286
+ });
62287
+ var ScanNetworkResultSchema = object({
62288
+ hits: array(object({
62289
+ providerId: string(),
62290
+ hit: DiscoveryHitSchema
62291
+ })),
62292
+ /** CIDRs actually covered: the hub's own /24s, the saved list and the per-call list. */
62293
+ subnetsScanned: array(string()),
62294
+ errors: array(ScanErrorSchema),
62295
+ stats: ScanStatsSchema
62296
+ });
61605
62297
  method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(object({
61606
62298
  /**
61607
62299
  * LEGACY HINT — do not send from new code. Kept optional so clients
@@ -61656,6 +62348,16 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
61656
62348
  }), method(_void(), object({ pem: string() })), method(_void(), TlsStatusSchema, {
61657
62349
  kind: "mutation",
61658
62350
  auth: "admin"
62351
+ }), method(object({
62352
+ requests: array(ScanRequestSchema),
62353
+ subnets: array(string()).optional()
62354
+ }), ScanNetworkResultSchema, {
62355
+ kind: "mutation",
62356
+ auth: "admin",
62357
+ timeoutMs: 9e4
62358
+ }), method(_void(), object({ subnets: array(string()) }), { auth: "admin" }), method(object({ subnets: array(string()) }), object({ subnets: array(string()) }), {
62359
+ kind: "mutation",
62360
+ auth: "admin"
61659
62361
  }), method(_void(), TlsStatusSchema, {
61660
62362
  kind: "mutation",
61661
62363
  auth: "admin"
@@ -68599,6 +69301,13 @@ Object.freeze({
68599
69301
  access: "create",
68600
69302
  role: "admin"
68601
69303
  },
69304
+ "addonSettings.getDeclaredSettingsKeys": {
69305
+ capName: "addon-settings",
69306
+ capScope: "system",
69307
+ addonId: null,
69308
+ access: "view",
69309
+ role: "admin"
69310
+ },
68602
69311
  "addonSettings.getDeviceSettings": {
68603
69312
  capName: "addon-settings",
68604
69313
  capScope: "system",
@@ -68620,6 +69329,13 @@ Object.freeze({
68620
69329
  access: "view",
68621
69330
  role: "admin"
68622
69331
  },
69332
+ "addonSettings.unsetStoredSettingsKeys": {
69333
+ capName: "addon-settings",
69334
+ capScope: "system",
69335
+ addonId: null,
69336
+ access: "create",
69337
+ role: "admin"
69338
+ },
68623
69339
  "addonSettings.updateDeviceSettings": {
68624
69340
  capName: "addon-settings",
68625
69341
  capScope: "system",
@@ -71035,6 +71751,13 @@ Object.freeze({
71035
71751
  access: "create",
71036
71752
  role: "admin"
71037
71753
  },
71754
+ "deviceProvider.classifyDiscoveryHits": {
71755
+ capName: "device-provider",
71756
+ capScope: "system",
71757
+ addonId: null,
71758
+ access: "create",
71759
+ role: "admin"
71760
+ },
71038
71761
  "deviceProvider.createDevice": {
71039
71762
  capName: "device-provider",
71040
71763
  capScope: "system",
@@ -71063,6 +71786,13 @@ Object.freeze({
71063
71786
  access: "view",
71064
71787
  role: "admin"
71065
71788
  },
71789
+ "deviceProvider.getDiscoveryModes": {
71790
+ capName: "device-provider",
71791
+ capScope: "system",
71792
+ addonId: null,
71793
+ access: "view",
71794
+ role: "admin"
71795
+ },
71066
71796
  "deviceProvider.getDiscoveryParamsSchema": {
71067
71797
  capName: "device-provider",
71068
71798
  capScope: "system",
@@ -71840,6 +72570,13 @@ Object.freeze({
71840
72570
  access: "create",
71841
72571
  role: "admin"
71842
72572
  },
72573
+ "keychain.releaseRecordRefs": {
72574
+ capName: "keychain",
72575
+ capScope: "system",
72576
+ addonId: null,
72577
+ access: "create",
72578
+ role: "admin"
72579
+ },
71843
72580
  "keychain.resolve": {
71844
72581
  capName: "keychain",
71845
72582
  capScope: "system",
@@ -72260,6 +72997,13 @@ Object.freeze({
72260
72997
  access: "view",
72261
72998
  role: "admin"
72262
72999
  },
73000
+ "localNetwork.getScanSubnets": {
73001
+ capName: "local-network",
73002
+ capScope: "system",
73003
+ addonId: null,
73004
+ access: "view",
73005
+ role: "admin"
73006
+ },
72263
73007
  "localNetwork.getTlsStatus": {
72264
73008
  capName: "local-network",
72265
73009
  capScope: "system",
@@ -72309,6 +73053,13 @@ Object.freeze({
72309
73053
  access: "create",
72310
73054
  role: "admin"
72311
73055
  },
73056
+ "localNetwork.scanNetwork": {
73057
+ capName: "local-network",
73058
+ capScope: "system",
73059
+ addonId: null,
73060
+ access: "create",
73061
+ role: "admin"
73062
+ },
72312
73063
  "localNetwork.setAddressRole": {
72313
73064
  capName: "local-network",
72314
73065
  capScope: "system",
@@ -72330,6 +73081,13 @@ Object.freeze({
72330
73081
  access: "create",
72331
73082
  role: "admin"
72332
73083
  },
73084
+ "localNetwork.setScanSubnets": {
73085
+ capName: "local-network",
73086
+ capScope: "system",
73087
+ addonId: null,
73088
+ access: "create",
73089
+ role: "admin"
73090
+ },
72333
73091
  "localNetwork.setViewerEndpoints": {
72334
73092
  capName: "local-network",
72335
73093
  capScope: "system",
@@ -76551,6 +77309,13 @@ Object.freeze({
76551
77309
  access: "create",
76552
77310
  role: "admin"
76553
77311
  },
77312
+ "storageAdmin.dropOrphanedKind": {
77313
+ capName: "storage-admin",
77314
+ capScope: "system",
77315
+ addonId: null,
77316
+ access: "delete",
77317
+ role: "admin"
77318
+ },
76554
77319
  "storageAdmin.listKinds": {
76555
77320
  capName: "storage-admin",
76556
77321
  capScope: "system",
@@ -76572,6 +77337,27 @@ Object.freeze({
76572
77337
  access: "view",
76573
77338
  role: "admin"
76574
77339
  },
77340
+ "storageAdmin.scanStaleData": {
77341
+ capName: "storage-admin",
77342
+ capScope: "system",
77343
+ addonId: null,
77344
+ access: "create",
77345
+ role: "admin"
77346
+ },
77347
+ "storageAdmin.staleDataReport": {
77348
+ capName: "storage-admin",
77349
+ capScope: "system",
77350
+ addonId: null,
77351
+ access: "view",
77352
+ role: "admin"
77353
+ },
77354
+ "storageAdmin.unsetStaleSettingsKey": {
77355
+ capName: "storage-admin",
77356
+ capScope: "system",
77357
+ addonId: null,
77358
+ access: "create",
77359
+ role: "admin"
77360
+ },
76575
77361
  "storageEvictable.evict": {
76576
77362
  capName: "storage-evictable",
76577
77363
  capScope: "system",