@camstack/types 1.2.65 → 1.2.67

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 (79) hide show
  1. package/dist/addon.js +2 -2
  2. package/dist/addon.mjs +2 -2
  3. package/dist/capabilities/air-quality-sensor.cap.d.ts +10 -0
  4. package/dist/capabilities/alarm-panel.cap.d.ts +10 -0
  5. package/dist/capabilities/ambient-light-sensor.cap.d.ts +10 -0
  6. package/dist/capabilities/audio-metrics.cap.d.ts +7 -0
  7. package/dist/capabilities/automation-control.cap.d.ts +7 -0
  8. package/dist/capabilities/battery.cap.d.ts +10 -0
  9. package/dist/capabilities/binary.cap.d.ts +10 -0
  10. package/dist/capabilities/brightness.cap.d.ts +7 -0
  11. package/dist/capabilities/camera-streams.cap.d.ts +7 -0
  12. package/dist/capabilities/capability-definition.d.ts +58 -0
  13. package/dist/capabilities/carbon-monoxide.cap.d.ts +10 -0
  14. package/dist/capabilities/climate-control.cap.d.ts +7 -0
  15. package/dist/capabilities/color.cap.d.ts +7 -0
  16. package/dist/capabilities/connectivity.cap.d.ts +10 -0
  17. package/dist/capabilities/consumables.cap.d.ts +7 -0
  18. package/dist/capabilities/contact.cap.d.ts +10 -0
  19. package/dist/capabilities/control.cap.d.ts +7 -0
  20. package/dist/capabilities/cover.cap.d.ts +10 -0
  21. package/dist/capabilities/day-night.cap.d.ts +10 -0
  22. package/dist/capabilities/device-discovery.cap.d.ts +7 -0
  23. package/dist/capabilities/device-status.cap.d.ts +10 -0
  24. package/dist/capabilities/doorbell.cap.d.ts +7 -0
  25. package/dist/capabilities/enum-sensor.cap.d.ts +10 -0
  26. package/dist/capabilities/event-emitter.cap.d.ts +7 -0
  27. package/dist/capabilities/fan-control.cap.d.ts +7 -0
  28. package/dist/capabilities/feature-probe.cap.d.ts +7 -0
  29. package/dist/capabilities/flood.cap.d.ts +10 -0
  30. package/dist/capabilities/gas.cap.d.ts +10 -0
  31. package/dist/capabilities/humidifier.cap.d.ts +7 -0
  32. package/dist/capabilities/humidity-sensor.cap.d.ts +10 -0
  33. package/dist/capabilities/image-settings.cap.d.ts +10 -0
  34. package/dist/capabilities/image.cap.d.ts +7 -0
  35. package/dist/capabilities/index.d.ts +3 -3
  36. package/dist/capabilities/lawn-mower-control.cap.d.ts +7 -0
  37. package/dist/capabilities/lock-control.cap.d.ts +10 -0
  38. package/dist/capabilities/media-player.cap.d.ts +7 -0
  39. package/dist/capabilities/motion-trigger.cap.d.ts +7 -0
  40. package/dist/capabilities/motion-zones.cap.d.ts +10 -0
  41. package/dist/capabilities/motion.cap.d.ts +7 -0
  42. package/dist/capabilities/native-object-detection.cap.d.ts +10 -0
  43. package/dist/capabilities/notification-output.cap.d.ts +62 -0
  44. package/dist/capabilities/notification-rules.cap.d.ts +134 -6
  45. package/dist/capabilities/notifier.cap.d.ts +7 -0
  46. package/dist/capabilities/numeric-sensor.cap.d.ts +10 -0
  47. package/dist/capabilities/osd-manager.cap.d.ts +14 -0
  48. package/dist/capabilities/pet-feeder.cap.d.ts +7 -0
  49. package/dist/capabilities/power-meter.cap.d.ts +10 -0
  50. package/dist/capabilities/presence.cap.d.ts +10 -0
  51. package/dist/capabilities/pressure-sensor.cap.d.ts +10 -0
  52. package/dist/capabilities/privacy-mask.cap.d.ts +10 -0
  53. package/dist/capabilities/ptz-autotrack.cap.d.ts +7 -0
  54. package/dist/capabilities/scene-monitor.cap.d.ts +7 -0
  55. package/dist/capabilities/script-runner.cap.d.ts +7 -0
  56. package/dist/capabilities/smoke.cap.d.ts +10 -0
  57. package/dist/capabilities/stream-params.cap.d.ts +10 -0
  58. package/dist/capabilities/switch.cap.d.ts +10 -0
  59. package/dist/capabilities/tamper.cap.d.ts +10 -0
  60. package/dist/capabilities/temperature-sensor.cap.d.ts +10 -0
  61. package/dist/capabilities/update.cap.d.ts +7 -0
  62. package/dist/capabilities/vacuum-control.cap.d.ts +7 -0
  63. package/dist/capabilities/valve.cap.d.ts +7 -0
  64. package/dist/capabilities/vibration.cap.d.ts +10 -0
  65. package/dist/capabilities/water-heater.cap.d.ts +7 -0
  66. package/dist/capabilities/weather.cap.d.ts +7 -0
  67. package/dist/capabilities/zone-analytics.cap.d.ts +7 -0
  68. package/dist/capabilities/zone-rules.cap.d.ts +7 -0
  69. package/dist/capabilities/zones.cap.d.ts +7 -0
  70. package/dist/catalogs/coco-classmap.d.ts +21 -0
  71. package/dist/device/system-mirror.d.ts +37 -0
  72. package/dist/generated/runtime-state-durability.d.ts +31 -0
  73. package/dist/index.d.ts +4 -1
  74. package/dist/index.js +1345 -91
  75. package/dist/index.mjs +1322 -92
  76. package/dist/notification/audio-condition.d.ts +118 -0
  77. package/dist/{sleep-BxjBLp3L.mjs → sleep-CSgK0rNX.mjs} +3 -1
  78. package/dist/{sleep-BszXLEvP.js → sleep-Dh1EJSqF.js} +8 -0
  79. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  const require_event_category = require("./event-category-DxZbWydC.js");
3
- const require_sleep = require("./sleep-BszXLEvP.js");
3
+ const require_sleep = require("./sleep-Dh1EJSqF.js");
4
4
  const require_canonical_hash = require("./canonical-hash-DNV8S5ET.js");
5
5
  const require_enums = require("./enums.js");
6
6
  const require_err_msg = require("./err-msg-COpsHMw2.js");
@@ -6664,7 +6664,14 @@ var cameraStreamsCapability = {
6664
6664
  low: zod.z.string().optional()
6665
6665
  }),
6666
6666
  lastChangedAt: zod.z.number()
6667
- })
6667
+ }),
6668
+ /**
6669
+ * Runtime-state durability: **session** — a restored `slotStatuses: streaming` for a camera that has been dark for two hours is a lie the UI renders as truth.
6670
+ *
6671
+ * See `RuntimeStateDurability`. Enforced by
6672
+ * `scripts/check-runtime-state-durability.ts`.
6673
+ */
6674
+ durability: "session"
6668
6675
  };
6669
6676
  //#endregion
6670
6677
  //#region src/capabilities/cap-router-predicates.ts
@@ -7831,6 +7838,13 @@ var deviceDiscoveryCapability = {
7831
7838
  kind: "poll"
7832
7839
  },
7833
7840
  runtimeState: DeviceDiscoveryStatusSchema.extend({ lastFetchedAt: zod.z.number().int().nonnegative() }),
