@camstack/system 1.2.485 → 1.2.486

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/dist/{agent-https-_EXvi6F8.mjs → agent-https-C4PLR74Z.mjs} +1 -1
  2. package/dist/{agent-https-DBP7P-ee.js → agent-https-DP-Ds2hU.js} +1 -1
  3. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.js +1 -1
  4. package/dist/builtins/addon-pages-aggregator/addon-pages-aggregator.addon.mjs +1 -1
  5. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.js +1 -1
  6. package/dist/builtins/addon-widgets-aggregator/addon-widgets-aggregator.addon.mjs +1 -1
  7. package/dist/builtins/alerts/alerts.addon.js +1 -1
  8. package/dist/builtins/alerts/alerts.addon.mjs +1 -1
  9. package/dist/builtins/autotrack/index.js +1 -1
  10. package/dist/builtins/autotrack/index.mjs +1 -1
  11. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.js +2 -2
  12. package/dist/builtins/backup-orchestrator/backup-orchestrator.addon.mjs +2 -2
  13. package/dist/builtins/calendars/calendars.addon.js +1 -1
  14. package/dist/builtins/calendars/calendars.addon.mjs +1 -1
  15. package/dist/builtins/camera-grid/index.js +1 -1
  16. package/dist/builtins/camera-grid/index.mjs +1 -1
  17. package/dist/builtins/composer/composer.addon.js +59 -13
  18. package/dist/builtins/composer/composer.addon.mjs +59 -13
  19. package/dist/builtins/composer/composition-runtime.d.ts +2 -0
  20. package/dist/builtins/console-logging/index.js +1 -1
  21. package/dist/builtins/console-logging/index.mjs +1 -1
  22. package/dist/builtins/core-blocks/composition-sources.d.ts +3 -1
  23. package/dist/builtins/core-blocks/core-blocks.addon.js +53 -28
  24. package/dist/builtins/core-blocks/core-blocks.addon.mjs +53 -28
  25. package/dist/builtins/device-history/device-history.addon.js +2 -2
  26. package/dist/builtins/device-history/device-history.addon.mjs +2 -2
  27. package/dist/builtins/device-manager/device-manager.addon.js +1 -1
  28. package/dist/builtins/device-manager/device-manager.addon.mjs +1 -1
  29. package/dist/builtins/hub-forwarder/index.js +1 -1
  30. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  31. package/dist/builtins/keychain/index.js +3 -3
  32. package/dist/builtins/keychain/index.mjs +3 -3
  33. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  34. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  35. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  36. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  37. package/dist/builtins/local-network/local-network.addon.js +2 -2
  38. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  39. package/dist/builtins/loki-logging/index.js +1 -1
  40. package/dist/builtins/loki-logging/index.mjs +1 -1
  41. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  42. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  43. package/dist/builtins/platform-probe/index.js +1 -1
  44. package/dist/builtins/platform-probe/index.mjs +1 -1
  45. package/dist/builtins/record-entries/index.js +1 -1
  46. package/dist/builtins/record-entries/index.mjs +1 -1
  47. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  48. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  49. package/dist/builtins/snapshot/index.js +1 -1
  50. package/dist/builtins/snapshot/index.mjs +1 -1
  51. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  52. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  53. package/dist/builtins/sqlite-storage/index.js +1 -1
  54. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  55. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
  56. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
  57. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  58. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  59. package/dist/builtins/system-config/system-config.addon.js +3 -3
  60. package/dist/builtins/system-config/system-config.addon.mjs +3 -3
  61. package/dist/builtins/winston-logging/index.js +1 -1
  62. package/dist/builtins/winston-logging/index.mjs +1 -1
  63. package/dist/{composition-sources-BSQneZV9.js → composition-sources-CN8K2Q91.js} +4 -2
  64. package/dist/{composition-sources-BnZpIA8u.mjs → composition-sources-IxiUkrz1.mjs} +4 -2
  65. package/dist/{constants-CQfTeCnB.js → constants-1S7BBGMb.js} +1 -1
  66. package/dist/{constants-7D7xjw0h.mjs → constants-DLyGFHZ2.mjs} +1 -1
  67. package/dist/{device-ca-Bzd6UO_c.mjs → device-ca-Cbf7_R9t.mjs} +1 -1
  68. package/dist/{device-ca-D_4dI5gv.js → device-ca-DoOFG0nm.js} +1 -1
  69. package/dist/{dist-xCNJAQMh.js → dist-D-d_UWL4.js} +760 -19
  70. package/dist/{dist-Df6c0RX_.mjs → dist-HBue971z.mjs} +760 -19
  71. package/dist/file-host-worker.js +1 -1
  72. package/dist/file-host-worker.mjs +1 -1
  73. package/dist/{hub-mdns-ciao-D7GMnEz0.mjs → hub-mdns-ciao-CuEWb6gB.mjs} +2 -2
  74. package/dist/{hub-mdns-ciao-DRfmLR_Z.js → hub-mdns-ciao-dcacrwqd.js} +2 -2
  75. package/dist/index.js +7 -7
  76. package/dist/index.mjs +7 -7
  77. package/dist/{restore-archive-Co70oRya.mjs → restore-archive-BGBRadFJ.mjs} +1 -1
  78. package/dist/{restore-archive-DA7N6ZZA.js → restore-archive-DHdXrKwG.js} +1 -1
  79. package/dist/{retention-DfCW-dZS.mjs → retention-8uqDWll8.mjs} +1 -1
  80. package/dist/{retention-DNDOPW7Z.js → retention-ChgrkTfz.js} +1 -1
  81. package/dist/{wal-maintenance-CA0L6WZ9.js → wal-maintenance-BOT7wWvH.js} +0 -0
  82. package/dist/{wal-maintenance-71-j48Ci.mjs → wal-maintenance-DubGwqz2.mjs} +0 -0
  83. package/package.json +1 -1
@@ -765,7 +765,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
765
765
  return EventCategory;
766
766
  }({});
767
767
  //#endregion
