camstack 1.2.23 → 1.2.24

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-D4nuaPdd.js
23635
+ var require_dist_D4nuaPdd = __commonJS({
23636
+ "../system/dist/dist-D4nuaPdd.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
@@ -31985,7 +31999,23 @@ var require_dist_BV5Bug9t = __commonJS({
31985
31999
  * else — see `notification-center/action-token.ts` for what that does and
31986
32000
  * does not buy.
31987
32001
  */
31988
- destructive: zod.z.boolean().optional()
32002
+ destructive: zod.z.boolean().optional(),
32003
+ /**
32004
+ * How the tap should REACH the url.
32005
+ *
32006
+ * `navigate` (absent, and every button authored before this field) opens it:
32007
+ * the phone leaves the notification and shows whatever the callback returns.
32008
+ * That is right for a button whose answer the operator wants to read.
32009
+ *
32010
+ * `background` fires it as a POST and stays put. It exists for the buttons
32011
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
32012
+ * an answer to the notification, and being thrown into a browser tab to
32013
+ * confirm it costs more attention than the notification did. A backend that
32014
+ * cannot do a background call renders it as an ordinary link (the adapters
32015
+ * fall back rather than dropping the button), so this is a preference, never
32016
+ * a requirement.
32017
+ */
32018
+ mode: zod.z.enum(["navigate", "background"]).optional()
31989
32019
  });
31990
32020
  var NotificationSchema = zod.z.object({
31991
32021
  body: zod.z.string(),
@@ -32763,7 +32793,17 @@ var require_dist_BV5Bug9t = __commonJS({
32763
32793
  * full slice; renders an arm button per `availableModes` entry and
32764
32794
  * a PIN field iff `requiresCode === true`.
32765
32795
  */
32766
- runtimeState: AlarmPanelStatusSchema
32796
+ runtimeState: AlarmPanelStatusSchema,
32797
+ /**
32798
+ * Runtime-state durability: **restored** — armed state is the one thing a panel must not lose across a restart.
32799
+ *
32800
+ * See `RuntimeStateDurability`. Enforced by
32801
+ * `scripts/check-runtime-state-durability.ts`.
32802
+ */
32803
+ durability: "restored",
32804
+ /** Clock fields: written, but excluded from the compare that decides
32805
+ * whether persisting is worth a SQLite commit. */
32806
+ volatileStateFields: ["lastChangedAt"]
32767
32807
  };
32768
32808
  var MaskPointSchema = zod.z.object({
32769
32809
  x: zod.z.number(),
@@ -32861,6 +32901,7 @@ var require_dist_BV5Bug9t = __commonJS({
32861
32901
  nodeIds: zod.z.array(zod.z.string().min(1)).min(1).optional(),
32862
32902
  packageNames: zod.z.array(zod.z.string().min(1)).min(1).optional()
32863
32903
  });
32904
+ var NC_SNOOZE_MAX_MINUTES = 1440;
32864
32905
  var NcScheduleWindowSchema = zod.z.object({
32865
32906
  /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
32866
32907
  days: zod.z.array(zod.z.number().int().min(0).max(6)).min(1),
@@ -33117,15 +33158,15 @@ var require_dist_BV5Bug9t = __commonJS({
33117
33158
  * (an `immediate` rule naming an `audio-*` class, one notification per
33118
33159
  * classified sample) stays exactly as it was for rules that already use it.
33119
33160
  *
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
33161
+ * In {@link NC_CONDITION_CATALOG} since P2, and the ORDER it got there is the
33162
+ * rule rather than an accident: the viewer mirrors the descriptor enums BY
33163
+ * HAND (`camstack/src/data/notification-center.ts`, guarded by
33164
+ * `scripts/check-viewer-condition-mirror.ts`) and its rule editor strips the
33124
33165
  * condition fields it does not know when a rule is saved from the phone.
33125
33166
  * 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.
33167
+ * operator loses a rule's conditions by opening it — so the viewer mirror
33168
+ * (P3, shipped) went FIRST, and the descriptor an editor renders from
33169
+ * follows here.
33129
33170
  */
33130
33171
  audio: NcAudioConditionSchema.optional()
33131
33172
  });
@@ -33294,6 +33335,30 @@ var require_dist_BV5Bug9t = __commonJS({
33294
33335
  */
33295
33336
  snoozeAllowGlobal: zod.z.boolean().optional(),
33296
33337
  /**
33338
+ * The snooze durations THIS rule's notification offers as buttons, in
33339
+ * minutes.
33340
+ *
33341
+ * Three states, and all three are distinct — which is exactly why this is
33342
+ * `.optional()` and never `.default()`. A Zod default does not run on the
33343
+ * addon cap path (three production failures in one day), so a schema default
33344
+ * would collapse the first two:
33345
+ *
33346
+ * | value | meaning |
33347
+ * | --- | --- |
33348
+ * | absent | the operator never said ⇒ {@link NC_DEFAULT_SNOOZE_MINUTES} |
33349
+ * | `[]` | **no snooze buttons on this rule** — the explicit override |
33350
+ * | a list | these choices, de-duplicated and sorted, at most four |
33351
+ *
33352
+ * `.max(4)` because the notifier's own action budget is small (ntfy allows
33353
+ * three buttons in total) and a rule that spent it all on snooze choices
33354
+ * would push its own tap-through actions off the notification.
33355
+ *
33356
+ * An empty list is NOT an alarm exemption: a rule the alarm is about, or
33357
+ * that arms the panel, is exempt automatically and cannot be silenced by a
33358
+ * window from anywhere (D133).
33359
+ */
33360
+ snoozeOptions: zod.z.array(zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES)).max(4).optional(),
33361
+ /**
33297
33362
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
33298
33363
  *
33299
33364
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -33383,6 +33448,7 @@ var require_dist_BV5Bug9t = __commonJS({
33383
33448
  "device",
33384
33449
  "package",
33385
33450
  "occupancy",
33451
+ "audio",
33386
33452
  "system"
33387
33453
  ]),
33388
33454
  label: zod.z.string(),
@@ -33401,6 +33467,7 @@ var require_dist_BV5Bug9t = __commonJS({
33401
33467
  "crossingSelect",
33402
33468
  "polygonDraw",
33403
33469
  "occupancy",
33470
+ "audio",
33404
33471
  "deviceState",
33405
33472
  "systemEvent"
33406
33473
  ]),
@@ -33511,7 +33578,20 @@ var require_dist_BV5Bug9t = __commonJS({
33511
33578
  ruleId: zod.z.string().optional(),
33512
33579
  /** Required when `scope: 'device'`. */
33513
33580
  deviceId: zod.z.number().int().optional(),
33514
- durationMinutes: zod.z.number().int().min(1).max(1440),
33581
+ /**
33582
+ * Narrow the window to these subject classes — "the cat, not the person".
33583
+ *
33584
+ * ORTHOGONAL to `scope`, deliberately, and absent means EVERY class: that is
33585
+ * what every window authored before this field meant, so no persisted row
33586
+ * changes meaning and no client has to learn anything to keep working.
33587
+ *
33588
+ * It is what makes the window's real key `(deviceId, classes[])` and lets it
33589
+ * cross rules (D133): the operator points at a camera and a kind of thing,
33590
+ * not at whichever of their four rules happened to produce the notification
33591
+ * they are dismissing.
33592
+ */
33593
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
33594
+ durationMinutes: zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
33515
33595
  /**
33516
33596
  * Silence this for EVERY recipient, not just the caller. Permission is
33517
33597
  * checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
@@ -33535,6 +33615,10 @@ var require_dist_BV5Bug9t = __commonJS({
33535
33615
  scope: NcSnoozeScopeSchema,
33536
33616
  ruleId: zod.z.string().optional(),
33537
33617
  deviceId: zod.z.number().int().optional(),
33618
+ /** Subject classes this window covers. ABSENT = every class — see
33619
+ * {@link NcSnoozeInputSchema.shape.classes}. Lives in the JSON blob and has
33620
+ * no SQLite column: nothing queries a window by class. */
33621
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
33538
33622
  startedAt: zod.z.number(),
33539
33623
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
33540
33624
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -35807,7 +35891,14 @@ var require_dist_BV5Bug9t = __commonJS({
35807
35891
  * handle. Slice shape is `{ zones: Zone[] }` so future extensions
35808
35892
  * (e.g. zone groupings) can sit alongside the polygon list.
35809
35893
  */
35810
- runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() })
35894
+ runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() }),
35895
+ /**
35896
+ * 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.
35897
+ *
35898
+ * See `RuntimeStateDurability`. Enforced by
35899
+ * `scripts/check-runtime-state-durability.ts`.
35900
+ */
35901
+ durability: "restored"
35811
35902
  };
35812
35903
  var NativeCropBboxSchema = zod.z.object({
35813
35904
  x: zod.z.number(),
@@ -39110,7 +39201,17 @@ var require_dist_BV5Bug9t = __commonJS({
39110
39201
  schema: AirQualitySensorStatusSchema,
39111
39202
  kind: "push"
39112
39203
  },
39113
- runtimeState: AirQualitySensorStatusSchema
39204
+ runtimeState: AirQualitySensorStatusSchema,
39205
+ /**
39206
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
39207
+ *
39208
+ * See `RuntimeStateDurability`. Enforced by
39209
+ * `scripts/check-runtime-state-durability.ts`.
39210
+ */
39211
+ durability: "restored",
39212
+ /** Clock fields: written, but excluded from the compare that decides
39213
+ * whether persisting is worth a SQLite commit. */
39214
+ volatileStateFields: ["lastFetchedAt"]
39114
39215
  };
39115
39216
  var AmbientLightSensorStatusSchema = zod.z.object({
39116
39217
  /** Current illuminance in lux (lx). */
@@ -39138,7 +39239,17 @@ var require_dist_BV5Bug9t = __commonJS({
39138
39239
  schema: AmbientLightSensorStatusSchema,
39139
39240
  kind: "push"
39140
39241
  },
39141
- runtimeState: AmbientLightSensorStatusSchema
39242
+ runtimeState: AmbientLightSensorStatusSchema,
39243
+ /**
39244
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
39245
+ *
39246
+ * See `RuntimeStateDurability`. Enforced by
39247
+ * `scripts/check-runtime-state-durability.ts`.
39248
+ */
39249
+ durability: "restored",
39250
+ /** Clock fields: written, but excluded from the compare that decides
39251
+ * whether persisting is worth a SQLite commit. */
39252
+ volatileStateFields: ["lastFetchedAt"]
39142
39253
  };
39143
39254
  var AudioClassSummarySchema = zod.z.object({
39144
39255
  className: zod.z.string(),
@@ -39228,7 +39339,14 @@ var require_dist_BV5Bug9t = __commonJS({
39228
39339
  }), AudioMetricsHistorySchema)
39229
39340
  },
39230
39341
  /** Reactive runtime-state mirror — live `device.state.audioMetrics.value`. */
39231
- runtimeState: AudioMetricsSnapshotSchema
39342
+ runtimeState: AudioMetricsSnapshotSchema,
39343
+ /**
39344
+ * 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.
39345
+ *
39346
+ * See `RuntimeStateDurability`. Enforced by
39347
+ * `scripts/check-runtime-state-durability.ts`.
39348
+ */
39349
+ durability: "session"
39232
39350
  };
39233
39351
  var AutomationControlStatusSchema = zod.z.object({
39234
39352
  /** Whether the automation is currently enabled. Disabled automations
@@ -39279,7 +39397,14 @@ var require_dist_BV5Bug9t = __commonJS({
39279
39397
  * reads `enabled` (toggle) + `isRunning` (spinner) + `lastError`
39280
39398
  * (badge) directly.
39281
39399
  */
39282
- runtimeState: AutomationControlStatusSchema
39400
+ runtimeState: AutomationControlStatusSchema,
39401
+ /**
39402
+ * 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.
39403
+ *
39404
+ * See `RuntimeStateDurability`. Enforced by
39405
+ * `scripts/check-runtime-state-durability.ts`.
39406
+ */
39407
+ durability: "session"
39283
39408
  };
39284
39409
  var BatteryStatusSchema = zod.z.object({
39285
39410
  /** 0..100 inclusive. Firmware-reported. */
@@ -39380,7 +39505,17 @@ var require_dist_BV5Bug9t = __commonJS({
39380
39505
  * via `device.runtimeState.getCapState('battery')` regardless of
39381
39506
  * the underlying driver.
39382
39507
  */
39383
- runtimeState: BatteryStatusSchema
39508
+ runtimeState: BatteryStatusSchema,
39509
+ /**
39510
+ * 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.
39511
+ *
39512
+ * See `RuntimeStateDurability`. Enforced by
39513
+ * `scripts/check-runtime-state-durability.ts`.
39514
+ */
39515
+ durability: "restored",
39516
+ /** Clock fields: written, but excluded from the compare that decides
39517
+ * whether persisting is worth a SQLite commit. */
39518
+ volatileStateFields: ["lastUpdated"]
39384
39519
  };
39385
39520
  var BinaryStatusSchema = zod.z.object({
39386
39521
  on: zod.z.boolean(),
@@ -39398,7 +39533,17 @@ var require_dist_BV5Bug9t = __commonJS({
39398
39533
  schema: BinaryStatusSchema,
39399
39534
  kind: "push"
39400
39535
  },
39401
- runtimeState: BinaryStatusSchema
39536
+ runtimeState: BinaryStatusSchema,
39537
+ /**
39538
+ * Runtime-state durability: **restored** — transition-driven sensor state; the restored value gives the boot comparison.
39539
+ *
39540
+ * See `RuntimeStateDurability`. Enforced by
39541
+ * `scripts/check-runtime-state-durability.ts`.
39542
+ */
39543
+ durability: "restored",
39544
+ /** Clock fields: written, but excluded from the compare that decides
39545
+ * whether persisting is worth a SQLite commit. */
39546
+ volatileStateFields: ["lastChangedAt"]
39402
39547
  };
39403
39548
  var BrightnessStatusSchema = zod.z.object({
39404
39549
  /** Current level as 0..100 inclusive. Firmware-reported. */
@@ -39440,7 +39585,14 @@ var require_dist_BV5Bug9t = __commonJS({
39440
39585
  * by the kernel. Read via `device.state.brightness.value` so UI
39441
39586
  * sliders surface the current level without polling the provider.
39442
39587
  */
39443
- runtimeState: BrightnessStatusSchema
39588
+ runtimeState: BrightnessStatusSchema,
39589
+ /**
39590
+ * Runtime-state durability: **session** — live lamp state, re-published by the provider on connect.
39591
+ *
39592
+ * See `RuntimeStateDurability`. Enforced by
39593
+ * `scripts/check-runtime-state-durability.ts`.
39594
+ */
39595
+ durability: "session"
39444
39596
  };
39445
39597
  var buttonCapability = {
39446
39598
  name: "button",
@@ -39520,7 +39672,17 @@ var require_dist_BV5Bug9t = __commonJS({
39520
39672
  schema: CarbonMonoxideStatusSchema,
39521
39673
  kind: "push"
39522
39674
  },
39523
- runtimeState: CarbonMonoxideStatusSchema
39675
+ runtimeState: CarbonMonoxideStatusSchema,
39676
+ /**
39677
+ * Runtime-state durability: **restored** — as `smoke`.
39678
+ *
39679
+ * See `RuntimeStateDurability`. Enforced by
39680
+ * `scripts/check-runtime-state-durability.ts`.
39681
+ */
39682
+ durability: "restored",
39683
+ /** Clock fields: written, but excluded from the compare that decides
39684
+ * whether persisting is worth a SQLite commit. */
39685
+ volatileStateFields: ["lastChangedAt"]
39524
39686
  };
39525
39687
  var HvacModeSchema = zod.z.enum([
39526
39688
  "off",
@@ -39649,7 +39811,14 @@ var require_dist_BV5Bug9t = __commonJS({
39649
39811
  * the full slice via `device.state.climate-control.value` and refresh
39650
39812
  * on every push without re-querying the provider.
39651
39813
  */
39652
- runtimeState: ClimateControlStatusSchema
39814
+ runtimeState: ClimateControlStatusSchema,
39815
+ /**
39816
+ * Runtime-state durability: **session** — as `brightness`; `currentTemp` moves continuously and is re-published on connect.
39817
+ *
39818
+ * See `RuntimeStateDurability`. Enforced by
39819
+ * `scripts/check-runtime-state-durability.ts`.
39820
+ */
39821
+ durability: "session"
39653
39822
  };
39654
39823
  var RgbTripletSchema = zod.z.object({
39655
39824
  r: zod.z.number().int().min(0).max(255),
@@ -39736,7 +39905,14 @@ var require_dist_BV5Bug9t = __commonJS({
39736
39905
  * kernel. Read via `device.state.color.value` so UI pickers surface
39737
39906
  * the current chromaticity without polling the provider.
39738
39907
  */
39739
- runtimeState: ColorStatusSchema
39908
+ runtimeState: ColorStatusSchema,
39909
+ /**
39910
+ * Runtime-state durability: **session** — as `brightness`.
39911
+ *
39912
+ * See `RuntimeStateDurability`. Enforced by
39913
+ * `scripts/check-runtime-state-durability.ts`.
39914
+ */
39915
+ durability: "session"
39740
39916
  };
39741
39917
  var ConnectionTestOutcomeSchema = zod.z.discriminatedUnion("outcome", [
39742
39918
  zod.z.object({
@@ -39790,7 +39966,17 @@ var require_dist_BV5Bug9t = __commonJS({
39790
39966
  schema: ConnectivityStatusSchema,
39791
39967
  kind: "push"
39792
39968
  },
39793
- runtimeState: ConnectivityStatusSchema
39969
+ runtimeState: ConnectivityStatusSchema,
39970
+ /**
39971
+ * Runtime-state durability: **restored** — same shape and same argument as `device-status`, for links rather than devices.
39972
+ *
39973
+ * See `RuntimeStateDurability`. Enforced by
39974
+ * `scripts/check-runtime-state-durability.ts`.
39975
+ */
39976
+ durability: "restored",
39977
+ /** Clock fields: written, but excluded from the compare that decides
39978
+ * whether persisting is worth a SQLite commit. */
39979
+ volatileStateFields: ["lastChangedAt"]
39794
39980
  };
39795
39981
  var ConsumableItemSchema = zod.z.object({
39796
39982
  /** Stable id, e.g. 'main-brush'. */
@@ -39857,7 +40043,14 @@ var require_dist_BV5Bug9t = __commonJS({
39857
40043
  }
39858
40044
  }
39859
40045
  },
39860
- runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() })
40046
+ runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() }),
40047
+ /**
40048
+ * Runtime-state durability: **session** — the authority is the appliance; the provider re-reads the whole item array on connect.
40049
+ *
40050
+ * See `RuntimeStateDurability`. Enforced by
40051
+ * `scripts/check-runtime-state-durability.ts`.
40052
+ */
40053
+ durability: "session"
39861
40054
  };
39862
40055
  var ContactStatusSchema = zod.z.object({
39863
40056
  /** True when the entry is open; false when closed. */
@@ -39876,7 +40069,17 @@ var require_dist_BV5Bug9t = __commonJS({
39876
40069
  schema: ContactStatusSchema,
39877
40070
  kind: "push"
39878
40071
  },
39879
- runtimeState: ContactStatusSchema
40072
+ runtimeState: ContactStatusSchema,
40073
+ /**
40074
+ * Runtime-state durability: **restored** — a door left open across a restart must still read open.
40075
+ *
40076
+ * See `RuntimeStateDurability`. Enforced by
40077
+ * `scripts/check-runtime-state-durability.ts`.
40078
+ */
40079
+ durability: "restored",
40080
+ /** Clock fields: written, but excluded from the compare that decides
40081
+ * whether persisting is worth a SQLite commit. */
40082
+ volatileStateFields: ["lastChangedAt"]
39880
40083
  };
39881
40084
  var ControlKindSchema = zod.z.enum([
39882
40085
  "numeric",
@@ -39963,7 +40166,14 @@ var require_dist_BV5Bug9t = __commonJS({
39963
40166
  * dropdown / text field / date picker) read the slice's discriminant
39964
40167
  * and value directly without polling the provider.
39965
40168
  */
39966
- runtimeState: ControlStatusSchema
40169
+ runtimeState: ControlStatusSchema,
40170
+ /**
40171
+ * Runtime-state durability: **session** — a generic control mirrors an external entity that re-publishes on connect; the options array is re-derived with it.
40172
+ *
40173
+ * See `RuntimeStateDurability`. Enforced by
40174
+ * `scripts/check-runtime-state-durability.ts`.
40175
+ */
40176
+ durability: "session"
39967
40177
  };
39968
40178
  var CoverStateSchema = zod.z.enum([
39969
40179
  "open",
@@ -40025,7 +40235,17 @@ var require_dist_BV5Bug9t = __commonJS({
40025
40235
  * Runtime-state slice — mirrored by the kernel. UI controls watch
40026
40236
  * the slice for live position changes during a move.
40027
40237
  */
40028
- runtimeState: CoverStatusSchema
40238
+ runtimeState: CoverStatusSchema,
40239
+ /**
40240
+ * Runtime-state durability: **restored** — position survives a restart on the device; the mirror should agree at boot rather than read blank.
40241
+ *
40242
+ * See `RuntimeStateDurability`. Enforced by
40243
+ * `scripts/check-runtime-state-durability.ts`.
40244
+ */
40245
+ durability: "restored",
40246
+ /** Clock fields: written, but excluded from the compare that decides
40247
+ * whether persisting is worth a SQLite commit. */
40248
+ volatileStateFields: ["lastChangedAt"]
40029
40249
  };
40030
40250
  var DayNightModeSchema = zod.z.enum([
40031
40251
  "auto",
@@ -40086,7 +40306,17 @@ var require_dist_BV5Bug9t = __commonJS({
40086
40306
  schema: DayNightStatusSchema,
40087
40307
  kind: "poll"
40088
40308
  },
40089
- runtimeState: DayNightStatusSchema
40309
+ runtimeState: DayNightStatusSchema,
40310
+ /**
40311
+ * Runtime-state durability: **restored** — operator-set IR-cut behaviour; mutation-driven.
40312
+ *
40313
+ * See `RuntimeStateDurability`. Enforced by
40314
+ * `scripts/check-runtime-state-durability.ts`.
40315
+ */
40316
+ durability: "restored",
40317
+ /** Clock fields: written, but excluded from the compare that decides
40318
+ * whether persisting is worth a SQLite commit. */
40319
+ volatileStateFields: ["lastFetchedAt"]
40090
40320
  };
40091
40321
  var DeviceStatusSchema = zod.z.object({
40092
40322
  /**
@@ -40119,7 +40349,17 @@ var require_dist_BV5Bug9t = __commonJS({
40119
40349
  schema: DeviceStatusSchema,
40120
40350
  kind: "push"
40121
40351
  },
40122
- runtimeState: DeviceStatusSchema
40352
+ runtimeState: DeviceStatusSchema,
40353
+ /**
40354
+ * 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.
40355
+ *
40356
+ * See `RuntimeStateDurability`. Enforced by
40357
+ * `scripts/check-runtime-state-durability.ts`.
40358
+ */
40359
+ durability: "restored",
40360
+ /** Clock fields: written, but excluded from the compare that decides
40361
+ * whether persisting is worth a SQLite commit. */
40362
+ volatileStateFields: ["lastChangedAt"]
40123
40363
  };
40124
40364
  var DoorbellStatusSchema = zod.z.object({
40125
40365
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -40159,7 +40399,14 @@ var require_dist_BV5Bug9t = __commonJS({
40159
40399
  * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
40160
40400
  * without subscribing.
40161
40401
  */
40162
- runtimeState: DoorbellStatusSchema
40402
+ runtimeState: DoorbellStatusSchema,
40403
+ /**
40404
+ * 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.
40405
+ *
40406
+ * See `RuntimeStateDurability`. Enforced by
40407
+ * `scripts/check-runtime-state-durability.ts`.
40408
+ */
40409
+ durability: "restored"
40163
40410
  };
40164
40411
  var EnumSensorDateTimeFormatSchema = zod.z.enum([
40165
40412
  "date",
@@ -40189,7 +40436,17 @@ var require_dist_BV5Bug9t = __commonJS({
40189
40436
  schema: EnumSensorStatusSchema,
40190
40437
  kind: "push"
40191
40438
  },
40192
- runtimeState: EnumSensorStatusSchema
40439
+ runtimeState: EnumSensorStatusSchema,
40440
+ /**
40441
+ * Runtime-state durability: **restored** — as `numeric-sensor`; 80 devices.
40442
+ *
40443
+ * See `RuntimeStateDurability`. Enforced by
40444
+ * `scripts/check-runtime-state-durability.ts`.
40445
+ */
40446
+ durability: "restored",
40447
+ /** Clock fields: written, but excluded from the compare that decides
40448
+ * whether persisting is worth a SQLite commit. */
40449
+ volatileStateFields: ["lastFetchedAt"]
40193
40450
  };
40194
40451
  var EventFireSchema = zod.z.object({
40195
40452
  deviceId: zod.z.number(),
@@ -40215,7 +40472,14 @@ var require_dist_BV5Bug9t = __commonJS({
40215
40472
  schema: EventEmitterStatusSchema,
40216
40473
  kind: "push"
40217
40474
  },
40218
- runtimeState: EventEmitterStatusSchema
40475
+ runtimeState: EventEmitterStatusSchema,
40476
+ /**
40477
+ * Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
40478
+ *
40479
+ * See `RuntimeStateDurability`. Enforced by
40480
+ * `scripts/check-runtime-state-durability.ts`.
40481
+ */
40482
+ durability: "session"
40219
40483
  };
40220
40484
  var EventItemSchema = zod.z.object({
40221
40485
  id: zod.z.string(),
@@ -40514,7 +40778,14 @@ var require_dist_BV5Bug9t = __commonJS({
40514
40778
  * Runtime-state slice — mirrored by the kernel. UI fan speed
40515
40779
  * sliders read `percentage` for live updates.
40516
40780
  */
40517
- runtimeState: FanControlStatusSchema
40781
+ runtimeState: FanControlStatusSchema,
40782
+ /**
40783
+ * Runtime-state durability: **session** — as `brightness`.
40784
+ *
40785
+ * See `RuntimeStateDurability`. Enforced by
40786
+ * `scripts/check-runtime-state-durability.ts`.
40787
+ */
40788
+ durability: "session"
40518
40789
  };
40519
40790
  var FeatureProbeStatusSchema = zod.z.object({
40520
40791
  /**
@@ -40567,7 +40838,14 @@ var require_dist_BV5Bug9t = __commonJS({
40567
40838
  schema: FeatureProbeStatusSchema,
40568
40839
  kind: "push"
40569
40840
  },
40570
- runtimeState: FeatureProbeStatusSchema
40841
+ runtimeState: FeatureProbeStatusSchema,
40842
+ /**
40843
+ * 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.
40844
+ *
40845
+ * See `RuntimeStateDurability`. Enforced by
40846
+ * `scripts/check-runtime-state-durability.ts`.
40847
+ */
40848
+ durability: "session"
40571
40849
  };
40572
40850
  var FloodStatusSchema = zod.z.object({
40573
40851
  /** True when leak is currently detected. */
@@ -40586,7 +40864,17 @@ var require_dist_BV5Bug9t = __commonJS({
40586
40864
  schema: FloodStatusSchema,
40587
40865
  kind: "push"
40588
40866
  },
40589
- runtimeState: FloodStatusSchema
40867
+ runtimeState: FloodStatusSchema,
40868
+ /**
40869
+ * Runtime-state durability: **restored** — as `smoke`.
40870
+ *
40871
+ * See `RuntimeStateDurability`. Enforced by
40872
+ * `scripts/check-runtime-state-durability.ts`.
40873
+ */
40874
+ durability: "restored",
40875
+ /** Clock fields: written, but excluded from the compare that decides
40876
+ * whether persisting is worth a SQLite commit. */
40877
+ volatileStateFields: ["lastChangedAt"]
40590
40878
  };
40591
40879
  var GasStatusSchema = zod.z.object({
40592
40880
  detected: zod.z.boolean(),
@@ -40604,7 +40892,17 @@ var require_dist_BV5Bug9t = __commonJS({
40604
40892
  schema: GasStatusSchema,
40605
40893
  kind: "push"
40606
40894
  },
40607
- runtimeState: GasStatusSchema
40895
+ runtimeState: GasStatusSchema,
40896
+ /**
40897
+ * Runtime-state durability: **restored** — as `smoke`.
40898
+ *
40899
+ * See `RuntimeStateDurability`. Enforced by
40900
+ * `scripts/check-runtime-state-durability.ts`.
40901
+ */
40902
+ durability: "restored",
40903
+ /** Clock fields: written, but excluded from the compare that decides
40904
+ * whether persisting is worth a SQLite commit. */
40905
+ volatileStateFields: ["lastChangedAt"]
40608
40906
  };
40609
40907
  var HumidifierStatusSchema = zod.z.object({
40610
40908
  /** Whether the humidifier is currently on. */
@@ -40665,7 +40963,14 @@ var require_dist_BV5Bug9t = __commonJS({
40665
40963
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
40666
40964
  * slice for live humidity / mode changes.
40667
40965
  */
40668
- runtimeState: HumidifierStatusSchema
40966
+ runtimeState: HumidifierStatusSchema,
40967
+ /**
40968
+ * Runtime-state durability: **session** — as `climate-control`.
40969
+ *
40970
+ * See `RuntimeStateDurability`. Enforced by
40971
+ * `scripts/check-runtime-state-durability.ts`.
40972
+ */
40973
+ durability: "session"
40669
40974
  };
40670
40975
  var HumiditySensorStatusSchema = zod.z.object({
40671
40976
  /** Current relative humidity, 0..100. */
@@ -40693,7 +40998,17 @@ var require_dist_BV5Bug9t = __commonJS({
40693
40998
  schema: HumiditySensorStatusSchema,
40694
40999
  kind: "push"
40695
41000
  },
40696
- runtimeState: HumiditySensorStatusSchema
41001
+ runtimeState: HumiditySensorStatusSchema,
41002
+ /**
41003
+ * Runtime-state durability: **restored** — as `numeric-sensor` (67 of 75 writes were the clock alone).
41004
+ *
41005
+ * See `RuntimeStateDurability`. Enforced by
41006
+ * `scripts/check-runtime-state-durability.ts`.
41007
+ */
41008
+ durability: "restored",
41009
+ /** Clock fields: written, but excluded from the compare that decides
41010
+ * whether persisting is worth a SQLite commit. */
41011
+ volatileStateFields: ["lastFetchedAt"]
40697
41012
  };
40698
41013
  var ImageStatusSchema = zod.z.object({
40699
41014
  /** Absolute signed URL the browser loads directly. Null when the
@@ -40717,7 +41032,14 @@ var require_dist_BV5Bug9t = __commonJS({
40717
41032
  * Runtime-state slice — mirrored by the kernel. The UI reads `url`
40718
41033
  * directly and renders the still image.
40719
41034
  */
40720
- runtimeState: ImageStatusSchema
41035
+ runtimeState: ImageStatusSchema,
41036
+ /**
41037
+ * Runtime-state durability: **session** — a snapshot URL is a session-scoped handle; a restored one points at nothing.
41038
+ *
41039
+ * See `RuntimeStateDurability`. Enforced by
41040
+ * `scripts/check-runtime-state-durability.ts`.
41041
+ */
41042
+ durability: "session"
40721
41043
  };
40722
41044
  var ImageRotateSchema = zod.z.enum([
40723
41045
  "0",
@@ -40818,7 +41140,17 @@ var require_dist_BV5Bug9t = __commonJS({
40818
41140
  schema: ImageSettingsStatusSchema,
40819
41141
  kind: "poll"
40820
41142
  },
40821
- runtimeState: ImageSettingsStatusSchema
41143
+ runtimeState: ImageSettingsStatusSchema,
41144
+ /**
41145
+ * Runtime-state durability: **restored** — operator-set camera imaging; mutation-driven.
41146
+ *
41147
+ * See `RuntimeStateDurability`. Enforced by
41148
+ * `scripts/check-runtime-state-durability.ts`.
41149
+ */
41150
+ durability: "restored",
41151
+ /** Clock fields: written, but excluded from the compare that decides
41152
+ * whether persisting is worth a SQLite commit. */
41153
+ volatileStateFields: ["lastFetchedAt"]
40822
41154
  };
40823
41155
  var IntegrationWithStateSchema = zod.z.object({
40824
41156
  id: zod.z.string(),
@@ -41149,7 +41481,14 @@ var require_dist_BV5Bug9t = __commonJS({
41149
41481
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
41150
41482
  * slice for live activity + battery changes.
41151
41483
  */
41152
- runtimeState: LawnMowerControlStatusSchema
41484
+ runtimeState: LawnMowerControlStatusSchema,
41485
+ /**
41486
+ * Runtime-state durability: **session** — as `vacuum-control`.
41487
+ *
41488
+ * See `RuntimeStateDurability`. Enforced by
41489
+ * `scripts/check-runtime-state-durability.ts`.
41490
+ */
41491
+ durability: "session"
41153
41492
  };
41154
41493
  var InterfaceKindEnum = zod.z.enum([
41155
41494
  "lan",
@@ -41398,7 +41737,17 @@ var require_dist_BV5Bug9t = __commonJS({
41398
41737
  * read `state` and disable themselves during `locking`/`unlocking`
41399
41738
  * transitions.
41400
41739
  */
41401
- runtimeState: LockControlStatusSchema
41740
+ runtimeState: LockControlStatusSchema,
41741
+ /**
41742
+ * Runtime-state durability: **restored** — a lock left locked must still read locked.
41743
+ *
41744
+ * See `RuntimeStateDurability`. Enforced by
41745
+ * `scripts/check-runtime-state-durability.ts`.
41746
+ */
41747
+ durability: "restored",
41748
+ /** Clock fields: written, but excluded from the compare that decides
41749
+ * whether persisting is worth a SQLite commit. */
41750
+ volatileStateFields: ["lastChangedAt"]
41402
41751
  };
41403
41752
  var MediaPlayerStateSchema = zod.z.enum([
41404
41753
  "off",
@@ -41548,7 +41897,14 @@ var require_dist_BV5Bug9t = __commonJS({
41548
41897
  * full slice for live now-playing, volume, and progress updates
41549
41898
  * without polling.
41550
41899
  */
41551
- runtimeState: MediaPlayerStatusSchema
41900
+ runtimeState: MediaPlayerStatusSchema,
41901
+ /**
41902
+ * Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
41903
+ *
41904
+ * See `RuntimeStateDurability`. Enforced by
41905
+ * `scripts/check-runtime-state-durability.ts`.
41906
+ */
41907
+ durability: "session"
41552
41908
  };
41553
41909
  var MeshEndpointSchema = zod.z.object({
41554
41910
  /** Stable identifier within the provider (e.g. `mesh-ipv4`, `magicdns`, `funnel`). */
@@ -41832,7 +42188,14 @@ var require_dist_BV5Bug9t = __commonJS({
41832
42188
  * `device.state.motion.value`. Reads never invoke the provider, so
41833
42189
  * UIs and other addons can poll the cached state safely.
41834
42190
  */
41835
- runtimeState: MotionStatusSchema
42191
+ runtimeState: MotionStatusSchema,
42192
+ /**
42193
+ * 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.
42194
+ *
42195
+ * See `RuntimeStateDurability`. Enforced by
42196
+ * `scripts/check-runtime-state-durability.ts`.
42197
+ */
42198
+ durability: "session"
41836
42199
  };
41837
42200
  var MotionTriggerStatusSchema = zod.z.object({
41838
42201
  enabled: zod.z.boolean(),
@@ -41869,7 +42232,14 @@ var require_dist_BV5Bug9t = __commonJS({
41869
42232
  schema: MotionTriggerStatusSchema,
41870
42233
  kind: "command-driven"
41871
42234
  },
41872
- runtimeState: MotionTriggerRuntimeStateSchema
42235
+ runtimeState: MotionTriggerRuntimeStateSchema,
42236
+ /**
42237
+ * 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.
42238
+ *
42239
+ * See `RuntimeStateDurability`. Enforced by
42240
+ * `scripts/check-runtime-state-durability.ts`.
42241
+ */
42242
+ durability: "session"
41873
42243
  };
41874
42244
  var MotionZoneRegionSchema = zod.z.object({
41875
42245
  id: zod.z.number(),
@@ -41924,7 +42294,17 @@ var require_dist_BV5Bug9t = __commonJS({
41924
42294
  schema: MotionZoneStatusSchema,
41925
42295
  kind: "poll"
41926
42296
  },
41927
- runtimeState: MotionZoneStatusSchema
42297
+ runtimeState: MotionZoneStatusSchema,
42298
+ /**
42299
+ * 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.
42300
+ *
42301
+ * See `RuntimeStateDurability`. Enforced by
42302
+ * `scripts/check-runtime-state-durability.ts`.
42303
+ */
42304
+ durability: "restored",
42305
+ /** Clock fields: written, but excluded from the compare that decides
42306
+ * whether persisting is worth a SQLite commit. */
42307
+ volatileStateFields: ["lastFetchedAt"]
41928
42308
  };
41929
42309
  var NativeObjectClassEnum = zod.z.enum([
41930
42310
  "person",
@@ -41987,7 +42367,17 @@ var require_dist_BV5Bug9t = __commonJS({
41987
42367
  schema: NativeObjectDetectionStatusSchema,
41988
42368
  kind: "push"
41989
42369
  },
41990
- runtimeState: NativeObjectDetectionRuntimeStateSchema
42370
+ runtimeState: NativeObjectDetectionRuntimeStateSchema,
42371
+ /**
42372
+ * 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.
42373
+ *
42374
+ * See `RuntimeStateDurability`. Enforced by
42375
+ * `scripts/check-runtime-state-durability.ts`.
42376
+ */
42377
+ durability: "restored",
42378
+ /** Clock fields: written, but excluded from the compare that decides
42379
+ * whether persisting is worth a SQLite commit. */
42380
+ volatileStateFields: ["lastFetchedAt"]
41991
42381
  };
41992
42382
  var StreamNetworkStatsSchema = zod.z.object({
41993
42383
  nominalBitrateKbps: zod.z.number(),
@@ -42310,7 +42700,14 @@ var require_dist_BV5Bug9t = __commonJS({
42310
42700
  * form reads `supports` to gate optional fields; history pane reads
42311
42701
  * `lastSentAt` / `lastError` / `queueDepth`.
42312
42702
  */
42313
- runtimeState: NotifierStatusSchema
42703
+ runtimeState: NotifierStatusSchema,
42704
+ /**
42705
+ * Runtime-state durability: **session** — live queue depth and last-send state; a restored queue depth describes a queue that no longer exists.
42706
+ *
42707
+ * See `RuntimeStateDurability`. Enforced by
42708
+ * `scripts/check-runtime-state-durability.ts`.
42709
+ */
42710
+ durability: "session"
42314
42711
  };
42315
42712
  var NumericSensorStatusSchema = zod.z.object({
42316
42713
  value: zod.z.number(),
@@ -42336,7 +42733,17 @@ var require_dist_BV5Bug9t = __commonJS({
42336
42733
  schema: NumericSensorStatusSchema,
42337
42734
  kind: "push"
42338
42735
  },
42339
- runtimeState: NumericSensorStatusSchema
42736
+ runtimeState: NumericSensorStatusSchema,
42737
+ /**
42738
+ * 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.
42739
+ *
42740
+ * See `RuntimeStateDurability`. Enforced by
42741
+ * `scripts/check-runtime-state-durability.ts`.
42742
+ */
42743
+ durability: "restored",
42744
+ /** Clock fields: written, but excluded from the compare that decides
42745
+ * whether persisting is worth a SQLite commit. */
42746
+ volatileStateFields: ["lastFetchedAt"]
42340
42747
  };
42341
42748
  var OsdOverlayKindEnum = zod.z.enum([
42342
42749
  "text",
@@ -42759,7 +43166,14 @@ var require_dist_BV5Bug9t = __commonJS({
42759
43166
  * the full slice via `device.state.petFeeder.value` and refresh on
42760
43167
  * every poll without re-querying the provider.
42761
43168
  */
42762
- runtimeState: PetFeederStatusSchema
43169
+ runtimeState: PetFeederStatusSchema,
43170
+ /**
43171
+ * Runtime-state durability: **session** — live appliance state re-published on connect.
43172
+ *
43173
+ * See `RuntimeStateDurability`. Enforced by
43174
+ * `scripts/check-runtime-state-durability.ts`.
43175
+ */
43176
+ durability: "session"
42763
43177
  };
42764
43178
  var VehicleSchema = zod.z.object({
42765
43179
  id: zod.z.string(),
@@ -43093,7 +43507,17 @@ var require_dist_BV5Bug9t = __commonJS({
43093
43507
  schema: PowerMeterStatusSchema,
43094
43508
  kind: "push"
43095
43509
  },
43096
- runtimeState: PowerMeterStatusSchema
43510
+ runtimeState: PowerMeterStatusSchema,
43511
+ /**
43512
+ * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
43513
+ *
43514
+ * See `RuntimeStateDurability`. Enforced by
43515
+ * `scripts/check-runtime-state-durability.ts`.
43516
+ */
43517
+ durability: "restored",
43518
+ /** Clock fields: written, but excluded from the compare that decides
43519
+ * whether persisting is worth a SQLite commit. */
43520
+ volatileStateFields: ["lastFetchedAt"]
43097
43521
  };
43098
43522
  var GpsLocationSchema = zod.z.object({
43099
43523
  /** Latitude in decimal degrees, -90..90. */
@@ -43134,7 +43558,17 @@ var require_dist_BV5Bug9t = __commonJS({
43134
43558
  * the map pin is rendered (use `DeviceFeature.PresenceGps` for the
43135
43559
  * pre-fetch fast-path check).
43136
43560
  */
43137
- runtimeState: PresenceStatusSchema
43561
+ runtimeState: PresenceStatusSchema,
43562
+ /**
43563
+ * Runtime-state durability: **restored** — occupancy-relevant: the restored state is what an occupancy rule compares the first post-restart observation against.
43564
+ *
43565
+ * See `RuntimeStateDurability`. Enforced by
43566
+ * `scripts/check-runtime-state-durability.ts`.
43567
+ */
43568
+ durability: "restored",
43569
+ /** Clock fields: written, but excluded from the compare that decides
43570
+ * whether persisting is worth a SQLite commit. */
43571
+ volatileStateFields: ["lastChangedAt"]
43138
43572
  };
43139
43573
  var PressureSensorStatusSchema = zod.z.object({
43140
43574
  /** Current pressure in hPa. */
@@ -43162,7 +43596,17 @@ var require_dist_BV5Bug9t = __commonJS({
43162
43596
  schema: PressureSensorStatusSchema,
43163
43597
  kind: "push"
43164
43598
  },
43165
- runtimeState: PressureSensorStatusSchema
43599
+ runtimeState: PressureSensorStatusSchema,
43600
+ /**
43601
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
43602
+ *
43603
+ * See `RuntimeStateDurability`. Enforced by
43604
+ * `scripts/check-runtime-state-durability.ts`.
43605
+ */
43606
+ durability: "restored",
43607
+ /** Clock fields: written, but excluded from the compare that decides
43608
+ * whether persisting is worth a SQLite commit. */
43609
+ volatileStateFields: ["lastFetchedAt"]
43166
43610
  };
43167
43611
  var PrivacyMaskShapeSchema = zod.z.discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
43168
43612
  var PrivacyMaskRegionSchema = zod.z.object({
@@ -43261,7 +43705,17 @@ var require_dist_BV5Bug9t = __commonJS({
43261
43705
  schema: PrivacyMaskStatusSchema,
43262
43706
  kind: "poll"
43263
43707
  },
43264
- runtimeState: PrivacyMaskStatusSchema
43708
+ runtimeState: PrivacyMaskStatusSchema,
43709
+ /**
43710
+ * Runtime-state durability: **restored** — operator-drawn regions, zero real churn — 22 writes in 25 minutes, every one of them the clock.
43711
+ *
43712
+ * See `RuntimeStateDurability`. Enforced by
43713
+ * `scripts/check-runtime-state-durability.ts`.
43714
+ */
43715
+ durability: "restored",
43716
+ /** Clock fields: written, but excluded from the compare that decides
43717
+ * whether persisting is worth a SQLite commit. */
43718
+ volatileStateFields: ["lastFetchedAt"]
43265
43719
  };
43266
43720
  var PtzPresetSchema = zod.z.object({
43267
43721
  id: zod.z.string(),
@@ -43447,7 +43901,14 @@ var require_dist_BV5Bug9t = __commonJS({
43447
43901
  * fetch / cache / fallback logic out of the four cap methods —
43448
43902
  * they become trampolines over `runtimeState`.
43449
43903
  */
43450
- runtimeState: PtzAutotrackRuntimeStateSchema
43904
+ runtimeState: PtzAutotrackRuntimeStateSchema,
43905
+ /**
43906
+ * Runtime-state durability: **session** — mirrors the camera's own autotrack config, re-read on connect.
43907
+ *
43908
+ * See `RuntimeStateDurability`. Enforced by
43909
+ * `scripts/check-runtime-state-durability.ts`.
43910
+ */
43911
+ durability: "session"
43451
43912
  };
43452
43913
  var rebootCapability = {
43453
43914
  name: "reboot",
@@ -44115,7 +44576,14 @@ var require_dist_BV5Bug9t = __commonJS({
44115
44576
  schema: SceneMonitorStatusSchema,
44116
44577
  kind: "push"
44117
44578
  },
44118
- runtimeState: SceneMonitorStatusSchema
44579
+ runtimeState: SceneMonitorStatusSchema,
44580
+ /**
44581
+ * Runtime-state durability: **session** — re-derived from the current scene on the next evaluation.
44582
+ *
44583
+ * See `RuntimeStateDurability`. Enforced by
44584
+ * `scripts/check-runtime-state-durability.ts`.
44585
+ */
44586
+ durability: "session"
44119
44587
  };
44120
44588
  var ZoneRuleModeEnum = zod.z.enum(["include", "exclude"]);
44121
44589
  var ZoneRuleSchema = zod.z.object({
@@ -44217,7 +44685,14 @@ var require_dist_BV5Bug9t = __commonJS({
44217
44685
  * `isRunning` to render a spinner during execution and surfaces
44218
44686
  * `lastError` / `lastRunSuccess` in the recent-runs panel.
44219
44687
  */
44220
- runtimeState: ScriptRunnerStatusSchema
44688
+ runtimeState: ScriptRunnerStatusSchema,
44689
+ /**
44690
+ * Runtime-state durability: **session** — a restored `isRunning: true` describes a process that died with the previous hub.
44691
+ *
44692
+ * See `RuntimeStateDurability`. Enforced by
44693
+ * `scripts/check-runtime-state-durability.ts`.
44694
+ */
44695
+ durability: "session"
44221
44696
  };
44222
44697
  var SmokeStatusSchema = zod.z.object({
44223
44698
  detected: zod.z.boolean(),
@@ -44235,7 +44710,17 @@ var require_dist_BV5Bug9t = __commonJS({
44235
44710
  schema: SmokeStatusSchema,
44236
44711
  kind: "push"
44237
44712
  },
44238
- runtimeState: SmokeStatusSchema
44713
+ runtimeState: SmokeStatusSchema,
44714
+ /**
44715
+ * Runtime-state durability: **restored** — a safety sensor must not read "clear" merely because the hub restarted.
44716
+ *
44717
+ * See `RuntimeStateDurability`. Enforced by
44718
+ * `scripts/check-runtime-state-durability.ts`.
44719
+ */
44720
+ durability: "restored",
44721
+ /** Clock fields: written, but excluded from the compare that decides
44722
+ * whether persisting is worth a SQLite commit. */
44723
+ volatileStateFields: ["lastChangedAt"]
44239
44724
  };
44240
44725
  var CamStreamDescriptorSchema = zod.z.object({
44241
44726
  camStreamId: zod.z.string().min(1),
@@ -44386,7 +44871,17 @@ var require_dist_BV5Bug9t = __commonJS({
44386
44871
  schema: StreamParamsStatusSchema,
44387
44872
  kind: "poll"
44388
44873
  },
44389
- runtimeState: StreamParamsStatusSchema
44874
+ runtimeState: StreamParamsStatusSchema,
44875
+ /**
44876
+ * Runtime-state durability: **restored** — operator-set encoder profile; mutation-driven.
44877
+ *
44878
+ * See `RuntimeStateDurability`. Enforced by
44879
+ * `scripts/check-runtime-state-durability.ts`.
44880
+ */
44881
+ durability: "restored",
44882
+ /** Clock fields: written, but excluded from the compare that decides
44883
+ * whether persisting is worth a SQLite commit. */
44884
+ volatileStateFields: ["lastFetchedAt"]
44390
44885
  };
44391
44886
  var STREAM_PROFILE_META = [
44392
44887
  {
@@ -44657,6 +45152,16 @@ var require_dist_BV5Bug9t = __commonJS({
44657
45152
  * not need to re-query the provider after a setState mutation.
44658
45153
  */
44659
45154
  runtimeState: SwitchStatusSchema,
45155
+ /**
45156
+ * Runtime-state durability: **restored** — device state an operator reads as authoritative; 55 devices, transition-driven.
45157
+ *
45158
+ * See `RuntimeStateDurability`. Enforced by
45159
+ * `scripts/check-runtime-state-durability.ts`.
45160
+ */
45161
+ durability: "restored",
45162
+ /** Clock fields: written, but excluded from the compare that decides
45163
+ * whether persisting is worth a SQLite commit. */
45164
+ volatileStateFields: ["lastChangedAt"],
44660
45165
  settings: { bindings: [{
44661
45166
  kind: "scalar",
44662
45167
  statusPath: "on",
@@ -44729,7 +45234,17 @@ var require_dist_BV5Bug9t = __commonJS({
44729
45234
  schema: TamperStatusSchema,
44730
45235
  kind: "push"
44731
45236
  },
44732
- runtimeState: TamperStatusSchema
45237
+ runtimeState: TamperStatusSchema,
45238
+ /**
45239
+ * Runtime-state durability: **restored** — as `smoke`.
45240
+ *
45241
+ * See `RuntimeStateDurability`. Enforced by
45242
+ * `scripts/check-runtime-state-durability.ts`.
45243
+ */
45244
+ durability: "restored",
45245
+ /** Clock fields: written, but excluded from the compare that decides
45246
+ * whether persisting is worth a SQLite commit. */
45247
+ volatileStateFields: ["lastChangedAt"]
44733
45248
  };
44734
45249
  var TemperatureSensorStatusSchema = zod.z.object({
44735
45250
  /** Current temperature in Celsius. */
@@ -44758,7 +45273,17 @@ var require_dist_BV5Bug9t = __commonJS({
44758
45273
  schema: TemperatureSensorStatusSchema,
44759
45274
  kind: "push"
44760
45275
  },
44761
- runtimeState: TemperatureSensorStatusSchema
45276
+ runtimeState: TemperatureSensorStatusSchema,
45277
+ /**
45278
+ * Runtime-state durability: **restored** — as `numeric-sensor` (69 of 125 writes were the clock alone).
45279
+ *
45280
+ * See `RuntimeStateDurability`. Enforced by
45281
+ * `scripts/check-runtime-state-durability.ts`.
45282
+ */
45283
+ durability: "restored",
45284
+ /** Clock fields: written, but excluded from the compare that decides
45285
+ * whether persisting is worth a SQLite commit. */
45286
+ volatileStateFields: ["lastFetchedAt"]
44762
45287
  };
44763
45288
  var ToastSchema = zod.z.object({
44764
45289
  title: zod.z.string(),
@@ -44809,7 +45334,14 @@ var require_dist_BV5Bug9t = __commonJS({
44809
45334
  schema: UpdateStatusSchema,
44810
45335
  kind: "poll"
44811
45336
  },
44812
- runtimeState: UpdateStatusSchema
45337
+ runtimeState: UpdateStatusSchema,
45338
+ /**
45339
+ * Runtime-state durability: **session** — a restored `inProgress: true` describes an update that is no longer running; versions are re-probed at boot.
45340
+ *
45341
+ * See `RuntimeStateDurability`. Enforced by
45342
+ * `scripts/check-runtime-state-durability.ts`.
45343
+ */
45344
+ durability: "session"
44813
45345
  };
44814
45346
  var UserSummarySchema = zod.z.object({
44815
45347
  id: zod.z.string(),
@@ -45143,7 +45675,14 @@ var require_dist_BV5Bug9t = __commonJS({
45143
45675
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
45144
45676
  * slice for live state + battery + fan-speed changes.
45145
45677
  */
45146
- runtimeState: VacuumControlStatusSchema
45678
+ runtimeState: VacuumControlStatusSchema,
45679
+ /**
45680
+ * Runtime-state durability: **session** — as `media-player` — a restored `state: cleaning` is a robot that is not cleaning.
45681
+ *
45682
+ * See `RuntimeStateDurability`. Enforced by
45683
+ * `scripts/check-runtime-state-durability.ts`.
45684
+ */
45685
+ durability: "session"
45147
45686
  };
45148
45687
  var ValveStateSchema = zod.z.enum([
45149
45688
  "open",
@@ -45196,7 +45735,14 @@ var require_dist_BV5Bug9t = __commonJS({
45196
45735
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
45197
45736
  * slice for live position changes during a move.
45198
45737
  */
45199
- runtimeState: ValveStatusSchema
45738
+ runtimeState: ValveStatusSchema,
45739
+ /**
45740
+ * Runtime-state durability: **session** — as `brightness`.
45741
+ *
45742
+ * See `RuntimeStateDurability`. Enforced by
45743
+ * `scripts/check-runtime-state-durability.ts`.
45744
+ */
45745
+ durability: "session"
45200
45746
  };
45201
45747
  var VibrationStatusSchema = zod.z.object({
45202
45748
  detected: zod.z.boolean(),
@@ -45214,7 +45760,17 @@ var require_dist_BV5Bug9t = __commonJS({
45214
45760
  schema: VibrationStatusSchema,
45215
45761
  kind: "push"
45216
45762
  },
45217
- runtimeState: VibrationStatusSchema
45763
+ runtimeState: VibrationStatusSchema,
45764
+ /**
45765
+ * Runtime-state durability: **restored** — as `smoke`.
45766
+ *
45767
+ * See `RuntimeStateDurability`. Enforced by
45768
+ * `scripts/check-runtime-state-durability.ts`.
45769
+ */
45770
+ durability: "restored",
45771
+ /** Clock fields: written, but excluded from the compare that decides
45772
+ * whether persisting is worth a SQLite commit. */
45773
+ volatileStateFields: ["lastChangedAt"]
45218
45774
  };
45219
45775
  var WaterHeaterStatusSchema = zod.z.object({
45220
45776
  /** Current measured temperature. Null when not reported. */
@@ -45274,7 +45830,14 @@ var require_dist_BV5Bug9t = __commonJS({
45274
45830
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
45275
45831
  * slice for live temperature / mode / away changes.
45276
45832
  */
45277
- runtimeState: WaterHeaterStatusSchema
45833
+ runtimeState: WaterHeaterStatusSchema,
45834
+ /**
45835
+ * Runtime-state durability: **session** — as `climate-control`.
45836
+ *
45837
+ * See `RuntimeStateDurability`. Enforced by
45838
+ * `scripts/check-runtime-state-durability.ts`.
45839
+ */
45840
+ durability: "session"
45278
45841
  };
45279
45842
  var WeatherStatusSchema = zod.z.object({
45280
45843
  /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
@@ -45314,7 +45877,14 @@ var require_dist_BV5Bug9t = __commonJS({
45314
45877
  * Runtime-state slice — mirrored by the kernel. The UI reads the
45315
45878
  * current conditions directly from the slice on each weather push.
45316
45879
  */
45317
- runtimeState: WeatherStatusSchema
45880
+ runtimeState: WeatherStatusSchema,
45881
+ /**
45882
+ * Runtime-state durability: **session** — a forecast is stale the moment the hub is down; the provider re-fetches on connect.
45883
+ *
45884
+ * See `RuntimeStateDurability`. Enforced by
45885
+ * `scripts/check-runtime-state-durability.ts`.
45886
+ */
45887
+ durability: "session"
45318
45888
  };
45319
45889
  var PerScopeBreakdownSchema = zod.z.object({
45320
45890
  /** Total tracked objects in this scope (frame / zone / unzoned). */
@@ -45423,7 +45993,14 @@ var require_dist_BV5Bug9t = __commonJS({
45423
45993
  * automatically; the explicit `getCurrentSnapshot` cap method is
45424
45994
  * still useful for one-off polls without a subscription.
45425
45995
  */
45426
- runtimeState: CameraOccupancySnapshotSchema
45996
+ runtimeState: CameraOccupancySnapshotSchema,
45997
+ /**
45998
+ * Runtime-state durability: **session** — per-frame analytics; with `audio-metrics` it is ~90 % of the offered write rate. Re-derived on the next frame.
45999
+ *
46000
+ * See `RuntimeStateDurability`. Enforced by
46001
+ * `scripts/check-runtime-state-durability.ts`.
46002
+ */
46003
+ durability: "session"
45427
46004
  };
45428
46005
  var ZoneRuleStageEnum = zod.z.enum([
45429
46006
  "motion",
@@ -45475,7 +46052,14 @@ var require_dist_BV5Bug9t = __commonJS({
45475
46052
  motion: zod.z.array(ZoneRuleSchema).readonly(),
45476
46053
  detection: zod.z.array(ZoneRuleSchema).readonly(),
45477
46054
  package: zod.z.array(ZoneRuleSchema).readonly()
45478
- })
46055
+ }),
46056
+ /**
46057
+ * Runtime-state durability: **restored** — operator intent, mutation-only, same argument as `zones`.
46058
+ *
46059
+ * See `RuntimeStateDurability`. Enforced by
46060
+ * `scripts/check-runtime-state-durability.ts`.
46061
+ */
46062
+ durability: "restored"
45479
46063
  };
45480
46064
  var RUNTIME_DEFAULTS = {
45481
46065
  "features.streaming": true,
@@ -51618,6 +52202,264 @@ var require_dist_BV5Bug9t = __commonJS({
51618
52202
  "network-access": "ingress",
51619
52203
  "smtp-provider": "email"
51620
52204
  });
52205
+ var RUNTIME_STATE_POLICY = {
52206
+ "air-quality-sensor": {
52207
+ durability: "restored",
52208
+ volatileFields: ["lastFetchedAt"]
52209
+ },
52210
+ "alarm-panel": {
52211
+ durability: "restored",
52212
+ volatileFields: ["lastChangedAt"]
52213
+ },
52214
+ "ambient-light-sensor": {
52215
+ durability: "restored",
52216
+ volatileFields: ["lastFetchedAt"]
52217
+ },
52218
+ "audio-metrics": {
52219
+ durability: "session",
52220
+ volatileFields: []
52221
+ },
52222
+ "automation-control": {
52223
+ durability: "session",
52224
+ volatileFields: []
52225
+ },
52226
+ "battery": {
52227
+ durability: "restored",
52228
+ volatileFields: ["lastUpdated"]
52229
+ },
52230
+ "binary": {
52231
+ durability: "restored",
52232
+ volatileFields: ["lastChangedAt"]
52233
+ },
52234
+ "brightness": {
52235
+ durability: "session",
52236
+ volatileFields: []
52237
+ },
52238
+ "camera-streams": {
52239
+ durability: "session",
52240
+ volatileFields: []
52241
+ },
52242
+ "carbon-monoxide": {
52243
+ durability: "restored",
52244
+ volatileFields: ["lastChangedAt"]
52245
+ },
52246
+ "climate-control": {
52247
+ durability: "session",
52248
+ volatileFields: []
52249
+ },
52250
+ "color": {
52251
+ durability: "session",
52252
+ volatileFields: []
52253
+ },
52254
+ "connectivity": {
52255
+ durability: "restored",
52256
+ volatileFields: ["lastChangedAt"]
52257
+ },
52258
+ "consumables": {
52259
+ durability: "session",
52260
+ volatileFields: []
52261
+ },
52262
+ "contact": {
52263
+ durability: "restored",
52264
+ volatileFields: ["lastChangedAt"]
52265
+ },
52266
+ "control": {
52267
+ durability: "session",
52268
+ volatileFields: []
52269
+ },
52270
+ "cover": {
52271
+ durability: "restored",
52272
+ volatileFields: ["lastChangedAt"]
52273
+ },
52274
+ "day-night": {
52275
+ durability: "restored",
52276
+ volatileFields: ["lastFetchedAt"]
52277
+ },
52278
+ "device-discovery": {
52279
+ durability: "session",
52280
+ volatileFields: []
52281
+ },
52282
+ "device-status": {
52283
+ durability: "restored",
52284
+ volatileFields: ["lastChangedAt"]
52285
+ },
52286
+ "doorbell": {
52287
+ durability: "restored",
52288
+ volatileFields: []
52289
+ },
52290
+ "enum-sensor": {
52291
+ durability: "restored",
52292
+ volatileFields: ["lastFetchedAt"]
52293
+ },
52294
+ "event-emitter": {
52295
+ durability: "session",
52296
+ volatileFields: []
52297
+ },
52298
+ "fan-control": {
52299
+ durability: "session",
52300
+ volatileFields: []
52301
+ },
52302
+ "feature-probe": {
52303
+ durability: "session",
52304
+ volatileFields: []
52305
+ },
52306
+ "flood": {
52307
+ durability: "restored",
52308
+ volatileFields: ["lastChangedAt"]
52309
+ },
52310
+ "gas": {
52311
+ durability: "restored",
52312
+ volatileFields: ["lastChangedAt"]
52313
+ },
52314
+ "humidifier": {
52315
+ durability: "session",
52316
+ volatileFields: []
52317
+ },
52318
+ "humidity-sensor": {
52319
+ durability: "restored",
52320
+ volatileFields: ["lastFetchedAt"]
52321
+ },
52322
+ "image": {
52323
+ durability: "session",
52324
+ volatileFields: []
52325
+ },
52326
+ "image-settings": {
52327
+ durability: "restored",
52328
+ volatileFields: ["lastFetchedAt"]
52329
+ },
52330
+ "lawn-mower-control": {
52331
+ durability: "session",
52332
+ volatileFields: []
52333
+ },
52334
+ "lock-control": {
52335
+ durability: "restored",
52336
+ volatileFields: ["lastChangedAt"]
52337
+ },
52338
+ "media-player": {
52339
+ durability: "session",
52340
+ volatileFields: []
52341
+ },
52342
+ "motion": {
52343
+ durability: "session",
52344
+ volatileFields: []
52345
+ },
52346
+ "motion-trigger": {
52347
+ durability: "session",
52348
+ volatileFields: []
52349
+ },
52350
+ "motion-zones": {
52351
+ durability: "restored",
52352
+ volatileFields: ["lastFetchedAt"]
52353
+ },
52354
+ "native-object-detection": {
52355
+ durability: "restored",
52356
+ volatileFields: ["lastFetchedAt"]
52357
+ },
52358
+ "notifier": {
52359
+ durability: "session",
52360
+ volatileFields: []
52361
+ },
52362
+ "numeric-sensor": {
52363
+ durability: "restored",
52364
+ volatileFields: ["lastFetchedAt"]
52365
+ },
52366
+ "pet-feeder": {
52367
+ durability: "session",
52368
+ volatileFields: []
52369
+ },
52370
+ "power-meter": {
52371
+ durability: "restored",
52372
+ volatileFields: ["lastFetchedAt"]
52373
+ },
52374
+ "presence": {
52375
+ durability: "restored",
52376
+ volatileFields: ["lastChangedAt"]
52377
+ },
52378
+ "pressure-sensor": {
52379
+ durability: "restored",
52380
+ volatileFields: ["lastFetchedAt"]
52381
+ },
52382
+ "privacy-mask": {
52383
+ durability: "restored",
52384
+ volatileFields: ["lastFetchedAt"]
52385
+ },
52386
+ "ptz-autotrack": {
52387
+ durability: "session",
52388
+ volatileFields: []
52389
+ },
52390
+ "scene-monitor": {
52391
+ durability: "session",
52392
+ volatileFields: []
52393
+ },
52394
+ "script-runner": {
52395
+ durability: "session",
52396
+ volatileFields: []
52397
+ },
52398
+ "smoke": {
52399
+ durability: "restored",
52400
+ volatileFields: ["lastChangedAt"]
52401
+ },
52402
+ "stream-params": {
52403
+ durability: "restored",
52404
+ volatileFields: ["lastFetchedAt"]
52405
+ },
52406
+ "switch": {
52407
+ durability: "restored",
52408
+ volatileFields: ["lastChangedAt"]
52409
+ },
52410
+ "tamper": {
52411
+ durability: "restored",
52412
+ volatileFields: ["lastChangedAt"]
52413
+ },
52414
+ "temperature-sensor": {
52415
+ durability: "restored",
52416
+ volatileFields: ["lastFetchedAt"]
52417
+ },
52418
+ "update": {
52419
+ durability: "session",
52420
+ volatileFields: []
52421
+ },
52422
+ "vacuum-control": {
52423
+ durability: "session",
52424
+ volatileFields: []
52425
+ },
52426
+ "valve": {
52427
+ durability: "session",
52428
+ volatileFields: []
52429
+ },
52430
+ "vibration": {
52431
+ durability: "restored",
52432
+ volatileFields: ["lastChangedAt"]
52433
+ },
52434
+ "water-heater": {
52435
+ durability: "session",
52436
+ volatileFields: []
52437
+ },
52438
+ "weather": {
52439
+ durability: "session",
52440
+ volatileFields: []
52441
+ },
52442
+ "zone-analytics": {
52443
+ durability: "session",
52444
+ volatileFields: []
52445
+ },
52446
+ "zone-rules": {
52447
+ durability: "restored",
52448
+ volatileFields: []
52449
+ },
52450
+ "zones": {
52451
+ durability: "restored",
52452
+ volatileFields: []
52453
+ }
52454
+ };
52455
+ function runtimeStatePolicyFor(capName) {
52456
+ return RUNTIME_STATE_POLICY[capName] ?? SESSION_ONLY_POLICY;
52457
+ }
52458
+ var SESSION_ONLY_POLICY = {
52459
+ durability: "session",
52460
+ volatileFields: []
52461
+ };
52462
+ new Map(AUDIO_MACRO_LABELS.flatMap((macro2) => macro2.icon === void 0 ? [] : [[macro2.id, macro2.icon]]));
51621
52463
  var TimelapseTemplateSchema = zod.z.object({
51622
52464
  title: zod.z.string().max(500).optional(),
51623
52465
  body: zod.z.string().max(2e3).optional()
@@ -52435,6 +53277,12 @@ var require_dist_BV5Bug9t = __commonJS({
52435
53277
  return resolveCapMount;
52436
53278
  }
52437
53279
  });
53280
+ Object.defineProperty(exports, "runtimeStatePolicyFor", {
53281
+ enumerable: true,
53282
+ get: function() {
53283
+ return runtimeStatePolicyFor;
53284
+ }
53285
+ });
52438
53286
  Object.defineProperty(exports, "scopeKey", {
52439
53287
  enumerable: true,
52440
53288
  get: function() {
@@ -52531,7 +53379,7 @@ var require_alerts_addon = __commonJS({
52531
53379
  [Symbol.toStringTag]: { value: "Module" }
52532
53380
  });
52533
53381
  require_chunk_Cek0wNdY();
52534
- var require_dist10 = require_dist_BV5Bug9t();
53382
+ var require_dist10 = require_dist_D4nuaPdd();
52535
53383
  function selectExpired(alerts, cutoffMs) {
52536
53384
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
52537
53385
  }
@@ -53344,7 +54192,7 @@ var require_console_logging = __commonJS({
53344
54192
  [Symbol.toStringTag]: { value: "Module" }
53345
54193
  });
53346
54194
  require_chunk_Cek0wNdY();
53347
- var require_dist10 = require_dist_BV5Bug9t();
54195
+ var require_dist10 = require_dist_D4nuaPdd();
53348
54196
  var require_formatter = require_formatter_DqAKDlvN();
53349
54197
  var LEVEL_RANK = {
53350
54198
  debug: 0,
@@ -53438,7 +54286,7 @@ var require_core_blocks_addon = __commonJS({
53438
54286
  "use strict";
53439
54287
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
53440
54288
  var require_chunk = require_chunk_Cek0wNdY();
53441
- var require_dist10 = require_dist_BV5Bug9t();
54289
+ var require_dist10 = require_dist_D4nuaPdd();
53442
54290
  var node_crypto = __require("crypto");
53443
54291
  var node_fs_promises = __require("fs/promises");
53444
54292
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -56136,7 +56984,7 @@ var require_device_manager_addon = __commonJS({
56136
56984
  [Symbol.toStringTag]: { value: "Module" }
56137
56985
  });
56138
56986
  require_chunk_Cek0wNdY();
56139
- var require_dist10 = require_dist_BV5Bug9t();
56987
+ var require_dist10 = require_dist_D4nuaPdd();
56140
56988
  var node_crypto = __require("crypto");
56141
56989
  var _camstack_types_node = require_node();
56142
56990
  var JOB_HISTORY = 20;
@@ -59332,8 +60180,60 @@ var require_device_manager_addon = __commonJS({
59332
60180
  return current;
59333
60181
  };
59334
60182
  };
60183
+ function persistableBlob(blob, policyFor) {
60184
+ const out = {};
60185
+ for (const [capName, slice] of Object.entries(blob)) {
60186
+ if (policyFor(capName).durability !== "restored") continue;
60187
+ out[capName] = { ...slice };
60188
+ }
60189
+ return out;
60190
+ }
60191
+ function sliceEffectivelyEqual(a, b, volatileFields) {
60192
+ const skip = new Set(volatileFields);
60193
+ const keys = /* @__PURE__ */ new Set();
60194
+ for (const k of Object.keys(a)) if (!skip.has(k)) keys.add(k);
60195
+ for (const k of Object.keys(b)) if (!skip.has(k)) keys.add(k);
60196
+ for (const k of keys) {
60197
+ if (!deepEqual(a[k], b[k])) return false;
60198
+ if (Object.hasOwn(a, k) !== Object.hasOwn(b, k)) return false;
60199
+ }
60200
+ return true;
60201
+ }
60202
+ function effectivelyEqual(before, after, policyFor) {
60203
+ const caps = /* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)]);
60204
+ for (const capName of caps) {
60205
+ const a = before[capName];
60206
+ const b = after[capName];
60207
+ if (a === void 0 || b === void 0) return false;
60208
+ if (!sliceEffectivelyEqual(a, b, policyFor(capName).volatileFields)) return false;
60209
+ }
60210
+ return true;
60211
+ }
60212
+ function deepEqual(a, b) {
60213
+ if (a === b) return true;
60214
+ if (typeof a === "number" && typeof b === "number") return Number.isNaN(a) && Number.isNaN(b);
60215
+ if (a === null || b === null || typeof a !== "object" || typeof b !== "object") return false;
60216
+ const aIsArray = Array.isArray(a);
60217
+ if (aIsArray !== Array.isArray(b)) return false;
60218
+ if (aIsArray) {
60219
+ const bArr = b;
60220
+ if (a.length !== bArr.length) return false;
60221
+ for (let i = 0; i < a.length; i += 1) if (!deepEqual(a[i], bArr[i])) return false;
60222
+ return true;
60223
+ }
60224
+ const aRec = a;
60225
+ const bRec = b;
60226
+ const aKeys = Object.keys(aRec);
60227
+ if (aKeys.length !== Object.keys(bRec).length) return false;
60228
+ for (const k of aKeys) {
60229
+ if (!Object.hasOwn(bRec, k)) return false;
60230
+ if (!deepEqual(aRec[k], bRec[k])) return false;
60231
+ }
60232
+ return true;
60233
+ }
59335
60234
  var DeviceStateMirror = class DeviceStateMirror2 {
59336
60235
  ctx;
60236
+ policyFor;
59337
60237
  /**
59338
60238
  * Hub-side mirror of every device's cap-keyed runtime state.
59339
60239
  * Key: deviceId. Value: per-cap slice map. Empty by default —
@@ -59347,8 +60247,24 @@ var require_device_manager_addon = __commonJS({
59347
60247
  */
59348
60248
  runtimeStateDebounce = /* @__PURE__ */ new Map();
59349
60249
  static RUNTIME_STATE_DEBOUNCE_MS = 1e3;
59350
- constructor(ctx) {
60250
+ /**
60251
+ * What is believed to be ON DISK for each device — the persistable projection
60252
+ * of the last blob actually written (or seeded at boot). The effective-change
60253
+ * gate compares against THIS, not against the previous mirror state: two
60254
+ * clock-only ticks in a row must not add up to a write just because each was
60255
+ * compared with its immediate predecessor.
60256
+ */
60257
+ lastPersisted = /* @__PURE__ */ new Map();
60258
+ /**
60259
+ * Per-device count of writes the effective-change gate skipped since the last
60260
+ * real one. Reported on the next write that DOES happen, so the log says how
60261
+ * much churn the gate absorbed instead of saying nothing at all — a branch
60262
+ * that drops work silently reads as "never happened".
60263
+ */
60264
+ skippedSinceWrite = /* @__PURE__ */ new Map();
60265
+ constructor(ctx, policyFor = require_dist10.runtimeStatePolicyFor) {
59351
60266
  this.ctx = ctx;
60267
+ this.policyFor = policyFor;
59352
60268
  }
59353
60269
  /**
59354
60270
  * Single-cap mirror update — diff against the current mirror,
@@ -59375,13 +60291,23 @@ var require_device_manager_addon = __commonJS({
59375
60291
  return true;
59376
60292
  }
59377
60293
  /**
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
60294
+ * Debounced disk writer, behind two gates.
60295
+ *
60296
+ * GATE — DURABILITY: a change to a `durability: 'session'` cap never even
60297
+ * arms the timer. That is where the fleet's write rate goes: `audio-metrics`
60298
+ * and `zone-analytics` are ~90 % of the offered 12–19 writes/s, they change
60299
+ * genuinely every second, and their restored value is worthless.
60300
+ *
60301
+ * GATE — EFFECTIVE CHANGE (at flush): the persistable blob is compared with
60302
+ * what is believed to be on disk, ignoring the clock fields each cap declared
60303
+ * volatile. `battery` wrote its blob 526 times in 25 minutes without one
60304
+ * percentage moving; this is the gate that turns those into one write.
60305
+ *
60306
+ * The blob is read from the live mirror at flush time, so the disk picture is
59382
60307
  * always the latest state — no risk of writing a stale snapshot.
59383
60308
  */
59384
- scheduleRuntimeStateDiskWrite(deviceId, settings) {
60309
+ scheduleRuntimeStateDiskWrite(deviceId, settings, changedCap) {
60310
+ if (this.policyFor(changedCap).durability !== "restored") return;
59385
60311
  let slot = this.runtimeStateDebounce.get(deviceId);
59386
60312
  if (!slot) {
59387
60313
  slot = {
@@ -59393,10 +60319,21 @@ var require_device_manager_addon = __commonJS({
59393
60319
  if (slot.timer) return;
59394
60320
  slot.timer = setTimeout(() => {
59395
60321
  slot.timer = null;
59396
- const blob = this.snapshotForDevice(deviceId);
60322
+ const blob = this.persistableForDevice(deviceId);
60323
+ if (this.isAlreadyPersisted(deviceId, blob)) return;
60324
+ const skipped = this.skippedSinceWrite.get(deviceId) ?? 0;
60325
+ this.skippedSinceWrite.delete(deviceId);
59397
60326
  const write = (async () => {
59398
60327
  try {
59399
60328
  await settings.writeDeviceRuntimeState(deviceId, blob);
60329
+ this.lastPersisted.set(deviceId, blob);
60330
+ if (skipped > 0) this.ctx.logger.debug("runtime state persisted", {
60331
+ tags: { deviceId },
60332
+ meta: {
60333
+ caps: Object.keys(blob).length,
60334
+ skippedSinceLastWrite: skipped
60335
+ }
60336
+ });
59400
60337
  } catch (err) {
59401
60338
  this.ctx.logger.warn("writeDeviceRuntimeState failed", {
59402
60339
  tags: { deviceId },
@@ -59410,6 +60347,22 @@ var require_device_manager_addon = __commonJS({
59410
60347
  }, DeviceStateMirror2.RUNTIME_STATE_DEBOUNCE_MS);
59411
60348
  }
59412
60349
  /**
60350
+ * True when `blob` differs from what is believed to be on disk only in fields
60351
+ * the owning caps declared volatile. Counts the skip so the next real write
60352
+ * can report how much churn was absorbed.
60353
+ */
60354
+ isAlreadyPersisted(deviceId, blob) {
60355
+ const onDisk = this.lastPersisted.get(deviceId);
60356
+ if (!onDisk) return false;
60357
+ if (!effectivelyEqual(onDisk, blob, this.policyFor)) return false;
60358
+ this.skippedSinceWrite.set(deviceId, (this.skippedSinceWrite.get(deviceId) ?? 0) + 1);
60359
+ return true;
60360
+ }
60361
+ /** The device's mirror, restricted to the slices that may reach disk. */
60362
+ persistableForDevice(deviceId) {
60363
+ return persistableBlob(this.snapshotForDevice(deviceId), this.policyFor);
60364
+ }
60365
+ /**
59413
60366
  * One-shot mirror seed used by `loadRuntimeState` at boot so the
59414
60367
  * hub knows about every persisted slice without waiting for the
59415
60368
  * first `setCapSlice` call. No events emitted — this is
@@ -59429,6 +60382,7 @@ var require_device_manager_addon = __commonJS({
59429
60382
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
59430
60383
  perCap.set(capName, { ...raw });
59431
60384
  }
60385
+ this.lastPersisted.set(deviceId, persistableBlob(this.snapshotForDevice(deviceId), this.policyFor));
59432
60386
  }
59433
60387
  /**
59434
60388
  * The hub mirror's `feature-probe.lastProbedAt` is a PER-SESSION liveness
@@ -59542,15 +60496,15 @@ var require_device_manager_addon = __commonJS({
59542
60496
  if (slot.timer) {
59543
60497
  clearTimeout(slot.timer);
59544
60498
  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
- }
60499
+ const blob = settings ? this.persistableForDevice(deviceId) : null;
60500
+ if (settings && blob !== null && !this.isAlreadyPersisted(deviceId, blob)) pending.push(settings.writeDeviceRuntimeState(deviceId, blob).then(() => {
60501
+ this.lastPersisted.set(deviceId, blob);
60502
+ }).catch((err) => {
60503
+ this.ctx.logger.warn("shutdown writeDeviceRuntimeState failed", {
60504
+ tags: { deviceId },
60505
+ meta: { error: err instanceof Error ? err.message : String(err) }
60506
+ });
60507
+ }));
59554
60508
  }
59555
60509
  if (slot.inFlight) pending.push(slot.inFlight);
59556
60510
  }
@@ -59977,7 +60931,7 @@ var require_device_manager_addon = __commonJS({
59977
60931
  setCapSlice: async (input) => {
59978
60932
  const { deviceId, capName, slice } = input;
59979
60933
  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);
60934
+ if (stateMirror.applySingleCapUpdate(deviceId, capName, slice)) stateMirror.scheduleRuntimeStateDiskWrite(deviceId, settings, capName);
59981
60935
  }
59982
60936
  }
59983
60937
  }];
@@ -60018,7 +60972,7 @@ var require_hub_forwarder = __commonJS({
60018
60972
  [Symbol.toStringTag]: { value: "Module" }
60019
60973
  });
60020
60974
  require_chunk_Cek0wNdY();
60021
- var require_dist10 = require_dist_BV5Bug9t();
60975
+ var require_dist10 = require_dist_D4nuaPdd();
60022
60976
  var require_formatter = require_formatter_DqAKDlvN();
60023
60977
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
60024
60978
  var HubForwarderDestination = class {
@@ -60155,7 +61109,7 @@ var require_liveness_monitor_addon = __commonJS({
60155
61109
  "use strict";
60156
61110
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60157
61111
  require_chunk_Cek0wNdY();
60158
- var require_dist10 = require_dist_BV5Bug9t();
61112
+ var require_dist10 = require_dist_D4nuaPdd();
60159
61113
  var NO_DEVICES = "liveness:no-devices";
60160
61114
  var ALL_OFFLINE = "liveness:all-devices-offline";
60161
61115
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -60345,7 +61299,7 @@ var require_local_auth_addon = __commonJS({
60345
61299
  [Symbol.toStringTag]: { value: "Module" }
60346
61300
  });
60347
61301
  var require_chunk = require_chunk_Cek0wNdY();
60348
- var require_dist10 = require_dist_BV5Bug9t();
61302
+ var require_dist10 = require_dist_D4nuaPdd();
60349
61303
  var node_crypto = __require("crypto");
60350
61304
  node_crypto = require_chunk.__toESM(node_crypto);
60351
61305
  var crypto$1 = __require("crypto");
@@ -68029,7 +68983,7 @@ var require_loki_logging = __commonJS({
68029
68983
  [Symbol.toStringTag]: { value: "Module" }
68030
68984
  });
68031
68985
  require_chunk_Cek0wNdY();
68032
- var require_dist10 = require_dist_BV5Bug9t();
68986
+ var require_dist10 = require_dist_D4nuaPdd();
68033
68987
  function sanitizeLabelName(raw) {
68034
68988
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
68035
68989
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -68594,7 +69548,7 @@ var require_native_metrics_addon = __commonJS({
68594
69548
  [Symbol.toStringTag]: { value: "Module" }
68595
69549
  });
68596
69550
  var require_chunk = require_chunk_Cek0wNdY();
68597
- var require_dist10 = require_dist_BV5Bug9t();
69551
+ var require_dist10 = require_dist_D4nuaPdd();
68598
69552
  var node_child_process = __require("child_process");
68599
69553
  var node_util = __require("util");
68600
69554
  var node_os = __require("os");
@@ -69536,7 +70490,7 @@ var require_filesystem_storage_addon = __commonJS({
69536
70490
  [Symbol.toStringTag]: { value: "Module" }
69537
70491
  });
69538
70492
  var require_chunk = require_chunk_Cek0wNdY();
69539
- var require_dist10 = require_dist_BV5Bug9t();
70493
+ var require_dist10 = require_dist_D4nuaPdd();
69540
70494
  var node_crypto = __require("crypto");
69541
70495
  var node_fs_promises = __require("fs/promises");
69542
70496
  var node_path = __require("path");
@@ -70652,7 +71606,7 @@ var require_sqlite_settings_addon = __commonJS({
70652
71606
  [Symbol.toStringTag]: { value: "Module" }
70653
71607
  });
70654
71608
  var require_chunk = require_chunk_Cek0wNdY();
70655
- var require_dist10 = require_dist_BV5Bug9t();
71609
+ var require_dist10 = require_dist_D4nuaPdd();
70656
71610
  var node_crypto = __require("crypto");
70657
71611
  var node_fs = __require("fs");
70658
71612
  var node_module = __require("module");
@@ -71324,6 +72278,42 @@ var require_sqlite_settings_addon = __commonJS({
71324
72278
  const where = this.prefixWhere(`${addonId}:${deviceId}.`);
71325
72279
  this.getDb().prepare(`DELETE FROM "addon-device-settings" WHERE ${where.sql}`).run(...where.params);
71326
72280
  }
72281
+ static LEGACY_RUNTIME_STATE_ADDON_ID = "__device-state";
72282
+ /**
72283
+ * The device's whole cap-slice map. Canonical row first, legacy prefix as the
72284
+ * fallback. `{}` — never null — when the device has never persisted a slice,
72285
+ * because every caller treats the result as a cap map to iterate.
72286
+ */
72287
+ getDeviceRuntimeState(deviceId) {
72288
+ this.requireDeclared("device-runtime-state");
72289
+ const row = this.getDb().prepare('SELECT data FROM "device-runtime-state" WHERE id = ?').get(deviceId);
72290
+ if (row) return parseRowData(row.data);
72291
+ return this.getAddonDevice(SqliteSettingsBackend2.LEGACY_RUNTIME_STATE_ADDON_ID, deviceId);
72292
+ }
72293
+ /**
72294
+ * Replace the device's whole cap-slice map. Replace, not merge — the caller
72295
+ * always hands over the full persistable blob, and merging would resurrect a
72296
+ * slice the durability policy has since stopped persisting.
72297
+ */
72298
+ setDeviceRuntimeState(deviceId, blob) {
72299
+ this.requireDeclared("device-runtime-state");
72300
+ this.requireDeclared("addon-device-settings");
72301
+ const db = this.getDb();
72302
+ const upsert = db.prepare(`INSERT INTO "device-runtime-state" (id, data) VALUES (?, ?)
72303
+ ON CONFLICT(id) DO UPDATE SET data = excluded.data`);
72304
+ const legacy = this.prefixWhere(`${SqliteSettingsBackend2.LEGACY_RUNTIME_STATE_ADDON_ID}:${deviceId}.`);
72305
+ const retire = db.prepare(`DELETE FROM "addon-device-settings" WHERE ${legacy.sql}`);
72306
+ db.transaction(() => {
72307
+ upsert.run(deviceId, JSON.stringify(blob));
72308
+ retire.run(...legacy.params);
72309
+ })();
72310
+ }
72311
+ /** Forget the device's runtime state in BOTH stores (device removal). */
72312
+ clearDeviceRuntimeState(deviceId) {
72313
+ this.requireDeclared("device-runtime-state");
72314
+ this.getDb().prepare('DELETE FROM "device-runtime-state" WHERE id = ?').run(deviceId);
72315
+ this.clearAddonDevice(SqliteSettingsBackend2.LEGACY_RUNTIME_STATE_ADDON_ID, deviceId);
72316
+ }
71327
72317
  /** Seed system-settings with runtime defaults (first boot) */
71328
72318
  async seedDefaults() {
71329
72319
  this.requireDeclared("system-settings");
@@ -72167,7 +73157,7 @@ var require_storage_orchestrator_addon = __commonJS({
72167
73157
  [Symbol.toStringTag]: { value: "Module" }
72168
73158
  });
72169
73159
  var require_chunk = require_chunk_Cek0wNdY();
72170
- var require_dist10 = require_dist_BV5Bug9t();
73160
+ var require_dist10 = require_dist_D4nuaPdd();
72171
73161
  var node_crypto = __require("crypto");
72172
73162
  var node_fs_promises = __require("fs/promises");
72173
73163
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -74008,7 +74998,7 @@ var require_system_config_addon = __commonJS({
74008
74998
  [Symbol.toStringTag]: { value: "Module" }
74009
74999
  });
74010
75000
  require_chunk_Cek0wNdY();
74011
- var require_dist10 = require_dist_BV5Bug9t();
75001
+ var require_dist10 = require_dist_D4nuaPdd();
74012
75002
  var SECTION_TITLES = {
74013
75003
  server: "Server",
74014
75004
  auth: "Authentication"
@@ -92069,7 +93059,7 @@ var require_winston_logging = __commonJS({
92069
93059
  [Symbol.toStringTag]: { value: "Module" }
92070
93060
  });
92071
93061
  var require_chunk = require_chunk_Cek0wNdY();
92072
- var require_dist10 = require_dist_BV5Bug9t();
93062
+ var require_dist10 = require_dist_D4nuaPdd();
92073
93063
  var require_formatter = require_formatter_DqAKDlvN();
92074
93064
  var node_path = __require("path");
92075
93065
  node_path = require_chunk.__toESM(node_path);
@@ -93220,9 +94210,9 @@ var require_event_category_DxZbWydC = __commonJS({
93220
94210
  }
93221
94211
  });
93222
94212
 
93223
- // ../types/dist/sleep-BszXLEvP.js
93224
- var require_sleep_BszXLEvP = __commonJS({
93225
- "../types/dist/sleep-BszXLEvP.js"(exports) {
94213
+ // ../types/dist/sleep-Dh1EJSqF.js
94214
+ var require_sleep_Dh1EJSqF = __commonJS({
94215
+ "../types/dist/sleep-Dh1EJSqF.js"(exports) {
93226
94216
  "use strict";
93227
94217
  var require_event_category = require_event_category_DxZbWydC();
93228
94218
  var zod = require_zod();
@@ -94830,6 +95820,7 @@ var require_sleep_BszXLEvP = __commonJS({
94830
95820
  const value = Reflect.get(context, CAP_NODE_PIN_CONTEXT_KEY);
94831
95821
  return typeof value === "string" ? value : void 0;
94832
95822
  }
95823
+ var DEFAULT_RUNTIME_STATE_DURABILITY = "session";
94833
95824
  function resolveCapMount(def) {
94834
95825
  if (def.mount) return def.mount;
94835
95826
  if (def.deviceNative === true) return { kind: "device-native" };
@@ -96215,6 +97206,12 @@ var require_sleep_BszXLEvP = __commonJS({
96215
97206
  return DATAPLANE_SECRET_HEADER;
96216
97207
  }
96217
97208
  });
97209
+ Object.defineProperty(exports, "DEFAULT_RUNTIME_STATE_DURABILITY", {
97210
+ enumerable: true,
97211
+ get: function() {
97212
+ return DEFAULT_RUNTIME_STATE_DURABILITY;
97213
+ }
97214
+ });
96218
97215
  Object.defineProperty(exports, "DEVICE_SCOPED_CAPS", {
96219
97216
  enumerable: true,
96220
97217
  get: function() {
@@ -96638,7 +97635,7 @@ var require_addon = __commonJS({
96638
97635
  "use strict";
96639
97636
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
96640
97637
  var require_event_category = require_event_category_DxZbWydC();
96641
- var require_sleep = require_sleep_BszXLEvP();
97638
+ var require_sleep = require_sleep_Dh1EJSqF();
96642
97639
  var require_err_msg = require_err_msg_COpsHMw2();
96643
97640
  var COLLECTION_ARRAY_METHODS = Object.freeze({
96644
97641
  "addon-pages-source": ["listPages"],
@@ -114082,7 +115079,7 @@ var require_dist3 = __commonJS({
114082
115079
  "use strict";
114083
115080
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
114084
115081
  var require_chunk = require_chunk_Cek0wNdY();
114085
- var require_dist10 = require_dist_BV5Bug9t();
115082
+ var require_dist10 = require_dist_D4nuaPdd();
114086
115083
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
114087
115084
  require_alerts();
114088
115085
  var require_formatter = require_formatter_DqAKDlvN();
@@ -122793,6 +123790,32 @@ var require_dist3 = __commonJS({
122793
123790
  if (this.settingsStore === null) return;
122794
123791
  this.settingsStore.clearAddonDevice(addonId, deviceId);
122795
123792
  }
123793
+ /**
123794
+ * The device's persisted runtime-state blob. `{}` before the settings store
123795
+ * is wired — the same answer as "this device has never persisted a slice",
123796
+ * which is what every caller already handles.
123797
+ */
123798
+ getDeviceRuntimeState(deviceId) {
123799
+ if (this.settingsStore === null) return {};
123800
+ return this.settingsStore.getDeviceRuntimeState(deviceId);
123801
+ }
123802
+ /**
123803
+ * Replace the device's persisted runtime-state blob.
123804
+ *
123805
+ * THROWS when the store is not wired, and that is deliberate: the caller
123806
+ * (`DeviceStateMirror.scheduleRuntimeStateDiskWrite`) catches and logs, so a
123807
+ * persist that cannot happen leaves a line naming the device. A silent no-op
123808
+ * here is the shape that let a whole collection read zero for months.
123809
+ */
123810
+ setDeviceRuntimeState(deviceId, blob) {
123811
+ if (this.settingsStore === null) throw new Error("[ConfigManager] SettingsStore not initialized -- call setSettingsStore() first");
123812
+ this.settingsStore.setDeviceRuntimeState(deviceId, blob);
123813
+ }
123814
+ /** Forget the device's runtime state. No-op before the store is wired. */
123815
+ clearDeviceRuntimeState(deviceId) {
123816
+ if (this.settingsStore === null) return;
123817
+ this.settingsStore.clearDeviceRuntimeState(deviceId);
123818
+ }
122796
123819
  createSettingsView(addonId) {
122797
123820
  const cm = this;
122798
123821
  return {
@@ -122821,13 +123844,13 @@ var require_dist3 = __commonJS({
122821
123844
  cm.clearAddonDevice(addonId, String(deviceId));
122822
123845
  },
122823
123846
  async readDeviceRuntimeState(deviceId) {
122824
- return cm.getAddonDevice("__device-state", String(deviceId));
123847
+ return cm.getDeviceRuntimeState(String(deviceId));
122825
123848
  },
122826
123849
  async writeDeviceRuntimeState(deviceId, data) {
122827
- cm.setAddonDevice("__device-state", String(deviceId), data);
123850
+ cm.setDeviceRuntimeState(String(deviceId), data);
122828
123851
  },
122829
123852
  async clearDeviceRuntimeState(deviceId) {
122830
- cm.clearAddonDevice("__device-state", String(deviceId));
123853
+ cm.clearDeviceRuntimeState(String(deviceId));
122831
123854
  },
122832
123855
  async getSection(section) {
122833
123856
  return cm.getSection(section);
@@ -233388,7 +234411,7 @@ var require_dist9 = __commonJS({
233388
234411
  "use strict";
233389
234412
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
233390
234413
  var require_event_category = require_event_category_DxZbWydC();
233391
- var require_sleep = require_sleep_BszXLEvP();
234414
+ var require_sleep = require_sleep_Dh1EJSqF();
233392
234415
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
233393
234416
  var require_enums2 = require_enums();
233394
234417
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -238479,7 +239502,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
238479
239502
  low: zod.z.string().optional()
238480
239503
  }),
238481
239504
  lastChangedAt: zod.z.number()
238482
- })
239505
+ }),
239506
+ /**
239507
+ * 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.
239508
+ *
239509
+ * See `RuntimeStateDurability`. Enforced by
239510
+ * `scripts/check-runtime-state-durability.ts`.
239511
+ */
239512
+ durability: "session"
238483
239513
  };
238484
239514
  function isVoidInput(schema) {
238485
239515
  const def2 = schema._def;
@@ -239258,6 +240288,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
239258
240288
  kind: "poll"
239259
240289
  },
239260
240290
  runtimeState: DeviceDiscoveryStatusSchema.extend({ lastFetchedAt: zod.z.number().int().nonnegative() }),
240291
+ /**
240292
+ * Runtime-state durability: **session** — 5.7 KB of scan output on the largest device, fully re-derivable by re-scanning.
240293
+ *
240294
+ * See `RuntimeStateDurability`. Enforced by
240295
+ * `scripts/check-runtime-state-durability.ts`.
240296
+ */
240297
+ durability: "session",
239261
240298
  methods: {
239262
240299
  /**
239263
240300
  * Snapshot of the current `discovered` list. Returns the
@@ -241486,7 +242523,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241486
242523
  * else — see `notification-center/action-token.ts` for what that does and
241487
242524
  * does not buy.
241488
242525
  */
241489
- destructive: zod.z.boolean().optional()
242526
+ destructive: zod.z.boolean().optional(),
242527
+ /**
242528
+ * How the tap should REACH the url.
242529
+ *
242530
+ * `navigate` (absent, and every button authored before this field) opens it:
242531
+ * the phone leaves the notification and shows whatever the callback returns.
242532
+ * That is right for a button whose answer the operator wants to read.
242533
+ *
242534
+ * `background` fires it as a POST and stays put. It exists for the buttons
242535
+ * whose whole point is not to interrupt — "silence this for 30 minutes" is
242536
+ * an answer to the notification, and being thrown into a browser tab to
242537
+ * confirm it costs more attention than the notification did. A backend that
242538
+ * cannot do a background call renders it as an ordinary link (the adapters
242539
+ * fall back rather than dropping the button), so this is a preference, never
242540
+ * a requirement.
242541
+ */
242542
+ mode: zod.z.enum(["navigate", "background"]).optional()
241490
242543
  });
241491
242544
  var NotificationSchema = zod.z.object({
241492
242545
  body: zod.z.string(),
@@ -242622,7 +243675,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242622
243675
  * full slice; renders an arm button per `availableModes` entry and
242623
243676
  * a PIN field iff `requiresCode === true`.
242624
243677
  */
242625
- runtimeState: AlarmPanelStatusSchema
243678
+ runtimeState: AlarmPanelStatusSchema,
243679
+ /**
243680
+ * Runtime-state durability: **restored** — armed state is the one thing a panel must not lose across a restart.
243681
+ *
243682
+ * See `RuntimeStateDurability`. Enforced by
243683
+ * `scripts/check-runtime-state-durability.ts`.
243684
+ */
243685
+ durability: "restored",
243686
+ /** Clock fields: written, but excluded from the compare that decides
243687
+ * whether persisting is worth a SQLite commit. */
243688
+ volatileStateFields: ["lastChangedAt"]
242626
243689
  };
242627
243690
  var MaskPointSchema = zod.z.object({
242628
243691
  x: zod.z.number(),
@@ -242726,6 +243789,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242726
243789
  packageNames: zod.z.array(zod.z.string().min(1)).min(1).optional()
242727
243790
  });
242728
243791
  var NC_MAX_PER_TRACK_IMMEDIATE = 1;
243792
+ var NC_SNOOZE_MAX_MINUTES = 1440;
243793
+ var NC_DEFAULT_SNOOZE_MINUTES = [
243794
+ 10,
243795
+ 30,
243796
+ 60
243797
+ ];
242729
243798
  var NcScheduleWindowSchema = zod.z.object({
242730
243799
  /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
242731
243800
  days: zod.z.array(zod.z.number().int().min(0).max(6)).min(1),
@@ -242983,15 +244052,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242983
244052
  * (an `immediate` rule naming an `audio-*` class, one notification per
242984
244053
  * classified sample) stays exactly as it was for rules that already use it.
242985
244054
  *
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
244055
+ * In {@link NC_CONDITION_CATALOG} since P2, and the ORDER it got there is the
244056
+ * rule rather than an accident: the viewer mirrors the descriptor enums BY
244057
+ * HAND (`camstack/src/data/notification-center.ts`, guarded by
244058
+ * `scripts/check-viewer-condition-mirror.ts`) and its rule editor strips the
242990
244059
  * condition fields it does not know when a rule is saved from the phone.
242991
244060
  * 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.
244061
+ * operator loses a rule's conditions by opening it — so the viewer mirror
244062
+ * (P3, shipped) went FIRST, and the descriptor an editor renders from
244063
+ * follows here.
242995
244064
  */
242996
244065
  audio: NcAudioConditionSchema.optional()
242997
244066
  });
@@ -243161,6 +244230,30 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243161
244230
  */
243162
244231
  snoozeAllowGlobal: zod.z.boolean().optional(),
243163
244232
  /**
244233
+ * The snooze durations THIS rule's notification offers as buttons, in
244234
+ * minutes.
244235
+ *
244236
+ * Three states, and all three are distinct — which is exactly why this is
244237
+ * `.optional()` and never `.default()`. A Zod default does not run on the
244238
+ * addon cap path (three production failures in one day), so a schema default
244239
+ * would collapse the first two:
244240
+ *
244241
+ * | value | meaning |
244242
+ * | --- | --- |
244243
+ * | absent | the operator never said ⇒ {@link NC_DEFAULT_SNOOZE_MINUTES} |
244244
+ * | `[]` | **no snooze buttons on this rule** — the explicit override |
244245
+ * | a list | these choices, de-duplicated and sorted, at most four |
244246
+ *
244247
+ * `.max(4)` because the notifier's own action budget is small (ntfy allows
244248
+ * three buttons in total) and a rule that spent it all on snooze choices
244249
+ * would push its own tap-through actions off the notification.
244250
+ *
244251
+ * An empty list is NOT an alarm exemption: a rule the alarm is about, or
244252
+ * that arms the panel, is exempt automatically and cannot be silenced by a
244253
+ * window from anywhere (D133).
244254
+ */
244255
+ snoozeOptions: zod.z.array(zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES)).max(4).optional(),
244256
+ /**
243164
244257
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
243165
244258
  *
243166
244259
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -243250,6 +244343,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243250
244343
  "device",
243251
244344
  "package",
243252
244345
  "occupancy",
244346
+ "audio",
243253
244347
  "system"
243254
244348
  ]),
243255
244349
  label: zod.z.string(),
@@ -243268,6 +244362,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243268
244362
  "crossingSelect",
243269
244363
  "polygonDraw",
243270
244364
  "occupancy",
244365
+ "audio",
243271
244366
  "deviceState",
243272
244367
  "systemEvent"
243273
244368
  ]),
@@ -243661,6 +244756,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243661
244756
  phase: "P1",
243662
244757
  description: "ZoneAnalytics occupancy edge (optionally zone/class-scoped): count crosses the threshold and holds for sustainSeconds. Fail-closed on a missing snapshot."
243663
244758
  },
244759
+ {
244760
+ id: "audio",
244761
+ group: "audio",
244762
+ label: "Sound",
244763
+ valueType: "audio",
244764
+ operator: "anyOf",
244765
+ appliesTo: ["immediate"],
244766
+ phase: "P2",
244767
+ 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)."
244768
+ },
243664
244769
  {
243665
244770
  id: "customZones",
243666
244771
  group: "zones",
@@ -243759,7 +244864,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243759
244864
  "device",
243760
244865
  "all"
243761
244866
  ]);
243762
- var NC_SNOOZE_MAX_MINUTES = 1440;
243763
244867
  var NcSnoozeInputSchema = zod.z.object({
243764
244868
  scope: NcSnoozeScopeSchema,
243765
244869
  /** Required when `scope: 'rule'` — a scoped snooze with no id matches
@@ -243767,6 +244871,19 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243767
244871
  ruleId: zod.z.string().optional(),
243768
244872
  /** Required when `scope: 'device'`. */
243769
244873
  deviceId: zod.z.number().int().optional(),
244874
+ /**
244875
+ * Narrow the window to these subject classes — "the cat, not the person".
244876
+ *
244877
+ * ORTHOGONAL to `scope`, deliberately, and absent means EVERY class: that is
244878
+ * what every window authored before this field meant, so no persisted row
244879
+ * changes meaning and no client has to learn anything to keep working.
244880
+ *
244881
+ * It is what makes the window's real key `(deviceId, classes[])` and lets it
244882
+ * cross rules (D133): the operator points at a camera and a kind of thing,
244883
+ * not at whichever of their four rules happened to produce the notification
244884
+ * they are dismissing.
244885
+ */
244886
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
243770
244887
  durationMinutes: zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
243771
244888
  /**
243772
244889
  * Silence this for EVERY recipient, not just the caller. Permission is
@@ -243791,6 +244908,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243791
244908
  scope: NcSnoozeScopeSchema,
243792
244909
  ruleId: zod.z.string().optional(),
243793
244910
  deviceId: zod.z.number().int().optional(),
244911
+ /** Subject classes this window covers. ABSENT = every class — see
244912
+ * {@link NcSnoozeInputSchema.shape.classes}. Lives in the JSON blob and has
244913
+ * no SQLite column: nothing queries a window by class. */
244914
+ classes: zod.z.array(zod.z.string().min(1)).min(1).optional(),
243794
244915
  startedAt: zod.z.number(),
243795
244916
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
243796
244917
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -246063,7 +247184,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246063
247184
  * handle. Slice shape is `{ zones: Zone[] }` so future extensions
246064
247185
  * (e.g. zone groupings) can sit alongside the polygon list.
246065
247186
  */
246066
- runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() })
247187
+ runtimeState: zod.z.object({ zones: zod.z.array(ZoneSchema).readonly() }),
247188
+ /**
247189
+ * 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.
247190
+ *
247191
+ * See `RuntimeStateDurability`. Enforced by
247192
+ * `scripts/check-runtime-state-durability.ts`.
247193
+ */
247194
+ durability: "restored"
246067
247195
  };
246068
247196
  var NativeCropBboxSchema = zod.z.object({
246069
247197
  x: zod.z.number(),
@@ -249510,7 +250638,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249510
250638
  schema: AirQualitySensorStatusSchema,
249511
250639
  kind: "push"
249512
250640
  },
249513
- runtimeState: AirQualitySensorStatusSchema
250641
+ runtimeState: AirQualitySensorStatusSchema,
250642
+ /**
250643
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
250644
+ *
250645
+ * See `RuntimeStateDurability`. Enforced by
250646
+ * `scripts/check-runtime-state-durability.ts`.
250647
+ */
250648
+ durability: "restored",
250649
+ /** Clock fields: written, but excluded from the compare that decides
250650
+ * whether persisting is worth a SQLite commit. */
250651
+ volatileStateFields: ["lastFetchedAt"]
249514
250652
  };
249515
250653
  var AmbientLightSensorStatusSchema = zod.z.object({
249516
250654
  /** Current illuminance in lux (lx). */
@@ -249538,7 +250676,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249538
250676
  schema: AmbientLightSensorStatusSchema,
249539
250677
  kind: "push"
249540
250678
  },
249541
- runtimeState: AmbientLightSensorStatusSchema
250679
+ runtimeState: AmbientLightSensorStatusSchema,
250680
+ /**
250681
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
250682
+ *
250683
+ * See `RuntimeStateDurability`. Enforced by
250684
+ * `scripts/check-runtime-state-durability.ts`.
250685
+ */
250686
+ durability: "restored",
250687
+ /** Clock fields: written, but excluded from the compare that decides
250688
+ * whether persisting is worth a SQLite commit. */
250689
+ volatileStateFields: ["lastFetchedAt"]
249542
250690
  };
249543
250691
  var AudioClassSummarySchema = zod.z.object({
249544
250692
  className: zod.z.string(),
@@ -249628,7 +250776,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249628
250776
  }), AudioMetricsHistorySchema)
249629
250777
  },
249630
250778
  /** Reactive runtime-state mirror — live `device.state.audioMetrics.value`. */
249631
- runtimeState: AudioMetricsSnapshotSchema
250779
+ runtimeState: AudioMetricsSnapshotSchema,
250780
+ /**
250781
+ * 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.
250782
+ *
250783
+ * See `RuntimeStateDurability`. Enforced by
250784
+ * `scripts/check-runtime-state-durability.ts`.
250785
+ */
250786
+ durability: "session"
249632
250787
  };
249633
250788
  var AutomationControlStatusSchema = zod.z.object({
249634
250789
  /** Whether the automation is currently enabled. Disabled automations
@@ -249679,7 +250834,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249679
250834
  * reads `enabled` (toggle) + `isRunning` (spinner) + `lastError`
249680
250835
  * (badge) directly.
249681
250836
  */
249682
- runtimeState: AutomationControlStatusSchema
250837
+ runtimeState: AutomationControlStatusSchema,
250838
+ /**
250839
+ * 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.
250840
+ *
250841
+ * See `RuntimeStateDurability`. Enforced by
250842
+ * `scripts/check-runtime-state-durability.ts`.
250843
+ */
250844
+ durability: "session"
249683
250845
  };
249684
250846
  var BatteryStatusSchema = zod.z.object({
249685
250847
  /** 0..100 inclusive. Firmware-reported. */
@@ -249780,7 +250942,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249780
250942
  * via `device.runtimeState.getCapState('battery')` regardless of
249781
250943
  * the underlying driver.
249782
250944
  */
249783
- runtimeState: BatteryStatusSchema
250945
+ runtimeState: BatteryStatusSchema,
250946
+ /**
250947
+ * 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.
250948
+ *
250949
+ * See `RuntimeStateDurability`. Enforced by
250950
+ * `scripts/check-runtime-state-durability.ts`.
250951
+ */
250952
+ durability: "restored",
250953
+ /** Clock fields: written, but excluded from the compare that decides
250954
+ * whether persisting is worth a SQLite commit. */
250955
+ volatileStateFields: ["lastUpdated"]
249784
250956
  };
249785
250957
  var BinaryStatusSchema = zod.z.object({
249786
250958
  on: zod.z.boolean(),
@@ -249798,7 +250970,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249798
250970
  schema: BinaryStatusSchema,
249799
250971
  kind: "push"
249800
250972
  },
249801
- runtimeState: BinaryStatusSchema
250973
+ runtimeState: BinaryStatusSchema,
250974
+ /**
250975
+ * Runtime-state durability: **restored** — transition-driven sensor state; the restored value gives the boot comparison.
250976
+ *
250977
+ * See `RuntimeStateDurability`. Enforced by
250978
+ * `scripts/check-runtime-state-durability.ts`.
250979
+ */
250980
+ durability: "restored",
250981
+ /** Clock fields: written, but excluded from the compare that decides
250982
+ * whether persisting is worth a SQLite commit. */
250983
+ volatileStateFields: ["lastChangedAt"]
249802
250984
  };
249803
250985
  var BrightnessStatusSchema = zod.z.object({
249804
250986
  /** Current level as 0..100 inclusive. Firmware-reported. */
@@ -249840,7 +251022,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249840
251022
  * by the kernel. Read via `device.state.brightness.value` so UI
249841
251023
  * sliders surface the current level without polling the provider.
249842
251024
  */
249843
- runtimeState: BrightnessStatusSchema
251025
+ runtimeState: BrightnessStatusSchema,
251026
+ /**
251027
+ * Runtime-state durability: **session** — live lamp state, re-published by the provider on connect.
251028
+ *
251029
+ * See `RuntimeStateDurability`. Enforced by
251030
+ * `scripts/check-runtime-state-durability.ts`.
251031
+ */
251032
+ durability: "session"
249844
251033
  };
249845
251034
  var buttonCapability = {
249846
251035
  name: "button",
@@ -249920,7 +251109,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249920
251109
  schema: CarbonMonoxideStatusSchema,
249921
251110
  kind: "push"
249922
251111
  },
249923
- runtimeState: CarbonMonoxideStatusSchema
251112
+ runtimeState: CarbonMonoxideStatusSchema,
251113
+ /**
251114
+ * Runtime-state durability: **restored** — as `smoke`.
251115
+ *
251116
+ * See `RuntimeStateDurability`. Enforced by
251117
+ * `scripts/check-runtime-state-durability.ts`.
251118
+ */
251119
+ durability: "restored",
251120
+ /** Clock fields: written, but excluded from the compare that decides
251121
+ * whether persisting is worth a SQLite commit. */
251122
+ volatileStateFields: ["lastChangedAt"]
249924
251123
  };
249925
251124
  var HvacModeSchema = zod.z.enum([
249926
251125
  "off",
@@ -250049,7 +251248,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250049
251248
  * the full slice via `device.state.climate-control.value` and refresh
250050
251249
  * on every push without re-querying the provider.
250051
251250
  */
250052
- runtimeState: ClimateControlStatusSchema
251251
+ runtimeState: ClimateControlStatusSchema,
251252
+ /**
251253
+ * Runtime-state durability: **session** — as `brightness`; `currentTemp` moves continuously and is re-published on connect.
251254
+ *
251255
+ * See `RuntimeStateDurability`. Enforced by
251256
+ * `scripts/check-runtime-state-durability.ts`.
251257
+ */
251258
+ durability: "session"
250053
251259
  };
250054
251260
  var RgbTripletSchema = zod.z.object({
250055
251261
  r: zod.z.number().int().min(0).max(255),
@@ -250136,7 +251342,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250136
251342
  * kernel. Read via `device.state.color.value` so UI pickers surface
250137
251343
  * the current chromaticity without polling the provider.
250138
251344
  */
250139
- runtimeState: ColorStatusSchema
251345
+ runtimeState: ColorStatusSchema,
251346
+ /**
251347
+ * Runtime-state durability: **session** — as `brightness`.
251348
+ *
251349
+ * See `RuntimeStateDurability`. Enforced by
251350
+ * `scripts/check-runtime-state-durability.ts`.
251351
+ */
251352
+ durability: "session"
250140
251353
  };
250141
251354
  var CONNECTION_TEST_TIMEOUT_MS = 2e4;
250142
251355
  var ConnectionTestOutcomeSchema = zod.z.discriminatedUnion("outcome", [
@@ -250191,7 +251404,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250191
251404
  schema: ConnectivityStatusSchema,
250192
251405
  kind: "push"
250193
251406
  },
250194
- runtimeState: ConnectivityStatusSchema
251407
+ runtimeState: ConnectivityStatusSchema,
251408
+ /**
251409
+ * Runtime-state durability: **restored** — same shape and same argument as `device-status`, for links rather than devices.
251410
+ *
251411
+ * See `RuntimeStateDurability`. Enforced by
251412
+ * `scripts/check-runtime-state-durability.ts`.
251413
+ */
251414
+ durability: "restored",
251415
+ /** Clock fields: written, but excluded from the compare that decides
251416
+ * whether persisting is worth a SQLite commit. */
251417
+ volatileStateFields: ["lastChangedAt"]
250195
251418
  };
250196
251419
  var ConsumableItemSchema = zod.z.object({
250197
251420
  /** Stable id, e.g. 'main-brush'. */
@@ -250258,7 +251481,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250258
251481
  }
250259
251482
  }
250260
251483
  },
250261
- runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() })
251484
+ runtimeState: ConsumablesStatusSchema.extend({ lastFetchedAt: zod.z.number() }),
251485
+ /**
251486
+ * Runtime-state durability: **session** — the authority is the appliance; the provider re-reads the whole item array on connect.
251487
+ *
251488
+ * See `RuntimeStateDurability`. Enforced by
251489
+ * `scripts/check-runtime-state-durability.ts`.
251490
+ */
251491
+ durability: "session"
250262
251492
  };
250263
251493
  var ContactStatusSchema = zod.z.object({
250264
251494
  /** True when the entry is open; false when closed. */
@@ -250277,7 +251507,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250277
251507
  schema: ContactStatusSchema,
250278
251508
  kind: "push"
250279
251509
  },
250280
- runtimeState: ContactStatusSchema
251510
+ runtimeState: ContactStatusSchema,
251511
+ /**
251512
+ * Runtime-state durability: **restored** — a door left open across a restart must still read open.
251513
+ *
251514
+ * See `RuntimeStateDurability`. Enforced by
251515
+ * `scripts/check-runtime-state-durability.ts`.
251516
+ */
251517
+ durability: "restored",
251518
+ /** Clock fields: written, but excluded from the compare that decides
251519
+ * whether persisting is worth a SQLite commit. */
251520
+ volatileStateFields: ["lastChangedAt"]
250281
251521
  };
250282
251522
  var ControlKindSchema = zod.z.enum([
250283
251523
  "numeric",
@@ -250364,7 +251604,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250364
251604
  * dropdown / text field / date picker) read the slice's discriminant
250365
251605
  * and value directly without polling the provider.
250366
251606
  */
250367
- runtimeState: ControlStatusSchema
251607
+ runtimeState: ControlStatusSchema,
251608
+ /**
251609
+ * Runtime-state durability: **session** — a generic control mirrors an external entity that re-publishes on connect; the options array is re-derived with it.
251610
+ *
251611
+ * See `RuntimeStateDurability`. Enforced by
251612
+ * `scripts/check-runtime-state-durability.ts`.
251613
+ */
251614
+ durability: "session"
250368
251615
  };
250369
251616
  var CoverStateSchema = zod.z.enum([
250370
251617
  "open",
@@ -250426,7 +251673,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250426
251673
  * Runtime-state slice — mirrored by the kernel. UI controls watch
250427
251674
  * the slice for live position changes during a move.
250428
251675
  */
250429
- runtimeState: CoverStatusSchema
251676
+ runtimeState: CoverStatusSchema,
251677
+ /**
251678
+ * Runtime-state durability: **restored** — position survives a restart on the device; the mirror should agree at boot rather than read blank.
251679
+ *
251680
+ * See `RuntimeStateDurability`. Enforced by
251681
+ * `scripts/check-runtime-state-durability.ts`.
251682
+ */
251683
+ durability: "restored",
251684
+ /** Clock fields: written, but excluded from the compare that decides
251685
+ * whether persisting is worth a SQLite commit. */
251686
+ volatileStateFields: ["lastChangedAt"]
250430
251687
  };
250431
251688
  var DayNightModeSchema = zod.z.enum([
250432
251689
  "auto",
@@ -250487,7 +251744,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250487
251744
  schema: DayNightStatusSchema,
250488
251745
  kind: "poll"
250489
251746
  },
250490
- runtimeState: DayNightStatusSchema
251747
+ runtimeState: DayNightStatusSchema,
251748
+ /**
251749
+ * Runtime-state durability: **restored** — operator-set IR-cut behaviour; mutation-driven.
251750
+ *
251751
+ * See `RuntimeStateDurability`. Enforced by
251752
+ * `scripts/check-runtime-state-durability.ts`.
251753
+ */
251754
+ durability: "restored",
251755
+ /** Clock fields: written, but excluded from the compare that decides
251756
+ * whether persisting is worth a SQLite commit. */
251757
+ volatileStateFields: ["lastFetchedAt"]
250491
251758
  };
250492
251759
  var DeviceStatusSchema = zod.z.object({
250493
251760
  /**
@@ -250520,7 +251787,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250520
251787
  schema: DeviceStatusSchema,
250521
251788
  kind: "push"
250522
251789
  },
250523
- runtimeState: DeviceStatusSchema
251790
+ runtimeState: DeviceStatusSchema,
251791
+ /**
251792
+ * 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.
251793
+ *
251794
+ * See `RuntimeStateDurability`. Enforced by
251795
+ * `scripts/check-runtime-state-durability.ts`.
251796
+ */
251797
+ durability: "restored",
251798
+ /** Clock fields: written, but excluded from the compare that decides
251799
+ * whether persisting is worth a SQLite commit. */
251800
+ volatileStateFields: ["lastChangedAt"]
250524
251801
  };
250525
251802
  var DoorbellStatusSchema = zod.z.object({
250526
251803
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -250560,7 +251837,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250560
251837
  * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
250561
251838
  * without subscribing.
250562
251839
  */
250563
- runtimeState: DoorbellStatusSchema
251840
+ runtimeState: DoorbellStatusSchema,
251841
+ /**
251842
+ * 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.
251843
+ *
251844
+ * See `RuntimeStateDurability`. Enforced by
251845
+ * `scripts/check-runtime-state-durability.ts`.
251846
+ */
251847
+ durability: "restored"
250564
251848
  };
250565
251849
  var EnumSensorDateTimeFormatSchema = zod.z.enum([
250566
251850
  "date",
@@ -250590,7 +251874,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250590
251874
  schema: EnumSensorStatusSchema,
250591
251875
  kind: "push"
250592
251876
  },
250593
- runtimeState: EnumSensorStatusSchema
251877
+ runtimeState: EnumSensorStatusSchema,
251878
+ /**
251879
+ * Runtime-state durability: **restored** — as `numeric-sensor`; 80 devices.
251880
+ *
251881
+ * See `RuntimeStateDurability`. Enforced by
251882
+ * `scripts/check-runtime-state-durability.ts`.
251883
+ */
251884
+ durability: "restored",
251885
+ /** Clock fields: written, but excluded from the compare that decides
251886
+ * whether persisting is worth a SQLite commit. */
251887
+ volatileStateFields: ["lastFetchedAt"]
250594
251888
  };
250595
251889
  var EventFireSchema = zod.z.object({
250596
251890
  deviceId: zod.z.number(),
@@ -250616,7 +251910,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250616
251910
  schema: EventEmitterStatusSchema,
250617
251911
  kind: "push"
250618
251912
  },
250619
- runtimeState: EventEmitterStatusSchema
251913
+ runtimeState: EventEmitterStatusSchema,
251914
+ /**
251915
+ * Runtime-state durability: **session** — `eventCountSinceStart` names its own scope.
251916
+ *
251917
+ * See `RuntimeStateDurability`. Enforced by
251918
+ * `scripts/check-runtime-state-durability.ts`.
251919
+ */
251920
+ durability: "session"
250620
251921
  };
250621
251922
  var EventItemSchema = zod.z.object({
250622
251923
  id: zod.z.string(),
@@ -250915,7 +252216,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250915
252216
  * Runtime-state slice — mirrored by the kernel. UI fan speed
250916
252217
  * sliders read `percentage` for live updates.
250917
252218
  */
250918
- runtimeState: FanControlStatusSchema
252219
+ runtimeState: FanControlStatusSchema,
252220
+ /**
252221
+ * Runtime-state durability: **session** — as `brightness`.
252222
+ *
252223
+ * See `RuntimeStateDurability`. Enforced by
252224
+ * `scripts/check-runtime-state-durability.ts`.
252225
+ */
252226
+ durability: "session"
250919
252227
  };
250920
252228
  var FeatureProbeStatusSchema = zod.z.object({
250921
252229
  /**
@@ -250968,7 +252276,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250968
252276
  schema: FeatureProbeStatusSchema,
250969
252277
  kind: "push"
250970
252278
  },
250971
- runtimeState: FeatureProbeStatusSchema
252279
+ runtimeState: FeatureProbeStatusSchema,
252280
+ /**
252281
+ * 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.
252282
+ *
252283
+ * See `RuntimeStateDurability`. Enforced by
252284
+ * `scripts/check-runtime-state-durability.ts`.
252285
+ */
252286
+ durability: "session"
250972
252287
  };
250973
252288
  var FloodStatusSchema = zod.z.object({
250974
252289
  /** True when leak is currently detected. */
@@ -250987,7 +252302,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250987
252302
  schema: FloodStatusSchema,
250988
252303
  kind: "push"
250989
252304
  },
250990
- runtimeState: FloodStatusSchema
252305
+ runtimeState: FloodStatusSchema,
252306
+ /**
252307
+ * Runtime-state durability: **restored** — as `smoke`.
252308
+ *
252309
+ * See `RuntimeStateDurability`. Enforced by
252310
+ * `scripts/check-runtime-state-durability.ts`.
252311
+ */
252312
+ durability: "restored",
252313
+ /** Clock fields: written, but excluded from the compare that decides
252314
+ * whether persisting is worth a SQLite commit. */
252315
+ volatileStateFields: ["lastChangedAt"]
250991
252316
  };
250992
252317
  var GasStatusSchema = zod.z.object({
250993
252318
  detected: zod.z.boolean(),
@@ -251005,7 +252330,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251005
252330
  schema: GasStatusSchema,
251006
252331
  kind: "push"
251007
252332
  },
251008
- runtimeState: GasStatusSchema
252333
+ runtimeState: GasStatusSchema,
252334
+ /**
252335
+ * Runtime-state durability: **restored** — as `smoke`.
252336
+ *
252337
+ * See `RuntimeStateDurability`. Enforced by
252338
+ * `scripts/check-runtime-state-durability.ts`.
252339
+ */
252340
+ durability: "restored",
252341
+ /** Clock fields: written, but excluded from the compare that decides
252342
+ * whether persisting is worth a SQLite commit. */
252343
+ volatileStateFields: ["lastChangedAt"]
251009
252344
  };
251010
252345
  var HumidifierStatusSchema = zod.z.object({
251011
252346
  /** Whether the humidifier is currently on. */
@@ -251066,7 +252401,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251066
252401
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
251067
252402
  * slice for live humidity / mode changes.
251068
252403
  */
251069
- runtimeState: HumidifierStatusSchema
252404
+ runtimeState: HumidifierStatusSchema,
252405
+ /**
252406
+ * Runtime-state durability: **session** — as `climate-control`.
252407
+ *
252408
+ * See `RuntimeStateDurability`. Enforced by
252409
+ * `scripts/check-runtime-state-durability.ts`.
252410
+ */
252411
+ durability: "session"
251070
252412
  };
251071
252413
  var HumiditySensorStatusSchema = zod.z.object({
251072
252414
  /** Current relative humidity, 0..100. */
@@ -251094,7 +252436,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251094
252436
  schema: HumiditySensorStatusSchema,
251095
252437
  kind: "push"
251096
252438
  },
251097
- runtimeState: HumiditySensorStatusSchema
252439
+ runtimeState: HumiditySensorStatusSchema,
252440
+ /**
252441
+ * Runtime-state durability: **restored** — as `numeric-sensor` (67 of 75 writes were the clock alone).
252442
+ *
252443
+ * See `RuntimeStateDurability`. Enforced by
252444
+ * `scripts/check-runtime-state-durability.ts`.
252445
+ */
252446
+ durability: "restored",
252447
+ /** Clock fields: written, but excluded from the compare that decides
252448
+ * whether persisting is worth a SQLite commit. */
252449
+ volatileStateFields: ["lastFetchedAt"]
251098
252450
  };
251099
252451
  var ImageStatusSchema = zod.z.object({
251100
252452
  /** Absolute signed URL the browser loads directly. Null when the
@@ -251118,7 +252470,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251118
252470
  * Runtime-state slice — mirrored by the kernel. The UI reads `url`
251119
252471
  * directly and renders the still image.
251120
252472
  */
251121
- runtimeState: ImageStatusSchema
252473
+ runtimeState: ImageStatusSchema,
252474
+ /**
252475
+ * Runtime-state durability: **session** — a snapshot URL is a session-scoped handle; a restored one points at nothing.
252476
+ *
252477
+ * See `RuntimeStateDurability`. Enforced by
252478
+ * `scripts/check-runtime-state-durability.ts`.
252479
+ */
252480
+ durability: "session"
251122
252481
  };
251123
252482
  var ImageRotateSchema = zod.z.enum([
251124
252483
  "0",
@@ -251219,7 +252578,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251219
252578
  schema: ImageSettingsStatusSchema,
251220
252579
  kind: "poll"
251221
252580
  },
251222
- runtimeState: ImageSettingsStatusSchema
252581
+ runtimeState: ImageSettingsStatusSchema,
252582
+ /**
252583
+ * Runtime-state durability: **restored** — operator-set camera imaging; mutation-driven.
252584
+ *
252585
+ * See `RuntimeStateDurability`. Enforced by
252586
+ * `scripts/check-runtime-state-durability.ts`.
252587
+ */
252588
+ durability: "restored",
252589
+ /** Clock fields: written, but excluded from the compare that decides
252590
+ * whether persisting is worth a SQLite commit. */
252591
+ volatileStateFields: ["lastFetchedAt"]
251223
252592
  };
251224
252593
  var IntegrationWithStateSchema = zod.z.object({
251225
252594
  id: zod.z.string(),
@@ -251550,7 +252919,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251550
252919
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
251551
252920
  * slice for live activity + battery changes.
251552
252921
  */
251553
- runtimeState: LawnMowerControlStatusSchema
252922
+ runtimeState: LawnMowerControlStatusSchema,
252923
+ /**
252924
+ * Runtime-state durability: **session** — as `vacuum-control`.
252925
+ *
252926
+ * See `RuntimeStateDurability`. Enforced by
252927
+ * `scripts/check-runtime-state-durability.ts`.
252928
+ */
252929
+ durability: "session"
251554
252930
  };
251555
252931
  var InterfaceKindEnum = zod.z.enum([
251556
252932
  "lan",
@@ -251799,7 +253175,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251799
253175
  * read `state` and disable themselves during `locking`/`unlocking`
251800
253176
  * transitions.
251801
253177
  */
251802
- runtimeState: LockControlStatusSchema
253178
+ runtimeState: LockControlStatusSchema,
253179
+ /**
253180
+ * Runtime-state durability: **restored** — a lock left locked must still read locked.
253181
+ *
253182
+ * See `RuntimeStateDurability`. Enforced by
253183
+ * `scripts/check-runtime-state-durability.ts`.
253184
+ */
253185
+ durability: "restored",
253186
+ /** Clock fields: written, but excluded from the compare that decides
253187
+ * whether persisting is worth a SQLite commit. */
253188
+ volatileStateFields: ["lastChangedAt"]
251803
253189
  };
251804
253190
  var MediaPlayerStateSchema = zod.z.enum([
251805
253191
  "off",
@@ -251949,7 +253335,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251949
253335
  * full slice for live now-playing, volume, and progress updates
251950
253336
  * without polling.
251951
253337
  */
251952
- runtimeState: MediaPlayerStatusSchema
253338
+ runtimeState: MediaPlayerStatusSchema,
253339
+ /**
253340
+ * Runtime-state durability: **session** — a restored transport position describes a playback that stopped when the hub did.
253341
+ *
253342
+ * See `RuntimeStateDurability`. Enforced by
253343
+ * `scripts/check-runtime-state-durability.ts`.
253344
+ */
253345
+ durability: "session"
251953
253346
  };
251954
253347
  var MeshEndpointSchema = zod.z.object({
251955
253348
  /** Stable identifier within the provider (e.g. `mesh-ipv4`, `magicdns`, `funnel`). */
@@ -252233,7 +253626,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252233
253626
  * `device.state.motion.value`. Reads never invoke the provider, so
252234
253627
  * UIs and other addons can poll the cached state safely.
252235
253628
  */
252236
- runtimeState: MotionStatusSchema
253629
+ runtimeState: MotionStatusSchema,
253630
+ /**
253631
+ * 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.
253632
+ *
253633
+ * See `RuntimeStateDurability`. Enforced by
253634
+ * `scripts/check-runtime-state-durability.ts`.
253635
+ */
253636
+ durability: "session"
252237
253637
  };
252238
253638
  var MotionTriggerStatusSchema = zod.z.object({
252239
253639
  enabled: zod.z.boolean(),
@@ -252270,7 +253670,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252270
253670
  schema: MotionTriggerStatusSchema,
252271
253671
  kind: "command-driven"
252272
253672
  },
252273
- runtimeState: MotionTriggerRuntimeStateSchema
253673
+ runtimeState: MotionTriggerRuntimeStateSchema,
253674
+ /**
253675
+ * 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.
253676
+ *
253677
+ * See `RuntimeStateDurability`. Enforced by
253678
+ * `scripts/check-runtime-state-durability.ts`.
253679
+ */
253680
+ durability: "session"
252274
253681
  };
252275
253682
  var MotionZoneRegionSchema = zod.z.object({
252276
253683
  id: zod.z.number(),
@@ -252325,7 +253732,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252325
253732
  schema: MotionZoneStatusSchema,
252326
253733
  kind: "poll"
252327
253734
  },
252328
- runtimeState: MotionZoneStatusSchema
253735
+ runtimeState: MotionZoneStatusSchema,
253736
+ /**
253737
+ * 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.
253738
+ *
253739
+ * See `RuntimeStateDurability`. Enforced by
253740
+ * `scripts/check-runtime-state-durability.ts`.
253741
+ */
253742
+ durability: "restored",
253743
+ /** Clock fields: written, but excluded from the compare that decides
253744
+ * whether persisting is worth a SQLite commit. */
253745
+ volatileStateFields: ["lastFetchedAt"]
252329
253746
  };
252330
253747
  var NativeObjectClassEnum = zod.z.enum([
252331
253748
  "person",
@@ -252388,7 +253805,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252388
253805
  schema: NativeObjectDetectionStatusSchema,
252389
253806
  kind: "push"
252390
253807
  },
252391
- runtimeState: NativeObjectDetectionRuntimeStateSchema
253808
+ runtimeState: NativeObjectDetectionRuntimeStateSchema,
253809
+ /**
253810
+ * 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.
253811
+ *
253812
+ * See `RuntimeStateDurability`. Enforced by
253813
+ * `scripts/check-runtime-state-durability.ts`.
253814
+ */
253815
+ durability: "restored",
253816
+ /** Clock fields: written, but excluded from the compare that decides
253817
+ * whether persisting is worth a SQLite commit. */
253818
+ volatileStateFields: ["lastFetchedAt"]
252392
253819
  };
252393
253820
  var StreamNetworkStatsSchema = zod.z.object({
252394
253821
  nominalBitrateKbps: zod.z.number(),
@@ -252711,7 +254138,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252711
254138
  * form reads `supports` to gate optional fields; history pane reads
252712
254139
  * `lastSentAt` / `lastError` / `queueDepth`.
252713
254140
  */
252714
- runtimeState: NotifierStatusSchema
254141
+ runtimeState: NotifierStatusSchema,
254142
+ /**
254143
+ * Runtime-state durability: **session** — live queue depth and last-send state; a restored queue depth describes a queue that no longer exists.
254144
+ *
254145
+ * See `RuntimeStateDurability`. Enforced by
254146
+ * `scripts/check-runtime-state-durability.ts`.
254147
+ */
254148
+ durability: "session"
252715
254149
  };
252716
254150
  var NumericSensorStatusSchema = zod.z.object({
252717
254151
  value: zod.z.number(),
@@ -252737,7 +254171,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252737
254171
  schema: NumericSensorStatusSchema,
252738
254172
  kind: "push"
252739
254173
  },
252740
- runtimeState: NumericSensorStatusSchema
254174
+ runtimeState: NumericSensorStatusSchema,
254175
+ /**
254176
+ * 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.
254177
+ *
254178
+ * See `RuntimeStateDurability`. Enforced by
254179
+ * `scripts/check-runtime-state-durability.ts`.
254180
+ */
254181
+ durability: "restored",
254182
+ /** Clock fields: written, but excluded from the compare that decides
254183
+ * whether persisting is worth a SQLite commit. */
254184
+ volatileStateFields: ["lastFetchedAt"]
252741
254185
  };
252742
254186
  var OsdOverlayKindEnum = zod.z.enum([
252743
254187
  "text",
@@ -253162,7 +254606,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253162
254606
  * the full slice via `device.state.petFeeder.value` and refresh on
253163
254607
  * every poll without re-querying the provider.
253164
254608
  */
253165
- runtimeState: PetFeederStatusSchema
254609
+ runtimeState: PetFeederStatusSchema,
254610
+ /**
254611
+ * Runtime-state durability: **session** — live appliance state re-published on connect.
254612
+ *
254613
+ * See `RuntimeStateDurability`. Enforced by
254614
+ * `scripts/check-runtime-state-durability.ts`.
254615
+ */
254616
+ durability: "session"
253166
254617
  };
253167
254618
  var VehicleSchema = zod.z.object({
253168
254619
  id: zod.z.string(),
@@ -253496,7 +254947,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253496
254947
  schema: PowerMeterStatusSchema,
253497
254948
  kind: "push"
253498
254949
  },
253499
- runtimeState: PowerMeterStatusSchema
254950
+ runtimeState: PowerMeterStatusSchema,
254951
+ /**
254952
+ * Runtime-state durability: **restored** — as `numeric-sensor`; `kwhTotal` is an accumulator whose restored value is the baseline.
254953
+ *
254954
+ * See `RuntimeStateDurability`. Enforced by
254955
+ * `scripts/check-runtime-state-durability.ts`.
254956
+ */
254957
+ durability: "restored",
254958
+ /** Clock fields: written, but excluded from the compare that decides
254959
+ * whether persisting is worth a SQLite commit. */
254960
+ volatileStateFields: ["lastFetchedAt"]
253500
254961
  };
253501
254962
  var GpsLocationSchema = zod.z.object({
253502
254963
  /** Latitude in decimal degrees, -90..90. */
@@ -253537,7 +254998,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253537
254998
  * the map pin is rendered (use `DeviceFeature.PresenceGps` for the
253538
254999
  * pre-fetch fast-path check).
253539
255000
  */
253540
- runtimeState: PresenceStatusSchema
255001
+ runtimeState: PresenceStatusSchema,
255002
+ /**
255003
+ * Runtime-state durability: **restored** — occupancy-relevant: the restored state is what an occupancy rule compares the first post-restart observation against.
255004
+ *
255005
+ * See `RuntimeStateDurability`. Enforced by
255006
+ * `scripts/check-runtime-state-durability.ts`.
255007
+ */
255008
+ durability: "restored",
255009
+ /** Clock fields: written, but excluded from the compare that decides
255010
+ * whether persisting is worth a SQLite commit. */
255011
+ volatileStateFields: ["lastChangedAt"]
253541
255012
  };
253542
255013
  var PressureSensorStatusSchema = zod.z.object({
253543
255014
  /** Current pressure in hPa. */
@@ -253565,7 +255036,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253565
255036
  schema: PressureSensorStatusSchema,
253566
255037
  kind: "push"
253567
255038
  },
253568
- runtimeState: PressureSensorStatusSchema
255039
+ runtimeState: PressureSensorStatusSchema,
255040
+ /**
255041
+ * Runtime-state durability: **restored** — as `numeric-sensor`.
255042
+ *
255043
+ * See `RuntimeStateDurability`. Enforced by
255044
+ * `scripts/check-runtime-state-durability.ts`.
255045
+ */
255046
+ durability: "restored",
255047
+ /** Clock fields: written, but excluded from the compare that decides
255048
+ * whether persisting is worth a SQLite commit. */
255049
+ volatileStateFields: ["lastFetchedAt"]
253569
255050
  };
253570
255051
  var PrivacyMaskShapeSchema = zod.z.discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
253571
255052
  var PrivacyMaskRegionSchema = zod.z.object({
@@ -253664,7 +255145,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253664
255145
  schema: PrivacyMaskStatusSchema,
253665
255146
  kind: "poll"
253666
255147
  },
253667
- runtimeState: PrivacyMaskStatusSchema
255148
+ runtimeState: PrivacyMaskStatusSchema,
255149
+ /**
255150
+ * Runtime-state durability: **restored** — operator-drawn regions, zero real churn — 22 writes in 25 minutes, every one of them the clock.
255151
+ *
255152
+ * See `RuntimeStateDurability`. Enforced by
255153
+ * `scripts/check-runtime-state-durability.ts`.
255154
+ */
255155
+ durability: "restored",
255156
+ /** Clock fields: written, but excluded from the compare that decides
255157
+ * whether persisting is worth a SQLite commit. */
255158
+ volatileStateFields: ["lastFetchedAt"]
253668
255159
  };
253669
255160
  function summarisePrivacyAudio(profiles) {
253670
255161
  if (profiles.length === 0) return null;
@@ -253854,7 +255345,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253854
255345
  * fetch / cache / fallback logic out of the four cap methods —
253855
255346
  * they become trampolines over `runtimeState`.
253856
255347
  */
253857
- runtimeState: PtzAutotrackRuntimeStateSchema
255348
+ runtimeState: PtzAutotrackRuntimeStateSchema,
255349
+ /**
255350
+ * Runtime-state durability: **session** — mirrors the camera's own autotrack config, re-read on connect.
255351
+ *
255352
+ * See `RuntimeStateDurability`. Enforced by
255353
+ * `scripts/check-runtime-state-durability.ts`.
255354
+ */
255355
+ durability: "session"
253858
255356
  };
253859
255357
  var rebootCapability = {
253860
255358
  name: "reboot",
@@ -254524,7 +256022,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254524
256022
  schema: SceneMonitorStatusSchema,
254525
256023
  kind: "push"
254526
256024
  },
254527
- runtimeState: SceneMonitorStatusSchema
256025
+ runtimeState: SceneMonitorStatusSchema,
256026
+ /**
256027
+ * Runtime-state durability: **session** — re-derived from the current scene on the next evaluation.
256028
+ *
256029
+ * See `RuntimeStateDurability`. Enforced by
256030
+ * `scripts/check-runtime-state-durability.ts`.
256031
+ */
256032
+ durability: "session"
254528
256033
  };
254529
256034
  var ZoneRuleModeEnum = zod.z.enum(["include", "exclude"]);
254530
256035
  var ZoneRuleSchema = zod.z.object({
@@ -254626,7 +256131,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254626
256131
  * `isRunning` to render a spinner during execution and surfaces
254627
256132
  * `lastError` / `lastRunSuccess` in the recent-runs panel.
254628
256133
  */
254629
- runtimeState: ScriptRunnerStatusSchema
256134
+ runtimeState: ScriptRunnerStatusSchema,
256135
+ /**
256136
+ * Runtime-state durability: **session** — a restored `isRunning: true` describes a process that died with the previous hub.
256137
+ *
256138
+ * See `RuntimeStateDurability`. Enforced by
256139
+ * `scripts/check-runtime-state-durability.ts`.
256140
+ */
256141
+ durability: "session"
254630
256142
  };
254631
256143
  var SmokeStatusSchema = zod.z.object({
254632
256144
  detected: zod.z.boolean(),
@@ -254644,7 +256156,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254644
256156
  schema: SmokeStatusSchema,
254645
256157
  kind: "push"
254646
256158
  },
254647
- runtimeState: SmokeStatusSchema
256159
+ runtimeState: SmokeStatusSchema,
256160
+ /**
256161
+ * Runtime-state durability: **restored** — a safety sensor must not read "clear" merely because the hub restarted.
256162
+ *
256163
+ * See `RuntimeStateDurability`. Enforced by
256164
+ * `scripts/check-runtime-state-durability.ts`.
256165
+ */
256166
+ durability: "restored",
256167
+ /** Clock fields: written, but excluded from the compare that decides
256168
+ * whether persisting is worth a SQLite commit. */
256169
+ volatileStateFields: ["lastChangedAt"]
254648
256170
  };
254649
256171
  var CamStreamDescriptorSchema = zod.z.object({
254650
256172
  camStreamId: zod.z.string().min(1),
@@ -254795,7 +256317,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254795
256317
  schema: StreamParamsStatusSchema,
254796
256318
  kind: "poll"
254797
256319
  },
254798
- runtimeState: StreamParamsStatusSchema
256320
+ runtimeState: StreamParamsStatusSchema,
256321
+ /**
256322
+ * Runtime-state durability: **restored** — operator-set encoder profile; mutation-driven.
256323
+ *
256324
+ * See `RuntimeStateDurability`. Enforced by
256325
+ * `scripts/check-runtime-state-durability.ts`.
256326
+ */
256327
+ durability: "restored",
256328
+ /** Clock fields: written, but excluded from the compare that decides
256329
+ * whether persisting is worth a SQLite commit. */
256330
+ volatileStateFields: ["lastFetchedAt"]
254799
256331
  };
254800
256332
  var STREAM_PROFILE_META = [
254801
256333
  {
@@ -255066,6 +256598,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255066
256598
  * not need to re-query the provider after a setState mutation.
255067
256599
  */
255068
256600
  runtimeState: SwitchStatusSchema,
256601
+ /**
256602
+ * Runtime-state durability: **restored** — device state an operator reads as authoritative; 55 devices, transition-driven.
256603
+ *
256604
+ * See `RuntimeStateDurability`. Enforced by
256605
+ * `scripts/check-runtime-state-durability.ts`.
256606
+ */
256607
+ durability: "restored",
256608
+ /** Clock fields: written, but excluded from the compare that decides
256609
+ * whether persisting is worth a SQLite commit. */
256610
+ volatileStateFields: ["lastChangedAt"],
255069
256611
  settings: { bindings: [{
255070
256612
  kind: "scalar",
255071
256613
  statusPath: "on",
@@ -255138,7 +256680,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255138
256680
  schema: TamperStatusSchema,
255139
256681
  kind: "push"
255140
256682
  },
255141
- runtimeState: TamperStatusSchema
256683
+ runtimeState: TamperStatusSchema,
256684
+ /**
256685
+ * Runtime-state durability: **restored** — as `smoke`.
256686
+ *
256687
+ * See `RuntimeStateDurability`. Enforced by
256688
+ * `scripts/check-runtime-state-durability.ts`.
256689
+ */
256690
+ durability: "restored",
256691
+ /** Clock fields: written, but excluded from the compare that decides
256692
+ * whether persisting is worth a SQLite commit. */
256693
+ volatileStateFields: ["lastChangedAt"]
255142
256694
  };
255143
256695
  var TemperatureSensorStatusSchema = zod.z.object({
255144
256696
  /** Current temperature in Celsius. */
@@ -255167,7 +256719,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255167
256719
  schema: TemperatureSensorStatusSchema,
255168
256720
  kind: "push"
255169
256721
  },
255170
- runtimeState: TemperatureSensorStatusSchema
256722
+ runtimeState: TemperatureSensorStatusSchema,
256723
+ /**
256724
+ * Runtime-state durability: **restored** — as `numeric-sensor` (69 of 125 writes were the clock alone).
256725
+ *
256726
+ * See `RuntimeStateDurability`. Enforced by
256727
+ * `scripts/check-runtime-state-durability.ts`.
256728
+ */
256729
+ durability: "restored",
256730
+ /** Clock fields: written, but excluded from the compare that decides
256731
+ * whether persisting is worth a SQLite commit. */
256732
+ volatileStateFields: ["lastFetchedAt"]
255171
256733
  };
255172
256734
  var ToastSchema = zod.z.object({
255173
256735
  title: zod.z.string(),
@@ -255218,7 +256780,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255218
256780
  schema: UpdateStatusSchema,
255219
256781
  kind: "poll"
255220
256782
  },
255221
- runtimeState: UpdateStatusSchema
256783
+ runtimeState: UpdateStatusSchema,
256784
+ /**
256785
+ * Runtime-state durability: **session** — a restored `inProgress: true` describes an update that is no longer running; versions are re-probed at boot.
256786
+ *
256787
+ * See `RuntimeStateDurability`. Enforced by
256788
+ * `scripts/check-runtime-state-durability.ts`.
256789
+ */
256790
+ durability: "session"
255222
256791
  };
255223
256792
  var UserSummarySchema = zod.z.object({
255224
256793
  id: zod.z.string(),
@@ -255552,7 +257121,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255552
257121
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
255553
257122
  * slice for live state + battery + fan-speed changes.
255554
257123
  */
255555
- runtimeState: VacuumControlStatusSchema
257124
+ runtimeState: VacuumControlStatusSchema,
257125
+ /**
257126
+ * Runtime-state durability: **session** — as `media-player` — a restored `state: cleaning` is a robot that is not cleaning.
257127
+ *
257128
+ * See `RuntimeStateDurability`. Enforced by
257129
+ * `scripts/check-runtime-state-durability.ts`.
257130
+ */
257131
+ durability: "session"
255556
257132
  };
255557
257133
  var ValveStateSchema = zod.z.enum([
255558
257134
  "open",
@@ -255605,7 +257181,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255605
257181
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
255606
257182
  * slice for live position changes during a move.
255607
257183
  */
255608
- runtimeState: ValveStatusSchema
257184
+ runtimeState: ValveStatusSchema,
257185
+ /**
257186
+ * Runtime-state durability: **session** — as `brightness`.
257187
+ *
257188
+ * See `RuntimeStateDurability`. Enforced by
257189
+ * `scripts/check-runtime-state-durability.ts`.
257190
+ */
257191
+ durability: "session"
255609
257192
  };
255610
257193
  var VibrationStatusSchema = zod.z.object({
255611
257194
  detected: zod.z.boolean(),
@@ -255623,7 +257206,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255623
257206
  schema: VibrationStatusSchema,
255624
257207
  kind: "push"
255625
257208
  },
255626
- runtimeState: VibrationStatusSchema
257209
+ runtimeState: VibrationStatusSchema,
257210
+ /**
257211
+ * Runtime-state durability: **restored** — as `smoke`.
257212
+ *
257213
+ * See `RuntimeStateDurability`. Enforced by
257214
+ * `scripts/check-runtime-state-durability.ts`.
257215
+ */
257216
+ durability: "restored",
257217
+ /** Clock fields: written, but excluded from the compare that decides
257218
+ * whether persisting is worth a SQLite commit. */
257219
+ volatileStateFields: ["lastChangedAt"]
255627
257220
  };
255628
257221
  var WaterHeaterStatusSchema = zod.z.object({
255629
257222
  /** Current measured temperature. Null when not reported. */
@@ -255683,7 +257276,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255683
257276
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
255684
257277
  * slice for live temperature / mode / away changes.
255685
257278
  */
255686
- runtimeState: WaterHeaterStatusSchema
257279
+ runtimeState: WaterHeaterStatusSchema,
257280
+ /**
257281
+ * Runtime-state durability: **session** — as `climate-control`.
257282
+ *
257283
+ * See `RuntimeStateDurability`. Enforced by
257284
+ * `scripts/check-runtime-state-durability.ts`.
257285
+ */
257286
+ durability: "session"
255687
257287
  };
255688
257288
  var WeatherStatusSchema = zod.z.object({
255689
257289
  /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
@@ -255723,7 +257323,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255723
257323
  * Runtime-state slice — mirrored by the kernel. The UI reads the
255724
257324
  * current conditions directly from the slice on each weather push.
255725
257325
  */
255726
- runtimeState: WeatherStatusSchema
257326
+ runtimeState: WeatherStatusSchema,
257327
+ /**
257328
+ * Runtime-state durability: **session** — a forecast is stale the moment the hub is down; the provider re-fetches on connect.
257329
+ *
257330
+ * See `RuntimeStateDurability`. Enforced by
257331
+ * `scripts/check-runtime-state-durability.ts`.
257332
+ */
257333
+ durability: "session"
255727
257334
  };
255728
257335
  var PerScopeBreakdownSchema = zod.z.object({
255729
257336
  /** Total tracked objects in this scope (frame / zone / unzoned). */
@@ -255832,7 +257439,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255832
257439
  * automatically; the explicit `getCurrentSnapshot` cap method is
255833
257440
  * still useful for one-off polls without a subscription.
255834
257441
  */
255835
- runtimeState: CameraOccupancySnapshotSchema
257442
+ runtimeState: CameraOccupancySnapshotSchema,
257443
+ /**
257444
+ * Runtime-state durability: **session** — per-frame analytics; with `audio-metrics` it is ~90 % of the offered write rate. Re-derived on the next frame.
257445
+ *
257446
+ * See `RuntimeStateDurability`. Enforced by
257447
+ * `scripts/check-runtime-state-durability.ts`.
257448
+ */
257449
+ durability: "session"
255836
257450
  };
255837
257451
  var ZoneRuleStageEnum = zod.z.enum([
255838
257452
  "motion",
@@ -255884,7 +257498,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255884
257498
  motion: zod.z.array(ZoneRuleSchema).readonly(),
255885
257499
  detection: zod.z.array(ZoneRuleSchema).readonly(),
255886
257500
  package: zod.z.array(ZoneRuleSchema).readonly()
255887
- })
257501
+ }),
257502
+ /**
257503
+ * Runtime-state durability: **restored** — operator intent, mutation-only, same argument as `zones`.
257504
+ *
257505
+ * See `RuntimeStateDurability`. Enforced by
257506
+ * `scripts/check-runtime-state-durability.ts`.
257507
+ */
257508
+ durability: "restored"
255888
257509
  };
255889
257510
  var PIPELINE_FLOW_CAPABILITY_NAMES = [
255890
257511
  "decoder",
@@ -265134,6 +266755,301 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
265134
266755
  for (const [capName, capKind] of Object.entries(CAP_PROVIDER_KIND_MAP)) if (capKind === kind) out.push(capName);
265135
266756
  return out;
265136
266757
  }
266758
+ var RUNTIME_STATE_POLICY = {
266759
+ "air-quality-sensor": {
266760
+ durability: "restored",
266761
+ volatileFields: ["lastFetchedAt"]
266762
+ },
266763
+ "alarm-panel": {
266764
+ durability: "restored",
266765
+ volatileFields: ["lastChangedAt"]
266766
+ },
266767
+ "ambient-light-sensor": {
266768
+ durability: "restored",
266769
+ volatileFields: ["lastFetchedAt"]
266770
+ },
266771
+ "audio-metrics": {
266772
+ durability: "session",
266773
+ volatileFields: []
266774
+ },
266775
+ "automation-control": {
266776
+ durability: "session",
266777
+ volatileFields: []
266778
+ },
266779
+ "battery": {
266780
+ durability: "restored",
266781
+ volatileFields: ["lastUpdated"]
266782
+ },
266783
+ "binary": {
266784
+ durability: "restored",
266785
+ volatileFields: ["lastChangedAt"]
266786
+ },
266787
+ "brightness": {
266788
+ durability: "session",
266789
+ volatileFields: []
266790
+ },
266791
+ "camera-streams": {
266792
+ durability: "session",
266793
+ volatileFields: []
266794
+ },
266795
+ "carbon-monoxide": {
266796
+ durability: "restored",
266797
+ volatileFields: ["lastChangedAt"]
266798
+ },
266799
+ "climate-control": {
266800
+ durability: "session",
266801
+ volatileFields: []
266802
+ },
266803
+ "color": {
266804
+ durability: "session",
266805
+ volatileFields: []
266806
+ },
266807
+ "connectivity": {
266808
+ durability: "restored",
266809
+ volatileFields: ["lastChangedAt"]
266810
+ },
266811
+ "consumables": {
266812
+ durability: "session",
266813
+ volatileFields: []
266814
+ },
266815
+ "contact": {
266816
+ durability: "restored",
266817
+ volatileFields: ["lastChangedAt"]
266818
+ },
266819
+ "control": {
266820
+ durability: "session",
266821
+ volatileFields: []
266822
+ },
266823
+ "cover": {
266824
+ durability: "restored",
266825
+ volatileFields: ["lastChangedAt"]
266826
+ },
266827
+ "day-night": {
266828
+ durability: "restored",
266829
+ volatileFields: ["lastFetchedAt"]
266830
+ },
266831
+ "device-discovery": {
266832
+ durability: "session",
266833
+ volatileFields: []
266834
+ },
266835
+ "device-status": {
266836
+ durability: "restored",
266837
+ volatileFields: ["lastChangedAt"]
266838
+ },
266839
+ "doorbell": {
266840
+ durability: "restored",
266841
+ volatileFields: []
266842
+ },
266843
+ "enum-sensor": {
266844
+ durability: "restored",
266845
+ volatileFields: ["lastFetchedAt"]
266846
+ },
266847
+ "event-emitter": {
266848
+ durability: "session",
266849
+ volatileFields: []
266850
+ },
266851
+ "fan-control": {
266852
+ durability: "session",
266853
+ volatileFields: []
266854
+ },
266855
+ "feature-probe": {
266856
+ durability: "session",
266857
+ volatileFields: []
266858
+ },
266859
+ "flood": {
266860
+ durability: "restored",
266861
+ volatileFields: ["lastChangedAt"]
266862
+ },
266863
+ "gas": {
266864
+ durability: "restored",
266865
+ volatileFields: ["lastChangedAt"]
266866
+ },
266867
+ "humidifier": {
266868
+ durability: "session",
266869
+ volatileFields: []
266870
+ },
266871
+ "humidity-sensor": {
266872
+ durability: "restored",
266873
+ volatileFields: ["lastFetchedAt"]
266874
+ },
266875
+ "image": {
266876
+ durability: "session",
266877
+ volatileFields: []
266878
+ },
266879
+ "image-settings": {
266880
+ durability: "restored",
266881
+ volatileFields: ["lastFetchedAt"]
266882
+ },
266883
+ "lawn-mower-control": {
266884
+ durability: "session",
266885
+ volatileFields: []
266886
+ },
266887
+ "lock-control": {
266888
+ durability: "restored",
266889
+ volatileFields: ["lastChangedAt"]
266890
+ },
266891
+ "media-player": {
266892
+ durability: "session",
266893
+ volatileFields: []
266894
+ },
266895
+ "motion": {
266896
+ durability: "session",
266897
+ volatileFields: []
266898
+ },
266899
+ "motion-trigger": {
266900
+ durability: "session",
266901
+ volatileFields: []
266902
+ },
266903
+ "motion-zones": {
266904
+ durability: "restored",
266905
+ volatileFields: ["lastFetchedAt"]
266906
+ },
266907
+ "native-object-detection": {
266908
+ durability: "restored",
266909
+ volatileFields: ["lastFetchedAt"]
266910
+ },
266911
+ "notifier": {
266912
+ durability: "session",
266913
+ volatileFields: []
266914
+ },
266915
+ "numeric-sensor": {
266916
+ durability: "restored",
266917
+ volatileFields: ["lastFetchedAt"]
266918
+ },
266919
+ "pet-feeder": {
266920
+ durability: "session",
266921
+ volatileFields: []
266922
+ },
266923
+ "power-meter": {
266924
+ durability: "restored",
266925
+ volatileFields: ["lastFetchedAt"]
266926
+ },
266927
+ "presence": {
266928
+ durability: "restored",
266929
+ volatileFields: ["lastChangedAt"]
266930
+ },
266931
+ "pressure-sensor": {
266932
+ durability: "restored",
266933
+ volatileFields: ["lastFetchedAt"]
266934
+ },
266935
+ "privacy-mask": {
266936
+ durability: "restored",
266937
+ volatileFields: ["lastFetchedAt"]
266938
+ },
266939
+ "ptz-autotrack": {
266940
+ durability: "session",
266941
+ volatileFields: []
266942
+ },
266943
+ "scene-monitor": {
266944
+ durability: "session",
266945
+ volatileFields: []
266946
+ },
266947
+ "script-runner": {
266948
+ durability: "session",
266949
+ volatileFields: []
266950
+ },
266951
+ "smoke": {
266952
+ durability: "restored",
266953
+ volatileFields: ["lastChangedAt"]
266954
+ },
266955
+ "stream-params": {
266956
+ durability: "restored",
266957
+ volatileFields: ["lastFetchedAt"]
266958
+ },
266959
+ "switch": {
266960
+ durability: "restored",
266961
+ volatileFields: ["lastChangedAt"]
266962
+ },
266963
+ "tamper": {
266964
+ durability: "restored",
266965
+ volatileFields: ["lastChangedAt"]
266966
+ },
266967
+ "temperature-sensor": {
266968
+ durability: "restored",
266969
+ volatileFields: ["lastFetchedAt"]
266970
+ },
266971
+ "update": {
266972
+ durability: "session",
266973
+ volatileFields: []
266974
+ },
266975
+ "vacuum-control": {
266976
+ durability: "session",
266977
+ volatileFields: []
266978
+ },
266979
+ "valve": {
266980
+ durability: "session",
266981
+ volatileFields: []
266982
+ },
266983
+ "vibration": {
266984
+ durability: "restored",
266985
+ volatileFields: ["lastChangedAt"]
266986
+ },
266987
+ "water-heater": {
266988
+ durability: "session",
266989
+ volatileFields: []
266990
+ },
266991
+ "weather": {
266992
+ durability: "session",
266993
+ volatileFields: []
266994
+ },
266995
+ "zone-analytics": {
266996
+ durability: "session",
266997
+ volatileFields: []
266998
+ },
266999
+ "zone-rules": {
267000
+ durability: "restored",
267001
+ volatileFields: []
267002
+ },
267003
+ "zones": {
267004
+ durability: "restored",
267005
+ volatileFields: []
267006
+ }
267007
+ };
267008
+ function runtimeStatePolicyFor(capName) {
267009
+ return RUNTIME_STATE_POLICY[capName] ?? SESSION_ONLY_POLICY;
267010
+ }
267011
+ var SESSION_ONLY_POLICY = {
267012
+ durability: "session",
267013
+ volatileFields: []
267014
+ };
267015
+ function isRestoredCap(capName) {
267016
+ return runtimeStatePolicyFor(capName).durability === "restored";
267017
+ }
267018
+ var RESTORED_CAP_NAMES = [
267019
+ "air-quality-sensor",
267020
+ "alarm-panel",
267021
+ "ambient-light-sensor",
267022
+ "battery",
267023
+ "binary",
267024
+ "carbon-monoxide",
267025
+ "connectivity",
267026
+ "contact",
267027
+ "cover",
267028
+ "day-night",
267029
+ "device-status",
267030
+ "doorbell",
267031
+ "enum-sensor",
267032
+ "flood",
267033
+ "gas",
267034
+ "humidity-sensor",
267035
+ "image-settings",
267036
+ "lock-control",
267037
+ "motion-zones",
267038
+ "native-object-detection",
267039
+ "numeric-sensor",
267040
+ "power-meter",
267041
+ "presence",
267042
+ "pressure-sensor",
267043
+ "privacy-mask",
267044
+ "smoke",
267045
+ "stream-params",
267046
+ "switch",
267047
+ "tamper",
267048
+ "temperature-sensor",
267049
+ "vibration",
267050
+ "zone-rules",
267051
+ "zones"
267052
+ ];
265137
267053
  var SCOPE_PRESETS = [
265138
267054
  {
265139
267055
  id: "camera-viewer",
@@ -265777,6 +267693,96 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
265777
267693
  }
265778
267694
  };
265779
267695
  }
267696
+ var AUDIO_KIND_PREFIX = "audio-";
267697
+ function normalizeAudioLabel(value) {
267698
+ return value.startsWith(AUDIO_KIND_PREFIX) ? value.slice(6) : value;
267699
+ }
267700
+ var NC_AUDIO_DB_MIN = -96;
267701
+ var NC_AUDIO_DB_MAX = 0;
267702
+ var NC_AUDIO_DB_STEP = 3;
267703
+ var NC_AUDIO_DB_OFFERED = -30;
267704
+ var NC_AUDIO_HIT_PERCENT_MIN = 1;
267705
+ var NC_AUDIO_HIT_PERCENT_MAX = 100;
267706
+ var NC_AUDIO_SAMPLING_MIN_SEC = 1;
267707
+ var NC_AUDIO_SAMPLING_MAX_SEC = 300;
267708
+ var NC_AUDIO_DEFAULTS = {
267709
+ hitPercent: 60,
267710
+ samplingSeconds: 10
267711
+ };
267712
+ function audioOrDefaults(value) {
267713
+ return value ?? NC_AUDIO_DEFAULTS;
267714
+ }
267715
+ function audioIsFailClosed(value) {
267716
+ if (value === void 0) return false;
267717
+ return value.dbThreshold === void 0 && (value.labels === void 0 || value.labels.length === 0);
267718
+ }
267719
+ function clampInt(value, min, max) {
267720
+ if (!Number.isFinite(value)) return min;
267721
+ return Math.min(max, Math.max(min, Math.round(value)));
267722
+ }
267723
+ function has(patch, key) {
267724
+ return Object.prototype.hasOwnProperty.call(patch, key);
267725
+ }
267726
+ function patchAudio(current, patch) {
267727
+ const base = audioOrDefaults(current);
267728
+ const labels = has(patch, "labels") ? patch.labels : base.labels;
267729
+ const dbThreshold = has(patch, "dbThreshold") ? patch.dbThreshold : base.dbThreshold;
267730
+ return {
267731
+ ...labels !== void 0 && labels.length > 0 ? { labels: [...labels] } : {},
267732
+ ...dbThreshold !== void 0 ? { dbThreshold: clampInt(dbThreshold, NC_AUDIO_DB_MIN, 0) } : {},
267733
+ hitPercent: clampInt(patch.hitPercent ?? base.hitPercent, 1, 100),
267734
+ samplingSeconds: clampInt(patch.samplingSeconds ?? base.samplingSeconds, 1, 300)
267735
+ };
267736
+ }
267737
+ var UNKNOWN_LABEL_ICON = "\u{1F508}";
267738
+ var ICON_BY_ID = new Map(AUDIO_MACRO_LABELS.flatMap((macro2) => macro2.icon === void 0 ? [] : [[macro2.id, macro2.icon]]));
267739
+ function audioLabelChoices(taxonomy, selected) {
267740
+ const fromHub = taxonomy?.audioKinds ?? [];
267741
+ const choices = fromHub.length > 0 ? fromHub.map((entry) => {
267742
+ const id = normalizeAudioLabel(entry.kind);
267743
+ return {
267744
+ value: id,
267745
+ label: entry.label,
267746
+ icon: ICON_BY_ID.get(id) ?? UNKNOWN_LABEL_ICON,
267747
+ unknown: false
267748
+ };
267749
+ }) : AUDIO_MACRO_LABELS.map((macro2) => ({
267750
+ value: macro2.id,
267751
+ label: macro2.name,
267752
+ icon: macro2.icon ?? UNKNOWN_LABEL_ICON,
267753
+ unknown: false
267754
+ }));
267755
+ const known = new Set(choices.map((choice) => choice.value));
267756
+ const unknownLabels = [];
267757
+ for (const stored of selected ?? []) {
267758
+ const id = normalizeAudioLabel(stored);
267759
+ if (known.has(id) || unknownLabels.includes(id)) continue;
267760
+ unknownLabels.push(id);
267761
+ choices.push({
267762
+ value: id,
267763
+ label: id,
267764
+ icon: UNKNOWN_LABEL_ICON,
267765
+ unknown: true
267766
+ });
267767
+ }
267768
+ return {
267769
+ choices,
267770
+ unknownLabels
267771
+ };
267772
+ }
267773
+ function isAudioLabelSelected(labels, id) {
267774
+ const wanted = normalizeAudioLabel(id);
267775
+ return (labels ?? []).some((stored) => normalizeAudioLabel(stored) === wanted);
267776
+ }
267777
+ function toggleAudioLabel(labels, id) {
267778
+ const wanted = normalizeAudioLabel(id);
267779
+ const current = labels ?? [];
267780
+ if (isAudioLabelSelected(current, wanted)) {
267781
+ const next = current.filter((stored) => normalizeAudioLabel(stored) !== wanted);
267782
+ return next.length > 0 ? next : void 0;
267783
+ }
267784
+ return [...current, wanted];
267785
+ }
265780
267786
  var CLASS_CONDITION_IDS = /* @__PURE__ */ new Set(["classes", "classesExclude"]);
265781
267787
  var LABEL_CONDITION_IDS = /* @__PURE__ */ new Set(["sensorKinds"]);
265782
267788
  function toOption(entry) {
@@ -267629,6 +269635,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267629
269635
  exports.DEFAULT_FEATURES = DEFAULT_FEATURES;
267630
269636
  exports.DEFAULT_NATIVE_LEASE_SETTINGS = DEFAULT_NATIVE_LEASE_SETTINGS;
267631
269637
  exports.DEFAULT_RETENTION = DEFAULT_RETENTION;
269638
+ exports.DEFAULT_RUNTIME_STATE_DURABILITY = require_sleep.DEFAULT_RUNTIME_STATE_DURABILITY;
267632
269639
  exports.DEFAULT_SCRUB_THUMBNAIL_PRESET = DEFAULT_SCRUB_THUMBNAIL_PRESET;
267633
269640
  exports.DEFAULT_TIMELAPSE_PREVIEW_TEXT = DEFAULT_TIMELAPSE_PREVIEW_TEXT;
267634
269641
  exports.DETAIL_CROP_PADDING_FIELD = DETAIL_CROP_PADDING_FIELD;
@@ -267878,6 +269885,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267878
269885
  exports.NATIVE_LEASE_TILE_BUDGET_KEY = NATIVE_LEASE_TILE_BUDGET_KEY;
267879
269886
  exports.NC_ALARM_SYSTEM_EVENT_KINDS = NC_ALARM_SYSTEM_EVENT_KINDS;
267880
269887
  exports.NC_AUDIO_DBFS_FLOOR = NC_AUDIO_DBFS_FLOOR;
269888
+ exports.NC_AUDIO_DB_MAX = NC_AUDIO_DB_MAX;
269889
+ exports.NC_AUDIO_DB_MIN = NC_AUDIO_DB_MIN;
269890
+ exports.NC_AUDIO_DB_OFFERED = NC_AUDIO_DB_OFFERED;
269891
+ exports.NC_AUDIO_DB_STEP = NC_AUDIO_DB_STEP;
269892
+ exports.NC_AUDIO_DEFAULTS = NC_AUDIO_DEFAULTS;
269893
+ exports.NC_AUDIO_HIT_PERCENT_MAX = NC_AUDIO_HIT_PERCENT_MAX;
269894
+ exports.NC_AUDIO_HIT_PERCENT_MIN = NC_AUDIO_HIT_PERCENT_MIN;
269895
+ exports.NC_AUDIO_SAMPLING_MAX_SEC = NC_AUDIO_SAMPLING_MAX_SEC;
269896
+ exports.NC_AUDIO_SAMPLING_MIN_SEC = NC_AUDIO_SAMPLING_MIN_SEC;
267881
269897
  exports.NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NC_AUTHORABLE_SYSTEM_EVENT_KINDS;
267882
269898
  exports.NC_BASE_CONDITION_KEYS = NC_BASE_CONDITION_KEYS;
267883
269899
  exports.NC_CONDITION_CATALOG = NC_CONDITION_CATALOG;
@@ -267885,6 +269901,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267885
269901
  exports.NC_CONFIRM_DEFAULT_TIMEOUT_MS = NC_CONFIRM_DEFAULT_TIMEOUT_MS;
267886
269902
  exports.NC_CONFIRM_MAX_TIMEOUT_MS = NC_CONFIRM_MAX_TIMEOUT_MS;
267887
269903
  exports.NC_CONFIRM_MIN_TIMEOUT_MS = NC_CONFIRM_MIN_TIMEOUT_MS;
269904
+ exports.NC_DEFAULT_SNOOZE_MINUTES = NC_DEFAULT_SNOOZE_MINUTES;
267888
269905
  exports.NC_HISTORY_LIMIT_DEFAULT = NC_HISTORY_LIMIT_DEFAULT;
267889
269906
  exports.NC_HISTORY_LIMIT_MAX = NC_HISTORY_LIMIT_MAX;
267890
269907
  exports.NC_MAX_PER_TRACK_IMMEDIATE = NC_MAX_PER_TRACK_IMMEDIATE;
@@ -268031,7 +270048,9 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268031
270048
  exports.RECOGNITION_TYPES = RECOGNITION_TYPES;
268032
270049
  exports.RECORDING_EXPORT_MAX_READ_BYTES = RECORDING_EXPORT_MAX_READ_BYTES;
268033
270050
  exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
270051
+ exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
268034
270052
  exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
270053
+ exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
268035
270054
  exports.RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT;
268036
270055
  exports.RawStateResultSchema = require_sleep.RawStateResultSchema;
268037
270056
  exports.ReadGopBytesResultSchema = ReadGopBytesResultSchema;
@@ -268303,7 +270322,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268303
270322
  exports.audioAnalysisCapability = audioAnalysisCapability;
268304
270323
  exports.audioAnalyzerCapability = audioAnalyzerCapability;
268305
270324
  exports.audioCodecCapability = audioCodecCapability;
270325
+ exports.audioIsFailClosed = audioIsFailClosed;
270326
+ exports.audioLabelChoices = audioLabelChoices;
268306
270327
  exports.audioMetricsCapability = audioMetricsCapability;
270328
+ exports.audioOrDefaults = audioOrDefaults;
268307
270329
  exports.audioPlanFromEncodeProfile = require_canonical_hash.audioPlanFromEncodeProfile;
268308
270330
  exports.authProviderCapability = authProviderCapability;
268309
270331
  exports.autoAssignProfiles = autoAssignProfiles;
@@ -268441,6 +270463,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268441
270463
  exports.invocationFromEncodeProfile = require_canonical_hash.invocationFromEncodeProfile;
268442
270464
  exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
268443
270465
  exports.isArrayOutputSchema = isArrayOutputSchema;
270466
+ exports.isAudioLabelSelected = isAudioLabelSelected;
268444
270467
  exports.isBaseConditionKey = isBaseConditionKey;
268445
270468
  exports.isCollectionArrayMethod = isCollectionArrayMethod;
268446
270469
  exports.isDeployableToAgent = isDeployableToAgent;
@@ -268450,6 +270473,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268450
270473
  exports.isIsolatedBuiltin = isIsolatedBuiltin;
268451
270474
  exports.isNode = isNode;
268452
270475
  exports.isObjectInput = isObjectInput;
270476
+ exports.isRestoredCap = isRestoredCap;
268453
270477
  exports.isSameAddonId = isSameAddonId;
268454
270478
  exports.isScheduleActive = isScheduleActive;
268455
270479
  exports.isSoftwareDecode = require_canonical_hash.isSoftwareDecode;
@@ -268498,6 +270522,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268498
270522
  exports.nodePin = require_sleep.nodePin;
268499
270523
  exports.nodesCapability = nodesCapability;
268500
270524
  exports.normalizeAddonInitResult = require_sleep.normalizeAddonInitResult;
270525
+ exports.normalizeAudioLabel = normalizeAudioLabel;
268501
270526
  exports.normalizeUnit = normalizeUnit;
268502
270527
  exports.notificationOutputCapability = notificationOutputCapability;
268503
270528
  exports.notificationRulesCapability = notificationRulesCapability;
@@ -268514,6 +270539,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268514
270539
  exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
268515
270540
  exports.parseProfileBrokerId = require_sleep.parseProfileBrokerId;
268516
270541
  exports.parseStreamParamsFormPatch = parseStreamParamsFormPatch;
270542
+ exports.patchAudio = patchAudio;
268517
270543
  exports.petFeederCapability = petFeederCapability;
268518
270544
  exports.pickAccessoryControl = pickAccessoryControl;
268519
270545
  exports.pickDetailCropConvention = pickDetailCropConvention;
@@ -268566,6 +270592,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268566
270592
  exports.resolveVariantModelId = resolveVariantModelId;
268567
270593
  exports.runInferenceStep = runInferenceStep;
268568
270594
  exports.runtimeDevices = runtimeDevices;
270595
+ exports.runtimeStatePolicyFor = runtimeStatePolicyFor;
268569
270596
  exports.sceneMonitorCapability = sceneMonitorCapability;
268570
270597
  exports.scopeKey = require_sleep.scopeKey;
268571
270598
  exports.scopesAllowAddon = scopesAllowAddon;
@@ -268612,6 +270639,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268612
270639
  exports.toNodeId = require_sleep.toNodeId;
268613
270640
  exports.toStreamSourceEntry = toStreamSourceEntry;
268614
270641
  exports.toastCapability = toastCapability;
270642
+ exports.toggleAudioLabel = toggleAudioLabel;
268615
270643
  exports.tokenize = tokenize;
268616
270644
  exports.transcodeBody = transcodeBody;
268617
270645
  exports.tryConvertUnit = tryConvertUnit;
@@ -389744,7 +391772,10 @@ var require_addon_registry_service = __commonJS({
389744
391772
  "setDevice",
389745
391773
  "getAddonDevice",
389746
391774
  "setAddonDevice",
389747
- "clearAddonDevice"
391775
+ "clearAddonDevice",
391776
+ "getDeviceRuntimeState",
391777
+ "setDeviceRuntimeState",
391778
+ "clearDeviceRuntimeState"
389748
391779
  ];
389749
391780
  for (const key of required) {
389750
391781
  if (typeof Reflect.get(backend, key) !== "function")
@@ -397201,6 +399232,18 @@ var require_cluster_node_history_store = __commonJS({
397201
399232
  descriptor: DescriptorSchema
397202
399233
  });
397203
399234
  var COLLECTION = "cluster-node-history";
399235
+ var LAST_ACTIVE_HEARTBEAT_MS = 5 * 6e4;
399236
+ function descriptorsEqual(a, b) {
399237
+ 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);
399238
+ }
399239
+ function sameStrings(a, b) {
399240
+ return a.length === b.length && a.every((v, i) => v === b[i]);
399241
+ }
399242
+ function samePackages(a, b) {
399243
+ if (a === void 0 || b === void 0)
399244
+ return a === b;
399245
+ return a.length === b.length && a.every((p, i) => p.name === b[i]?.name && p.version === b[i]?.version);
399246
+ }
397204
399247
  var ClusterNodeHistoryStore = class _ClusterNodeHistoryStore {
397205
399248
  resolveSettingsStore;
397206
399249
  logger;
@@ -397242,6 +399285,19 @@ var require_cluster_node_history_store = __commonJS({
397242
399285
  * Upsert a node's descriptor + addon roster while it is ONLINE, stamping
397243
399286
  * `lastActive = Date.now()`. Called on each `listNodes()` refresh for every
397244
399287
  * live node (R2 capture point).
399288
+ *
399289
+ * WRITES ONLY ON A CHANGE, OR ONCE PER {@link LAST_ACTIVE_HEARTBEAT_MS}.
399290
+ * `listNodes()` runs off the 30 s topology heartbeat, so the unconditional
399291
+ * upsert this used to be was the worst write:row ratio in the whole system —
399292
+ * measured on the live hub at ~6 upserts/minute = **8,640 a day to maintain
399293
+ * three rows**, i.e. 40 % of every ledger write on the hub, on the same shfs
399294
+ * SQLite file whose WAL checkpoint can stall hub-main for seconds (D96).
399295
+ *
399296
+ * Nothing is lost. `lastActive` exists to answer "when was this node last
399297
+ * seen", which only matters at OFFLINE resolution — and the disconnect edge
399298
+ * is written by {@link touch}, which is unbounded. The bound costs at most
399299
+ * five minutes of precision on a node that then disappeared without a
399300
+ * `$node.disconnected`, and buys 288 writes/day/node instead of 2,880.
397245
399301
  */
397246
399302
  async snapshot(descriptor) {
397247
399303
  const store = await this.ensureDeclared();
@@ -397250,13 +399306,13 @@ var require_cluster_node_history_store = __commonJS({
397250
399306
  try {
397251
399307
  const existing = await this.readRow(store, descriptor.id);
397252
399308
  const packages = descriptor.packages ?? existing?.descriptor.packages;
399309
+ const next = packages === void 0 ? descriptor : { ...descriptor, packages };
399310
+ if (existing !== null && !this.worthWriting(existing, next))
399311
+ return;
397253
399312
  await store.set({
397254
399313
  collection: COLLECTION,
397255
399314
  key: descriptor.id,
397256
- value: {
397257
- lastActive: Date.now(),
397258
- descriptor: packages === void 0 ? descriptor : { ...descriptor, packages }
397259
- }
399315
+ value: { lastActive: Date.now(), descriptor: next }
397260
399316
  });
397261
399317
  } catch (err) {
397262
399318
  this.logger.warn("snapshot upsert failed (best-effort)", {
@@ -397265,6 +399321,16 @@ var require_cluster_node_history_store = __commonJS({
397265
399321
  });
397266
399322
  }
397267
399323
  }
399324
+ /**
399325
+ * Is this snapshot worth a commit? Yes when the descriptor CHANGED, or when
399326
+ * the stored `lastActive` has aged past the heartbeat bound. A read that
399327
+ * fails never reaches here — `snapshot` writes when there is no existing row.
399328
+ */
399329
+ worthWriting(existing, next) {
399330
+ if (Date.now() - existing.lastActive >= LAST_ACTIVE_HEARTBEAT_MS)
399331
+ return true;
399332
+ return !descriptorsEqual(existing.descriptor, next);
399333
+ }
397268
399334
  /** Read one node's row, validated. `null` when absent or malformed. */
397269
399335
  async readRow(store, nodeId) {
397270
399336
  const raw = await store.get({ collection: COLLECTION, key: nodeId });