7841
+ /**
7842
+ * Runtime-state durability: **session** — 5.7 KB of scan output on the largest device, fully re-derivable by re-scanning.
7843
+ *
7844
+ * See `RuntimeStateDurability`. Enforced by
7845
+ * `scripts/check-runtime-state-durability.ts`.
7846
+ */
7847
+ durability: "session",
7834
7848
  methods: {
7835
7849
  /**
7836
7850
  * Snapshot of the current `discovered` list. Returns the
@@ -10453,11 +10467,33 @@ var NotificationFormatSchema = zod.z.enum([
10453
10467
  * Named by INTENT, never by glyph. "check" would tie the vocabulary to one
10454
10468
  * renderer's icon set; "acknowledge" survives an adapter that draws it
10455
10469
  * differently.
10470
+ *
10471
+ * ── A TOKEN IS NOT A WIRE VALUE ─────────────────────────────────────
10472
+ *
10473
+ * These names are for US. **No adapter may forward one verbatim.** Each maps
10474
+ * the whole set onto its own renderer's vocabulary through a
10475
+ * `Record<NotificationActionIcon, string>` — a Record, never a lookup with a
10476
+ * fallback, so adding a member here fails every adapter's build until someone
10477
+ * decides its glyph, which is the only place that decision can be made
10478
+ * honestly.
10479
+ *
10480
+ * This paragraph is the bug. Zentik declared `actionIcons: true` and passed
10481
+ * `disarm` straight through; iOS feeds that string to
10482
+ * `UNNotificationActionIcon(systemImageName:)`, `disarm` is not an SF Symbol,
10483
+ * and every snooze and alarm button arrived BLANK. A pass-through is not a
10484
+ * mapping, and "the field is documented" is not "the value renders".
10485
+ *
10486
+ * Adding a member is TRAIN-BOUND. The enum lives in the published
10487
+ * `@camstack/server` closure and the cap seam validates against the HUB's copy,
10488
+ * so an addon that emits a token the running hub does not know does not lose an
10489
+ * icon — its whole `send` fails Zod validation and the notification never
10490
+ * arrives. Never emit a new token from an addon before the train carrying it.
10456
10491
  */
10457
10492
  var NotificationActionIconSchema = zod.z.enum([
10458
10493
  "acknowledge",
10459
10494
  "dismiss",
10460
10495
  "silence",
10496
+ "snooze",
10461
10497
  "view",
10462
10498
  "play",
10463
10499
  "open",
@@ -10465,9 +10501,13 @@ var NotificationActionIconSchema = zod.z.enum([
10465
10501
  "lock",
10466
10502
  "unlock",
10467
10503
  "arm",
10504
+ "arm-home",
10505
+ "arm-away",
10506
+ "arm-night",
10468
10507
  "disarm",
10469
10508
  "light",
10470
- "alert"
10509
+ "alert",
10510
+ "camera"
10471
10511
  ]);
10472
10512
  /** A single tap-through action button. */
10473
10513
  var NotificationActionSchema = zod.z.object({
@@ -10483,7 +10523,23 @@ var NotificationActionSchema = zod.z.object({
10483
10523
  * else — see `notification-center/action-token.ts` for what that does and
10484
10524
  * does not buy.
10485
10525
  */
10486
- destructive: zod.z.boolean().optional()
10526
+ destructive: zod.z.boolean().optional(),
10527
+ /**
10528
+ * How the tap should REACH the url.
10529
+ *
10530
+ * `navigate` (absent, and every button authored before this field) opens it:
10531
+ * the phone leaves the notification and shows whatever the callback returns.
10532
+ * That is right for a button whose answer the operator wants to read.
10533
+ *
10534
+ * `background` fires it as a POST and stays put. It exists for the buttons
10535
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
10536
+ * an answer to the notification, and being thrown into a browser tab to
10537
+ * confirm it costs more attention than the notification did. A backend that
10538
+ * cannot do a background call renders it as an ordinary link (the adapters
10539
+ * fall back rather than dropping the button), so this is a preference, never
10540
+ * a requirement.
10541
+ */
10542
+ mode: zod.z.enum(["navigate", "background"]).optional()
10487
10543
  });
10488
10544
  /**
10489
10545
  * The canonical notification. `body` is the only hard field (Apprise model).
@@ -11009,6 +11065,29 @@ var MACRO_LABELS = [
11009
11065
  name: "Package"
11010
11066
  }
11011
11067
  ];
11068
+ /**
11069
+ * The class names a per-class COUNT can ever be keyed on — the macro ids, and
11070
+ * only those.
11071
+ *
11072
+ * `preserveOriginal: false` below is the whole reason this set exists: the
11073
+ * executor maps each raw COCO label onto its macro and DROPS everything else,
11074
+ * so a tracked detection's `className` is always one of {@link MACRO_LABELS}.
11075
+ * Any surface that indexes counts by class name — the ZoneAnalytics occupancy
11076
+ * snapshot's `byClass`, and therefore every occupancy rule — can match nothing
11077
+ * else. Derived from the catalog, never hand-listed, so a new macro joins it by
11078
+ * being declared once.
11079
+ *
11080
+ * Cost of not having it, 2026-08-12: the notification rule editor offered the
11081
+ * full taxonomy for an occupancy class (occupancy is the one condition that
11082
+ * does not go through the engine's macro-expanding `expandClassSelector`), two
11083
+ * live rules were authored on `car`, and both counters read 0 forever.
11084
+ */
11085
+ var DETECTION_MACRO_CLASSES = new Set(MACRO_LABELS.map((l) => l.id));
11086
+ /** True when `className` is a macro the detector can actually emit (and hence a
11087
+ * class a count can be keyed on) — see {@link DETECTION_MACRO_CLASSES}. */
11088
+ function isDetectionMacroClass(className) {
11089
+ return DETECTION_MACRO_CLASSES.has(className.trim().toLowerCase());
11090
+ }
11012
11091
  var COCO_TO_MACRO = {
11013
11092
  mapping: {
11014
11093
  person: "person",
@@ -11726,7 +11805,17 @@ var alarmPanelCapability = {
11726
11805
  * full slice; renders an arm button per `availableModes` entry and
11727
11806
  * a PIN field iff `requiresCode === true`.
11728
11807
  */
11729
- runtimeState: AlarmPanelStatusSchema
11808
+ runtimeState: AlarmPanelStatusSchema,
11809
+ /**
11810
+ * Runtime-state durability: **restored** — armed state is the one thing a panel must not lose across a restart.
11811
+ *
11812
+ * See `RuntimeStateDurability`. Enforced by
11813
+ * `scripts/check-runtime-state-durability.ts`.
11814
+ */
11815
+ durability: "restored",
11816
+ /** Clock fields: written, but excluded from the compare that decides
11817
+ * whether persisting is worth a SQLite commit. */
11818
+ volatileStateFields: ["lastChangedAt"]
11730
11819
  };
11731
11820
  //#endregion
11732
11821
  //#region src/capabilities/mask-shape.ts
@@ -11874,6 +11963,8 @@ var NcSystemEventKindSchema = zod.z.enum([
11874
11963
  "alarm-triggered",
11875
11964
  "alarm-armed",
11876
11965
  "alarm-disarmed",
11966
+ "alarm-arming",
11967
+ "alarm-arm-refused",
11877
11968
  "camera-online",
11878
11969
  "camera-offline",
11879
11970
  "camera-disabled",
@@ -11893,16 +11984,22 @@ var NC_LEGACY_SYSTEM_EVENT_KINDS = new Set([
11893
11984
  */
11894
11985
  var NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NcSystemEventKindSchema.options.filter((kind) => !NC_LEGACY_SYSTEM_EVENT_KINDS.has(kind));
11895
11986
  /**
11896
- * The panel's three transitions, as ONE list.
11987
+ * The panel's transitions, as ONE list.
11897
11988
  *
11898
11989
  * Named here rather than spelled out at each of the four sites that need them
11899
11990
  * (the emitter, the intake, the editor group, the combined-notification gate),
11900
- * because a fourth transition added to the enum and forgotten at one of them is
11901
- * an alarm state nobody can be notified about.
11991
+ * because a transition added to the enum and forgotten at one of them is an
11992
+ * alarm state nobody can be notified about. That is exactly what this list
11993
+ * failed to prevent while it held three: `alarm-arming` was the fourth.
11994
+ *
11995
+ * In LIFECYCLE order, which is the order an editor lists them in and the order
11996
+ * a person experiences them — not the order they were added.
11902
11997
  */
11903
11998
  var NC_ALARM_SYSTEM_EVENT_KINDS = [
11904
- "alarm-triggered",
11999
+ "alarm-arm-refused",
12000
+ "alarm-arming",
11905
12001
  "alarm-armed",
12002
+ "alarm-triggered",
11906
12003
  "alarm-disarmed"
11907
12004
  ];
11908
12005
  /**
@@ -11934,6 +12031,26 @@ var NcSystemEventConditionSchema = zod.z.object({
11934
12031
  * unique key `(ruleId, trackId, targetId)`. Not a rule field in P1.
11935
12032
  */
11936
12033
  var NC_MAX_PER_TRACK_IMMEDIATE = 1;
12034
+ /** Hard ceiling on a window (24h). A snooze that could not expire would be an
12035
+ * outage the operator asked for once and forgot. */
12036
+ var NC_SNOOZE_MAX_MINUTES = 1440;
12037
+ /**
12038
+ * The durations a notification offers when its rule does not say otherwise.
12039
+ *
12040
+ * MINUTES, and short ones: a snooze button is answered on a lock screen, and
12041
+ * the three choices that fit there are "the next few minutes", "the next half
12042
+ * hour" and "the next hour". Anything longer is a decision somebody sits down
12043
+ * to make, which is what the app's own snooze surface is for.
12044
+ *
12045
+ * Resolved in CODE (`snoozeChoices`), never as a Zod default — see
12046
+ * {@link NcRuleInputSchema.shape.snoozeOptions} for why the distinction is
12047
+ * load-bearing.
12048
+ */
12049
+ var NC_DEFAULT_SNOOZE_MINUTES = [
12050
+ 10,
12051
+ 30,
12052
+ 60
12053
+ ];
11937
12054
  /** One weekly activation window. `startMinute > endMinute` crosses midnight. */
11938
12055
  var NcScheduleWindowSchema = zod.z.object({
11939
12056
  /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
@@ -12335,15 +12452,15 @@ var NcConditionsSchema = zod.z.object({
12335
12452
  * (an `immediate` rule naming an `audio-*` class, one notification per
12336
12453
  * classified sample) stays exactly as it was for rules that already use it.
12337
12454
  *
12338
- * NOT in {@link NC_CONDITION_CATALOG} yet, and that is the sequencing rule
12339
- * rather than an oversight: the viewer mirrors the descriptor enums BY HAND
12340
- * (`camstack/src/data/notification-center.ts`, guarded by
12341
- * `scripts/check-viewer-condition-mirror.ts`) and its rule editor STRIPS the
12455
+ * In {@link NC_CONDITION_CATALOG} since P2, and the ORDER it got there is the
12456
+ * rule rather than an accident: the viewer mirrors the descriptor enums BY
12457
+ * HAND (`camstack/src/data/notification-center.ts`, guarded by
12458
+ * `scripts/check-viewer-condition-mirror.ts`) and its rule editor strips the
12342
12459
  * condition fields it does not know when a rule is saved from the phone.
12343
12460
  * Publishing an editor for a condition the app cannot round-trip is how an
12344
- * operator loses a rule's conditions by opening it — so the descriptor, the
12345
- * admin widget and the viewer mirror land together (P2 + P3), and only then
12346
- * does an audio rule become authorable.
12461
+ * operator loses a rule's conditions by opening it — so the viewer mirror
12462
+ * (P3, shipped) went FIRST, and the descriptor an editor renders from
12463
+ * follows here.
12347
12464
  */
12348
12465
  audio: NcAudioConditionSchema.optional()
12349
12466
  });
@@ -12580,6 +12697,30 @@ var NcRuleInputSchema = zod.z.object({
12580
12697
  */
12581
12698
  snoozeAllowGlobal: zod.z.boolean().optional(),
12582
12699
  /**
12700
+ * The snooze durations THIS rule's notification offers as buttons, in
12701
+ * minutes.
12702
+ *
12703
+ * Three states, and all three are distinct — which is exactly why this is
12704
+ * `.optional()` and never `.default()`. A Zod default does not run on the
12705
+ * addon cap path (three production failures in one day), so a schema default
12706
+ * would collapse the first two:
12707
+ *
12708
+ * | value | meaning |
12709
+ * | --- | --- |
12710
+ * | absent | the operator never said ⇒ {@link NC_DEFAULT_SNOOZE_MINUTES} |
12711
+ * | `[]` | **no snooze buttons on this rule** — the explicit override |
12712
+ * | a list | these choices, de-duplicated and sorted, at most four |
12713
+ *
12714
+ * `.max(4)` because the notifier's own action budget is small (ntfy allows
12715
+ * three buttons in total) and a rule that spent it all on snooze choices
12716
+ * would push its own tap-through actions off the notification.
12717
+ *
12718
+ * An empty list is NOT an alarm exemption: a rule the alarm is about, or
12719
+ * that arms the panel, is exempt automatically and cannot be silenced by a
12720
+ * window from anywhere (D133).
12721
+ */
12722
+ snoozeOptions: zod.z.array(zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES)).max(4).optional(),
12723
+ /**
12583
12724
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
12584
12725
  *
12585
12726
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -12679,6 +12820,7 @@ var NcConditionDescriptorSchema = zod.z.object({
12679
12820
  "device",
12680
12821
  "package",
12681
12822
  "occupancy",
12823
+ "audio",
12682
12824
  "system"
12683
12825
  ]),
12684
12826
  label: zod.z.string(),
@@ -12697,6 +12839,7 @@ var NcConditionDescriptorSchema = zod.z.object({
12697
12839
  "crossingSelect",
12698
12840
  "polygonDraw",
12699
12841
  "occupancy",
12842
+ "audio",
12700
12843
  "deviceState",
12701
12844
  "systemEvent"
12702
12845
  ]),
@@ -12787,13 +12930,17 @@ var NC_CONDITION_CATALOG = [
12787
12930
  label: "Server update available"
12788
12931
  },
12789
12932
  {
12790
- value: "alarm-triggered",
12791
- label: "Alarm triggered"
12933
+ value: "alarm-arming",
12934
+ label: "Alarm arming (exit delay)"
12792
12935
  },
12793
12936
  {
12794
12937
  value: "alarm-armed",
12795
12938
  label: "Alarm armed"
12796
12939
  },
12940
+ {
12941
+ value: "alarm-triggered",
12942
+ label: "Alarm triggered"
12943
+ },
12797
12944
  {
12798
12945
  value: "alarm-disarmed",
12799
12946
  label: "Alarm disarmed"
@@ -13094,6 +13241,16 @@ var NC_CONDITION_CATALOG = [
13094
13241
  phase: "P1",
13095
13242
  description: "ZoneAnalytics occupancy edge (optionally zone/class-scoped): count crosses the threshold and holds for sustainSeconds. Fail-closed on a missing snapshot."
13096
13243
  },
13244
+ {
13245
+ id: "audio",
13246
+ group: "audio",
13247
+ label: "Sound",
13248
+ valueType: "audio",
13249
+ operator: "anyOf",
13250
+ appliesTo: ["immediate"],
13251
+ phase: "P2",
13252
+ description: "Fires when at least hitPercent% of the samples in a samplingSeconds window are hits — a hit clears the dBFS floor AND carries one of the chosen sounds. Both filters are optional and independent, but naming NEITHER never matches: every sample would be a hit, so the engine refuses rather than notifying on silence. dBFS is negative-going (0 = full scale, -96 = silence)."
13253
+ },
13097
13254
  {
13098
13255
  id: "customZones",
13099
13256
  group: "zones",
@@ -13229,9 +13386,6 @@ var NcSnoozeScopeSchema = zod.z.enum([
13229
13386
  "device",
13230
13387
  "all"
13231
13388
  ]);
13232
- /** Hard ceiling on a window (24h). A snooze that could not expire would be an
13233
- * outage the operator asked for once and forgot. */
13234
- var NC_SNOOZE_MAX_MINUTES = 1440;
13235
13389
  /**
13236
13390
  * Client-authored snooze. The server stamps `userId`, `startedAt` and
13237
13391
  * `expiresAt` — a DURATION is sent rather than an instant so a client with a
@@ -13244,6 +13398,19 @@ var NcSnoozeInputSchema = zod.z.object({
13244
13398
  ruleId: zod.z.string().optional(),
13245
13399
  /** Required when `scope: 'device'`. */
13246
13400
  deviceId: zod.z.number().int().optional(),
13401
+ /**
13402
+ * Narrow the window to these subject classes — "the cat, not the person".
13403
+ *
13404
+ * ORTHOGONAL to `scope`, deliberately, and absent means EVERY class: that is
13405
+ * what every window authored before this field meant, so no persisted row
13406
+ * changes meaning and no client has to learn anything to keep working.
13407
+ *
13408
+ * It is what makes the window's real key `(deviceId, classes[])` and lets it
13409
+ * cross rules (D133): the operator points at a camera and a kind of thing,
13410
+ * not at whichever of their four rules happened to produce the notification
13411
+ * they are dismissing.
13412
+ */
13413
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
13247
13414
  durationMinutes: zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
13248
13415
  /**
13249
13416
  * Silence this for EVERY recipient, not just the caller. Permission is
@@ -13269,6 +13436,10 @@ var NcSnoozeSchema = zod.z.object({
13269
13436
  scope: NcSnoozeScopeSchema,
13270
13437
  ruleId: zod.z.string().optional(),
13271
13438
  deviceId: zod.z.number().int().optional(),
13439
+ /** Subject classes this window covers. ABSENT = every class — see
13440
+ * {@link NcSnoozeInputSchema.shape.classes}. Lives in the JSON blob and has
13441
+ * no SQLite column: nothing queries a window by class. */
13442
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
13272
13443
  startedAt: zod.z.number(),
13273
13444
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
13274
13445
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -16061,7 +16232,14 @@ var zonesCapability = {
16061
16232
  * handle. Slice shape is `{ zones: Zone[] }` so future extensions
16062
16233
  * (e.g. zone groupings) can sit alongside the polygon list.
16063
16234
  */
16064
- runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() })
16235
+ runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() }),
16236
+ /**
16237
+ * Runtime-state durability: **restored** — written only on operator mutation, so a camera that never had one has nothing to re-derive from. This is the slice `zone-mirror-hydration.ts` exists to paper over.
16238
+ *
16239
+ * See `RuntimeStateDurability`. Enforced by
16240
+ * `scripts/check-runtime-state-durability.ts`.
16241
+ */
16242
+ durability: "restored"
16065
16243
  };
16066
16244
  //#endregion
16067
16245
  //#region src/capabilities/pipeline-runner.cap.ts
@@ -16233,6 +16411,22 @@ var detectionFpsField = {
16233
16411
  default: 10,
16234
16412
  step: 1
16235
16413
  };
16414
+ /**
16415
+ * The occupancy re-check interval. DEFAULT 300 s (2026-08-13 — was 30 s).
16416
+ *
16417
+ * The recheck is now on by default (a parked car is invisible to occupancy
16418
+ * rules until the stationary registry has been rebuilt by motion, which after a
16419
+ * restart may be never on a quiet camera). Each cycle re-subscribes a detection
16420
+ * session — an RTSP re-dial — so the switch is only affordable at a WIDE
16421
+ * interval: 300 s is ~12 re-dials an hour per camera, against 120 at the old
16422
+ * 30 s. A parked car is therefore counted within 5 minutes of a restart.
16423
+ *
16424
+ * Why not wider: `max` is 300 and raising it is TRAIN-BOUND, not addon-bound —
16425
+ * the host validates `attachCamera` against ITS copy of this schema, so a
16426
+ * runner asked for 600 would be rejected by the hub until a `@camstack/server`
16427
+ * carrying the wider bound is installed everywhere. 300 is the widest value
16428
+ * that ships with an addon deploy.
16429
+ */
16236
16430
  var occupancyRecheckSecField = {
16237
16431
  min: 0,
16238
16432
  max: 300,
@@ -16434,15 +16628,21 @@ var RunnerCameraConfigSchema = zod.z.object({
16434
16628
  */
16435
16629
  onboardMotionDrivesAnalyzer: zod.z.boolean().default(true),
16436
16630
  /**
16437
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
16438
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
16439
- * this is off by default because the recheck re-subscribes a detection session
16440
- * every N seconds while `watching`, a major source of pull-decoder re-dial
16441
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
16631
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
16632
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
16442
16633
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
16443
16634
  * (and only render) when this is enabled.
16444
- */
16445
- occupancyRecheckEnabled: zod.z.boolean().default(false),
16635
+ *
16636
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
16637
+ * recheck re-subscribes a detection session every N seconds while `watching`
16638
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
16639
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
16640
+ * object is counted only while the stationary registry holds it, and the
16641
+ * registry rebuilds from motion, so after a restart a parked car was invisible
16642
+ * to every occupancy rule until something moved in front of it. The churn is
16643
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
16644
+ */
16645
+ occupancyRecheckEnabled: zod.z.boolean().default(true),
16446
16646
  occupancyRecheckSec: zod.z.number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
16447
16647
  occupancyRecheckFrames: zod.z.number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
16448
16648
  /**
@@ -16575,8 +16775,8 @@ var RunnerCameraDeviceUIFields = [
16575
16775
  type: "boolean",
16576
16776
  style: "checkbox",
16577
16777
  label: "Occupancy re-check",
16578
- description: "Periodically re-sample a few frames during the watching phase to confirm the scene is truly empty (catches stationary objects motion-gating would miss). Off by default it adds decoder re-dial churn.",
16579
- default: false
16778
+ description: "Periodically re-sample a few frames during the watching phase, so objects that are simply parked are still counted (motion gating alone loses them after a restart). On by default at a wide interval; turn it off on cameras where the extra decoder re-dial is expensive.",
16779
+ default: true
16580
16780
  },
16581
16781
  {
16582
16782
  key: "occupancyRecheckSec",
@@ -20268,7 +20468,17 @@ var airQualitySensorCapability = {
20268
20468
  schema: AirQualitySensorStatusSchema,
20269
20469
  kind: "push"
20270
20470
  },
20271
- runtimeState: AirQualitySensorStatusSchema
20471
+ runtimeState: AirQualitySensorStatusSchema,
20472
+ /**
20473
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
20474
+ *
20475
+ * See `RuntimeStateDurability`. Enforced by
20476
+ * `scripts/check-runtime-state-durability.ts`.
20477
+ */
20478
+ durability: "restored",
20479
+ /** Clock fields: written, but excluded from the compare that decides
20480
+ * whether persisting is worth a SQLite commit. */
20481
+ volatileStateFields: ["lastFetchedAt"]
20272
20482
  };
20273
20483
  //#endregion
20274
20484
  //#region src/capabilities/ambient-light-sensor.cap.ts
@@ -20302,7 +20512,17 @@ var ambientLightSensorCapability = {
20302
20512
  schema: AmbientLightSensorStatusSchema,
20303
20513
  kind: "push"
20304
20514
  },
20305
- runtimeState: AmbientLightSensorStatusSchema
20515
+ runtimeState: AmbientLightSensorStatusSchema,
20516
+ /**
20517
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
20518
+ *
20519
+ * See `RuntimeStateDurability`. Enforced by
20520
+ * `scripts/check-runtime-state-durability.ts`.
20521
+ */
20522
+ durability: "restored",
20523
+ /** Clock fields: written, but excluded from the compare that decides
20524
+ * whether persisting is worth a SQLite commit. */
20525
+ volatileStateFields: ["lastFetchedAt"]
20306
20526
  };
20307
20527
  //#endregion
20308
20528
  //#region src/capabilities/audio-metrics.cap.ts
@@ -20431,7 +20651,14 @@ var audioMetricsCapability = {
20431
20651
  }), AudioMetricsHistorySchema)
20432
20652
  },
20433
20653
  /** Reactive runtime-state mirror — live `device.state.audioMetrics.value`. */
20434
- runtimeState: AudioMetricsSnapshotSchema
20654
+ runtimeState: AudioMetricsSnapshotSchema,
20655
+ /**
20656
+ * Runtime-state durability: **session** — 1 Hz per camera at the mirror and ~63 % of the fleet's whole runtime-state write rate. `avgDbfs` is a rolling 60 s mean that genuinely moves every second, so no equality fix reclaims it — and a two-hour-old dB reading rendered as current is worse than no reading.
20657
+ *
20658
+ * See `RuntimeStateDurability`. Enforced by
20659
+ * `scripts/check-runtime-state-durability.ts`.
20660
+ */
20661
+ durability: "session"
20435
20662
  };
20436
20663
  //#endregion
20437
20664
  //#region src/capabilities/automation-control.cap.ts
@@ -20495,7 +20722,14 @@ var automationControlCapability = {
20495
20722
  * reads `enabled` (toggle) + `isRunning` (spinner) + `lastError`
20496
20723
  * (badge) directly.
20497
20724
  */
20498
- runtimeState: AutomationControlStatusSchema
20725
+ runtimeState: AutomationControlStatusSchema,
20726
+ /**
20727
+ * Runtime-state durability: **session** — the authority for an automation being enabled is the automation store; a restored `isRunning` would be a lie. D62: one authority per switch.
20728
+ *
20729
+ * See `RuntimeStateDurability`. Enforced by
20730
+ * `scripts/check-runtime-state-durability.ts`.
20731
+ */
20732
+ durability: "session"
20499
20733
  };
20500
20734
  //#endregion
20501
20735
  //#region src/capabilities/battery.cap.ts
@@ -20603,7 +20837,17 @@ onStatusChanged: { data: zod.z.object({
20603
20837
  * via `device.runtimeState.getCapState('battery')` regardless of
20604
20838
  * the underlying driver.
20605
20839
  */
20606
- runtimeState: BatteryStatusSchema
20840
+ runtimeState: BatteryStatusSchema,
20841
+ /**
20842
+ * Runtime-state durability: **restored** — a sleeping battery camera may not report for hours; the restored percentage is the only thing the UI and the sleep gate have. Zero churn once `lastUpdated` is excluded — 526 writes, 0 value changes, in 25 minutes.
20843
+ *
20844
+ * See `RuntimeStateDurability`. Enforced by
20845
+ * `scripts/check-runtime-state-durability.ts`.
20846
+ */
20847
+ durability: "restored",
20848
+ /** Clock fields: written, but excluded from the compare that decides
20849
+ * whether persisting is worth a SQLite commit. */
20850
+ volatileStateFields: ["lastUpdated"]
20607
20851
  };
20608
20852
  //#endregion
20609
20853
  //#region src/capabilities/binary.cap.ts
@@ -20634,7 +20878,17 @@ var binaryCapability = {
20634
20878
  schema: BinaryStatusSchema,
20635
20879
  kind: "push"
20636
20880
  },
20637
- runtimeState: BinaryStatusSchema
20881
+ runtimeState: BinaryStatusSchema,
20882
+ /**
20883
+ * Runtime-state durability: **restored** — transition-driven sensor state; the restored value gives the boot comparison.
20884
+ *
20885
+ * See `RuntimeStateDurability`. Enforced by
20886
+ * `scripts/check-runtime-state-durability.ts`.
20887
+ */
20888
+ durability: "restored",
20889
+ /** Clock fields: written, but excluded from the compare that decides
20890
+ * whether persisting is worth a SQLite commit. */
20891
+ volatileStateFields: ["lastChangedAt"]
20638
20892
  };
20639
20893
  //#endregion
20640
20894
  //#region src/capabilities/brightness.cap.ts
@@ -20689,7 +20943,14 @@ onBrightnessChanged: { data: zod.z.object({
20689
20943
  * by the kernel. Read via `device.state.brightness.value` so UI
20690
20944
  * sliders surface the current level without polling the provider.
20691
20945
  */
20692
- runtimeState: BrightnessStatusSchema
20946
+ runtimeState: BrightnessStatusSchema,
20947
+ /**
20948
+ * Runtime-state durability: **session** — live lamp state, re-published by the provider on connect.
20949
+ *
20950
+ * See `RuntimeStateDurability`. Enforced by
20951
+ * `scripts/check-runtime-state-durability.ts`.
20952
+ */
20953
+ durability: "session"
20693
20954
  };
20694
20955
  //#endregion
20695
20956
  //#region src/capabilities/button.cap.ts
@@ -20824,7 +21085,17 @@ var carbonMonoxideCapability = {
20824
21085
  schema: CarbonMonoxideStatusSchema,
20825
21086
  kind: "push"
20826
21087
  },
20827
- runtimeState: CarbonMonoxideStatusSchema
21088
+ runtimeState: CarbonMonoxideStatusSchema,
21089
+ /**
21090
+ * Runtime-state durability: **restored** — as `smoke`.
21091
+ *
21092
+ * See `RuntimeStateDurability`. Enforced by
21093
+ * `scripts/check-runtime-state-durability.ts`.
21094
+ */
21095
+ durability: "restored",
21096
+ /** Clock fields: written, but excluded from the compare that decides
21097
+ * whether persisting is worth a SQLite commit. */
21098
+ volatileStateFields: ["lastChangedAt"]
20828
21099
  };
20829
21100
  //#endregion
20830
21101
  //#region src/capabilities/climate-control.cap.ts
@@ -20986,7 +21257,14 @@ var climateControlCapability = {
20986
21257
  * the full slice via `device.state.climate-control.value` and refresh
20987
21258
  * on every push without re-querying the provider.
20988
21259
  */
20989
- runtimeState: ClimateControlStatusSchema
21260
+ runtimeState: ClimateControlStatusSchema,
21261
+ /**
21262
+ * Runtime-state durability: **session** — as `brightness`; `currentTemp` moves continuously and is re-published on connect.
21263
+ *
21264
+ * See `RuntimeStateDurability`. Enforced by
21265
+ * `scripts/check-runtime-state-durability.ts`.
21266
+ */
21267
+ durability: "session"
20990
21268
  };
20991
21269
  //#endregion
20992
21270
  //#region src/capabilities/color.cap.ts
@@ -21098,7 +21376,14 @@ onColorChanged: { data: zod.z.object({
21098
21376
  * kernel. Read via `device.state.color.value` so UI pickers surface
21099
21377
  * the current chromaticity without polling the provider.
21100
21378
  */
21101
- runtimeState: ColorStatusSchema
21379
+ runtimeState: ColorStatusSchema,
21380
+ /**
21381
+ * Runtime-state durability: **session** — as `brightness`.
21382
+ *
21383
+ * See `RuntimeStateDurability`. Enforced by
21384
+ * `scripts/check-runtime-state-durability.ts`.
21385
+ */
21386
+ durability: "session"
21102
21387
  };
21103
21388
  //#endregion
21104
21389
  //#region src/capabilities/connection-test.cap.ts
@@ -21222,7 +21507,17 @@ var connectivityCapability = {
21222
21507
  schema: ConnectivityStatusSchema,
21223
21508
  kind: "push"
21224
21509
  },
21225
- runtimeState: ConnectivityStatusSchema
21510
+ runtimeState: ConnectivityStatusSchema,
21511
+ /**
21512
+ * Runtime-state durability: **restored** — same shape and same argument as `device-status`, for links rather than devices.
21513
+ *
21514
+ * See `RuntimeStateDurability`. Enforced by
21515
+ * `scripts/check-runtime-state-durability.ts`.
21516
+ */
21517
+ durability: "restored",
21518
+ /** Clock fields: written, but excluded from the compare that decides
21519
+ * whether persisting is worth a SQLite commit. */
21520
+ volatileStateFields: ["lastChangedAt"]
21226
21521
  };
21227
21522
  //#endregion
21228
21523
  //#region src/capabilities/consumables.cap.ts
@@ -21306,7 +21601,14 @@ reset: require_sleep.method(zod.z.object({
21306
21601
  }
21307
21602
  }
21308
21603
  },
21309
- runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() })
21604
+ runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() }),
21605
+ /**
21606
+ * Runtime-state durability: **session** — the authority is the appliance; the provider re-reads the whole item array on connect.
21607
+ *
21608
+ * See `RuntimeStateDurability`. Enforced by
21609
+ * `scripts/check-runtime-state-durability.ts`.
21610
+ */
21611
+ durability: "session"
21310
21612
  };
21311
21613
  //#endregion
21312
21614
  //#region src/capabilities/contact.cap.ts
@@ -21339,7 +21641,17 @@ var contactCapability = {
21339
21641
  schema: ContactStatusSchema,
21340
21642
  kind: "push"
21341
21643
  },
21342
- runtimeState: ContactStatusSchema
21644
+ runtimeState: ContactStatusSchema,
21645
+ /**
21646
+ * Runtime-state durability: **restored** — a door left open across a restart must still read open.
21647
+ *
21648
+ * See `RuntimeStateDurability`. Enforced by
21649
+ * `scripts/check-runtime-state-durability.ts`.
21650
+ */
21651
+ durability: "restored",
21652
+ /** Clock fields: written, but excluded from the compare that decides
21653
+ * whether persisting is worth a SQLite commit. */
21654
+ volatileStateFields: ["lastChangedAt"]
21343
21655
  };
21344
21656
  //#endregion
21345
21657
  //#region src/capabilities/control.cap.ts
@@ -21471,7 +21783,14 @@ var controlCapability = {
21471
21783
  * dropdown / text field / date picker) read the slice's discriminant
21472
21784
  * and value directly without polling the provider.
21473
21785
  */
21474
- runtimeState: ControlStatusSchema
21786
+ runtimeState: ControlStatusSchema,
21787
+ /**
21788
+ * Runtime-state durability: **session** — a generic control mirrors an external entity that re-publishes on connect; the options array is re-derived with it.
21789
+ *
21790
+ * See `RuntimeStateDurability`. Enforced by
21791
+ * `scripts/check-runtime-state-durability.ts`.
21792
+ */
21793
+ durability: "session"
21475
21794
  };
21476
21795
  //#endregion
21477
21796
  //#region src/capabilities/cover.cap.ts
@@ -21556,7 +21875,17 @@ var coverCapability = {
21556
21875
  * Runtime-state slice — mirrored by the kernel. UI controls watch
21557
21876
  * the slice for live position changes during a move.
21558
21877
  */
21559
- runtimeState: CoverStatusSchema
21878
+ runtimeState: CoverStatusSchema,
21879
+ /**
21880
+ * Runtime-state durability: **restored** — position survives a restart on the device; the mirror should agree at boot rather than read blank.
21881
+ *
21882
+ * See `RuntimeStateDurability`. Enforced by
21883
+ * `scripts/check-runtime-state-durability.ts`.
21884
+ */
21885
+ durability: "restored",
21886
+ /** Clock fields: written, but excluded from the compare that decides
21887
+ * whether persisting is worth a SQLite commit. */
21888
+ volatileStateFields: ["lastChangedAt"]
21560
21889
  };
21561
21890
  //#endregion
21562
21891
  //#region src/capabilities/day-night.cap.ts
@@ -21652,7 +21981,17 @@ var dayNightCapability = {
21652
21981
  schema: DayNightStatusSchema,
21653
21982
  kind: "poll"
21654
21983
  },
21655
- runtimeState: DayNightStatusSchema
21984
+ runtimeState: DayNightStatusSchema,
21985
+ /**
21986
+ * Runtime-state durability: **restored** — operator-set IR-cut behaviour; mutation-driven.
21987
+ *
21988
+ * See `RuntimeStateDurability`. Enforced by
21989
+ * `scripts/check-runtime-state-durability.ts`.
21990
+ */
21991
+ durability: "restored",
21992
+ /** Clock fields: written, but excluded from the compare that decides
21993
+ * whether persisting is worth a SQLite commit. */
21994
+ volatileStateFields: ["lastFetchedAt"]
21656
21995
  };
21657
21996
  //#endregion
21658
21997
  //#region src/capabilities/device-status.cap.ts
@@ -21701,7 +22040,17 @@ onStatusChanged: { data: zod.z.object({
21701
22040
  schema: DeviceStatusSchema,
21702
22041
  kind: "push"
21703
22042
  },
21704
- runtimeState: DeviceStatusSchema
22043
+ runtimeState: DeviceStatusSchema,
22044
+ /**
22045
+ * Runtime-state durability: **restored** — the previous observation is what makes the first reading after a restart a COMPARISON instead of a phantom transition (D130). 32 real flips across 16 devices in 25 min — the busiest slice in the cold half.
22046
+ *
22047
+ * See `RuntimeStateDurability`. Enforced by
22048
+ * `scripts/check-runtime-state-durability.ts`.
22049
+ */
22050
+ durability: "restored",
22051
+ /** Clock fields: written, but excluded from the compare that decides
22052
+ * whether persisting is worth a SQLite commit. */
22053
+ volatileStateFields: ["lastChangedAt"]
21705
22054
  };
21706
22055
  //#endregion
21707
22056
  //#region src/capabilities/doorbell.cap.ts
@@ -21765,7 +22114,14 @@ onPressed: { data: DoorbellPressEventSchema } },
21765
22114
  * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
21766
22115
  * without subscribing.
21767
22116
  */
21768
- runtimeState: DoorbellStatusSchema
22117
+ runtimeState: DoorbellStatusSchema,
22118
+ /**
22119
+ * Runtime-state durability: **restored** — a monotonic accumulator: the slice IS the record. `lastPressedAt` is content here, not a clock, so it is deliberately NOT volatile.
22120
+ *
22121
+ * See `RuntimeStateDurability`. Enforced by
22122
+ * `scripts/check-runtime-state-durability.ts`.
22123
+ */
22124
+ durability: "restored"
21769
22125
  };
21770
22126
  //#endregion
21771
22127
  //#region src/capabilities/enum-sensor.cap.ts
@@ -21807,7 +22163,17 @@ var enumSensorCapability = {
21807
22163
  schema: EnumSensorStatusSchema,
21808
22164
  kind: "push"
21809
22165
  },
21810
- runtimeState: EnumSensorStatusSchema
22166
+ runtimeState: EnumSensorStatusSchema,
22167
+ /**
22168
+ * Runtime-state durability: **restored** — as `numeric-sensor`; 80 devices.
22169
+ *
22170
+ * See `RuntimeStateDurability`. Enforced by
22171
+ * `scripts/check-runtime-state-durability.ts`.
22172
+ */
22173
+ durability: "restored",
22174
+ /** Clock fields: written, but excluded from the compare that decides
22175
+ * whether persisting is worth a SQLite commit. */
22176
+ volatileStateFields: ["lastFetchedAt"]
21811
22177
  };