768
- //#region ../types/dist/sleep-CARd6wZF.mjs
768
+ //#region ../types/dist/sleep-B81gwQuR.mjs
769
769
  /**
770
770
  * The audio chunk plane's byte format, and the ONE expansion from a coded
771
771
  * window to float samples (D455).
@@ -5183,6 +5183,7 @@ var CAP_NAMES = Object.freeze([
5183
5183
  "filesystem-browse",
5184
5184
  "flood",
5185
5185
  "gas",
5186
+ "group-status",
5186
5187
  "heating-valve",
5187
5188
  "humidifier",
5188
5189
  "humidity-sensor",
@@ -6001,6 +6002,20 @@ Object.freeze({
6001
6002
  "path": "detected",
6002
6003
  "valueType": "boolean"
6003
6004
  }],
6005
+ "group-status": [
6006
+ {
6007
+ "path": "included",
6008
+ "valueType": "number"
6009
+ },
6010
+ {
6011
+ "path": "excluded",
6012
+ "valueType": "number"
6013
+ },
6014
+ {
6015
+ "path": "total",
6016
+ "valueType": "number"
6017
+ }
6018
+ ],
6004
6019
  "heating-valve": [{
6005
6020
  "path": "position",
6006
6021
  "valueType": "number"
@@ -7468,7 +7483,7 @@ function errMsg(err) {
7468
7483
  return String(err);
7469
7484
  }
7470
7485
  //#endregion
7471
- //#region ../types/dist/composition-kEl9mevu.mjs
7486
+ //#region ../types/dist/composition-B85l9j1M.mjs
7472
7487
  var AUDIO_ENCODER_BY_CODEC = {
7473
7488
  opus: "libopus",
7474
7489
  aac: "aac",
@@ -8986,6 +9001,68 @@ function evaluateExpressionSource(expr, bindingValues, now, opts) {
8986
9001
  }
8987
9002
  }
8988
9003
  /**
9004
+ * A composition source identity: `(addonId, stableId)`, never a numeric device
9005
+ * id. Split out so helper schemas can name it without a cycle through
9006
+ * `composition.ts` (D659).
9007
+ */
9008
+ var CompositionSourceRefSchema = z.object({
9009
+ /** The owning addon; `stableId` is unique only within it. */
9010
+ addonId: z.string().min(1),
9011
+ stableId: z.string().min(1)
9012
+ });
9013
+ var GROUP_TEMPLATE_IDS = [
9014
+ "temperature",
9015
+ "humidity",
9016
+ "pressure",
9017
+ "illuminance",
9018
+ "power",
9019
+ "energy",
9020
+ "battery",
9021
+ "numeric",
9022
+ "motion",
9023
+ "contact",
9024
+ "leak",
9025
+ "smoke",
9026
+ "occupancy",
9027
+ "binary"
9028
+ ];
9029
+ var NUMERIC_AGGREGATES = [
9030
+ "mean",
9031
+ "median",
9032
+ "min",
9033
+ "max",
9034
+ "sum"
9035
+ ];
9036
+ var BINARY_AGGREGATES = ["any", "all"];
9037
+ var AGGREGATE_REDUCERS = [
9038
+ "mean",
9039
+ "median",
9040
+ "min",
9041
+ "max",
9042
+ "sum",
9043
+ "any",
9044
+ "all"
9045
+ ];
9046
+ var GroupTemplateIdSchema = z.enum(GROUP_TEMPLATE_IDS);
9047
+ var AggregateReducerSchema = z.enum(AGGREGATE_REDUCERS);
9048
+ /** One member. The field is stored so occupancy can name motion or binary. */
9049
+ var HelperMemberSchema = z.object({
9050
+ source: CompositionSourceRefSchema,
9051
+ cap: z.string().min(1),
9052
+ fieldPath: z.string().min(1)
9053
+ });
9054
+ /**
9055
+ * Phase 1a is groups only. Math, integral and statistics stay off this union
9056
+ * until the composer can run them — a control must not save a spec it cannot.
9057
+ */
9058
+ var GroupHelperSpecSchema = z.object({
9059
+ kind: z.literal("group"),
9060
+ templateId: GroupTemplateIdSchema,
9061
+ aggregate: AggregateReducerSchema,
9062
+ members: z.array(HelperMemberSchema).min(1).max(64)
9063
+ });
9064
+ var HelperSpecSchema = z.discriminatedUnion("kind", [GroupHelperSpecSchema]);
9065
+ /**
8989
9066
  * A COMPOSITION: a device assembled from other devices' state (D659).
8990
9067
  *
8991
9068
  * The data half of the composed-devices spec §1. A composition is stored inside
@@ -9010,11 +9087,6 @@ var COMPOSER_ADDON_ID = "composer";
9010
9087
  var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
9011
9088
  /** Longest snippet a `code` source may carry (slice 4). */
9012
9089
  var MAX_COMPOSITION_CODE_LENGTH = 2e4;
9013
- var CompositionSourceRefSchema = z.object({
9014
- /** The owning addon; `stableId` is unique only within it. */
9015
- addonId: z.string().min(1),
9016
- stableId: z.string().min(1)
9017
- });
9018
9090
  /** One field of one capability of one source device. */
