camstack 1.2.22 → 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.
@@ -6378,62 +6378,30 @@ var require_first_boot_addon_plan = __commonJS({
6378
6378
  "../../server/backend/dist/first-boot-addon-plan.js"(exports) {
6379
6379
  "use strict";
6380
6380
  Object.defineProperty(exports, "__esModule", { value: true });
6381
- exports.resolveWantedVersion = resolveWantedVersion;
6382
- exports.compareVersions = compareVersions;
6383
- exports.bootstrapPinsFrom = bootstrapPinsFrom;
6384
6381
  exports.planFirstBootAddons = planFirstBootAddons;
6385
6382
  exports.formatFirstBootPlan = formatFirstBootPlan;
6386
- var EXACT_VERSION = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z-.]+)?$/;
6387
- function resolveWantedVersion(raw) {
6388
- if (raw !== void 0 && EXACT_VERSION.test(raw))
6389
- return { spec: raw, pinned: true };
6390
- return { spec: "latest", pinned: false };
6391
- }
6392
- function compareVersions(a, b) {
6393
- if (!EXACT_VERSION.test(a) || !EXACT_VERSION.test(b))
6394
- return null;
6395
- const parse4 = (v) => v.split(/[-+]/)[0].split(".").map((n) => Number.parseInt(n, 10));
6396
- const left = parse4(a);
6397
- const right = parse4(b);
6398
- for (let i = 0; i < 3; i++) {
6399
- const l = left[i] ?? 0;
6400
- const r = right[i] ?? 0;
6401
- if (l !== r)
6402
- return l > r ? 1 : -1;
6403
- }
6404
- return 0;
6405
- }
6406
- function bootstrapPinsFrom(manifest) {
6407
- const out = {};
6408
- for (const [name, spec] of Object.entries(manifest.dependencies ?? {})) {
6409
- if (name.startsWith("@camstack/"))
6410
- out[name] = spec;
6411
- }
6412
- return out;
6413
- }
6414
6383
  function planFirstBootAddons(input) {
6415
6384
  const provided = new Set(input.closureProvided);
6416
- const decisions = input.required.map((pkg) => decide(pkg, input.closurePins[pkg], input.installed, provided));
6385
+ const decisions = input.required.map((pkg) => decide(pkg, input.installed, input.closureVersions, provided));
6417
6386
  const wouldInstall = decisions.filter((d) => d.action === "install-npm").map((d) => d.pkg);
6418
6387
  return {
6419
6388
  decisions,
6420
6389
  wouldInstall,
6421
- unpinned: decisions.filter((d) => d.action === "install-npm" && !d.want.pinned).map((d) => d.pkg),
6422
- divergent: decisions.filter((d) => d.divergent).map((d) => d.pkg),
6390
+ twoVersions: decisions.filter((d) => d.twoVersions).map((d) => d.pkg),
6423
6391
  needsRegistry: wouldInstall.length > 0
6424
6392
  };
6425
6393
  }
6426
- function decide(pkg, pin, installed, closureProvided) {
6427
- const want = resolveWantedVersion(pin);
6394
+ function decide(pkg, installed, closureVersions, closureProvided) {
6428
6395
  const hasEntry = Object.hasOwn(installed, pkg);
6429
6396
  const version = installed[pkg] ?? null;
6397
+ const closureVersion = closureVersions[pkg] ?? null;
6430
6398
  if (closureProvided.has(pkg)) {
6431
6399
  return {
6432
6400
  pkg,
6433
6401
  action: "skip-closure-provided",
6434
- want,
6435
6402
  installedVersion: version,
6436
- divergent: false,
6403
+ closureVersion,
6404
+ twoVersions: false,
6437
6405
  reason: "provided by the server closure \u2014 a copy here would shadow it (D15)"
6438
6406
  };
6439
6407
  }
@@ -6441,59 +6409,33 @@ var require_first_boot_addon_plan = __commonJS({
6441
6409
  return {
6442
6410
  pkg,
6443
6411
  action: "install-npm",
6444
- want,
6445
6412
  installedVersion: null,
6446
- divergent: false,
6447
- reason: hasEntry ? `installed copy has an unreadable package.json \u2014 fetching ${want.spec}` : want.pinned ? `absent \u2014 fetch ${want.spec}, the version the running closure pins` : "absent, and the closure carries no pin \u2014 fetching latest"
6413
+ closureVersion,
6414
+ twoVersions: false,
6415
+ reason: hasEntry ? "installed copy has an unreadable package.json \u2014 fetching latest" : "absent \u2014 fetching latest, which is the whole version contract (addons-agnostic)"
6448
6416
  };
6449
6417
  }
6418
+ const twoVersions = closureVersion !== null && closureVersion !== version;
6450
6419
  return {
6451
6420
  pkg,
6452
6421
  action: "adopt-installed",
6453
- want,
6454
6422
  installedVersion: version,
6455
- ...adoptionVerdict(version, want)
6456
- };
6457
- }
6458
- function adoptionVerdict(version, want) {
6459
- if (!want.pinned) {
6460
- return { divergent: false, reason: `installed ${version}; the closure carries no pin to check` };
6461
- }
6462
- const order = compareVersions(version, want.spec);
6463
- if (order === null) {
6464
- return { divergent: true, reason: `installed ${version} is not comparable to pin ${want.spec}` };
6465
- }
6466
- if (order === 0)
6467
- return { divergent: false, reason: `installed ${version} matches the pin` };
6468
- const sameMajor = version.split(".")[0] === want.spec.split(".")[0];
6469
- if (!sameMajor) {
6470
- return {
6471
- divergent: true,
6472
- reason: `installed ${version} is a different major from the pinned ${want.spec} \u2014 a framework contract, not a version gap`
6473
- };
6474
- }
6475
- if (order > 0) {
6476
- return {
6477
- divergent: false,
6478
- reason: `installed ${version} is ahead of the pinned ${want.spec} \u2014 a deploy outranks the image (D90)`
6479
- };
6480
- }
6481
- return {
6482
- divergent: true,
6483
- reason: `installed ${version} is BEHIND the pinned ${want.spec} \u2014 kept, because replacing it is an un-deploy`
6423
+ closureVersion,
6424
+ twoVersions,
6425
+ reason: twoVersions ? `installed ${version}; this closure carries ${closureVersion} \u2014 the installed copy is what runs (D90)` : `installed ${version} \u2014 kept`
6484
6426
  };
6485
6427
  }
6486
6428
  function formatFirstBootPlan(plan, mode) {
6487
- const header = `first-boot addon plan (${mode}) \u2014 ${plan.decisions.length} package(s), ${plan.wouldInstall.length} to install, ${plan.divergent.length} divergent`;
6429
+ const header = `first-boot addon plan (${mode}) \u2014 ${plan.decisions.length} package(s), ${plan.wouldInstall.length} to install`;
6488
6430
  const lines = [
6489
6431
  mode === "observe" ? `${header}; nothing was installed from this plan` : header
6490
6432
  ];
6491
6433
  for (const d of plan.decisions) {
6492
- const target = d.action === "install-npm" ? `@${d.want.spec}` : "";
6434
+ const target = d.action === "install-npm" ? "@latest" : "";
6493
6435
  lines.push(` ${d.pkg}${target} \u2014 ${d.action}: ${d.reason}`);
6494
6436
  }
6495
- if (plan.unpinned.length > 0) {
6496
- lines.push(` unpinned (would resolve the registry's latest): ${plan.unpinned.join(", ")} \u2014 the running closure declares no exact version for these, so what a node ends up with depends on when it booted`);
6437
+ if (plan.twoVersions.length > 0) {
6438
+ lines.push(` two versions on this node (installed vs this closure's own copy): ${plan.twoVersions.join(", ")} \u2014 INFO: the installed copy is the one that runs`);
6497
6439
  }
6498
6440
  if (!plan.needsRegistry) {
6499
6441
  lines.push(" no registry access needed \u2014 every required addon is already on disk");
@@ -23689,9 +23631,9 @@ var require_zod = __commonJS({
23689
23631
  }
23690
23632
  });
23691
23633
 
23692
- // ../system/dist/dist-DiScsb8j.js
23693
- var require_dist_DiScsb8j = __commonJS({
23694
- "../system/dist/dist-DiScsb8j.js"(exports) {
23634
+ // ../system/dist/dist-D4nuaPdd.js
23635
+ var require_dist_D4nuaPdd = __commonJS({
23636
+ "../system/dist/dist-D4nuaPdd.js"(exports) {
23695
23637
  "use strict";
23696
23638
  var zod = require_zod();
23697
23639
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -29149,7 +29091,14 @@ var require_dist_DiScsb8j = __commonJS({
29149
29091
  low: zod.z.string().optional()
29150
29092
  }),
29151
29093
  lastChangedAt: zod.z.number()
29152
- })
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"
29153
29102
  };
29154
29103
  function isVoidInput(schema) {
29155
29104
  const def = schema._def;
@@ -29819,6 +29768,13 @@ var require_dist_DiScsb8j = __commonJS({
29819
29768
  kind: "poll"
29820
29769
  },
29821
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",
29822
29778
  methods: {
29823
29779
  /**
29824
29780
  * Snapshot of the current `discovered` list. Returns the
@@ -32043,7 +31999,23 @@ var require_dist_DiScsb8j = __commonJS({
32043
31999
  * else — see `notification-center/action-token.ts` for what that does and
32044
32000
  * does not buy.
32045
32001
  */
32046
- 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()
32047
32019
  });
32048
32020
  var NotificationSchema = zod.z.object({
32049
32021
  body: zod.z.string(),
@@ -32821,7 +32793,17 @@ var require_dist_DiScsb8j = __commonJS({
32821
32793
  * full slice; renders an arm button per `availableModes` entry and
32822
32794
  * a PIN field iff `requiresCode === true`.
32823
32795
  */
32824
- 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"]
32825
32807
  };
32826
32808
  var MaskPointSchema = zod.z.object({
32827
32809
  x: zod.z.number(),
@@ -32919,6 +32901,7 @@ var require_dist_DiScsb8j = __commonJS({
32919
32901
  nodeIds: zod.z.array(zod.z.string().min(1)).min(1).optional(),
32920
32902
  packageNames: zod.z.array(zod.z.string().min(1)).min(1).optional()
32921
32903
  });
32904
+ var NC_SNOOZE_MAX_MINUTES = 1440;
32922
32905
  var NcScheduleWindowSchema = zod.z.object({
32923
32906
  /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
32924
32907
  days: zod.z.array(zod.z.number().int().min(0).max(6)).min(1),
@@ -33175,15 +33158,15 @@ var require_dist_DiScsb8j = __commonJS({
33175
33158
  * (an `immediate` rule naming an `audio-*` class, one notification per
33176
33159
  * classified sample) stays exactly as it was for rules that already use it.
33177
33160
  *
33178
- * NOT in {@link NC_CONDITION_CATALOG} yet, and that is the sequencing rule
33179
- * rather than an oversight: the viewer mirrors the descriptor enums BY HAND
33180
- * (`camstack/src/data/notification-center.ts`, guarded by
33181
- * `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
33182
33165
  * condition fields it does not know when a rule is saved from the phone.
33183
33166
  * Publishing an editor for a condition the app cannot round-trip is how an
33184
- * operator loses a rule's conditions by opening it — so the descriptor, the
33185
- * admin widget and the viewer mirror land together (P2 + P3), and only then
33186
- * 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.
33187
33170
  */
33188
33171
  audio: NcAudioConditionSchema.optional()
33189
33172
  });
@@ -33352,6 +33335,30 @@ var require_dist_DiScsb8j = __commonJS({
33352
33335
  */
33353
33336
  snoozeAllowGlobal: zod.z.boolean().optional(),
33354
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
+ /**
33355
33362
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
33356
33363
  *
33357
33364
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -33441,6 +33448,7 @@ var require_dist_DiScsb8j = __commonJS({
33441
33448
  "device",
33442
33449
  "package",
33443
33450
  "occupancy",
33451
+ "audio",
33444
33452
  "system"
33445
33453
  ]),
33446
33454
  label: zod.z.string(),
@@ -33459,6 +33467,7 @@ var require_dist_DiScsb8j = __commonJS({
33459
33467
  "crossingSelect",
33460
33468
  "polygonDraw",
33461
33469
  "occupancy",
33470
+ "audio",
33462
33471
  "deviceState",
33463
33472
  "systemEvent"
33464
33473
  ]),
@@ -33569,7 +33578,20 @@ var require_dist_DiScsb8j = __commonJS({
33569
33578
  ruleId: zod.z.string().optional(),
33570
33579
  /** Required when `scope: 'device'`. */
33571
33580
  deviceId: zod.z.number().int().optional(),
33572
- 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),
33573
33595
  /**
33574
33596
  * Silence this for EVERY recipient, not just the caller. Permission is
33575
33597
  * checked server-side (the rule's `snoozeAllowGlobal`, or admin for the
@@ -33593,6 +33615,10 @@ var require_dist_DiScsb8j = __commonJS({
33593
33615
  scope: NcSnoozeScopeSchema,
33594
33616
  ruleId: zod.z.string().optional(),
33595
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(),
33596
33622
  startedAt: zod.z.number(),
33597
33623
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
33598
33624
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -35865,7 +35891,14 @@ var require_dist_DiScsb8j = __commonJS({
35865
35891
  * handle. Slice shape is `{ zones: Zone[] }` so future extensions
35866
35892
  * (e.g. zone groupings) can sit alongside the polygon list.
35867
35893
  */
35868
- 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"
35869
35902
  };
35870
35903
  var NativeCropBboxSchema = zod.z.object({
35871
35904
  x: zod.z.number(),
@@ -39168,7 +39201,17 @@ var require_dist_DiScsb8j = __commonJS({
39168
39201
  schema: AirQualitySensorStatusSchema,
39169
39202
  kind: "push"
39170
39203
  },
39171
- 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"]
39172
39215
  };
39173
39216
  var AmbientLightSensorStatusSchema = zod.z.object({
39174
39217
  /** Current illuminance in lux (lx). */
@@ -39196,7 +39239,17 @@ var require_dist_DiScsb8j = __commonJS({
39196
39239
  schema: AmbientLightSensorStatusSchema,
39197
39240
  kind: "push"
39198
39241
  },
39199
- 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"]
39200
39253
  };
39201
39254
  var AudioClassSummarySchema = zod.z.object({
39202
39255
  className: zod.z.string(),
@@ -39286,7 +39339,14 @@ var require_dist_DiScsb8j = __commonJS({
39286
39339
  }), AudioMetricsHistorySchema)
39287
39340
  },
39288
39341
  /** Reactive runtime-state mirror — live `device.state.audioMetrics.value`. */
39289
- 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"
39290
39350
  };
39291
39351
  var AutomationControlStatusSchema = zod.z.object({
39292
39352
  /** Whether the automation is currently enabled. Disabled automations
@@ -39337,7 +39397,14 @@ var require_dist_DiScsb8j = __commonJS({
39337
39397
  * reads `enabled` (toggle) + `isRunning` (spinner) + `lastError`
39338
39398
  * (badge) directly.
39339
39399
  */
39340
- 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"
39341
39408
  };
39342
39409
  var BatteryStatusSchema = zod.z.object({
39343
39410
  /** 0..100 inclusive. Firmware-reported. */
@@ -39438,7 +39505,17 @@ var require_dist_DiScsb8j = __commonJS({
39438
39505
  * via `device.runtimeState.getCapState('battery')` regardless of
39439
39506
  * the underlying driver.
39440
39507
  */
39441
- 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"]
39442
39519
  };
39443
39520
  var BinaryStatusSchema = zod.z.object({
39444
39521
  on: zod.z.boolean(),
@@ -39456,7 +39533,17 @@ var require_dist_DiScsb8j = __commonJS({
39456
39533
  schema: BinaryStatusSchema,
39457
39534
  kind: "push"
39458
39535
  },
39459
- 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"]
39460
39547
  };
39461
39548
  var BrightnessStatusSchema = zod.z.object({
39462
39549
  /** Current level as 0..100 inclusive. Firmware-reported. */
@@ -39498,7 +39585,14 @@ var require_dist_DiScsb8j = __commonJS({
39498
39585
  * by the kernel. Read via `device.state.brightness.value` so UI
39499
39586
  * sliders surface the current level without polling the provider.
39500
39587
  */
39501
- 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"
39502
39596
  };
39503
39597
  var buttonCapability = {
39504
39598
  name: "button",
@@ -39578,7 +39672,17 @@ var require_dist_DiScsb8j = __commonJS({
39578
39672
  schema: CarbonMonoxideStatusSchema,
39579
39673
  kind: "push"
39580
39674
  },
39581
- 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"]
39582
39686
  };
39583
39687
  var HvacModeSchema = zod.z.enum([
39584
39688
  "off",
@@ -39707,7 +39811,14 @@ var require_dist_DiScsb8j = __commonJS({
39707
39811
  * the full slice via `device.state.climate-control.value` and refresh
39708
39812
  * on every push without re-querying the provider.
39709
39813
  */
39710
- 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"
39711
39822
  };
39712
39823
  var RgbTripletSchema = zod.z.object({
39713
39824
  r: zod.z.number().int().min(0).max(255),
@@ -39794,7 +39905,14 @@ var require_dist_DiScsb8j = __commonJS({
39794
39905
  * kernel. Read via `device.state.color.value` so UI pickers surface
39795
39906
  * the current chromaticity without polling the provider.
39796
39907
  */
39797
- 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"
39798
39916
  };
39799
39917
  var ConnectionTestOutcomeSchema = zod.z.discriminatedUnion("outcome", [
39800
39918
  zod.z.object({
@@ -39848,7 +39966,17 @@ var require_dist_DiScsb8j = __commonJS({
39848
39966
  schema: ConnectivityStatusSchema,
39849
39967
  kind: "push"
39850
39968
  },
39851
- 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"]
39852
39980
  };
39853
39981
  var ConsumableItemSchema = zod.z.object({
39854
39982
  /** Stable id, e.g. 'main-brush'. */
@@ -39915,7 +40043,14 @@ var require_dist_DiScsb8j = __commonJS({
39915
40043
  }
39916
40044
  }
39917
40045
  },
39918
- 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"
39919
40054
  };
39920
40055
  var ContactStatusSchema = zod.z.object({
39921
40056
  /** True when the entry is open; false when closed. */
@@ -39934,7 +40069,17 @@ var require_dist_DiScsb8j = __commonJS({
39934
40069
  schema: ContactStatusSchema,
39935
40070
  kind: "push"
39936
40071
  },
39937
- 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"]
39938
40083
  };
39939
40084
  var ControlKindSchema = zod.z.enum([
39940
40085
  "numeric",
@@ -40021,7 +40166,14 @@ var require_dist_DiScsb8j = __commonJS({
40021
40166
  * dropdown / text field / date picker) read the slice's discriminant
40022
40167
  * and value directly without polling the provider.
40023
40168
  */
40024
- 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"
40025
40177
  };
40026
40178
  var CoverStateSchema = zod.z.enum([
40027
40179
  "open",
@@ -40083,7 +40235,17 @@ var require_dist_DiScsb8j = __commonJS({
40083
40235
  * Runtime-state slice — mirrored by the kernel. UI controls watch
40084
40236
  * the slice for live position changes during a move.
40085
40237
  */
40086
- 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"]
40087
40249
  };
40088
40250
  var DayNightModeSchema = zod.z.enum([
40089
40251
  "auto",
@@ -40144,7 +40306,17 @@ var require_dist_DiScsb8j = __commonJS({
40144
40306
  schema: DayNightStatusSchema,
40145
40307
  kind: "poll"
40146
40308
  },
40147
- 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"]
40148
40320
  };
40149
40321
  var DeviceStatusSchema = zod.z.object({
40150
40322
  /**
@@ -40177,7 +40349,17 @@ var require_dist_DiScsb8j = __commonJS({
40177
40349
  schema: DeviceStatusSchema,
40178
40350
  kind: "push"
40179
40351
  },
40180
- 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"]
40181
40363
  };
40182
40364
  var DoorbellStatusSchema = zod.z.object({
40183
40365
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -40217,7 +40399,14 @@ var require_dist_DiScsb8j = __commonJS({
40217
40399
  * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
40218
40400
  * without subscribing.
40219
40401
  */
40220
- 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"
40221
40410
  };
40222
40411
  var EnumSensorDateTimeFormatSchema = zod.z.enum([
40223
40412
  "date",
@@ -40247,7 +40436,17 @@ var require_dist_DiScsb8j = __commonJS({
40247
40436
  schema: EnumSensorStatusSchema,
40248
40437
  kind: "push"
40249
40438
  },
40250
- 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"]
40251
40450
  };
40252
40451
  var EventFireSchema = zod.z.object({
40253
40452
  deviceId: zod.z.number(),
@@ -40273,7 +40472,14 @@ var require_dist_DiScsb8j = __commonJS({
40273
40472
  schema: EventEmitterStatusSchema,
40274
40473
  kind: "push"
40275
40474
  },
40276
- 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"
40277
40483
  };
40278
40484
  var EventItemSchema = zod.z.object({
40279
40485
  id: zod.z.string(),
@@ -40572,7 +40778,14 @@ var require_dist_DiScsb8j = __commonJS({
40572
40778
  * Runtime-state slice — mirrored by the kernel. UI fan speed
40573
40779
  * sliders read `percentage` for live updates.
40574
40780
  */
40575
- 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"
40576
40789
  };
40577
40790
  var FeatureProbeStatusSchema = zod.z.object({
40578
40791
  /**
@@ -40625,7 +40838,14 @@ var require_dist_DiScsb8j = __commonJS({
40625
40838
  schema: FeatureProbeStatusSchema,
40626
40839
  kind: "push"
40627
40840
  },
40628
- 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"
40629
40849
  };
40630
40850
  var FloodStatusSchema = zod.z.object({
40631
40851
  /** True when leak is currently detected. */
@@ -40644,7 +40864,17 @@ var require_dist_DiScsb8j = __commonJS({
40644
40864
  schema: FloodStatusSchema,
40645
40865
  kind: "push"
40646
40866
  },
40647
- 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"]
40648
40878
  };
40649
40879
  var GasStatusSchema = zod.z.object({
40650
40880
  detected: zod.z.boolean(),
@@ -40662,7 +40892,17 @@ var require_dist_DiScsb8j = __commonJS({
40662
40892
  schema: GasStatusSchema,
40663
40893
  kind: "push"
40664
40894
  },
40665
- 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"]
40666
40906
  };
40667
40907
  var HumidifierStatusSchema = zod.z.object({
40668
40908
  /** Whether the humidifier is currently on. */
@@ -40723,7 +40963,14 @@ var require_dist_DiScsb8j = __commonJS({
40723
40963
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
40724
40964
  * slice for live humidity / mode changes.
40725
40965
  */
40726
- 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"
40727
40974
  };
40728
40975
  var HumiditySensorStatusSchema = zod.z.object({
40729
40976
  /** Current relative humidity, 0..100. */
@@ -40751,7 +40998,17 @@ var require_dist_DiScsb8j = __commonJS({
40751
40998
  schema: HumiditySensorStatusSchema,
40752
40999
  kind: "push"
40753
41000
  },
40754
- 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"]
40755
41012
  };
40756
41013
  var ImageStatusSchema = zod.z.object({
40757
41014
  /** Absolute signed URL the browser loads directly. Null when the
@@ -40775,7 +41032,14 @@ var require_dist_DiScsb8j = __commonJS({
40775
41032
  * Runtime-state slice — mirrored by the kernel. The UI reads `url`
40776
41033
  * directly and renders the still image.
40777
41034
  */
40778
- 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"
40779
41043
  };
40780
41044
  var ImageRotateSchema = zod.z.enum([
40781
41045
  "0",
@@ -40876,7 +41140,17 @@ var require_dist_DiScsb8j = __commonJS({
40876
41140
  schema: ImageSettingsStatusSchema,
40877
41141
  kind: "poll"
40878
41142
  },
40879
- 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"]
40880
41154
  };
40881
41155
  var IntegrationWithStateSchema = zod.z.object({
40882
41156
  id: zod.z.string(),
@@ -41207,7 +41481,14 @@ var require_dist_DiScsb8j = __commonJS({
41207
41481
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
41208
41482
  * slice for live activity + battery changes.
41209
41483
  */
41210
- 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"
41211
41492
  };
41212
41493
  var InterfaceKindEnum = zod.z.enum([
41213
41494
  "lan",
@@ -41456,7 +41737,17 @@ var require_dist_DiScsb8j = __commonJS({
41456
41737
  * read `state` and disable themselves during `locking`/`unlocking`
41457
41738
  * transitions.
41458
41739
  */
41459
- 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"]
41460
41751
  };
41461
41752
  var MediaPlayerStateSchema = zod.z.enum([
41462
41753
  "off",
@@ -41606,7 +41897,14 @@ var require_dist_DiScsb8j = __commonJS({
41606
41897
  * full slice for live now-playing, volume, and progress updates
41607
41898
  * without polling.
41608
41899
  */
41609
- 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"
41610
41908
  };
41611
41909
  var MeshEndpointSchema = zod.z.object({
41612
41910
  /** Stable identifier within the provider (e.g. `mesh-ipv4`, `magicdns`, `funnel`). */
@@ -41890,7 +42188,14 @@ var require_dist_DiScsb8j = __commonJS({
41890
42188
  * `device.state.motion.value`. Reads never invoke the provider, so
41891
42189
  * UIs and other addons can poll the cached state safely.
41892
42190
  */
41893
- 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"
41894
42199
  };
41895
42200
  var MotionTriggerStatusSchema = zod.z.object({
41896
42201
  enabled: zod.z.boolean(),
@@ -41927,7 +42232,14 @@ var require_dist_DiScsb8j = __commonJS({
41927
42232
  schema: MotionTriggerStatusSchema,
41928
42233
  kind: "command-driven"
41929
42234
  },
41930
- 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"
41931
42243
  };
41932
42244
  var MotionZoneRegionSchema = zod.z.object({
41933
42245
  id: zod.z.number(),
@@ -41982,7 +42294,17 @@ var require_dist_DiScsb8j = __commonJS({
41982
42294
  schema: MotionZoneStatusSchema,
41983
42295
  kind: "poll"
41984
42296
  },
41985
- 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"]
41986
42308
  };
41987
42309
  var NativeObjectClassEnum = zod.z.enum([
41988
42310
  "person",
@@ -42045,7 +42367,17 @@ var require_dist_DiScsb8j = __commonJS({
42045
42367
  schema: NativeObjectDetectionStatusSchema,
42046
42368
  kind: "push"
42047
42369
  },
42048
- 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"]
42049
42381
  };
42050
42382
  var StreamNetworkStatsSchema = zod.z.object({
42051
42383
  nominalBitrateKbps: zod.z.number(),
@@ -42368,7 +42700,14 @@ var require_dist_DiScsb8j = __commonJS({
42368
42700
  * form reads `supports` to gate optional fields; history pane reads
42369
42701
  * `lastSentAt` / `lastError` / `queueDepth`.
42370
42702
  */
42371
- 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"
42372
42711
  };
42373
42712
  var NumericSensorStatusSchema = zod.z.object({
42374
42713
  value: zod.z.number(),
@@ -42394,7 +42733,17 @@ var require_dist_DiScsb8j = __commonJS({
42394
42733
  schema: NumericSensorStatusSchema,
42395
42734
  kind: "push"
42396
42735
  },
42397
- 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"]
42398
42747
  };
42399
42748
  var OsdOverlayKindEnum = zod.z.enum([
42400
42749
  "text",
@@ -42817,7 +43166,14 @@ var require_dist_DiScsb8j = __commonJS({
42817
43166
  * the full slice via `device.state.petFeeder.value` and refresh on
42818
43167
  * every poll without re-querying the provider.
42819
43168
  */
42820
- 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"
42821
43177
  };
42822
43178
  var VehicleSchema = zod.z.object({
42823
43179
  id: zod.z.string(),
@@ -43151,7 +43507,17 @@ var require_dist_DiScsb8j = __commonJS({
43151
43507
  schema: PowerMeterStatusSchema,
43152
43508
  kind: "push"
43153
43509
  },
43154
- 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"]
43155
43521
  };
43156
43522
  var GpsLocationSchema = zod.z.object({
43157
43523
  /** Latitude in decimal degrees, -90..90. */
@@ -43192,7 +43558,17 @@ var require_dist_DiScsb8j = __commonJS({
43192
43558
  * the map pin is rendered (use `DeviceFeature.PresenceGps` for the
43193
43559
  * pre-fetch fast-path check).
43194
43560
  */
43195
- 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"]
43196
43572
  };
43197
43573
  var PressureSensorStatusSchema = zod.z.object({
43198
43574
  /** Current pressure in hPa. */
@@ -43220,7 +43596,17 @@ var require_dist_DiScsb8j = __commonJS({
43220
43596
  schema: PressureSensorStatusSchema,
43221
43597
  kind: "push"
43222
43598
  },
43223
- 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"]
43224
43610
  };
43225
43611
  var PrivacyMaskShapeSchema = zod.z.discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
43226
43612
  var PrivacyMaskRegionSchema = zod.z.object({
@@ -43319,7 +43705,17 @@ var require_dist_DiScsb8j = __commonJS({
43319
43705
  schema: PrivacyMaskStatusSchema,
43320
43706
  kind: "poll"
43321
43707
  },
43322
- 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"]
43323
43719
  };
43324
43720
  var PtzPresetSchema = zod.z.object({
43325
43721
  id: zod.z.string(),
@@ -43505,7 +43901,14 @@ var require_dist_DiScsb8j = __commonJS({
43505
43901
  * fetch / cache / fallback logic out of the four cap methods —
43506
43902
  * they become trampolines over `runtimeState`.
43507
43903
  */
43508
- 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"
43509
43912
  };
43510
43913
  var rebootCapability = {
43511
43914
  name: "reboot",
@@ -44173,7 +44576,14 @@ var require_dist_DiScsb8j = __commonJS({
44173
44576
  schema: SceneMonitorStatusSchema,
44174
44577
  kind: "push"
44175
44578
  },
44176
- 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"
44177
44587
  };
44178
44588
  var ZoneRuleModeEnum = zod.z.enum(["include", "exclude"]);
44179
44589
  var ZoneRuleSchema = zod.z.object({
@@ -44275,7 +44685,14 @@ var require_dist_DiScsb8j = __commonJS({
44275
44685
  * `isRunning` to render a spinner during execution and surfaces
44276
44686
  * `lastError` / `lastRunSuccess` in the recent-runs panel.
44277
44687
  */
44278
- 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"
44279
44696
  };
44280
44697
  var SmokeStatusSchema = zod.z.object({
44281
44698
  detected: zod.z.boolean(),
@@ -44293,7 +44710,17 @@ var require_dist_DiScsb8j = __commonJS({
44293
44710
  schema: SmokeStatusSchema,
44294
44711
  kind: "push"
44295
44712
  },
44296
- 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"]
44297
44724
  };
44298
44725
  var CamStreamDescriptorSchema = zod.z.object({
44299
44726
  camStreamId: zod.z.string().min(1),
@@ -44444,7 +44871,17 @@ var require_dist_DiScsb8j = __commonJS({
44444
44871
  schema: StreamParamsStatusSchema,
44445
44872
  kind: "poll"
44446
44873
  },
44447
- 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"]
44448
44885
  };
44449
44886
  var STREAM_PROFILE_META = [
44450
44887
  {
@@ -44715,6 +45152,16 @@ var require_dist_DiScsb8j = __commonJS({
44715
45152
  * not need to re-query the provider after a setState mutation.
44716
45153
  */
44717
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"],
44718
45165
  settings: { bindings: [{
44719
45166
  kind: "scalar",
44720
45167
  statusPath: "on",
@@ -44787,7 +45234,17 @@ var require_dist_DiScsb8j = __commonJS({
44787
45234
  schema: TamperStatusSchema,
44788
45235
  kind: "push"
44789
45236
  },
44790
- 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"]
44791
45248
  };
44792
45249
  var TemperatureSensorStatusSchema = zod.z.object({
44793
45250
  /** Current temperature in Celsius. */
@@ -44816,7 +45273,17 @@ var require_dist_DiScsb8j = __commonJS({
44816
45273
  schema: TemperatureSensorStatusSchema,
44817
45274
  kind: "push"
44818
45275
  },
44819
- 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"]
44820
45287
  };
44821
45288
  var ToastSchema = zod.z.object({
44822
45289
  title: zod.z.string(),
@@ -44867,7 +45334,14 @@ var require_dist_DiScsb8j = __commonJS({
44867
45334
  schema: UpdateStatusSchema,
44868
45335
  kind: "poll"
44869
45336
  },
44870
- 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"
44871
45345
  };
44872
45346
  var UserSummarySchema = zod.z.object({
44873
45347
  id: zod.z.string(),
@@ -45201,7 +45675,14 @@ var require_dist_DiScsb8j = __commonJS({
45201
45675
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
45202
45676
  * slice for live state + battery + fan-speed changes.
45203
45677
  */
45204
- 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"
45205
45686
  };
45206
45687
  var ValveStateSchema = zod.z.enum([
45207
45688
  "open",
@@ -45254,7 +45735,14 @@ var require_dist_DiScsb8j = __commonJS({
45254
45735
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
45255
45736
  * slice for live position changes during a move.
45256
45737
  */
45257
- 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"
45258
45746
  };
45259
45747
  var VibrationStatusSchema = zod.z.object({
45260
45748
  detected: zod.z.boolean(),
@@ -45272,7 +45760,17 @@ var require_dist_DiScsb8j = __commonJS({
45272
45760
  schema: VibrationStatusSchema,
45273
45761
  kind: "push"
45274
45762
  },
45275
- 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"]
45276
45774
  };
45277
45775
  var WaterHeaterStatusSchema = zod.z.object({
45278
45776
  /** Current measured temperature. Null when not reported. */
@@ -45332,7 +45830,14 @@ var require_dist_DiScsb8j = __commonJS({
45332
45830
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
45333
45831
  * slice for live temperature / mode / away changes.
45334
45832
  */
45335
- 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"
45336
45841
  };
45337
45842
  var WeatherStatusSchema = zod.z.object({
45338
45843
  /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
@@ -45372,7 +45877,14 @@ var require_dist_DiScsb8j = __commonJS({
45372
45877
  * Runtime-state slice — mirrored by the kernel. The UI reads the
45373
45878
  * current conditions directly from the slice on each weather push.
45374
45879
  */
45375
- 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"
45376
45888
  };
45377
45889
  var PerScopeBreakdownSchema = zod.z.object({
45378
45890
  /** Total tracked objects in this scope (frame / zone / unzoned). */
@@ -45481,7 +45993,14 @@ var require_dist_DiScsb8j = __commonJS({
45481
45993
  * automatically; the explicit `getCurrentSnapshot` cap method is
45482
45994
  * still useful for one-off polls without a subscription.
45483
45995
  */
45484
- 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"
45485
46004
  };
45486
46005
  var ZoneRuleStageEnum = zod.z.enum([
45487
46006
  "motion",
@@ -45533,7 +46052,14 @@ var require_dist_DiScsb8j = __commonJS({
45533
46052
  motion: zod.z.array(ZoneRuleSchema).readonly(),
45534
46053
  detection: zod.z.array(ZoneRuleSchema).readonly(),
45535
46054
  package: zod.z.array(ZoneRuleSchema).readonly()
45536
- })
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"
45537
46063
  };
45538
46064
  var RUNTIME_DEFAULTS = {
45539
46065
  "features.streaming": true,
@@ -51676,6 +52202,264 @@ var require_dist_DiScsb8j = __commonJS({
51676
52202
  "network-access": "ingress",
51677
52203
  "smtp-provider": "email"
51678
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]]));
51679
52463
  var TimelapseTemplateSchema = zod.z.object({
51680
52464
  title: zod.z.string().max(500).optional(),
51681
52465
  body: zod.z.string().max(2e3).optional()
@@ -51812,25 +52596,32 @@ var require_dist_DiScsb8j = __commonJS({
51812
52596
  var NativeLeaseAdmissionSchema = zod.z.enum(["all", "inferred"]);
51813
52597
  zod.z.object({
51814
52598
  /**
51815
- * How long a retained native frame is served before it counts as a miss.
52599
+ * How many delivered frames the worker HOLDS at once, waiting for each one's
52600
+ * detection result.
52601
+ *
52602
+ * This replaced a TTL on 2026-08-13, and the replacement is the whole point:
52603
+ * a time window was never related to the event the pixels were waiting for.
52604
+ * A held frame now lives from delivery until the runner has its `FrameResult`
52605
+ * — at which moment the runner cuts the subject tiles it actually wanted and
52606
+ * releases the frame. The bound exists only so a runner that stops answering
52607
+ * cannot pin RAM: above it the OLDEST held frame is dropped and counted.
51816
52608
  *
51817
- * Must cover the FULL late-crop horizon: detection inference + the
51818
- * cross-process inference-result hop to hub post-analysis + tracking + the
51819
- * tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
51820
- * outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
51821
- * RAM per busy camera grows linearly with no measured hit-rate gain.
52609
+ * Sizing: the steady state is `inferenceLatency × deliveredFps`, measured at
52610
+ * 40-160 ms × ≤25 fps = 1-4 frames. The default leaves headroom for a hiccup
52611
+ * without ever approaching the old resident set (43 frames × 24.9 MB at 4K).
52612
+ * Raising it does not buy hit rate it buys tolerance for a slow runner, and
52613
+ * `holdOverflow` on the metrics line is what says you need it.
51822
52614
  */
51823
- ttlMs: zod.z.number().int().min(250).max(1e4),
52615
+ holdFrames: zod.z.number().int().min(1).max(64),
51824
52616
  /**
51825
52617
  * Hard per-decode-worker RAM ceiling for retained native frames, in MB.
51826
52618
  *
51827
- * Intended as a SAFETY ceiling with the TTL as the effective cap — but check
51828
- * which one is actually binding before reasoning from that. At the shipped
51829
- * 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
51830
- * at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
51831
- * `leaseFrames` on the metrics line say which. When the ceiling binds, a
51832
- * change that admits fewer frames buys retention WINDOW at constant RAM
51833
- * rather than giving RAM back — lower this knob if RAM is what you wanted.
52619
+ * Since 2026-08-13 this is a SAFETY ceiling and nothing else: `holdFrames`
52620
+ * is what decides how much is held, and the ceiling is the number above which
52621
+ * something is wrong. Before that it was the effective cap at 1024 MB with
52622
+ * a 2 800 ms TTL a 4K camera sat pinned at `leaseMb:1020, leaseFrames:43`
52623
+ * with the TTL expiring nothing, which is exactly the confusion the hold
52624
+ * removes. `leaseMb` / `leaseFrames` still say what is resident.
51834
52625
  * `0` DISABLES the lease entirely and falls the worker back to the tiny
51835
52626
  * leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
51836
52627
  * to replace).
@@ -51856,17 +52647,36 @@ var require_dist_DiScsb8j = __commonJS({
51856
52647
  * there is the signal that some caller names frames outside the inference set
51857
52648
  * and that this must go back to `all`.
51858
52649
  */
51859
- admission: NativeLeaseAdmissionSchema
52650
+ admission: NativeLeaseAdmissionSchema,
52651
+ /**
52652
+ * RAM ceiling per decode worker, in MB, for the SUBJECT TILES — the
52653
+ * compressed native crops the worker cuts at the moment a frame's detection
52654
+ * result arrives, and keeps long after the frame itself is freed.
52655
+ *
52656
+ * This is the knob that replaced the old retention window, and it buys about
52657
+ * three orders of magnitude more of it: a tile is one subject at native
52658
+ * resolution, JPEG-encoded (~60-120 KB on a 4K person), against ~24.9 MB for
52659
+ * the frame it was cut from. A frame on which nothing was detected costs
52660
+ * nothing at all, which is the real change — the old lease paid per FRAME and
52661
+ * was interrogated per SUBJECT.
52662
+ *
52663
+ * `0` DISABLES tiles, leaving only the hold window and the ≤640 RAM
52664
+ * fallback — i.e. the pre-2026-08-13 miss profile. Set it there only to
52665
+ * reproduce that.
52666
+ */
52667
+ tileBudgetMb: zod.z.number().int().min(0).max(1024)
51860
52668
  });
51861
52669
  var DEFAULT_NATIVE_LEASE_SETTINGS = {
51862
- ttlMs: 1200,
52670
+ holdFrames: 8,
51863
52671
  budgetMb: 1024,
51864
52672
  activityMs: 15e3,
52673
+ tileBudgetMb: 64,
51865
52674
  admission: "inferred"
51866
52675
  };
51867
- DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs;
52676
+ DEFAULT_NATIVE_LEASE_SETTINGS.holdFrames;
51868
52677
  DEFAULT_NATIVE_LEASE_SETTINGS.budgetMb;
51869
52678
  DEFAULT_NATIVE_LEASE_SETTINGS.activityMs;
52679
+ DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb;
51870
52680
  DEFAULT_NATIVE_LEASE_SETTINGS.admission;
51871
52681
  var ADDON_ID_PREFIX = "addon:";
51872
52682
  function bareAddonId(id) {
@@ -52467,6 +53277,12 @@ var require_dist_DiScsb8j = __commonJS({
52467
53277
  return resolveCapMount;
52468
53278
  }
52469
53279
  });
53280
+ Object.defineProperty(exports, "runtimeStatePolicyFor", {
53281
+ enumerable: true,
53282
+ get: function() {
53283
+ return runtimeStatePolicyFor;
53284
+ }
53285
+ });
52470
53286
  Object.defineProperty(exports, "scopeKey", {
52471
53287
  enumerable: true,
52472
53288
  get: function() {
@@ -52563,7 +53379,7 @@ var require_alerts_addon = __commonJS({
52563
53379
  [Symbol.toStringTag]: { value: "Module" }
52564
53380
  });
52565
53381
  require_chunk_Cek0wNdY();
52566
- var require_dist10 = require_dist_DiScsb8j();
53382
+ var require_dist10 = require_dist_D4nuaPdd();
52567
53383
  function selectExpired(alerts, cutoffMs) {
52568
53384
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
52569
53385
  }
@@ -53376,7 +54192,7 @@ var require_console_logging = __commonJS({
53376
54192
  [Symbol.toStringTag]: { value: "Module" }
53377
54193
  });
53378
54194
  require_chunk_Cek0wNdY();
53379
- var require_dist10 = require_dist_DiScsb8j();
54195
+ var require_dist10 = require_dist_D4nuaPdd();
53380
54196
  var require_formatter = require_formatter_DqAKDlvN();
53381
54197
  var LEVEL_RANK = {
53382
54198
  debug: 0,
@@ -53470,7 +54286,7 @@ var require_core_blocks_addon = __commonJS({
53470
54286
  "use strict";
53471
54287
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
53472
54288
  var require_chunk = require_chunk_Cek0wNdY();
53473
- var require_dist10 = require_dist_DiScsb8j();
54289
+ var require_dist10 = require_dist_D4nuaPdd();
53474
54290
  var node_crypto = __require("crypto");
53475
54291
  var node_fs_promises = __require("fs/promises");
53476
54292
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -56168,7 +56984,7 @@ var require_device_manager_addon = __commonJS({
56168
56984
  [Symbol.toStringTag]: { value: "Module" }
56169
56985
  });
56170
56986
  require_chunk_Cek0wNdY();
56171
- var require_dist10 = require_dist_DiScsb8j();
56987
+ var require_dist10 = require_dist_D4nuaPdd();
56172
56988
  var node_crypto = __require("crypto");
56173
56989
  var _camstack_types_node = require_node();
56174
56990
  var JOB_HISTORY = 20;
@@ -59364,8 +60180,60 @@ var require_device_manager_addon = __commonJS({
59364
60180
  return current;
59365
60181
  };
59366
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
+ }
59367
60234
  var DeviceStateMirror = class DeviceStateMirror2 {
59368
60235
  ctx;
60236
+ policyFor;
59369
60237
  /**
59370
60238
  * Hub-side mirror of every device's cap-keyed runtime state.
59371
60239
  * Key: deviceId. Value: per-cap slice map. Empty by default —
@@ -59379,8 +60247,24 @@ var require_device_manager_addon = __commonJS({
59379
60247
  */
59380
60248
  runtimeStateDebounce = /* @__PURE__ */ new Map();
59381
60249
  static RUNTIME_STATE_DEBOUNCE_MS = 1e3;
59382
- 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) {
59383
60266
  this.ctx = ctx;
60267
+ this.policyFor = policyFor;
59384
60268
  }
59385
60269
  /**
59386
60270
  * Single-cap mirror update — diff against the current mirror,
@@ -59407,13 +60291,23 @@ var require_device_manager_addon = __commonJS({
59407
60291
  return true;
59408
60292
  }
59409
60293
  /**
59410
- * Debounced disk writer. Coalesces frequent writes (motion phase
59411
- * transitions, battery pushes) into one `writeDeviceRuntimeState`
59412
- * per `RUNTIME_STATE_DEBOUNCE_MS` window. Reads the per-device
59413
- * 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
59414
60307
  * always the latest state — no risk of writing a stale snapshot.
59415
60308
  */
59416
- scheduleRuntimeStateDiskWrite(deviceId, settings) {
60309
+ scheduleRuntimeStateDiskWrite(deviceId, settings, changedCap) {
60310
+ if (this.policyFor(changedCap).durability !== "restored") return;
59417
60311
  let slot = this.runtimeStateDebounce.get(deviceId);
59418
60312
  if (!slot) {
59419
60313
  slot = {
@@ -59425,10 +60319,21 @@ var require_device_manager_addon = __commonJS({
59425
60319
  if (slot.timer) return;
59426
60320
  slot.timer = setTimeout(() => {
59427
60321
  slot.timer = null;
59428
- 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);
59429
60326
  const write = (async () => {
59430
60327
  try {
59431
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
+ });
59432
60337
  } catch (err) {
59433
60338
  this.ctx.logger.warn("writeDeviceRuntimeState failed", {
59434
60339
  tags: { deviceId },
@@ -59442,6 +60347,22 @@ var require_device_manager_addon = __commonJS({
59442
60347
  }, DeviceStateMirror2.RUNTIME_STATE_DEBOUNCE_MS);
59443
60348
  }
59444
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
+ /**
59445
60366
  * One-shot mirror seed used by `loadRuntimeState` at boot so the
59446
60367
  * hub knows about every persisted slice without waiting for the
59447
60368
  * first `setCapSlice` call. No events emitted — this is
@@ -59461,6 +60382,7 @@ var require_device_manager_addon = __commonJS({
59461
60382
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) continue;
59462
60383
  perCap.set(capName, { ...raw });
59463
60384
  }
60385
+ this.lastPersisted.set(deviceId, persistableBlob(this.snapshotForDevice(deviceId), this.policyFor));
59464
60386
  }
59465
60387
  /**
59466
60388
  * The hub mirror's `feature-probe.lastProbedAt` is a PER-SESSION liveness
@@ -59574,15 +60496,15 @@ var require_device_manager_addon = __commonJS({
59574
60496
  if (slot.timer) {
59575
60497
  clearTimeout(slot.timer);
59576
60498
  slot.timer = null;
59577
- if (settings) {
59578
- const blob = this.snapshotForDevice(deviceId);
59579
- pending.push(settings.writeDeviceRuntimeState(deviceId, blob).catch((err) => {
59580
- this.ctx.logger.warn("shutdown writeDeviceRuntimeState failed", {
59581
- tags: { deviceId },
59582
- meta: { error: err instanceof Error ? err.message : String(err) }
59583
- });
59584
- }));
59585
- }
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
+ }));
59586
60508
  }
59587
60509
  if (slot.inFlight) pending.push(slot.inFlight);
59588
60510
  }
@@ -60009,7 +60931,7 @@ var require_device_manager_addon = __commonJS({
60009
60931
  setCapSlice: async (input) => {
60010
60932
  const { deviceId, capName, slice } = input;
60011
60933
  if (!await resolvePersistedById(deviceId)) throw new Error(`[device-manager] setCapSlice: unknown device id=${deviceId}`);
60012
- if (stateMirror.applySingleCapUpdate(deviceId, capName, slice)) stateMirror.scheduleRuntimeStateDiskWrite(deviceId, settings);
60934
+ if (stateMirror.applySingleCapUpdate(deviceId, capName, slice)) stateMirror.scheduleRuntimeStateDiskWrite(deviceId, settings, capName);
60013
60935
  }
60014
60936
  }
60015
60937
  }];
@@ -60050,7 +60972,7 @@ var require_hub_forwarder = __commonJS({
60050
60972
  [Symbol.toStringTag]: { value: "Module" }
60051
60973
  });
60052
60974
  require_chunk_Cek0wNdY();
60053
- var require_dist10 = require_dist_DiScsb8j();
60975
+ var require_dist10 = require_dist_D4nuaPdd();
60054
60976
  var require_formatter = require_formatter_DqAKDlvN();
60055
60977
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
60056
60978
  var HubForwarderDestination = class {
@@ -60187,7 +61109,7 @@ var require_liveness_monitor_addon = __commonJS({
60187
61109
  "use strict";
60188
61110
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60189
61111
  require_chunk_Cek0wNdY();
60190
- var require_dist10 = require_dist_DiScsb8j();
61112
+ var require_dist10 = require_dist_D4nuaPdd();
60191
61113
  var NO_DEVICES = "liveness:no-devices";
60192
61114
  var ALL_OFFLINE = "liveness:all-devices-offline";
60193
61115
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -60377,7 +61299,7 @@ var require_local_auth_addon = __commonJS({
60377
61299
  [Symbol.toStringTag]: { value: "Module" }
60378
61300
  });
60379
61301
  var require_chunk = require_chunk_Cek0wNdY();
60380
- var require_dist10 = require_dist_DiScsb8j();
61302
+ var require_dist10 = require_dist_D4nuaPdd();
60381
61303
  var node_crypto = __require("crypto");
60382
61304
  node_crypto = require_chunk.__toESM(node_crypto);
60383
61305
  var crypto$1 = __require("crypto");
@@ -68061,7 +68983,7 @@ var require_loki_logging = __commonJS({
68061
68983
  [Symbol.toStringTag]: { value: "Module" }
68062
68984
  });
68063
68985
  require_chunk_Cek0wNdY();
68064
- var require_dist10 = require_dist_DiScsb8j();
68986
+ var require_dist10 = require_dist_D4nuaPdd();
68065
68987
  function sanitizeLabelName(raw) {
68066
68988
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
68067
68989
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -68626,7 +69548,7 @@ var require_native_metrics_addon = __commonJS({
68626
69548
  [Symbol.toStringTag]: { value: "Module" }
68627
69549
  });
68628
69550
  var require_chunk = require_chunk_Cek0wNdY();
68629
- var require_dist10 = require_dist_DiScsb8j();
69551
+ var require_dist10 = require_dist_D4nuaPdd();
68630
69552
  var node_child_process = __require("child_process");
68631
69553
  var node_util = __require("util");
68632
69554
  var node_os = __require("os");
@@ -69568,7 +70490,7 @@ var require_filesystem_storage_addon = __commonJS({
69568
70490
  [Symbol.toStringTag]: { value: "Module" }
69569
70491
  });
69570
70492
  var require_chunk = require_chunk_Cek0wNdY();
69571
- var require_dist10 = require_dist_DiScsb8j();
70493
+ var require_dist10 = require_dist_D4nuaPdd();
69572
70494
  var node_crypto = __require("crypto");
69573
70495
  var node_fs_promises = __require("fs/promises");
69574
70496
  var node_path = __require("path");
@@ -70684,7 +71606,7 @@ var require_sqlite_settings_addon = __commonJS({
70684
71606
  [Symbol.toStringTag]: { value: "Module" }
70685
71607
  });
70686
71608
  var require_chunk = require_chunk_Cek0wNdY();
70687
- var require_dist10 = require_dist_DiScsb8j();
71609
+ var require_dist10 = require_dist_D4nuaPdd();
70688
71610
  var node_crypto = __require("crypto");
70689
71611
  var node_fs = __require("fs");
70690
71612
  var node_module = __require("module");
@@ -71356,6 +72278,42 @@ var require_sqlite_settings_addon = __commonJS({
71356
72278
  const where = this.prefixWhere(`${addonId}:${deviceId}.`);
71357
72279
  this.getDb().prepare(`DELETE FROM "addon-device-settings" WHERE ${where.sql}`).run(...where.params);
71358
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
+ }
71359
72317
  /** Seed system-settings with runtime defaults (first boot) */
71360
72318
  async seedDefaults() {
71361
72319
  this.requireDeclared("system-settings");
@@ -72199,7 +73157,7 @@ var require_storage_orchestrator_addon = __commonJS({
72199
73157
  [Symbol.toStringTag]: { value: "Module" }
72200
73158
  });
72201
73159
  var require_chunk = require_chunk_Cek0wNdY();
72202
- var require_dist10 = require_dist_DiScsb8j();
73160
+ var require_dist10 = require_dist_D4nuaPdd();
72203
73161
  var node_crypto = __require("crypto");
72204
73162
  var node_fs_promises = __require("fs/promises");
72205
73163
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -74040,7 +74998,7 @@ var require_system_config_addon = __commonJS({
74040
74998
  [Symbol.toStringTag]: { value: "Module" }
74041
74999
  });
74042
75000
  require_chunk_Cek0wNdY();
74043
- var require_dist10 = require_dist_DiScsb8j();
75001
+ var require_dist10 = require_dist_D4nuaPdd();
74044
75002
  var SECTION_TITLES = {
74045
75003
  server: "Server",
74046
75004
  auth: "Authentication"
@@ -92101,7 +93059,7 @@ var require_winston_logging = __commonJS({
92101
93059
  [Symbol.toStringTag]: { value: "Module" }
92102
93060
  });
92103
93061
  var require_chunk = require_chunk_Cek0wNdY();
92104
- var require_dist10 = require_dist_DiScsb8j();
93062
+ var require_dist10 = require_dist_D4nuaPdd();
92105
93063
  var require_formatter = require_formatter_DqAKDlvN();
92106
93064
  var node_path = __require("path");
92107
93065
  node_path = require_chunk.__toESM(node_path);
@@ -93252,9 +94210,9 @@ var require_event_category_DxZbWydC = __commonJS({
93252
94210
  }
93253
94211
  });
93254
94212
 
93255
- // ../types/dist/sleep-BszXLEvP.js
93256
- var require_sleep_BszXLEvP = __commonJS({
93257
- "../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) {
93258
94216
  "use strict";
93259
94217
  var require_event_category = require_event_category_DxZbWydC();
93260
94218
  var zod = require_zod();
@@ -94862,6 +95820,7 @@ var require_sleep_BszXLEvP = __commonJS({
94862
95820
  const value = Reflect.get(context, CAP_NODE_PIN_CONTEXT_KEY);
94863
95821
  return typeof value === "string" ? value : void 0;
94864
95822
  }
95823
+ var DEFAULT_RUNTIME_STATE_DURABILITY = "session";
94865
95824
  function resolveCapMount(def) {
94866
95825
  if (def.mount) return def.mount;
94867
95826
  if (def.deviceNative === true) return { kind: "device-native" };
@@ -96247,6 +97206,12 @@ var require_sleep_BszXLEvP = __commonJS({
96247
97206
  return DATAPLANE_SECRET_HEADER;
96248
97207
  }
96249
97208
  });
97209
+ Object.defineProperty(exports, "DEFAULT_RUNTIME_STATE_DURABILITY", {
97210
+ enumerable: true,
97211
+ get: function() {
97212
+ return DEFAULT_RUNTIME_STATE_DURABILITY;
97213
+ }
97214
+ });
96250
97215
  Object.defineProperty(exports, "DEVICE_SCOPED_CAPS", {
96251
97216
  enumerable: true,
96252
97217
  get: function() {
@@ -96670,7 +97635,7 @@ var require_addon = __commonJS({
96670
97635
  "use strict";
96671
97636
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
96672
97637
  var require_event_category = require_event_category_DxZbWydC();
96673
- var require_sleep = require_sleep_BszXLEvP();
97638
+ var require_sleep = require_sleep_Dh1EJSqF();
96674
97639
  var require_err_msg = require_err_msg_COpsHMw2();
96675
97640
  var COLLECTION_ARRAY_METHODS = Object.freeze({
96676
97641
  "addon-pages-source": ["listPages"],
@@ -114114,7 +115079,7 @@ var require_dist3 = __commonJS({
114114
115079
  "use strict";
114115
115080
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
114116
115081
  var require_chunk = require_chunk_Cek0wNdY();
114117
- var require_dist10 = require_dist_DiScsb8j();
115082
+ var require_dist10 = require_dist_D4nuaPdd();
114118
115083
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
114119
115084
  require_alerts();
114120
115085
  var require_formatter = require_formatter_DqAKDlvN();
@@ -117666,23 +118631,15 @@ var require_dist3 = __commonJS({
117666
118631
  * installs from the admin UI go through `install()` instead, which
117667
118632
  * defaults to npm in production.
117668
118633
  *
117669
- * @param packages optional custom package list (default: REQUIRED_PACKAGES)
117670
- * @param pins — optional `name exact version` map. A named package is
117671
- * fetched at THAT version instead of the registry's `latest`.
118634
+ * There is no version argument and there is deliberately no pin: the system is
118635
+ * addons-agnostic and `latest` is the whole contract at first boot (operator,
118636
+ * 2026-08-13). A copy already under the addon root is never replaced either —
118637
+ * bootstrap is seed-only, and overwriting a deployed bundle would be an
118638
+ * un-deploy ([D90](../../../../docs/decisions/adr-0090.md)).
117672
118639
  *
117673
- * Preparation for [D45](../../../../docs/decisions/adr-0045.md) task 32: the
117674
- * image seed answers first today, so `latest` has never been the version a
117675
- * node actually got. Once the image stops baking an addon tree under `/opt`
117676
- * this call IS the answer, and it has to be the version the running closure was
117677
- * published with — otherwise a node fetches latest-of-everything and its
117678
- * addon set depends on the minute it first booted. Passing nothing keeps
117679
- * exactly today's behaviour, and `launcher.ts` passes nothing yet.
117680
- *
117681
- * A pin never triggers a re-install: bootstrap stays seed-only, so a copy
117682
- * already under the addon root is left alone whatever its version. Replacing
117683
- * it would be an un-deploy ([D90](../../../../docs/decisions/adr-0090.md)).
118640
+ * @param packages optional custom package list (default: REQUIRED_PACKAGES)
117684
118641
  */
117685
- async ensureRequiredPackages(packages, pins) {
118642
+ async ensureRequiredPackages(packages) {
117686
118643
  const pkgList = packages ?? AddonInstaller2.REQUIRED_PACKAGES;
117687
118644
  ensureDir(this.addonsDir);
117688
118645
  const isLocal = this.installSource !== "npm" && this.workspaceDir != null;
@@ -117710,7 +118667,7 @@ var require_dist3 = __commonJS({
117710
118667
  }
117711
118668
  this.logger.info(`${packageName} \u2014 not found locally, trying npm`);
117712
118669
  }
117713
- await this.installFromNpm(packageName, pins?.[packageName]);
118670
+ await this.installFromNpm(packageName);
117714
118671
  } catch (err) {
117715
118672
  const msg = require_dist10.errMsg(err);
117716
118673
  if (packageName === "@camstack/system") throw new Error(`Required package ${packageName} failed to install: ${msg}`, { cause: err });
@@ -122833,6 +123790,32 @@ var require_dist3 = __commonJS({
122833
123790
  if (this.settingsStore === null) return;
122834
123791
  this.settingsStore.clearAddonDevice(addonId, deviceId);
122835
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
+ }
122836
123819
  createSettingsView(addonId) {
122837
123820
  const cm = this;
122838
123821
  return {
@@ -122861,13 +123844,13 @@ var require_dist3 = __commonJS({
122861
123844
  cm.clearAddonDevice(addonId, String(deviceId));
122862
123845
  },
122863
123846
  async readDeviceRuntimeState(deviceId) {
122864
- return cm.getAddonDevice("__device-state", String(deviceId));
123847
+ return cm.getDeviceRuntimeState(String(deviceId));
122865
123848
  },
122866
123849
  async writeDeviceRuntimeState(deviceId, data) {
122867
- cm.setAddonDevice("__device-state", String(deviceId), data);
123850
+ cm.setDeviceRuntimeState(String(deviceId), data);
122868
123851
  },
122869
123852
  async clearDeviceRuntimeState(deviceId) {
122870
- cm.clearAddonDevice("__device-state", String(deviceId));
123853
+ cm.clearDeviceRuntimeState(String(deviceId));
122871
123854
  },
122872
123855
  async getSection(section) {
122873
123856
  return cm.getSection(section);
@@ -233137,6 +234120,271 @@ var require_agent_http = __commonJS({
233137
234120
  }
233138
234121
  });
233139
234122
 
234123
+ // ../../server/backend/dist/single-copy-cleanup.js
234124
+ var require_single_copy_cleanup = __commonJS({
234125
+ "../../server/backend/dist/single-copy-cleanup.js"(exports) {
234126
+ "use strict";
234127
+ Object.defineProperty(exports, "__esModule", { value: true });
234128
+ exports.CLOSURE_PROVIDED_PACKAGES = void 0;
234129
+ exports.planSingleCopyCleanup = planSingleCopyCleanup;
234130
+ exports.executeSingleCopyCleanup = executeSingleCopyCleanup;
234131
+ exports.formatCleanupPlan = formatCleanupPlan;
234132
+ exports.discoverRedundantCopies = discoverRedundantCopies;
234133
+ exports.isCleanupEnabled = isCleanupEnabled;
234134
+ var IMAGE_PREFIX = "/opt/";
234135
+ function planSingleCopyCleanup(input) {
234136
+ const blockedReason = nodeWideBlock(input);
234137
+ if (blockedReason !== null) {
234138
+ return {
234139
+ remove: [],
234140
+ keep: input.candidates.map((candidate) => ({ candidate, reason: blockedReason })),
234141
+ blocked: true,
234142
+ blockedReason
234143
+ };
234144
+ }
234145
+ const remove = [];
234146
+ const keep = [];
234147
+ for (const candidate of input.candidates) {
234148
+ const refusal = perCandidateRefusal(candidate, input);
234149
+ if (refusal === null)
234150
+ remove.push(candidate);
234151
+ else
234152
+ keep.push({ candidate, reason: refusal });
234153
+ }
234154
+ return { remove, keep, blocked: false, blockedReason: null };
234155
+ }
234156
+ function nodeWideBlock(input) {
234157
+ if (!input.bootHealthy) {
234158
+ return "this boot is not confirmed healthy \u2014 the redundant copies are the recovery path and stay";
234159
+ }
234160
+ if (input.activeRoot === null) {
234161
+ return "baked mode: there is no active closure, so the node is running FROM the fallback tree";
234162
+ }
234163
+ if (input.closureResolvedFrom === null) {
234164
+ return "the process resolved no @camstack/system at all \u2014 nothing here is safe to remove";
234165
+ }
234166
+ if (!isInside(input.closureResolvedFrom, input.activeRoot)) {
234167
+ return `@camstack/system resolved from ${input.closureResolvedFrom}, which is OUTSIDE the active closure \u2014 the copy being removed could be the one in memory`;
234168
+ }
234169
+ return null;
234170
+ }
234171
+ function perCandidateRefusal(candidate, input) {
234172
+ if (input.activeRoot !== null && isInside(candidate.path, input.activeRoot)) {
234173
+ return "inside the active closure \u2014 this is the copy that runs, not a redundant one";
234174
+ }
234175
+ if (candidate.path.startsWith(IMAGE_PREFIX)) {
234176
+ return "an image tree \u2014 the operator keeps it as the first-boot and fallback source";
234177
+ }
234178
+ const onNodePath = input.nodePathEntries.some((entry) => entry === candidate.path || isInside(entry, candidate.path));
234179
+ if (onNodePath) {
234180
+ return "still on NODE_PATH \u2014 a live resolution path, whatever the boot mode says";
234181
+ }
234182
+ return null;
234183
+ }
234184
+ function isInside(child, parent) {
234185
+ const normalise = (p2) => p2.endsWith("/") ? p2.slice(0, -1) : p2;
234186
+ const c = normalise(child);
234187
+ const p = normalise(parent);
234188
+ return c === p || c.startsWith(`${p}/`);
234189
+ }
234190
+ async function executeSingleCopyCleanup(plan, fs, log) {
234191
+ if (plan.blocked) {
234192
+ log(`single-copy cleanup REFUSED \u2014 ${plan.blockedReason ?? "blocked"}`);
234193
+ return { removed: [], failed: [], refused: true };
234194
+ }
234195
+ const removed = [];
234196
+ const failed = [];
234197
+ for (const candidate of plan.remove) {
234198
+ if (!fs.exists(candidate.path))
234199
+ continue;
234200
+ const label = `${candidate.pkg ?? candidate.kind} ${candidate.version ?? "<unknown version>"}`;
234201
+ const aside = `${candidate.path}.removing-${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}`;
234202
+ try {
234203
+ await fs.rename(candidate.path, aside);
234204
+ } catch (err) {
234205
+ failed.push(candidate.path);
234206
+ log(`single-copy cleanup \u2014 could NOT remove ${candidate.path} (${label}): ${errMsg(err)}`);
234207
+ continue;
234208
+ }
234209
+ removed.push(candidate.path);
234210
+ log(`single-copy cleanup \u2014 removed ${candidate.path} (${label})`);
234211
+ try {
234212
+ await fs.remove(aside);
234213
+ } catch (err) {
234214
+ log(`single-copy cleanup \u2014 ${aside} still on disk (held open?): ${errMsg(err)}`);
234215
+ }
234216
+ }
234217
+ return { removed, failed, refused: false };
234218
+ }
234219
+ function errMsg(err) {
234220
+ return err instanceof Error ? err.message : String(err);
234221
+ }
234222
+ function formatCleanupPlan(plan) {
234223
+ const lines = [];
234224
+ if (plan.blocked) {
234225
+ lines.push(`single-copy cleanup blocked \u2014 ${plan.blockedReason ?? "unknown reason"}; ${plan.keep.length} copy(ies) kept`);
234226
+ } else {
234227
+ lines.push(`single-copy cleanup \u2014 ${plan.remove.length} copy(ies) to remove`);
234228
+ }
234229
+ for (const c of plan.remove) {
234230
+ lines.push(` remove ${c.path} \u2014 ${c.pkg ?? c.kind} ${c.version ?? "<unknown version>"}`);
234231
+ }
234232
+ for (const k of plan.keep) {
234233
+ lines.push(` keep ${k.candidate.path} \u2014 ${k.candidate.pkg ?? k.candidate.kind} ${k.candidate.version ?? "<unknown version>"}: ${k.reason}`);
234234
+ }
234235
+ return lines;
234236
+ }
234237
+ exports.CLOSURE_PROVIDED_PACKAGES = [
234238
+ "@camstack/system",
234239
+ "@camstack/types",
234240
+ "@camstack/sdk",
234241
+ "@camstack/shm-ring",
234242
+ "@camstack/ui-library"
234243
+ ];
234244
+ function discoverRedundantCopies(input, fs) {
234245
+ const found = [];
234246
+ const legacyFramework = `${trimSlash(input.dataDir)}/framework`;
234247
+ if (fs.exists(legacyFramework)) {
234248
+ found.push({
234249
+ kind: "legacy-framework-tree",
234250
+ path: legacyFramework,
234251
+ pkg: null,
234252
+ version: fs.readVersion(`${legacyFramework}/node_modules/@camstack/system`)
234253
+ });
234254
+ }
234255
+ for (const pkg of exports.CLOSURE_PROVIDED_PACKAGES) {
234256
+ if (!input.closureProvides(pkg))
234257
+ continue;
234258
+ const dir = `${trimSlash(input.addonRoot)}/${pkg}`;
234259
+ if (!fs.exists(dir))
234260
+ continue;
234261
+ found.push({
234262
+ kind: "addon-root-closure-copy",
234263
+ path: dir,
234264
+ pkg,
234265
+ version: fs.readVersion(dir)
234266
+ });
234267
+ }
234268
+ return found;
234269
+ }
234270
+ function trimSlash(p) {
234271
+ return p.endsWith("/") ? p.slice(0, -1) : p;
234272
+ }
234273
+ function isCleanupEnabled(env) {
234274
+ const raw = env["CAMSTACK_SINGLE_COPY_CLEANUP"]?.trim().toLowerCase();
234275
+ return raw !== "off" && raw !== "0" && raw !== "false";
234276
+ }
234277
+ }
234278
+ });
234279
+
234280
+ // ../../server/backend/dist/single-copy-cleanup-runner.js
234281
+ var require_single_copy_cleanup_runner = __commonJS({
234282
+ "../../server/backend/dist/single-copy-cleanup-runner.js"(exports) {
234283
+ "use strict";
234284
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
234285
+ if (k2 === void 0) k2 = k;
234286
+ var desc = Object.getOwnPropertyDescriptor(m, k);
234287
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
234288
+ desc = { enumerable: true, get: function() {
234289
+ return m[k];
234290
+ } };
234291
+ }
234292
+ Object.defineProperty(o, k2, desc);
234293
+ }) : (function(o, m, k, k2) {
234294
+ if (k2 === void 0) k2 = k;
234295
+ o[k2] = m[k];
234296
+ }));
234297
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
234298
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
234299
+ }) : function(o, v) {
234300
+ o["default"] = v;
234301
+ });
234302
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
234303
+ var ownKeys = function(o) {
234304
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
234305
+ var ar = [];
234306
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
234307
+ return ar;
234308
+ };
234309
+ return ownKeys(o);
234310
+ };
234311
+ return function(mod) {
234312
+ if (mod && mod.__esModule) return mod;
234313
+ var result = {};
234314
+ if (mod != null) {
234315
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
234316
+ }
234317
+ __setModuleDefault(result, mod);
234318
+ return result;
234319
+ };
234320
+ })();
234321
+ Object.defineProperty(exports, "__esModule", { value: true });
234322
+ exports.runSingleCopyCleanup = runSingleCopyCleanup;
234323
+ var fs = __importStar(__require("fs"));
234324
+ var path = __importStar(__require("path"));
234325
+ var single_copy_cleanup_js_1 = require_single_copy_cleanup();
234326
+ var EMPTY_RESULT = { removed: [], failed: [], refused: true };
234327
+ async function runSingleCopyCleanup(opts) {
234328
+ try {
234329
+ if (!(0, single_copy_cleanup_js_1.isCleanupEnabled)(process.env)) {
234330
+ opts.log("single-copy cleanup disabled by CAMSTACK_SINGLE_COPY_CLEANUP \u2014 nothing removed");
234331
+ return EMPTY_RESULT;
234332
+ }
234333
+ const candidates = (0, single_copy_cleanup_js_1.discoverRedundantCopies)({
234334
+ dataDir: opts.dataDir,
234335
+ addonRoot: opts.addonRoot,
234336
+ closureProvides: (pkg) => {
234337
+ try {
234338
+ __require.resolve(`${pkg}/package.json`);
234339
+ return true;
234340
+ } catch {
234341
+ return false;
234342
+ }
234343
+ }
234344
+ }, {
234345
+ exists: (p) => fs.existsSync(p),
234346
+ readVersion: (dir) => readVersion(dir)
234347
+ });
234348
+ const plan = (0, single_copy_cleanup_js_1.planSingleCopyCleanup)({
234349
+ activeRoot: process.env["CAMSTACK_SERVER_ACTIVE_ROOT"] ?? null,
234350
+ closureResolvedFrom: resolveSystemPath(),
234351
+ bootHealthy: true,
234352
+ nodePathEntries: (process.env["NODE_PATH"] ?? "").split(process.platform === "win32" ? ";" : ":").map((entry) => entry.trim()).filter((entry) => entry.length > 0),
234353
+ candidates
234354
+ });
234355
+ for (const line of (0, single_copy_cleanup_js_1.formatCleanupPlan)(plan))
234356
+ opts.log(line);
234357
+ return await (0, single_copy_cleanup_js_1.executeSingleCopyCleanup)(plan, {
234358
+ exists: (p) => fs.existsSync(p),
234359
+ rename: (from, to) => fs.renameSync(from, to),
234360
+ remove: async (p) => {
234361
+ await fs.promises.rm(p, { recursive: true, force: true });
234362
+ }
234363
+ }, opts.log);
234364
+ } catch (err) {
234365
+ opts.log(`single-copy cleanup failed: ${err instanceof Error ? err.message : String(err)} \u2014 nothing was removed`);
234366
+ return EMPTY_RESULT;
234367
+ }
234368
+ }
234369
+ function resolveSystemPath() {
234370
+ try {
234371
+ return __require.resolve("@camstack/system/package.json");
234372
+ } catch {
234373
+ return null;
234374
+ }
234375
+ }
234376
+ function readVersion(packageDir) {
234377
+ try {
234378
+ const raw = JSON.parse(fs.readFileSync(path.join(packageDir, "package.json"), "utf-8"));
234379
+ const version = typeof raw === "object" && raw !== null ? raw.version : void 0;
234380
+ return typeof version === "string" ? version : null;
234381
+ } catch {
234382
+ return null;
234383
+ }
234384
+ }
234385
+ }
234386
+ });
234387
+
233140
234388
  // ../types/dist/enums.js
233141
234389
  var require_enums = __commonJS({
233142
234390
  "../types/dist/enums.js"(exports) {
@@ -233163,7 +234411,7 @@ var require_dist9 = __commonJS({
233163
234411
  "use strict";
233164
234412
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
233165
234413
  var require_event_category = require_event_category_DxZbWydC();
233166
- var require_sleep = require_sleep_BszXLEvP();
234414
+ var require_sleep = require_sleep_Dh1EJSqF();
233167
234415
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
233168
234416
  var require_enums2 = require_enums();
233169
234417
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -238254,7 +239502,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
238254
239502
  low: zod.z.string().optional()
238255
239503
  }),
238256
239504
  lastChangedAt: zod.z.number()
238257
- })
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"
238258
239513
  };
238259
239514
  function isVoidInput(schema) {
238260
239515
  const def2 = schema._def;
@@ -239033,6 +240288,13 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
239033
240288
  kind: "poll"
239034
240289
  },
239035
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",
239036
240298
  methods: {
239037
240299
  /**
239038
240300
  * Snapshot of the current `discovered` list. Returns the
@@ -241261,7 +242523,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241261
242523
  * else — see `notification-center/action-token.ts` for what that does and
241262
242524
  * does not buy.
241263
242525
  */
241264
- 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()
241265
242543
  });
241266
242544
  var NotificationSchema = zod.z.object({
241267
242545
  body: zod.z.string(),
@@ -242397,7 +243675,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242397
243675
  * full slice; renders an arm button per `availableModes` entry and
242398
243676
  * a PIN field iff `requiresCode === true`.
242399
243677
  */
242400
- 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"]
242401
243689
  };
242402
243690
  var MaskPointSchema = zod.z.object({
242403
243691
  x: zod.z.number(),
@@ -242501,6 +243789,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242501
243789
  packageNames: zod.z.array(zod.z.string().min(1)).min(1).optional()
242502
243790
  });
242503
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
+ ];
242504
243798
  var NcScheduleWindowSchema = zod.z.object({
242505
243799
  /** Days of week the window STARTS on (0 = Sunday … 6 = Saturday). */
242506
243800
  days: zod.z.array(zod.z.number().int().min(0).max(6)).min(1),
@@ -242758,15 +244052,15 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242758
244052
  * (an `immediate` rule naming an `audio-*` class, one notification per
242759
244053
  * classified sample) stays exactly as it was for rules that already use it.
242760
244054
  *
242761
- * NOT in {@link NC_CONDITION_CATALOG} yet, and that is the sequencing rule
242762
- * rather than an oversight: the viewer mirrors the descriptor enums BY HAND
242763
- * (`camstack/src/data/notification-center.ts`, guarded by
242764
- * `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
242765
244059
  * condition fields it does not know when a rule is saved from the phone.
242766
244060
  * Publishing an editor for a condition the app cannot round-trip is how an
242767
- * operator loses a rule's conditions by opening it — so the descriptor, the
242768
- * admin widget and the viewer mirror land together (P2 + P3), and only then
242769
- * 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.
242770
244064
  */
242771
244065
  audio: NcAudioConditionSchema.optional()
242772
244066
  });
@@ -242936,6 +244230,30 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242936
244230
  */
242937
244231
  snoozeAllowGlobal: zod.z.boolean().optional(),
242938
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
+ /**
242939
244257
  * Devices this rule ACTUATES — arm the alarm, open a gate, turn on a light.
242940
244258
  *
242941
244259
  * This is what makes the rule set the alarm's trigger set without the alarm
@@ -243025,6 +244343,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243025
244343
  "device",
243026
244344
  "package",
243027
244345
  "occupancy",
244346
+ "audio",
243028
244347
  "system"
243029
244348
  ]),
243030
244349
  label: zod.z.string(),
@@ -243043,6 +244362,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243043
244362
  "crossingSelect",
243044
244363
  "polygonDraw",
243045
244364
  "occupancy",
244365
+ "audio",
243046
244366
  "deviceState",
243047
244367
  "systemEvent"
243048
244368
  ]),
@@ -243436,6 +244756,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243436
244756
  phase: "P1",
243437
244757
  description: "ZoneAnalytics occupancy edge (optionally zone/class-scoped): count crosses the threshold and holds for sustainSeconds. Fail-closed on a missing snapshot."
243438
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
+ },
243439
244769
  {
243440
244770
  id: "customZones",
243441
244771
  group: "zones",
@@ -243534,7 +244864,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243534
244864
  "device",
243535
244865
  "all"
243536
244866
  ]);
243537
- var NC_SNOOZE_MAX_MINUTES = 1440;
243538
244867
  var NcSnoozeInputSchema = zod.z.object({
243539
244868
  scope: NcSnoozeScopeSchema,
243540
244869
  /** Required when `scope: 'rule'` — a scoped snooze with no id matches
@@ -243542,6 +244871,19 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243542
244871
  ruleId: zod.z.string().optional(),
243543
244872
  /** Required when `scope: 'device'`. */
243544
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(),
243545
244887
  durationMinutes: zod.z.number().int().min(1).max(NC_SNOOZE_MAX_MINUTES),
243546
244888
  /**
243547
244889
  * Silence this for EVERY recipient, not just the caller. Permission is
@@ -243566,6 +244908,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243566
244908
  scope: NcSnoozeScopeSchema,
243567
244909
  ruleId: zod.z.string().optional(),
243568
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(),
243569
244915
  startedAt: zod.z.number(),
243570
244916
  /** Exclusive: at exactly this instant the snooze is over. Expiry is a
243571
244917
  * COMPARISON, not a job — no sweeper can leave the operator silenced. */
@@ -245838,7 +247184,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
245838
247184
  * handle. Slice shape is `{ zones: Zone[] }` so future extensions
245839
247185
  * (e.g. zone groupings) can sit alongside the polygon list.
245840
247186
  */
245841
- 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"
245842
247195
  };
245843
247196
  var NativeCropBboxSchema = zod.z.object({
245844
247197
  x: zod.z.number(),
@@ -249285,7 +250638,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249285
250638
  schema: AirQualitySensorStatusSchema,
249286
250639
  kind: "push"
249287
250640
  },
249288
- 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"]
249289
250652
  };
249290
250653
  var AmbientLightSensorStatusSchema = zod.z.object({
249291
250654
  /** Current illuminance in lux (lx). */
@@ -249313,7 +250676,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249313
250676
  schema: AmbientLightSensorStatusSchema,
249314
250677
  kind: "push"
249315
250678
  },
249316
- 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"]
249317
250690
  };
249318
250691
  var AudioClassSummarySchema = zod.z.object({
249319
250692
  className: zod.z.string(),
@@ -249403,7 +250776,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249403
250776
  }), AudioMetricsHistorySchema)
249404
250777
  },
249405
250778
  /** Reactive runtime-state mirror — live `device.state.audioMetrics.value`. */
249406
- 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"
249407
250787
  };
249408
250788
  var AutomationControlStatusSchema = zod.z.object({
249409
250789
  /** Whether the automation is currently enabled. Disabled automations
@@ -249454,7 +250834,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249454
250834
  * reads `enabled` (toggle) + `isRunning` (spinner) + `lastError`
249455
250835
  * (badge) directly.
249456
250836
  */
249457
- 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"
249458
250845
  };
249459
250846
  var BatteryStatusSchema = zod.z.object({
249460
250847
  /** 0..100 inclusive. Firmware-reported. */
@@ -249555,7 +250942,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249555
250942
  * via `device.runtimeState.getCapState('battery')` regardless of
249556
250943
  * the underlying driver.
249557
250944
  */
249558
- 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"]
249559
250956
  };
249560
250957
  var BinaryStatusSchema = zod.z.object({
249561
250958
  on: zod.z.boolean(),
@@ -249573,7 +250970,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249573
250970
  schema: BinaryStatusSchema,
249574
250971
  kind: "push"
249575
250972
  },
249576
- 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"]
249577
250984
  };
249578
250985
  var BrightnessStatusSchema = zod.z.object({
249579
250986
  /** Current level as 0..100 inclusive. Firmware-reported. */
@@ -249615,7 +251022,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249615
251022
  * by the kernel. Read via `device.state.brightness.value` so UI
249616
251023
  * sliders surface the current level without polling the provider.
249617
251024
  */
249618
- 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"
249619
251033
  };
249620
251034
  var buttonCapability = {
249621
251035
  name: "button",
@@ -249695,7 +251109,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249695
251109
  schema: CarbonMonoxideStatusSchema,
249696
251110
  kind: "push"
249697
251111
  },
249698
- 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"]
249699
251123
  };
249700
251124
  var HvacModeSchema = zod.z.enum([
249701
251125
  "off",
@@ -249824,7 +251248,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249824
251248
  * the full slice via `device.state.climate-control.value` and refresh
249825
251249
  * on every push without re-querying the provider.
249826
251250
  */
249827
- 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"
249828
251259
  };
249829
251260
  var RgbTripletSchema = zod.z.object({
249830
251261
  r: zod.z.number().int().min(0).max(255),
@@ -249911,7 +251342,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249911
251342
  * kernel. Read via `device.state.color.value` so UI pickers surface
249912
251343
  * the current chromaticity without polling the provider.
249913
251344
  */
249914
- 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"
249915
251353
  };
249916
251354
  var CONNECTION_TEST_TIMEOUT_MS = 2e4;
249917
251355
  var ConnectionTestOutcomeSchema = zod.z.discriminatedUnion("outcome", [
@@ -249966,7 +251404,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
249966
251404
  schema: ConnectivityStatusSchema,
249967
251405
  kind: "push"
249968
251406
  },
249969
- 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"]
249970
251418
  };
249971
251419
  var ConsumableItemSchema = zod.z.object({
249972
251420
  /** Stable id, e.g. 'main-brush'. */
@@ -250033,7 +251481,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250033
251481
  }
250034
251482
  }
250035
251483
  },
250036
- 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"
250037
251492
  };
250038
251493
  var ContactStatusSchema = zod.z.object({
250039
251494
  /** True when the entry is open; false when closed. */
@@ -250052,7 +251507,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250052
251507
  schema: ContactStatusSchema,
250053
251508
  kind: "push"
250054
251509
  },
250055
- 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"]
250056
251521
  };
250057
251522
  var ControlKindSchema = zod.z.enum([
250058
251523
  "numeric",
@@ -250139,7 +251604,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250139
251604
  * dropdown / text field / date picker) read the slice's discriminant
250140
251605
  * and value directly without polling the provider.
250141
251606
  */
250142
- 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"
250143
251615
  };
250144
251616
  var CoverStateSchema = zod.z.enum([
250145
251617
  "open",
@@ -250201,7 +251673,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250201
251673
  * Runtime-state slice — mirrored by the kernel. UI controls watch
250202
251674
  * the slice for live position changes during a move.
250203
251675
  */
250204
- 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"]
250205
251687
  };
250206
251688
  var DayNightModeSchema = zod.z.enum([
250207
251689
  "auto",
@@ -250262,7 +251744,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250262
251744
  schema: DayNightStatusSchema,
250263
251745
  kind: "poll"
250264
251746
  },
250265
- 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"]
250266
251758
  };
250267
251759
  var DeviceStatusSchema = zod.z.object({
250268
251760
  /**
@@ -250295,7 +251787,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250295
251787
  schema: DeviceStatusSchema,
250296
251788
  kind: "push"
250297
251789
  },
250298
- 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"]
250299
251801
  };
250300
251802
  var DoorbellStatusSchema = zod.z.object({
250301
251803
  /** Ms epoch of the last press. null = never observed since this provider started. */
@@ -250335,7 +251837,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250335
251837
  * `device.state.doorbell.value`. UIs can show "last ring 5m ago"
250336
251838
  * without subscribing.
250337
251839
  */
250338
- 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"
250339
251848
  };
250340
251849
  var EnumSensorDateTimeFormatSchema = zod.z.enum([
250341
251850
  "date",
@@ -250365,7 +251874,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250365
251874
  schema: EnumSensorStatusSchema,
250366
251875
  kind: "push"
250367
251876
  },
250368
- 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"]
250369
251888
  };
250370
251889
  var EventFireSchema = zod.z.object({
250371
251890
  deviceId: zod.z.number(),
@@ -250391,7 +251910,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250391
251910
  schema: EventEmitterStatusSchema,
250392
251911
  kind: "push"
250393
251912
  },
250394
- 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"
250395
251921
  };
250396
251922
  var EventItemSchema = zod.z.object({
250397
251923
  id: zod.z.string(),
@@ -250690,7 +252216,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250690
252216
  * Runtime-state slice — mirrored by the kernel. UI fan speed
250691
252217
  * sliders read `percentage` for live updates.
250692
252218
  */
250693
- 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"
250694
252227
  };
250695
252228
  var FeatureProbeStatusSchema = zod.z.object({
250696
252229
  /**
@@ -250743,7 +252276,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250743
252276
  schema: FeatureProbeStatusSchema,
250744
252277
  kind: "push"
250745
252278
  },
250746
- 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"
250747
252287
  };
250748
252288
  var FloodStatusSchema = zod.z.object({
250749
252289
  /** True when leak is currently detected. */
@@ -250762,7 +252302,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250762
252302
  schema: FloodStatusSchema,
250763
252303
  kind: "push"
250764
252304
  },
250765
- 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"]
250766
252316
  };
250767
252317
  var GasStatusSchema = zod.z.object({
250768
252318
  detected: zod.z.boolean(),
@@ -250780,7 +252330,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250780
252330
  schema: GasStatusSchema,
250781
252331
  kind: "push"
250782
252332
  },
250783
- 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"]
250784
252344
  };
250785
252345
  var HumidifierStatusSchema = zod.z.object({
250786
252346
  /** Whether the humidifier is currently on. */
@@ -250841,7 +252401,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250841
252401
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
250842
252402
  * slice for live humidity / mode changes.
250843
252403
  */
250844
- 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"
250845
252412
  };
250846
252413
  var HumiditySensorStatusSchema = zod.z.object({
250847
252414
  /** Current relative humidity, 0..100. */
@@ -250869,7 +252436,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250869
252436
  schema: HumiditySensorStatusSchema,
250870
252437
  kind: "push"
250871
252438
  },
250872
- 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"]
250873
252450
  };
250874
252451
  var ImageStatusSchema = zod.z.object({
250875
252452
  /** Absolute signed URL the browser loads directly. Null when the
@@ -250893,7 +252470,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250893
252470
  * Runtime-state slice — mirrored by the kernel. The UI reads `url`
250894
252471
  * directly and renders the still image.
250895
252472
  */
250896
- 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"
250897
252481
  };
250898
252482
  var ImageRotateSchema = zod.z.enum([
250899
252483
  "0",
@@ -250994,7 +252578,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
250994
252578
  schema: ImageSettingsStatusSchema,
250995
252579
  kind: "poll"
250996
252580
  },
250997
- 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"]
250998
252592
  };
250999
252593
  var IntegrationWithStateSchema = zod.z.object({
251000
252594
  id: zod.z.string(),
@@ -251325,7 +252919,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251325
252919
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
251326
252920
  * slice for live activity + battery changes.
251327
252921
  */
251328
- 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"
251329
252930
  };
251330
252931
  var InterfaceKindEnum = zod.z.enum([
251331
252932
  "lan",
@@ -251574,7 +253175,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251574
253175
  * read `state` and disable themselves during `locking`/`unlocking`
251575
253176
  * transitions.
251576
253177
  */
251577
- 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"]
251578
253189
  };
251579
253190
  var MediaPlayerStateSchema = zod.z.enum([
251580
253191
  "off",
@@ -251724,7 +253335,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251724
253335
  * full slice for live now-playing, volume, and progress updates
251725
253336
  * without polling.
251726
253337
  */
251727
- 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"
251728
253346
  };
251729
253347
  var MeshEndpointSchema = zod.z.object({
251730
253348
  /** Stable identifier within the provider (e.g. `mesh-ipv4`, `magicdns`, `funnel`). */
@@ -252008,7 +253626,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252008
253626
  * `device.state.motion.value`. Reads never invoke the provider, so
252009
253627
  * UIs and other addons can poll the cached state safely.
252010
253628
  */
252011
- 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"
252012
253637
  };
252013
253638
  var MotionTriggerStatusSchema = zod.z.object({
252014
253639
  enabled: zod.z.boolean(),
@@ -252045,7 +253670,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252045
253670
  schema: MotionTriggerStatusSchema,
252046
253671
  kind: "command-driven"
252047
253672
  },
252048
- 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"
252049
253681
  };
252050
253682
  var MotionZoneRegionSchema = zod.z.object({
252051
253683
  id: zod.z.number(),
@@ -252100,7 +253732,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252100
253732
  schema: MotionZoneStatusSchema,
252101
253733
  kind: "poll"
252102
253734
  },
252103
- 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"]
252104
253746
  };
252105
253747
  var NativeObjectClassEnum = zod.z.enum([
252106
253748
  "person",
@@ -252163,7 +253805,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252163
253805
  schema: NativeObjectDetectionStatusSchema,
252164
253806
  kind: "push"
252165
253807
  },
252166
- 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"]
252167
253819
  };
252168
253820
  var StreamNetworkStatsSchema = zod.z.object({
252169
253821
  nominalBitrateKbps: zod.z.number(),
@@ -252486,7 +254138,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252486
254138
  * form reads `supports` to gate optional fields; history pane reads
252487
254139
  * `lastSentAt` / `lastError` / `queueDepth`.
252488
254140
  */
252489
- 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"
252490
254149
  };
252491
254150
  var NumericSensorStatusSchema = zod.z.object({
252492
254151
  value: zod.z.number(),
@@ -252512,7 +254171,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252512
254171
  schema: NumericSensorStatusSchema,
252513
254172
  kind: "push"
252514
254173
  },
252515
- 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"]
252516
254185
  };
252517
254186
  var OsdOverlayKindEnum = zod.z.enum([
252518
254187
  "text",
@@ -252937,7 +254606,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252937
254606
  * the full slice via `device.state.petFeeder.value` and refresh on
252938
254607
  * every poll without re-querying the provider.
252939
254608
  */
252940
- 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"
252941
254617
  };
252942
254618
  var VehicleSchema = zod.z.object({
252943
254619
  id: zod.z.string(),
@@ -253271,7 +254947,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253271
254947
  schema: PowerMeterStatusSchema,
253272
254948
  kind: "push"
253273
254949
  },
253274
- 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"]
253275
254961
  };
253276
254962
  var GpsLocationSchema = zod.z.object({
253277
254963
  /** Latitude in decimal degrees, -90..90. */
@@ -253312,7 +254998,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253312
254998
  * the map pin is rendered (use `DeviceFeature.PresenceGps` for the
253313
254999
  * pre-fetch fast-path check).
253314
255000
  */
253315
- 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"]
253316
255012
  };
253317
255013
  var PressureSensorStatusSchema = zod.z.object({
253318
255014
  /** Current pressure in hPa. */
@@ -253340,7 +255036,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253340
255036
  schema: PressureSensorStatusSchema,
253341
255037
  kind: "push"
253342
255038
  },
253343
- 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"]
253344
255050
  };
253345
255051
  var PrivacyMaskShapeSchema = zod.z.discriminatedUnion("kind", [MaskRectShapeSchema, MaskPolygonShapeSchema]);
253346
255052
  var PrivacyMaskRegionSchema = zod.z.object({
@@ -253439,7 +255145,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253439
255145
  schema: PrivacyMaskStatusSchema,
253440
255146
  kind: "poll"
253441
255147
  },
253442
- 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"]
253443
255159
  };
253444
255160
  function summarisePrivacyAudio(profiles) {
253445
255161
  if (profiles.length === 0) return null;
@@ -253629,7 +255345,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
253629
255345
  * fetch / cache / fallback logic out of the four cap methods —
253630
255346
  * they become trampolines over `runtimeState`.
253631
255347
  */
253632
- 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"
253633
255356
  };
253634
255357
  var rebootCapability = {
253635
255358
  name: "reboot",
@@ -254299,7 +256022,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254299
256022
  schema: SceneMonitorStatusSchema,
254300
256023
  kind: "push"
254301
256024
  },
254302
- 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"
254303
256033
  };
254304
256034
  var ZoneRuleModeEnum = zod.z.enum(["include", "exclude"]);
254305
256035
  var ZoneRuleSchema = zod.z.object({
@@ -254401,7 +256131,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254401
256131
  * `isRunning` to render a spinner during execution and surfaces
254402
256132
  * `lastError` / `lastRunSuccess` in the recent-runs panel.
254403
256133
  */
254404
- 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"
254405
256142
  };
254406
256143
  var SmokeStatusSchema = zod.z.object({
254407
256144
  detected: zod.z.boolean(),
@@ -254419,7 +256156,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254419
256156
  schema: SmokeStatusSchema,
254420
256157
  kind: "push"
254421
256158
  },
254422
- 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"]
254423
256170
  };
254424
256171
  var CamStreamDescriptorSchema = zod.z.object({
254425
256172
  camStreamId: zod.z.string().min(1),
@@ -254570,7 +256317,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254570
256317
  schema: StreamParamsStatusSchema,
254571
256318
  kind: "poll"
254572
256319
  },
254573
- 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"]
254574
256331
  };
254575
256332
  var STREAM_PROFILE_META = [
254576
256333
  {
@@ -254841,6 +256598,16 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254841
256598
  * not need to re-query the provider after a setState mutation.
254842
256599
  */
254843
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"],
254844
256611
  settings: { bindings: [{
254845
256612
  kind: "scalar",
254846
256613
  statusPath: "on",
@@ -254913,7 +256680,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254913
256680
  schema: TamperStatusSchema,
254914
256681
  kind: "push"
254915
256682
  },
254916
- 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"]
254917
256694
  };
254918
256695
  var TemperatureSensorStatusSchema = zod.z.object({
254919
256696
  /** Current temperature in Celsius. */
@@ -254942,7 +256719,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254942
256719
  schema: TemperatureSensorStatusSchema,
254943
256720
  kind: "push"
254944
256721
  },
254945
- 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"]
254946
256733
  };
254947
256734
  var ToastSchema = zod.z.object({
254948
256735
  title: zod.z.string(),
@@ -254993,7 +256780,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
254993
256780
  schema: UpdateStatusSchema,
254994
256781
  kind: "poll"
254995
256782
  },
254996
- 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"
254997
256791
  };
254998
256792
  var UserSummarySchema = zod.z.object({
254999
256793
  id: zod.z.string(),
@@ -255327,7 +257121,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255327
257121
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
255328
257122
  * slice for live state + battery + fan-speed changes.
255329
257123
  */
255330
- 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"
255331
257132
  };
255332
257133
  var ValveStateSchema = zod.z.enum([
255333
257134
  "open",
@@ -255380,7 +257181,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255380
257181
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
255381
257182
  * slice for live position changes during a move.
255382
257183
  */
255383
- 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"
255384
257192
  };
255385
257193
  var VibrationStatusSchema = zod.z.object({
255386
257194
  detected: zod.z.boolean(),
@@ -255398,7 +257206,17 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255398
257206
  schema: VibrationStatusSchema,
255399
257207
  kind: "push"
255400
257208
  },
255401
- 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"]
255402
257220
  };
255403
257221
  var WaterHeaterStatusSchema = zod.z.object({
255404
257222
  /** Current measured temperature. Null when not reported. */
@@ -255458,7 +257276,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255458
257276
  * Runtime-state slice — mirrored by the kernel. UI controls watch the
255459
257277
  * slice for live temperature / mode / away changes.
255460
257278
  */
255461
- 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"
255462
257287
  };
255463
257288
  var WeatherStatusSchema = zod.z.object({
255464
257289
  /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
@@ -255498,7 +257323,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255498
257323
  * Runtime-state slice — mirrored by the kernel. The UI reads the
255499
257324
  * current conditions directly from the slice on each weather push.
255500
257325
  */
255501
- 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"
255502
257334
  };
255503
257335
  var PerScopeBreakdownSchema = zod.z.object({
255504
257336
  /** Total tracked objects in this scope (frame / zone / unzoned). */
@@ -255607,7 +257439,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255607
257439
  * automatically; the explicit `getCurrentSnapshot` cap method is
255608
257440
  * still useful for one-off polls without a subscription.
255609
257441
  */
255610
- 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"
255611
257450
  };
255612
257451
  var ZoneRuleStageEnum = zod.z.enum([
255613
257452
  "motion",
@@ -255659,7 +257498,14 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
255659
257498
  motion: zod.z.array(ZoneRuleSchema).readonly(),
255660
257499
  detection: zod.z.array(ZoneRuleSchema).readonly(),
255661
257500
  package: zod.z.array(ZoneRuleSchema).readonly()
255662
- })
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"
255663
257509
  };
255664
257510
  var PIPELINE_FLOW_CAPABILITY_NAMES = [
255665
257511
  "decoder",
@@ -264909,6 +266755,301 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
264909
266755
  for (const [capName, capKind] of Object.entries(CAP_PROVIDER_KIND_MAP)) if (capKind === kind) out.push(capName);
264910
266756
  return out;
264911
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
+ ];
264912
267053
  var SCOPE_PRESETS = [
264913
267054
  {
264914
267055
  id: "camera-viewer",
@@ -265552,6 +267693,96 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
265552
267693
  }
265553
267694
  };
265554
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
+ }
265555
267786
  var CLASS_CONDITION_IDS = /* @__PURE__ */ new Set(["classes", "classesExclude"]);
265556
267787
  var LABEL_CONDITION_IDS = /* @__PURE__ */ new Set(["sensorKinds"]);
265557
267788
  function toOption(entry) {
@@ -266086,32 +268317,40 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
266086
268317
  };
266087
268318
  }
266088
268319
  var NATIVE_LEASE_SECTION_ID = "native-lease";
266089
- var NATIVE_LEASE_TTL_KEY = "nativeLeaseTtlMs";
268320
+ var NATIVE_LEASE_HOLD_KEY = "nativeLeaseHoldFrames";
266090
268321
  var NATIVE_LEASE_BUDGET_KEY = "nativeLeaseBudgetMb";
266091
268322
  var NATIVE_LEASE_ACTIVITY_KEY = "nativeLeaseActivityMs";
266092
268323
  var NATIVE_LEASE_ADMISSION_KEY = "nativeLeaseAdmission";
268324
+ var NATIVE_LEASE_TILE_BUDGET_KEY = "nativeTileBudgetMb";
266093
268325
  var NativeLeaseAdmissionSchema = zod.z.enum(["all", "inferred"]);
266094
268326
  var NativeLeaseSettingsSchema = zod.z.object({
266095
268327
  /**
266096
- * How long a retained native frame is served before it counts as a miss.
268328
+ * How many delivered frames the worker HOLDS at once, waiting for each one's
268329
+ * detection result.
266097
268330
  *
266098
- * Must cover the FULL late-crop horizon: detection inference + the
266099
- * cross-process inference-result hop to hub post-analysis + tracking + the
266100
- * tRPC crop round-trip back. Below ~500 ms the busiest cameras' subject crops
266101
- * outrun it and fall back to the ≤640 detection frame; above ~3 s the resident
266102
- * RAM per busy camera grows linearly with no measured hit-rate gain.
268331
+ * This replaced a TTL on 2026-08-13, and the replacement is the whole point:
268332
+ * a time window was never related to the event the pixels were waiting for.
268333
+ * A held frame now lives from delivery until the runner has its `FrameResult`
268334
+ * at which moment the runner cuts the subject tiles it actually wanted and
268335
+ * releases the frame. The bound exists only so a runner that stops answering
268336
+ * cannot pin RAM: above it the OLDEST held frame is dropped and counted.
268337
+ *
268338
+ * Sizing: the steady state is `inferenceLatency × deliveredFps`, measured at
268339
+ * 40-160 ms × ≤25 fps = 1-4 frames. The default leaves headroom for a hiccup
268340
+ * without ever approaching the old resident set (43 frames × 24.9 MB at 4K).
268341
+ * Raising it does not buy hit rate — it buys tolerance for a slow runner, and
268342
+ * `holdOverflow` on the metrics line is what says you need it.
266103
268343
  */
266104
- ttlMs: zod.z.number().int().min(250).max(1e4),
268344
+ holdFrames: zod.z.number().int().min(1).max(64),
266105
268345
  /**
266106
268346
  * Hard per-decode-worker RAM ceiling for retained native frames, in MB.
266107
268347
  *
266108
- * Intended as a SAFETY ceiling with the TTL as the effective cap — but check
266109
- * which one is actually binding before reasoning from that. At the shipped
266110
- * 1024 MB and a 2 800 ms TTL, a 4K camera hits the CEILING first (~43 frames
266111
- * at ~24 MB each) and the TTL never gets to expire anything; `leaseMb` /
266112
- * `leaseFrames` on the metrics line say which. When the ceiling binds, a
266113
- * change that admits fewer frames buys retention WINDOW at constant RAM
266114
- * rather than giving RAM back — lower this knob if RAM is what you wanted.
268348
+ * Since 2026-08-13 this is a SAFETY ceiling and nothing else: `holdFrames`
268349
+ * is what decides how much is held, and the ceiling is the number above which
268350
+ * something is wrong. Before that it was the effective cap at 1024 MB with
268351
+ * a 2 800 ms TTL a 4K camera sat pinned at `leaseMb:1020, leaseFrames:43`
268352
+ * with the TTL expiring nothing, which is exactly the confusion the hold
268353
+ * removes. `leaseMb` / `leaseFrames` still say what is resident.
266115
268354
  * `0` DISABLES the lease entirely and falls the worker back to the tiny
266116
268355
  * leak-prone GPU surface ring (~85% crop miss; that is what the lease exists
266117
268356
  * to replace).
@@ -266137,19 +268376,37 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
266137
268376
  * there is the signal that some caller names frames outside the inference set
266138
268377
  * and that this must go back to `all`.
266139
268378
  */
266140
- admission: NativeLeaseAdmissionSchema
268379
+ admission: NativeLeaseAdmissionSchema,
268380
+ /**
268381
+ * RAM ceiling per decode worker, in MB, for the SUBJECT TILES — the
268382
+ * compressed native crops the worker cuts at the moment a frame's detection
268383
+ * result arrives, and keeps long after the frame itself is freed.
268384
+ *
268385
+ * This is the knob that replaced the old retention window, and it buys about
268386
+ * three orders of magnitude more of it: a tile is one subject at native
268387
+ * resolution, JPEG-encoded (~60-120 KB on a 4K person), against ~24.9 MB for
268388
+ * the frame it was cut from. A frame on which nothing was detected costs
268389
+ * nothing at all, which is the real change — the old lease paid per FRAME and
268390
+ * was interrogated per SUBJECT.
268391
+ *
268392
+ * `0` DISABLES tiles, leaving only the hold window and the ≤640 RAM
268393
+ * fallback — i.e. the pre-2026-08-13 miss profile. Set it there only to
268394
+ * reproduce that.
268395
+ */
268396
+ tileBudgetMb: zod.z.number().int().min(0).max(1024)
266141
268397
  });
266142
268398
  var DEFAULT_NATIVE_LEASE_SETTINGS = {
266143
- ttlMs: 1200,
268399
+ holdFrames: 8,
266144
268400
  budgetMb: 1024,
266145
268401
  activityMs: 15e3,
268402
+ tileBudgetMb: 64,
266146
268403
  admission: "inferred"
266147
268404
  };
266148
- var NATIVE_LEASE_TTL_FIELD = {
266149
- min: 250,
266150
- max: 1e4,
266151
- step: 50,
266152
- default: DEFAULT_NATIVE_LEASE_SETTINGS.ttlMs
268405
+ var NATIVE_LEASE_HOLD_FIELD = {
268406
+ min: 1,
268407
+ max: 64,
268408
+ step: 1,
268409
+ default: DEFAULT_NATIVE_LEASE_SETTINGS.holdFrames
266153
268410
  };
266154
268411
  var NATIVE_LEASE_BUDGET_FIELD = {
266155
268412
  min: 0,
@@ -266163,6 +268420,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
266163
268420
  step: 1e3,
266164
268421
  default: DEFAULT_NATIVE_LEASE_SETTINGS.activityMs
266165
268422
  };
268423
+ var NATIVE_LEASE_TILE_BUDGET_FIELD = {
268424
+ min: 0,
268425
+ max: 1024,
268426
+ step: 16,
268427
+ default: DEFAULT_NATIVE_LEASE_SETTINGS.tileBudgetMb
268428
+ };
266166
268429
  var NATIVE_LEASE_ADMISSION_FIELD = {
266167
268430
  options: [{
266168
268431
  value: "all",
@@ -266184,25 +268447,28 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
266184
268447
  return parsed.data === DEFAULT_NATIVE_LEASE_SETTINGS.admission ? null : parsed.data;
266185
268448
  }
266186
268449
  function readNativeLeaseOverride(config) {
266187
- const ttlMs = readKnob("ttlMs", config[NATIVE_LEASE_TTL_KEY]);
268450
+ const holdFrames = readKnob("holdFrames", config[NATIVE_LEASE_HOLD_KEY]);
266188
268451
  const budgetMb = readKnob("budgetMb", config[NATIVE_LEASE_BUDGET_KEY]);
266189
268452
  const activityMs = readKnob("activityMs", config[NATIVE_LEASE_ACTIVITY_KEY]);
266190
268453
  const admission = readAdmissionKnob(config[NATIVE_LEASE_ADMISSION_KEY]);
268454
+ const tileBudgetMb = readKnob("tileBudgetMb", config[NATIVE_LEASE_TILE_BUDGET_KEY]);
266191
268455
  return {
266192
- ...ttlMs === null ? {} : { ttlMs },
268456
+ ...holdFrames === null ? {} : { holdFrames },
266193
268457
  ...budgetMb === null ? {} : { budgetMb },
266194
268458
  ...activityMs === null ? {} : { activityMs },
266195
- ...admission === null ? {} : { admission }
268459
+ ...admission === null ? {} : { admission },
268460
+ ...tileBudgetMb === null ? {} : { tileBudgetMb }
266196
268461
  };
266197
268462
  }
266198
268463
  function isHydratedField(entry) {
266199
268464
  return typeof entry === "object" && entry !== null && "key" in entry;
266200
268465
  }
266201
268466
  var LEASE_KEYS = [
266202
- NATIVE_LEASE_TTL_KEY,
268467
+ NATIVE_LEASE_HOLD_KEY,
266203
268468
  NATIVE_LEASE_BUDGET_KEY,
266204
268469
  NATIVE_LEASE_ACTIVITY_KEY,
266205
- NATIVE_LEASE_ADMISSION_KEY
268470
+ NATIVE_LEASE_ADMISSION_KEY,
268471
+ NATIVE_LEASE_TILE_BUDGET_KEY
266206
268472
  ];
266207
268473
  function pickNativeLeaseOverride(view) {
266208
268474
  if (view === null) return {};
@@ -267369,6 +269635,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267369
269635
  exports.DEFAULT_FEATURES = DEFAULT_FEATURES;
267370
269636
  exports.DEFAULT_NATIVE_LEASE_SETTINGS = DEFAULT_NATIVE_LEASE_SETTINGS;
267371
269637
  exports.DEFAULT_RETENTION = DEFAULT_RETENTION;
269638
+ exports.DEFAULT_RUNTIME_STATE_DURABILITY = require_sleep.DEFAULT_RUNTIME_STATE_DURABILITY;
267372
269639
  exports.DEFAULT_SCRUB_THUMBNAIL_PRESET = DEFAULT_SCRUB_THUMBNAIL_PRESET;
267373
269640
  exports.DEFAULT_TIMELAPSE_PREVIEW_TEXT = DEFAULT_TIMELAPSE_PREVIEW_TEXT;
267374
269641
  exports.DETAIL_CROP_PADDING_FIELD = DETAIL_CROP_PADDING_FIELD;
@@ -267611,11 +269878,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267611
269878
  exports.NATIVE_LEASE_ADMISSION_KEY = NATIVE_LEASE_ADMISSION_KEY;
267612
269879
  exports.NATIVE_LEASE_BUDGET_FIELD = NATIVE_LEASE_BUDGET_FIELD;
267613
269880
  exports.NATIVE_LEASE_BUDGET_KEY = NATIVE_LEASE_BUDGET_KEY;
269881
+ exports.NATIVE_LEASE_HOLD_FIELD = NATIVE_LEASE_HOLD_FIELD;
269882
+ exports.NATIVE_LEASE_HOLD_KEY = NATIVE_LEASE_HOLD_KEY;
267614
269883
  exports.NATIVE_LEASE_SECTION_ID = NATIVE_LEASE_SECTION_ID;
267615
- exports.NATIVE_LEASE_TTL_FIELD = NATIVE_LEASE_TTL_FIELD;
267616
- exports.NATIVE_LEASE_TTL_KEY = NATIVE_LEASE_TTL_KEY;
269884
+ exports.NATIVE_LEASE_TILE_BUDGET_FIELD = NATIVE_LEASE_TILE_BUDGET_FIELD;
269885
+ exports.NATIVE_LEASE_TILE_BUDGET_KEY = NATIVE_LEASE_TILE_BUDGET_KEY;
267617
269886
  exports.NC_ALARM_SYSTEM_EVENT_KINDS = NC_ALARM_SYSTEM_EVENT_KINDS;
267618
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;
267619
269897
  exports.NC_AUTHORABLE_SYSTEM_EVENT_KINDS = NC_AUTHORABLE_SYSTEM_EVENT_KINDS;
267620
269898
  exports.NC_BASE_CONDITION_KEYS = NC_BASE_CONDITION_KEYS;
267621
269899
  exports.NC_CONDITION_CATALOG = NC_CONDITION_CATALOG;
@@ -267623,6 +269901,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267623
269901
  exports.NC_CONFIRM_DEFAULT_TIMEOUT_MS = NC_CONFIRM_DEFAULT_TIMEOUT_MS;
267624
269902
  exports.NC_CONFIRM_MAX_TIMEOUT_MS = NC_CONFIRM_MAX_TIMEOUT_MS;
267625
269903
  exports.NC_CONFIRM_MIN_TIMEOUT_MS = NC_CONFIRM_MIN_TIMEOUT_MS;
269904
+ exports.NC_DEFAULT_SNOOZE_MINUTES = NC_DEFAULT_SNOOZE_MINUTES;
267626
269905
  exports.NC_HISTORY_LIMIT_DEFAULT = NC_HISTORY_LIMIT_DEFAULT;
267627
269906
  exports.NC_HISTORY_LIMIT_MAX = NC_HISTORY_LIMIT_MAX;
267628
269907
  exports.NC_MAX_PER_TRACK_IMMEDIATE = NC_MAX_PER_TRACK_IMMEDIATE;
@@ -267769,7 +270048,9 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
267769
270048
  exports.RECOGNITION_TYPES = RECOGNITION_TYPES;
267770
270049
  exports.RECORDING_EXPORT_MAX_READ_BYTES = RECORDING_EXPORT_MAX_READ_BYTES;
267771
270050
  exports.RESERVED_BINDING_NAMES = RESERVED_BINDING_NAMES;
270051
+ exports.RESTORED_CAP_NAMES = RESTORED_CAP_NAMES;
267772
270052
  exports.RUNTIME_DEFAULTS = RUNTIME_DEFAULTS;
270053
+ exports.RUNTIME_STATE_POLICY = RUNTIME_STATE_POLICY;
267773
270054
  exports.RUNTIME_TO_FORMAT = RUNTIME_TO_FORMAT;
267774
270055
  exports.RawStateResultSchema = require_sleep.RawStateResultSchema;
267775
270056
  exports.ReadGopBytesResultSchema = ReadGopBytesResultSchema;
@@ -268041,7 +270322,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268041
270322
  exports.audioAnalysisCapability = audioAnalysisCapability;
268042
270323
  exports.audioAnalyzerCapability = audioAnalyzerCapability;
268043
270324
  exports.audioCodecCapability = audioCodecCapability;
270325
+ exports.audioIsFailClosed = audioIsFailClosed;
270326
+ exports.audioLabelChoices = audioLabelChoices;
268044
270327
  exports.audioMetricsCapability = audioMetricsCapability;
270328
+ exports.audioOrDefaults = audioOrDefaults;
268045
270329
  exports.audioPlanFromEncodeProfile = require_canonical_hash.audioPlanFromEncodeProfile;
268046
270330
  exports.authProviderCapability = authProviderCapability;
268047
270331
  exports.autoAssignProfiles = autoAssignProfiles;
@@ -268179,6 +270463,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268179
270463
  exports.invocationFromEncodeProfile = require_canonical_hash.invocationFromEncodeProfile;
268180
270464
  exports.isAgentOnlyPlacement = isAgentOnlyPlacement;
268181
270465
  exports.isArrayOutputSchema = isArrayOutputSchema;
270466
+ exports.isAudioLabelSelected = isAudioLabelSelected;
268182
270467
  exports.isBaseConditionKey = isBaseConditionKey;
268183
270468
  exports.isCollectionArrayMethod = isCollectionArrayMethod;
268184
270469
  exports.isDeployableToAgent = isDeployableToAgent;
@@ -268188,6 +270473,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268188
270473
  exports.isIsolatedBuiltin = isIsolatedBuiltin;
268189
270474
  exports.isNode = isNode;
268190
270475
  exports.isObjectInput = isObjectInput;
270476
+ exports.isRestoredCap = isRestoredCap;
268191
270477
  exports.isSameAddonId = isSameAddonId;
268192
270478
  exports.isScheduleActive = isScheduleActive;
268193
270479
  exports.isSoftwareDecode = require_canonical_hash.isSoftwareDecode;
@@ -268236,6 +270522,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268236
270522
  exports.nodePin = require_sleep.nodePin;
268237
270523
  exports.nodesCapability = nodesCapability;
268238
270524
  exports.normalizeAddonInitResult = require_sleep.normalizeAddonInitResult;
270525
+ exports.normalizeAudioLabel = normalizeAudioLabel;
268239
270526
  exports.normalizeUnit = normalizeUnit;
268240
270527
  exports.notificationOutputCapability = notificationOutputCapability;
268241
270528
  exports.notificationRulesCapability = notificationRulesCapability;
@@ -268252,6 +270539,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268252
270539
  exports.parseJsonUnknown = require_sleep.parseJsonUnknown;
268253
270540
  exports.parseProfileBrokerId = require_sleep.parseProfileBrokerId;
268254
270541
  exports.parseStreamParamsFormPatch = parseStreamParamsFormPatch;
270542
+ exports.patchAudio = patchAudio;
268255
270543
  exports.petFeederCapability = petFeederCapability;
268256
270544
  exports.pickAccessoryControl = pickAccessoryControl;
268257
270545
  exports.pickDetailCropConvention = pickDetailCropConvention;
@@ -268304,6 +270592,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268304
270592
  exports.resolveVariantModelId = resolveVariantModelId;
268305
270593
  exports.runInferenceStep = runInferenceStep;
268306
270594
  exports.runtimeDevices = runtimeDevices;
270595
+ exports.runtimeStatePolicyFor = runtimeStatePolicyFor;
268307
270596
  exports.sceneMonitorCapability = sceneMonitorCapability;
268308
270597
  exports.scopeKey = require_sleep.scopeKey;
268309
270598
  exports.scopesAllowAddon = scopesAllowAddon;
@@ -268350,6 +270639,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
268350
270639
  exports.toNodeId = require_sleep.toNodeId;
268351
270640
  exports.toStreamSourceEntry = toStreamSourceEntry;
268352
270641
  exports.toastCapability = toastCapability;
270642
+ exports.toggleAudioLabel = toggleAudioLabel;
268353
270643
  exports.tokenize = tokenize;
268354
270644
  exports.transcodeBody = transcodeBody;
268355
270645
  exports.tryConvertUnit = tryConvertUnit;
@@ -362585,6 +364875,7 @@ var require_main2 = __commonJS({
362585
364875
  var fs = __importStar(__require("fs"));
362586
364876
  var path = __importStar(__require("path"));
362587
364877
  var agent_http_js_1 = require_agent_http();
364878
+ var single_copy_cleanup_runner_js_1 = require_single_copy_cleanup_runner();
362588
364879
  var derive_hub_url_js_1 = require_derive_hub_url();
362589
364880
  var system_1 = require_dist3();
362590
364881
  var types_1 = require_dist9();
@@ -362805,6 +365096,11 @@ var require_main2 = __commonJS({
362805
365096
  } catch (err) {
362806
365097
  consoleLogger.warn(`agent root boot confirmation failed: ${err instanceof Error ? err.message : String(err)}`);
362807
365098
  }
365099
+ void (0, single_copy_cleanup_runner_js_1.runSingleCopyCleanup)({
365100
+ dataDir: config.dataDir,
365101
+ addonRoot: config.addonsDir,
365102
+ log: (line) => consoleLogger.info(line)
365103
+ });
362808
365104
  };
362809
365105
  const loggerFactory = (addonId) => agentLogManager.createLogger().withTags({ addonId });
362810
365106
  const agentServiceSchema = (0, agent_service_js_1.createAgentService)({
@@ -389476,7 +391772,10 @@ var require_addon_registry_service = __commonJS({
389476
391772
  "setDevice",
389477
391773
  "getAddonDevice",
389478
391774
  "setAddonDevice",
389479
- "clearAddonDevice"
391775
+ "clearAddonDevice",
391776
+ "getDeviceRuntimeState",
391777
+ "setDeviceRuntimeState",
391778
+ "clearDeviceRuntimeState"
389480
391779
  ];
389481
391780
  for (const key of required) {
389482
391781
  if (typeof Reflect.get(backend, key) !== "function")
@@ -396933,6 +399232,18 @@ var require_cluster_node_history_store = __commonJS({
396933
399232
  descriptor: DescriptorSchema
396934
399233
  });
396935
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
+ }
396936
399247
  var ClusterNodeHistoryStore = class _ClusterNodeHistoryStore {
396937
399248
  resolveSettingsStore;
396938
399249
  logger;
@@ -396974,6 +399285,19 @@ var require_cluster_node_history_store = __commonJS({
396974
399285
  * Upsert a node's descriptor + addon roster while it is ONLINE, stamping
396975
399286
  * `lastActive = Date.now()`. Called on each `listNodes()` refresh for every
396976
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.
396977
399301
  */
396978
399302
  async snapshot(descriptor) {
396979
399303
  const store = await this.ensureDeclared();
@@ -396982,13 +399306,13 @@ var require_cluster_node_history_store = __commonJS({
396982
399306
  try {
396983
399307
  const existing = await this.readRow(store, descriptor.id);
396984
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;
396985
399312
  await store.set({
396986
399313
  collection: COLLECTION,
396987
399314
  key: descriptor.id,
396988
- value: {
396989
- lastActive: Date.now(),
396990
- descriptor: packages === void 0 ? descriptor : { ...descriptor, packages }
396991
- }
399315
+ value: { lastActive: Date.now(), descriptor: next }
396992
399316
  });
396993
399317
  } catch (err) {
396994
399318
  this.logger.warn("snapshot upsert failed (best-effort)", {
@@ -396997,6 +399321,16 @@ var require_cluster_node_history_store = __commonJS({
396997
399321
  });
396998
399322
  }
396999
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
+ }
397000
399334
  /** Read one node's row, validated. `null` when absent or malformed. */
397001
399335
  async readRow(store, nodeId) {
397002
399336
  const raw = await store.get({ collection: COLLECTION, key: nodeId });
@@ -397832,6 +400166,7 @@ var require_main4 = __commonJS({
397832
400166
  var fs = __importStar(__require("fs"));
397833
400167
  var path = __importStar(__require("path"));
397834
400168
  var node_child_process_1 = __require("child_process");
400169
+ var single_copy_cleanup_runner_js_1 = require_single_copy_cleanup_runner();
397835
400170
  var logging_service_1 = require_logging_service();
397836
400171
  var event_bus_service_1 = require_event_bus_service();
397837
400172
  var config_service_1 = require_config_service();
@@ -398684,6 +401019,11 @@ var require_main4 = __commonJS({
398684
401019
  meta: { error: err instanceof Error ? err.message : String(err) }
398685
401020
  });
398686
401021
  }
401022
+ void (0, single_copy_cleanup_runner_js_1.runSingleCopyCleanup)({
401023
+ dataDir: dataPath,
401024
+ addonRoot: process.env["CAMSTACK_ADDONS_DIR"] ?? path.join(dataPath, "addons"),
401025
+ log: (line) => logger.info(line)
401026
+ });
398687
401027
  try {
398688
401028
  const dmForBackfill = capabilityRegistry.getSingleton("device-manager");
398689
401029
  const integrationRegistry = addonRegistry.getIntegrationRegistry();
@@ -398982,11 +401322,11 @@ var require_launcher = __commonJS({
398982
401322
  const bootstrapRequired = readBootstrapRequiredAddons(dataDir, bootstrapSchema) ?? roleDefaultBootstrap;
398983
401323
  console.log(`[launcher] bootstrap (${role}): ${bootstrapRequired.length} required package(s)`);
398984
401324
  try {
398985
- const ownManifest = readOwnManifest();
401325
+ const closureNodeModules = path.resolve(__dirname, "..", "node_modules");
398986
401326
  const plan = (0, first_boot_addon_plan_js_1.planFirstBootAddons)({
398987
401327
  required: bootstrapRequired,
398988
- closurePins: ownManifest === null ? {} : (0, first_boot_addon_plan_js_1.bootstrapPinsFrom)(ownManifest),
398989
401328
  installed: readInstalledAddonVersions(addonsDir, bootstrapRequired),
401329
+ closureVersions: readInstalledAddonVersions(closureNodeModules, bootstrapRequired),
398990
401330
  // Mirrors `shouldSkipClosureProvidedSeed`: only `@camstack/system`, and
398991
401331
  // only while it actually resolves.
398992
401332
  closureProvided: (() => {