21812
22178
  //#endregion
21813
22179
  //#region src/capabilities/event-emitter.cap.ts
@@ -21845,7 +22211,14 @@ var eventEmitterCapability = {
21845
22211
  schema: EventEmitterStatusSchema,
21846
22212
  kind: "push"
21847
22213
  },
21848
- runtimeState: EventEmitterStatusSchema
22214
+ runtimeState: EventEmitterStatusSchema,
22215
+ /**
22216
+ * Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
22217
+ *
22218
+ * See `RuntimeStateDurability`. Enforced by
22219
+ * `scripts/check-runtime-state-durability.ts`.
22220
+ */
22221
+ durability: "session"
21849
22222
  };
21850
22223
  //#endregion
21851
22224
  //#region src/capabilities/events.cap.ts
@@ -22168,7 +22541,14 @@ var fanControlCapability = {
22168
22541
  * Runtime-state slice — mirrored by the kernel. UI fan speed
22169
22542
  * sliders read `percentage` for live updates.
22170
22543
  */
22171
- runtimeState: FanControlStatusSchema
22544
+ runtimeState: FanControlStatusSchema,
22545
+ /**
22546
+ * Runtime-state durability: **session** — as `brightness`.
22547
+ *
22548
+ * See `RuntimeStateDurability`. Enforced by
22549
+ * `scripts/check-runtime-state-durability.ts`.
22550
+ */
22551
+ durability: "session"
22172
22552
  };