9019
9091
  var CompositionFieldReadSchema = z.object({
9020
9092
  source: CompositionSourceRefSchema,
@@ -9057,10 +9129,42 @@ var CompositionCodeSourceSchema = z.object({
9057
9129
  kind: z.literal("code"),
9058
9130
  code: z.string().min(1).max(MAX_COMPOSITION_CODE_LENGTH)
9059
9131
  });
9132
+ /**
9133
+ * `aggregate`: a group reducer. Offline and unknown reads are excluded, not
9134
+ * folded in as 0 or false (D944). Free expressions stay `expression` so a
9135
+ * stale member is still included there.
9136
+ */
9137
+ var CompositionAggregateSourceSchema = z.object({
9138
+ kind: z.literal("aggregate"),
9139
+ reducer: z.enum([
9140
+ "mean",
9141
+ "median",
9142
+ "min",
9143
+ "max",
9144
+ "sum",
9145
+ "any",
9146
+ "all"
9147
+ ]),
9148
+ reads: z.array(CompositionFieldReadSchema).min(1).max(64),
9149
+ /** `value` is the reading; the others are the member counts. */
9150
+ publish: z.enum([
9151
+ "value",
9152
+ "included",
9153
+ "excluded",
9154
+ "total"
9155
+ ]),
9156
+ /** What a `value` publish does when nobody is included. Counts ignore this. */
9157
+ empty: z.enum([
9158
+ "null",
9159
+ "omit",
9160
+ "offline"
9161
+ ])
9162
+ });
9060
9163
  var CompositionFieldSourceSchema = z.discriminatedUnion("kind", [
9061
9164
  CompositionFromSourceSchema,
9062
9165
  CompositionExpressionSourceSchema,
9063
- CompositionCodeSourceSchema
9166
+ CompositionCodeSourceSchema,
9167
+ CompositionAggregateSourceSchema
9064
9168
  ]);
9065
9169
  /** `forward`: RESERVED for slice 3. Argument mapping arrives there as an additive optional `args`. */
9066
9170
  var CompositionForwardCommandSchema = z.object({
@@ -9125,7 +9229,13 @@ var CompositionExistingTargetSchema = z.object({
9125
9229
  var CompositionTargetSchema = z.discriminatedUnion("kind", [CompositionNewTargetSchema, CompositionExistingTargetSchema]);
9126
9230
  var CompositionSchema = z.object({
9127
9231
  target: CompositionTargetSchema,
9128
- features: z.array(CompositionFeatureSchema).min(1).max(16)
9232
+ features: z.array(CompositionFeatureSchema).min(1).max(16),
9233
+ /**
9234
+ * Present when the block is a helper template. Absent on a free
9235
+ * composition (the room example, device 4603). The editor reads this,
9236
+ * never the compiled features.
9237
+ */
9238
+ helper: HelperSpecSchema.optional()
9129
9239
  }).superRefine((composition, ctx) => {
9130
9240
  const seen = /* @__PURE__ */ new Set();
9131
9241
  composition.features.forEach((feature, index) => {
@@ -9172,6 +9282,7 @@ function fieldReads(source) {
9172
9282
  cap: binding.cap,
9173
9283
  fieldPath: binding.fieldPath
9174
9284
  }] : []);
9285
+ case "aggregate": return source.reads;
9175
9286
  case "code": return [];
9176
9287
  }
9177
9288
  }
@@ -17363,7 +17474,10 @@ var CompositionProblemCodeSchema = z.enum([
17363
17474
  "target-missing",
17364
17475
  "target-unreadable",
17365
17476
  "not-replaceable",
17366
- "native-capability-replace"
17477
+ "native-capability-replace",
17478
+ "mixed-quantity",
17479
+ "helper-mismatch",
17480
+ "member-constraint"
17367
17481
  ]);
17368
17482
  /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
17369
17483
  var CompositionProblemSchema = z.object({
@@ -17387,7 +17501,8 @@ var CompositionUnavailableModeSchema = z.enum([
17387
17501
  "null",
17388
17502
  "offline",
17389
17503
  "unknown",
17390
- "release"
17504
+ "release",
17505
+ "omit"
17391
17506
  ]);
17392
17507
  /**
17393
17508
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -17749,6 +17864,24 @@ function inferExpressionKinds(input) {
17749
17864
  * planner (`composition-items.ts`). Internal to the composition module — NOT
17750
17865
  * in the barrel: these are planner internals, not a published contract.
17751
17866
  */
17867
+ function aggregateKinds(source) {
17868
+ if (source.publish !== "value") return {
17869
+ ok: true,
17870
+ kinds: new Set(["number"])
17871
+ };
17872
+ if (source.reducer === "any" || source.reducer === "all") return {
17873
+ ok: true,
17874
+ kinds: new Set(["boolean"])
17875
+ };
17876
+ if (source.empty === "null") return {
17877
+ ok: true,
17878
+ kinds: new Set(["number", "null"])
17879
+ };
17880
+ return {
17881
+ ok: true,
17882
+ kinds: new Set(["number"])
17883
+ };
17884
+ }
17752
17885
  function readKinds(read, lookupCap) {
17753
17886
  const def = lookupCap(read.cap);
17754
17887
  const schema = def?.runtimeState ?? def?.status?.schema;
@@ -17776,6 +17909,7 @@ function sourceKinds(source, lookupCap) {
17776
17909
  code: "reserved-variant",
17777
17910
  message: "code fields arrive in slice 4"
17778
17911
  };
17912
+ if (source.kind === "aggregate") return aggregateKinds(source);
17779
17913
  const bindingKinds = /* @__PURE__ */ new Map();
17780
17914
  for (const [name, binding] of Object.entries(source.bindings)) {
17781
17915
  if (binding.kind === "literal") {
@@ -17970,6 +18104,379 @@ function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
17970
18104
  };
17971
18105
  }
17972
18106
  /**
18107
+ * One catalog row per base reading a group helper can build (D944 §3).
18108
+ * Data, not a form default written into the block.
18109
+ */
18110
+ var NOW = {
18111
+ fieldPath: "lastFetchedAt",
18112
+ expr: "now"
18113
+ };
18114
+ var CHANGED = {
18115
+ fieldPath: "lastChangedAt",
18116
+ expr: "now"
18117
+ };
18118
+ function numeric(input) {
18119
+ return {
18120
+ ...input,
18121
+ family: "numeric",
18122
+ deviceType: DeviceType.Sensor,
18123
+ memberDeviceType: DeviceType.Sensor,
18124
+ baseUnit: baseUnitOf(input.quantity),
18125
+ aggregates: NUMERIC_AGGREGATES,
18126
+ accept: [{
18127
+ cap: input.outputCap,
18128
+ fieldPath: input.outputField
18129
+ }]
18130
+ };
18131
+ }
18132
+ function binary(input) {
18133
+ return {
18134
+ ...input,
18135
+ family: "binary",
18136
+ deviceType: DeviceType.Sensor,
18137
+ memberDeviceType: DeviceType.Sensor,
18138
+ quantity: "none",
18139
+ baseUnit: void 0,
18140
+ aggregates: BINARY_AGGREGATES,
18141
+ empty: "offline"
18142
+ };
18143
+ }
18144
+ var GROUP_TEMPLATES = [
18145
+ numeric({
18146
+ id: "temperature",
18147
+ summary: "Mean, median, min, max or sum of temperatures. Offline and unknown members are left out, never read as 0 °C.",
18148
+ role: DeviceRole.TemperatureSensor,
18149
+ outputCap: "temperature-sensor",
18150
+ outputField: "celsius",
18151
+ quantity: "temperature",
18152
+ defaultAggregate: "mean",
18153
+ empty: "null",
18154
+ companions: [NOW]
18155
+ }),
18156
+ numeric({
18157
+ id: "humidity",
18158
+ summary: "Humidity in percent. Offline and unknown members are left out, never read as 0 %.",
18159
+ role: DeviceRole.HumiditySensor,
18160
+ outputCap: "humidity-sensor",
18161
+ outputField: "percent",
18162
+ quantity: "humidity",
18163
+ defaultAggregate: "mean",
18164
+ empty: "null",
18165
+ companions: [NOW]
18166
+ }),
18167
+ numeric({
18168
+ id: "pressure",
18169
+ summary: "Pressure in hectopascals. Offline and unknown members are left out, never read as 0 hPa.",
18170
+ role: DeviceRole.PressureSensor,
18171
+ outputCap: "pressure-sensor",
18172
+ outputField: "hpa",
18173
+ quantity: "pressure",
18174
+ defaultAggregate: "mean",
18175
+ empty: "null",
18176
+ companions: [NOW]
18177
+ }),
18178
+ numeric({
18179
+ id: "illuminance",
18180
+ summary: "Illuminance in lux. Offline and unknown members are left out, never read as 0 lx.",
18181
+ role: DeviceRole.AmbientLightSensor,
18182
+ outputCap: "ambient-light-sensor",
18183
+ outputField: "lux",
18184
+ quantity: "illuminance",
18185
+ defaultAggregate: "mean",
18186
+ empty: "null",
18187
+ companions: [NOW]
18188
+ }),
18189
+ numeric({
18190
+ id: "power",
18191
+ summary: "Power in watts, summed by default. Nobody reporting omits the key rather than writing 0 W.",
18192
+ role: DeviceRole.PowerSensor,
18193
+ outputCap: "power-meter",
18194
+ outputField: "watts",
18195
+ quantity: "power",
18196
+ defaultAggregate: "sum",
18197
+ empty: "omit",
18198
+ companions: [NOW]
18199
+ }),
18200
+ numeric({
18201
+ id: "energy",
18202
+ summary: "Energy in kilowatt-hours, summed by default. Nobody reporting omits the key rather than writing 0 kWh.",
18203
+ role: DeviceRole.EnergySensor,
18204
+ outputCap: "power-meter",
18205
+ outputField: "kwhTotal",
18206
+ quantity: "energy",
18207
+ defaultAggregate: "sum",
18208
+ empty: "omit",
18209
+ companions: [NOW]
18210
+ }),
18211
+ numeric({
18212
+ id: "battery",
18213
+ summary: "Battery charge in percent. Offline and unknown members are left out, never read as 0 %.",
18214
+ role: DeviceRole.BatterySensor,
18215
+ outputCap: "battery",
18216
+ outputField: "percentage",
18217
+ quantity: "percentage",
18218
+ defaultAggregate: "mean",
18219
+ empty: "null",
18220
+ companions: [
18221
+ {
18222
+ fieldPath: "charging",
18223
+ expr: "\"unknown\""
18224
+ },
18225
+ {
18226
+ fieldPath: "sleeping",
18227
+ expr: "false"
18228
+ },
18229
+ {
18230
+ fieldPath: "lastUpdated",
18231
+ expr: "now"
18232
+ }
18233
+ ]
18234
+ }),
18235
+ numeric({
18236
+ id: "numeric",
18237
+ summary: "A numeric reading in the members’ shared quantity. Mixed quantities are refused. Nobody reporting publishes null, not 0.",
18238
+ role: DeviceRole.NumericSensor,
18239
+ outputCap: "numeric-sensor",
18240
+ outputField: "value",
18241
+ quantity: "device",
18242
+ defaultAggregate: "mean",
18243
+ empty: "null",
18244
+ companions: [NOW]
18245
+ }),
18246
+ binary({
18247
+ id: "motion",
18248
+ summary: "Any or all motion. Offline and unknown members are left out, never read as off. Nobody reporting takes the device offline.",
18249
+ role: DeviceRole.MotionSensor,
18250
+ outputCap: "motion",
18251
+ outputField: "detected",
18252
+ defaultAggregate: "any",
18253
+ accept: [{
18254
+ cap: "motion",
18255
+ fieldPath: "detected"
18256
+ }],
18257
+ companions: [{
18258
+ fieldPath: "lastDetectedAt",
18259
+ expr: "null"
18260
+ }, {
18261
+ fieldPath: "autoClearAfterMs",
18262
+ expr: "null"
18263
+ }]
18264
+ }),
18265
+ binary({
18266
+ id: "contact",
18267
+ summary: "Any or all open contacts. Offline and unknown members are left out, never read as closed.",
18268
+ role: DeviceRole.ContactSensor,
18269
+ outputCap: "contact",
18270
+ outputField: "entryOpen",
18271
+ defaultAggregate: "any",
18272
+ accept: [{
18273
+ cap: "contact",
18274
+ fieldPath: "entryOpen"
18275
+ }],
18276
+ companions: [CHANGED]
18277
+ }),
18278
+ binary({
18279
+ id: "leak",
18280
+ summary: "Any or all leak detections. Offline and unknown members are left out, never read as dry.",
18281
+ role: DeviceRole.LeakSensor,
18282
+ outputCap: "flood",
18283
+ outputField: "flooded",
18284
+ defaultAggregate: "any",
18285
+ accept: [{
18286
+ cap: "flood",
18287
+ fieldPath: "flooded"
18288
+ }],
18289
+ companions: [CHANGED]
18290
+ }),
18291
+ binary({
18292
+ id: "smoke",
18293
+ summary: "Any or all smoke detections. Offline and unknown members are left out, never read as clear.",
18294
+ role: DeviceRole.SmokeSensor,
18295
+ outputCap: "smoke",
18296
+ outputField: "detected",
18297
+ defaultAggregate: "any",
18298
+ accept: [{
18299
+ cap: "smoke",
18300
+ fieldPath: "detected"
18301
+ }],
18302
+ companions: [CHANGED]
18303
+ }),
18304
+ binary({
18305
+ id: "occupancy",
18306
+ summary: "Any or all of motion or binary sensors, published as a binary. Presence zones are not aggregated. Nobody reporting is offline, not off.",
18307
+ role: DeviceRole.BinarySensor,
18308
+ outputCap: "binary",
18309
+ outputField: "on",
18310
+ defaultAggregate: "any",
18311
+ accept: [{
18312
+ cap: "motion",
18313
+ fieldPath: "detected"
18314
+ }, {
18315
+ cap: "binary",
18316
+ fieldPath: "on"
18317
+ }],
18318
+ companions: [CHANGED]
18319
+ }),
18320
+ binary({
18321
+ id: "binary",
18322
+ summary: "Any or all generic binary sensors. Offline and unknown members are left out, never read as off.",
18323
+ role: DeviceRole.BinarySensor,
18324
+ outputCap: "binary",
18325
+ outputField: "on",
18326
+ defaultAggregate: "any",
18327
+ accept: [{
18328
+ cap: "binary",
18329
+ fieldPath: "on"
18330
+ }],
18331
+ companions: [CHANGED]
18332
+ })
18333
+ ];
18334
+ var BY_ID = new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
18335
+ function groupTemplate(id) {
18336
+ return BY_ID.get(id) ?? null;
18337
+ }
18338
+ function sameSource(a, b) {
18339
+ return a.addonId === b.addonId && a.stableId === b.stableId;
18340
+ }
18341
+ function sameMember(a, b) {
18342
+ return sameSource(a.source, b.source) && a.cap === b.cap && a.fieldPath === b.fieldPath;
18343
+ }
18344
+ function accepted(template, member) {
18345
+ return template.accept.some((field) => field.cap === member.cap && field.fieldPath === member.fieldPath);
18346
+ }
18347
+ function memberProblems(template, spec) {
18348
+ return spec.members.flatMap((member, index) => {
18349
+ const at = `helper.members[${index}]`;
18350
+ const problems = [];
18351
+ if (!accepted(template, member)) {
18352
+ const allowed = template.accept.map((field) => `${field.cap}.${field.fieldPath}`).join(" or ");
18353
+ problems.push(problem("member-constraint", at, `a ${template.id} group reads ${allowed}; this member is ${member.cap}.${member.fieldPath}`));
18354
+ }
18355
+ if (spec.members.slice(0, index).some((other) => sameMember(other, member))) problems.push(problem("member-constraint", at, "this member is already in the group"));
18356
+ return problems;
18357
+ });
18358
+ }
18359
+ function aggregateSource(template, spec, reads, publish) {
18360
+ return {
18361
+ kind: "aggregate",
18362
+ reducer: spec.aggregate,
18363
+ reads: [...reads],
18364
+ publish,
18365
+ empty: template.empty
18366
+ };
18367
+ }
18368
+ function expressionSource(expr) {
18369
+ return {
18370
+ kind: "expression",
18371
+ expr,
18372
+ bindings: {}
18373
+ };
18374
+ }
18375
+ function draftOf$1(template, spec) {
18376
+ const reads = spec.members.map((member) => ({
18377
+ source: member.source,
18378
+ cap: member.cap,
18379
+ fieldPath: member.fieldPath
18380
+ }));
18381
+ const companions = Object.fromEntries(template.companions.map((companion) => [companion.fieldPath, expressionSource(companion.expr)]));
18382
+ return {
18383
+ target: {
18384
+ kind: "new",
18385
+ type: template.deviceType,
18386
+ role: template.role
18387
+ },
18388
+ helper: spec,
18389
+ features: [{
18390
+ kind: "fields",
18391
+ cap: template.outputCap,
18392
+ fields: {
18393
+ [template.outputField]: aggregateSource(template, spec, reads, "value"),
18394
+ ...companions
18395
+ }
18396
+ }, {
18397
+ kind: "fields",
18398
+ cap: "group-status",
18399
+ fields: {
18400
+ included: aggregateSource(template, spec, reads, "included"),
18401
+ excluded: aggregateSource(template, spec, reads, "excluded"),
18402
+ total: aggregateSource(template, spec, reads, "total")
18403
+ }
18404
+ }]
18405
+ };
18406
+ }
18407
+ function parseDraft(draft) {
18408
+ const parsed = CompositionSchema.safeParse(draft);
18409
+ if (parsed.success) return {
18410
+ ok: true,
18411
+ composition: parsed.data
18412
+ };
18413
+ return {
18414
+ ok: false,
18415
+ problems: [problem("helper-mismatch", "helper", `the template did not compile: ${parsed.error.issues.map((issue) => issue.message).join("; ")}`)]
18416
+ };
18417
+ }
18418
+ /** Compile a group template to a new-device composition. Refusals are named. */
18419
+ function compileHelper(spec) {
18420
+ const template = groupTemplate(spec.templateId);
18421
+ if (template === null) return {
18422
+ ok: false,
18423
+ problems: [problem("member-constraint", "helper.templateId", `unknown template \`${spec.templateId}\``)]
18424
+ };
18425
+ if (!template.aggregates.includes(spec.aggregate)) {
18426
+ const allowed = template.aggregates.join(", ");
18427
+ return {
18428
+ ok: false,
18429
+ problems: [problem("kind-mismatch", "helper.aggregate", `a ${template.id} group aggregates by ${allowed}, not \`${spec.aggregate}\``)]
18430
+ };
18431
+ }
18432
+ const members = memberProblems(template, spec);
18433
+ if (members.length > 0) return {
18434
+ ok: false,
18435
+ problems: members
18436
+ };
18437
+ return parseDraft(draftOf$1(template, spec));
18438
+ }
18439
+ /** The built body, ignoring `helper`, so a save can demand one writer. */
18440
+ function sameHelperBody(stored, compiled) {
18441
+ return JSON.stringify(stored.target) === JSON.stringify(compiled.target) && JSON.stringify(stored.features) === JSON.stringify(compiled.features);
18442
+ }
18443
+ function quantityProblems(spec, facts) {
18444
+ const template = groupTemplate(spec.templateId);
18445
+ if (template === null || template.family !== "numeric") return [];
18446
+ const known = spec.members.flatMap((member, index) => {
18447
+ const quantity = facts.get(compositionSourceKey(member.source))?.quantity;
18448
+ return quantity === void 0 ? [] : [{
18449
+ index,
18450
+ quantity
18451
+ }];
18452
+ });
18453
+ if (template.quantity !== "device") return known.flatMap((member) => member.quantity === template.quantity ? [] : [problem("mixed-quantity", `helper.members[${member.index}]`, `a ${template.id} group is ${template.quantity}${template.baseUnit === void 0 ? "" : ` (${template.baseUnit})`}; this member is ${member.quantity}`)]);
18454
+ const distinct = [...new Set(known.map((member) => member.quantity))];
18455
+ if (distinct.length <= 1) return [];
18456
+ return [problem("mixed-quantity", "helper.members", `a numeric group requires one quantity; members report ${distinct.join(" and ")}`)];
18457
+ }
18458
+ function deviceTypeProblems(spec, facts) {
18459
+ const template = groupTemplate(spec.templateId);
18460
+ if (template === null) return [];
18461
+ return spec.members.flatMap((member, index) => {
18462
+ const deviceType = facts.get(compositionSourceKey(member.source))?.deviceType;
18463
+ if (deviceType === void 0 || deviceType === template.memberDeviceType) return [];
18464
+ return [problem("member-constraint", `helper.members[${index}]`, `a ${template.id} group takes ${template.memberDeviceType} devices; this member is a ${deviceType}`)];
18465
+ });
18466
+ }
18467
+ /**
18468
+ * Problems that refuse a save. No `helper` key means a free composition: this
18469
+ * returns nothing, and expression semantics stay as they are.
18470
+ */
18471
+ function helperSaveProblems(composition, facts) {
18472
+ const spec = composition.helper;
18473
+ if (spec === void 0) return [];
18474
+ const compiled = compileHelper(spec);
18475
+ if (!compiled.ok) return compiled.problems;
18476
+ if (!sameHelperBody(composition, compiled.composition)) return [problem("helper-mismatch", "helper", "the stored features are not this template — the helper spec is the only writer")];
18477
+ return [...quantityProblems(spec, facts), ...deviceTypeProblems(spec, facts)];
18478
+ }
18479
+ /**
17973
18480
  * The field graph across every composition, and the cycles in it (D659).
17974
18481
  *
17975
18482
  * A node is ONE field of ONE device (`<addon>/<stableId>#<cap>.<field>`); an
@@ -18174,6 +18681,7 @@ function planTopLevelField({ def, fields }, field, mode, lookupCap) {
18174
18681
  const role = roleOf(source);
18175
18682
  const unavailable = unavailableFor(tf, role, mode, def.name, fieldAt);
18176
18683
  if (!unavailable.ok) return refused(unavailable.problem);
18684
+ const omitEmpty = source.kind === "aggregate" && source.publish === "value" && source.empty === "omit";
18177
18685
  const plan = {
18178
18686
  cap: def.name,
18179
18687
  path,
@@ -18181,7 +18689,7 @@ function planTopLevelField({ def, fields }, field, mode, lookupCap) {
18181
18689
  kind: tf.kind,
18182
18690
  nullable: tf.nullable,
18183
18691
  required: tf.required,
18184
- unavailable: unavailable.unavailable,
18692
+ unavailable: omitEmpty ? "omit" : unavailable.unavailable,
18185
18693
  role,
18186
18694
  source,
18187
18695
  reads: [...fieldReads(source)]
@@ -18446,10 +18954,15 @@ function validateComposition(composition, ctx) {
18446
18954
  if (refusedFeatures.some((at) => item.path === at || item.path.startsWith(`${at}.`))) return false;
18447
18955
  return refusedFeatures.length === 0 || item.path !== "features";
18448
18956
  });
18957
+ const facts = new Map([...ctx.sources].flatMap(([key, source]) => source.kind === "found" ? [[key, {
18958
+ deviceType: source.deviceType,
18959
+ quantity: source.quantity
18960
+ }]] : []));
18449
18961
  const problems = [
18450
18962
  ...plan.problems,
18451
18963
  ...sourceProblems(plan.fields, ctx, self),
18452
- ...cycles
18964
+ ...cycles,
18965
+ ...helperSaveProblems(composition, facts)
18453
18966
  ];
18454
18967
  return {
18455
18968
  ok: problems.length === 0,
@@ -65651,6 +66164,51 @@ var gasCapability = {
65651
66164
  * whether persisting is worth a SQLite commit. */
65652
66165
  volatileStateFields: ["lastChangedAt"]
65653
66166
  };
66167
+ /**
66168
+ * How many members of a helper group answered (D944). Counts are integers,
66169
+ * not measurements: zero included is a real count, never a stand-in reading.
66170
+ * The reading itself stays on the output capability. History is none — the
66171
+ * series that later statistics read is the output cap's own history block.
66172
+ */
66173
+ var GroupStatusSchema = z.object({
66174
+ /** Members whose current value entered the aggregate. */
66175
+ included: z.number().int().nonnegative().meta(readingMeta("count", {
66176
+ precision: 0,
66177
+ label: "Reporting"
66178
+ })),
66179
+ /** Members left out: offline, unknown, or not a finite reading (D393). */
66180
+ excluded: z.number().int().nonnegative().meta(readingMeta("count", {
66181
+ precision: 0,
66182
+ label: "Excluded"
66183
+ })),
66184
+ /** `included` + `excluded`. */
66185
+ total: z.number().int().nonnegative().meta(readingMeta("count", {
66186
+ precision: 0,
66187
+ label: "Members"
66188
+ }))
66189
+ });
66190
+ var groupStatusCapability = {
66191
+ name: "group-status",
66192
+ scope: "device",
66193
+ deviceNative: true,
66194
+ mode: "singleton",
66195
+ deviceTypes: [DeviceType.Sensor],
66196
+ methods: {},
66197
+ status: {
66198
+ schema: GroupStatusSchema,
66199
+ kind: "push"
66200
+ },
66201
+ runtimeState: GroupStatusSchema,
66202
+ /**
66203
+ * Runtime-state durability: **session** — recomputed from members on the
66204
+ * next source event; a restored count would outlive the readings it named.
66205
+ *
66206
+ * See `RuntimeStateDurability`. Enforced by
66207
+ * `scripts/check-runtime-state-durability.ts`.
66208
+ */
66209
+ durability: "session",
66210
+ history: "none"
66211
+ };
65654
66212
  var HeatingValveStatusSchema = z.object({
65655
66213
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
65656
66214
  position: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
@@ -67715,12 +68273,18 @@ var motionCapability = {
67715
68273
  methods: {
67716
68274
  /**
67717
68275
  * Pull the current motion state synchronously. Convenience shortcut
67718
- * for consumers that don't need the full status object; equivalent
67719
- * to `getStatus()?.detected ?? false`. Will likely be folded into
67720
- * `getStatus` once the auto-injected status surface lands in every
67721
- * consumer.
68276
+ * for consumers that don't need the full status object. A composed
68277
+ * group answers from its slice (D944): unknown throws, it is not off.
67722
68278
  */
67723
68279
  isDetected: method(z.object({ deviceId: z.number() }), z.boolean()) },
68280
+ /**
68281
+ * A composed motion group serves `isDetected` from `detected` (D944).
68282
+ * Without this the cap stays uncomposable (`has-commands`).
68283
+ */
68284
+ composedMethods: { isDetected: {
68285
+ kind: "slice",
68286
+ field: "detected"
68287
+ } },
67724
68288
  events: {
67725
68289
  /**
67726
68290
  * Fires every time the runner transitions a camera between
@@ -76628,6 +77192,103 @@ function createDeviceExtensionProvider(options) {
76628
77192
  }
76629
77193
  };
76630
77194
  }
77195
+ function isBinaryReducer(reducer) {
77196
+ return reducer === "any" || reducer === "all";
77197
+ }
77198
+ function includedValue(reducer, member) {
77199
+ if (member.state !== "fresh") return null;
77200
+ if (isBinaryReducer(reducer)) return typeof member.value === "boolean" ? member.value : null;
77201
+ return typeof member.value === "number" && Number.isFinite(member.value) ? member.value : null;
77202
+ }
77203
+ function median$1(values) {
77204
+ const sorted = [...values].toSorted((a, b) => a - b);
77205
+ const mid = Math.floor(sorted.length / 2);
77206
+ const right = sorted[mid];
77207
+ const left = sorted[mid - 1];
77208
+ if (right === void 0) throw new Error("median requires at least one finite number");
77209
+ if (sorted.length % 2 === 1) return right;
77210
+ if (left === void 0) throw new Error("median requires at least one finite number");
77211
+ return (left + right) / 2;
77212
+ }
77213
+ function reduceNumbers(reducer, values) {
77214
+ if (reducer === "min") return Math.min(...values);
77215
+ if (reducer === "max") return Math.max(...values);
77216
+ if (reducer === "sum") return values.reduce((sum, value) => sum + value, 0);
77217
+ if (reducer === "median") return median$1(values);
77218
+ return values.reduce((sum, value) => sum + value, 0) / values.length;
77219
+ }
77220
+ function reduceBooleans(reducer, values) {
77221
+ return reducer === "all" ? values.every((value) => value) : values.some((value) => value);
77222
+ }
77223
+ function countsOf(included, total) {
77224
+ return {
77225
+ included,
77226
+ excluded: total - included,
77227
+ total
77228
+ };
77229
+ }
77230
+ function fatalReason(members) {
77231
+ const reasons = members.flatMap((member) => member.state === "unavailable" && member.fatal === true ? [member.reason ?? "source gone"] : []);
77232
+ return reasons.length === 0 ? null : reasons.join("; ");
77233
+ }
77234
+ function emptyOutcome(empty, counts) {
77235
+ if (empty === "null") return {
77236
+ kind: "value",
77237
+ value: null,
77238
+ counts
77239
+ };
77240
+ return {
77241
+ kind: "unavailable",
77242
+ fatal: false,
77243
+ reason: "no member included — unknown is not 0 or off",
77244
+ counts
77245
+ };
77246
+ }
77247
+ /** Pure. `members` is not mutated. An empty included set is null or unavailable, never 0 or false. */
77248
+ function evaluateAggregate(input) {
77249
+ const included = input.members.flatMap((member) => {
77250
+ const value = includedValue(input.reducer, member);
77251
+ return value === null ? [] : [value];
77252
+ });
77253
+ const counts = countsOf(included.length, input.members.length);
77254
+ const gone = fatalReason(input.members);
77255
+ if (gone !== null) return {
77256
+ kind: "unavailable",
77257
+ fatal: true,
77258
+ reason: gone,
77259
+ counts
77260
+ };
77261
+ if (input.publish === "included") return {
77262
+ kind: "value",
77263
+ value: counts.included,
77264
+ counts
77265
+ };
77266
+ if (input.publish === "excluded") return {
77267
+ kind: "value",
77268
+ value: counts.excluded,
77269
+ counts
77270
+ };
77271
+ if (input.publish === "total") return {
77272
+ kind: "value",
77273
+ value: counts.total,
77274
+ counts
77275
+ };
77276
+ if (included.length === 0) return emptyOutcome(input.empty, counts);
77277
+ if (isBinaryReducer(input.reducer)) {
77278
+ const flags = included.filter((value) => typeof value === "boolean");
77279
+ return {
77280
+ kind: "value",
77281
+ value: reduceBooleans(input.reducer, flags),
77282
+ counts
77283
+ };
77284
+ }
77285
+ const numbers = included.filter((value) => typeof value === "number");
77286
+ return {
77287
+ kind: "value",
77288
+ value: reduceNumbers(input.reducer, numbers),
77289
+ counts
77290
+ };
77291
+ }
76631
77292
  /**
76632
77293
  * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
76633
77294
  * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
@@ -77133,11 +77794,45 @@ function expressionOutcome(source, read, now, stateful) {
77133
77794
  deadline: deadlines.length > 0 ? Math.min(...deadlines) : null
77134
77795
  };
77135
77796
  }
77797
+ function aggregateRead(reading) {
77798
+ if (reading.state === "unavailable") return {
77799
+ state: "unavailable",
77800
+ fatal: reading.fatal,
77801
+ reason: reading.reason
77802
+ };
77803
+ return {
77804
+ state: reading.state,
77805
+ value: reading.value,
77806
+ ...reading.state === "stale" ? { reason: reading.reason } : {}
77807
+ };
77808
+ }
77809
+ function aggregateField(source, read) {
77810
+ const outcome = evaluateAggregate({
77811
+ reducer: source.reducer,
77812
+ members: source.reads.map((item) => aggregateRead(read(item))),
77813
+ publish: source.publish,
77814
+ empty: source.empty
77815
+ });
77816
+ if (outcome.kind === "value") return {
77817
+ state: "fresh",
77818
+ value: outcome.value,
77819
+ reason: null,
77820
+ deadline: null
77821
+ };
77822
+ return {
77823
+ state: "unavailable",
77824
+ value: null,
77825
+ reason: outcome.reason,
77826
+ fatal: outcome.fatal,
77827
+ confirmed: true
77828
+ };
77829
+ }
77136
77830
  function evaluateCompositionField(input) {
77137
77831
  const { source } = input;
77138
77832
  switch (source.kind) {
77139
77833
  case "from": return fromReading(input.read(source), source);
77140
77834
  case "expression": return expressionOutcome(source, input.read, input.now, input.stateful);
77835
+ case "aggregate": return aggregateField(source, input.read);
77141
77836
  case "code": return {
77142
77837
  state: "unavailable",
77143
77838
  value: null,
@@ -77484,6 +78179,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
77484
78179
  featureProbe: featureProbeCapability,
77485
78180
  flood: floodCapability,
77486
78181
  gas: gasCapability,
78182
+ groupStatus: groupStatusCapability,
77487
78183
  heatingValve: heatingValveCapability,
77488
78184
  humidifier: humidifierCapability,
77489
78185
  humiditySensor: humiditySensorCapability,
@@ -81746,6 +82442,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
81746
82442
  access: "view",
81747
82443
  role: "viewer"
81748
82444
  },
82445
+ "groupStatus.getStatus": {
82446
+ capName: "group-status",
82447
+ capScope: "device",
82448
+ addonId: null,
82449
+ access: "view",
82450
+ role: "viewer"
82451
+ },
81749
82452
  "heatingValve.getStatus": {
81750
82453
  capName: "heating-valve",
81751
82454
  capScope: "device",
@@ -91568,6 +92271,33 @@ var SENSOR_CAP_VOCABULARY = {
91568
92271
  icon: "flame"
91569
92272
  }]
91570
92273
  },
92274
+ "group-status": {
92275
+ scope: "device",
92276
+ noun: t("Group", "Gruppo"),
92277
+ state: [
92278
+ {
92279
+ kind: "numeric",
92280
+ field: "included",
92281
+ quantity: t("the members reporting", "i membri che rispondono"),
92282
+ label: t("Reporting", "Rispondono"),
92283
+ icon: "users"
92284
+ },
92285
+ {
92286
+ kind: "numeric",
92287
+ field: "excluded",
92288
+ quantity: t("the members left out", "i membri esclusi"),
92289
+ label: t("Excluded", "Esclusi"),
92290
+ icon: "users"
92291
+ },
92292
+ {
92293
+ kind: "numeric",
92294
+ field: "total",
92295
+ quantity: t("the member count", "il numero di membri"),
92296
+ label: t("Members", "Membri"),
92297
+ icon: "sigma"
92298
+ }
92299
+ ]
92300
+ },
91571
92301
  "humidity-sensor": {
91572
92302
  scope: "device",
91573
92303
  noun: t("Humidity", "Umidità"),
@@ -92104,6 +92834,7 @@ var CAP_NAMES_WITH_STATUS = [
92104
92834
  "feature-probe",
92105
92835
  "flood",
92106
92836
  "gas",
92837
+ "group-status",
92107
92838
  "heating-valve",
92108
92839
  "humidifier",
92109
92840
  "humidity-sensor",
@@ -92233,6 +92964,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
92233
92964
  filesystemBrowseCapability,
92234
92965
  floodCapability,
92235
92966
  gasCapability,
92967
+ groupStatusCapability,
92236
92968
  heatingValveCapability,
92237
92969
  humidifierCapability,
92238
92970
  humiditySensorCapability,
@@ -93307,6 +94039,11 @@ Object.freeze({
93307
94039
  form: "single",
93308
94040
  optional: false
93309
94041
  }],
94042
+ "groupStatus.getStatus": [{
94043
+ name: "deviceId",
94044
+ form: "single",
94045
+ optional: false
94046
+ }],
93310
94047
  "heatingValve.getStatus": [{
93311
94048
  name: "deviceId",
93312
94049
  form: "single",
@@ -95116,7 +95853,7 @@ Object.freeze({
95116
95853
  });
95117
95854
  /**
95118
95855
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
95119
- * `runtimeState`. 72 caps: 38 `restored`, 34 `session`.
95856
+ * `runtimeState`. 73 caps: 38 `restored`, 35 `session`.
95120
95857
  *
95121
95858
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
95122
95859
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -95234,6 +95971,10 @@ var RUNTIME_STATE_POLICY = {
95234
95971
  durability: "restored",
95235
95972
  volatileFields: ["lastChangedAt"]
95236
95973
  },
95974
+ "group-status": {
95975
+ durability: "session",
95976
+ volatileFields: []
95977
+ },
95237
95978
  "heating-valve": {
95238
95979
  durability: "session",
95239
95980
  volatileFields: ["lastFetchedAt"]
@@ -95428,7 +96169,7 @@ var SESSION_ONLY_POLICY = {
95428
96169
  };
95429
96170
  /**
95430
96171
  * Cap history catalog, projected from every `*.cap.ts` `history` block.
95431
- * 41 caps with series (75 series), 30 caps answered `'none'`.
96172
+ * 41 caps with series (75 series), 31 caps answered `'none'`.
95432
96173
  * Field keys are `runtimeState` paths (`a.b` into fixed objects only).
95433
96174
  */
95434
96175
  var HISTORY_CATALOG = {