camstack 1.2.23 → 1.2.25

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.
@@ -23631,9 +23631,9 @@ var require_zod = __commonJS({
23631
23631
  }
23632
23632
  });
23633
23633
 
23634
- // ../system/dist/dist-BV5Bug9t.js
23635
- var require_dist_BV5Bug9t = __commonJS({
23636
- "../system/dist/dist-BV5Bug9t.js"(exports) {
23634
+ // ../system/dist/dist-B4WyhfjH.js
23635
+ var require_dist_B4WyhfjH = __commonJS({
23636
+ "../system/dist/dist-B4WyhfjH.js"(exports) {
23637
23637
  "use strict";
23638
23638
  var zod = require_zod();
23639
23639
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -29091,7 +29091,14 @@ var require_dist_BV5Bug9t = __commonJS({
29091
29091
  low: zod.z.string().optional()
29092
29092
  }),
29093
29093
  lastChangedAt: zod.z.number()
29094
- })
29094
+ }),
29095
+ /**
29096
+ * 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.
29097
+ *
29098
+ * See `RuntimeStateDurability`. Enforced by
29099
+ * `scripts/check-runtime-state-durability.ts`.
29100
+ */
29101
+ durability: "session"
29095
29102
  };
29096
29103
  function isVoidInput(schema) {
29097
29104
  const def = schema._def;
@@ -29761,6 +29768,13 @@ var require_dist_BV5Bug9t = __commonJS({
29761
29768
  kind: "poll"
29762
29769
  },
29763
29770
  runtimeState: DeviceDiscoveryStatusSchema.extend({ lastFetchedAt: zod.z.number().int().nonnegative() }),
29771
+ /**
29772
+ * Runtime-state durability: **session** — 5.7 KB of scan output on the largest device, fully re-derivable by re-scanning.
29773
+ *
29774
+ * See `RuntimeStateDurability`. Enforced by
29775
+ * `scripts/check-runtime-state-durability.ts`.
29776
+ */
29777
+ durability: "session",
29764
29778
  methods: {
29765
29779
  /**
29766
29780
  * Snapshot of the current `discovered` list. Returns the
@@ -31961,6 +31975,7 @@ var require_dist_BV5Bug9t = __commonJS({
31961
31975
  "acknowledge",
31962
31976
  "dismiss",
31963
31977
  "silence",
31978
+ "snooze",
31964
31979
  "view",
31965
31980
  "play",
31966
31981
  "open",
@@ -31968,9 +31983,13 @@ var require_dist_BV5Bug9t = __commonJS({
31968
31983
  "lock",
31969
31984
  "unlock",
31970
31985
  "arm",
31986
+ "arm-home",
31987
+ "arm-away",
31988
+ "arm-night",
31971
31989
  "disarm",
31972
31990
  "light",
31973
- "alert"
31991
+ "alert",
31992
+ "camera"
31974
31993
  ]);
31975
31994
  var NotificationActionSchema = zod.z.object({
31976
31995
  id: zod.z.string(),
@@ -31985,7 +32004,23 @@ var require_dist_BV5Bug9t = __commonJS({
31985
32004
  * else — see `notification-center/action-token.ts` for what that does and
31986
32005
  * does not buy.
31987
32006
  */
31988
- destructive: zod.z.boolean().optional()
32007
+ destructive: zod.z.boolean().optional(),
32008
+ /**
32009
+ * How the tap should REACH the url.
32010
+ *
32011
+ * `navigate` (absent, and every button authored before this field) opens it:
32012
+ * the phone leaves the notification and shows whatever the callback returns.
32013
+ * That is right for a button whose answer the operator wants to read.
32014
+ *
32015
+ * `background` fires it as a POST and stays put. It exists for the buttons
32016
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
32017
+ * an answer to the notification, and being thrown into a browser tab to
32018
+ * confirm it costs more attention than the notification did. A backend that
32019
+ * cannot do a background call renders it as an ordinary link (the adapters
32020
+ * fall back rather than dropping the button), so this is a preference, never
32021
+ * a requirement.
32022
+ */
32023
+ mode: zod.z.enum(["navigate", "background"]).optional()
31989
32024
  });
31990
32025
  var NotificationSchema = zod.z.object({
31991
32026
  body: zod.z.string(),
@@ -32143,6 +32178,24 @@ var require_dist_BV5Bug9t = __commonJS({
32143
32178
  }), zod.z.void(), { kind: "mutation" })
32144
32179
  }
32145
32180
  };
32181
+ new Set([
32182
+ {
32183
+ id: "person",
32184
+ name: "Person"
32185
+ },
32186
+ {
32187
+ id: "vehicle",
32188
+ name: "Vehicle"
32189
+ },
32190
+ {
32191
+ id: "animal",
32192
+ name: "Animal"
32193
+ },
32194
+ {
32195
+ id: "package",
32196
+ name: "Package"
32197
+ }
32198
+ ].map((l) => l.id));
32146
32199
  var COCO_TO_MACRO = {
32147
32200
  mapping: {
32148
32201
  person: "person",
@@ -32763,7 +32816,17 @@ var require_dist_BV5Bug9t = __commonJS({
32763
32816
  * full slice; renders an arm button per `availableModes` entry and
32764
32817
  * a PIN field iff `requiresCode === true`.
32765
32818
  */
32766
- runtimeState: AlarmPanelStatusSchema
32819
+ runtimeState: AlarmPanelStatusSchema,
32820
+ /**
32821
+ * Runtime-state durability: **restored** — armed state is the one thing a panel must not lose across a restart.
32822
+ *
32823
+ * See `RuntimeStateDurability`. Enforced by
32824
+ * `scripts/check-runtime-state-durability.ts`.
32825
+ */
32826
+ durability: "restored",
32827
+ /** Clock fields: written, but excluded from the compare that decides
32828
+ * whether persisting is worth a SQLite commit. */
32829
+ volatileStateFields: ["lastChangedAt"]
32767
32830
  };
32768
32831
  var MaskPointSchema = zod.z.object({
32769
32832
  x: zod.z.number(),
@@ -32831,6 +32894,8 @@ var require_dist_BV5Bug9t = __commonJS({
32831
32894
  "alarm-triggered",
32832
32895
  "alarm-armed",
32833
32896
  "alarm-disarmed",
32897
+ "alarm-arming",
32898
+ "alarm-arm-refused",
32834
32899
  "camera-online",
32835
32900
  "camera-offline",
32836
32901
  "camera-disabled",
@@ -32861,6 +32926,7 @@ var require_dist_BV5Bug9t = __commonJS({
32861
32926
  nodeIds: zod.z.array(zod.z.string().min(1)).min(1).optional(),
32862
32927
  packageNames: zod.z.array(zod.z.string().min(1)).min(1).optional()
32863
32928
  });
32929
+ var NC_SNOOZE_MAX_MINUTES = 1440;
32864
32930
  var NcScheduleWindowSchema = zod.z.object({
32865
32931
  /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
32866
32932
  days: zod.z.array(zod.z.number().int().min(0).max(6)).min(1),
@@ -33117,15 +33183,15 @@ var require_dist_BV5Bug9t = __commonJS({
33117
33183
  * (an `immediate` rule naming an `audio-*` class, one notification per
33118
33184
  * classified sample) stays exactly as it was for rules that already use it.
33119
33185
  *
33120
- * NOT in {@link NC_CONDITION_CATALOG} yet, and that is the sequencing rule
33121
- * rather than an oversight: the viewer mirrors the descriptor enums BY HAND
33122
- * (`camstack/src/data/notification-center.ts`, guarded by
33123
- * `scripts/check-viewer-condition-mirror.ts`) and its rule editor STRIPS the
33186
+ * In {@link NC_CONDITION_CATALOG} since P2, and the ORDER it got there is the
33187
+ * rule rather than an accident: the viewer mirrors the descriptor enums BY
33188
+ * HAND (`camstack/src/data/notification-center.ts`, guarded by
33189
+ * `scripts/check-viewer-condition-mirror.ts`) and its rule editor strips the
33124
33190
  * condition fields it does not know when a rule is saved from the phone.
33125
33191
  * Publishing an editor for a condition the app cannot round-trip is how an
33126
- * operator loses a rule's conditions by opening it — so the descriptor, the
33127
- * admin widget and the viewer mirror land together (P2 + P3), and only then
33128
- * does an audio rule become authorable.
33192
+ * operator loses a rule's conditions by opening it — so the viewer mirror
33193
+ * (P3, shipped) went FIRST, and the descriptor an editor renders from
33194
+ * follows here.
33129
33195
  */
33130
33196
  audio: NcAudioConditionSchema.optional()
33131
33197
  });
@@ -33294,6 +33360,30 @@ var require_dist_BV5Bug9t = __commonJS({
33294
33360
  */
33295
33361
  snoozeAllowGlobal: zod.z.boolean().optional(),
33296
33362
  /**
33363
+ * The snooze durations THIS rule's notification offers as buttons, in
33364
+ * minutes.
33365
+ *
33366
+ * Three states, and all three are distinct — which is exactly why this is
33367
+ * `.optional()` and never `.default()`. A Zod default does not run on the
33368
+ * addon cap path (three production failures in one day), so a schema default
33369
+ * would collapse the first two:
33370
+ *
33371
+ * | value | meaning |
33372
+ * | --- | --- |
33373
+ * | absent | the operator never said ⇒ {@link NC_DEFAULT_SNOOZE_MINUTES} |
33374
+ * | `[]` | **no snooze buttons on this rule** — the explicit override |
33375
+ * | a list | these choices, de-duplicated and sorted, at most four |
33376
+ *
33377
+ * `.max(4)` because the notifier's own action budget is small (ntfy allows
33378
+ * three buttons in total) and a rule that spent it all on snooze choices
33379
+ * would push its own tap-through actions off the notification.
33380
+ *
33381
+ * An empty list is NOT an alarm exemption: a rule the alarm is about, or
33382
+ * that arms the panel, is exempt automatically and cannot be silenced by a
33383
+ * window from anywhere (D133).
33384
+ */
33385
+ snoozeOptions: zod.z.array(zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES)).max(4).optional(),
33386
+ /**
33297
33387
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
33298
33388
  *
33299
33389
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -33383,6 +33473,7 @@ var require_dist_BV5Bug9t = __commonJS({
33383
33473
  "device",
33384
33474
  "package",
33385
33475
  "occupancy",
33476
+ "audio",
33386
33477
  "system"
33387
33478
  ]),
33388
33479
  label: zod.z.string(),
@@ -33401,6 +33492,7 @@ var require_dist_BV5Bug9t = __commonJS({
33401
33492
  "crossingSelect",
33402
33493
  "polygonDraw",
33403
33494
  "occupancy",
33495
+ "audio",
33404
33496
  "deviceState",
33405
33497
  "systemEvent"
33406
33498
  ]),
@@ -33511,7 +33603,20 @@ var require_dist_BV5Bug9t = __commonJS({
33511
33603
  ruleId: zod.z.string().optional(),
33512
33604
  /** Required when `scope: 'device'`. */
33513
33605
  deviceId: zod.z.number().int().optional(),
33514
- durationMinutes: zod.z.number().int().min(1).max(1440),
33606
+ /**
33607
+ * Narrow the window to these subject classes — "the cat, not the person".
33608
+ *
33609
+ * ORTHOGONAL to `scope`, deliberately, and absent means EVERY class: that is
33610
+ * what every window authored before this field meant, so no persisted row
33611
+ * changes meaning and no client has to learn anything to keep working.
33612
+ *
33613
+ * It is what makes the window's real key `(deviceId, classes[])` and lets it
33614
+ * cross rules (D133): the operator points at a camera and a kind of thing,
33615
+ * not at whichever of their four rules happened to produce the notification
33616
+ * they are dismissing.
33617
+ */
33618
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
33619
+ durationMinutes: zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
33515
33620
  /**
33516
33621
  * Silence this for EVERY recipient, not just the caller. Permission is
33517
33622
  * checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
@@ -33535,6 +33640,10 @@ var require_dist_BV5Bug9t = __commonJS({
33535
33640
  scope: NcSnoozeScopeSchema,
33536
33641
  ruleId: zod.z.string().optional(),
33537
33642
  deviceId: zod.z.number().int().optional(),
33643
+ /** Subject classes this window covers. ABSENT = every class — see
33644
+ * {@link NcSnoozeInputSchema.shape.classes}. Lives in the JSON blob and has
33645
+ * no SQLite column: nothing queries a window by class. */
33646
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
33538
33647
  startedAt: zod.z.number(),
33539
33648
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
33540
33649
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -35807,7 +35916,14 @@ var require_dist_BV5Bug9t = __commonJS({
35807
35916
  * handle. Slice shape is `{ zones: Zone[] }` so future extensions
35808
35917
  * (e.g. zone groupings) can sit alongside the polygon list.
35809
35918
  */
35810
- runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() })
35919
+ runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() }),
35920
+ /**
35921
+ * 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.
35922
+ *
35923
+ * See `RuntimeStateDurability`. Enforced by
35924
+ * `scripts/check-runtime-state-durability.ts`.
35925
+ */
35926
+ durability: "restored"
35811
35927
  };
35812
35928
  var NativeCropBboxSchema = zod.z.object({
35813
35929
  x: zod.z.number(),
@@ -36071,15 +36187,21 @@ var require_dist_BV5Bug9t = __commonJS({
36071
36187
  */
36072
36188
  onboardMotionDrivesAnalyzer: zod.z.boolean().default(true),
36073
36189
  /**
36074
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
36075
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
36076
- * this is off by default because the recheck re-subscribes a detection session
36077
- * every N seconds while `watching`, a major source of pull-decoder re-dial
36078
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
36190
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
36191
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
36079
36192
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
36080
36193
  * (and only render) when this is enabled.
36194
+ *
36195
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
36196
+ * recheck re-subscribes a detection session every N seconds while `watching`
36197
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
36198
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
36199
+ * object is counted only while the stationary registry holds it, and the
36200
+ * registry rebuilds from motion, so after a restart a parked car was invisible
36201
+ * to every occupancy rule until something moved in front of it. The churn is
36202
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
36081
36203
  */
36082
- occupancyRecheckEnabled: zod.z.boolean().default(false),
36204
+ occupancyRecheckEnabled: zod.z.boolean().default(true),
36083
36205
  occupancyRecheckSec: zod.z.number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
36084
36206
  occupancyRecheckFrames: zod.z.number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
36085
36207
  /**
@@ -39110,7 +39232,17 @@ var require_dist_BV5Bug9t = __commonJS({
39110
39232
  schema: AirQualitySensorStatusSchema,
39111
39233
  kind: "push"
39112
39234
  },
39113
- runtimeState: AirQualitySensorStatusSchema
39235
+ runtimeState: AirQualitySensorStatusSchema,
39236
+ /**
39237
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
39238
+ *
39239
+ * See `RuntimeStateDurability`. Enforced by
39240
+ * `scripts/check-runtime-state-durability.ts`.
39241
+ */
39242
+ durability: "restored",
39243
+ /** Clock fields: written, but excluded from the compare that decides
39244
+ * whether persisting is worth a SQLite commit. */
39245
+ volatileStateFields: ["lastFetchedAt"]
39114
39246
  };
39115
39247
  var AmbientLightSensorStatusSchema = zod.z.object({
39116
39248
  /** Current illuminance in lux (lx). */
@@ -39138,7 +39270,17 @@ var require_dist_BV5Bug9t = __commonJS({
39138
39270
  schema: AmbientLightSensorStatusSchema,
39139
39271
  kind: "push"
39140
39272
  },
39141
- runtimeState: AmbientLightSensorStatusSchema
39273
+ runtimeState: AmbientLightSensorStatusSchema,
39274
+ /**
39275
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
39276
+ *
39277
+ * See `RuntimeStateDurability`. Enforced by
39278
+ * `scripts/check-runtime-state-durability.ts`.
39279
+ */
39280
+ durability: "restored",
39281
+ /** Clock fields: written, but excluded from the compare that decides
39282
+ * whether persisting is worth a SQLite commit. */
39283
+ volatileStateFields: ["lastFetchedAt"]
39142
39284
  };
39143
39285
  var AudioClassSummarySchema = zod.z.object({
39144
39286
  className: zod.z.string(),
@@ -39228,7 +39370,14 @@ var require_dist_BV5Bug9t = __commonJS({
39228
39370
  }), AudioMetricsHistorySchema)
39229
39371
  },
39230
39372
  /** Reactive runtime-state mirror — live `device.state.audioMetrics.value`. */
39231
- runtimeState: AudioMetricsSnapshotSchema
39373
+ runtimeState: AudioMetricsSnapshotSchema,
39374
+ /**
39375
+ * 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.
39376
+ *
39377
+ * See `RuntimeStateDurability`. Enforced by
39378
+ * `scripts/check-runtime-state-durability.ts`.
39379
+ */
39380
+ durability: "session"
39232
39381
  };
39233
39382
  var AutomationControlStatusSchema = zod.z.object({
39234
39383
  /** Whether the automation is currently enabled. Disabled automations
@@ -39279,7 +39428,14 @@ var require_dist_BV5Bug9t = __commonJS({
39279
39428
  * reads `enabled` (toggle) + `isRunning` (spinner) + `lastError`
39280
39429
  * (badge) directly.
39281
39430
  */
39282
- runtimeState: AutomationControlStatusSchema
39431
+ runtimeState: AutomationControlStatusSchema,
39432
+ /**
39433
+ * 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.
39434
+ *
39435
+ * See `RuntimeStateDurability`. Enforced by
39436
+ * `scripts/check-runtime-state-durability.ts`.
39437
+ */
39438
+ durability: "session"
39283
39439
  };
39284
39440
  var BatteryStatusSchema = zod.z.object({
39285
39441
  /** 0..100 inclusive. Firmware-reported. */
@@ -39380,7 +39536,17 @@ var require_dist_BV5Bug9t = __commonJS({
39380
39536
  * via `device.runtimeState.getCapState('battery')` regardless of
39381
39537
  * the underlying driver.
39382
39538
  */
39383
- runtimeState: BatteryStatusSchema
39539
+ runtimeState: BatteryStatusSchema,
39540
+ /**
39541
+ * 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.
39542
+ *
39543
+ * See `RuntimeStateDurability`. Enforced by
39544
+ * `scripts/check-runtime-state-durability.ts`.
39545
+ */
39546
+ durability: "restored",
39547
+ /** Clock fields: written, but excluded from the compare that decides
39548
+ * whether persisting is worth a SQLite commit. */
39549
+ volatileStateFields: ["lastUpdated"]
39384
39550
  };
39385
39551
  var BinaryStatusSchema = zod.z.object({
39386
39552
  on: zod.z.boolean(),
@@ -39398,7 +39564,17 @@ var require_dist_BV5Bug9t = __commonJS({
39398
39564
  schema: BinaryStatusSchema,
39399
39565
  kind: "push"
39400
39566
  },
39401
- runtimeState: BinaryStatusSchema
39567
+ runtimeState: BinaryStatusSchema,
39568
+ /**
39569
+ * Runtime-state durability: **restored** — transition-driven sensor state; the restored value gives the boot comparison.
39570
+ *
39571
+ * See `RuntimeStateDurability`. Enforced by
39572
+ * `scripts/check-runtime-state-durability.ts`.
39573
+ */
39574
+ durability: "restored",
39575
+ /** Clock fields: written, but excluded from the compare that decides
39576
+ * whether persisting is worth a SQLite commit. */
39577
+ volatileStateFields: ["lastChangedAt"]
39402
39578
  };
39403
39579
  var BrightnessStatusSchema = zod.z.object({
39404
39580
  /** Current level as 0..100 inclusive. Firmware-reported. */
@@ -39440,7 +39616,14 @@ var require_dist_BV5Bug9t = __commonJS({
39440
39616
  * by the kernel. Read via `device.state.brightness.value` so UI
39441
39617
  * sliders surface the current level without polling the provider.
39442
39618
  */
39443
- runtimeState: BrightnessStatusSchema
39619
+ runtimeState: BrightnessStatusSchema,
39620
+ /**
39621
+ * Runtime-state durability: **session** — live lamp state, re-published by the provider on connect.
39622
+ *
39623
+ * See `RuntimeStateDurability`. Enforced by
39624
+ * `scripts/check-runtime-state-durability.ts`.
39625
+ */
39626
+ durability: "session"
39444
39627
  };
39445
39628
  var buttonCapability = {
39446
39629
  name: "button",
@@ -39520,7 +39703,17 @@ var require_dist_BV5Bug9t = __commonJS({
39520
39703
  schema: CarbonMonoxideStatusSchema,
39521
39704
  kind: "push"
39522
39705
  },
39523
- runtimeState: CarbonMonoxideStatusSchema
39706
+ runtimeState: CarbonMonoxideStatusSchema,
39707
+ /**
39708
+ * Runtime-state durability: **restored** — as `smoke`.
39709
+ *
39710
+ * See `RuntimeStateDurability`. Enforced by
39711
+ * `scripts/check-runtime-state-durability.ts`.
39712
+ */
39713
+ durability: "restored",
39714
+ /** Clock fields: written, but excluded from the compare that decides
39715
+ * whether persisting is worth a SQLite commit. */
39716
+ volatileStateFields: ["lastChangedAt"]
39524
39717
  };
39525
39718
  var HvacModeSchema = zod.z.enum([
39526
39719
  "off",
@@ -39649,7 +39842,14 @@ var require_dist_BV5Bug9t = __commonJS({
39649
39842
  * the full slice via `device.state.climate-control.value` and refresh
39650
39843
  * on every push without re-querying the provider.
39651
39844
  */
39652
- runtimeState: ClimateControlStatusSchema
39845
+ runtimeState: ClimateControlStatusSchema,
39846
+ /**
39847
+ * Runtime-state durability: **session** — as `brightness`; `currentTemp` moves continuously and is re-published on connect.
39848
+ *
39849
+ * See `RuntimeStateDurability`. Enforced by
39850
+ * `scripts/check-runtime-state-durability.ts`.
39851
+ */
39852
+ durability: "session"
39653
39853
  };
39654
39854
  var RgbTripletSchema = zod.z.object({
39655
39855
  r: zod.z.number().int().min(0).max(255),
@@ -39736,7 +39936,14 @@ var require_dist_BV5Bug9t = __commonJS({
39736
39936
  * kernel. Read via `device.state.color.value` so UI pickers surface
39737
39937
  * the current chromaticity without polling the provider.
39738
39938
  */
39739
- runtimeState: ColorStatusSchema
39939
+ runtimeState: ColorStatusSchema,
39940
+ /**
39941
+ * Runtime-state durability: **session** — as `brightness`.
39942
+ *
39943
+ * See `RuntimeStateDurability`. Enforced by
39944
+ * `scripts/check-runtime-state-durability.ts`.
39945
+ */
39946
+ durability: "session"
39740
39947
  };
39741
39948
  var ConnectionTestOutcomeSchema = zod.z.discriminatedUnion("outcome", [
39742
39949
  zod.z.object({
@@ -39790,7 +39997,17 @@ var require_dist_BV5Bug9t = __commonJS({
39790
39997
  schema: ConnectivityStatusSchema,
39791
39998
  kind: "push"
39792
39999
  },
39793
- runtimeState: ConnectivityStatusSchema
40000
+ runtimeState: ConnectivityStatusSchema,
40001
+ /**
40002
+ * Runtime-state durability: **restored** — same shape and same argument as `device-status`, for links rather than devices.
40003
+ *
40004
+ * See `RuntimeStateDurability`. Enforced by
40005
+ * `scripts/check-runtime-state-durability.ts`.
40006
+ */
40007
+ durability: "restored",
40008
+ /** Clock fields: written, but excluded from the compare that decides
40009
+ * whether persisting is worth a SQLite commit. */
40010
+ volatileStateFields: ["lastChangedAt"]
39794
40011
  };
39795
40012
  var ConsumableItemSchema = zod.z.object({
39796
40013
  /** Stable id, e.g. 'main-brush'. */
@@ -39857,7 +40074,14 @@ var require_dist_BV5Bug9t = __commonJS({
39857
40074
  }
39858
40075
  }
39859
40076
  },
39860
- runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() })
40077
+ runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() }),
40078
+ /**
40079
+ * Runtime-state durability: **session** — the authority is the appliance; the provider re-reads the whole item array on connect.
40080
+ *
40081
+ * See `RuntimeStateDurability`. Enforced by
40082
+ * `scripts/check-runtime-state-durability.ts`.
40083
+ */
40084
+ durability: "session"
39861
40085
  };
39862
40086
  var ContactStatusSchema = zod.z.object({
39863
40087
  /** True when the entry is open; false when closed. */
@@ -39876,7 +40100,17 @@ var require_dist_BV5Bug9t = __commonJS({
39876
40100
  schema: ContactStatusSchema,
39877
40101
  kind: "push"
39878
40102
  },
39879
- runtimeState: ContactStatusSchema
40103
+ runtimeState: ContactStatusSchema,
40104
+ /**
40105
+ * Runtime-state durability: **restored** — a door left open across a restart must still read open.
40106
+ *
40107
+ * See `RuntimeStateDurability`. Enforced by
40108
+ * `scripts/check-runtime-state-durability.ts`.
40109
+ */
40110
+ durability: "restored",
40111
+ /** Clock fields: written, but excluded from the compare that decides
40112
+ * whether persisting is worth a SQLite commit. */
40113
+ volatileStateFields: ["lastChangedAt"]
39880
40114
  };
39881
40115
  var ControlKindSchema = zod.z.enum([
39882
40116
  "numeric",
@@ -39963,7 +40197,14 @@ var require_dist_BV5Bug9t = __commonJS({
39963
40197
  * dropdown / text field / date picker) read the slice's discriminant
39964
40198
  * and value directly without polling the provider.
39965
40199
  */
39966
- runtimeState: ControlStatusSchema
40200
+ runtimeState: ControlStatusSchema,
40201
+ /**
40202
+ * Runtime-state durability: **session** — a generic control mirrors an external entity that re-publishes on connect; the options array is re-derived with it.
40203
+ *
40204
+ * See `RuntimeStateDurability`. Enforced by
40205
+ * `scripts/check-runtime-state-durability.ts`.
40206
+ */
40207
+ durability: "session"
39967
40208
  };
39968
40209
  var CoverStateSchema = zod.z.enum([
39969
40210
  "open",
@@ -40025,7 +40266,17 @@ var require_dist_BV5Bug9t = __commonJS({
40025
40266
  * Runtime-state slice — mirrored by the kernel. UI controls watch
40026
40267
  * the slice for live position changes during a move.
40027
40268
  */
40028
- runtimeState: CoverStatusSchema
40269
+ runtimeState: CoverStatusSchema,
40270
+ /**
40271
+ * Runtime-state durability: **restored** — position survives a restart on the device; the mirror should agree at boot rather than read blank.
40272
+ *
40273
+ * See `RuntimeStateDurability`. Enforced by
40274
+ * `scripts/check-runtime-state-durability.ts`.
40275
+ */
40276
+ durability: "restored",
40277
+ /** Clock fields: written, but excluded from the compare that decides
40278
+ * whether persisting is worth a SQLite commit. */
40279
+ volatileStateFields: ["lastChangedAt"]
40029
40280
  };
40030
40281
  var DayNightModeSchema = zod.z.enum([
40031
40282
  "auto",
@@ -40086,7 +40337,17 @@ var require_dist_BV5Bug9t = __commonJS({
40086
40337
  schema: DayNightStatusSchema,
40087
40338
  kind: "poll"
40088
40339
  },
40089
- runtimeState: DayNightStatusSchema
40340
+ runtimeState: DayNightStatusSchema,
40341
+ /**
40342
+ * Runtime-state durability: **restored** — operator-set IR-cut behaviour; mutation-driven.
40343
+ *
40344
+ * See `RuntimeStateDurability`. Enforced by
40345
+ * `scripts/check-runtime-state-durability.ts`.
40346
+ */
40347
+ durability: "restored",
40348
+ /** Clock fields: written, but excluded from the compare that decides
40349
+ * whether persisting is worth a SQLite commit. */
40350
+ volatileStateFields: ["lastFetchedAt"]
40090
40351
  };
40091
40352
  var DeviceStatusSchema = zod.z.object({
40092
40353
  /**
@@ -40119,7 +40380,17 @@ var require_dist_BV5Bug9t = __commonJS({
40119
40380
  schema: DeviceStatusSchema,
40120
40381
  kind: "push"
40121
40382
  },
40122
- runtimeState: DeviceStatusSchema
40383
+ runtimeState: DeviceStatusSchema,
40384
+ /**
40385
+ * 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.
40386
+ *
40387
+ * See `RuntimeStateDurability`. Enforced by
40388
+ * `scripts/check-runtime-state-durability.ts`.
40389
+ */
40390
+ durability: "restored",
40391
+ /** Clock fields: written, but excluded from the compare that decides
40392
+ * whether persisting is worth a SQLite commit. */
40393
+ volatileStateFields: ["lastChangedAt"]
40123
40394
  };
40124
40395
  var DoorbellStatusSchema = zod.z.object({
40125
40396
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -40159,7 +40430,14 @@ var require_dist_BV5Bug9t = __commonJS({
40159
40430
  * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
40160
40431
  * without subscribing.
40161
40432
  */
40162
- runtimeState: DoorbellStatusSchema
40433
+ runtimeState: DoorbellStatusSchema,
40434
+ /**
40435
+ * 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.
40436
+ *
40437
+ * See `RuntimeStateDurability`. Enforced by
40438
+ * `scripts/check-runtime-state-durability.ts`.
40439
+ */
40440
+ durability: "restored"
40163
40441
  };
40164
40442
  var EnumSensorDateTimeFormatSchema = zod.z.enum([
40165
40443
  "date",
@@ -40189,7 +40467,17 @@ var require_dist_BV5Bug9t = __commonJS({
40189
40467
  schema: EnumSensorStatusSchema,
40190
40468
  kind: "push"
40191
40469
  },
40192
- runtimeState: EnumSensorStatusSchema
40470
+ runtimeState: EnumSensorStatusSchema,
40471
+ /**
40472
+ * Runtime-state durability: **restored** — as `numeric-sensor`; 80 devices.
40473
+ *
40474
+ * See `RuntimeStateDurability`. Enforced by
40475
+ * `scripts/check-runtime-state-durability.ts`.
40476
+ */
40477
+ durability: "restored",
40478
+ /** Clock fields: written, but excluded from the compare that decides
40479
+ * whether persisting is worth a SQLite commit. */
40480
+ volatileStateFields: ["lastFetchedAt"]
40193
40481
  };
40194
40482
  var EventFireSchema = zod.z.object({
40195
40483
  deviceId: zod.z.number(),
@@ -40215,7 +40503,14 @@ var require_dist_BV5Bug9t = __commonJS({
40215
40503
  schema: EventEmitterStatusSchema,
40216
40504
  kind: "push"
40217
40505
  },
40218
- runtimeState: EventEmitterStatusSchema
40506
+ runtimeState: EventEmitterStatusSchema,
40507
+ /**
40508
+ * Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
40509
+ *
40510
+ * See `RuntimeStateDurability`. Enforced by
40511
+ * `scripts/check-runtime-state-durability.ts`.
40512
+ */
40513
+ durability: "session"
40219
40514
  };
40220
40515
  var EventItemSchema = zod.z.object({
40221
40516
  id: zod.z.string(),
@@ -40514,7 +40809,14 @@ var require_dist_BV5Bug9t = __commonJS({
40514
40809
  * Runtime-state slice — mirrored by the kernel. UI fan speed
40515
40810
  * sliders read `percentage` for live updates.
40516
40811
  */
40517
- runtimeState: FanControlStatusSchema
40812
+ runtimeState: FanControlStatusSchema,
40813
+ /**
40814
+ * Runtime-state durability: **session** — as `brightness`.
40815
+ *
40816
+ * See `RuntimeStateDurability`. Enforced by
40817
+ * `scripts/check-runtime-state-durability.ts`.
40818
+ */
40819
+ durability: "session"
40518
40820
  };
40519
40821
  var FeatureProbeStatusSchema = zod.z.object({
40520
40822
  /**
@@ -40567,7 +40869,14 @@ var require_dist_BV5Bug9t = __commonJS({
40567
40869
  schema: FeatureProbeStatusSchema,
40568
40870
  kind: "push"
40569
40871
  },
40570
- runtimeState: FeatureProbeStatusSchema
40872
+ runtimeState: FeatureProbeStatusSchema,
40873
+ /**
40874
+ * 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.
40875
+ *
40876
+ * See `RuntimeStateDurability`. Enforced by
40877
+ * `scripts/check-runtime-state-durability.ts`.
40878
+ */
40879
+ durability: "session"
40571
40880
  };
40572
40881
  var FloodStatusSchema = zod.z.object({
40573
40882
  /** True when leak is currently detected. */
@@ -40586,7 +40895,17 @@ var require_dist_BV5Bug9t = __commonJS({
40586
40895
  schema: FloodStatusSchema,
40587
40896
  kind: "push"
40588
40897
  },
40589
- runtimeState: FloodStatusSchema
40898
+ runtimeState: FloodStatusSchema,
40899
+ /**
40900
+ * Runtime-state durability: **restored** — as `smoke`.
40901
+ *
40902
+ * See `RuntimeStateDurability`. Enforced by
40903
+ * `scripts/check-runtime-state-durability.ts`.
40904
+ */
40905
+ durability: "restored",
40906
+ /** Clock fields: written, but excluded from the compare that decides
40907
+ * whether persisting is worth a SQLite commit. */
40908
+ volatileStateFields: ["lastChangedAt"]
40590
40909
  };
40591
40910
  var GasStatusSchema = zod.z.object({
40592
40911
  detected: zod.z.boolean(),
@@ -40604,7 +40923,17 @@ var require_dist_BV5Bug9t = __commonJS({
40604
40923
  schema: GasStatusSchema,
40605
40924
  kind: "push"
40606
40925
  },
40607
- runtimeState: GasStatusSchema
40926
+ runtimeState: GasStatusSchema,
40927
+ /**
40928
+ * Runtime-state durability: **restored** — as `smoke`.
40929
+ *
40930
+ * See `RuntimeStateDurability`. Enforced by
40931
+ * `scripts/check-runtime-state-durability.ts`.
40932
+ */
40933
+ durability: "restored",
40934
+ /** Clock fields: written, but excluded from the compare that decides
40935
+ * whether persisting is worth a SQLite commit. */
40936
+ volatileStateFields: ["lastChangedAt"]
40608
40937
  };
40609
40938
  var HumidifierStatusSchema = zod.z.object({
40610
40939
  /** Whether the humidifier is currently on. */
@@ -40665,7 +40994,14 @@ var require_dist_BV5Bug9t = __commonJS({
40665
40994
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
40666
40995
  * slice for live humidity / mode changes.
40667
40996
  */
40668
- runtimeState: HumidifierStatusSchema
40997
+ runtimeState: HumidifierStatusSchema,
40998
+ /**
40999
+ * Runtime-state durability: **session** — as `climate-control`.
41000
+ *
41001
+ * See `RuntimeStateDurability`. Enforced by
41002
+ * `scripts/check-runtime-state-durability.ts`.
41003
+ */
41004
+ durability: "session"
40669
41005
  };
40670
41006
  var HumiditySensorStatusSchema = zod.z.object({
40671
41007
  /** Current relative humidity, 0..100. */
@@ -40693,7 +41029,17 @@ var require_dist_BV5Bug9t = __commonJS({
40693
41029
  schema: HumiditySensorStatusSchema,
40694
41030
  kind: "push"
40695
41031
  },
40696
- runtimeState: HumiditySensorStatusSchema
41032
+ runtimeState: HumiditySensorStatusSchema,
41033
+ /**
41034
+ * Runtime-state durability: **restored** — as `numeric-sensor` (67 of 75 writes were the clock alone).
41035
+ *
41036
+ * See `RuntimeStateDurability`. Enforced by
41037
+ * `scripts/check-runtime-state-durability.ts`.
41038
+ */
41039
+ durability: "restored",
41040
+ /** Clock fields: written, but excluded from the compare that decides
41041
+ * whether persisting is worth a SQLite commit. */
41042
+ volatileStateFields: ["lastFetchedAt"]
40697
41043
  };
40698
41044
  var ImageStatusSchema = zod.z.object({
40699
41045
  /** Absolute signed URL the browser loads directly. Null when the
@@ -40717,7 +41063,14 @@ var require_dist_BV5Bug9t = __commonJS({
40717
41063
  * Runtime-state slice — mirrored by the kernel. The UI reads `url`
40718
41064
  * directly and renders the still image.
40719
41065
  */
40720
- runtimeState: ImageStatusSchema
41066
+ runtimeState: ImageStatusSchema,
41067
+ /**
41068
+ * Runtime-state durability: **session** — a snapshot URL is a session-scoped handle; a restored one points at nothing.
41069
+ *
41070
+ * See `RuntimeStateDurability`. Enforced by
41071
+ * `scripts/check-runtime-state-durability.ts`.
41072
+ */
41073
+ durability: "session"
40721
41074
  };
40722
41075
  var ImageRotateSchema = zod.z.enum([
40723
41076
  "0",
@@ -40818,7 +41171,17 @@ var require_dist_BV5Bug9t = __commonJS({
40818
41171
  schema: ImageSettingsStatusSchema,
40819
41172
  kind: "poll"
40820
41173
  },
40821
- runtimeState: ImageSettingsStatusSchema
41174
+ runtimeState: ImageSettingsStatusSchema,
41175
+ /**
41176
+ * Runtime-state durability: **restored** — operator-set camera imaging; mutation-driven.
41177
+ *
41178
+ * See `RuntimeStateDurability`. Enforced by
41179
+ * `scripts/check-runtime-state-durability.ts`.
41180
+ */
41181
+ durability: "restored",
41182
+ /** Clock fields: written, but excluded from the compare that decides
41183
+ * whether persisting is worth a SQLite commit. */
41184
+ volatileStateFields: ["lastFetchedAt"]
40822
41185
  };
40823
41186
  var IntegrationWithStateSchema = zod.z.object({
40824
41187
  id: zod.z.string(),
@@ -41149,7 +41512,14 @@ var require_dist_BV5Bug9t = __commonJS({
41149
41512
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
41150
41513
  * slice for live activity + battery changes.
41151
41514
  */
41152
- runtimeState: LawnMowerControlStatusSchema
41515
+ runtimeState: LawnMowerControlStatusSchema,
41516
+ /**
41517
+ * Runtime-state durability: **session** — as `vacuum-control`.
41518
+ *
41519
+ * See `RuntimeStateDurability`. Enforced by
41520
+ * `scripts/check-runtime-state-durability.ts`.
41521
+ */
41522
+ durability: "session"
41153
41523
  };
41154
41524
  var InterfaceKindEnum = zod.z.enum([
41155
41525
  "lan",
@@ -41398,7 +41768,17 @@ var require_dist_BV5Bug9t = __commonJS({
41398
41768
  * read `state` and disable themselves during `locking`/`unlocking`
41399
41769
  * transitions.
41400
41770
  */
41401
- runtimeState: LockControlStatusSchema
41771
+ runtimeState: LockControlStatusSchema,
41772
+ /**
41773
+ * Runtime-state durability: **restored** — a lock left locked must still read locked.
41774
+ *
41775
+ * See `RuntimeStateDurability`. Enforced by
41776
+ * `scripts/check-runtime-state-durability.ts`.
41777
+ */
41778
+ durability: "restored",
41779
+ /** Clock fields: written, but excluded from the compare that decides
41780
+ * whether persisting is worth a SQLite commit. */
41781
+ volatileStateFields: ["lastChangedAt"]
41402
41782
  };
41403
41783
  var MediaPlayerStateSchema = zod.z.enum([
41404
41784
  "off",
@@ -41548,7 +41928,14 @@ var require_dist_BV5Bug9t = __commonJS({
41548
41928
  * full slice for live now-playing, volume, and progress updates
41549
41929
  * without polling.
41550
41930
  */
41551
- runtimeState: MediaPlayerStatusSchema
41931
+ runtimeState: MediaPlayerStatusSchema,
41932
+ /**
41933
+ * Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
41934
+ *
41935
+ * See `RuntimeStateDurability`. Enforced by
41936
+ * `scripts/check-runtime-state-durability.ts`.
41937
+ */
41938
+ durability: "session"
41552
41939
  };
41553
41940
  var MeshEndpointSchema = zod.z.object({
41554
41941
  /** Stable identifier within the provider (e.g. `mesh-ipv4`, `magicdns`, `funnel`). */
@@ -41832,7 +42219,14 @@ var require_dist_BV5Bug9t = __commonJS({
41832
42219
  * `device.state.motion.value`. Reads never invoke the provider, so
41833
42220
  * UIs and other addons can poll the cached state safely.
41834
42221
  */
41835
- runtimeState: MotionStatusSchema
42222
+ runtimeState: MotionStatusSchema,
42223
+ /**
42224
+ * 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.
42225
+ *
42226
+ * See `RuntimeStateDurability`. Enforced by
42227
+ * `scripts/check-runtime-state-durability.ts`.
42228
+ */
42229
+ durability: "session"
41836
42230
  };
41837
42231
  var MotionTriggerStatusSchema = zod.z.object({
41838
42232
  enabled: zod.z.boolean(),
@@ -41869,7 +42263,14 @@ var require_dist_BV5Bug9t = __commonJS({
41869
42263
  schema: MotionTriggerStatusSchema,
41870
42264
  kind: "command-driven"
41871
42265
  },
41872
- runtimeState: MotionTriggerRuntimeStateSchema
42266
+ runtimeState: MotionTriggerRuntimeStateSchema,
42267
+ /**
42268
+ * 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.
42269
+ *
42270
+ * See `RuntimeStateDurability`. Enforced by
42271
+ * `scripts/check-runtime-state-durability.ts`.
42272
+ */
42273
+ durability: "session"
41873
42274
  };
41874
42275
  var MotionZoneRegionSchema = zod.z.object({
41875
42276
  id: zod.z.number(),
@@ -41924,7 +42325,17 @@ var require_dist_BV5Bug9t = __commonJS({
41924
42325
  schema: MotionZoneStatusSchema,
41925
42326
  kind: "poll"
41926
42327
  },
41927
- runtimeState: MotionZoneStatusSchema
42328
+ runtimeState: MotionZoneStatusSchema,
42329
+ /**
42330
+ * 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.
42331
+ *
42332
+ * See `RuntimeStateDurability`. Enforced by
42333
+ * `scripts/check-runtime-state-durability.ts`.
42334
+ */
42335
+ durability: "restored",
42336
+ /** Clock fields: written, but excluded from the compare that decides
42337
+ * whether persisting is worth a SQLite commit. */
42338
+ volatileStateFields: ["lastFetchedAt"]
41928
42339
  };
41929
42340
  var NativeObjectClassEnum = zod.z.enum([
41930
42341
  "person",
@@ -41987,7 +42398,17 @@ var require_dist_BV5Bug9t = __commonJS({
41987
42398
  schema: NativeObjectDetectionStatusSchema,
41988
42399
  kind: "push"
41989
42400
  },
41990
- runtimeState: NativeObjectDetectionRuntimeStateSchema
42401
+ runtimeState: NativeObjectDetectionRuntimeStateSchema,
42402
+ /**
42403
+ * 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.
42404
+ *
42405
+ * See `RuntimeStateDurability`. Enforced by
42406
+ * `scripts/check-runtime-state-durability.ts`.
42407
+ */
42408
+ durability: "restored",
42409
+ /** Clock fields: written, but excluded from the compare that decides
42410
+ * whether persisting is worth a SQLite commit. */
42411
+ volatileStateFields: ["lastFetchedAt"]
41991
42412
  };
41992
42413
  var StreamNetworkStatsSchema = zod.z.object({
41993
42414
  nominalBitrateKbps: zod.z.number(),
@@ -42310,7 +42731,14 @@ var require_dist_BV5Bug9t = __commonJS({
42310
42731
  * form reads `supports` to gate optional fields; history pane reads
42311
42732
  * `lastSentAt` / `lastError` / `queueDepth`.
42312
42733
  */
42313
- runtimeState: NotifierStatusSchema
42734
+ runtimeState: NotifierStatusSchema,
42735
+ /**
42736
+ * Runtime-state durability: **session** — live queue depth and last-send state; a restored queue depth describes a queue that no longer exists.
42737
+ *
42738
+ * See `RuntimeStateDurability`. Enforced by
42739
+ * `scripts/check-runtime-state-durability.ts`.
42740
+ */
42741
+ durability: "session"
42314
42742
  };
42315
42743
  var NumericSensorStatusSchema = zod.z.object({
42316
42744
  value: zod.z.number(),
@@ -42336,7 +42764,17 @@ var require_dist_BV5Bug9t = __commonJS({
42336
42764
  schema: NumericSensorStatusSchema,
42337
42765
  kind: "push"
42338
42766
  },
42339
- runtimeState: NumericSensorStatusSchema
42767
+ runtimeState: NumericSensorStatusSchema,
42768
+ /**
42769
+ * 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.
42770
+ *
42771
+ * See `RuntimeStateDurability`. Enforced by
42772
+ * `scripts/check-runtime-state-durability.ts`.
42773
+ */
42774
+ durability: "restored",
42775
+ /** Clock fields: written, but excluded from the compare that decides
42776
+ * whether persisting is worth a SQLite commit. */
42777
+ volatileStateFields: ["lastFetchedAt"]
42340
42778
  };
42341
42779
  var OsdOverlayKindEnum = zod.z.enum([
42342
42780
  "text",
@@ -42759,7 +43197,14 @@ var require_dist_BV5Bug9t = __commonJS({
42759
43197
  * the full slice via `device.state.petFeeder.value` and refresh on
42760
43198
  * every poll without re-querying the provider.
42761
43199
  */
42762
- runtimeState: PetFeederStatusSchema
43200
+ runtimeState: PetFeederStatusSchema,
43201
+ /**
43202
+ * Runtime-state durability: **session** — live appliance state re-published on connect.
43203
+ *
43204
+ * See `RuntimeStateDurability`. Enforced by
43205
+ * `scripts/check-runtime-state-durability.ts`.
43206
+ */
43207
+ durability: "session"
42763
43208
  };
42764
43209
  var VehicleSchema = zod.z.object({
42765
43210
  id: zod.z.string(),
@@ -43093,7 +43538,17 @@ var require_dist_BV5Bug9t = __commonJS({
43093
43538
  schema: PowerMeterStatusSchema,
43094
43539
  kind: "push"
43095
43540
  },
43096
- runtimeState: PowerMeterStatusSchema
43541
+ runtimeState: PowerMeterStatusSchema,
43542
+ /**
43543
+ * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
43544
+ *
43545
+ * See `RuntimeStateDurability`. Enforced by
43546
+ * `scripts/check-runtime-state-durability.ts`.
43547
+ */
43548
+ durability: "restored",
43549
+ /** Clock fields: written, but excluded from the compare that decides
43550
+ * whether persisting is worth a SQLite commit. */
43551
+ volatileStateFields: ["lastFetchedAt"]
43097
43552
  };
43098
43553
  var GpsLocationSchema = zod.z.object({
43099
43554
  /** Latitude in decimal degrees, -90..90. */
@@ -43134,7 +43589,17 @@ var require_dist_BV5Bug9t = __commonJS({
43134
43589
  * the map pin is rendered (use `DeviceFeature.PresenceGps` for the
43135
43590
  * pre-fetch fast-path check).
43136
43591
  */
43137
- runtimeState: PresenceStatusSchema
43592
+ runtimeState: PresenceStatusSchema,
43593
+ /**
43594
+ * Runtime-state durability: **restored** — occupancy-relevant: the restored state is what an occupancy rule compares the first post-restart observation against.
43595
+ *
43596
+ * See `RuntimeStateDurability`. Enforced by
43597
+ * `scripts/check-runtime-state-durability.ts`.
43598
+ */
43599
+ durability: "restored",
43600
+ /** Clock fields: written, but excluded from the compare that decides
43601
+ * whether persisting is worth a SQLite commit. */
43602
+ volatileStateFields: ["lastChangedAt"]
43138
43603
  };
43139
43604
  var PressureSensorStatusSchema = zod.z.object({
43140
43605
  /** Current pressure in hPa. */
@@ -43162,7 +43627,17 @@ var require_dist_BV5Bug9t = __commonJS({
43162
43627
  schema: PressureSensorStatusSchema,
43163
43628
  kind: "push"
43164
43629
  },
43165
- runtimeState: PressureSensorStatusSchema
43630
+ runtimeState: PressureSensorStatusSchema,
43631
+ /**
43632
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
43633
+ *
43634
+ * See `RuntimeStateDurability`. Enforced by
43635
+ * `scripts/check-runtime-state-durability.ts`.
43636
+ */
43637
+ durability: "restored",
43638
+ /** Clock fields: written, but excluded from the compare that decides
43639
+ * whether persisting is worth a SQLite commit. */
43640
+ volatileStateFields: ["lastFetchedAt"]
43166
43641
  };
43167
43642
  var PrivacyMaskShapeSchema = zod.z.discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
43168
43643
  var PrivacyMaskRegionSchema = zod.z.object({
@@ -43261,7 +43736,17 @@ var require_dist_BV5Bug9t = __commonJS({
43261
43736
  schema: PrivacyMaskStatusSchema,
43262
43737
  kind: "poll"
43263
43738
  },
43264
- runtimeState: PrivacyMaskStatusSchema
43739
+ runtimeState: PrivacyMaskStatusSchema,
43740
+ /**
43741
+ * Runtime-state durability: **restored** — operator-drawn regions, zero real churn — 22 writes in 25 minutes, every one of them the clock.
43742
+ *
43743
+ * See `RuntimeStateDurability`. Enforced by
43744
+ * `scripts/check-runtime-state-durability.ts`.
43745
+ */
43746
+ durability: "restored",
43747
+ /** Clock fields: written, but excluded from the compare that decides
43748
+ * whether persisting is worth a SQLite commit. */
43749
+ volatileStateFields: ["lastFetchedAt"]
43265
43750
  };
43266
43751
  var PtzPresetSchema = zod.z.object({
43267
43752
  id: zod.z.string(),
@@ -43447,7 +43932,14 @@ var require_dist_BV5Bug9t = __commonJS({
43447
43932
  * fetch / cache / fallback logic out of the four cap methods —
43448
43933
  * they become trampolines over `runtimeState`.
43449
43934
  */
43450
- runtimeState: PtzAutotrackRuntimeStateSchema
43935
+ runtimeState: PtzAutotrackRuntimeStateSchema,
43936
+ /**
43937
+ * Runtime-state durability: **session** — mirrors the camera's own autotrack config, re-read on connect.
43938
+ *
43939
+ * See `RuntimeStateDurability`. Enforced by
43940
+ * `scripts/check-runtime-state-durability.ts`.
43941
+ */
43942
+ durability: "session"
43451
43943
  };
43452
43944
  var rebootCapability = {
43453
43945
  name: "reboot",
@@ -44115,7 +44607,14 @@ var require_dist_BV5Bug9t = __commonJS({
44115
44607
  schema: SceneMonitorStatusSchema,
44116
44608
  kind: "push"
44117
44609
  },
44118
- runtimeState: SceneMonitorStatusSchema
44610
+ runtimeState: SceneMonitorStatusSchema,
44611
+ /**
44612
+ * Runtime-state durability: **session** — re-derived from the current scene on the next evaluation.
44613
+ *
44614
+ * See `RuntimeStateDurability`. Enforced by
44615
+ * `scripts/check-runtime-state-durability.ts`.
44616
+ */
44617
+ durability: "session"
44119
44618
  };
44120
44619
  var ZoneRuleModeEnum = zod.z.enum(["include", "exclude"]);
44121
44620
  var ZoneRuleSchema = zod.z.object({
@@ -44217,7 +44716,14 @@ var require_dist_BV5Bug9t = __commonJS({
44217
44716
  * `isRunning` to render a spinner during execution and surfaces
44218
44717
  * `lastError` / `lastRunSuccess` in the recent-runs panel.
44219
44718
  */
44220
- runtimeState: ScriptRunnerStatusSchema
44719
+ runtimeState: ScriptRunnerStatusSchema,
44720
+ /**
44721
+ * Runtime-state durability: **session** — a restored `isRunning: true` describes a process that died with the previous hub.
44722
+ *
44723
+ * See `RuntimeStateDurability`. Enforced by
44724
+ * `scripts/check-runtime-state-durability.ts`.
44725
+ */
44726
+ durability: "session"
44221
44727
  };
44222
44728
  var SmokeStatusSchema = zod.z.object({
44223
44729
  detected: zod.z.boolean(),
@@ -44235,7 +44741,17 @@ var require_dist_BV5Bug9t = __commonJS({
44235
44741
  schema: SmokeStatusSchema,
44236
44742
  kind: "push"
44237
44743
  },
44238
- runtimeState: SmokeStatusSchema
44744
+ runtimeState: SmokeStatusSchema,
44745
+ /**
44746
+ * Runtime-state durability: **restored** — a safety sensor must not read "clear" merely because the hub restarted.
44747
+ *
44748
+ * See `RuntimeStateDurability`. Enforced by
44749
+ * `scripts/check-runtime-state-durability.ts`.
44750
+ */
44751
+ durability: "restored",
44752
+ /** Clock fields: written, but excluded from the compare that decides
44753
+ * whether persisting is worth a SQLite commit. */
44754
+ volatileStateFields: ["lastChangedAt"]
44239
44755
  };
44240
44756
  var CamStreamDescriptorSchema = zod.z.object({
44241
44757
  camStreamId: zod.z.string().min(1),
@@ -44386,7 +44902,17 @@ var require_dist_BV5Bug9t = __commonJS({
44386
44902
  schema: StreamParamsStatusSchema,
44387
44903
  kind: "poll"
44388
44904
  },
44389
- runtimeState: StreamParamsStatusSchema
44905
+ runtimeState: StreamParamsStatusSchema,
44906
+ /**
44907
+ * Runtime-state durability: **restored** — operator-set encoder profile; mutation-driven.
44908
+ *
44909
+ * See `RuntimeStateDurability`. Enforced by
44910
+ * `scripts/check-runtime-state-durability.ts`.
44911
+ */
44912
+ durability: "restored",
44913
+ /** Clock fields: written, but excluded from the compare that decides
44914
+ * whether persisting is worth a SQLite commit. */
44915
+ volatileStateFields: ["lastFetchedAt"]
44390
44916
  };
44391
44917
  var STREAM_PROFILE_META = [
44392
44918
  {
@@ -44657,6 +45183,16 @@ var require_dist_BV5Bug9t = __commonJS({
44657
45183
  * not need to re-query the provider after a setState mutation.
44658
45184
  */
44659
45185
  runtimeState: SwitchStatusSchema,
45186
+ /**
45187
+ * Runtime-state durability: **restored** — device state an operator reads as authoritative; 55 devices, transition-driven.
45188
+ *
45189
+ * See `RuntimeStateDurability`. Enforced by
45190
+ * `scripts/check-runtime-state-durability.ts`.
45191
+ */
45192
+ durability: "restored",
45193
+ /** Clock fields: written, but excluded from the compare that decides
45194
+ * whether persisting is worth a SQLite commit. */
45195
+ volatileStateFields: ["lastChangedAt"],
44660
45196
  settings: { bindings: [{
44661
45197
  kind: "scalar",
44662
45198
  statusPath: "on",
@@ -44729,7 +45265,17 @@ var require_dist_BV5Bug9t = __commonJS({
44729
45265
  schema: TamperStatusSchema,
44730
45266
  kind: "push"
44731
45267
  },
44732
- runtimeState: TamperStatusSchema
45268
+ runtimeState: TamperStatusSchema,
45269
+ /**
45270
+ * Runtime-state durability: **restored** — as `smoke`.
45271
+ *
45272
+ * See `RuntimeStateDurability`. Enforced by
45273
+ * `scripts/check-runtime-state-durability.ts`.
45274
+ */
45275
+ durability: "restored",
45276
+ /** Clock fields: written, but excluded from the compare that decides
45277
+ * whether persisting is worth a SQLite commit. */
45278
+ volatileStateFields: ["lastChangedAt"]
44733
45279
  };
44734
45280
  var TemperatureSensorStatusSchema = zod.z.object({
44735
45281
  /** Current temperature in Celsius. */
@@ -44758,7 +45304,17 @@ var require_dist_BV5Bug9t = __commonJS({
44758
45304
  schema: TemperatureSensorStatusSchema,
44759
45305
  kind: "push"
44760
45306
  },
44761
- runtimeState: TemperatureSensorStatusSchema
45307
+ runtimeState: TemperatureSensorStatusSchema,
45308
+ /**
45309
+ * Runtime-state durability: **restored** — as `numeric-sensor` (69 of 125 writes were the clock alone).
45310
+ *
45311
+ * See `RuntimeStateDurability`. Enforced by
45312
+ * `scripts/check-runtime-state-durability.ts`.
45313
+ */
45314
+ durability: "restored",
45315
+ /** Clock fields: written, but excluded from the compare that decides
45316
+ * whether persisting is worth a SQLite commit. */
45317
+ volatileStateFields: ["lastFetchedAt"]
44762
45318
  };
44763
45319
  var ToastSchema = zod.z.object({
44764
45320
  title: zod.z.string(),
@@ -44809,7 +45365,14 @@ var require_dist_BV5Bug9t = __commonJS({
44809
45365
  schema: UpdateStatusSchema,
44810
45366
  kind: "poll"
44811
45367
  },
44812
- runtimeState: UpdateStatusSchema
45368
+ runtimeState: UpdateStatusSchema,
45369
+ /**
45370
+ * Runtime-state durability: **session** — a restored `inProgress: true` describes an update that is no longer running; versions are re-probed at boot.
45371
+ *
45372
+ * See `RuntimeStateDurability`. Enforced by
45373
+ * `scripts/check-runtime-state-durability.ts`.
45374
+ */
45375
+ durability: "session"
44813
45376
  };
44814
45377
  var UserSummarySchema = zod.z.object({
44815
45378
  id: zod.z.string(),
@@ -45143,7 +45706,14 @@ var require_dist_BV5Bug9t = __commonJS({
45143
45706
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
45144
45707
  * slice for live state + battery + fan-speed changes.
45145
45708
  */
45146
- runtimeState: VacuumControlStatusSchema
45709
+ runtimeState: VacuumControlStatusSchema,
45710
+ /**
45711
+ * Runtime-state durability: **session** — as `media-player` — a restored `state: cleaning` is a robot that is not cleaning.
45712
+ *
45713
+ * See `RuntimeStateDurability`. Enforced by
45714
+ * `scripts/check-runtime-state-durability.ts`.
45715
+ */
45716
+ durability: "session"
45147
45717
  };
45148
45718
  var ValveStateSchema = zod.z.enum([
45149
45719
  "open",
@@ -45196,7 +45766,14 @@ var require_dist_BV5Bug9t = __commonJS({
45196
45766
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
45197
45767
  * slice for live position changes during a move.
45198
45768
  */
45199
- runtimeState: ValveStatusSchema
45769
+ runtimeState: ValveStatusSchema,
45770
+ /**
45771
+ * Runtime-state durability: **session** — as `brightness`.
45772
+ *
45773
+ * See `RuntimeStateDurability`. Enforced by
45774
+ * `scripts/check-runtime-state-durability.ts`.
45775
+ */
45776
+ durability: "session"
45200
45777
  };
45201
45778
  var VibrationStatusSchema = zod.z.object({
45202
45779
  detected: zod.z.boolean(),
@@ -45214,7 +45791,17 @@ var require_dist_BV5Bug9t = __commonJS({
45214
45791
  schema: VibrationStatusSchema,
45215
45792
  kind: "push"
45216
45793
  },
45217
- runtimeState: VibrationStatusSchema
45794
+ runtimeState: VibrationStatusSchema,
45795
+ /**
45796
+ * Runtime-state durability: **restored** — as `smoke`.
45797
+ *
45798
+ * See `RuntimeStateDurability`. Enforced by
45799
+ * `scripts/check-runtime-state-durability.ts`.
45800
+ */
45801
+ durability: "restored",
45802
+ /** Clock fields: written, but excluded from the compare that decides
45803
+ * whether persisting is worth a SQLite commit. */
45804
+ volatileStateFields: ["lastChangedAt"]
45218
45805
  };
45219
45806
  var WaterHeaterStatusSchema = zod.z.object({
45220
45807
  /** Current measured temperature. Null when not reported. */
@@ -45274,7 +45861,14 @@ var require_dist_BV5Bug9t = __commonJS({
45274
45861
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
45275
45862
  * slice for live temperature / mode / away changes.
45276
45863
  */
45277
- runtimeState: WaterHeaterStatusSchema
45864
+ runtimeState: WaterHeaterStatusSchema,
45865
+ /**
45866
+ * Runtime-state durability: **session** — as `climate-control`.
45867
+ *
45868
+ * See `RuntimeStateDurability`. Enforced by
45869
+ * `scripts/check-runtime-state-durability.ts`.
45870
+ */
45871
+ durability: "session"
45278
45872
  };
45279
45873
  var WeatherStatusSchema = zod.z.object({
45280
45874
  /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
@@ -45314,7 +45908,14 @@ var require_dist_BV5Bug9t = __commonJS({
45314
45908
  * Runtime-state slice — mirrored by the kernel. The UI reads the
45315
45909
  * current conditions directly from the slice on each weather push.
45316
45910
  */
45317
- runtimeState: WeatherStatusSchema
45911
+ runtimeState: WeatherStatusSchema,
45912
+ /**
45913
+ * Runtime-state durability: **session** — a forecast is stale the moment the hub is down; the provider re-fetches on connect.
45914
+ *
45915
+ * See `RuntimeStateDurability`. Enforced by
45916
+ * `scripts/check-runtime-state-durability.ts`.
45917
+ */
45918
+ durability: "session"
45318
45919
  };
45319
45920
  var PerScopeBreakdownSchema = zod.z.object({
45320
45921
  /** Total tracked objects in this scope (frame / zone / unzoned). */
@@ -45423,7 +46024,14 @@ var require_dist_BV5Bug9t = __commonJS({
45423
46024
  * automatically; the explicit `getCurrentSnapshot` cap method is
45424
46025
  * still useful for one-off polls without a subscription.
45425
46026
  */
45426
- runtimeState: CameraOccupancySnapshotSchema
46027
+ runtimeState: CameraOccupancySnapshotSchema,
46028
+ /**
46029
+ * Runtime-state durability: **session** — per-frame analytics; with `audio-metrics` it is ~90 % of the offered write rate. Re-derived on the next frame.
46030
+ *
46031
+ * See `RuntimeStateDurability`. Enforced by
46032
+ * `scripts/check-runtime-state-durability.ts`.
46033
+ */
46034
+ durability: "session"
45427
46035
  };
45428
46036
  var ZoneRuleStageEnum = zod.z.enum([
45429
46037
  "motion",
@@ -45475,7 +46083,14 @@ var require_dist_BV5Bug9t = __commonJS({
45475
46083
  motion: zod.z.array(ZoneRuleSchema).readonly(),
45476
46084
  detection: zod.z.array(ZoneRuleSchema).readonly(),
45477
46085
  package: zod.z.array(ZoneRuleSchema).readonly()
45478
- })
46086
+ }),
46087
+ /**
46088
+ * Runtime-state durability: **restored** — operator intent, mutation-only, same argument as `zones`.
46089
+ *
46090
+ * See `RuntimeStateDurability`. Enforced by
46091
+ * `scripts/check-runtime-state-durability.ts`.
46092
+ */
46093
+ durability: "restored"
45479
46094
  };
45480
46095
  var RUNTIME_DEFAULTS = {
45481
46096
  "features.streaming": true,
@@ -51618,6 +52233,264 @@ var require_dist_BV5Bug9t = __commonJS({
51618
52233
  "network-access": "ingress",
51619
52234
  "smtp-provider": "email"
51620
52235
  });
52236
+ var RUNTIME_STATE_POLICY = {
52237
+ "air-quality-sensor": {
52238
+ durability: "restored",
52239
+ volatileFields: ["lastFetchedAt"]
52240
+ },
52241
+ "alarm-panel": {
52242
+ durability: "restored",
52243
+ volatileFields: ["lastChangedAt"]
52244
+ },
52245
+ "ambient-light-sensor": {
52246
+ durability: "restored",
52247
+ volatileFields: ["lastFetchedAt"]
52248
+ },
52249
+ "audio-metrics": {
52250
+ durability: "session",
52251
+ volatileFields: []
52252
+ },
52253
+ "automation-control": {
52254
+ durability: "session",
52255
+ volatileFields: []
52256
+ },
52257
+ "battery": {
52258
+ durability: "restored",
52259
+ volatileFields: ["lastUpdated"]
52260
+ },
52261
+ "binary": {
52262
+ durability: "restored",
52263
+ volatileFields: ["lastChangedAt"]
52264
+ },
52265
+ "brightness": {
52266
+ durability: "session",
52267
+ volatileFields: []
52268
+ },
52269
+ "camera-streams": {
52270
+ durability: "session",
52271
+ volatileFields: []
52272
+ },
52273
+ "carbon-monoxide": {
52274
+ durability: "restored",
52275
+ volatileFields: ["lastChangedAt"]
52276
+ },
52277
+ "climate-control": {
52278
+ durability: "session",
52279
+ volatileFields: []
52280
+ },
52281
+ "color": {
52282
+ durability: "session",
52283
+ volatileFields: []
52284
+ },
52285
+ "connectivity": {
52286
+ durability: "restored",
52287
+ volatileFields: ["lastChangedAt"]
52288
+ },
52289
+ "consumables": {
52290
+ durability: "session",
52291
+ volatileFields: []
52292
+ },
52293
+ "contact": {
52294
+ durability: "restored",
52295
+ volatileFields: ["lastChangedAt"]
52296
+ },
52297
+ "control": {
52298
+ durability: "session",
52299
+ volatileFields: []
52300
+ },
52301
+ "cover": {
52302
+ durability: "restored",
52303
+ volatileFields: ["lastChangedAt"]
52304
+ },
52305
+ "day-night": {
52306
+ durability: "restored",
52307
+ volatileFields: ["lastFetchedAt"]
52308
+ },
52309
+ "device-discovery": {
52310
+ durability: "session",
52311
+ volatileFields: []
52312
+ },
52313
+ "device-status": {
52314
+ durability: "restored",
52315
+ volatileFields: ["lastChangedAt"]
52316
+ },
52317
+ "doorbell": {
52318
+ durability: "restored",
52319
+ volatileFields: []
52320
+ },
52321
+ "enum-sensor": {
52322
+ durability: "restored",
52323
+ volatileFields: ["lastFetchedAt"]
52324
+ },
52325
+ "event-emitter": {
52326
+ durability: "session",
52327
+ volatileFields: []
52328
+ },
52329
+ "fan-control": {
52330
+ durability: "session",
52331
+ volatileFields: []
52332
+ },
52333
+ "feature-probe": {
52334
+ durability: "session",
52335
+ volatileFields: []
52336
+ },
52337
+ "flood": {
52338
+ durability: "restored",
52339
+ volatileFields: ["lastChangedAt"]
52340
+ },
52341
+ "gas": {
52342
+ durability: "restored",
52343
+ volatileFields: ["lastChangedAt"]
52344
+ },
52345
+ "humidifier": {
52346
+ durability: "session",
52347
+ volatileFields: []
52348
+ },
52349
+ "humidity-sensor": {
52350
+ durability: "restored",
52351
+ volatileFields: ["lastFetchedAt"]
52352
+ },
52353
+ "image": {
52354
+ durability: "session",
52355
+ volatileFields: []
52356
+ },
52357
+ "image-settings": {
52358
+ durability: "restored",
52359
+ volatileFields: ["lastFetchedAt"]
52360
+ },
52361
+ "lawn-mower-control": {
52362
+ durability: "session",
52363
+ volatileFields: []
52364
+ },
52365
+ "lock-control": {
52366
+ durability: "restored",
52367
+ volatileFields: ["lastChangedAt"]
52368
+ },
52369
+ "media-player": {
52370
+ durability: "session",
52371
+ volatileFields: []
52372
+ },
52373
+ "motion": {
52374
+ durability: "session",
52375
+ volatileFields: []
52376
+ },
52377
+ "motion-trigger": {
52378
+ durability: "session",
52379
+ volatileFields: []
52380
+ },
52381
+ "motion-zones": {
52382
+ durability: "restored",
52383
+ volatileFields: ["lastFetchedAt"]
52384
+ },
52385
+ "native-object-detection": {
52386
+ durability: "restored",
52387
+ volatileFields: ["lastFetchedAt"]
52388
+ },
52389
+ "notifier": {
52390
+ durability: "session",
52391
+ volatileFields: []
52392
+ },
52393
+ "numeric-sensor": {
52394
+ durability: "restored",
52395
+ volatileFields: ["lastFetchedAt"]
52396
+ },
52397
+ "pet-feeder": {
52398
+ durability: "session",
52399
+ volatileFields: []
52400
+ },
52401
+ "power-meter": {
52402
+ durability: "restored",
52403
+ volatileFields: ["lastFetchedAt"]
52404
+ },
52405
+ "presence": {
52406
+ durability: "restored",
52407
+ volatileFields: ["lastChangedAt"]
52408
+ },
52409
+ "pressure-sensor": {
52410
+ durability: "restored",
52411
+ volatileFields: ["lastFetchedAt"]
52412
+ },
52413
+ "privacy-mask": {
52414
+ durability: "restored",
52415
+ volatileFields: ["lastFetchedAt"]
52416
+ },
52417
+ "ptz-autotrack": {
52418
+ durability: "session",
52419
+ volatileFields: []
52420
+ },
52421
+ "scene-monitor": {
52422
+ durability: "session",
52423
+ volatileFields: []
52424
+ },
52425
+ "script-runner": {
52426
+ durability: "session",
52427
+ volatileFields: []
52428
+ },
52429
+ "smoke": {
52430
+ durability: "restored",
52431
+ volatileFields: ["lastChangedAt"]
52432
+ },
52433
+ "stream-params": {
52434
+ durability: "restored",
52435
+ volatileFields: ["lastFetchedAt"]
52436
+ },
52437
+ "switch": {
52438
+ durability: "restored",
52439
+ volatileFields: ["lastChangedAt"]
52440
+ },
52441
+ "tamper": {
52442
+ durability: "restored",
52443
+ volatileFields: ["lastChangedAt"]
52444
+ },
52445
+ "temperature-sensor": {
52446
+ durability: "restored",
52447
+ volatileFields: ["lastFetchedAt"]
52448
+ },
52449
+ "update": {
52450
+ durability: "session",
52451
+ volatileFields: []
52452
+ },
52453
+ "vacuum-control": {
52454
+ durability: "session",
52455
+ volatileFields: []
52456
+ },
52457
+ "valve": {
52458
+ durability: "session",
52459
+ volatileFields: []
52460
+ },
52461
+ "vibration": {
52462
+ durability: "restored",
52463
+ volatileFields: ["lastChangedAt"]
52464
+ },
52465
+ "water-heater": {
52466
+ durability: "session",
52467
+ volatileFields: []
52468
+ },
52469
+ "weather": {
52470
+ durability: "session",
52471
+ volatileFields: []
52472
+ },
52473
+ "zone-analytics": {
52474
+ durability: "session",
52475
+ volatileFields: []
52476
+ },
52477
+ "zone-rules": {
52478
+ durability: "restored",
52479
+ volatileFields: []
52480
+ },
52481
+ "zones": {
52482
+ durability: "restored",
52483
+ volatileFields: []
52484
+ }
52485
+ };
52486
+ function runtimeStatePolicyFor(capName) {
52487
+ return RUNTIME_STATE_POLICY[capName] ?? SESSION_ONLY_POLICY;
52488
+ }
52489
+ var SESSION_ONLY_POLICY = {
52490
+ durability: "session",
52491
+ volatileFields: []
52492
+ };
52493
+ new Map(AUDIO_MACRO_LABELS.flatMap((macro2) => macro2.icon === void 0 ? [] : [[macro2.id, macro2.icon]]));
51621
52494
  var TimelapseTemplateSchema = zod.z.object({
51622
52495
  title: zod.z.string().max(500).optional(),
51623
52496
  body: zod.z.string().max(2e3).optional()
@@ -52435,6 +53308,12 @@ var require_dist_BV5Bug9t = __commonJS({
52435
53308
  return resolveCapMount;
52436
53309
  }
52437
53310
  });
53311
+ Object.defineProperty(exports, "runtimeStatePolicyFor", {
53312
+ enumerable: true,
53313
+ get: function() {
53314
+ return runtimeStatePolicyFor;
53315
+ }
53316
+ });
52438
53317
  Object.defineProperty(exports, "scopeKey", {
52439
53318
  enumerable: true,
52440
53319
  get: function() {
@@ -52531,7 +53410,7 @@ var require_alerts_addon = __commonJS({
52531
53410
  [Symbol.toStringTag]: { value: "Module" }
52532
53411
  });
52533
53412
  require_chunk_Cek0wNdY();
52534
- var require_dist10 = require_dist_BV5Bug9t();
53413
+ var require_dist10 = require_dist_B4WyhfjH();
52535
53414
  function selectExpired(alerts, cutoffMs) {
52536
53415
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
52537
53416
  }
@@ -53344,7 +54223,7 @@ var require_console_logging = __commonJS({
53344
54223
  [Symbol.toStringTag]: { value: "Module" }
53345
54224
  });
53346
54225
  require_chunk_Cek0wNdY();
53347
- var require_dist10 = require_dist_BV5Bug9t();
54226
+ var require_dist10 = require_dist_B4WyhfjH();
53348
54227
  var require_formatter = require_formatter_DqAKDlvN();
53349
54228
  var LEVEL_RANK = {
53350
54229
  debug: 0,
@@ -53438,7 +54317,7 @@ var require_core_blocks_addon = __commonJS({
53438
54317
  "use strict";
53439
54318
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
53440
54319
  var require_chunk = require_chunk_Cek0wNdY();
53441
- var require_dist10 = require_dist_BV5Bug9t();
54320
+ var require_dist10 = require_dist_B4WyhfjH();
53442
54321
  var node_crypto = __require("crypto");
53443
54322
  var node_fs_promises = __require("fs/promises");
53444
54323
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -56136,7 +57015,7 @@ var require_device_manager_addon = __commonJS({
56136
57015
  [Symbol.toStringTag]: { value: "Module" }
56137
57016
  });
56138
57017
  require_chunk_Cek0wNdY();
56139
- var require_dist10 = require_dist_BV5Bug9t();
57018
+ var require_dist10 = require_dist_B4WyhfjH();
56140
57019
  var node_crypto = __require("crypto");
56141
57020
  var _camstack_types_node = require_node();
56142
57021
  var JOB_HISTORY = 20;
@@ -59332,8 +60211,60 @@ var require_device_manager_addon = __commonJS({
59332
60211
  return current;
59333
60212
  };
59334
60213
  };
60214
+ function persistableBlob(blob, policyFor) {
60215
+ const out = {};
60216
+ for (const [capName, slice] of Object.entries(blob)) {
60217
+ if (policyFor(capName).durability !== "restored") continue;
60218
+ out[capName] = { ...slice };
60219
+ }
60220
+ return out;
60221
+ }
60222
+ function sliceEffectivelyEqual(a, b, volatileFields) {
60223
+ const skip = new Set(volatileFields);
60224
+ const keys = /* @__PURE__ */ new Set();
60225
+ for (const k of Object.keys(a)) if (!skip.has(k)) keys.add(k);
60226
+ for (const k of Object.keys(b)) if (!skip.has(k)) keys.add(k);
60227
+ for (const k of keys) {
60228
+ if (!deepEqual(a[k], b[k])) return false;
60229
+ if (Object.hasOwn(a, k) !== Object.hasOwn(b, k)) return false;
60230
+ }
60231
+ return true;
60232
+ }
60233
+ function effectivelyEqual(before, after, policyFor) {
60234
+ const caps = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
60235
+ for (const capName of caps) {
60236
+ const a = before[capName];
60237
+ const b = after[capName];
60238
+ if (a === void 0 || b === void 0) return false;
60239
+ if (!sliceEffectivelyEqual(a, b, policyFor(capName).volatileFields)) return false;
60240
+ }
60241
+ return true;
60242
+ }
60243
+ function deepEqual(a, b) {
60244
+ if (a === b) return true;
60245
+ if (typeof a === "number" && typeof b === "number") return Number.isNaN(a) && Number.isNaN(b);
60246
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
60247
+ const aIsArray = Array.isArray(a);
60248
+ if (aIsArray !== Array.isArray(b)) return false;
60249
+ if (aIsArray) {
60250
+ const bArr = b;
60251
+ if (a.length !== bArr.length) return false;
60252
+ for (let i = 0; i < a.length; i += 1) if (!deepEqual(a[i], bArr[i])) return false;
60253
+ return true;
60254
+ }
60255
+ const aRec = a;
60256
+ const bRec = b;
60257
+ const aKeys = Object.keys(aRec);
60258
+ if (aKeys.length !== Object.keys(bRec).length) return false;
60259
+ for (const k of aKeys) {
60260
+ if (!Object.hasOwn(bRec, k)) return false;
60261
+ if (!deepEqual(aRec[k], bRec[k])) return false;
60262
+ }
60263
+ return true;
60264
+ }
59335
60265
  var DeviceStateMirror = class DeviceStateMirror2 {
59336
60266
  ctx;
60267
+ policyFor;
59337
60268
  /**
59338
60269
  * Hub-side mirror of every device's cap-keyed runtime state.
59339
60270
  * Key: deviceId. Value: per-cap slice map. Empty by default —
@@ -59347,8 +60278,24 @@ var require_device_manager_addon = __commonJS({
59347
60278
  */
59348
60279
  runtimeStateDebounce = /* @__PURE__ */ new Map();
59349
60280
  static RUNTIME_STATE_DEBOUNCE_MS = 1e3;
59350
- constructor(ctx) {
60281
+ /**
60282
+ * What is believed to be ON DISK for each device — the persistable projection
60283
+ * of the last blob actually written (or seeded at boot). The effective-change
60284
+ * gate compares against THIS, not against the previous mirror state: two
60285
+ * clock-only ticks in a row must not add up to a write just because each was
60286
+ * compared with its immediate predecessor.
60287
+ */
60288
+ lastPersisted = /* @__PURE__ */ new Map();
60289
+ /**
60290
+ * Per-device count of writes the effective-change gate skipped since the last
60291
+ * real one. Reported on the next write that DOES happen, so the log says how
60292
+ * much churn the gate absorbed instead of saying nothing at all — a branch
60293
+ * that drops work silently reads as "never happened".
60294
+ */
60295
+ skippedSinceWrite = /* @__PURE__ */ new Map();
60296
+ constructor(ctx, policyFor = require_dist10.runtimeStatePolicyFor) {
59351
60297
  this.ctx = ctx;
60298
+ this.policyFor = policyFor;
59352
60299
  }
59353
60300
  /**
59354
60301
  * Single-cap mirror update — diff against the current mirror,
@@ -59375,13 +60322,23 @@ var require_device_manager_addon = __commonJS({
59375
60322
  return true;
59376
60323
  }
59377
60324
  /**
59378
- * Debounced disk writer. Coalesces frequent writes (motion phase
59379
- * transitions, battery pushes) into one `writeDeviceRuntimeState`
59380
- * per `RUNTIME_STATE_DEBOUNCE_MS` window. Reads the per-device
59381
- * blob from the live mirror at flush time so the disk picture is
60325
+ * Debounced disk writer, behind two gates.
60326
+ *
60327
+ * GATE — DURABILITY: a change to a `durability: 'session'` cap never even
60328
+ * arms the timer. That is where the fleet's write rate goes: `audio-metrics`
60329
+ * and `zone-analytics` are ~90 % of the offered 12–19 writes/s, they change
60330
+ * genuinely every second, and their restored value is worthless.
60331
+ *
60332
+ * GATE — EFFECTIVE CHANGE (at flush): the persistable blob is compared with
60333
+ * what is believed to be on disk, ignoring the clock fields each cap declared
60334
+ * volatile. `battery` wrote its blob 526 times in 25 minutes without one
60335
+ * percentage moving; this is the gate that turns those into one write.
60336
+ *
60337
+ * The blob is read from the live mirror at flush time, so the disk picture is
59382
60338
  * always the latest state — no risk of writing a stale snapshot.
59383
60339
  */
59384
- scheduleRuntimeStateDiskWrite(deviceId, settings) {
60340
+ scheduleRuntimeStateDiskWrite(deviceId, settings, changedCap) {
60341
+ if (this.policyFor(changedCap).durability !== "restored") return;
59385
60342
  let slot = this.runtimeStateDebounce.get(deviceId);
59386
60343
  if (!slot) {
59387
60344
  slot = {
@@ -59393,10 +60350,21 @@ var require_device_manager_addon = __commonJS({
59393
60350
  if (slot.timer) return;
59394
60351
  slot.timer = setTimeout(() => {
59395
60352
  slot.timer = null;
59396
- const blob = this.snapshotForDevice(deviceId);
60353
+ const blob = this.persistableForDevice(deviceId);
60354
+ if (this.isAlreadyPersisted(deviceId, blob)) return;
60355
+ const skipped = this.skippedSinceWrite.get(deviceId) ?? 0;
60356
+ this.skippedSinceWrite.delete(deviceId);
59397
60357
  const write = (async () => {
59398
60358
  try {
59399
60359
  await settings.writeDeviceRuntimeState(deviceId, blob);
60360
+ this.lastPersisted.set(deviceId, blob);
60361
+ if (skipped > 0) this.ctx.logger.debug("runtime state persisted", {
60362
+ tags: { deviceId },
60363
+ meta: {
60364
+ caps: Object.keys(blob).length,
60365
+ skippedSinceLastWrite: skipped
60366
+ }
60367
+ });
59400
60368
  } catch (err) {
59401
60369
  this.ctx.logger.warn("writeDeviceRuntimeState failed", {
59402
60370
  tags: { deviceId },
@@ -59410,6 +60378,22 @@ var require_device_manager_addon = __commonJS({
59410
60378
  }, DeviceStateMirror2.RUNTIME_STATE_DEBOUNCE_MS);
59411
60379
  }
59412
60380
  /**
60381
+ * True when `blob` differs from what is believed to be on disk only in fields
60382
+ * the owning caps declared volatile. Counts the skip so the next real write
60383
+ * can report how much churn was absorbed.
60384
+ */
60385
+ isAlreadyPersisted(deviceId, blob) {
60386
+ const onDisk = this.lastPersisted.get(deviceId);
60387
+ if (!onDisk) return false;
60388
+ if (!effectivelyEqual(onDisk, blob, this.policyFor)) return false;
60389
+ this.skippedSinceWrite.set(deviceId, (this.skippedSinceWrite.get(deviceId) ?? 0) + 1);
60390
+ return true;
60391
+ }
60392
+ /** The device's mirror, restricted to the slices that may reach disk. */
60393
+ persistableForDevice(deviceId) {
60394
+ return persistableBlob(this.snapshotForDevice(deviceId), this.policyFor);
60395
+ }
60396
+ /**
59413
60397
  * One-shot mirror seed used by `loadRuntimeState` at boot so the
59414
60398
  * hub knows about every persisted slice without waiting for the
59415
60399
  * first `setCapSlice` call. No events emitted — this is
@@ -59429,6 +60413,7 @@ var require_device_manager_addon = __commonJS({
59429
60413
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
59430
60414
  perCap.set(capName, { ...raw });
59431
60415
  }
60416
+ this.lastPersisted.set(deviceId, persistableBlob(this.snapshotForDevice(deviceId), this.policyFor));
59432
60417
  }
59433
60418
  /**
59434
60419
  * The hub mirror's `feature-probe.lastProbedAt` is a PER-SESSION liveness
@@ -59542,15 +60527,15 @@ var require_device_manager_addon = __commonJS({
59542
60527
  if (slot.timer) {
59543
60528
  clearTimeout(slot.timer);
59544
60529
  slot.timer = null;
59545
- if (settings) {
59546
- const blob = this.snapshotForDevice(deviceId);
59547
- pending.push(settings.writeDeviceRuntimeState(deviceId, blob).catch((err) => {
59548
- this.ctx.logger.warn("shutdown writeDeviceRuntimeState failed", {
59549
- tags: { deviceId },
59550
- meta: { error: err instanceof Error ? err.message : String(err) }
59551
- });
59552
- }));
59553
- }
60530
+ const blob = settings ? this.persistableForDevice(deviceId) : null;
60531
+ if (settings && blob !== null && !this.isAlreadyPersisted(deviceId, blob)) pending.push(settings.writeDeviceRuntimeState(deviceId, blob).then(() => {
60532
+ this.lastPersisted.set(deviceId, blob);
60533
+ }).catch((err) => {
60534
+ this.ctx.logger.warn("shutdown writeDeviceRuntimeState failed", {
60535
+ tags: { deviceId },
60536
+ meta: { error: err instanceof Error ? err.message : String(err) }
60537
+ });
60538
+ }));
59554
60539
  }
59555
60540
  if (slot.inFlight) pending.push(slot.inFlight);
59556
60541
  }
@@ -59977,7 +60962,7 @@ var require_device_manager_addon = __commonJS({
59977
60962
  setCapSlice: async (input) => {
59978
60963
  const { deviceId, capName, slice } = input;
59979
60964
  if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] setCapSlice: unknown device id=${deviceId}`);
59980
- if (stateMirror.applySingleCapUpdate(deviceId, capName, slice)) stateMirror.scheduleRuntimeStateDiskWrite(deviceId, settings);
60965
+ if (stateMirror.applySingleCapUpdate(deviceId, capName, slice)) stateMirror.scheduleRuntimeStateDiskWrite(deviceId, settings, capName);
59981
60966
  }
59982
60967
  }
59983
60968
  }];
@@ -60018,7 +61003,7 @@ var require_hub_forwarder = __commonJS({
60018
61003
  [Symbol.toStringTag]: { value: "Module" }
60019
61004
  });
60020
61005
  require_chunk_Cek0wNdY();
60021
- var require_dist10 = require_dist_BV5Bug9t();
61006
+ var require_dist10 = require_dist_B4WyhfjH();
60022
61007
  var require_formatter = require_formatter_DqAKDlvN();
60023
61008
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
60024
61009
  var HubForwarderDestination = class {
@@ -60155,7 +61140,7 @@ var require_liveness_monitor_addon = __commonJS({
60155
61140
  "use strict";
60156
61141
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60157
61142
  require_chunk_Cek0wNdY();
60158
- var require_dist10 = require_dist_BV5Bug9t();
61143
+ var require_dist10 = require_dist_B4WyhfjH();
60159
61144
  var NO_DEVICES = "liveness:no-devices";
60160
61145
  var ALL_OFFLINE = "liveness:all-devices-offline";
60161
61146
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -60345,7 +61330,7 @@ var require_local_auth_addon = __commonJS({
60345
61330
  [Symbol.toStringTag]: { value: "Module" }
60346
61331
  });
60347
61332
  var require_chunk = require_chunk_Cek0wNdY();
60348
- var require_dist10 = require_dist_BV5Bug9t();
61333
+ var require_dist10 = require_dist_B4WyhfjH();
60349
61334
  var node_crypto = __require("crypto");
60350
61335
  node_crypto = require_chunk.__toESM(node_crypto);
60351
61336
  var crypto$1 = __require("crypto");
@@ -68029,7 +69014,7 @@ var require_loki_logging = __commonJS({
68029
69014
  [Symbol.toStringTag]: { value: "Module" }
68030
69015
  });
68031
69016
  require_chunk_Cek0wNdY();
68032
- var require_dist10 = require_dist_BV5Bug9t();
69017
+ var require_dist10 = require_dist_B4WyhfjH();
68033
69018
  function sanitizeLabelName(raw) {
68034
69019
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
68035
69020
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -68594,7 +69579,7 @@ var require_native_metrics_addon = __commonJS({
68594
69579
  [Symbol.toStringTag]: { value: "Module" }
68595
69580
  });
68596
69581
  var require_chunk = require_chunk_Cek0wNdY();
68597
- var require_dist10 = require_dist_BV5Bug9t();
69582
+ var require_dist10 = require_dist_B4WyhfjH();
68598
69583
  var node_child_process = __require("child_process");
68599
69584
  var node_util = __require("util");
68600
69585
  var node_os = __require("os");
@@ -69536,7 +70521,7 @@ var require_filesystem_storage_addon = __commonJS({
69536
70521
  [Symbol.toStringTag]: { value: "Module" }
69537
70522
  });
69538
70523
  var require_chunk = require_chunk_Cek0wNdY();
69539
- var require_dist10 = require_dist_BV5Bug9t();
70524
+ var require_dist10 = require_dist_B4WyhfjH();
69540
70525
  var node_crypto = __require("crypto");
69541
70526
  var node_fs_promises = __require("fs/promises");
69542
70527
  var node_path = __require("path");
@@ -70652,7 +71637,7 @@ var require_sqlite_settings_addon = __commonJS({
70652
71637
  [Symbol.toStringTag]: { value: "Module" }
70653
71638
  });
70654
71639
  var require_chunk = require_chunk_Cek0wNdY();
70655
- var require_dist10 = require_dist_BV5Bug9t();
71640
+ var require_dist10 = require_dist_B4WyhfjH();
70656
71641
  var node_crypto = __require("crypto");
70657
71642
  var node_fs = __require("fs");
70658
71643
  var node_module = __require("module");
@@ -71324,6 +72309,42 @@ var require_sqlite_settings_addon = __commonJS({
71324
72309
  const where = this.prefixWhere(`${addonId}:${deviceId}.`);
71325
72310
  this.getDb().prepare(`DELETE FROM "addon-device-settings" WHERE ${where.sql}`).run(...where.params);
71326
72311
  }
72312
+ static LEGACY_RUNTIME_STATE_ADDON_ID = "__device-state";
72313
+ /**
72314
+ * The device's whole cap-slice map. Canonical row first, legacy prefix as the
72315
+ * fallback. `{}` — never null — when the device has never persisted a slice,
72316
+ * because every caller treats the result as a cap map to iterate.
72317
+ */
72318
+ getDeviceRuntimeState(deviceId) {
72319
+ this.requireDeclared("device-runtime-state");
72320
+ const row = this.getDb().prepare('SELECT data FROM "device-runtime-state" WHERE id = ?').get(deviceId);
72321
+ if (row) return parseRowData(row.data);
72322
+ return this.getAddonDevice(SqliteSettingsBackend2.LEGACY_RUNTIME_STATE_ADDON_ID, deviceId);
72323
+ }
72324
+ /**
72325
+ * Replace the device's whole cap-slice map. Replace, not merge — the caller
72326
+ * always hands over the full persistable blob, and merging would resurrect a
72327
+ * slice the durability policy has since stopped persisting.
72328
+ */
72329
+ setDeviceRuntimeState(deviceId, blob) {
72330
+ this.requireDeclared("device-runtime-state");
72331
+ this.requireDeclared("addon-device-settings");
72332
+ const db = this.getDb();
72333
+ const upsert = db.prepare(`INSERT INTO "device-runtime-state" (id, data) VALUES (?, ?)
72334
+ ON CONFLICT(id) DO UPDATE SET data = excluded.data`);
72335
+ const legacy = this.prefixWhere(`${SqliteSettingsBackend2.LEGACY_RUNTIME_STATE_ADDON_ID}:${deviceId}.`);
72336
+ const retire = db.prepare(`DELETE FROM "addon-device-settings" WHERE ${legacy.sql}`);
72337
+ db.transaction(() => {
72338
+ upsert.run(deviceId, JSON.stringify(blob));
72339
+ retire.run(...legacy.params);
72340
+ })();
72341
+ }
72342
+ /** Forget the device's runtime state in BOTH stores (device removal). */
72343
+ clearDeviceRuntimeState(deviceId) {
72344
+ this.requireDeclared("device-runtime-state");
72345
+ this.getDb().prepare('DELETE FROM "device-runtime-state" WHERE id = ?').run(deviceId);
72346
+ this.clearAddonDevice(SqliteSettingsBackend2.LEGACY_RUNTIME_STATE_ADDON_ID, deviceId);
72347
+ }
71327
72348
  /** Seed system-settings with runtime defaults (first boot) */
71328
72349
  async seedDefaults() {
71329
72350
  this.requireDeclared("system-settings");
@@ -72167,7 +73188,7 @@ var require_storage_orchestrator_addon = __commonJS({
72167
73188
  [Symbol.toStringTag]: { value: "Module" }
72168
73189
  });
72169
73190
  var require_chunk = require_chunk_Cek0wNdY();
72170
- var require_dist10 = require_dist_BV5Bug9t();
73191
+ var require_dist10 = require_dist_B4WyhfjH();
72171
73192
  var node_crypto = __require("crypto");
72172
73193
  var node_fs_promises = __require("fs/promises");
72173
73194
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -74008,7 +75029,7 @@ var require_system_config_addon = __commonJS({
74008
75029
  [Symbol.toStringTag]: { value: "Module" }
74009
75030
  });
74010
75031
  require_chunk_Cek0wNdY();
74011
- var require_dist10 = require_dist_BV5Bug9t();
75032
+ var require_dist10 = require_dist_B4WyhfjH();
74012
75033
  var SECTION_TITLES = {
74013
75034
  server: "Server",
74014
75035
  auth: "Authentication"
@@ -92069,7 +93090,7 @@ var require_winston_logging = __commonJS({
92069
93090
  [Symbol.toStringTag]: { value: "Module" }
92070
93091
  });
92071
93092
  var require_chunk = require_chunk_Cek0wNdY();
92072
- var require_dist10 = require_dist_BV5Bug9t();
93093
+ var require_dist10 = require_dist_B4WyhfjH();
92073
93094
  var require_formatter = require_formatter_DqAKDlvN();
92074
93095
  var node_path = __require("path");
92075
93096
  node_path = require_chunk.__toESM(node_path);
@@ -93220,9 +94241,9 @@ var require_event_category_DxZbWydC = __commonJS({
93220
94241
  }
93221
94242
  });
93222
94243
 
93223
- // ../types/dist/sleep-BszXLEvP.js
93224
- var require_sleep_BszXLEvP = __commonJS({
93225
- "../types/dist/sleep-BszXLEvP.js"(exports) {
94244
+ // ../types/dist/sleep-Dh1EJSqF.js
94245
+ var require_sleep_Dh1EJSqF = __commonJS({
94246
+ "../types/dist/sleep-Dh1EJSqF.js"(exports) {
93226
94247
  "use strict";
93227
94248
  var require_event_category = require_event_category_DxZbWydC();
93228
94249
  var zod = require_zod();
@@ -94830,6 +95851,7 @@ var require_sleep_BszXLEvP = __commonJS({
94830
95851
  const value = Reflect.get(context, CAP_NODE_PIN_CONTEXT_KEY);
94831
95852
  return typeof value === "string" ? value : void 0;
94832
95853
  }
95854
+ var DEFAULT_RUNTIME_STATE_DURABILITY = "session";
94833
95855
  function resolveCapMount(def) {
94834
95856
  if (def.mount) return def.mount;
94835
95857
  if (def.deviceNative === true) return { kind: "device-native" };
@@ -96215,6 +97237,12 @@ var require_sleep_BszXLEvP = __commonJS({
96215
97237
  return DATAPLANE_SECRET_HEADER;
96216
97238
  }
96217
97239
  });
97240
+ Object.defineProperty(exports, "DEFAULT_RUNTIME_STATE_DURABILITY", {
97241
+ enumerable: true,
97242
+ get: function() {
97243
+ return DEFAULT_RUNTIME_STATE_DURABILITY;
97244
+ }
97245
+ });
96218
97246
  Object.defineProperty(exports, "DEVICE_SCOPED_CAPS", {
96219
97247
  enumerable: true,
96220
97248
  get: function() {
@@ -96638,7 +97666,7 @@ var require_addon = __commonJS({
96638
97666
  "use strict";
96639
97667
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
96640
97668
  var require_event_category = require_event_category_DxZbWydC();
96641
- var require_sleep = require_sleep_BszXLEvP();
97669
+ var require_sleep = require_sleep_Dh1EJSqF();
96642
97670
  var require_err_msg = require_err_msg_COpsHMw2();
96643
97671
  var COLLECTION_ARRAY_METHODS = Object.freeze({
96644
97672
  "addon-pages-source": ["listPages"],
@@ -96786,7 +97814,7 @@ var require_addon = __commonJS({
96786
97814
  "pipelineEnabled": true,
96787
97815
  "zones": [],
96788
97816
  "onboardMotionDrivesAnalyzer": true,
96789
- "occupancyRecheckEnabled": false,
97817
+ "occupancyRecheckEnabled": true,
96790
97818
  "occupancyRecheckSec": 30,
96791
97819
  "occupancyRecheckFrames": 4,
96792
97820
  "frameSource": { "kind": "local-broker" }
@@ -114082,7 +115110,7 @@ var require_dist3 = __commonJS({
114082
115110
  "use strict";
114083
115111
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
114084
115112
  var require_chunk = require_chunk_Cek0wNdY();
114085
- var require_dist10 = require_dist_BV5Bug9t();
115113
+ var require_dist10 = require_dist_B4WyhfjH();
114086
115114
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
114087
115115
  require_alerts();
114088
115116
  var require_formatter = require_formatter_DqAKDlvN();
@@ -122793,6 +123821,32 @@ var require_dist3 = __commonJS({
122793
123821
  if (this.settingsStore === null) return;
122794
123822
  this.settingsStore.clearAddonDevice(addonId, deviceId);
122795
123823
  }
123824
+ /**
123825
+ * The device's persisted runtime-state blob. `{}` before the settings store
123826
+ * is wired — the same answer as "this device has never persisted a slice",
123827
+ * which is what every caller already handles.
123828
+ */
123829
+ getDeviceRuntimeState(deviceId) {
123830
+ if (this.settingsStore === null) return {};
123831
+ return this.settingsStore.getDeviceRuntimeState(deviceId);
123832
+ }
123833
+ /**
123834
+ * Replace the device's persisted runtime-state blob.
123835
+ *
123836
+ * THROWS when the store is not wired, and that is deliberate: the caller
123837
+ * (`DeviceStateMirror.scheduleRuntimeStateDiskWrite`) catches and logs, so a
123838
+ * persist that cannot happen leaves a line naming the device. A silent no-op
123839
+ * here is the shape that let a whole collection read zero for months.
123840
+ */
123841
+ setDeviceRuntimeState(deviceId, blob) {
123842
+ if (this.settingsStore === null) throw new Error("[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first");
123843
+ this.settingsStore.setDeviceRuntimeState(deviceId, blob);
123844
+ }
123845
+ /** Forget the device's runtime state. No-op before the store is wired. */
123846
+ clearDeviceRuntimeState(deviceId) {
123847
+ if (this.settingsStore === null) return;
123848
+ this.settingsStore.clearDeviceRuntimeState(deviceId);
123849
+ }
122796
123850
  createSettingsView(addonId) {
122797
123851
  const cm = this;
122798
123852
  return {
@@ -122821,13 +123875,13 @@ var require_dist3 = __commonJS({
122821
123875
  cm.clearAddonDevice(addonId, String(deviceId));
122822
123876
  },
122823
123877
  async readDeviceRuntimeState(deviceId) {
122824
- return cm.getAddonDevice("__device-state", String(deviceId));
123878
+ return cm.getDeviceRuntimeState(String(deviceId));
122825
123879
  },
122826
123880
  async writeDeviceRuntimeState(deviceId, data) {
122827
- cm.setAddonDevice("__device-state", String(deviceId), data);
123881
+ cm.setDeviceRuntimeState(String(deviceId), data);
122828
123882
  },
122829
123883
  async clearDeviceRuntimeState(deviceId) {
122830
- cm.clearAddonDevice("__device-state", String(deviceId));
123884
+ cm.clearDeviceRuntimeState(String(deviceId));
122831
123885
  },
122832
123886
  async getSection(section) {
122833
123887
  return cm.getSection(section);
@@ -233388,7 +234442,7 @@ var require_dist9 = __commonJS({
233388
234442
  "use strict";
233389
234443
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
233390
234444
  var require_event_category = require_event_category_DxZbWydC();
233391
- var require_sleep = require_sleep_BszXLEvP();
234445
+ var require_sleep = require_sleep_Dh1EJSqF();
233392
234446
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
233393
234447
  var require_enums2 = require_enums();
233394
234448
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -238479,7 +239533,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
238479
239533
  low: zod.z.string().optional()
238480
239534
  }),
238481
239535
  lastChangedAt: zod.z.number()
238482
- })
239536
+ }),
239537
+ /**
239538
+ * 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.
239539
+ *
239540
+ * See `RuntimeStateDurability`. Enforced by
239541
+ * `scripts/check-runtime-state-durability.ts`.
239542
+ */
239543
+ durability: "session"
238483
239544
  };
238484
239545
  function isVoidInput(schema) {
238485
239546
  const def2 = schema._def;
@@ -239258,6 +240319,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
239258
240319
  kind: "poll"
239259
240320
  },
239260
240321
  runtimeState: DeviceDiscoveryStatusSchema.extend({ lastFetchedAt: zod.z.number().int().nonnegative() }),
240322
+ /**
240323
+ * Runtime-state durability: **session** — 5.7 KB of scan output on the largest device, fully re-derivable by re-scanning.
240324
+ *
240325
+ * See `RuntimeStateDurability`. Enforced by
240326
+ * `scripts/check-runtime-state-durability.ts`.
240327
+ */
240328
+ durability: "session",
239261
240329
  methods: {
239262
240330
  /**
239263
240331
  * Snapshot of the current `discovered` list. Returns the
@@ -241462,6 +242530,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241462
242530
  "acknowledge",
241463
242531
  "dismiss",
241464
242532
  "silence",
242533
+ "snooze",
241465
242534
  "view",
241466
242535
  "play",
241467
242536
  "open",
@@ -241469,9 +242538,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241469
242538
  "lock",
241470
242539
  "unlock",
241471
242540
  "arm",
242541
+ "arm-home",
242542
+ "arm-away",
242543
+ "arm-night",
241472
242544
  "disarm",
241473
242545
  "light",
241474
- "alert"
242546
+ "alert",
242547
+ "camera"
241475
242548
  ]);
241476
242549
  var NotificationActionSchema = zod.z.object({
241477
242550
  id: zod.z.string(),
@@ -241486,7 +242559,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241486
242559
  * else — see `notification-center/action-token.ts` for what that does and
241487
242560
  * does not buy.
241488
242561
  */
241489
- destructive: zod.z.boolean().optional()
242562
+ destructive: zod.z.boolean().optional(),
242563
+ /**
242564
+ * How the tap should REACH the url.
242565
+ *
242566
+ * `navigate` (absent, and every button authored before this field) opens it:
242567
+ * the phone leaves the notification and shows whatever the callback returns.
242568
+ * That is right for a button whose answer the operator wants to read.
242569
+ *
242570
+ * `background` fires it as a POST and stays put. It exists for the buttons
242571
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
242572
+ * an answer to the notification, and being thrown into a browser tab to
242573
+ * confirm it costs more attention than the notification did. A backend that
242574
+ * cannot do a background call renders it as an ordinary link (the adapters
242575
+ * fall back rather than dropping the button), so this is a preference, never
242576
+ * a requirement.
242577
+ */
242578
+ mode: zod.z.enum(["navigate", "background"]).optional()
241490
242579
  });
241491
242580
  var NotificationSchema = zod.z.object({
241492
242581
  body: zod.z.string(),
@@ -241984,6 +243073,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241984
243073
  name: "Package"
241985
243074
  }
241986
243075
  ];
243076
+ var DETECTION_MACRO_CLASSES = new Set(MACRO_LABELS.map((l) => l.id));
243077
+ function isDetectionMacroClass(className) {
243078
+ return DETECTION_MACRO_CLASSES.has(className.trim().toLowerCase());
243079
+ }
241987
243080
  var COCO_TO_MACRO = {
241988
243081
  mapping: {
241989
243082
  person: "person",
@@ -242622,7 +243715,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242622
243715
  * full slice; renders an arm button per `availableModes` entry and
242623
243716
  * a PIN field iff `requiresCode === true`.
242624
243717
  */
242625
- runtimeState: AlarmPanelStatusSchema
243718
+ runtimeState: AlarmPanelStatusSchema,
243719
+ /**
243720
+ * Runtime-state durability: **restored** — armed state is the one thing a panel must not lose across a restart.
243721
+ *
243722
+ * See `RuntimeStateDurability`. Enforced by
243723
+ * `scripts/check-runtime-state-durability.ts`.
243724
+ */
243725
+ durability: "restored",
243726
+ /** Clock fields: written, but excluded from the compare that decides
243727
+ * whether persisting is worth a SQLite commit. */
243728
+ volatileStateFields: ["lastChangedAt"]
242626
243729
  };
242627
243730
  var MaskPointSchema = zod.z.object({
242628
243731
  x: zod.z.number(),
@@ -242690,6 +243793,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242690
243793
  "alarm-triggered",
242691
243794
  "alarm-armed",
242692
243795
  "alarm-disarmed",
243796
+ "alarm-arming",
243797
+ "alarm-arm-refused",
242693
243798
  "camera-online",
242694
243799
  "camera-offline",
242695
243800
  "camera-disabled",
@@ -242703,8 +243808,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242703
243808
  ]);
242704
243809
  var NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NcSystemEventKindSchema.options.filter((kind) => !NC_LEGACY_SYSTEM_EVENT_KINDS.has(kind));
242705
243810
  var NC_ALARM_SYSTEM_EVENT_KINDS = [
242706
- "alarm-triggered",
243811
+ "alarm-arm-refused",
243812
+ "alarm-arming",
242707
243813
  "alarm-armed",
243814
+ "alarm-triggered",
242708
243815
  "alarm-disarmed"
242709
243816
  ];
242710
243817
  var NcSystemEventConditionSchema = zod.z.object({
@@ -242726,6 +243833,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242726
243833
  packageNames: zod.z.array(zod.z.string().min(1)).min(1).optional()
242727
243834
  });
242728
243835
  var NC_MAX_PER_TRACK_IMMEDIATE = 1;
243836
+ var NC_SNOOZE_MAX_MINUTES = 1440;
243837
+ var NC_DEFAULT_SNOOZE_MINUTES = [
243838
+ 10,
243839
+ 30,
243840
+ 60
243841
+ ];
242729
243842
  var NcScheduleWindowSchema = zod.z.object({
242730
243843
  /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
242731
243844
  days: zod.z.array(zod.z.number().int().min(0).max(6)).min(1),
@@ -242983,15 +244096,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242983
244096
  * (an `immediate` rule naming an `audio-*` class, one notification per
242984
244097
  * classified sample) stays exactly as it was for rules that already use it.
242985
244098
  *
242986
- * NOT in {@link NC_CONDITION_CATALOG} yet, and that is the sequencing rule
242987
- * rather than an oversight: the viewer mirrors the descriptor enums BY HAND
242988
- * (`camstack/src/data/notification-center.ts`, guarded by
242989
- * `scripts/check-viewer-condition-mirror.ts`) and its rule editor STRIPS the
244099
+ * In {@link NC_CONDITION_CATALOG} since P2, and the ORDER it got there is the
244100
+ * rule rather than an accident: the viewer mirrors the descriptor enums BY
244101
+ * HAND (`camstack/src/data/notification-center.ts`, guarded by
244102
+ * `scripts/check-viewer-condition-mirror.ts`) and its rule editor strips the
242990
244103
  * condition fields it does not know when a rule is saved from the phone.
242991
244104
  * Publishing an editor for a condition the app cannot round-trip is how an
242992
- * operator loses a rule's conditions by opening it — so the descriptor, the
242993
- * admin widget and the viewer mirror land together (P2 + P3), and only then
242994
- * does an audio rule become authorable.
244105
+ * operator loses a rule's conditions by opening it — so the viewer mirror
244106
+ * (P3, shipped) went FIRST, and the descriptor an editor renders from
244107
+ * follows here.
242995
244108
  */
242996
244109
  audio: NcAudioConditionSchema.optional()
242997
244110
  });
@@ -243161,6 +244274,30 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243161
244274
  */
243162
244275
  snoozeAllowGlobal: zod.z.boolean().optional(),
243163
244276
  /**
244277
+ * The snooze durations THIS rule's notification offers as buttons, in
244278
+ * minutes.
244279
+ *
244280
+ * Three states, and all three are distinct — which is exactly why this is
244281
+ * `.optional()` and never `.default()`. A Zod default does not run on the
244282
+ * addon cap path (three production failures in one day), so a schema default
244283
+ * would collapse the first two:
244284
+ *
244285
+ * | value | meaning |
244286
+ * | --- | --- |
244287
+ * | absent | the operator never said ⇒ {@link NC_DEFAULT_SNOOZE_MINUTES} |
244288
+ * | `[]` | **no snooze buttons on this rule** — the explicit override |
244289
+ * | a list | these choices, de-duplicated and sorted, at most four |
244290
+ *
244291
+ * `.max(4)` because the notifier's own action budget is small (ntfy allows
244292
+ * three buttons in total) and a rule that spent it all on snooze choices
244293
+ * would push its own tap-through actions off the notification.
244294
+ *
244295
+ * An empty list is NOT an alarm exemption: a rule the alarm is about, or
244296
+ * that arms the panel, is exempt automatically and cannot be silenced by a
244297
+ * window from anywhere (D133).
244298
+ */
244299
+ snoozeOptions: zod.z.array(zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES)).max(4).optional(),
244300
+ /**
243164
244301
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
243165
244302
  *
243166
244303
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -243250,6 +244387,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243250
244387
  "device",
243251
244388
  "package",
243252
244389
  "occupancy",
244390
+ "audio",
243253
244391
  "system"
243254
244392
  ]),
243255
244393
  label: zod.z.string(),
@@ -243268,6 +244406,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243268
244406
  "crossingSelect",
243269
244407
  "polygonDraw",
243270
244408
  "occupancy",
244409
+ "audio",
243271
244410
  "deviceState",
243272
244411
  "systemEvent"
243273
244412
  ]),
@@ -243354,13 +244493,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243354
244493
  label: "Server update available"
243355
244494
  },
243356
244495
  {
243357
- value: "alarm-triggered",
243358
- label: "Alarm triggered"
244496
+ value: "alarm-arming",
244497
+ label: "Alarm arming (exit delay)"
243359
244498
  },
243360
244499
  {
243361
244500
  value: "alarm-armed",
243362
244501
  label: "Alarm armed"
243363
244502
  },
244503
+ {
244504
+ value: "alarm-triggered",
244505
+ label: "Alarm triggered"
244506
+ },
243364
244507
  {
243365
244508
  value: "alarm-disarmed",
243366
244509
  label: "Alarm disarmed"
@@ -243661,6 +244804,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243661
244804
  phase: "P1",
243662
244805
  description: "ZoneAnalytics occupancy edge (optionally zone/class-scoped): count crosses the threshold and holds for sustainSeconds. Fail-closed on a missing snapshot."
243663
244806
  },
244807
+ {
244808
+ id: "audio",
244809
+ group: "audio",
244810
+ label: "Sound",
244811
+ valueType: "audio",
244812
+ operator: "anyOf",
244813
+ appliesTo: ["immediate"],
244814
+ phase: "P2",
244815
+ description: "Fires when at least hitPercent% of the samples in a samplingSeconds window are hits \u2014 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)."
244816
+ },
243664
244817
  {
243665
244818
  id: "customZones",
243666
244819
  group: "zones",
@@ -243759,7 +244912,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243759
244912
  "device",
243760
244913
  "all"
243761
244914
  ]);
243762
- var NC_SNOOZE_MAX_MINUTES = 1440;
243763
244915
  var NcSnoozeInputSchema = zod.z.object({
243764
244916
  scope: NcSnoozeScopeSchema,
243765
244917
  /** Required when `scope: 'rule'` — a scoped snooze with no id matches
@@ -243767,6 +244919,19 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243767
244919
  ruleId: zod.z.string().optional(),
243768
244920
  /** Required when `scope: 'device'`. */
243769
244921
  deviceId: zod.z.number().int().optional(),
244922
+ /**
244923
+ * Narrow the window to these subject classes — "the cat, not the person".
244924
+ *
244925
+ * ORTHOGONAL to `scope`, deliberately, and absent means EVERY class: that is
244926
+ * what every window authored before this field meant, so no persisted row
244927
+ * changes meaning and no client has to learn anything to keep working.
244928
+ *
244929
+ * It is what makes the window's real key `(deviceId, classes[])` and lets it
244930
+ * cross rules (D133): the operator points at a camera and a kind of thing,
244931
+ * not at whichever of their four rules happened to produce the notification
244932
+ * they are dismissing.
244933
+ */
244934
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
243770
244935
  durationMinutes: zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
243771
244936
  /**
243772
244937
  * Silence this for EVERY recipient, not just the caller. Permission is
@@ -243791,6 +244956,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243791
244956
  scope: NcSnoozeScopeSchema,
243792
244957
  ruleId: zod.z.string().optional(),
243793
244958
  deviceId: zod.z.number().int().optional(),
244959
+ /** Subject classes this window covers. ABSENT = every class — see
244960
+ * {@link NcSnoozeInputSchema.shape.classes}. Lives in the JSON blob and has
244961
+ * no SQLite column: nothing queries a window by class. */
244962
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
243794
244963
  startedAt: zod.z.number(),
243795
244964
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
243796
244965
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -246063,7 +247232,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246063
247232
  * handle. Slice shape is `{ zones: Zone[] }` so future extensions
246064
247233
  * (e.g. zone groupings) can sit alongside the polygon list.
246065
247234
  */
246066
- runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() })
247235
+ runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() }),
247236
+ /**
247237
+ * 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.
247238
+ *
247239
+ * See `RuntimeStateDurability`. Enforced by
247240
+ * `scripts/check-runtime-state-durability.ts`.
247241
+ */
247242
+ durability: "restored"
246067
247243
  };
246068
247244
  var NativeCropBboxSchema = zod.z.object({
246069
247245
  x: zod.z.number(),
@@ -246327,15 +247503,21 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246327
247503
  */
246328
247504
  onboardMotionDrivesAnalyzer: zod.z.boolean().default(true),
246329
247505
  /**
246330
- * Master toggle for the occupancy re-check. When `false` (DEFAULT) the runner
246331
- * never arms the periodic recheck timer, regardless of `occupancyRecheckSec`
246332
- * this is off by default because the recheck re-subscribes a detection session
246333
- * every N seconds while `watching`, a major source of pull-decoder re-dial
246334
- * churn (each cycle creates+tears a session → RTSP re-dial → latency). The
247506
+ * Master toggle for the occupancy re-check. When `false` the runner never arms
247507
+ * the periodic recheck timer, regardless of `occupancyRecheckSec`; the
246335
247508
  * `occupancyRecheckSec` / `occupancyRecheckFrames` sliders only take effect
246336
247509
  * (and only render) when this is enabled.
247510
+ *
247511
+ * DEFAULT `true` since 2026-08-13 (was `false`). It was off because the
247512
+ * recheck re-subscribes a detection session every N seconds while `watching`
247513
+ * — each cycle creates+tears a session ⇒ an RTSP re-dial ⇒ latency, a major
247514
+ * pull-decoder churn source. What that bought was a blind spot: a STATIONARY
247515
+ * object is counted only while the stationary registry holds it, and the
247516
+ * registry rebuilds from motion, so after a restart a parked car was invisible
247517
+ * to every occupancy rule until something moved in front of it. The churn is
247518
+ * now paid on the interval instead — see `occupancyRecheckSecField`.
246337
247519
  */
246338
- occupancyRecheckEnabled: zod.z.boolean().default(false),
247520
+ occupancyRecheckEnabled: zod.z.boolean().default(true),
246339
247521
  occupancyRecheckSec: zod.z.number().min(occupancyRecheckSecField.min).max(occupancyRecheckSecField.max).default(occupancyRecheckSecField.default),
246340
247522
  occupancyRecheckFrames: zod.z.number().min(occupancyRecheckFramesField.min).max(occupancyRecheckFramesField.max).default(occupancyRecheckFramesField.default),
246341
247523
  /**
@@ -246453,8 +247635,8 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246453
247635
  type: "boolean",
246454
247636
  style: "checkbox",
246455
247637
  label: "Occupancy re-check",
246456
- 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 \u2014 it adds decoder re-dial churn.",
246457
- default: false
247638
+ 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.",
247639
+ default: true
246458
247640
  },
246459
247641
  {
246460
247642
  key: "occupancyRecheckSec",
@@ -249510,7 +250692,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249510
250692
  schema: AirQualitySensorStatusSchema,
249511
250693
  kind: "push"
249512
250694
  },
249513
- runtimeState: AirQualitySensorStatusSchema
250695
+ runtimeState: AirQualitySensorStatusSchema,
250696
+ /**
250697
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
250698
+ *
250699
+ * See `RuntimeStateDurability`. Enforced by
250700
+ * `scripts/check-runtime-state-durability.ts`.
250701
+ */
250702
+ durability: "restored",
250703
+ /** Clock fields: written, but excluded from the compare that decides
250704
+ * whether persisting is worth a SQLite commit. */
250705
+ volatileStateFields: ["lastFetchedAt"]
249514
250706
  };
249515
250707
  var AmbientLightSensorStatusSchema = zod.z.object({
249516
250708
  /** Current illuminance in lux (lx). */
@@ -249538,7 +250730,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249538
250730
  schema: AmbientLightSensorStatusSchema,
249539
250731
  kind: "push"
249540
250732
  },
249541
- runtimeState: AmbientLightSensorStatusSchema
250733
+ runtimeState: AmbientLightSensorStatusSchema,
250734
+ /**
250735
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
250736
+ *
250737
+ * See `RuntimeStateDurability`. Enforced by
250738
+ * `scripts/check-runtime-state-durability.ts`.
250739
+ */
250740
+ durability: "restored",
250741
+ /** Clock fields: written, but excluded from the compare that decides
250742
+ * whether persisting is worth a SQLite commit. */
250743
+ volatileStateFields: ["lastFetchedAt"]
249542
250744
  };
249543
250745
  var AudioClassSummarySchema = zod.z.object({
249544
250746
  className: zod.z.string(),
@@ -249628,7 +250830,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249628
250830
  }), AudioMetricsHistorySchema)
249629
250831
  },
249630
250832
  /** Reactive runtime-state mirror — live `device.state.audioMetrics.value`. */
249631
- runtimeState: AudioMetricsSnapshotSchema
250833
+ runtimeState: AudioMetricsSnapshotSchema,
250834
+ /**
250835
+ * 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.
250836
+ *
250837
+ * See `RuntimeStateDurability`. Enforced by
250838
+ * `scripts/check-runtime-state-durability.ts`.
250839
+ */
250840
+ durability: "session"
249632
250841
  };
249633
250842
  var AutomationControlStatusSchema = zod.z.object({
249634
250843
  /** Whether the automation is currently enabled. Disabled automations
@@ -249679,7 +250888,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249679
250888
  * reads `enabled` (toggle) + `isRunning` (spinner) + `lastError`
249680
250889
  * (badge) directly.
249681
250890
  */
249682
- runtimeState: AutomationControlStatusSchema
250891
+ runtimeState: AutomationControlStatusSchema,
250892
+ /**
250893
+ * 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.
250894
+ *
250895
+ * See `RuntimeStateDurability`. Enforced by
250896
+ * `scripts/check-runtime-state-durability.ts`.
250897
+ */
250898
+ durability: "session"
249683
250899
  };
249684
250900
  var BatteryStatusSchema = zod.z.object({
249685
250901
  /** 0..100 inclusive. Firmware-reported. */
@@ -249780,7 +250996,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249780
250996
  * via `device.runtimeState.getCapState('battery')` regardless of
249781
250997
  * the underlying driver.
249782
250998
  */
249783
- runtimeState: BatteryStatusSchema
250999
+ runtimeState: BatteryStatusSchema,
251000
+ /**
251001
+ * 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.
251002
+ *
251003
+ * See `RuntimeStateDurability`. Enforced by
251004
+ * `scripts/check-runtime-state-durability.ts`.
251005
+ */
251006
+ durability: "restored",
251007
+ /** Clock fields: written, but excluded from the compare that decides
251008
+ * whether persisting is worth a SQLite commit. */
251009
+ volatileStateFields: ["lastUpdated"]
249784
251010
  };
249785
251011
  var BinaryStatusSchema = zod.z.object({
249786
251012
  on: zod.z.boolean(),
@@ -249798,7 +251024,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249798
251024
  schema: BinaryStatusSchema,
249799
251025
  kind: "push"
249800
251026
  },
249801
- runtimeState: BinaryStatusSchema
251027
+ runtimeState: BinaryStatusSchema,
251028
+ /**
251029
+ * Runtime-state durability: **restored** — transition-driven sensor state; the restored value gives the boot comparison.
251030
+ *
251031
+ * See `RuntimeStateDurability`. Enforced by
251032
+ * `scripts/check-runtime-state-durability.ts`.
251033
+ */
251034
+ durability: "restored",
251035
+ /** Clock fields: written, but excluded from the compare that decides
251036
+ * whether persisting is worth a SQLite commit. */
251037
+ volatileStateFields: ["lastChangedAt"]
249802
251038
  };
249803
251039
  var BrightnessStatusSchema = zod.z.object({
249804
251040
  /** Current level as 0..100 inclusive. Firmware-reported. */
@@ -249840,7 +251076,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249840
251076
  * by the kernel. Read via `device.state.brightness.value` so UI
249841
251077
  * sliders surface the current level without polling the provider.
249842
251078
  */
249843
- runtimeState: BrightnessStatusSchema
251079
+ runtimeState: BrightnessStatusSchema,
251080
+ /**
251081
+ * Runtime-state durability: **session** — live lamp state, re-published by the provider on connect.
251082
+ *
251083
+ * See `RuntimeStateDurability`. Enforced by
251084
+ * `scripts/check-runtime-state-durability.ts`.
251085
+ */
251086
+ durability: "session"
249844
251087
  };
249845
251088
  var buttonCapability = {
249846
251089
  name: "button",
@@ -249920,7 +251163,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249920
251163
  schema: CarbonMonoxideStatusSchema,
249921
251164
  kind: "push"
249922
251165
  },
249923
- runtimeState: CarbonMonoxideStatusSchema
251166
+ runtimeState: CarbonMonoxideStatusSchema,
251167
+ /**
251168
+ * Runtime-state durability: **restored** — as `smoke`.
251169
+ *
251170
+ * See `RuntimeStateDurability`. Enforced by
251171
+ * `scripts/check-runtime-state-durability.ts`.
251172
+ */
251173
+ durability: "restored",
251174
+ /** Clock fields: written, but excluded from the compare that decides
251175
+ * whether persisting is worth a SQLite commit. */
251176
+ volatileStateFields: ["lastChangedAt"]
249924
251177
  };
249925
251178
  var HvacModeSchema = zod.z.enum([
249926
251179
  "off",
@@ -250049,7 +251302,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250049
251302
  * the full slice via `device.state.climate-control.value` and refresh
250050
251303
  * on every push without re-querying the provider.
250051
251304
  */
250052
- runtimeState: ClimateControlStatusSchema
251305
+ runtimeState: ClimateControlStatusSchema,
251306
+ /**
251307
+ * Runtime-state durability: **session** — as `brightness`; `currentTemp` moves continuously and is re-published on connect.
251308
+ *
251309
+ * See `RuntimeStateDurability`. Enforced by
251310
+ * `scripts/check-runtime-state-durability.ts`.
251311
+ */
251312
+ durability: "session"
250053
251313
  };
250054
251314
  var RgbTripletSchema = zod.z.object({
250055
251315
  r: zod.z.number().int().min(0).max(255),
@@ -250136,7 +251396,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250136
251396
  * kernel. Read via `device.state.color.value` so UI pickers surface
250137
251397
  * the current chromaticity without polling the provider.
250138
251398
  */
250139
- runtimeState: ColorStatusSchema
251399
+ runtimeState: ColorStatusSchema,
251400
+ /**
251401
+ * Runtime-state durability: **session** — as `brightness`.
251402
+ *
251403
+ * See `RuntimeStateDurability`. Enforced by
251404
+ * `scripts/check-runtime-state-durability.ts`.
251405
+ */
251406
+ durability: "session"
250140
251407
  };
250141
251408
  var CONNECTION_TEST_TIMEOUT_MS = 2e4;
250142
251409
  var ConnectionTestOutcomeSchema = zod.z.discriminatedUnion("outcome", [
@@ -250191,7 +251458,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250191
251458
  schema: ConnectivityStatusSchema,
250192
251459
  kind: "push"
250193
251460
  },
250194
- runtimeState: ConnectivityStatusSchema
251461
+ runtimeState: ConnectivityStatusSchema,
251462
+ /**
251463
+ * Runtime-state durability: **restored** — same shape and same argument as `device-status`, for links rather than devices.
251464
+ *
251465
+ * See `RuntimeStateDurability`. Enforced by
251466
+ * `scripts/check-runtime-state-durability.ts`.
251467
+ */
251468
+ durability: "restored",
251469
+ /** Clock fields: written, but excluded from the compare that decides
251470
+ * whether persisting is worth a SQLite commit. */
251471
+ volatileStateFields: ["lastChangedAt"]
250195
251472
  };
250196
251473
  var ConsumableItemSchema = zod.z.object({
250197
251474
  /** Stable id, e.g. 'main-brush'. */
@@ -250258,7 +251535,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250258
251535
  }
250259
251536
  }
250260
251537
  },
250261
- runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() })
251538
+ runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() }),
251539
+ /**
251540
+ * Runtime-state durability: **session** — the authority is the appliance; the provider re-reads the whole item array on connect.
251541
+ *
251542
+ * See `RuntimeStateDurability`. Enforced by
251543
+ * `scripts/check-runtime-state-durability.ts`.
251544
+ */
251545
+ durability: "session"
250262
251546
  };
250263
251547
  var ContactStatusSchema = zod.z.object({
250264
251548
  /** True when the entry is open; false when closed. */
@@ -250277,7 +251561,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250277
251561
  schema: ContactStatusSchema,
250278
251562
  kind: "push"
250279
251563
  },
250280
- runtimeState: ContactStatusSchema
251564
+ runtimeState: ContactStatusSchema,
251565
+ /**
251566
+ * Runtime-state durability: **restored** — a door left open across a restart must still read open.
251567
+ *
251568
+ * See `RuntimeStateDurability`. Enforced by
251569
+ * `scripts/check-runtime-state-durability.ts`.
251570
+ */
251571
+ durability: "restored",
251572
+ /** Clock fields: written, but excluded from the compare that decides
251573
+ * whether persisting is worth a SQLite commit. */
251574
+ volatileStateFields: ["lastChangedAt"]
250281
251575
  };
250282
251576
  var ControlKindSchema = zod.z.enum([
250283
251577
  "numeric",
@@ -250364,7 +251658,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250364
251658
  * dropdown / text field / date picker) read the slice's discriminant
250365
251659
  * and value directly without polling the provider.
250366
251660
  */
250367
- runtimeState: ControlStatusSchema
251661
+ runtimeState: ControlStatusSchema,
251662
+ /**
251663
+ * Runtime-state durability: **session** — a generic control mirrors an external entity that re-publishes on connect; the options array is re-derived with it.
251664
+ *
251665
+ * See `RuntimeStateDurability`. Enforced by
251666
+ * `scripts/check-runtime-state-durability.ts`.
251667
+ */
251668
+ durability: "session"
250368
251669
  };
250369
251670
  var CoverStateSchema = zod.z.enum([
250370
251671
  "open",
@@ -250426,7 +251727,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250426
251727
  * Runtime-state slice — mirrored by the kernel. UI controls watch
250427
251728
  * the slice for live position changes during a move.
250428
251729
  */
250429
- runtimeState: CoverStatusSchema
251730
+ runtimeState: CoverStatusSchema,
251731
+ /**
251732
+ * Runtime-state durability: **restored** — position survives a restart on the device; the mirror should agree at boot rather than read blank.
251733
+ *
251734
+ * See `RuntimeStateDurability`. Enforced by
251735
+ * `scripts/check-runtime-state-durability.ts`.
251736
+ */
251737
+ durability: "restored",
251738
+ /** Clock fields: written, but excluded from the compare that decides
251739
+ * whether persisting is worth a SQLite commit. */
251740
+ volatileStateFields: ["lastChangedAt"]
250430
251741
  };
250431
251742
  var DayNightModeSchema = zod.z.enum([
250432
251743
  "auto",
@@ -250487,7 +251798,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250487
251798
  schema: DayNightStatusSchema,
250488
251799
  kind: "poll"
250489
251800
  },
250490
- runtimeState: DayNightStatusSchema
251801
+ runtimeState: DayNightStatusSchema,
251802
+ /**
251803
+ * Runtime-state durability: **restored** — operator-set IR-cut behaviour; mutation-driven.
251804
+ *
251805
+ * See `RuntimeStateDurability`. Enforced by
251806
+ * `scripts/check-runtime-state-durability.ts`.
251807
+ */
251808
+ durability: "restored",
251809
+ /** Clock fields: written, but excluded from the compare that decides
251810
+ * whether persisting is worth a SQLite commit. */
251811
+ volatileStateFields: ["lastFetchedAt"]
250491
251812
  };
250492
251813
  var DeviceStatusSchema = zod.z.object({
250493
251814
  /**
@@ -250520,7 +251841,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250520
251841
  schema: DeviceStatusSchema,
250521
251842
  kind: "push"
250522
251843
  },
250523
- runtimeState: DeviceStatusSchema
251844
+ runtimeState: DeviceStatusSchema,
251845
+ /**
251846
+ * 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.
251847
+ *
251848
+ * See `RuntimeStateDurability`. Enforced by
251849
+ * `scripts/check-runtime-state-durability.ts`.
251850
+ */
251851
+ durability: "restored",
251852
+ /** Clock fields: written, but excluded from the compare that decides
251853
+ * whether persisting is worth a SQLite commit. */
251854
+ volatileStateFields: ["lastChangedAt"]
250524
251855
  };
250525
251856
  var DoorbellStatusSchema = zod.z.object({
250526
251857
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -250560,7 +251891,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250560
251891
  * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
250561
251892
  * without subscribing.
250562
251893
  */
250563
- runtimeState: DoorbellStatusSchema
251894
+ runtimeState: DoorbellStatusSchema,
251895
+ /**
251896
+ * 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.
251897
+ *
251898
+ * See `RuntimeStateDurability`. Enforced by
251899
+ * `scripts/check-runtime-state-durability.ts`.
251900
+ */
251901
+ durability: "restored"
250564
251902
  };
250565
251903
  var EnumSensorDateTimeFormatSchema = zod.z.enum([
250566
251904
  "date",
@@ -250590,7 +251928,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250590
251928
  schema: EnumSensorStatusSchema,
250591
251929
  kind: "push"
250592
251930
  },
250593
- runtimeState: EnumSensorStatusSchema
251931
+ runtimeState: EnumSensorStatusSchema,
251932
+ /**
251933
+ * Runtime-state durability: **restored** — as `numeric-sensor`; 80 devices.
251934
+ *
251935
+ * See `RuntimeStateDurability`. Enforced by
251936
+ * `scripts/check-runtime-state-durability.ts`.
251937
+ */
251938
+ durability: "restored",
251939
+ /** Clock fields: written, but excluded from the compare that decides
251940
+ * whether persisting is worth a SQLite commit. */
251941
+ volatileStateFields: ["lastFetchedAt"]
250594
251942
  };
250595
251943
  var EventFireSchema = zod.z.object({
250596
251944
  deviceId: zod.z.number(),
@@ -250616,7 +251964,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250616
251964
  schema: EventEmitterStatusSchema,
250617
251965
  kind: "push"
250618
251966
  },
250619
- runtimeState: EventEmitterStatusSchema
251967
+ runtimeState: EventEmitterStatusSchema,
251968
+ /**
251969
+ * Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
251970
+ *
251971
+ * See `RuntimeStateDurability`. Enforced by
251972
+ * `scripts/check-runtime-state-durability.ts`.
251973
+ */
251974
+ durability: "session"
250620
251975
  };
250621
251976
  var EventItemSchema = zod.z.object({
250622
251977
  id: zod.z.string(),
@@ -250915,7 +252270,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250915
252270
  * Runtime-state slice — mirrored by the kernel. UI fan speed
250916
252271
  * sliders read `percentage` for live updates.
250917
252272
  */
250918
- runtimeState: FanControlStatusSchema
252273
+ runtimeState: FanControlStatusSchema,
252274
+ /**
252275
+ * Runtime-state durability: **session** — as `brightness`.
252276
+ *
252277
+ * See `RuntimeStateDurability`. Enforced by
252278
+ * `scripts/check-runtime-state-durability.ts`.
252279
+ */
252280
+ durability: "session"
250919
252281
  };
250920
252282
  var FeatureProbeStatusSchema = zod.z.object({
250921
252283
  /**
@@ -250968,7 +252330,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250968
252330
  schema: FeatureProbeStatusSchema,
250969
252331
  kind: "push"
250970
252332
  },
250971
- runtimeState: FeatureProbeStatusSchema
252333
+ runtimeState: FeatureProbeStatusSchema,
252334
+ /**
252335
+ * 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.
252336
+ *
252337
+ * See `RuntimeStateDurability`. Enforced by
252338
+ * `scripts/check-runtime-state-durability.ts`.
252339
+ */
252340
+ durability: "session"
250972
252341
  };
250973
252342
  var FloodStatusSchema = zod.z.object({
250974
252343
  /** True when leak is currently detected. */
@@ -250987,7 +252356,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250987
252356
  schema: FloodStatusSchema,
250988
252357
  kind: "push"
250989
252358
  },
250990
- runtimeState: FloodStatusSchema
252359
+ runtimeState: FloodStatusSchema,
252360
+ /**
252361
+ * Runtime-state durability: **restored** — as `smoke`.
252362
+ *
252363
+ * See `RuntimeStateDurability`. Enforced by
252364
+ * `scripts/check-runtime-state-durability.ts`.
252365
+ */
252366
+ durability: "restored",
252367
+ /** Clock fields: written, but excluded from the compare that decides
252368
+ * whether persisting is worth a SQLite commit. */
252369
+ volatileStateFields: ["lastChangedAt"]
250991
252370
  };
250992
252371
  var GasStatusSchema = zod.z.object({
250993
252372
  detected: zod.z.boolean(),
@@ -251005,7 +252384,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251005
252384
  schema: GasStatusSchema,
251006
252385
  kind: "push"
251007
252386
  },
251008
- runtimeState: GasStatusSchema
252387
+ runtimeState: GasStatusSchema,
252388
+ /**
252389
+ * Runtime-state durability: **restored** — as `smoke`.
252390
+ *
252391
+ * See `RuntimeStateDurability`. Enforced by
252392
+ * `scripts/check-runtime-state-durability.ts`.
252393
+ */
252394
+ durability: "restored",
252395
+ /** Clock fields: written, but excluded from the compare that decides
252396
+ * whether persisting is worth a SQLite commit. */
252397
+ volatileStateFields: ["lastChangedAt"]
251009
252398
  };
251010
252399
  var HumidifierStatusSchema = zod.z.object({
251011
252400
  /** Whether the humidifier is currently on. */
@@ -251066,7 +252455,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251066
252455
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
251067
252456
  * slice for live humidity / mode changes.
251068
252457
  */
251069
- runtimeState: HumidifierStatusSchema
252458
+ runtimeState: HumidifierStatusSchema,
252459
+ /**
252460
+ * Runtime-state durability: **session** — as `climate-control`.
252461
+ *
252462
+ * See `RuntimeStateDurability`. Enforced by
252463
+ * `scripts/check-runtime-state-durability.ts`.
252464
+ */
252465
+ durability: "session"
251070
252466
  };
251071
252467
  var HumiditySensorStatusSchema = zod.z.object({
251072
252468
  /** Current relative humidity, 0..100. */
@@ -251094,7 +252490,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251094
252490
  schema: HumiditySensorStatusSchema,
251095
252491
  kind: "push"
251096
252492
  },
251097
- runtimeState: HumiditySensorStatusSchema
252493
+ runtimeState: HumiditySensorStatusSchema,
252494
+ /**
252495
+ * Runtime-state durability: **restored** — as `numeric-sensor` (67 of 75 writes were the clock alone).
252496
+ *
252497
+ * See `RuntimeStateDurability`. Enforced by
252498
+ * `scripts/check-runtime-state-durability.ts`.
252499
+ */
252500
+ durability: "restored",
252501
+ /** Clock fields: written, but excluded from the compare that decides
252502
+ * whether persisting is worth a SQLite commit. */
252503
+ volatileStateFields: ["lastFetchedAt"]
251098
252504
  };
251099
252505
  var ImageStatusSchema = zod.z.object({
251100
252506
  /** Absolute signed URL the browser loads directly. Null when the
@@ -251118,7 +252524,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251118
252524
  * Runtime-state slice — mirrored by the kernel. The UI reads `url`
251119
252525
  * directly and renders the still image.
251120
252526
  */
251121
- runtimeState: ImageStatusSchema
252527
+ runtimeState: ImageStatusSchema,
252528
+ /**
252529
+ * Runtime-state durability: **session** — a snapshot URL is a session-scoped handle; a restored one points at nothing.
252530
+ *
252531
+ * See `RuntimeStateDurability`. Enforced by
252532
+ * `scripts/check-runtime-state-durability.ts`.
252533
+ */
252534
+ durability: "session"
251122
252535
  };
251123
252536
  var ImageRotateSchema = zod.z.enum([
251124
252537
  "0",
@@ -251219,7 +252632,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251219
252632
  schema: ImageSettingsStatusSchema,
251220
252633
  kind: "poll"
251221
252634
  },
251222
- runtimeState: ImageSettingsStatusSchema
252635
+ runtimeState: ImageSettingsStatusSchema,
252636
+ /**
252637
+ * Runtime-state durability: **restored** — operator-set camera imaging; mutation-driven.
252638
+ *
252639
+ * See `RuntimeStateDurability`. Enforced by
252640
+ * `scripts/check-runtime-state-durability.ts`.
252641
+ */
252642
+ durability: "restored",
252643
+ /** Clock fields: written, but excluded from the compare that decides
252644
+ * whether persisting is worth a SQLite commit. */
252645
+ volatileStateFields: ["lastFetchedAt"]
251223
252646
  };
251224
252647
  var IntegrationWithStateSchema = zod.z.object({
251225
252648
  id: zod.z.string(),
@@ -251550,7 +252973,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251550
252973
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
251551
252974
  * slice for live activity + battery changes.
251552
252975
  */
251553
- runtimeState: LawnMowerControlStatusSchema
252976
+ runtimeState: LawnMowerControlStatusSchema,
252977
+ /**
252978
+ * Runtime-state durability: **session** — as `vacuum-control`.
252979
+ *
252980
+ * See `RuntimeStateDurability`. Enforced by
252981
+ * `scripts/check-runtime-state-durability.ts`.
252982
+ */
252983
+ durability: "session"
251554
252984
  };
251555
252985
  var InterfaceKindEnum = zod.z.enum([
251556
252986
  "lan",
@@ -251799,7 +253229,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251799
253229
  * read `state` and disable themselves during `locking`/`unlocking`
251800
253230
  * transitions.
251801
253231
  */
251802
- runtimeState: LockControlStatusSchema
253232
+ runtimeState: LockControlStatusSchema,
253233
+ /**
253234
+ * Runtime-state durability: **restored** — a lock left locked must still read locked.
253235
+ *
253236
+ * See `RuntimeStateDurability`. Enforced by
253237
+ * `scripts/check-runtime-state-durability.ts`.
253238
+ */
253239
+ durability: "restored",
253240
+ /** Clock fields: written, but excluded from the compare that decides
253241
+ * whether persisting is worth a SQLite commit. */
253242
+ volatileStateFields: ["lastChangedAt"]
251803
253243
  };
251804
253244
  var MediaPlayerStateSchema = zod.z.enum([
251805
253245
  "off",
@@ -251949,7 +253389,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251949
253389
  * full slice for live now-playing, volume, and progress updates
251950
253390
  * without polling.
251951
253391
  */
251952
- runtimeState: MediaPlayerStatusSchema
253392
+ runtimeState: MediaPlayerStatusSchema,
253393
+ /**
253394
+ * Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
253395
+ *
253396
+ * See `RuntimeStateDurability`. Enforced by
253397
+ * `scripts/check-runtime-state-durability.ts`.
253398
+ */
253399
+ durability: "session"
251953
253400
  };
251954
253401
  var MeshEndpointSchema = zod.z.object({
251955
253402
  /** Stable identifier within the provider (e.g. `mesh-ipv4`, `magicdns`, `funnel`). */
@@ -252233,7 +253680,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252233
253680
  * `device.state.motion.value`. Reads never invoke the provider, so
252234
253681
  * UIs and other addons can poll the cached state safely.
252235
253682
  */
252236
- runtimeState: MotionStatusSchema
253683
+ runtimeState: MotionStatusSchema,
253684
+ /**
253685
+ * 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.
253686
+ *
253687
+ * See `RuntimeStateDurability`. Enforced by
253688
+ * `scripts/check-runtime-state-durability.ts`.
253689
+ */
253690
+ durability: "session"
252237
253691
  };
252238
253692
  var MotionTriggerStatusSchema = zod.z.object({
252239
253693
  enabled: zod.z.boolean(),
@@ -252270,7 +253724,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252270
253724
  schema: MotionTriggerStatusSchema,
252271
253725
  kind: "command-driven"
252272
253726
  },
252273
- runtimeState: MotionTriggerRuntimeStateSchema
253727
+ runtimeState: MotionTriggerRuntimeStateSchema,
253728
+ /**
253729
+ * 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.
253730
+ *
253731
+ * See `RuntimeStateDurability`. Enforced by
253732
+ * `scripts/check-runtime-state-durability.ts`.
253733
+ */
253734
+ durability: "session"
252274
253735
  };
252275
253736
  var MotionZoneRegionSchema = zod.z.object({
252276
253737
  id: zod.z.number(),
@@ -252325,7 +253786,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252325
253786
  schema: MotionZoneStatusSchema,
252326
253787
  kind: "poll"
252327
253788
  },
252328
- runtimeState: MotionZoneStatusSchema
253789
+ runtimeState: MotionZoneStatusSchema,
253790
+ /**
253791
+ * 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.
253792
+ *
253793
+ * See `RuntimeStateDurability`. Enforced by
253794
+ * `scripts/check-runtime-state-durability.ts`.
253795
+ */
253796
+ durability: "restored",
253797
+ /** Clock fields: written, but excluded from the compare that decides
253798
+ * whether persisting is worth a SQLite commit. */
253799
+ volatileStateFields: ["lastFetchedAt"]
252329
253800
  };
252330
253801
  var NativeObjectClassEnum = zod.z.enum([
252331
253802
  "person",
@@ -252388,7 +253859,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252388
253859
  schema: NativeObjectDetectionStatusSchema,
252389
253860
  kind: "push"
252390
253861
  },
252391
- runtimeState: NativeObjectDetectionRuntimeStateSchema
253862
+ runtimeState: NativeObjectDetectionRuntimeStateSchema,
253863
+ /**
253864
+ * 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.
253865
+ *
253866
+ * See `RuntimeStateDurability`. Enforced by
253867
+ * `scripts/check-runtime-state-durability.ts`.
253868
+ */
253869
+ durability: "restored",
253870
+ /** Clock fields: written, but excluded from the compare that decides
253871
+ * whether persisting is worth a SQLite commit. */
253872
+ volatileStateFields: ["lastFetchedAt"]
252392
253873
  };
252393
253874
  var StreamNetworkStatsSchema = zod.z.object({
252394
253875
  nominalBitrateKbps: zod.z.number(),
@@ -252711,7 +254192,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252711
254192
  * form reads `supports` to gate optional fields; history pane reads
252712
254193
  * `lastSentAt` / `lastError` / `queueDepth`.
252713
254194
  */
252714
- runtimeState: NotifierStatusSchema
254195
+ runtimeState: NotifierStatusSchema,
254196
+ /**
254197
+ * Runtime-state durability: **session** — live queue depth and last-send state; a restored queue depth describes a queue that no longer exists.
254198
+ *
254199
+ * See `RuntimeStateDurability`. Enforced by
254200
+ * `scripts/check-runtime-state-durability.ts`.
254201
+ */
254202
+ durability: "session"
252715
254203
  };
252716
254204
  var NumericSensorStatusSchema = zod.z.object({
252717
254205
  value: zod.z.number(),
@@ -252737,7 +254225,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252737
254225
  schema: NumericSensorStatusSchema,
252738
254226
  kind: "push"
252739
254227
  },
252740
- runtimeState: NumericSensorStatusSchema
254228
+ runtimeState: NumericSensorStatusSchema,
254229
+ /**
254230
+ * 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.
254231
+ *
254232
+ * See `RuntimeStateDurability`. Enforced by
254233
+ * `scripts/check-runtime-state-durability.ts`.
254234
+ */
254235
+ durability: "restored",
254236
+ /** Clock fields: written, but excluded from the compare that decides
254237
+ * whether persisting is worth a SQLite commit. */
254238
+ volatileStateFields: ["lastFetchedAt"]
252741
254239
  };
252742
254240
  var OsdOverlayKindEnum = zod.z.enum([
252743
254241
  "text",
@@ -253162,7 +254660,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253162
254660
  * the full slice via `device.state.petFeeder.value` and refresh on
253163
254661
  * every poll without re-querying the provider.
253164
254662
  */
253165
- runtimeState: PetFeederStatusSchema
254663
+ runtimeState: PetFeederStatusSchema,
254664
+ /**
254665
+ * Runtime-state durability: **session** — live appliance state re-published on connect.
254666
+ *
254667
+ * See `RuntimeStateDurability`. Enforced by
254668
+ * `scripts/check-runtime-state-durability.ts`.
254669
+ */
254670
+ durability: "session"
253166
254671
  };
253167
254672
  var VehicleSchema = zod.z.object({
253168
254673
  id: zod.z.string(),
@@ -253496,7 +255001,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253496
255001
  schema: PowerMeterStatusSchema,
253497
255002
  kind: "push"
253498
255003
  },
253499
- runtimeState: PowerMeterStatusSchema
255004
+ runtimeState: PowerMeterStatusSchema,
255005
+ /**
255006
+ * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
255007
+ *
255008
+ * See `RuntimeStateDurability`. Enforced by
255009
+ * `scripts/check-runtime-state-durability.ts`.
255010
+ */
255011
+ durability: "restored",
255012
+ /** Clock fields: written, but excluded from the compare that decides
255013
+ * whether persisting is worth a SQLite commit. */
255014
+ volatileStateFields: ["lastFetchedAt"]
253500
255015
  };
253501
255016
  var GpsLocationSchema = zod.z.object({
253502
255017
  /** Latitude in decimal degrees, -90..90. */
@@ -253537,7 +255052,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253537
255052
  * the map pin is rendered (use `DeviceFeature.PresenceGps` for the
253538
255053
  * pre-fetch fast-path check).
253539
255054
  */
253540
- runtimeState: PresenceStatusSchema
255055
+ runtimeState: PresenceStatusSchema,
255056
+ /**
255057
+ * Runtime-state durability: **restored** — occupancy-relevant: the restored state is what an occupancy rule compares the first post-restart observation against.
255058
+ *
255059
+ * See `RuntimeStateDurability`. Enforced by
255060
+ * `scripts/check-runtime-state-durability.ts`.
255061
+ */
255062
+ durability: "restored",
255063
+ /** Clock fields: written, but excluded from the compare that decides
255064
+ * whether persisting is worth a SQLite commit. */
255065
+ volatileStateFields: ["lastChangedAt"]
253541
255066
  };
253542
255067
  var PressureSensorStatusSchema = zod.z.object({
253543
255068
  /** Current pressure in hPa. */
@@ -253565,7 +255090,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253565
255090
  schema: PressureSensorStatusSchema,
253566
255091
  kind: "push"
253567
255092
  },
253568
- runtimeState: PressureSensorStatusSchema
255093
+ runtimeState: PressureSensorStatusSchema,
255094
+ /**
255095
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
255096
+ *
255097
+ * See `RuntimeStateDurability`. Enforced by
255098
+ * `scripts/check-runtime-state-durability.ts`.
255099
+ */
255100
+ durability: "restored",
255101
+ /** Clock fields: written, but excluded from the compare that decides
255102
+ * whether persisting is worth a SQLite commit. */
255103
+ volatileStateFields: ["lastFetchedAt"]
253569
255104
  };
253570
255105
  var PrivacyMaskShapeSchema = zod.z.discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
253571
255106
  var PrivacyMaskRegionSchema = zod.z.object({
@@ -253664,7 +255199,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253664
255199
  schema: PrivacyMaskStatusSchema,
253665
255200
  kind: "poll"
253666
255201
  },
253667
- runtimeState: PrivacyMaskStatusSchema
255202
+ runtimeState: PrivacyMaskStatusSchema,
255203
+ /**
255204
+ * Runtime-state durability: **restored** — operator-drawn regions, zero real churn — 22 writes in 25 minutes, every one of them the clock.
255205
+ *
255206
+ * See `RuntimeStateDurability`. Enforced by
255207
+ * `scripts/check-runtime-state-durability.ts`.
255208
+ */
255209
+ durability: "restored",
255210
+ /** Clock fields: written, but excluded from the compare that decides
255211
+ * whether persisting is worth a SQLite commit. */
255212
+ volatileStateFields: ["lastFetchedAt"]
253668
255213
  };
253669
255214
  function summarisePrivacyAudio(profiles) {
253670
255215
  if (profiles.length === 0) return null;
@@ -253854,7 +255399,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253854
255399
  * fetch / cache / fallback logic out of the four cap methods —
253855
255400
  * they become trampolines over `runtimeState`.
253856
255401
  */
253857
- runtimeState: PtzAutotrackRuntimeStateSchema
255402
+ runtimeState: PtzAutotrackRuntimeStateSchema,
255403
+ /**
255404
+ * Runtime-state durability: **session** — mirrors the camera's own autotrack config, re-read on connect.
255405
+ *
255406
+ * See `RuntimeStateDurability`. Enforced by
255407
+ * `scripts/check-runtime-state-durability.ts`.
255408
+ */
255409
+ durability: "session"
253858
255410
  };
253859
255411
  var rebootCapability = {
253860
255412
  name: "reboot",
@@ -254524,7 +256076,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254524
256076
  schema: SceneMonitorStatusSchema,
254525
256077
  kind: "push"
254526
256078
  },
254527
- runtimeState: SceneMonitorStatusSchema
256079
+ runtimeState: SceneMonitorStatusSchema,
256080
+ /**
256081
+ * Runtime-state durability: **session** — re-derived from the current scene on the next evaluation.
256082
+ *
256083
+ * See `RuntimeStateDurability`. Enforced by
256084
+ * `scripts/check-runtime-state-durability.ts`.
256085
+ */
256086
+ durability: "session"
254528
256087
  };
254529
256088
  var ZoneRuleModeEnum = zod.z.enum(["include", "exclude"]);
254530
256089
  var ZoneRuleSchema = zod.z.object({
@@ -254626,7 +256185,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254626
256185
  * `isRunning` to render a spinner during execution and surfaces
254627
256186
  * `lastError` / `lastRunSuccess` in the recent-runs panel.
254628
256187
  */
254629
- runtimeState: ScriptRunnerStatusSchema
256188
+ runtimeState: ScriptRunnerStatusSchema,
256189
+ /**
256190
+ * Runtime-state durability: **session** — a restored `isRunning: true` describes a process that died with the previous hub.
256191
+ *
256192
+ * See `RuntimeStateDurability`. Enforced by
256193
+ * `scripts/check-runtime-state-durability.ts`.
256194
+ */
256195
+ durability: "session"
254630
256196
  };
254631
256197
  var SmokeStatusSchema = zod.z.object({
254632
256198
  detected: zod.z.boolean(),
@@ -254644,7 +256210,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254644
256210
  schema: SmokeStatusSchema,
254645
256211
  kind: "push"
254646
256212
  },
254647
- runtimeState: SmokeStatusSchema
256213
+ runtimeState: SmokeStatusSchema,
256214
+ /**
256215
+ * Runtime-state durability: **restored** — a safety sensor must not read "clear" merely because the hub restarted.
256216
+ *
256217
+ * See `RuntimeStateDurability`. Enforced by
256218
+ * `scripts/check-runtime-state-durability.ts`.
256219
+ */
256220
+ durability: "restored",
256221
+ /** Clock fields: written, but excluded from the compare that decides
256222
+ * whether persisting is worth a SQLite commit. */
256223
+ volatileStateFields: ["lastChangedAt"]
254648
256224
  };
254649
256225
  var CamStreamDescriptorSchema = zod.z.object({
254650
256226
  camStreamId: zod.z.string().min(1),
@@ -254795,7 +256371,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254795
256371
  schema: StreamParamsStatusSchema,
254796
256372
  kind: "poll"
254797
256373
  },
254798
- runtimeState: StreamParamsStatusSchema
256374
+ runtimeState: StreamParamsStatusSchema,
256375
+ /**
256376
+ * Runtime-state durability: **restored** — operator-set encoder profile; mutation-driven.
256377
+ *
256378
+ * See `RuntimeStateDurability`. Enforced by
256379
+ * `scripts/check-runtime-state-durability.ts`.
256380
+ */
256381
+ durability: "restored",
256382
+ /** Clock fields: written, but excluded from the compare that decides
256383
+ * whether persisting is worth a SQLite commit. */
256384
+ volatileStateFields: ["lastFetchedAt"]
254799
256385
  };
254800
256386
  var STREAM_PROFILE_META = [
254801
256387
  {
@@ -255066,6 +256652,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255066
256652
  * not need to re-query the provider after a setState mutation.
255067
256653
  */
255068
256654
  runtimeState: SwitchStatusSchema,
256655
+ /**
256656
+ * Runtime-state durability: **restored** — device state an operator reads as authoritative; 55 devices, transition-driven.
256657
+ *
256658
+ * See `RuntimeStateDurability`. Enforced by
256659
+ * `scripts/check-runtime-state-durability.ts`.
256660
+ */
256661
+ durability: "restored",
256662
+ /** Clock fields: written, but excluded from the compare that decides
256663
+ * whether persisting is worth a SQLite commit. */
256664
+ volatileStateFields: ["lastChangedAt"],
255069
256665
  settings: { bindings: [{
255070
256666
  kind: "scalar",
255071
256667
  statusPath: "on",
@@ -255138,7 +256734,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255138
256734
  schema: TamperStatusSchema,
255139
256735
  kind: "push"
255140
256736
  },
255141
- runtimeState: TamperStatusSchema
256737
+ runtimeState: TamperStatusSchema,
256738
+ /**
256739
+ * Runtime-state durability: **restored** — as `smoke`.
256740
+ *
256741
+ * See `RuntimeStateDurability`. Enforced by
256742
+ * `scripts/check-runtime-state-durability.ts`.
256743
+ */
256744
+ durability: "restored",
256745
+ /** Clock fields: written, but excluded from the compare that decides
256746
+ * whether persisting is worth a SQLite commit. */
256747
+ volatileStateFields: ["lastChangedAt"]
255142
256748
  };
255143
256749
  var TemperatureSensorStatusSchema = zod.z.object({
255144
256750
  /** Current temperature in Celsius. */
@@ -255167,7 +256773,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255167
256773
  schema: TemperatureSensorStatusSchema,
255168
256774
  kind: "push"
255169
256775
  },
255170
- runtimeState: TemperatureSensorStatusSchema
256776
+ runtimeState: TemperatureSensorStatusSchema,
256777
+ /**
256778
+ * Runtime-state durability: **restored** — as `numeric-sensor` (69 of 125 writes were the clock alone).
256779
+ *
256780
+ * See `RuntimeStateDurability`. Enforced by
256781
+ * `scripts/check-runtime-state-durability.ts`.
256782
+ */
256783
+ durability: "restored",
256784
+ /** Clock fields: written, but excluded from the compare that decides
256785
+ * whether persisting is worth a SQLite commit. */
256786
+ volatileStateFields: ["lastFetchedAt"]
255171
256787
  };
255172
256788
  var ToastSchema = zod.z.object({
255173
256789
  title: zod.z.string(),
@@ -255218,7 +256834,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255218
256834
  schema: UpdateStatusSchema,
255219
256835
  kind: "poll"
255220
256836
  },
255221
- runtimeState: UpdateStatusSchema
256837
+ runtimeState: UpdateStatusSchema,
256838
+ /**
256839
+ * Runtime-state durability: **session** — a restored `inProgress: true` describes an update that is no longer running; versions are re-probed at boot.
256840
+ *
256841
+ * See `RuntimeStateDurability`. Enforced by
256842
+ * `scripts/check-runtime-state-durability.ts`.
256843
+ */
256844
+ durability: "session"
255222
256845
  };
255223
256846
  var UserSummarySchema = zod.z.object({
255224
256847
  id: zod.z.string(),
@@ -255552,7 +257175,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255552
257175
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
255553
257176
  * slice for live state + battery + fan-speed changes.
255554
257177
  */
255555
- runtimeState: VacuumControlStatusSchema
257178
+ runtimeState: VacuumControlStatusSchema,
257179
+ /**
257180
+ * Runtime-state durability: **session** — as `media-player` — a restored `state: cleaning` is a robot that is not cleaning.
257181
+ *
257182
+ * See `RuntimeStateDurability`. Enforced by
257183
+ * `scripts/check-runtime-state-durability.ts`.
257184
+ */
257185
+ durability: "session"
255556
257186
  };
255557
257187
  var ValveStateSchema = zod.z.enum([
255558
257188
  "open",
@@ -255605,7 +257235,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255605
257235
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
255606
257236
  * slice for live position changes during a move.
255607
257237
  */
255608
- runtimeState: ValveStatusSchema
257238
+ runtimeState: ValveStatusSchema,
257239
+ /**
257240
+ * Runtime-state durability: **session** — as `brightness`.
257241
+ *
257242
+ * See `RuntimeStateDurability`. Enforced by
257243
+ * `scripts/check-runtime-state-durability.ts`.
257244
+ */
257245
+ durability: "session"
255609
257246
  };
255610
257247
  var VibrationStatusSchema = zod.z.object({
255611
257248
  detected: zod.z.boolean(),
@@ -255623,7 +257260,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255623
257260
  schema: VibrationStatusSchema,
255624
257261
  kind: "push"
255625
257262
  },
255626
- runtimeState: VibrationStatusSchema
257263
+ runtimeState: VibrationStatusSchema,
257264
+ /**
257265
+ * Runtime-state durability: **restored** — as `smoke`.
257266
+ *
257267
+ * See `RuntimeStateDurability`. Enforced by
257268
+ * `scripts/check-runtime-state-durability.ts`.
257269
+ */
257270
+ durability: "restored",
257271
+ /** Clock fields: written, but excluded from the compare that decides
257272
+ * whether persisting is worth a SQLite commit. */
257273
+ volatileStateFields: ["lastChangedAt"]
255627
257274
  };
255628
257275
  var WaterHeaterStatusSchema = zod.z.object({
255629
257276
  /** Current measured temperature. Null when not reported. */
@@ -255683,7 +257330,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255683
257330
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
255684
257331
  * slice for live temperature / mode / away changes.
255685
257332
  */
255686
- runtimeState: WaterHeaterStatusSchema
257333
+ runtimeState: WaterHeaterStatusSchema,
257334
+ /**
257335
+ * Runtime-state durability: **session** — as `climate-control`.
257336
+ *
257337
+ * See `RuntimeStateDurability`. Enforced by
257338
+ * `scripts/check-runtime-state-durability.ts`.
257339
+ */
257340
+ durability: "session"
255687
257341
  };
255688
257342
  var WeatherStatusSchema = zod.z.object({
255689
257343
  /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
@@ -255723,7 +257377,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255723
257377
  * Runtime-state slice — mirrored by the kernel. The UI reads the
255724
257378
  * current conditions directly from the slice on each weather push.
255725
257379
  */
255726
- runtimeState: WeatherStatusSchema
257380
+ runtimeState: WeatherStatusSchema,
257381
+ /**
257382
+ * Runtime-state durability: **session** — a forecast is stale the moment the hub is down; the provider re-fetches on connect.
257383
+ *
257384
+ * See `RuntimeStateDurability`. Enforced by
257385
+ * `scripts/check-runtime-state-durability.ts`.
257386
+ */
257387
+ durability: "session"
255727
257388
  };
255728
257389
  var PerScopeBreakdownSchema = zod.z.object({
255729
257390
  /** Total tracked objects in this scope (frame / zone / unzoned). */
@@ -255832,7 +257493,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255832
257493
  * automatically; the explicit `getCurrentSnapshot` cap method is
255833
257494
  * still useful for one-off polls without a subscription.
255834
257495
  */
255835
- runtimeState: CameraOccupancySnapshotSchema
257496
+ runtimeState: CameraOccupancySnapshotSchema,
257497
+ /**
257498
+ * Runtime-state durability: **session** — per-frame analytics; with `audio-metrics` it is ~90 % of the offered write rate. Re-derived on the next frame.
257499
+ *
257500
+ * See `RuntimeStateDurability`. Enforced by
257501
+ * `scripts/check-runtime-state-durability.ts`.
257502
+ */
257503
+ durability: "session"
255836
257504
  };
255837
257505
  var ZoneRuleStageEnum = zod.z.enum([
255838
257506
  "motion",
@@ -255884,7 +257552,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255884
257552
  motion: zod.z.array(ZoneRuleSchema).readonly(),
255885
257553
  detection: zod.z.array(ZoneRuleSchema).readonly(),
255886
257554
  package: zod.z.array(ZoneRuleSchema).readonly()
255887
- })
257555
+ }),
257556
+ /**
257557
+ * Runtime-state durability: **restored** — operator intent, mutation-only, same argument as `zones`.
257558
+ *
257559
+ * See `RuntimeStateDurability`. Enforced by
257560
+ * `scripts/check-runtime-state-durability.ts`.
257561
+ */
257562
+ durability: "restored"
255888
257563
  };
255889
257564
  var PIPELINE_FLOW_CAPABILITY_NAMES = [
255890
257565
  "decoder",
@@ -258156,11 +259831,40 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
258156
259831
  } catch {
258157
259832
  }
258158
259833
  }
259834
+ /**
259835
+ * ONE device's bindings, not the fleet's.
259836
+ *
259837
+ * This runs on every `capability.binding-changed` AND on every
259838
+ * `device.registered`, per mirror. It used to answer that with
259839
+ * `getAllBindings` and `.find()` the one row — measured on the live
259840
+ * hub (524 devices): 903 ms and 373 KB per call, against 21 ms and
259841
+ * 613 bytes for `getBindings({deviceId})`.
259842
+ *
259843
+ * Adopting 20 Home Assistant devices registers 166 devices (parents +
259844
+ * accessories), so an operator with the admin UI open bought 166
259845
+ * full-fleet binding sweeps: ~150 s of hub main-thread time inside a
259846
+ * 219 s adoption, with every other RPC — including the adoption's own —
259847
+ * queued behind them. Same defect, same fix, as `refreshDeviceMetadata`
259848
+ * below (`listAll` → `getDevice`).
259849
+ *
259850
+ * Removal is the destructive direction (every proxy handle a consumer
259851
+ * holds goes inert), and an EMPTY binding list is not a claim that the
259852
+ * device is gone — a device can legitimately bind nothing. So it needs
259853
+ * a second read to agree (D49); `device.unregistered` remains the
259854
+ * primary removal path.
259855
+ */
258159
259856
  async refreshBinding(deviceId) {
258160
259857
  try {
258161
- const fresh = (await this.api.deviceManager.getAllBindings.query({})).find((b) => b.deviceId === deviceId);
258162
- if (fresh) this.bindings.set(deviceId, fresh);
258163
- else this.applyDeviceRemoval(deviceId);
259858
+ const fresh = await this.api.deviceManager.getBindings.query({ deviceId });
259859
+ if (fresh.entries.length > 0) {
259860
+ this.bindings.set(deviceId, fresh);
259861
+ return;
259862
+ }
259863
+ if (await this.api.deviceManager.getDevice.query({ deviceId }) === null) {
259864
+ this.applyDeviceRemoval(deviceId);
259865
+ return;
259866
+ }
259867
+ this.bindings.set(deviceId, fresh);
258164
259868
  } catch {
258165
259869
  }
258166
259870
  }
@@ -265134,6 +266838,301 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
265134
266838
  for (const [capName, capKind] of Object.entries(CAP_PROVIDER_KIND_MAP)) if (capKind === kind) out.push(capName);
265135
266839
  return out;
265136
266840
  }
266841
+ var RUNTIME_STATE_POLICY = {
266842
+ "air-quality-sensor": {
266843
+ durability: "restored",
266844
+ volatileFields: ["lastFetchedAt"]
266845
+ },
266846
+ "alarm-panel": {
266847
+ durability: "restored",
266848
+ volatileFields: ["lastChangedAt"]
266849
+ },
266850
+ "ambient-light-sensor": {
266851
+ durability: "restored",
266852
+ volatileFields: ["lastFetchedAt"]
266853
+ },
266854
+ "audio-metrics": {
266855
+ durability: "session",
266856
+ volatileFields: []
266857
+ },
266858
+ "automation-control": {
266859
+ durability: "session",
266860
+ volatileFields: []
266861
+ },
266862
+ "battery": {
266863
+ durability: "restored",
266864
+ volatileFields: ["lastUpdated"]
266865
+ },
266866
+ "binary": {
266867
+ durability: "restored",
266868
+ volatileFields: ["lastChangedAt"]
266869
+ },
266870
+ "brightness": {
266871
+ durability: "session",
266872
+ volatileFields: []
266873
+ },
266874
+ "camera-streams": {
266875
+ durability: "session",
266876
+ volatileFields: []
266877
+ },
266878
+ "carbon-monoxide": {
266879
+ durability: "restored",
266880
+ volatileFields: ["lastChangedAt"]
266881
+ },
266882
+ "climate-control": {
266883
+ durability: "session",
266884
+ volatileFields: []
266885
+ },
266886
+ "color": {
266887
+ durability: "session",
266888
+ volatileFields: []
266889
+ },
266890
+ "connectivity": {
266891
+ durability: "restored",
266892
+ volatileFields: ["lastChangedAt"]
266893
+ },
266894
+ "consumables": {
266895
+ durability: "session",
266896
+ volatileFields: []
266897
+ },
266898
+ "contact": {
266899
+ durability: "restored",
266900
+ volatileFields: ["lastChangedAt"]
266901
+ },
266902
+ "control": {
266903
+ durability: "session",
266904
+ volatileFields: []
266905
+ },
266906
+ "cover": {
266907
+ durability: "restored",
266908
+ volatileFields: ["lastChangedAt"]
266909
+ },
266910
+ "day-night": {
266911
+ durability: "restored",
266912
+ volatileFields: ["lastFetchedAt"]
266913
+ },
266914
+ "device-discovery": {
266915
+ durability: "session",
266916
+ volatileFields: []
266917
+ },
266918
+ "device-status": {
266919
+ durability: "restored",
266920
+ volatileFields: ["lastChangedAt"]
266921
+ },
266922
+ "doorbell": {
266923
+ durability: "restored",
266924
+ volatileFields: []
266925
+ },
266926
+ "enum-sensor": {
266927
+ durability: "restored",
266928
+ volatileFields: ["lastFetchedAt"]
266929
+ },
266930
+ "event-emitter": {
266931
+ durability: "session",
266932
+ volatileFields: []
266933
+ },
266934
+ "fan-control": {
266935
+ durability: "session",
266936
+ volatileFields: []
266937
+ },
266938
+ "feature-probe": {
266939
+ durability: "session",
266940
+ volatileFields: []
266941
+ },
266942
+ "flood": {
266943
+ durability: "restored",
266944
+ volatileFields: ["lastChangedAt"]
266945
+ },
266946
+ "gas": {
266947
+ durability: "restored",
266948
+ volatileFields: ["lastChangedAt"]
266949
+ },
266950
+ "humidifier": {
266951
+ durability: "session",
266952
+ volatileFields: []
266953
+ },
266954
+ "humidity-sensor": {
266955
+ durability: "restored",
266956
+ volatileFields: ["lastFetchedAt"]
266957
+ },
266958
+ "image": {
266959
+ durability: "session",
266960
+ volatileFields: []
266961
+ },
266962
+ "image-settings": {
266963
+ durability: "restored",
266964
+ volatileFields: ["lastFetchedAt"]
266965
+ },
266966
+ "lawn-mower-control": {
266967
+ durability: "session",
266968
+ volatileFields: []
266969
+ },
266970
+ "lock-control": {
266971
+ durability: "restored",
266972
+ volatileFields: ["lastChangedAt"]
266973
+ },
266974
+ "media-player": {
266975
+ durability: "session",
266976
+ volatileFields: []
266977
+ },
266978
+ "motion": {
266979
+ durability: "session",
266980
+ volatileFields: []
266981
+ },
266982
+ "motion-trigger": {
266983
+ durability: "session",
266984
+ volatileFields: []
266985
+ },
266986
+ "motion-zones": {
266987
+ durability: "restored",
266988
+ volatileFields: ["lastFetchedAt"]
266989
+ },
266990
+ "native-object-detection": {
266991
+ durability: "restored",
266992
+ volatileFields: ["lastFetchedAt"]
266993
+ },
266994
+ "notifier": {
266995
+ durability: "session",
266996
+ volatileFields: []
266997
+ },
266998
+ "numeric-sensor": {
266999
+ durability: "restored",
267000
+ volatileFields: ["lastFetchedAt"]
267001
+ },
267002
+ "pet-feeder": {
267003
+ durability: "session",
267004
+ volatileFields: []
267005
+ },
267006
+ "power-meter": {
267007
+ durability: "restored",
267008
+ volatileFields: ["lastFetchedAt"]
267009
+ },
267010
+ "presence": {
267011
+ durability: "restored",
267012
+ volatileFields: ["lastChangedAt"]
267013
+ },
267014
+ "pressure-sensor": {
267015
+ durability: "restored",
267016
+ volatileFields: ["lastFetchedAt"]
267017
+ },
267018
+ "privacy-mask": {
267019
+ durability: "restored",
267020
+ volatileFields: ["lastFetchedAt"]
267021
+ },
267022
+ "ptz-autotrack": {
267023
+ durability: "session",
267024
+ volatileFields: []
267025
+ },
267026
+ "scene-monitor": {
267027
+ durability: "session",
267028
+ volatileFields: []
267029
+ },
267030
+ "script-runner": {
267031
+ durability: "session",
267032
+ volatileFields: []
267033
+ },
267034
+ "smoke": {
267035
+ durability: "restored",
267036
+ volatileFields: ["lastChangedAt"]
267037
+ },
267038
+ "stream-params": {
267039
+ durability: "restored",
267040
+ volatileFields: ["lastFetchedAt"]
267041
+ },
267042
+ "switch": {
267043
+ durability: "restored",
267044
+ volatileFields: ["lastChangedAt"]
267045
+ },
267046
+ "tamper": {
267047
+ durability: "restored",
267048
+ volatileFields: ["lastChangedAt"]
267049
+ },
267050
+ "temperature-sensor": {
267051
+ durability: "restored",
267052
+ volatileFields: ["lastFetchedAt"]
267053
+ },
267054
+ "update": {
267055
+ durability: "session",
267056
+ volatileFields: []
267057
+ },
267058
+ "vacuum-control": {
267059
+ durability: "session",
267060
+ volatileFields: []
267061
+ },
267062
+ "valve": {
267063
+ durability: "session",
267064
+ volatileFields: []
267065
+ },
267066
+ "vibration": {
267067
+ durability: "restored",
267068
+ volatileFields: ["lastChangedAt"]
267069
+ },
267070
+ "water-heater": {
267071
+ durability: "session",
267072
+ volatileFields: []
267073
+ },
267074
+ "weather": {
267075
+ durability: "session",
267076
+ volatileFields: []
267077
+ },
267078
+ "zone-analytics": {
267079
+ durability: "session",
267080
+ volatileFields: []
267081
+ },
267082
+ "zone-rules": {
267083
+ durability: "restored",
267084
+ volatileFields: []
267085
+ },
267086
+ "zones": {
267087
+ durability: "restored",
267088
+ volatileFields: []
267089
+ }
267090
+ };
267091
+ function runtimeStatePolicyFor(capName) {
267092
+ return RUNTIME_STATE_POLICY[capName] ?? SESSION_ONLY_POLICY;
267093
+ }
267094
+ var SESSION_ONLY_POLICY = {
267095
+ durability: "session",
267096
+ volatileFields: []
267097
+ };
267098
+ function isRestoredCap(capName) {
267099
+ return runtimeStatePolicyFor(capName).durability === "restored";
267100
+ }
267101
+ var RESTORED_CAP_NAMES = [
267102
+ "air-quality-sensor",
267103
+ "alarm-panel",
267104
+ "ambient-light-sensor",
267105
+ "battery",
267106
+ "binary",
267107
+ "carbon-monoxide",
267108
+ "connectivity",
267109
+ "contact",
267110
+ "cover",
267111
+ "day-night",
267112
+ "device-status",
267113
+ "doorbell",
267114
+ "enum-sensor",
267115
+ "flood",
267116
+ "gas",
267117
+ "humidity-sensor",
267118
+ "image-settings",
267119
+ "lock-control",
267120
+ "motion-zones",
267121
+ "native-object-detection",
267122
+ "numeric-sensor",
267123
+ "power-meter",
267124
+ "presence",
267125
+ "pressure-sensor",
267126
+ "privacy-mask",
267127
+ "smoke",
267128
+ "stream-params",
267129
+ "switch",
267130
+ "tamper",
267131
+ "temperature-sensor",
267132
+ "vibration",
267133
+ "zone-rules",
267134
+ "zones"
267135
+ ];
265137
267136
  var SCOPE_PRESETS = [
265138
267137
  {
265139
267138
  id: "camera-viewer",
@@ -265777,8 +267776,99 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
265777
267776
  }
265778
267777
  };
265779
267778
  }
267779
+ var AUDIO_KIND_PREFIX = "audio-";
267780
+ function normalizeAudioLabel(value) {
267781
+ return value.startsWith(AUDIO_KIND_PREFIX) ? value.slice(6) : value;
267782
+ }
267783
+ var NC_AUDIO_DB_MIN = -96;
267784
+ var NC_AUDIO_DB_MAX = 0;
267785
+ var NC_AUDIO_DB_STEP = 3;
267786
+ var NC_AUDIO_DB_OFFERED = -30;
267787
+ var NC_AUDIO_HIT_PERCENT_MIN = 1;
267788
+ var NC_AUDIO_HIT_PERCENT_MAX = 100;
267789
+ var NC_AUDIO_SAMPLING_MIN_SEC = 1;
267790
+ var NC_AUDIO_SAMPLING_MAX_SEC = 300;
267791
+ var NC_AUDIO_DEFAULTS = {
267792
+ hitPercent: 60,
267793
+ samplingSeconds: 10
267794
+ };
267795
+ function audioOrDefaults(value) {
267796
+ return value ?? NC_AUDIO_DEFAULTS;
267797
+ }
267798
+ function audioIsFailClosed(value) {
267799
+ if (value === void 0) return false;
267800
+ return value.dbThreshold === void 0 && (value.labels === void 0 || value.labels.length === 0);
267801
+ }
267802
+ function clampInt(value, min, max) {
267803
+ if (!Number.isFinite(value)) return min;
267804
+ return Math.min(max, Math.max(min, Math.round(value)));
267805
+ }
267806
+ function has(patch, key) {
267807
+ return Object.prototype.hasOwnProperty.call(patch, key);
267808
+ }
267809
+ function patchAudio(current, patch) {
267810
+ const base = audioOrDefaults(current);
267811
+ const labels = has(patch, "labels") ? patch.labels : base.labels;
267812
+ const dbThreshold = has(patch, "dbThreshold") ? patch.dbThreshold : base.dbThreshold;
267813
+ return {
267814
+ ...labels !== void 0 && labels.length > 0 ? { labels: [...labels] } : {},
267815
+ ...dbThreshold !== void 0 ? { dbThreshold: clampInt(dbThreshold, NC_AUDIO_DB_MIN, 0) } : {},
267816
+ hitPercent: clampInt(patch.hitPercent ?? base.hitPercent, 1, 100),
267817
+ samplingSeconds: clampInt(patch.samplingSeconds ?? base.samplingSeconds, 1, 300)
267818
+ };
267819
+ }
267820
+ var UNKNOWN_LABEL_ICON = "\u{1F508}";
267821
+ var ICON_BY_ID = new Map(AUDIO_MACRO_LABELS.flatMap((macro2) => macro2.icon === void 0 ? [] : [[macro2.id, macro2.icon]]));
267822
+ function audioLabelChoices(taxonomy, selected) {
267823
+ const fromHub = taxonomy?.audioKinds ?? [];
267824
+ const choices = fromHub.length > 0 ? fromHub.map((entry) => {
267825
+ const id = normalizeAudioLabel(entry.kind);
267826
+ return {
267827
+ value: id,
267828
+ label: entry.label,
267829
+ icon: ICON_BY_ID.get(id) ?? UNKNOWN_LABEL_ICON,
267830
+ unknown: false
267831
+ };
267832
+ }) : AUDIO_MACRO_LABELS.map((macro2) => ({
267833
+ value: macro2.id,
267834
+ label: macro2.name,
267835
+ icon: macro2.icon ?? UNKNOWN_LABEL_ICON,
267836
+ unknown: false
267837
+ }));
267838
+ const known = new Set(choices.map((choice) => choice.value));
267839
+ const unknownLabels = [];
267840
+ for (const stored of selected ?? []) {
267841
+ const id = normalizeAudioLabel(stored);
267842
+ if (known.has(id) || unknownLabels.includes(id)) continue;
267843
+ unknownLabels.push(id);
267844
+ choices.push({
267845
+ value: id,
267846
+ label: id,
267847
+ icon: UNKNOWN_LABEL_ICON,
267848
+ unknown: true
267849
+ });
267850
+ }
267851
+ return {
267852
+ choices,
267853
+ unknownLabels
267854
+ };
267855
+ }
267856
+ function isAudioLabelSelected(labels, id) {
267857
+ const wanted = normalizeAudioLabel(id);
267858
+ return (labels ?? []).some((stored) => normalizeAudioLabel(stored) === wanted);
267859
+ }
267860
+ function toggleAudioLabel(labels, id) {
267861
+ const wanted = normalizeAudioLabel(id);
267862
+ const current = labels ?? [];
267863
+ if (isAudioLabelSelected(current, wanted)) {
267864
+ const next = current.filter((stored) => normalizeAudioLabel(stored) !== wanted);
267865
+ return next.length > 0 ? next : void 0;
267866
+ }
267867
+ return [...current, wanted];
267868
+ }
265780
267869
  var CLASS_CONDITION_IDS = /* @__PURE__ */ new Set(["classes", "classesExclude"]);
265781
267870
  var LABEL_CONDITION_IDS = /* @__PURE__ */ new Set(["sensorKinds"]);
267871
+ var COUNT_CLASS_CONDITION_IDS = /* @__PURE__ */ new Set(["occupancy"]);
265782
267872
  function toOption(entry) {
265783
267873
  return {
265784
267874
  value: entry.kind,
@@ -265845,6 +267935,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
265845
267935
  });
265846
267936
  return groups.length > 0 ? { groups } : null;
265847
267937
  }
267938
+ if (COUNT_CLASS_CONDITION_IDS.has(conditionId)) {
267939
+ const options = taxonomy.videoClasses.filter((e) => e.parentKind === null).map(toOption);
267940
+ return options.length > 0 ? { groups: [{
267941
+ key: "macro",
267942
+ fallbackLabel: "Classes",
267943
+ options
267944
+ }] } : null;
267945
+ }
265848
267946
  if (LABEL_CONDITION_IDS.has(conditionId)) {
265849
267947
  const groups = groupLabels(taxonomy.labels);
265850
267948
  return groups.length > 0 ? { groups } : null;
@@ -267629,12 +269727,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267629
269727
  exports.DEFAULT_FEATURES = DEFAULT_FEATURES;
267630
269728
  exports.DEFAULT_NATIVE_LEASE_SETTINGS = DEFAULT_NATIVE_LEASE_SETTINGS;
267631
269729
  exports.DEFAULT_RETENTION = DEFAULT_RETENTION;
269730
+ exports.DEFAULT_RUNTIME_STATE_DURABILITY = require_sleep.DEFAULT_RUNTIME_STATE_DURABILITY;
267632
269731
  exports.DEFAULT_SCRUB_THUMBNAIL_PRESET = DEFAULT_SCRUB_THUMBNAIL_PRESET;
267633
269732
  exports.DEFAULT_TIMELAPSE_PREVIEW_TEXT = DEFAULT_TIMELAPSE_PREVIEW_TEXT;
267634
269733
  exports.DETAIL_CROP_PADDING_FIELD = DETAIL_CROP_PADDING_FIELD;
267635
269734
  exports.DETAIL_CROP_PADDING_KEY = DETAIL_CROP_PADDING_KEY;
267636
269735
  exports.DETAIL_CROP_SECTION_ID = DETAIL_CROP_SECTION_ID;
267637
269736
  exports.DETAIL_CROP_SQUARE_KEY = DETAIL_CROP_SQUARE_KEY;
269737
+ exports.DETECTION_MACRO_CLASSES = DETECTION_MACRO_CLASSES;
267638
269738
  exports.DETECTION_PIPELINE_CAP_NAME = DETECTION_PIPELINE_CAP_NAME;
267639
269739
  exports.DEVICE_BACKEND_TO_FORMAT = DEVICE_BACKEND_TO_FORMAT;
267640
269740
  exports.DEVICE_CAP_NAMES = DEVICE_CAP_NAMES;
@@ -267878,6 +269978,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267878
269978
  exports.NATIVE_LEASE_TILE_BUDGET_KEY = NATIVE_LEASE_TILE_BUDGET_KEY;
267879
269979
  exports.NC_ALARM_SYSTEM_EVENT_KINDS = NC_ALARM_SYSTEM_EVENT_KINDS;
267880
269980
  exports.NC_AUDIO_DBFS_FLOOR = NC_AUDIO_DBFS_FLOOR;
269981
+ exports.NC_AUDIO_DB_MAX = NC_AUDIO_DB_MAX;
269982
+ exports.NC_AUDIO_DB_MIN = NC_AUDIO_DB_MIN;
269983
+ exports.NC_AUDIO_DB_OFFERED = NC_AUDIO_DB_OFFERED;
269984
+ exports.NC_AUDIO_DB_STEP = NC_AUDIO_DB_STEP;
269985
+ exports.NC_AUDIO_DEFAULTS = NC_AUDIO_DEFAULTS;
269986
+ exports.NC_AUDIO_HIT_PERCENT_MAX = NC_AUDIO_HIT_PERCENT_MAX;
269987
+ exports.NC_AUDIO_HIT_PERCENT_MIN = NC_AUDIO_HIT_PERCENT_MIN;
269988
+ exports.NC_AUDIO_SAMPLING_MAX_SEC = NC_AUDIO_SAMPLING_MAX_SEC;
269989
+ exports.NC_AUDIO_SAMPLING_MIN_SEC = NC_AUDIO_SAMPLING_MIN_SEC;
267881
269990
  exports.NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NC_AUTHORABLE_SYSTEM_EVENT_KINDS;
267882
269991
  exports.NC_BASE_CONDITION_KEYS = NC_BASE_CONDITION_KEYS;
267883
269992
  exports.NC_CONDITION_CATALOG = NC_CONDITION_CATALOG;
@@ -267885,6 +269994,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267885
269994
  exports.NC_CONFIRM_DEFAULT_TIMEOUT_MS = NC_CONFIRM_DEFAULT_TIMEOUT_MS;
267886
269995
  exports.NC_CONFIRM_MAX_TIMEOUT_MS = NC_CONFIRM_MAX_TIMEOUT_MS;
267887
269996
  exports.NC_CONFIRM_MIN_TIMEOUT_MS = NC_CONFIRM_MIN_TIMEOUT_MS;
269997
+ exports.NC_DEFAULT_SNOOZE_MINUTES = NC_DEFAULT_SNOOZE_MINUTES;
267888
269998
  exports.NC_HISTORY_LIMIT_DEFAULT = NC_HISTORY_LIMIT_DEFAULT;
267889
269999
  exports.NC_HISTORY_LIMIT_MAX = NC_HISTORY_LIMIT_MAX;
267890
270000
  exports.NC_MAX_PER_TRACK_IMMEDIATE = NC_MAX_PER_TRACK_IMMEDIATE;
@@ -268031,7 +270141,9 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268031
270141
  exports.RECOGNITION_TYPES = RECOGNITION_TYPES;
268032
270142
  exports.RECORDING_EXPORT_MAX_READ_BYTES = RECORDING_EXPORT_MAX_READ_BYTES;
268033
270143
  exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
270144
+ exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
268034
270145
  exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
270146
+ exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
268035
270147
  exports.RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT;
268036
270148
  exports.RawStateResultSchema = require_sleep.RawStateResultSchema;
268037
270149
  exports.ReadGopBytesResultSchema = ReadGopBytesResultSchema;
@@ -268303,7 +270415,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268303
270415
  exports.audioAnalysisCapability = audioAnalysisCapability;
268304
270416
  exports.audioAnalyzerCapability = audioAnalyzerCapability;
268305
270417
  exports.audioCodecCapability = audioCodecCapability;
270418
+ exports.audioIsFailClosed = audioIsFailClosed;
270419
+ exports.audioLabelChoices = audioLabelChoices;
268306
270420
  exports.audioMetricsCapability = audioMetricsCapability;
270421
+ exports.audioOrDefaults = audioOrDefaults;
268307
270422
  exports.audioPlanFromEncodeProfile = require_canonical_hash.audioPlanFromEncodeProfile;
268308
270423
  exports.authProviderCapability = authProviderCapability;
268309
270424
  exports.autoAssignProfiles = autoAssignProfiles;
@@ -268441,15 +270556,18 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268441
270556
  exports.invocationFromEncodeProfile = require_canonical_hash.invocationFromEncodeProfile;
268442
270557
  exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
268443
270558
  exports.isArrayOutputSchema = isArrayOutputSchema;
270559
+ exports.isAudioLabelSelected = isAudioLabelSelected;
268444
270560
  exports.isBaseConditionKey = isBaseConditionKey;
268445
270561
  exports.isCollectionArrayMethod = isCollectionArrayMethod;
268446
270562
  exports.isDeployableToAgent = isDeployableToAgent;
270563
+ exports.isDetectionMacroClass = isDetectionMacroClass;
268447
270564
  exports.isDeviceConfigCap = require_sleep.isDeviceConfigCap;
268448
270565
  exports.isDeviceScopedCap = require_sleep.isDeviceScopedCap;
268449
270566
  exports.isEvent = require_sleep.isEvent;
268450
270567
  exports.isIsolatedBuiltin = isIsolatedBuiltin;
268451
270568
  exports.isNode = isNode;
268452
270569
  exports.isObjectInput = isObjectInput;
270570
+ exports.isRestoredCap = isRestoredCap;
268453
270571
  exports.isSameAddonId = isSameAddonId;
268454
270572
  exports.isScheduleActive = isScheduleActive;
268455
270573
  exports.isSoftwareDecode = require_canonical_hash.isSoftwareDecode;
@@ -268498,6 +270616,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268498
270616
  exports.nodePin = require_sleep.nodePin;
268499
270617
  exports.nodesCapability = nodesCapability;
268500
270618
  exports.normalizeAddonInitResult = require_sleep.normalizeAddonInitResult;
270619
+ exports.normalizeAudioLabel = normalizeAudioLabel;
268501
270620
  exports.normalizeUnit = normalizeUnit;
268502
270621
  exports.notificationOutputCapability = notificationOutputCapability;
268503
270622
  exports.notificationRulesCapability = notificationRulesCapability;
@@ -268514,6 +270633,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268514
270633
  exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
268515
270634
  exports.parseProfileBrokerId = require_sleep.parseProfileBrokerId;
268516
270635
  exports.parseStreamParamsFormPatch = parseStreamParamsFormPatch;
270636
+ exports.patchAudio = patchAudio;
268517
270637
  exports.petFeederCapability = petFeederCapability;
268518
270638
  exports.pickAccessoryControl = pickAccessoryControl;
268519
270639
  exports.pickDetailCropConvention = pickDetailCropConvention;
@@ -268566,6 +270686,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268566
270686
  exports.resolveVariantModelId = resolveVariantModelId;
268567
270687
  exports.runInferenceStep = runInferenceStep;
268568
270688
  exports.runtimeDevices = runtimeDevices;
270689
+ exports.runtimeStatePolicyFor = runtimeStatePolicyFor;
268569
270690
  exports.sceneMonitorCapability = sceneMonitorCapability;
268570
270691
  exports.scopeKey = require_sleep.scopeKey;
268571
270692
  exports.scopesAllowAddon = scopesAllowAddon;
@@ -268612,6 +270733,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268612
270733
  exports.toNodeId = require_sleep.toNodeId;
268613
270734
  exports.toStreamSourceEntry = toStreamSourceEntry;
268614
270735
  exports.toastCapability = toastCapability;
270736
+ exports.toggleAudioLabel = toggleAudioLabel;
268615
270737
  exports.tokenize = tokenize;
268616
270738
  exports.transcodeBody = transcodeBody;
268617
270739
  exports.tryConvertUnit = tryConvertUnit;
@@ -389744,7 +391866,10 @@ var require_addon_registry_service = __commonJS({
389744
391866
  "setDevice",
389745
391867
  "getAddonDevice",
389746
391868
  "setAddonDevice",
389747
- "clearAddonDevice"
391869
+ "clearAddonDevice",
391870
+ "getDeviceRuntimeState",
391871
+ "setDeviceRuntimeState",
391872
+ "clearDeviceRuntimeState"
389748
391873
  ];
389749
391874
  for (const key of required) {
389750
391875
  if (typeof Reflect.get(backend, key) !== "function")
@@ -397201,6 +399326,18 @@ var require_cluster_node_history_store = __commonJS({
397201
399326
  descriptor: DescriptorSchema
397202
399327
  });
397203
399328
  var COLLECTION = "cluster-node-history";
399329
+ var LAST_ACTIVE_HEARTBEAT_MS = 5 * 6e4;
399330
+ function descriptorsEqual(a, b) {
399331
+ return a.id === b.id && a.name === b.name && a.hostname === b.hostname && a.platform === b.platform && a.arch === b.arch && a.cpuModel === b.cpuModel && a.cpuCores === b.cpuCores && a.memoryMB === b.memoryMB && a.isHub === b.isHub && sameStrings(a.engines, b.engines) && sameStrings(a.localIps, b.localIps) && sameStrings(a.addonIds, b.addonIds) && samePackages(a.packages, b.packages);
399332
+ }
399333
+ function sameStrings(a, b) {
399334
+ return a.length === b.length && a.every((v, i) => v === b[i]);
399335
+ }
399336
+ function samePackages(a, b) {
399337
+ if (a === void 0 || b === void 0)
399338
+ return a === b;
399339
+ return a.length === b.length && a.every((p, i) => p.name === b[i]?.name && p.version === b[i]?.version);
399340
+ }
397204
399341
  var ClusterNodeHistoryStore = class _ClusterNodeHistoryStore {
397205
399342
  resolveSettingsStore;
397206
399343
  logger;
@@ -397242,6 +399379,19 @@ var require_cluster_node_history_store = __commonJS({
397242
399379
  * Upsert a node's descriptor + addon roster while it is ONLINE, stamping
397243
399380
  * `lastActive = Date.now()`. Called on each `listNodes()` refresh for every
397244
399381
  * live node (R2 capture point).
399382
+ *
399383
+ * WRITES ONLY ON A CHANGE, OR ONCE PER {@link LAST_ACTIVE_HEARTBEAT_MS}.
399384
+ * `listNodes()` runs off the 30 s topology heartbeat, so the unconditional
399385
+ * upsert this used to be was the worst write:row ratio in the whole system —
399386
+ * measured on the live hub at ~6 upserts/minute = **8,640 a day to maintain
399387
+ * three rows**, i.e. 40 % of every ledger write on the hub, on the same shfs
399388
+ * SQLite file whose WAL checkpoint can stall hub-main for seconds (D96).
399389
+ *
399390
+ * Nothing is lost. `lastActive` exists to answer "when was this node last
399391
+ * seen", which only matters at OFFLINE resolution — and the disconnect edge
399392
+ * is written by {@link touch}, which is unbounded. The bound costs at most
399393
+ * five minutes of precision on a node that then disappeared without a
399394
+ * `$node.disconnected`, and buys 288 writes/day/node instead of 2,880.
397245
399395
  */
397246
399396
  async snapshot(descriptor) {
397247
399397
  const store = await this.ensureDeclared();
@@ -397250,13 +399400,13 @@ var require_cluster_node_history_store = __commonJS({
397250
399400
  try {
397251
399401
  const existing = await this.readRow(store, descriptor.id);
397252
399402
  const packages = descriptor.packages ?? existing?.descriptor.packages;
399403
+ const next = packages === void 0 ? descriptor : { ...descriptor, packages };
399404
+ if (existing !== null && !this.worthWriting(existing, next))
399405
+ return;
397253
399406
  await store.set({
397254
399407
  collection: COLLECTION,
397255
399408
  key: descriptor.id,
397256
- value: {
397257
- lastActive: Date.now(),
397258
- descriptor: packages === void 0 ? descriptor : { ...descriptor, packages }
397259
- }
399409
+ value: { lastActive: Date.now(), descriptor: next }
397260
399410
  });
397261
399411
  } catch (err) {
397262
399412
  this.logger.warn("snapshot upsert failed (best-effort)", {
@@ -397265,6 +399415,16 @@ var require_cluster_node_history_store = __commonJS({
397265
399415
  });
397266
399416
  }
397267
399417
  }
399418
+ /**
399419
+ * Is this snapshot worth a commit? Yes when the descriptor CHANGED, or when
399420
+ * the stored `lastActive` has aged past the heartbeat bound. A read that
399421
+ * fails never reaches here — `snapshot` writes when there is no existing row.
399422
+ */
399423
+ worthWriting(existing, next) {
399424
+ if (Date.now() - existing.lastActive >= LAST_ACTIVE_HEARTBEAT_MS)
399425
+ return true;
399426
+ return !descriptorsEqual(existing.descriptor, next);
399427
+ }
397268
399428
  /** Read one node's row, validated. `null` when absent or malformed. */
397269
399429
  async readRow(store, nodeId) {
397270
399430
  const raw = await store.get({ collection: COLLECTION, key: nodeId });