22173
22553
  //#endregion
22174
22554
  //#region src/capabilities/feature-probe.cap.ts
@@ -22246,7 +22626,14 @@ onProbeChanged: { data: zod.z.object({
22246
22626
  schema: FeatureProbeStatusSchema,
22247
22627
  kind: "push"
22248
22628
  },
22249
- runtimeState: FeatureProbeStatusSchema
22629
+ runtimeState: FeatureProbeStatusSchema,
22630
+ /**
22631
+ * Runtime-state durability: **session** — per-session by definition — `lastProbedAt` means "this worker completed a probe THIS session", which is why the mirror seed already blanks it. Persisting it only creates something to blank.
22632
+ *
22633
+ * See `RuntimeStateDurability`. Enforced by
22634
+ * `scripts/check-runtime-state-durability.ts`.
22635
+ */
22636
+ durability: "session"
22250
22637
  };
22251
22638
  //#endregion
22252
22639
  //#region src/capabilities/flood.cap.ts
@@ -22275,7 +22662,17 @@ var floodCapability = {
22275
22662
  schema: FloodStatusSchema,
22276
22663
  kind: "push"
22277
22664
  },
22278
- runtimeState: FloodStatusSchema
22665
+ runtimeState: FloodStatusSchema,
22666
+ /**
22667
+ * Runtime-state durability: **restored** — as `smoke`.
22668
+ *
22669
+ * See `RuntimeStateDurability`. Enforced by
22670
+ * `scripts/check-runtime-state-durability.ts`.
22671
+ */
22672
+ durability: "restored",
22673
+ /** Clock fields: written, but excluded from the compare that decides
22674
+ * whether persisting is worth a SQLite commit. */
22675
+ volatileStateFields: ["lastChangedAt"]
22279
22676
  };
22280
22677
  //#endregion
22281
22678
  //#region src/capabilities/gas.cap.ts
@@ -22300,7 +22697,17 @@ var gasCapability = {
22300
22697
  schema: GasStatusSchema,
22301
22698
  kind: "push"
22302
22699
  },
22303
- runtimeState: GasStatusSchema
22700
+ runtimeState: GasStatusSchema,
22701
+ /**
22702
+ * Runtime-state durability: **restored** — as `smoke`.
22703
+ *
22704
+ * See `RuntimeStateDurability`. Enforced by
22705
+ * `scripts/check-runtime-state-durability.ts`.
22706
+ */
22707
+ durability: "restored",
22708
+ /** Clock fields: written, but excluded from the compare that decides
22709
+ * whether persisting is worth a SQLite commit. */
22710
+ volatileStateFields: ["lastChangedAt"]
22304
22711
  };
22305
22712
  //#endregion
22306
22713
  //#region src/capabilities/humidifier.cap.ts
@@ -22378,7 +22785,14 @@ var humidifierCapability = {
22378
22785
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
22379
22786
  * slice for live humidity / mode changes.
22380
22787
  */
22381
- runtimeState: HumidifierStatusSchema
22788
+ runtimeState: HumidifierStatusSchema,
22789
+ /**
22790
+ * Runtime-state durability: **session** — as `climate-control`.
22791
+ *
22792
+ * See `RuntimeStateDurability`. Enforced by
22793
+ * `scripts/check-runtime-state-durability.ts`.
22794
+ */
22795
+ durability: "session"
22382
22796
  };
22383
22797
  //#endregion
22384
22798
  //#region src/capabilities/humidity-sensor.cap.ts
@@ -22416,7 +22830,17 @@ var humiditySensorCapability = {
22416
22830
  schema: HumiditySensorStatusSchema,
22417
22831
  kind: "push"
22418
22832
  },
22419
- runtimeState: HumiditySensorStatusSchema
22833
+ runtimeState: HumiditySensorStatusSchema,
22834
+ /**
22835
+ * Runtime-state durability: **restored** — as `numeric-sensor` (67 of 75 writes were the clock alone).
22836
+ *
22837
+ * See `RuntimeStateDurability`. Enforced by
22838
+ * `scripts/check-runtime-state-durability.ts`.
22839
+ */
22840
+ durability: "restored",
22841
+ /** Clock fields: written, but excluded from the compare that decides
22842
+ * whether persisting is worth a SQLite commit. */
22843
+ volatileStateFields: ["lastFetchedAt"]
22420
22844
  };
22421
22845
  //#endregion
22422
22846
  //#region src/capabilities/image.cap.ts
@@ -22456,7 +22880,14 @@ var imageCapability = {
22456
22880
  * Runtime-state slice — mirrored by the kernel. The UI reads `url`
22457
22881
  * directly and renders the still image.
22458
22882
  */
22459
- runtimeState: ImageStatusSchema
22883
+ runtimeState: ImageStatusSchema,
22884
+ /**
22885
+ * Runtime-state durability: **session** — a snapshot URL is a session-scoped handle; a restored one points at nothing.
22886
+ *
22887
+ * See `RuntimeStateDurability`. Enforced by
22888
+ * `scripts/check-runtime-state-durability.ts`.
22889
+ */
22890
+ durability: "session"
22460
22891
  };
22461
22892
  //#endregion
22462
22893
  //#region src/capabilities/image-settings.cap.ts
@@ -22607,7 +23038,17 @@ var imageSettingsCapability = {
22607
23038
  schema: ImageSettingsStatusSchema,
22608
23039
  kind: "poll"
22609
23040
  },
22610
- runtimeState: ImageSettingsStatusSchema
23041
+ runtimeState: ImageSettingsStatusSchema,
23042
+ /**
23043
+ * Runtime-state durability: **restored** — operator-set camera imaging; mutation-driven.
23044
+ *
23045
+ * See `RuntimeStateDurability`. Enforced by
23046
+ * `scripts/check-runtime-state-durability.ts`.
23047
+ */
23048
+ durability: "restored",
23049
+ /** Clock fields: written, but excluded from the compare that decides
23050
+ * whether persisting is worth a SQLite commit. */
23051
+ volatileStateFields: ["lastFetchedAt"]
22611
23052
  };
22612
23053
  //#endregion
22613
23054
  //#region src/capabilities/integrations.cap.ts
@@ -23022,7 +23463,14 @@ var lawnMowerControlCapability = {
23022
23463
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
23023
23464
  * slice for live activity + battery changes.
23024
23465
  */
23025
- runtimeState: LawnMowerControlStatusSchema
23466
+ runtimeState: LawnMowerControlStatusSchema,
23467
+ /**
23468
+ * Runtime-state durability: **session** — as `vacuum-control`.
23469
+ *
23470
+ * See `RuntimeStateDurability`. Enforced by
23471
+ * `scripts/check-runtime-state-durability.ts`.
23472
+ */
23473
+ durability: "session"
23026
23474
  };
23027
23475
  //#endregion
23028
23476
  //#region src/capabilities/local-network.cap.ts
@@ -23329,7 +23777,17 @@ var lockControlCapability = {
23329
23777
  * read `state` and disable themselves during `locking`/`unlocking`
23330
23778
  * transitions.
23331
23779
  */
23332
- runtimeState: LockControlStatusSchema
23780
+ runtimeState: LockControlStatusSchema,
23781
+ /**
23782
+ * Runtime-state durability: **restored** — a lock left locked must still read locked.
23783
+ *
23784
+ * See `RuntimeStateDurability`. Enforced by
23785
+ * `scripts/check-runtime-state-durability.ts`.
23786
+ */
23787
+ durability: "restored",
23788
+ /** Clock fields: written, but excluded from the compare that decides
23789
+ * whether persisting is worth a SQLite commit. */
23790
+ volatileStateFields: ["lastChangedAt"]
23333
23791
  };
23334
23792
  //#endregion
23335
23793
  //#region src/capabilities/media-player.cap.ts
@@ -23493,7 +23951,14 @@ var mediaPlayerCapability = {
23493
23951
  * full slice for live now-playing, volume, and progress updates
23494
23952
  * without polling.
23495
23953
  */
23496
- runtimeState: MediaPlayerStatusSchema
23954
+ runtimeState: MediaPlayerStatusSchema,
23955
+ /**
23956
+ * Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
23957
+ *
23958
+ * See `RuntimeStateDurability`. Enforced by
23959
+ * `scripts/check-runtime-state-durability.ts`.
23960
+ */
23961
+ durability: "session"
23497
23962
  };
23498
23963
  //#endregion
23499
23964
  //#region src/capabilities/mesh-network.cap.ts
@@ -23822,7 +24287,14 @@ onMotionChanged: { data: MotionOnMotionChangedDataSchema } },
23822
24287
  * `device.state.motion.value`. Reads never invoke the provider, so
23823
24288
  * UIs and other addons can poll the cached state safely.
23824
24289
  */
23825
- runtimeState: MotionStatusSchema
24290
+ runtimeState: MotionStatusSchema,
24291
+ /**
24292
+ * Runtime-state durability: **session** — self-clearing by construction (`autoClearAfterMs`); a restored `detected: true` is a frozen event, and the next frame re-publishes the real one.
24293
+ *
24294
+ * See `RuntimeStateDurability`. Enforced by
24295
+ * `scripts/check-runtime-state-durability.ts`.
24296
+ */
24297
+ durability: "session"
23826
24298
  };
23827
24299
  //#endregion
23828
24300
  //#region src/capabilities/motion-trigger.cap.ts
@@ -23889,7 +24361,14 @@ var motionTriggerCapability = {
23889
24361
  schema: MotionTriggerStatusSchema,
23890
24362
  kind: "command-driven"
23891
24363
  },
23892
- runtimeState: MotionTriggerRuntimeStateSchema
24364
+ runtimeState: MotionTriggerRuntimeStateSchema,
24365
+ /**
24366
+ * Runtime-state durability: **session** — the authority for motion-trigger enablement is the provider's own config; the slice is a mirror of it, re-published on connect.
24367
+ *
24368
+ * See `RuntimeStateDurability`. Enforced by
24369
+ * `scripts/check-runtime-state-durability.ts`.
24370
+ */
24371
+ durability: "session"
23893
24372
  };
23894
24373
  //#endregion
23895
24374
  //#region src/capabilities/motion-zones.cap.ts
@@ -23958,7 +24437,17 @@ var motionZonesCapability = {
23958
24437
  schema: MotionZoneStatusSchema,
23959
24438
  kind: "poll"
23960
24439
  },
23961
- runtimeState: MotionZoneStatusSchema
24440
+ runtimeState: MotionZoneStatusSchema,
24441
+ /**
24442
+ * Runtime-state durability: **restored** — the 14 KB polygon list is the single largest slice on the fleet and has not changed since the operator drew it. Highest value per byte in the table.
24443
+ *
24444
+ * See `RuntimeStateDurability`. Enforced by
24445
+ * `scripts/check-runtime-state-durability.ts`.
24446
+ */
24447
+ durability: "restored",
24448
+ /** Clock fields: written, but excluded from the compare that decides
24449
+ * whether persisting is worth a SQLite commit. */
24450
+ volatileStateFields: ["lastFetchedAt"]
23962
24451
  };
23963
24452
  //#endregion
23964
24453
  //#region src/capabilities/native-object-detection.cap.ts
@@ -24034,7 +24523,17 @@ var nativeObjectDetectionCapability = {
24034
24523
  schema: NativeObjectDetectionStatusSchema,
24035
24524
  kind: "push"
24036
24525
  },
24037
- runtimeState: NativeObjectDetectionRuntimeStateSchema
24526
+ runtimeState: NativeObjectDetectionRuntimeStateSchema,
24527
+ /**
24528
+ * Runtime-state durability: **restored** — `enabled` is an operator toggle on a camera whose refresh is a no-op and whose staleMs is Infinity. There is no hardware value to re-read — losing it loses the setting.
24529
+ *
24530
+ * See `RuntimeStateDurability`. Enforced by
24531
+ * `scripts/check-runtime-state-durability.ts`.
24532
+ */
24533
+ durability: "restored",
24534
+ /** Clock fields: written, but excluded from the compare that decides
24535
+ * whether persisting is worth a SQLite commit. */
24536
+ volatileStateFields: ["lastFetchedAt"]
24038
24537
  };
24039
24538
  //#endregion
24040
24539
  //#region src/capabilities/network-quality.cap.ts
@@ -24427,7 +24926,14 @@ onSent: { data: zod.z.object({
24427
24926
  * form reads `supports` to gate optional fields; history pane reads
24428
24927
  * `lastSentAt` / `lastError` / `queueDepth`.
24429
24928
  */
24430
- runtimeState: NotifierStatusSchema
24929
+ runtimeState: NotifierStatusSchema,
24930
+ /**
24931
+ * Runtime-state durability: **session** — live queue depth and last-send state; a restored queue depth describes a queue that no longer exists.
24932
+ *
24933
+ * See `RuntimeStateDurability`. Enforced by
24934
+ * `scripts/check-runtime-state-durability.ts`.
24935
+ */
24936
+ durability: "session"
24431
24937
  };
24432
24938
  //#endregion
24433
24939
  //#region src/capabilities/numeric-sensor.cap.ts
@@ -24472,7 +24978,17 @@ var numericSensorCapability = {
24472
24978
  schema: NumericSensorStatusSchema,
24473
24979
  kind: "push"
24474
24980
  },
24475
- runtimeState: NumericSensorStatusSchema
24981
+ runtimeState: NumericSensorStatusSchema,
24982
+ /**
24983
+ * Runtime-state durability: **restored** — polled value, low churn once the clock is excluded (251 of 669 writes were the clock alone); restoring it removes the cold window before the first poll.
24984
+ *
24985
+ * See `RuntimeStateDurability`. Enforced by
24986
+ * `scripts/check-runtime-state-durability.ts`.
24987
+ */
24988
+ durability: "restored",
24989
+ /** Clock fields: written, but excluded from the compare that decides
24990
+ * whether persisting is worth a SQLite commit. */
24991
+ volatileStateFields: ["lastFetchedAt"]
24476
24992
  };
24477
24993
  //#endregion
24478
24994
  //#region src/capabilities/osd.cap.ts
@@ -24989,7 +25505,14 @@ var petFeederCapability = {
24989
25505
  * the full slice via `device.state.petFeeder.value` and refresh on
24990
25506
  * every poll without re-querying the provider.
24991
25507
  */
24992
- runtimeState: PetFeederStatusSchema
25508
+ runtimeState: PetFeederStatusSchema,
25509
+ /**
25510
+ * Runtime-state durability: **session** — live appliance state re-published on connect.
25511
+ *
25512
+ * See `RuntimeStateDurability`. Enforced by
25513
+ * `scripts/check-runtime-state-durability.ts`.
25514
+ */
25515
+ durability: "session"
24993
25516
  };
24994
25517
  //#endregion
24995
25518
  //#region src/capabilities/plate-gallery.cap.ts
@@ -25352,7 +25875,17 @@ var powerMeterCapability = {
25352
25875
  schema: PowerMeterStatusSchema,
25353
25876
  kind: "push"
25354
25877
  },
25355
- runtimeState: PowerMeterStatusSchema
25878
+ runtimeState: PowerMeterStatusSchema,
25879
+ /**
25880
+ * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
25881
+ *
25882
+ * See `RuntimeStateDurability`. Enforced by
25883
+ * `scripts/check-runtime-state-durability.ts`.
25884
+ */
25885
+ durability: "restored",
25886
+ /** Clock fields: written, but excluded from the compare that decides
25887
+ * whether persisting is worth a SQLite commit. */
25888
+ volatileStateFields: ["lastFetchedAt"]
25356
25889
  };
25357
25890
  //#endregion
25358
25891
  //#region src/capabilities/presence.cap.ts
@@ -25411,7 +25944,17 @@ var presenceCapability = {
25411
25944
  * the map pin is rendered (use `DeviceFeature.PresenceGps` for the
25412
25945
  * pre-fetch fast-path check).
25413
25946
  */
25414
- runtimeState: PresenceStatusSchema
25947
+ runtimeState: PresenceStatusSchema,
25948
+ /**
25949
+ * Runtime-state durability: **restored** — occupancy-relevant: the restored state is what an occupancy rule compares the first post-restart observation against.
25950
+ *
25951
+ * See `RuntimeStateDurability`. Enforced by
25952
+ * `scripts/check-runtime-state-durability.ts`.
25953
+ */
25954
+ durability: "restored",
25955
+ /** Clock fields: written, but excluded from the compare that decides
25956
+ * whether persisting is worth a SQLite commit. */
25957
+ volatileStateFields: ["lastChangedAt"]
25415
25958
  };
25416
25959
  //#endregion
25417
25960
  //#region src/capabilities/pressure-sensor.cap.ts
@@ -25449,7 +25992,17 @@ var pressureSensorCapability = {
25449
25992
  schema: PressureSensorStatusSchema,
25450
25993
  kind: "push"
25451
25994
  },
25452
- runtimeState: PressureSensorStatusSchema
25995
+ runtimeState: PressureSensorStatusSchema,
25996
+ /**
25997
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
25998
+ *
25999
+ * See `RuntimeStateDurability`. Enforced by
26000
+ * `scripts/check-runtime-state-durability.ts`.
26001
+ */
26002
+ durability: "restored",
26003
+ /** Clock fields: written, but excluded from the compare that decides
26004
+ * whether persisting is worth a SQLite commit. */
26005
+ volatileStateFields: ["lastFetchedAt"]
25453
26006
  };
25454
26007
  //#endregion
25455
26008
  //#region src/capabilities/privacy-mask.cap.ts
@@ -25581,7 +26134,17 @@ var privacyMaskCapability = {
25581
26134
  schema: PrivacyMaskStatusSchema,
25582
26135
  kind: "poll"
25583
26136
  },
25584
- runtimeState: PrivacyMaskStatusSchema
26137
+ runtimeState: PrivacyMaskStatusSchema,
26138
+ /**
26139
+ * Runtime-state durability: **restored** — operator-drawn regions, zero real churn — 22 writes in 25 minutes, every one of them the clock.
26140
+ *
26141
+ * See `RuntimeStateDurability`. Enforced by
26142
+ * `scripts/check-runtime-state-durability.ts`.
26143
+ */
26144
+ durability: "restored",
26145
+ /** Clock fields: written, but excluded from the compare that decides
26146
+ * whether persisting is worth a SQLite commit. */
26147
+ volatileStateFields: ["lastFetchedAt"]
25585
26148
  };
25586
26149
  /**
25587
26150
  * Collapse a camera's PER-PROFILE audio flags into the one answer
@@ -25848,7 +26411,14 @@ var ptzAutotrackCapability = {
25848
26411
  * fetch / cache / fallback logic out of the four cap methods —
25849
26412
  * they become trampolines over `runtimeState`.
25850
26413
  */
25851
- runtimeState: PtzAutotrackRuntimeStateSchema
26414
+ runtimeState: PtzAutotrackRuntimeStateSchema,
26415
+ /**
26416
+ * Runtime-state durability: **session** — mirrors the camera's own autotrack config, re-read on connect.
26417
+ *
26418
+ * See `RuntimeStateDurability`. Enforced by
26419
+ * `scripts/check-runtime-state-durability.ts`.
26420
+ */
26421
+ durability: "session"
25852
26422
  };
25853
26423
  //#endregion
25854
26424
  //#region src/capabilities/reboot.cap.ts
@@ -26693,7 +27263,14 @@ var sceneMonitorCapability = {
26693
27263
  schema: SceneMonitorStatusSchema,
26694
27264
  kind: "push"
26695
27265
  },
26696
- runtimeState: SceneMonitorStatusSchema
27266
+ runtimeState: SceneMonitorStatusSchema,
27267
+ /**
27268
+ * Runtime-state durability: **session** — re-derived from the current scene on the next evaluation.
27269
+ *
27270
+ * See `RuntimeStateDurability`. Enforced by
27271
+ * `scripts/check-runtime-state-durability.ts`.
27272
+ */
27273
+ durability: "session"
26697
27274
  };
26698
27275
  //#endregion
26699
27276
  //#region src/capabilities/schemas/zone-rule.ts
@@ -26845,7 +27422,14 @@ var scriptRunnerCapability = {
26845
27422
  * `isRunning` to render a spinner during execution and surfaces
26846
27423
  * `lastError` / `lastRunSuccess` in the recent-runs panel.
26847
27424
  */
26848
- runtimeState: ScriptRunnerStatusSchema
27425
+ runtimeState: ScriptRunnerStatusSchema,
27426
+ /**
27427
+ * Runtime-state durability: **session** — a restored `isRunning: true` describes a process that died with the previous hub.
27428
+ *
27429
+ * See `RuntimeStateDurability`. Enforced by
27430
+ * `scripts/check-runtime-state-durability.ts`.
27431
+ */
27432
+ durability: "session"
26849
27433
  };
26850
27434
  //#endregion
26851
27435
  //#region src/capabilities/smoke.cap.ts
@@ -26874,7 +27458,17 @@ var smokeCapability = {
26874
27458
  schema: SmokeStatusSchema,
26875
27459
  kind: "push"
26876
27460
  },
26877
- runtimeState: SmokeStatusSchema
27461
+ runtimeState: SmokeStatusSchema,
27462
+ /**
27463
+ * Runtime-state durability: **restored** — a safety sensor must not read "clear" merely because the hub restarted.
27464
+ *
27465
+ * See `RuntimeStateDurability`. Enforced by
27466
+ * `scripts/check-runtime-state-durability.ts`.
27467
+ */
27468
+ durability: "restored",
27469
+ /** Clock fields: written, but excluded from the compare that decides
27470
+ * whether persisting is worth a SQLite commit. */
27471
+ volatileStateFields: ["lastChangedAt"]
26878
27472
  };
26879
27473
  //#endregion
26880
27474
  //#region src/capabilities/stream-catalog.cap.ts
@@ -27064,7 +27658,17 @@ var streamParamsCapability = {
27064
27658
  schema: StreamParamsStatusSchema,
27065
27659
  kind: "poll"
27066
27660
  },
27067
- runtimeState: StreamParamsStatusSchema
27661
+ runtimeState: StreamParamsStatusSchema,
27662
+ /**
27663
+ * Runtime-state durability: **restored** — operator-set encoder profile; mutation-driven.
27664
+ *
27665
+ * See `RuntimeStateDurability`. Enforced by
27666
+ * `scripts/check-runtime-state-durability.ts`.
27667
+ */
27668
+ durability: "restored",
27669
+ /** Clock fields: written, but excluded from the compare that decides
27670
+ * whether persisting is worth a SQLite commit. */
27671
+ volatileStateFields: ["lastFetchedAt"]
27068
27672
  };
27069
27673
  //#endregion
27070
27674
  //#region src/capabilities/stream-params-config-schema.ts
@@ -27371,6 +27975,16 @@ var switchCapability = {
27371
27975
  * not need to re-query the provider after a setState mutation.
27372
27976
  */
27373
27977
  runtimeState: SwitchStatusSchema,
27978
+ /**
27979
+ * Runtime-state durability: **restored** — device state an operator reads as authoritative; 55 devices, transition-driven.
27980
+ *
27981
+ * See `RuntimeStateDurability`. Enforced by
27982
+ * `scripts/check-runtime-state-durability.ts`.
27983
+ */
27984
+ durability: "restored",
27985
+ /** Clock fields: written, but excluded from the compare that decides
27986
+ * whether persisting is worth a SQLite commit. */
27987
+ volatileStateFields: ["lastChangedAt"],
27374
27988
  settings: { bindings: [{
27375
27989
  kind: "scalar",
27376
27990
  statusPath: "on",
@@ -27469,7 +28083,17 @@ var tamperCapability = {
27469
28083
  schema: TamperStatusSchema,
27470
28084
  kind: "push"
27471
28085
  },
27472
- runtimeState: TamperStatusSchema
28086
+ runtimeState: TamperStatusSchema,
28087
+ /**
28088
+ * Runtime-state durability: **restored** — as `smoke`.
28089
+ *
28090
+ * See `RuntimeStateDurability`. Enforced by
28091
+ * `scripts/check-runtime-state-durability.ts`.
28092
+ */
28093
+ durability: "restored",
28094
+ /** Clock fields: written, but excluded from the compare that decides
28095
+ * whether persisting is worth a SQLite commit. */
28096
+ volatileStateFields: ["lastChangedAt"]
27473
28097
  };
27474
28098
  //#endregion
27475
28099
  //#region src/capabilities/temperature-sensor.cap.ts
@@ -27516,7 +28140,17 @@ var temperatureSensorCapability = {
27516
28140
  schema: TemperatureSensorStatusSchema,
27517
28141
  kind: "push"
27518
28142
  },
27519
- runtimeState: TemperatureSensorStatusSchema
28143
+ runtimeState: TemperatureSensorStatusSchema,
28144
+ /**
28145
+ * Runtime-state durability: **restored** — as `numeric-sensor` (69 of 125 writes were the clock alone).
28146
+ *
28147
+ * See `RuntimeStateDurability`. Enforced by
28148
+ * `scripts/check-runtime-state-durability.ts`.
28149
+ */
28150
+ durability: "restored",
28151
+ /** Clock fields: written, but excluded from the compare that decides
28152
+ * whether persisting is worth a SQLite commit. */
28153
+ volatileStateFields: ["lastFetchedAt"]
27520
28154
  };
27521
28155
  //#endregion
27522
28156
  //#region src/capabilities/toast.cap.ts
@@ -27596,7 +28230,14 @@ var updateCapability = {
27596
28230
  schema: UpdateStatusSchema,
27597
28231
  kind: "poll"
27598
28232
  },
27599
- runtimeState: UpdateStatusSchema
28233
+ runtimeState: UpdateStatusSchema,
28234
+ /**
28235
+ * Runtime-state durability: **session** — a restored `inProgress: true` describes an update that is no longer running; versions are re-probed at boot.
28236
+ *
28237
+ * See `RuntimeStateDurability`. Enforced by
28238
+ * `scripts/check-runtime-state-durability.ts`.
28239
+ */
28240
+ durability: "session"
27600
28241
  };
27601
28242
  //#endregion
27602
28243
  //#region src/capabilities/user-management.cap.ts
@@ -27976,7 +28617,14 @@ var vacuumControlCapability = {
27976
28617
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
27977
28618
  * slice for live state + battery + fan-speed changes.
27978
28619
  */
27979
- runtimeState: VacuumControlStatusSchema
28620
+ runtimeState: VacuumControlStatusSchema,
28621
+ /**
28622
+ * Runtime-state durability: **session** — as `media-player` — a restored `state: cleaning` is a robot that is not cleaning.
28623
+ *
28624
+ * See `RuntimeStateDurability`. Enforced by
28625
+ * `scripts/check-runtime-state-durability.ts`.
28626
+ */
28627
+ durability: "session"
27980
28628
  };
27981
28629
  //#endregion
27982
28630
  //#region src/capabilities/valve.cap.ts
@@ -28048,7 +28696,14 @@ var valveCapability = {
28048
28696
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
28049
28697
  * slice for live position changes during a move.
28050
28698
  */
28051
- runtimeState: ValveStatusSchema
28699
+ runtimeState: ValveStatusSchema,
28700
+ /**
28701
+ * Runtime-state durability: **session** — as `brightness`.
28702
+ *
28703
+ * See `RuntimeStateDurability`. Enforced by
28704
+ * `scripts/check-runtime-state-durability.ts`.
28705
+ */
28706
+ durability: "session"
28052
28707
  };
28053
28708
  //#endregion
28054
28709
  //#region src/capabilities/vibration.cap.ts
@@ -28072,7 +28727,17 @@ var vibrationCapability = {
28072
28727
  schema: VibrationStatusSchema,
28073
28728
  kind: "push"
28074
28729
  },
28075
- runtimeState: VibrationStatusSchema
28730
+ runtimeState: VibrationStatusSchema,
28731
+ /**
28732
+ * Runtime-state durability: **restored** — as `smoke`.
28733
+ *
28734
+ * See `RuntimeStateDurability`. Enforced by
28735
+ * `scripts/check-runtime-state-durability.ts`.
28736
+ */
28737
+ durability: "restored",
28738
+ /** Clock fields: written, but excluded from the compare that decides
28739
+ * whether persisting is worth a SQLite commit. */
28740
+ volatileStateFields: ["lastChangedAt"]
28076
28741
  };
28077
28742
  //#endregion
28078
28743
  //#region src/capabilities/water-heater.cap.ts
@@ -28148,7 +28813,14 @@ var waterHeaterCapability = {
28148
28813
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
28149
28814
  * slice for live temperature / mode / away changes.
28150
28815
  */
28151
- runtimeState: WaterHeaterStatusSchema
28816
+ runtimeState: WaterHeaterStatusSchema,
28817
+ /**
28818
+ * Runtime-state durability: **session** — as `climate-control`.
28819
+ *
28820
+ * See `RuntimeStateDurability`. Enforced by
28821
+ * `scripts/check-runtime-state-durability.ts`.
28822
+ */
28823
+ durability: "session"
28152
28824
  };
28153
28825
  //#endregion
28154
28826
  //#region src/capabilities/weather.cap.ts
@@ -28209,7 +28881,14 @@ var weatherCapability = {
28209
28881
  * Runtime-state slice — mirrored by the kernel. The UI reads the
28210
28882
  * current conditions directly from the slice on each weather push.
28211
28883
  */
28212
- runtimeState: WeatherStatusSchema
28884
+ runtimeState: WeatherStatusSchema,
28885
+ /**
28886
+ * Runtime-state durability: **session** — a forecast is stale the moment the hub is down; the provider re-fetches on connect.
28887
+ *
28888
+ * See `RuntimeStateDurability`. Enforced by
28889
+ * `scripts/check-runtime-state-durability.ts`.
28890
+ */
28891
+ durability: "session"
28213
28892
  };
28214
28893
  //#endregion
28215
28894
  //#region src/capabilities/zone-analytics.cap.ts
@@ -28373,7 +29052,14 @@ var zoneAnalyticsCapability = {
28373
29052
  * automatically; the explicit `getCurrentSnapshot` cap method is
28374
29053
  * still useful for one-off polls without a subscription.
28375
29054
  */
28376
- runtimeState: CameraOccupancySnapshotSchema
29055
+ runtimeState: CameraOccupancySnapshotSchema,
29056
+ /**
29057
+ * Runtime-state durability: **session** — per-frame analytics; with `audio-metrics` it is ~90 % of the offered write rate. Re-derived on the next frame.
29058
+ *
29059
+ * See `RuntimeStateDurability`. Enforced by
29060
+ * `scripts/check-runtime-state-durability.ts`.
29061
+ */
29062
+ durability: "session"
28377
29063
  };
28378
29064
  //#endregion
28379
29065
  //#region src/capabilities/zone-rules.cap.ts
@@ -28453,7 +29139,14 @@ var zoneRulesCapability = {
28453
29139
  motion: zod.z.array(ZoneRuleSchema).readonly(),
28454
29140
  detection: zod.z.array(ZoneRuleSchema).readonly(),
28455
29141
  package: zod.z.array(ZoneRuleSchema).readonly()
28456
- })
29142
+ }),
29143
+ /**
29144
+ * Runtime-state durability: **restored** — operator intent, mutation-only, same argument as `zones`.
29145
+ *
29146
+ * See `RuntimeStateDurability`. Enforced by
29147
+ * `scripts/check-runtime-state-durability.ts`.
29148
+ */
29149
+ durability: "restored"
28457
29150
  };
28458
29151
  //#endregion
28459
29152
  //#region src/capabilities/index.ts
@@ -31047,11 +31740,40 @@ var SystemMirror = class {
31047
31740
  cb(deviceId, lastInfo);
31048
31741
  } catch {}
31049
31742
  }
31743
+ /**
31744
+ * ONE device's bindings, not the fleet's.
31745
+ *
31746
+ * This runs on every `capability.binding-changed` AND on every
31747
+ * `device.registered`, per mirror. It used to answer that with
31748
+ * `getAllBindings` and `.find()` the one row — measured on the live
31749
+ * hub (524 devices): 903 ms and 373 KB per call, against 21 ms and
31750
+ * 613 bytes for `getBindings({deviceId})`.
31751
+ *
31752
+ * Adopting 20 Home Assistant devices registers 166 devices (parents +
31753
+ * accessories), so an operator with the admin UI open bought 166
31754
+ * full-fleet binding sweeps: ~150 s of hub main-thread time inside a
31755
+ * 219 s adoption, with every other RPC — including the adoption's own —
31756
+ * queued behind them. Same defect, same fix, as `refreshDeviceMetadata`
31757
+ * below (`listAll` → `getDevice`).
31758
+ *
31759
+ * Removal is the destructive direction (every proxy handle a consumer
31760
+ * holds goes inert), and an EMPTY binding list is not a claim that the
31761
+ * device is gone — a device can legitimately bind nothing. So it needs
31762
+ * a second read to agree (D49); `device.unregistered` remains the
31763
+ * primary removal path.
31764
+ */
31050
31765
  async refreshBinding(deviceId) {
31051
31766
  try {
31052
- const fresh = (await this.api.deviceManager.getAllBindings.query({})).find((b) => b.deviceId === deviceId);
31053
- if (fresh) this.bindings.set(deviceId, fresh);
31054
- else this.applyDeviceRemoval(deviceId);
31767
+ const fresh = await this.api.deviceManager.getBindings.query({ deviceId });
31768
+ if (fresh.entries.length > 0) {
31769
+ this.bindings.set(deviceId, fresh);
31770
+ return;
31771
+ }
31772
+ if (await this.api.deviceManager.getDevice.query({ deviceId }) === null) {
31773
+ this.applyDeviceRemoval(deviceId);
31774
+ return;
31775
+ }
31776
+ this.bindings.set(deviceId, fresh);
31055
31777
  } catch {}
31056
31778
  }
31057
31779
  async refreshDeviceMetadata(deviceId, kind) {
@@ -38071,6 +38793,320 @@ function getCapsByProviderKind(kind) {
38071
38793
  return out;
38072
38794
  }
38073
38795
  //#endregion
38796
+ //#region src/generated/runtime-state-durability.ts
38797
+ /**
38798
+ * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
38799
+ * `runtimeState`. 62 caps: 33 `restored`, 29 `session`.
38800
+ *
38801
+ * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
38802
+ * with `durability: 'session'` has one and has decided not to keep it.
38803
+ */
38804
+ var RUNTIME_STATE_POLICY = {
38805
+ "air-quality-sensor": {
38806
+ durability: "restored",
38807
+ volatileFields: ["lastFetchedAt"]
38808
+ },
38809
+ "alarm-panel": {
38810
+ durability: "restored",
38811
+ volatileFields: ["lastChangedAt"]
38812
+ },
38813
+ "ambient-light-sensor": {
38814
+ durability: "restored",
38815
+ volatileFields: ["lastFetchedAt"]
38816
+ },
38817
+ "audio-metrics": {
38818
+ durability: "session",
38819
+ volatileFields: []
38820
+ },
38821
+ "automation-control": {
38822
+ durability: "session",
38823
+ volatileFields: []
38824
+ },
38825
+ "battery": {
38826
+ durability: "restored",
38827
+ volatileFields: ["lastUpdated"]
38828
+ },
38829
+ "binary": {
38830
+ durability: "restored",
38831
+ volatileFields: ["lastChangedAt"]
38832
+ },
38833
+ "brightness": {
38834
+ durability: "session",
38835
+ volatileFields: []
38836
+ },
38837
+ "camera-streams": {
38838
+ durability: "session",
38839
+ volatileFields: []
38840
+ },
38841
+ "carbon-monoxide": {
38842
+ durability: "restored",
38843
+ volatileFields: ["lastChangedAt"]
38844
+ },
38845
+ "climate-control": {
38846
+ durability: "session",
38847
+ volatileFields: []
38848
+ },
38849
+ "color": {
38850
+ durability: "session",
38851
+ volatileFields: []
38852
+ },
38853
+ "connectivity": {
38854
+ durability: "restored",
38855
+ volatileFields: ["lastChangedAt"]
38856
+ },
38857
+ "consumables": {
38858
+ durability: "session",
38859
+ volatileFields: []
38860
+ },
38861
+ "contact": {
38862
+ durability: "restored",
38863
+ volatileFields: ["lastChangedAt"]
38864
+ },
38865
+ "control": {
38866
+ durability: "session",
38867
+ volatileFields: []
38868
+ },
38869
+ "cover": {
38870
+ durability: "restored",
38871
+ volatileFields: ["lastChangedAt"]
38872
+ },
38873
+ "day-night": {
38874
+ durability: "restored",
38875
+ volatileFields: ["lastFetchedAt"]
38876
+ },
38877
+ "device-discovery": {
38878
+ durability: "session",
38879
+ volatileFields: []
38880
+ },
38881
+ "device-status": {
38882
+ durability: "restored",
38883
+ volatileFields: ["lastChangedAt"]
38884
+ },
38885
+ "doorbell": {
38886
+ durability: "restored",
38887
+ volatileFields: []
38888
+ },
38889
+ "enum-sensor": {
38890
+ durability: "restored",
38891
+ volatileFields: ["lastFetchedAt"]
38892
+ },
38893
+ "event-emitter": {
38894
+ durability: "session",
38895
+ volatileFields: []
38896
+ },
38897
+ "fan-control": {
38898
+ durability: "session",
38899
+ volatileFields: []
38900
+ },
38901
+ "feature-probe": {
38902
+ durability: "session",
38903
+ volatileFields: []
38904
+ },
38905
+ "flood": {
38906
+ durability: "restored",
38907
+ volatileFields: ["lastChangedAt"]
38908
+ },
38909
+ "gas": {
38910
+ durability: "restored",
38911
+ volatileFields: ["lastChangedAt"]
38912
+ },
38913
+ "humidifier": {
38914
+ durability: "session",
38915
+ volatileFields: []
38916
+ },
38917
+ "humidity-sensor": {
38918
+ durability: "restored",
38919
+ volatileFields: ["lastFetchedAt"]
38920
+ },
38921
+ "image": {
38922
+ durability: "session",
38923
+ volatileFields: []
38924
+ },
38925
+ "image-settings": {
38926
+ durability: "restored",
38927
+ volatileFields: ["lastFetchedAt"]
38928
+ },
38929
+ "lawn-mower-control": {
38930
+ durability: "session",
38931
+ volatileFields: []
38932
+ },
38933
+ "lock-control": {
38934
+ durability: "restored",
38935
+ volatileFields: ["lastChangedAt"]
38936
+ },
38937
+ "media-player": {
38938
+ durability: "session",
38939
+ volatileFields: []
38940
+ },
38941
+ "motion": {
38942
+ durability: "session",
38943
+ volatileFields: []
38944
+ },
38945
+ "motion-trigger": {
38946
+ durability: "session",
38947
+ volatileFields: []
38948
+ },
38949
+ "motion-zones": {
38950
+ durability: "restored",
38951
+ volatileFields: ["lastFetchedAt"]
38952
+ },
38953
+ "native-object-detection": {
38954
+ durability: "restored",
38955
+ volatileFields: ["lastFetchedAt"]
38956
+ },
38957
+ "notifier": {
38958
+ durability: "session",
38959
+ volatileFields: []
38960
+ },
38961
+ "numeric-sensor": {
38962
+ durability: "restored",
38963
+ volatileFields: ["lastFetchedAt"]
38964
+ },
38965
+ "pet-feeder": {
38966
+ durability: "session",
38967
+ volatileFields: []
38968
+ },
38969
+ "power-meter": {
38970
+ durability: "restored",
38971
+ volatileFields: ["lastFetchedAt"]
38972
+ },
38973
+ "presence": {
38974
+ durability: "restored",
38975
+ volatileFields: ["lastChangedAt"]
38976
+ },
38977
+ "pressure-sensor": {
38978
+ durability: "restored",
38979
+ volatileFields: ["lastFetchedAt"]
38980
+ },
38981
+ "privacy-mask": {
38982
+ durability: "restored",
38983
+ volatileFields: ["lastFetchedAt"]
38984
+ },
38985
+ "ptz-autotrack": {
38986
+ durability: "session",
38987
+ volatileFields: []
38988
+ },
38989
+ "scene-monitor": {
38990
+ durability: "session",
38991
+ volatileFields: []
38992
+ },
38993
+ "script-runner": {
38994
+ durability: "session",
38995
+ volatileFields: []
38996
+ },
38997
+ "smoke": {
38998
+ durability: "restored",
38999
+ volatileFields: ["lastChangedAt"]
39000
+ },
39001
+ "stream-params": {
39002
+ durability: "restored",
39003
+ volatileFields: ["lastFetchedAt"]
39004
+ },
39005
+ "switch": {
39006
+ durability: "restored",
39007
+ volatileFields: ["lastChangedAt"]
39008
+ },
39009
+ "tamper": {
39010
+ durability: "restored",
39011
+ volatileFields: ["lastChangedAt"]
39012
+ },
39013
+ "temperature-sensor": {
39014
+ durability: "restored",
39015
+ volatileFields: ["lastFetchedAt"]
39016
+ },
39017
+ "update": {
39018
+ durability: "session",
39019
+ volatileFields: []
39020
+ },
39021
+ "vacuum-control": {
39022
+ durability: "session",
39023
+ volatileFields: []
39024
+ },
39025
+ "valve": {
39026
+ durability: "session",
39027
+ volatileFields: []
39028
+ },
39029
+ "vibration": {
39030
+ durability: "restored",
39031
+ volatileFields: ["lastChangedAt"]
39032
+ },
39033
+ "water-heater": {
39034
+ durability: "session",
39035
+ volatileFields: []
39036
+ },
39037
+ "weather": {
39038
+ durability: "session",
39039
+ volatileFields: []
39040
+ },
39041
+ "zone-analytics": {
39042
+ durability: "session",
39043
+ volatileFields: []
39044
+ },
39045
+ "zone-rules": {
39046
+ durability: "restored",
39047
+ volatileFields: []
39048
+ },
39049
+ "zones": {
39050
+ durability: "restored",
39051
+ volatileFields: []
39052
+ }
39053
+ };
39054
+ /**
39055
+ * The policy for `capName`. An UNKNOWN cap resolves to `'session'` — an
39056
+ * addon-private slice the codegen has never seen is not persisted until its
39057
+ * capability says so, which is the safe direction: the cost of a lost session
39058
+ * value is a cold window, the cost of an unasked-for persist is a commit per
39059
+ * second on the fleet's busiest write path.
39060
+ */
39061
+ function runtimeStatePolicyFor(capName) {
39062
+ return RUNTIME_STATE_POLICY[capName] ?? SESSION_ONLY_POLICY;
39063
+ }
39064
+ /** Shared fallback instance — see {@link runtimeStatePolicyFor}. */
39065
+ var SESSION_ONLY_POLICY = {
39066
+ durability: "session",
39067
+ volatileFields: []
39068
+ };
39069
+ /** True when this cap's slice is written to disk. */
39070
+ function isRestoredCap(capName) {
39071
+ return runtimeStatePolicyFor(capName).durability === "restored";
39072
+ }
39073
+ /** Cap names whose slice survives a restart, sorted. */
39074
+ var RESTORED_CAP_NAMES = [
39075
+ "air-quality-sensor",
39076
+ "alarm-panel",
39077
+ "ambient-light-sensor",
39078
+ "battery",
39079
+ "binary",
39080
+ "carbon-monoxide",
39081
+ "connectivity",
39082
+ "contact",
39083
+ "cover",
39084
+ "day-night",
39085
+ "device-status",
39086
+ "doorbell",
39087
+ "enum-sensor",
39088
+ "flood",
39089
+ "gas",
39090
+ "humidity-sensor",
39091
+ "image-settings",
39092
+ "lock-control",
39093
+ "motion-zones",
39094
+ "native-object-detection",
39095
+ "numeric-sensor",
39096
+ "power-meter",
39097
+ "presence",
39098
+ "pressure-sensor",
39099
+ "privacy-mask",
39100
+ "smoke",
39101
+ "stream-params",
39102
+ "switch",
39103
+ "tamper",
39104
+ "temperature-sensor",
39105
+ "vibration",
39106
+ "zone-rules",
39107
+ "zones"
39108
+ ];
39109
+ //#endregion
38074
39110
  //#region src/generated/scope-presets.ts
38075
39111
  var SCOPE_PRESETS = [
38076
39112
  {
@@ -38733,11 +39769,197 @@ function createSystemProxy(api) {
38733
39769
  };
38734
39770
  }
38735
39771
  //#endregion
39772
+ //#region src/notification/audio-condition.ts
39773
+ /**
39774
+ * audio-condition — the authoring logic behind the sustained-sound condition
39775
+ * (`NcConditions.audio`), pure and UI-framework-free.
39776
+ *
39777
+ * WHY IT LIVES HERE
39778
+ * ─────────────────
39779
+ * Same reason as `condition-taxonomy.ts` next door: two editors author this
39780
+ * condition (the admin UI and the viewer), the vocabulary it offers is already
39781
+ * in this package (`AUDIO_MACRO_LABELS`), and a second copy of "what a cleared
39782
+ * threshold writes" is a mirror free to drift. The viewer keeps its own copy
39783
+ * because it is a separate app with its own toolchain; every surface that CAN
39784
+ * import `@camstack/types` uses this one.
39785
+ *
39786
+ * Four behaviours here are not cosmetic:
39787
+ *
39788
+ * 1. **dBFS is negative-going.** `0` is full scale; digital silence reads
39789
+ * {@link NC_AUDIO_DBFS_FLOOR}. A control that let an operator author `60`
39790
+ * meaning "loud" would write a threshold no sample can reach, and the rule
39791
+ * would look broken rather than mis-configured. Every write is clamped.
39792
+ * 2. **Neither filter given = the engine REFUSES.** With no level floor and no
39793
+ * labels every sample is trivially a hit, so the window would fire on
39794
+ * silence. {@link audioIsFailClosed} is how an editor says that BEFORE the
39795
+ * save instead of shipping a rule that never notifies and never says why.
39796
+ * 3. **Both label spellings are the same label.** The hub's taxonomy serves
39797
+ * `audio-dog`; the macro catalog and an operator both say `dog`, and the
39798
+ * matcher normalizes the prefix away on both sides. Selection therefore
39799
+ * compares NORMALIZED ids — a rule authored in the admin UI must not read
39800
+ * as "nothing selected" on the phone.
39801
+ * 4. **A stored label the build does not know stays visible.** It is offered
39802
+ * as an explicitly-unknown, removable choice, never silently dropped:
39803
+ * rewriting the rule the operator is looking at is worse than showing a
39804
+ * stale id.
39805
+ */
39806
+ /** The `audio-` namespace the NC taxonomy uses for these same macro ids. */
39807
+ var AUDIO_KIND_PREFIX = "audio-";
39808
+ /** Strip the taxonomy's `audio-` namespace: `audio-dog` and `dog` are one label. */
39809
+ function normalizeAudioLabel(value) {
39810
+ return value.startsWith(AUDIO_KIND_PREFIX) ? value.slice(6) : value;
39811
+ }
39812
+ /** Level bounds, in dBFS. Negative-going: the ceiling is full scale. */
39813
+ var NC_AUDIO_DB_MIN = -96;
39814
+ var NC_AUDIO_DB_MAX = 0;
39815
+ /** Step of the level control — 3 dB is one perceptual notch, not one unit. */
39816
+ var NC_AUDIO_DB_STEP = 3;
39817
+ /**
39818
+ * Offered when the operator turns the level filter ON. NOT a schema default:
39819
+ * an absent `dbThreshold` means "any level" and stays absent until asked for,
39820
+ * and a stepper seeded at the -96 floor would need thirty taps to reach a
39821
+ * threshold that can actually fire.
39822
+ */
39823
+ var NC_AUDIO_DB_OFFERED = -30;
39824
+ var NC_AUDIO_HIT_PERCENT_MIN = 1;
39825
+ var NC_AUDIO_HIT_PERCENT_MAX = 100;
39826
+ var NC_AUDIO_SAMPLING_MIN_SEC = 1;
39827
+ var NC_AUDIO_SAMPLING_MAX_SEC = 300;
39828
+ /** Schema defaults — an untouched sub-field must author exactly these. */
39829
+ var NC_AUDIO_DEFAULTS = {
39830
+ hitPercent: 60,
39831
+ samplingSeconds: 10
39832
+ };
39833
+ /** What an editor SHOWS for an unset condition — without authoring it. */
39834
+ function audioOrDefaults(value) {
39835
+ return value ?? NC_AUDIO_DEFAULTS;
39836
+ }
39837
+ /**
39838
+ * True when the condition names NEITHER a level floor NOR any label. The engine
39839
+ * refuses such a rule (every sample is a hit, so the window fires on silence) —
39840
+ * an editor warns instead of letting the operator ship a rule that never
39841
+ * notifies and says nothing about why.
39842
+ *
39843
+ * An ABSENT condition is not flagged: a rule with no `audio` key is not an
39844
+ * audio rule at all.
39845
+ */
39846
+ function audioIsFailClosed(value) {
39847
+ if (value === void 0) return false;
39848
+ return value.dbThreshold === void 0 && (value.labels === void 0 || value.labels.length === 0);
39849
+ }
39850
+ function clampInt(value, min, max) {
39851
+ if (!Number.isFinite(value)) return min;
39852
+ return Math.min(max, Math.max(min, Math.round(value)));
39853
+ }
39854
+ function has(patch, key) {
39855
+ return Object.prototype.hasOwnProperty.call(patch, key);
39856
+ }
39857
+ /**
39858
+ * Apply a sub-field edit, seeding from the defaults when nothing is stored yet.
39859
+ * The result is always in-bounds for `NcAudioConditionSchema`, and a cleared
39860
+ * filter is OMITTED rather than written as an explicit `undefined` (which
39861
+ * survives a merge patch as a deliberate erase and reads as authored).
39862
+ */
39863
+ function patchAudio(current, patch) {
39864
+ const base = audioOrDefaults(current);
39865
+ const labels = has(patch, "labels") ? patch.labels : base.labels;
39866
+ const dbThreshold = has(patch, "dbThreshold") ? patch.dbThreshold : base.dbThreshold;
39867
+ return {
39868
+ ...labels !== void 0 && labels.length > 0 ? { labels: [...labels] } : {},
39869
+ ...dbThreshold !== void 0 ? { dbThreshold: clampInt(dbThreshold, NC_AUDIO_DB_MIN, 0) } : {},
39870
+ hitPercent: clampInt(patch.hitPercent ?? base.hitPercent, 1, 100),
39871
+ samplingSeconds: clampInt(patch.samplingSeconds ?? base.samplingSeconds, 1, 300)
39872
+ };
39873
+ }
39874
+ /** The icon glyph for a stored label the catalogue does not know. */
39875
+ var UNKNOWN_LABEL_ICON = "🔈";
39876
+ var ICON_BY_ID = new Map(AUDIO_MACRO_LABELS.flatMap((macro) => macro.icon === void 0 ? [] : [[macro.id, macro.icon]]));
39877
+ /**
39878
+ * The label choices: the hub's `audioKinds` when it serves them (its WORDS, the
39879
+ * local EMOJI, keyed by the normalized id), else the local macro catalog — plus
39880
+ * any stored label neither side knows, marked unknown.
39881
+ *
39882
+ * The hub's list wins on membership because it knows which macros this cluster
39883
+ * actually classifies; the icons stay local because the taxonomy carries none.
39884
+ */
39885
+ function audioLabelChoices(taxonomy, selected) {
39886
+ const fromHub = taxonomy?.audioKinds ?? [];
39887
+ const choices = fromHub.length > 0 ? fromHub.map((entry) => {
39888
+ const id = normalizeAudioLabel(entry.kind);
39889
+ return {
39890
+ value: id,
39891
+ label: entry.label,
39892
+ icon: ICON_BY_ID.get(id) ?? UNKNOWN_LABEL_ICON,
39893
+ unknown: false
39894
+ };
39895
+ }) : AUDIO_MACRO_LABELS.map((macro) => ({
39896
+ value: macro.id,
39897
+ label: macro.name,
39898
+ icon: macro.icon ?? UNKNOWN_LABEL_ICON,
39899
+ unknown: false
39900
+ }));
39901
+ const known = new Set(choices.map((choice) => choice.value));
39902
+ const unknownLabels = [];
39903
+ for (const stored of selected ?? []) {
39904
+ const id = normalizeAudioLabel(stored);
39905
+ if (known.has(id) || unknownLabels.includes(id)) continue;
39906
+ unknownLabels.push(id);
39907
+ choices.push({
39908
+ value: id,
39909
+ label: id,
39910
+ icon: UNKNOWN_LABEL_ICON,
39911
+ unknown: true
39912
+ });
39913
+ }
39914
+ return {
39915
+ choices,
39916
+ unknownLabels
39917
+ };
39918
+ }
39919
+ /**
39920
+ * Is this macro id selected? Compares NORMALIZED ids, so a rule authored with
39921
+ * the taxonomy spelling (`audio-dog`) reads as selected here.
39922
+ */
39923
+ function isAudioLabelSelected(labels, id) {
39924
+ const wanted = normalizeAudioLabel(id);
39925
+ return (labels ?? []).some((stored) => normalizeAudioLabel(stored) === wanted);
39926
+ }
39927
+ /**
39928
+ * Toggle one macro id. Removing drops EVERY stored spelling of it; adding
39929
+ * appends the bare macro id. Returns `undefined` when the last label goes — the
39930
+ * field is optional and absent means "any sound", never an empty array (which
39931
+ * `NcAudioConditionSchema` rejects outright).
39932
+ */
39933
+ function toggleAudioLabel(labels, id) {
39934
+ const wanted = normalizeAudioLabel(id);
39935
+ const current = labels ?? [];
39936
+ if (isAudioLabelSelected(current, wanted)) {
39937
+ const next = current.filter((stored) => normalizeAudioLabel(stored) !== wanted);
39938
+ return next.length > 0 ? next : void 0;
39939
+ }
39940
+ return [...current, wanted];
39941
+ }
39942
+ //#endregion
38736
39943
  //#region src/notification/condition-taxonomy.ts
38737
39944
  /** Condition ids that render the class picker (video macros/subs + audio). */
38738
39945
  var CLASS_CONDITION_IDS = new Set(["classes", "classesExclude"]);
38739
39946
  /** Condition ids that render the sensor/control label picker. */
38740
39947
  var LABEL_CONDITION_IDS = new Set(["sensorKinds"]);
39948
+ /**
39949
+ * Condition ids whose class scope is a COUNT, not a match — they get the
39950
+ * MACRO-ONLY vocabulary.
39951
+ *
39952
+ * Every other class condition goes through the engine's `expandClassSelector`,
39953
+ * which expands a macro to its subs and matches a sub against the detection's
39954
+ * own class. Occupancy does not: it reads `byClass[className]` out of the
39955
+ * ZoneAnalytics snapshot, and that record is keyed on MACROS only (the executor
39956
+ * maps COCO onto the macro and drops the rest). A sub-type therefore names a key
39957
+ * that does not exist — the counter is 0 forever and the rule cannot fire.
39958
+ *
39959
+ * Live cost, 2026-08-12: two rules on device 617 were authored with `car`
39960
+ * because this picker offered the whole taxonomy. They were silent for a day.
39961
+ */
39962
+ var COUNT_CLASS_CONDITION_IDS = new Set(["occupancy"]);
38741
39963
  function toOption(entry) {
38742
39964
  return {
38743
39965
  value: entry.kind,
@@ -38815,6 +40037,14 @@ function pickerForCondition(conditionId, taxonomy) {
38815
40037
  });
38816
40038
  return groups.length > 0 ? { groups } : null;
38817
40039
  }
40040
+ if (COUNT_CLASS_CONDITION_IDS.has(conditionId)) {
40041
+ const options = taxonomy.videoClasses.filter((e) => e.parentKind === null).map(toOption);
40042
+ return options.length > 0 ? { groups: [{
40043
+ key: "macro",
40044
+ fallbackLabel: "Classes",
40045
+ options
40046
+ }] } : null;
40047
+ }
38818
40048
  if (LABEL_CONDITION_IDS.has(conditionId)) {
38819
40049
  const groups = groupLabels(taxonomy.labels);
38820
40050
  return groups.length > 0 ? { groups } : null;
@@ -41410,12 +42640,14 @@ exports.DEFAULT_EVENT_COLOR = DEFAULT_EVENT_COLOR;
41410
42640
  exports.DEFAULT_FEATURES = DEFAULT_FEATURES;
41411
42641
  exports.DEFAULT_NATIVE_LEASE_SETTINGS = DEFAULT_NATIVE_LEASE_SETTINGS;
41412
42642
  exports.DEFAULT_RETENTION = DEFAULT_RETENTION;
42643
+ exports.DEFAULT_RUNTIME_STATE_DURABILITY = require_sleep.DEFAULT_RUNTIME_STATE_DURABILITY;
41413
42644
  exports.DEFAULT_SCRUB_THUMBNAIL_PRESET = DEFAULT_SCRUB_THUMBNAIL_PRESET;
41414
42645
  exports.DEFAULT_TIMELAPSE_PREVIEW_TEXT = DEFAULT_TIMELAPSE_PREVIEW_TEXT;
41415
42646
  exports.DETAIL_CROP_PADDING_FIELD = DETAIL_CROP_PADDING_FIELD;
41416
42647
  exports.DETAIL_CROP_PADDING_KEY = DETAIL_CROP_PADDING_KEY;
41417
42648
  exports.DETAIL_CROP_SECTION_ID = DETAIL_CROP_SECTION_ID;
41418
42649
  exports.DETAIL_CROP_SQUARE_KEY = DETAIL_CROP_SQUARE_KEY;
42650
+ exports.DETECTION_MACRO_CLASSES = DETECTION_MACRO_CLASSES;
41419
42651
  exports.DETECTION_PIPELINE_CAP_NAME = DETECTION_PIPELINE_CAP_NAME;
41420
42652
  exports.DEVICE_BACKEND_TO_FORMAT = DEVICE_BACKEND_TO_FORMAT;
41421
42653
  exports.DEVICE_CAP_NAMES = DEVICE_CAP_NAMES;
@@ -41659,6 +42891,15 @@ exports.NATIVE_LEASE_TILE_BUDGET_FIELD = NATIVE_LEASE_TILE_BUDGET_FIELD;
41659
42891
  exports.NATIVE_LEASE_TILE_BUDGET_KEY = NATIVE_LEASE_TILE_BUDGET_KEY;
41660
42892
  exports.NC_ALARM_SYSTEM_EVENT_KINDS = NC_ALARM_SYSTEM_EVENT_KINDS;
41661
42893
  exports.NC_AUDIO_DBFS_FLOOR = NC_AUDIO_DBFS_FLOOR;
42894
+ exports.NC_AUDIO_DB_MAX = NC_AUDIO_DB_MAX;
42895
+ exports.NC_AUDIO_DB_MIN = NC_AUDIO_DB_MIN;
42896
+ exports.NC_AUDIO_DB_OFFERED = NC_AUDIO_DB_OFFERED;
42897
+ exports.NC_AUDIO_DB_STEP = NC_AUDIO_DB_STEP;
42898
+ exports.NC_AUDIO_DEFAULTS = NC_AUDIO_DEFAULTS;
42899
+ exports.NC_AUDIO_HIT_PERCENT_MAX = NC_AUDIO_HIT_PERCENT_MAX;
42900
+ exports.NC_AUDIO_HIT_PERCENT_MIN = NC_AUDIO_HIT_PERCENT_MIN;
42901
+ exports.NC_AUDIO_SAMPLING_MAX_SEC = NC_AUDIO_SAMPLING_MAX_SEC;
42902
+ exports.NC_AUDIO_SAMPLING_MIN_SEC = NC_AUDIO_SAMPLING_MIN_SEC;
41662
42903
  exports.NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NC_AUTHORABLE_SYSTEM_EVENT_KINDS;
41663
42904
  exports.NC_BASE_CONDITION_KEYS = NC_BASE_CONDITION_KEYS;
41664
42905
  exports.NC_CONDITION_CATALOG = NC_CONDITION_CATALOG;
@@ -41666,6 +42907,7 @@ exports.NC_CONFIRM_DEFAULT_MAX_IMAGE_PX = NC_CONFIRM_DEFAULT_MAX_IMAGE_PX;
41666
42907
  exports.NC_CONFIRM_DEFAULT_TIMEOUT_MS = NC_CONFIRM_DEFAULT_TIMEOUT_MS;
41667
42908
  exports.NC_CONFIRM_MAX_TIMEOUT_MS = NC_CONFIRM_MAX_TIMEOUT_MS;
41668
42909
  exports.NC_CONFIRM_MIN_TIMEOUT_MS = NC_CONFIRM_MIN_TIMEOUT_MS;
42910
+ exports.NC_DEFAULT_SNOOZE_MINUTES = NC_DEFAULT_SNOOZE_MINUTES;
41669
42911
  exports.NC_HISTORY_LIMIT_DEFAULT = NC_HISTORY_LIMIT_DEFAULT;
41670
42912
  exports.NC_HISTORY_LIMIT_MAX = NC_HISTORY_LIMIT_MAX;
41671
42913
  exports.NC_MAX_PER_TRACK_IMMEDIATE = NC_MAX_PER_TRACK_IMMEDIATE;
@@ -41812,7 +43054,9 @@ exports.REACHABILITY_PROBE_TIMEOUT_MS = REACHABILITY_PROBE_TIMEOUT_MS;
41812
43054
  exports.RECOGNITION_TYPES = RECOGNITION_TYPES;
41813
43055
  exports.RECORDING_EXPORT_MAX_READ_BYTES = RECORDING_EXPORT_MAX_READ_BYTES;
41814
43056
  exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
43057
+ exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
41815
43058
  exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
43059
+ exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
41816
43060
  exports.RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT;
41817
43061
  exports.RawStateResultSchema = require_sleep.RawStateResultSchema;
41818
43062
  exports.ReadGopBytesResultSchema = ReadGopBytesResultSchema;
@@ -42084,7 +43328,10 @@ exports.assertTimelapseCadences = assertTimelapseCadences;
42084
43328
  exports.audioAnalysisCapability = audioAnalysisCapability;
42085
43329
  exports.audioAnalyzerCapability = audioAnalyzerCapability;
42086
43330
  exports.audioCodecCapability = audioCodecCapability;
43331
+ exports.audioIsFailClosed = audioIsFailClosed;
43332
+ exports.audioLabelChoices = audioLabelChoices;
42087
43333
  exports.audioMetricsCapability = audioMetricsCapability;
43334
+ exports.audioOrDefaults = audioOrDefaults;
42088
43335
  exports.audioPlanFromEncodeProfile = require_canonical_hash.audioPlanFromEncodeProfile;
42089
43336
  exports.authProviderCapability = authProviderCapability;
42090
43337
  exports.autoAssignProfiles = autoAssignProfiles;
@@ -42222,15 +43469,18 @@ exports.intercomCapability = intercomCapability;
42222
43469
  exports.invocationFromEncodeProfile = require_canonical_hash.invocationFromEncodeProfile;
42223
43470
  exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
42224
43471
  exports.isArrayOutputSchema = isArrayOutputSchema;
43472
+ exports.isAudioLabelSelected = isAudioLabelSelected;
42225
43473
  exports.isBaseConditionKey = isBaseConditionKey;
42226
43474
  exports.isCollectionArrayMethod = isCollectionArrayMethod;
42227
43475
  exports.isDeployableToAgent = isDeployableToAgent;
43476
+ exports.isDetectionMacroClass = isDetectionMacroClass;
42228
43477
  exports.isDeviceConfigCap = require_sleep.isDeviceConfigCap;
42229
43478
  exports.isDeviceScopedCap = require_sleep.isDeviceScopedCap;
42230
43479
  exports.isEvent = require_sleep.isEvent;
42231
43480
  exports.isIsolatedBuiltin = isIsolatedBuiltin;
42232
43481
  exports.isNode = isNode;
42233
43482
  exports.isObjectInput = isObjectInput;
43483
+ exports.isRestoredCap = isRestoredCap;
42234
43484
  exports.isSameAddonId = isSameAddonId;
42235
43485
  exports.isScheduleActive = isScheduleActive;
42236
43486
  exports.isSoftwareDecode = require_canonical_hash.isSoftwareDecode;
@@ -42279,6 +43529,7 @@ exports.networkQualityCapability = networkQualityCapability;
42279
43529
  exports.nodePin = require_sleep.nodePin;
42280
43530
  exports.nodesCapability = nodesCapability;
42281
43531
  exports.normalizeAddonInitResult = require_sleep.normalizeAddonInitResult;
43532
+ exports.normalizeAudioLabel = normalizeAudioLabel;
42282
43533
  exports.normalizeUnit = normalizeUnit;
42283
43534
  exports.notificationOutputCapability = notificationOutputCapability;
42284
43535
  exports.notificationRulesCapability = notificationRulesCapability;
@@ -42295,6 +43546,7 @@ exports.parseJsonObject = require_sleep.parseJsonObject;
42295
43546
  exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
42296
43547
  exports.parseProfileBrokerId = require_sleep.parseProfileBrokerId;
42297
43548
  exports.parseStreamParamsFormPatch = parseStreamParamsFormPatch;
43549
+ exports.patchAudio = patchAudio;
42298
43550
  exports.petFeederCapability = petFeederCapability;
42299
43551
  exports.pickAccessoryControl = pickAccessoryControl;
42300
43552
  exports.pickDetailCropConvention = pickDetailCropConvention;
@@ -42347,6 +43599,7 @@ exports.resolveScrubThumbnailGeometry = resolveScrubThumbnailGeometry;
42347
43599
  exports.resolveVariantModelId = resolveVariantModelId;
42348
43600
  exports.runInferenceStep = runInferenceStep;
42349
43601
  exports.runtimeDevices = runtimeDevices;
43602
+ exports.runtimeStatePolicyFor = runtimeStatePolicyFor;
42350
43603
  exports.sceneMonitorCapability = sceneMonitorCapability;
42351
43604
  exports.scopeKey = require_sleep.scopeKey;
42352
43605
  exports.scopesAllowAddon = scopesAllowAddon;
@@ -42393,6 +43646,7 @@ exports.toExpressionValue = toExpressionValue;
42393
43646
  exports.toNodeId = require_sleep.toNodeId;
42394
43647
  exports.toStreamSourceEntry = toStreamSourceEntry;
42395
43648
  exports.toastCapability = toastCapability;
43649
+ exports.toggleAudioLabel = toggleAudioLabel;
42396
43650
  exports.tokenize = tokenize;
42397
43651
  exports.transcodeBody = transcodeBody;
42398
43652
  exports.tryConvertUnit = tryConvertUnit;