@camstack/system 1.2.484 → 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 (89) hide show
  1. package/dist/{agent-https-DttPIFry.mjs → agent-https-C4PLR74Z.mjs} +1 -1
  2. package/dist/{agent-https-CW8aLMDL.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 +69 -15
  18. package/dist/builtins/composer/composer.addon.mjs +69 -15
  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.d.ts +2 -0
  26. package/dist/builtins/device-history/device-history.addon.js +248 -4
  27. package/dist/builtins/device-history/device-history.addon.mjs +248 -4
  28. package/dist/builtins/device-history/query/history-query.d.ts +5 -1
  29. package/dist/builtins/device-history/runtime/history-runtime.d.ts +18 -1
  30. package/dist/builtins/device-history/store/history-schema.d.ts +2 -0
  31. package/dist/builtins/device-history/store/history-store.d.ts +11 -0
  32. package/dist/builtins/device-history/store/series-scale.d.ts +37 -0
  33. package/dist/builtins/device-manager/device-manager.addon.js +3 -2
  34. package/dist/builtins/device-manager/device-manager.addon.mjs +3 -2
  35. package/dist/builtins/hub-forwarder/index.js +1 -1
  36. package/dist/builtins/hub-forwarder/index.mjs +1 -1
  37. package/dist/builtins/keychain/index.js +3 -3
  38. package/dist/builtins/keychain/index.mjs +3 -3
  39. package/dist/builtins/liveness-monitor/liveness-monitor.addon.js +1 -1
  40. package/dist/builtins/liveness-monitor/liveness-monitor.addon.mjs +1 -1
  41. package/dist/builtins/local-auth/local-auth.addon.js +2 -2
  42. package/dist/builtins/local-auth/local-auth.addon.mjs +2 -2
  43. package/dist/builtins/local-network/local-network.addon.js +2 -2
  44. package/dist/builtins/local-network/local-network.addon.mjs +2 -2
  45. package/dist/builtins/loki-logging/index.js +1 -1
  46. package/dist/builtins/loki-logging/index.mjs +1 -1
  47. package/dist/builtins/native-metrics/native-metrics.addon.js +1 -1
  48. package/dist/builtins/native-metrics/native-metrics.addon.mjs +1 -1
  49. package/dist/builtins/platform-probe/index.js +1 -1
  50. package/dist/builtins/platform-probe/index.mjs +1 -1
  51. package/dist/builtins/record-entries/index.js +1 -1
  52. package/dist/builtins/record-entries/index.mjs +1 -1
  53. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.js +1 -1
  54. package/dist/builtins/remote-access-orchestrator/remote-access-orchestrator.addon.mjs +1 -1
  55. package/dist/builtins/snapshot/index.js +1 -1
  56. package/dist/builtins/snapshot/index.mjs +1 -1
  57. package/dist/builtins/sqlite-storage/filesystem-storage.addon.js +1 -1
  58. package/dist/builtins/sqlite-storage/filesystem-storage.addon.mjs +1 -1
  59. package/dist/builtins/sqlite-storage/index.js +1 -1
  60. package/dist/builtins/sqlite-storage/index.mjs +1 -1
  61. package/dist/builtins/sqlite-storage/sqlite-settings.addon.js +2 -2
  62. package/dist/builtins/sqlite-storage/sqlite-settings.addon.mjs +2 -2
  63. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.js +1 -1
  64. package/dist/builtins/storage-orchestrator/storage-orchestrator.addon.mjs +1 -1
  65. package/dist/builtins/system-config/system-config.addon.js +3 -3
  66. package/dist/builtins/system-config/system-config.addon.mjs +3 -3
  67. package/dist/builtins/winston-logging/index.js +1 -1
  68. package/dist/builtins/winston-logging/index.mjs +1 -1
  69. package/dist/{composition-sources-DqRPHdtw.js → composition-sources-CN8K2Q91.js} +4 -2
  70. package/dist/{composition-sources-DEsDonpg.mjs → composition-sources-IxiUkrz1.mjs} +4 -2
  71. package/dist/{constants-71vf77HY.js → constants-1S7BBGMb.js} +1 -1
  72. package/dist/{constants-B3FDrBIq.mjs → constants-DLyGFHZ2.mjs} +1 -1
  73. package/dist/{device-ca-bhz04X_N.mjs → device-ca-Cbf7_R9t.mjs} +1 -1
  74. package/dist/{device-ca-BDPYXhkH.js → device-ca-DoOFG0nm.js} +1 -1
  75. package/dist/{dist-k--OhKkO.js → dist-D-d_UWL4.js} +1375 -537
  76. package/dist/{dist-BT_THdzJ.mjs → dist-HBue971z.mjs} +1364 -538
  77. package/dist/file-host-worker.js +1 -1
  78. package/dist/file-host-worker.mjs +1 -1
  79. package/dist/{hub-mdns-ciao-DDgdvcQw.mjs → hub-mdns-ciao-CuEWb6gB.mjs} +2 -2
  80. package/dist/{hub-mdns-ciao-CGXfhvTk.js → hub-mdns-ciao-dcacrwqd.js} +2 -2
  81. package/dist/index.js +7 -7
  82. package/dist/index.mjs +7 -7
  83. package/dist/{restore-archive-z0XT2g29.mjs → restore-archive-BGBRadFJ.mjs} +1 -1
  84. package/dist/{restore-archive-B-GiglWt.js → restore-archive-DHdXrKwG.js} +1 -1
  85. package/dist/{retention-DFkmhjeH.mjs → retention-8uqDWll8.mjs} +1 -1
  86. package/dist/{retention-DR0_xRTy.js → retention-ChgrkTfz.js} +1 -1
  87. package/dist/{wal-maintenance-C4cma95b.js → wal-maintenance-BOT7wWvH.js} +0 -0
  88. package/dist/{wal-maintenance-CVDrL_BD.mjs → wal-maintenance-DubGwqz2.mjs} +0 -0
  89. 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-BErAxhul.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).
@@ -3801,6 +3801,112 @@ function assertTransactScope(members) {
3801
3801
  if (missing.length > 0) throw new TransactRefusedError(`no-family: ${missing.map((m) => m.kind).join(", ")} declare no transactFamily; kinds that must agree through transact share one`);
3802
3802
  if (new Set(kinds.map((m) => m.declaration.transactFamily)).size > 1) throw new TransactRefusedError(`cross-family: the ops span more than one transactFamily (${kinds.map((m) => `${m.kind}=${String(m.declaration.transactFamily)}`).join(", ")})`);
3803
3803
  }
3804
+ var READING_QUANTITIES = [
3805
+ "temperature",
3806
+ "pressure",
3807
+ "speed",
3808
+ "precipitation",
3809
+ "precipitation-rate",
3810
+ "length",
3811
+ "area",
3812
+ "illuminance",
3813
+ "irradiance",
3814
+ "power",
3815
+ "energy",
3816
+ "voltage",
3817
+ "current",
3818
+ "apparent-power",
3819
+ "humidity",
3820
+ "percentage",
3821
+ "concentration-volume",
3822
+ "concentration-mass",
3823
+ "concentration-ppb",
3824
+ "volume",
3825
+ "duration",
3826
+ "duration-ms",
3827
+ "information",
3828
+ "sound-level",
3829
+ "signal-level",
3830
+ "colour-temperature",
3831
+ "angle",
3832
+ "bitrate",
3833
+ "rate",
3834
+ "mass",
3835
+ "index",
3836
+ "ratio",
3837
+ "count",
3838
+ "none",
3839
+ "device"
3840
+ ];
3841
+ var ReadingQuantitySchema = z.enum(READING_QUANTITIES);
3842
+ var QUANTITY_SET = new Set(READING_QUANTITIES);
3843
+ function isReadingQuantity(value) {
3844
+ return typeof value === "string" && QUANTITY_SET.has(value);
3845
+ }
3846
+ /**
3847
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
3848
+ * (or an index / ratio / count). `device` means the cap has no single unit.
3849
+ */
3850
+ var QUANTITY_BASE = {
3851
+ temperature: "°C",
3852
+ pressure: "hPa",
3853
+ speed: "m/s",
3854
+ precipitation: "mm",
3855
+ "precipitation-rate": "mm/h",
3856
+ length: "m",
3857
+ area: "m²",
3858
+ illuminance: "lx",
3859
+ irradiance: "W/m²",
3860
+ power: "W",
3861
+ energy: "kWh",
3862
+ voltage: "V",
3863
+ current: "A",
3864
+ "apparent-power": "VA",
3865
+ humidity: "%",
3866
+ percentage: "%",
3867
+ "concentration-volume": "ppm",
3868
+ "concentration-mass": "µg/m³",
3869
+ "concentration-ppb": "ppb",
3870
+ volume: "L",
3871
+ duration: "s",
3872
+ "duration-ms": "ms",
3873
+ information: "B",
3874
+ "sound-level": "dBFS",
3875
+ "signal-level": "dBm",
3876
+ "colour-temperature": "mired",
3877
+ angle: "°",
3878
+ bitrate: "kbps",
3879
+ rate: "fps",
3880
+ mass: "kg",
3881
+ index: "none",
3882
+ ratio: "none",
3883
+ count: "none",
3884
+ none: "none",
3885
+ device: "device"
3886
+ };
3887
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
3888
+ function baseUnitOf(quantity) {
3889
+ const symbol = QUANTITY_BASE[quantity];
3890
+ if (symbol === "none" || symbol === "device") return void 0;
3891
+ return symbol;
3892
+ }
3893
+ /**
3894
+ * History catalog label for a quantity. Index and ratio keep the labels the
3895
+ * series already used (`count`, `ratio`). A polymorphic reading has no
3896
+ * single label.
3897
+ */
3898
+ function historyUnitFor(quantity) {
3899
+ if (quantity === "index" || quantity === "count") return "count";
3900
+ if (quantity === "ratio") return "ratio";
3901
+ return baseUnitOf(quantity);
3902
+ }
3903
+ function readingMeta(quantity, extras) {
3904
+ return {
3905
+ quantity,
3906
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
3907
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
3908
+ };
3909
+ }
3804
3910
  /** Shared Zod schemas used across streaming capabilities. */
3805
3911
  var CamProfileSchema = z.enum([
3806
3912
  "high",
@@ -3828,8 +3934,8 @@ var CamStreamKindSchema = z.enum([
3828
3934
  "derived"
3829
3935
  ]);
3830
3936
  var CamStreamResolutionSchema = z.object({
3831
- width: z.number().int().positive(),
3832
- height: z.number().int().positive()
3937
+ width: z.number().int().positive().meta(readingMeta("none")),
3938
+ height: z.number().int().positive().meta(readingMeta("none"))
3833
3939
  });
3834
3940
  var CameraStreamSchema = z.object({
3835
3941
  /** Stable, provider-assigned id unique within the (deviceId) scope. */
@@ -5077,6 +5183,7 @@ var CAP_NAMES = Object.freeze([
5077
5183
  "filesystem-browse",
5078
5184
  "flood",
5079
5185
  "gas",
5186
+ "group-status",
5080
5187
  "heating-valve",
5081
5188
  "humidifier",
5082
5189
  "humidity-sensor",
@@ -5206,14 +5313,6 @@ Object.freeze({
5206
5313
  {
5207
5314
  "path": "aqi",
5208
5315
  "valueType": "number"
5209
- },
5210
- {
5211
- "path": "unit",
5212
- "valueType": "string"
5213
- },
5214
- {
5215
- "path": "precision",
5216
- "valueType": "number"
5217
5316
  }
5218
5317
  ],
5219
5318
  "alarm-panel": [
@@ -5249,20 +5348,10 @@ Object.freeze({
5249
5348
  "valueType": "boolean"
5250
5349
  }
5251
5350
  ],
5252
- "ambient-light-sensor": [
5253
- {
5254
- "path": "lux",
5255
- "valueType": "number"
5256
- },
5257
- {
5258
- "path": "unit",
5259
- "valueType": "string"
5260
- },
5261
- {
5262
- "path": "precision",
5263
- "valueType": "number"
5264
- }
5265
- ],
5351
+ "ambient-light-sensor": [{
5352
+ "path": "lux",
5353
+ "valueType": "number"
5354
+ }],
5266
5355
  "audio-metrics": [
5267
5356
  {
5268
5357
  "path": "ts",
@@ -5342,6 +5431,10 @@ Object.freeze({
5342
5431
  "path": "lastUpdated",
5343
5432
  "valueType": "number"
5344
5433
  },
5434
+ {
5435
+ "path": "volts",
5436
+ "valueType": "number"
5437
+ },
5345
5438
  {
5346
5439
  "path": "lastContactAt",
5347
5440
  "valueType": "number"
@@ -5584,10 +5677,6 @@ Object.freeze({
5584
5677
  "datetime"
5585
5678
  ]
5586
5679
  },
5587
- {
5588
- "path": "unit",
5589
- "valueType": "string"
5590
- },
5591
5680
  {
5592
5681
  "path": "min",
5593
5682
  "valueType": "number"
@@ -5599,19 +5688,6 @@ Object.freeze({
5599
5688
  {
5600
5689
  "path": "step",
5601
5690
  "valueType": "number"
5602
- },
5603
- {
5604
- "path": "precision",
5605
- "valueType": "number"
5606
- },
5607
- {
5608
- "path": "format",
5609
- "valueType": "enum",
5610
- "enumValues": [
5611
- "date",
5612
- "time",
5613
- "datetime"
5614
- ]
5615
5691
  }
5616
5692
  ],
5617
5693
  "cover": [
@@ -5852,13 +5928,8 @@ Object.freeze({
5852
5928
  "path": "value",
5853
5929
  "valueType": "string"
5854
5930
  }, {
5855
- "path": "format",
5856
- "valueType": "enum",
5857
- "enumValues": [
5858
- "date",
5859
- "time",
5860
- "datetime"
5861
- ]
5931
+ "path": "timestamp",
5932
+ "valueType": "boolean"
5862
5933
  }],
5863
5934
  "event-emitter": [
5864
5935
  {
@@ -5931,6 +6002,20 @@ Object.freeze({
5931
6002
  "path": "detected",
5932
6003
  "valueType": "boolean"
5933
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
+ ],
5934
6019
  "heating-valve": [{
5935
6020
  "path": "position",
5936
6021
  "valueType": "number"
@@ -5965,20 +6050,10 @@ Object.freeze({
5965
6050
  "valueType": "number"
5966
6051
  }
5967
6052
  ],
5968
- "humidity-sensor": [
5969
- {
5970
- "path": "percent",
5971
- "valueType": "number"
5972
- },
5973
- {
5974
- "path": "unit",
5975
- "valueType": "string"
5976
- },
5977
- {
5978
- "path": "precision",
5979
- "valueType": "number"
5980
- }
5981
- ],
6053
+ "humidity-sensor": [{
6054
+ "path": "percent",
6055
+ "valueType": "number"
6056
+ }],
5982
6057
  "image": [{
5983
6058
  "path": "url",
5984
6059
  "valueType": "string"
@@ -6317,20 +6392,10 @@ Object.freeze({
6317
6392
  "valueType": "boolean"
6318
6393
  }
6319
6394
  ],
6320
- "numeric-sensor": [
6321
- {
6322
- "path": "value",
6323
- "valueType": "number"
6324
- },
6325
- {
6326
- "path": "unit",
6327
- "valueType": "string"
6328
- },
6329
- {
6330
- "path": "precision",
6331
- "valueType": "number"
6332
- }
6333
- ],
6395
+ "numeric-sensor": [{
6396
+ "path": "value",
6397
+ "valueType": "number"
6398
+ }],
6334
6399
  "pet-feeder": [
6335
6400
  {
6336
6401
  "path": "foodLevel",
@@ -6422,14 +6487,6 @@ Object.freeze({
6422
6487
  {
6423
6488
  "path": "powerFactor",
6424
6489
  "valueType": "number"
6425
- },
6426
- {
6427
- "path": "unit",
6428
- "valueType": "string"
6429
- },
6430
- {
6431
- "path": "precision",
6432
- "valueType": "number"
6433
6490
  }
6434
6491
  ],
6435
6492
  "presence": [
@@ -6458,20 +6515,10 @@ Object.freeze({
6458
6515
  "valueType": "number"
6459
6516
  }
6460
6517
  ],
6461
- "pressure-sensor": [
6462
- {
6463
- "path": "hpa",
6464
- "valueType": "number"
6465
- },
6466
- {
6467
- "path": "unit",
6468
- "valueType": "string"
6469
- },
6470
- {
6471
- "path": "precision",
6472
- "valueType": "number"
6473
- }
6474
- ],
6518
+ "pressure-sensor": [{
6519
+ "path": "hpa",
6520
+ "valueType": "number"
6521
+ }],
6475
6522
  "privacy-mask": [{
6476
6523
  "path": "enabled",
6477
6524
  "valueType": "boolean"
@@ -6782,20 +6829,10 @@ Object.freeze({
6782
6829
  "path": "tampered",
6783
6830
  "valueType": "boolean"
6784
6831
  }],
6785
- "temperature-sensor": [
6786
- {
6787
- "path": "celsius",
6788
- "valueType": "number"
6789
- },
6790
- {
6791
- "path": "unit",
6792
- "valueType": "string"
6793
- },
6794
- {
6795
- "path": "precision",
6796
- "valueType": "number"
6797
- }
6798
- ],
6832
+ "temperature-sensor": [{
6833
+ "path": "celsius",
6834
+ "valueType": "number"
6835
+ }],
6799
6836
  "update": [
6800
6837
  {
6801
6838
  "path": "currentVersion",
@@ -6906,10 +6943,6 @@ Object.freeze({
6906
6943
  "path": "temperature",
6907
6944
  "valueType": "number"
6908
6945
  },
6909
- {
6910
- "path": "temperatureUnit",
6911
- "valueType": "string"
6912
- },
6913
6946
  {
6914
6947
  "path": "humidity",
6915
6948
  "valueType": "number"
@@ -6918,18 +6951,10 @@ Object.freeze({
6918
6951
  "path": "pressure",
6919
6952
  "valueType": "number"
6920
6953
  },
6921
- {
6922
- "path": "pressureUnit",
6923
- "valueType": "string"
6924
- },
6925
6954
  {
6926
6955
  "path": "windSpeed",
6927
6956
  "valueType": "number"
6928
6957
  },
6929
- {
6930
- "path": "windSpeedUnit",
6931
- "valueType": "string"
6932
- },
6933
6958
  {
6934
6959
  "path": "windBearing",
6935
6960
  "valueType": "number"
@@ -7458,7 +7483,7 @@ function errMsg(err) {
7458
7483
  return String(err);
7459
7484
  }
7460
7485
  //#endregion
7461
- //#region ../types/dist/composition-kEl9mevu.mjs
7486
+ //#region ../types/dist/composition-B85l9j1M.mjs
7462
7487
  var AUDIO_ENCODER_BY_CODEC = {
7463
7488
  opus: "libopus",
7464
7489
  aac: "aac",
@@ -8976,6 +9001,68 @@ function evaluateExpressionSource(expr, bindingValues, now, opts) {
8976
9001
  }
8977
9002
  }
8978
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
+ /**
8979
9066
  * A COMPOSITION: a device assembled from other devices' state (D659).
8980
9067
  *
8981
9068
  * The data half of the composed-devices spec §1. A composition is stored inside
@@ -9000,11 +9087,6 @@ var COMPOSER_ADDON_ID = "composer";
9000
9087
  var COMPOSED_DEVICE_STABLE_ID_PREFIX = "composed-";
9001
9088
  /** Longest snippet a `code` source may carry (slice 4). */
9002
9089
  var MAX_COMPOSITION_CODE_LENGTH = 2e4;
9003
- var CompositionSourceRefSchema = z.object({
9004
- /** The owning addon; `stableId` is unique only within it. */
9005
- addonId: z.string().min(1),
9006
- stableId: z.string().min(1)
9007
- });
9008
9090
  /** One field of one capability of one source device. */
9009
9091
  var CompositionFieldReadSchema = z.object({
9010
9092
  source: CompositionSourceRefSchema,
@@ -9047,10 +9129,42 @@ var CompositionCodeSourceSchema = z.object({
9047
9129
  kind: z.literal("code"),
9048
9130
  code: z.string().min(1).max(MAX_COMPOSITION_CODE_LENGTH)
9049
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
+ });
9050
9163
  var CompositionFieldSourceSchema = z.discriminatedUnion("kind", [
9051
9164
  CompositionFromSourceSchema,
9052
9165
  CompositionExpressionSourceSchema,
9053
- CompositionCodeSourceSchema
9166
+ CompositionCodeSourceSchema,
9167
+ CompositionAggregateSourceSchema
9054
9168
  ]);
9055
9169
  /** `forward`: RESERVED for slice 3. Argument mapping arrives there as an additive optional `args`. */
9056
9170
  var CompositionForwardCommandSchema = z.object({
@@ -9115,7 +9229,13 @@ var CompositionExistingTargetSchema = z.object({
9115
9229
  var CompositionTargetSchema = z.discriminatedUnion("kind", [CompositionNewTargetSchema, CompositionExistingTargetSchema]);
9116
9230
  var CompositionSchema = z.object({
9117
9231
  target: CompositionTargetSchema,
9118
- 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()
9119
9239
  }).superRefine((composition, ctx) => {
9120
9240
  const seen = /* @__PURE__ */ new Set();
9121
9241
  composition.features.forEach((feature, index) => {
@@ -9162,6 +9282,7 @@ function fieldReads(source) {
9162
9282
  cap: binding.cap,
9163
9283
  fieldPath: binding.fieldPath
9164
9284
  }] : []);
9285
+ case "aggregate": return source.reads;
9165
9286
  case "code": return [];
9166
9287
  }
9167
9288
  }
@@ -17134,6 +17255,17 @@ function unwrapSchema(schema) {
17134
17255
  while (s instanceof z.ZodNullable || s instanceof z.ZodOptional || s instanceof z.ZodDefault) s = s.unwrap();
17135
17256
  return s;
17136
17257
  }
17258
+ function isMetaBag(value) {
17259
+ return typeof value === "object" && value !== null;
17260
+ }
17261
+ function metaQuantity(schema) {
17262
+ const bag = z.globalRegistry.get(schema);
17263
+ if (isMetaBag(bag) && isReadingQuantity(bag.quantity)) return bag.quantity;
17264
+ }
17265
+ /** Quantity declared with `readingMeta` on the field or on the unwrapped leaf. */
17266
+ function quantityOfField(schema) {
17267
+ return metaQuantity(schema) ?? metaQuantity(unwrapSchema(schema));
17268
+ }
17137
17269
  function leafKind(schema) {
17138
17270
  const s = unwrapSchema(schema);
17139
17271
  if (s instanceof z.ZodEnum) return {
@@ -17162,11 +17294,15 @@ function enumerateSchemaFields(schema, prefix = "") {
17162
17294
  continue;
17163
17295
  }
17164
17296
  const leaf = leafKind(field);
17165
- if (leaf) out.push({
17166
- path,
17167
- kind: leaf.kind,
17168
- ...leaf.enumValues ? { enumValues: leaf.enumValues } : {}
17169
- });
17297
+ if (leaf) {
17298
+ const quantity = quantityOfField(field);
17299
+ out.push({
17300
+ path,
17301
+ kind: leaf.kind,
17302
+ ...leaf.enumValues ? { enumValues: leaf.enumValues } : {},
17303
+ ...quantity !== void 0 ? { quantity } : {}
17304
+ });
17305
+ }
17170
17306
  }
17171
17307
  return out;
17172
17308
  }
@@ -17225,7 +17361,7 @@ function requiredTopLevelKeys(schema) {
17225
17361
  /**
17226
17362
  * The proper prefixes of `path` whose schema accepts `undefined`: a required
17227
17363
  * leaf under one of them is required only once something writes that parent
17228
- * (`remaining.unit` is required only when `remaining` is present).
17364
+ * (`remaining.kind` is required only when `remaining` is present).
17229
17365
  */
17230
17366
  function optionalAncestors(schema, path) {
17231
17367
  const segments = path.split(".");
@@ -17337,7 +17473,11 @@ var CompositionProblemCodeSchema = z.enum([
17337
17473
  "item-field-reserved",
17338
17474
  "target-missing",
17339
17475
  "target-unreadable",
17340
- "not-replaceable"
17476
+ "not-replaceable",
17477
+ "native-capability-replace",
17478
+ "mixed-quantity",
17479
+ "helper-mismatch",
17480
+ "member-constraint"
17341
17481
  ]);
17342
17482
  /** One refusal, named (D391). `path` points into the composition, e.g. `features[0].fields.celsius`. */
17343
17483
  var CompositionProblemSchema = z.object({
@@ -17361,7 +17501,8 @@ var CompositionUnavailableModeSchema = z.enum([
17361
17501
  "null",
17362
17502
  "offline",
17363
17503
  "unknown",
17364
- "release"
17504
+ "release",
17505
+ "omit"
17365
17506
  ]);
17366
17507
  /**
17367
17508
  * `derived` reads a source · `clock` reads only `now`, recomputed when a sibling
@@ -17403,9 +17544,11 @@ function compositionFieldKey(plan) {
17403
17544
  return `${plan.cap}.${path}`;
17404
17545
  }
17405
17546
  /**
17406
- * What a feature does to its target (D663): `new` builds a capability on a new
17407
- * device; on an existing device it `add`s a capability the device lacks, or
17408
- * `replace`s named fields of one it has natively.
17547
+ * What a feature does to its target (D663, D944): `new` builds a capability on a
17548
+ * new device; on an existing device it `add`s a capability the device lacks.
17549
+ * Replacing a capability the device already serves is refused
17550
+ * (`native-capability-replace`), not planned. `replace` remains on the claim
17551
+ * wire for a claim the hub already holds.
17409
17552
  */
17410
17553
  var CompositionFeatureModeSchema = z.enum([
17411
17554
  "new",
@@ -17721,6 +17864,24 @@ function inferExpressionKinds(input) {
17721
17864
  * planner (`composition-items.ts`). Internal to the composition module — NOT
17722
17865
  * in the barrel: these are planner internals, not a published contract.
17723
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
+ }
17724
17885
  function readKinds(read, lookupCap) {
17725
17886
  const def = lookupCap(read.cap);
17726
17887
  const schema = def?.runtimeState ?? def?.status?.schema;
@@ -17748,6 +17909,7 @@ function sourceKinds(source, lookupCap) {
17748
17909
  code: "reserved-variant",
17749
17910
  message: "code fields arrive in slice 4"
17750
17911
  };
17912
+ if (source.kind === "aggregate") return aggregateKinds(source);
17751
17913
  const bindingKinds = /* @__PURE__ */ new Map();
17752
17914
  for (const [name, binding] of Object.entries(source.bindings)) {
17753
17915
  if (binding.kind === "literal") {
@@ -17942,6 +18104,379 @@ function planItems(def, feature, featureIndex, lookupCap, mode = "new") {
17942
18104
  };
17943
18105
  }
17944
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
+ /**
17945
18480
  * The field graph across every composition, and the cycles in it (D659).
17946
18481
  *
17947
18482
  * A node is ONE field of ONE device (`<addon>/<stableId>#<cap>.<field>`); an
@@ -18146,6 +18681,7 @@ function planTopLevelField({ def, fields }, field, mode, lookupCap) {
18146
18681
  const role = roleOf(source);
18147
18682
  const unavailable = unavailableFor(tf, role, mode, def.name, fieldAt);
18148
18683
  if (!unavailable.ok) return refused(unavailable.problem);
18684
+ const omitEmpty = source.kind === "aggregate" && source.publish === "value" && source.empty === "omit";
18149
18685
  const plan = {
18150
18686
  cap: def.name,
18151
18687
  path,
@@ -18153,7 +18689,7 @@ function planTopLevelField({ def, fields }, field, mode, lookupCap) {
18153
18689
  kind: tf.kind,
18154
18690
  nullable: tf.nullable,
18155
18691
  required: tf.required,
18156
- unavailable: unavailable.unavailable,
18692
+ unavailable: omitEmpty ? "omit" : unavailable.unavailable,
18157
18693
  role,
18158
18694
  source,
18159
18695
  reads: [...fieldReads(source)]
@@ -18202,6 +18738,9 @@ function isNativeCap(target, cap) {
18202
18738
  const owner = target.nativeCaps.get(cap);
18203
18739
  return owner !== void 0 && owner !== "composer";
18204
18740
  }
18741
+ function isPlanProblem(value) {
18742
+ return "code" in value;
18743
+ }
18205
18744
  /** The target read, or the refusal that replaces the whole plan when it could not be (D391). */
18206
18745
  function planTargetOf(composition, target) {
18207
18746
  if (composition.target.kind === "new") return {
@@ -18217,26 +18756,59 @@ function planTargetOf(composition, target) {
18217
18756
  found: target
18218
18757
  };
18219
18758
  }
18220
- function featureTargetOf(target, cap) {
18759
+ var NATIVE_REPLACE_KEPT = new Set([
18760
+ "not-composable",
18761
+ "unknown-capability",
18762
+ "reserved-variant"
18763
+ ]);
18764
+ /** A native override is refused, unless the cap cannot be composed at all — that reason stays. */
18765
+ function nativeReplaceOrBlock(refusal, probed) {
18766
+ const kept = probed.problems.filter((item) => NATIVE_REPLACE_KEPT.has(item.code));
18767
+ if (kept.length > 0) return {
18768
+ problems: kept,
18769
+ fields: [],
18770
+ features: [],
18771
+ events: []
18772
+ };
18773
+ return refused(refusal);
18774
+ }
18775
+ function featureTargetOf(target, cap, at) {
18221
18776
  if (target.kind === "new") return {
18222
18777
  mode: "new",
18223
18778
  deviceType: target.type
18224
18779
  };
18780
+ if (isNativeCap(target.found, cap)) return problem("native-capability-replace", at, `native-capability-replace: stored override replaces native capability \`${cap}\` on device ${target.found.deviceId} — refused (D944)`);
18225
18781
  return {
18226
- mode: isNativeCap(target.found, cap) ? "replace" : "add",
18782
+ mode: "add",
18227
18783
  deviceType: target.found.type
18228
18784
  };
18229
18785
  }
18230
18786
  /**
18231
18787
  * The static plan. `target` describes an EXISTING target device (one read);
18232
- * it is ignored for a new one. On an existing target each feature REPLACES a
18233
- * cap the device has natively, else ADDS it (D663).
18788
+ * it is ignored for a new one. On an existing target each feature ADDS a cap
18789
+ * the device lacks. A feature that would replace a native capability is
18790
+ * refused by name (D944). A new device is unchanged.
18234
18791
  */
18235
18792
  function planComposition(composition, lookupCap, target) {
18236
18793
  const planTarget = planTargetOf(composition, target);
18237
18794
  if ("code" in planTarget) return refused(planTarget);
18238
18795
  const results = composition.features.map((feature, index) => {
18239
- const result = feature.kind === "passthrough" ? refused(problem("reserved-variant", `features[${index}]`, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`)) : planFeature(feature, index, featureTargetOf(planTarget, feature.cap), lookupCap);
18796
+ const at = `features[${index}]`;
18797
+ if (feature.kind === "passthrough") return refused(problem("reserved-variant", at, `whole-capability passthrough of \`${feature.cap}\` arrives in slice 3`));
18798
+ const featureTarget = featureTargetOf(planTarget, feature.cap, at);
18799
+ if (isPlanProblem(featureTarget)) {
18800
+ if (!compositionFeatureEnabled(feature) || planTarget.kind !== "existing") return {
18801
+ problems: [],
18802
+ fields: [],
18803
+ features: [],
18804
+ events: []
18805
+ };
18806
+ return nativeReplaceOrBlock(featureTarget, planFeature(feature, index, {
18807
+ mode: "add",
18808
+ deviceType: planTarget.found.type
18809
+ }, lookupCap));
18810
+ }
18811
+ const result = planFeature(feature, index, featureTarget, lookupCap);
18240
18812
  return compositionFeatureEnabled(feature) ? result : {
18241
18813
  problems: result.problems,
18242
18814
  fields: [],
@@ -18377,11 +18949,20 @@ function cycleProblems(composition, ctx, fields, self) {
18377
18949
  function validateComposition(composition, ctx) {
18378
18950
  const plan = planComposition(composition, ctx.lookupCap, ctx.target ?? void 0);
18379
18951
  const self = candidateSelf(composition, ctx, plan);
18380
- const cycles = cycleProblems(composition, ctx, plan.fields, self);
18952
+ const refusedFeatures = plan.problems.filter((item) => item.code === "native-capability-replace").map((item) => item.path);
18953
+ const cycles = cycleProblems(composition, ctx, plan.fields, self).filter((item) => {
18954
+ if (refusedFeatures.some((at) => item.path === at || item.path.startsWith(`${at}.`))) return false;
18955
+ return refusedFeatures.length === 0 || item.path !== "features";
18956
+ });
18957
+ const facts = new Map([...ctx.sources].flatMap(([key, source]) => source.kind === "found" ? [[key, {
18958
+ deviceType: source.deviceType,
18959
+ quantity: source.quantity
18960
+ }]] : []));
18381
18961
  const problems = [
18382
18962
  ...plan.problems,
18383
18963
  ...sourceProblems(plan.fields, ctx, self),
18384
- ...cycles
18964
+ ...cycles,
18965
+ ...helperSaveProblems(composition, facts)
18385
18966
  ];
18386
18967
  return {
18387
18968
  ok: problems.length === 0,
@@ -20894,12 +21475,12 @@ getAdminLink: method(z.object({ deviceId: z.number().int().nonnegative() }), Dev
20894
21475
  * providers detect renames by matching `uniqueId` and patch `id`
20895
21476
  * in-place so dispatch keeps working.
20896
21477
  *
20897
- * **Rendering metadata is NOT stored here.** Display fields (`unit`,
20898
- * `precision`) live in the cap STATUS SLICE so they flow live with every
20899
- * state push and are the single source of truth. The `device_class` tag is
20900
- * an HA-specific input that is consumed at the discovery boundary
20901
- * (`ha-domain-mapping`) to derive the CamStack `DeviceRole`; it is never
20902
- * stored after that point.
21478
+ * **Display unit and precision are NOT stored here.** They are a read-time
21479
+ * preference (D944). A polymorphic reading stores its `quantity` here once,
21480
+ * at adopt, and a date/time value stores `dateTimeShape` the same way —
21481
+ * neither is repeated on each sample. The `device_class` tag is an
21482
+ * HA-specific input consumed at the discovery boundary (`ha-domain-mapping`)
21483
+ * to derive the CamStack `DeviceRole`; it is never stored after that point.
20903
21484
  *
20904
21485
  * The `raw` escape hatch carries provider-specific attributes that don't
20905
21486
  * fit the structured slots (HA `attributes` whitelist, Reolink `deviceCache`
@@ -20913,6 +21494,14 @@ var SourceInfoSchema = z.object({
20913
21494
  /** Immutable upstream identifier when available (HA `unique_id`, … ).
20914
21495
  * Used to detect rename when `id` changes. */
20915
21496
  uniqueId: z.string().optional(),
21497
+ /** Quantity of a polymorphic reading, written once at adopt (D944). */
21498
+ quantity: ReadingQuantitySchema.optional(),
21499
+ /** Date / time / datetime shape of a string reading, written once at adopt. */
21500
+ dateTimeShape: z.enum([
21501
+ "date",
21502
+ "time",
21503
+ "datetime"
21504
+ ]).optional(),
20916
21505
  /** Provider-specific extras that don't fit the structured slots.
20917
21506
  * Whitelist what goes here per provider — do NOT dump entire HA
20918
21507
  * attribute blobs (would explode the meta JSON column). */
@@ -20997,9 +21586,38 @@ function normalizeUnit(raw) {
20997
21586
  if (lower === "mph") return "mph";
20998
21587
  if (lower === "m/s") return "m/s";
20999
21588
  if (lower === "m") return "m";
21589
+ if (lower === "m²" || lower === "m2") return "m²";
21590
+ if (lower === "mm") return "mm";
21591
+ if (lower === "cm") return "cm";
21000
21592
  if (lower === "km") return "km";
21593
+ if (lower === "in" || lower === "inch" || lower === "inches") return "in";
21594
+ if (lower === "ft" || lower === "foot" || lower === "feet") return "ft";
21001
21595
  if (lower === "kg") return "kg";
21002
21596
  if (lower === "g") return "g";
21597
+ if (lower === "l" || lower === "liter" || lower === "litre" || lower === "liters" || lower === "litres") return "L";
21598
+ if (lower === "ml") return "mL";
21599
+ if (lower === "gal" || lower === "gallon" || lower === "gallons") return "gal";
21600
+ if (lower === "s" || lower === "sec" || lower === "second" || lower === "seconds") return "s";
21601
+ if (lower === "ms") return "ms";
21602
+ if (lower === "min" || lower === "minute" || lower === "minutes") return "min";
21603
+ if (lower === "h" || lower === "hr" || lower === "hour" || lower === "hours") return "h";
21604
+ if (lower === "d" || lower === "day" || lower === "days") return "d";
21605
+ if (lower === "b" || lower === "byte" || lower === "bytes") return "B";
21606
+ if (lower === "kib") return "KiB";
21607
+ if (lower === "mib") return "MiB";
21608
+ if (lower === "gib") return "GiB";
21609
+ if (lower === "tib") return "TiB";
21610
+ if (lower === "kb") return "kB";
21611
+ if (lower === "mb") return "MB";
21612
+ if (lower === "gb") return "GB";
21613
+ if (lower === "dbm") return "dBm";
21614
+ if (lower === "dbfs") return "dBFS";
21615
+ if (lower === "inhg") return "inHg";
21616
+ if (lower === "mm/h") return "mm/h";
21617
+ if (lower === "w/m²" || lower === "w/m2") return "W/m²";
21618
+ if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
21619
+ if (lower === "kbps") return "kbps";
21620
+ if (lower === "fps") return "fps";
21003
21621
  return trimmed;
21004
21622
  }
21005
21623
  /**
@@ -42261,7 +42879,8 @@ var deviceManagerCapability = {
42261
42879
  "enum"
42262
42880
  ]),
42263
42881
  enumValues: z.array(z.string()).optional(),
42264
- item: z.boolean().optional()
42882
+ item: z.boolean().optional(),
42883
+ quantity: ReadingQuantitySchema.optional()
42265
42884
  })).readonly(),
42266
42885
  itemArray: z.object({
42267
42886
  path: z.string(),
@@ -47526,8 +48145,8 @@ var MaskPolygonShapeSchema = z.object({
47526
48145
  /** Boolean cell grid — row-major, length === gridWidth*gridHeight. */
47527
48146
  var MaskGridShapeSchema = z.object({
47528
48147
  kind: z.literal("grid"),
47529
- gridWidth: z.number(),
47530
- gridHeight: z.number(),
48148
+ gridWidth: z.number().meta(readingMeta("none")),
48149
+ gridHeight: z.number().meta(readingMeta("none")),
47531
48150
  cells: z.array(z.boolean())
47532
48151
  });
47533
48152
  /** Directed line / tripwire — an ordered list of normalized points (≥2). */
@@ -62076,27 +62695,17 @@ var addonsCapability = {
62076
62695
  */
62077
62696
  var AirQualitySensorStatusSchema = z.object({
62078
62697
  /** Carbon dioxide concentration in ppm. */
62079
- co2Ppm: z.number().min(0).optional(),
62698
+ co2Ppm: z.number().min(0).optional().meta(readingMeta("concentration-volume")),
62080
62699
  /** Total volatile organic compounds in ppb. */
62081
- vocPpb: z.number().min(0).optional(),
62700
+ vocPpb: z.number().min(0).optional().meta(readingMeta("concentration-ppb")),
62082
62701
  /** Particulate matter ≤ 2.5 μm in µg/m³. */
62083
- pm25: z.number().min(0).optional(),
62702
+ pm25: z.number().min(0).optional().meta(readingMeta("concentration-mass")),
62084
62703
  /** Particulate matter ≤ 10 μm in µg/m³. */
62085
- pm10: z.number().min(0).optional(),
62704
+ pm10: z.number().min(0).optional().meta(readingMeta("concentration-mass")),
62086
62705
  /** Composite AQI value (typically 0..500). */
62087
- aqi: z.number().optional(),
62706
+ aqi: z.number().optional().meta(readingMeta("index")),
62088
62707
  /** Ms epoch when the slice was last updated. */
62089
- lastFetchedAt: z.number(),
62090
- /** Live display unit of the single metric this slice carries (e.g. HA
62091
- * `attributes.unit_of_measurement` → 'ppm' / 'ppb' / 'µg/m³'). Each
62092
- * upstream `sensor.*` entity surfaces ONE device_class, so one unit
62093
- * per slice is unambiguous. */
62094
- unit: z.string().optional(),
62095
- /** Suggested decimal places for numeric display.
62096
- * Populated live from the upstream source when provided (e.g. HA
62097
- * `attributes.suggested_display_precision`). Falls back to
62098
- * auto-formatting when absent. */
62099
- precision: z.number().int().min(0).max(10).optional()
62708
+ lastFetchedAt: z.number()
62100
62709
  });
62101
62710
  var airQualitySensorCapability = {
62102
62711
  name: "air-quality-sensor",
@@ -62133,12 +62742,12 @@ var airQualitySensorCapability = {
62133
62742
  },
62134
62743
  pm25: {
62135
62744
  kind: "gauge",
62136
- unit: "ug/m3",
62745
+ unit: "µg/m³",
62137
62746
  deadband: 1
62138
62747
  },
62139
62748
  pm10: {
62140
62749
  kind: "gauge",
62141
- unit: "ug/m3",
62750
+ unit: "µg/m³",
62142
62751
  deadband: 1
62143
62752
  },
62144
62753
  aqi: {
@@ -62156,22 +62765,13 @@ var airQualitySensorCapability = {
62156
62765
  * entries with `device_class: illuminance`.
62157
62766
  */
62158
62767
  var AmbientLightSensorStatusSchema = z.object({
62159
- /** Current illuminance in lux (lx). */
62160
- lux: z.number().min(0).meta({
62161
- unit: "lx",
62768
+ /** Current illuminance in lux (lx). `null` when the sample was refused (D393). */
62769
+ lux: z.number().min(0).nullable().meta(readingMeta("illuminance", {
62770
+ precision: 0,
62162
62771
  label: "Illuminance"
62163
- }),
62772
+ })),
62164
62773
  /** Ms epoch when the slice was last updated. */
62165
- lastFetchedAt: z.number(),
62166
- /** Live display unit from the upstream source (e.g. HA
62167
- * `attributes.unit_of_measurement`). The UI prefers this over the
62168
- * role's canonical unit. Absent → fall back to the canonical unit. */
62169
- unit: z.string().optional(),
62170
- /** Suggested decimal places for numeric display.
62171
- * Populated live from the upstream source when provided (e.g. HA
62172
- * `attributes.suggested_display_precision`). Falls back to
62173
- * auto-formatting when absent. */
62174
- precision: z.number().int().min(0).max(10).optional()
62774
+ lastFetchedAt: z.number()
62175
62775
  });
62176
62776
  var ambientLightSensorCapability = {
62177
62777
  name: "ambient-light-sensor",
@@ -62203,12 +62803,7 @@ var ambientLightSensorCapability = {
62203
62803
  } } },
62204
62804
  /** Clock fields: written, but excluded from the compare that decides
62205
62805
  * whether persisting is worth a SQLite commit. */
62206
- volatileStateFields: ["lastFetchedAt"],
62207
- customize: {
62208
- version: 1,
62209
- advanced: "derive",
62210
- targets: []
62211
- }
62806
+ volatileStateFields: ["lastFetchedAt"]
62212
62807
  };
62213
62808
  /**
62214
62809
  * Per-class audio metrics aggregated over a sliding window.
@@ -62444,10 +63039,7 @@ var BatteryStatusSchema = z.object({
62444
63039
  * Consumers must SKIP a null rather than coerce it — `battery-band` already
62445
63040
  * declines to band a non-finite reading, which is the correct shape.
62446
63041
  */
62447
- percentage: z.number().min(0).max(100).nullable().meta({
62448
- unit: "%",
62449
- label: "Charge"
62450
- }),
63042
+ percentage: z.number().min(0).max(100).nullable().meta(readingMeta("percentage", { label: "Charge" })),
62451
63043
  /**
62452
63044
  * Charging source. `'dc'` covers wall/USB adapters; `'solar'` is
62453
63045
  * Reolink-specific for the Solar Panel 2 accessory (will become
@@ -62474,7 +63066,14 @@ var BatteryStatusSchema = z.object({
62474
63066
  */
62475
63067
  sleeping: z.boolean().meta({ label: "Sleeping" }),
62476
63068
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
62477
- lastUpdated: z.number(),
63069
+ lastUpdated: z.number().meta(readingMeta("none")),
63070
+ /**
63071
+ * Battery voltage in volts, for a cell the firmware reports as voltage
63072
+ * rather than a percentage (Ecowitt capacitor packs). Absent on cameras
63073
+ * that only report charge. A second reading on this cap, not a second
63074
+ * `numeric-sensor` on the same device.
63075
+ */
63076
+ volts: z.number().nullable().optional().meta(readingMeta("voltage", { label: "Voltage" })),
62478
63077
  /**
62479
63078
  * Ms epoch of the last time the device PROVED it was reachable — a
62480
63079
  * completed firmware round-trip, an observed wake, or an inbound push
@@ -62526,7 +63125,6 @@ var BATTERY_DEVICE_TYPES = [
62526
63125
  /** Generic Customize target for the percentage reading (D936). */
62527
63126
  var batteryCustomize = {
62528
63127
  version: 1,
62529
- advanced: "derive",
62530
63128
  targets: [{
62531
63129
  id: "percentage",
62532
63130
  cardinality: "scalar",
@@ -62535,7 +63133,7 @@ var batteryCustomize = {
62535
63133
  stateField: "percentage"
62536
63134
  },
62537
63135
  deviceTypes: BATTERY_DEVICE_TYPES,
62538
- apply: "add-or-replace",
63136
+ apply: "add",
62539
63137
  path: "percentage",
62540
63138
  sources: [{
62541
63139
  kind: "field",
@@ -62656,10 +63254,16 @@ onStatusChanged: { data: z.object({
62656
63254
  /**
62657
63255
  * History (D850): recorded series. The generic capture replaces the `battery` series of `pipeline-analytics:stats-5m`.
62658
63256
  */
62659
- history: { fields: { percentage: {
62660
- kind: "gauge",
62661
- unit: "%"
62662
- } } },
63257
+ history: { fields: {
63258
+ percentage: {
63259
+ kind: "gauge",
63260
+ unit: "%"
63261
+ },
63262
+ volts: {
63263
+ kind: "gauge",
63264
+ unit: "V"
63265
+ }
63266
+ } },
62663
63267
  customize: batteryCustomize,
62664
63268
  /** Clock fields: written, but excluded from the compare that decides
62665
63269
  * whether persisting is worth a SQLite commit. */
@@ -62706,12 +63310,7 @@ var binaryCapability = {
62706
63310
  history: { fields: { on: { kind: "boolean" } } },
62707
63311
  /** Clock fields: written, but excluded from the compare that decides
62708
63312
  * whether persisting is worth a SQLite commit. */
62709
- volatileStateFields: ["lastChangedAt"],
62710
- customize: {
62711
- version: 1,
62712
- advanced: "derive",
62713
- targets: []
62714
- }
63313
+ volatileStateFields: ["lastChangedAt"]
62715
63314
  };
62716
63315
  /**
62717
63316
  * Dimmable-light brightness control. Co-exists with `switch` on the
@@ -62727,7 +63326,7 @@ var binaryCapability = {
62727
63326
  */
62728
63327
  var BrightnessStatusSchema = z.object({
62729
63328
  /** Current level as 0..100 inclusive. Firmware-reported. */
62730
- percentage: z.number().min(0).max(100),
63329
+ percentage: z.number().min(0).max(100).meta(readingMeta("percentage")),
62731
63330
  /** Ms epoch of the last operator-driven change. Useful for UI freshness. */
62732
63331
  lastChangedAt: z.number()
62733
63332
  });
@@ -63278,21 +63877,21 @@ var ClimateControlStatusSchema = z.object({
63278
63877
  /** Single setpoint in Celsius. Used by single-target modes
63279
63878
  * (heat / cool / dry). Null when the device is in a range mode
63280
63879
  * (`heat_cool`) or has no setpoint at all (`fan_only`/`off`). */
63281
- target: z.number().nullable(),
63880
+ target: z.number().nullable().meta(readingMeta("temperature")),
63282
63881
  /** Upper bound of the dual setpoint in Celsius. Populated only
63283
63882
  * in `heat_cool` mode. */
63284
- targetHigh: z.number().nullable(),
63883
+ targetHigh: z.number().nullable().meta(readingMeta("temperature")),
63285
63884
  /** Lower bound of the dual setpoint in Celsius. Populated only
63286
63885
  * in `heat_cool` mode. */
63287
- targetLow: z.number().nullable(),
63886
+ targetLow: z.number().nullable().meta(readingMeta("temperature")),
63288
63887
  /** Target relative humidity (0..100). Null when the device has no
63289
63888
  * humidity control. */
63290
- targetHumidity: z.number().min(0).max(100).nullable(),
63889
+ targetHumidity: z.number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63291
63890
  /** Current measured temperature in Celsius. Read-only — pushed by
63292
63891
  * the device's internal sensor. */
63293
- currentTemp: z.number().nullable(),
63892
+ currentTemp: z.number().nullable().meta(readingMeta("temperature")),
63294
63893
  /** Current measured relative humidity (0..100). Read-only. */
63295
- currentHumidity: z.number().min(0).max(100).nullable(),
63894
+ currentHumidity: z.number().min(0).max(100).nullable().meta(readingMeta("humidity")),
63296
63895
  /** Vertical louver swing on/off. Null when the device has no vertical
63297
63896
  * swing surface (gate with `DeviceFeature.ClimateSwingVertical`). */
63298
63897
  swingVertical: z.boolean().nullable(),
@@ -63300,11 +63899,11 @@ var ClimateControlStatusSchema = z.object({
63300
63899
  * horizontal swing surface (gate with `DeviceFeature.ClimateSwingHorizontal`). */
63301
63900
  swingHorizontal: z.boolean().nullable(),
63302
63901
  /** Lowest setpoint the device accepts, Celsius. Absent / null = not reported. */
63303
- minTemp: z.number().nullable().optional(),
63902
+ minTemp: z.number().nullable().optional().meta(readingMeta("temperature")),
63304
63903
  /** Highest setpoint the device accepts, Celsius. Absent / null = not reported. */
63305
- maxTemp: z.number().nullable().optional(),
63904
+ maxTemp: z.number().nullable().optional().meta(readingMeta("temperature")),
63306
63905
  /** Setpoint granularity, Celsius (0.5, 1). Absent / null = not reported. */
63307
- tempStep: z.number().positive().nullable().optional(),
63906
+ tempStep: z.number().positive().nullable().optional().meta(readingMeta("temperature")),
63308
63907
  /** Ms epoch when the slice was last updated (push or command). */
63309
63908
  lastFetchedAt: z.number()
63310
63909
  });
@@ -63446,17 +64045,17 @@ var climateControlCapability = {
63446
64045
  * modes to expose in the picker without a slice fetch.
63447
64046
  */
63448
64047
  var RgbTripletSchema = z.object({
63449
- r: z.number().int().min(0).max(255),
63450
- g: z.number().int().min(0).max(255),
63451
- b: z.number().int().min(0).max(255)
64048
+ r: z.number().int().min(0).max(255).meta(readingMeta("none")),
64049
+ g: z.number().int().min(0).max(255).meta(readingMeta("none")),
64050
+ b: z.number().int().min(0).max(255).meta(readingMeta("none"))
63452
64051
  });
63453
64052
  var HsvTripletSchema = z.object({
63454
64053
  /** Hue in degrees, 0..360. */
63455
- h: z.number().min(0).max(360),
64054
+ h: z.number().min(0).max(360).meta(readingMeta("none")),
63456
64055
  /** Saturation as 0..100 inclusive. */
63457
- s: z.number().min(0).max(100),
64056
+ s: z.number().min(0).max(100).meta(readingMeta("none")),
63458
64057
  /** Value/brightness as 0..100 inclusive. */
63459
- v: z.number().min(0).max(100)
64058
+ v: z.number().min(0).max(100).meta(readingMeta("none"))
63460
64059
  });
63461
64060
  var ColorInputSchema = z.discriminatedUnion("mode", [
63462
64061
  z.object({
@@ -63485,7 +64084,7 @@ var ColorStatusSchema = z.object({
63485
64084
  /** Populated when `mode === 'hsv'`. */
63486
64085
  hsv: HsvTripletSchema.optional(),
63487
64086
  /** Populated when `mode === 'mired'`. */
63488
- mireds: z.number().int().optional(),
64087
+ mireds: z.number().int().optional().meta(readingMeta("colour-temperature")),
63489
64088
  /** Ms epoch of the last operator-driven change. */
63490
64089
  lastChangedAt: z.number()
63491
64090
  });
@@ -63688,18 +64287,12 @@ z.enum([
63688
64287
  "empty",
63689
64288
  "full"
63690
64289
  ]);
63691
- /** Units a remaining-life counter is reported in. Closed: the panel renders
63692
- * each. */
63693
- var ConsumableRemainingUnitSchema = z.enum([
63694
- "days",
63695
- "hours",
63696
- "cycles"
63697
- ]);
63698
- /** Remaining life as a COUNT, not a percentage (the PetKit desiccant reports
63699
- * days). `value` null = unknown (D393). */
64290
+ /** Whether `remaining.value` is a duration in seconds or a dimensionless count. */
64291
+ var ConsumableRemainingKindSchema = z.enum(["duration", "count"]);
64292
+ /** Remaining life. `value` null = unknown (D393). Duration is seconds. */
63700
64293
  var ConsumableRemainingSchema = z.object({
63701
- value: z.number().min(0).nullable(),
63702
- unit: ConsumableRemainingUnitSchema
64294
+ value: z.number().min(0).nullable().meta(readingMeta("device")),
64295
+ kind: ConsumableRemainingKindSchema
63703
64296
  });
63704
64297
  /** A single consumable item. Either a continuous `level` (remaining
63705
64298
  * life %, or fill % for a `supply` / `waste` item) or a discrete `status`
@@ -63712,7 +64305,7 @@ var ConsumableItemSchema = z.object({
63712
64305
  /** Display name. */
63713
64306
  label: z.string().min(1),
63714
64307
  /** Remaining life % (`wear`) or fill % (`supply`, `waste`) when known (0..100). */
63715
- level: z.number().min(0).max(100).nullable(),
64308
+ level: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
63716
64309
  /** The provider's discrete verdict when known (binary mode). */
63717
64310
  status: ConsumableStatusSchema.nullable(),
63718
64311
  /** What the item is — decides whether low or full is bad. Absent = `wear`. */
@@ -63794,8 +64387,8 @@ var consumablesCustomize = {
63794
64387
  transforms: [{ kind: "identity" }]
63795
64388
  }],
63796
64389
  literals: [{
63797
- path: "remaining.unit",
63798
- values: [...ConsumableRemainingUnitSchema.options]
64390
+ path: "remaining.kind",
64391
+ values: [...ConsumableRemainingKindSchema.options]
63799
64392
  }]
63800
64393
  },
63801
64394
  {
@@ -63924,7 +64517,6 @@ reset: method(z.object({
63924
64517
  },
63925
64518
  "remaining.value": {
63926
64519
  kind: "gauge",
63927
- unitFrom: "remaining.unit",
63928
64520
  deadband: 1,
63929
64521
  displayName: "Remaining"
63930
64522
  }
@@ -64023,49 +64615,26 @@ var ControlKindSchema = z.enum([
64023
64615
  * (empty array for the other kinds). It lives in the slice because
64024
64616
  * HA `select.*` entities may update their options at runtime.
64025
64617
  *
64026
- * The `unit`/`min`/`max`/`step` fields are meaningful for
64027
- * `kind === 'numeric'` and are absent (undefined) for all other
64028
- * kinds. They are populated live from HA attributes on each state
64029
- * push — consistent with `options` and the sensor-unit approach.
64618
+ * Numeric `min` / `max` / `step` live in the slice and share the device
64619
+ * quantity (`SourceInfo.quantity`, D944). Display precision is a read-time
64620
+ * preference. A datetime control's shape is `SourceInfo.dateTimeShape`.
64030
64621
  */
64031
64622
  var ControlStatusSchema = z.object({
64032
64623
  kind: ControlKindSchema,
64033
64624
  /** Current value. Numeric kind → number; select/text/datetime → string.
64034
64625
  * Datetime values are ISO 8601 strings (full timestamp, date-only,
64035
64626
  * or time-only — HA accepts all three forms). */
64036
- value: z.union([z.number(), z.string()]),
64627
+ value: z.union([z.number().meta(readingMeta("device")), z.string()]),
64037
64628
  /** Acceptable values when `kind === 'select'`. Empty for other kinds. */
64038
64629
  options: z.array(z.string()),
64039
64630
  /** Ms epoch when the slice was last updated. */
64040
64631
  lastChangedAt: z.number(),
64041
- /** Display unit-of-measurement (e.g. '°C', '%', 'lx').
64042
- * Populated live from HA `attributes.unit_of_measurement` on each push.
64043
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
64044
- unit: z.string().optional(),
64045
- /** Minimum allowed value. Populated live from HA `attributes.min`.
64046
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
64047
- min: z.number().optional(),
64048
- /** Maximum allowed value. Populated live from HA `attributes.max`.
64049
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
64050
- max: z.number().optional(),
64051
- /** Step increment. Populated live from HA `attributes.step`.
64052
- * Meaningful for `kind === 'numeric'`; absent for other kinds. */
64053
- step: z.number().optional(),
64054
- /** Suggested decimal places for numeric display. Populated live from HA
64055
- * `attributes.suggested_display_precision`, or derived from `step` when the
64056
- * HA attribute is absent. Meaningful for `kind === 'numeric'`; absent for
64057
- * other kinds. Falls back to auto-formatting when absent. */
64058
- precision: z.number().int().min(0).max(10).optional(),
64059
- /** Date-picker granularity for `kind === 'datetime'`. 'date' → date-only,
64060
- * 'time' → time-only, 'datetime' → full timestamp. Absent for other kinds
64061
- * (UI defaults to 'datetime'). Derived live from the device's HA domain
64062
- * (date.* / time.* / datetime.*) or, for input_datetime, from the entity's
64063
- * `has_date`/`has_time` attributes on each push. */
64064
- format: z.enum([
64065
- "date",
64066
- "time",
64067
- "datetime"
64068
- ]).optional()
64632
+ /** Minimum allowed value, in the device quantity's base unit. */
64633
+ min: z.number().optional().meta(readingMeta("device")),
64634
+ /** Maximum allowed value, in the device quantity's base unit. */
64635
+ max: z.number().optional().meta(readingMeta("device")),
64636
+ /** Step increment, in the device quantity's base unit. */
64637
+ step: z.number().optional().meta(readingMeta("device"))
64069
64638
  });
64070
64639
  var ControlSetValueInputSchema = z.discriminatedUnion("kind", [
64071
64640
  z.object({
@@ -64153,9 +64722,9 @@ var CoverStatusSchema = z.object({
64153
64722
  state: CoverStateSchema,
64154
64723
  /** 0 = fully closed, 100 = fully open. Null when the device has
64155
64724
  * no intermediate position surface. */
64156
- position: z.number().min(0).max(100).nullable(),
64725
+ position: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64157
64726
  /** 0 = closed slats, 100 = open slats. Null when no tilt support. */
64158
- tiltPosition: z.number().min(0).max(100).nullable(),
64727
+ tiltPosition: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64159
64728
  /** Ms epoch when the slice was last updated. */
64160
64729
  lastChangedAt: z.number()
64161
64730
  });
@@ -64258,9 +64827,9 @@ var NormalizedRangeSchema$1 = z.object({
64258
64827
  var DayNightStatusSchema = z.object({
64259
64828
  mode: DayNightModeSchema,
64260
64829
  /** IR-cut trigger sensitivity, NORMALIZED 0–100 (higher = switches to night sooner). */
64261
- sensitivity: z.number().optional(),
64830
+ sensitivity: z.number().optional().meta(readingMeta("none")),
64262
64831
  /** Delay before the IR-cut filter flips, in seconds. */
64263
- switchDelaySec: z.number().optional(),
64832
+ switchDelaySec: z.number().optional().meta(readingMeta("duration")),
64264
64833
  lastFetchedAt: z.number()
64265
64834
  });
64266
64835
  /**
@@ -64487,11 +65056,11 @@ var DishwasherStatusSchema = z.object({
64487
65056
  /** Feature keys of the programs the appliance currently offers — drives the UI selector. */
64488
65057
  availablePrograms: z.array(z.string()),
64489
65058
  /** Seconds remaining on the active cycle, or null when not running / not known. */
64490
- remainingSeconds: z.number().int().nonnegative().nullable(),
65059
+ remainingSeconds: z.number().int().nonnegative().nullable().meta(readingMeta("duration")),
64491
65060
  /** Seconds the delayed start is set to (time until the cycle starts), or null. */
64492
- delayedStartSeconds: z.number().int().nonnegative().nullable(),
65061
+ delayedStartSeconds: z.number().int().nonnegative().nullable().meta(readingMeta("duration")),
64493
65062
  /** Cycle completion 0..100, or null when not applicable. */
64494
- progressPercent: z.number().min(0).max(100).nullable(),
65063
+ progressPercent: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
64495
65064
  /** True when the door is reported open; null when the door state is not known. */
64496
65065
  doorOpen: z.boolean().nullable(),
64497
65066
  /** True when the door is latched (locked) during a cycle; null when not reported. */
@@ -64515,7 +65084,7 @@ var DishwasherStatusSchema = z.object({
64515
65084
  /** True while the machine-care (descaling / cleaning-program) reminder is raised; false when clear; null when not reported. */
64516
65085
  machineCareDue: z.boolean().nullable(),
64517
65086
  /** Program runs left before the machine-care reminder is due; null when the appliance does not say. */
64518
- machineCareRemainingRuns: z.number().int().nonnegative().nullable(),
65087
+ machineCareRemainingRuns: z.number().int().nonnegative().nullable().meta(readingMeta("count")),
64519
65088
  /** Option states: null when the appliance does not expose the option or has not said. */
64520
65089
  halfLoad: z.boolean().nullable(),
64521
65090
  hygienePlus: z.boolean().nullable(),
@@ -64530,7 +65099,7 @@ var DishwasherStatusSchema = z.object({
64530
65099
  /** Feature keys of the events currently raised (salt low, rinse aid low, program finished, …). */
64531
65100
  activeEvents: z.array(z.string()),
64532
65101
  /** Numeric error code when the appliance reports one; null otherwise. */
64533
- errorCode: z.number().nullable(),
65102
+ errorCode: z.number().nullable().meta(readingMeta("none")),
64534
65103
  /** Name of the raised alert-level event while the appliance is in error; null otherwise. */
64535
65104
  errorLabel: z.string().nullable(),
64536
65105
  /** Ms epoch of the last value received from the appliance; 0 = never. */
@@ -64761,7 +65330,7 @@ var DoorbellStatusSchema = z.object({
64761
65330
  /** Ms epoch of the last press. null = never observed since this provider started. */
64762
65331
  lastPressedAt: z.number().nullable(),
64763
65332
  /** Counter since provider start. Resets on reboot. Useful for metrics/debug. */
64764
- pressCountSinceStart: z.number()
65333
+ pressCountSinceStart: z.number().meta(readingMeta("count"))
64765
65334
  });
64766
65335
  var DoorbellPressEventSchema = z.object({
64767
65336
  deviceId: z.number(),
@@ -64826,17 +65395,7 @@ onPressed: {
64826
65395
  history: "none",
64827
65396
  customize: doorbellCustomize
64828
65397
  };
64829
- /**
64830
- * Enum-state sensor — a string value picked from a finite option set.
64831
- * Drives HA `sensor` entries with `state_class: enum` (HVAC action
64832
- * states, weather conditions, contact-source channels, mode strings).
64833
- * The option set is stable across observations; it is persisted in the
64834
- * device config blob (via the control slice at adoption) and is NOT
64835
- * stored in `sourceInfo`.
64836
- */
64837
- /** Locale-render shape for an ISO `value` carried by a `DateTimeSensor`-role
64838
- * enum sensor: `date` (date-only), `time` (time-only), `datetime` (both). */
64839
- var EnumSensorDateTimeFormatSchema = z.enum([
65398
+ z.enum([
64840
65399
  "date",
64841
65400
  "time",
64842
65401
  "datetime"
@@ -64844,12 +65403,11 @@ var EnumSensorDateTimeFormatSchema = z.enum([
64844
65403
  var EnumSensorStatusSchema = z.object({
64845
65404
  value: z.string(),
64846
65405
  /**
64847
- * Set for `DateTimeSensor`-role sensors so the UI renders the ISO `value`
64848
- * as a locale date/time/datetime; absent for plain enum sensors. HA
64849
- * `device_class=timestamp` → `'datetime'`, `device_class=date` → `'date'`,
64850
- * a time-only sensor → `'time'`.
65406
+ * Set when `value` is a timestamp rather than an enum state, so history
65407
+ * does not learn ISO strings as states. The display shape (date / time /
65408
+ * datetime) lives on `SourceInfo.dateTimeShape`, written once at adopt.
64851
65409
  */
64852
- format: EnumSensorDateTimeFormatSchema.optional(),
65410
+ timestamp: z.boolean().optional(),
64853
65411
  /** Ms epoch when the slice was last updated. */
64854
65412
  lastFetchedAt: z.number()
64855
65413
  });
@@ -64876,11 +65434,11 @@ var enumSensorCapability = {
64876
65434
  * History (D850): the value as an `enum` series (an appliance's program or
64877
65435
  * operation state, an HVAC action). The option set is per device, so the
64878
65436
  * value list is LEARNED as values are first seen (bounded). A sensor with a
64879
- * `format` carries a timestamp, not a state, and is not recorded.
65437
+ * `timestamp` carries a clock, not a state, and is not recorded.
64880
65438
  */
64881
65439
  history: { fields: { value: {
64882
65440
  kind: "enum",
64883
- skipWhenSet: "format"
65441
+ skipWhenSet: "timestamp"
64884
65442
  } } },
64885
65443
  /** Clock fields: written, but excluded from the compare that decides
64886
65444
  * whether persisting is worth a SQLite commit. */
@@ -64900,13 +65458,13 @@ var EventFireSchema = z.object({
64900
65458
  deviceId: z.number(),
64901
65459
  eventType: z.string(),
64902
65460
  data: z.record(z.string(), z.unknown()).nullable(),
64903
- timestamp: z.number(),
64904
- seq: z.number()
65461
+ timestamp: z.number().meta(readingMeta("none")),
65462
+ seq: z.number().meta(readingMeta("count"))
64905
65463
  });
64906
65464
  var EventEmitterStatusSchema = z.object({
64907
65465
  eventTypes: z.array(z.string()),
64908
65466
  lastEvent: EventFireSchema.nullable(),
64909
- eventCountSinceStart: z.number()
65467
+ eventCountSinceStart: z.number().meta(readingMeta("count"))
64910
65468
  });
64911
65469
  var eventEmitterCapability = {
64912
65470
  name: "event-emitter",
@@ -65359,10 +65917,10 @@ var FanDirectionSchema = z.enum(["forward", "reverse"]);
65359
65917
  var FanControlStatusSchema = z.object({
65360
65918
  /** Active speed as 0..100 inclusive. Null when the device has no
65361
65919
  * speed surface (single-speed fan). */
65362
- percentage: z.number().min(0).max(100).nullable(),
65920
+ percentage: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65363
65921
  /** Speed granularity the device accepts (HA `percentage_step`, e.g. 25 for
65364
65922
  * a 4-speed fan). Absent when unknown — the UI then falls back to step 1. */
65365
- percentageStep: z.number().positive().optional(),
65923
+ percentageStep: z.number().positive().optional().meta(readingMeta("percentage")),
65366
65924
  /** Active preset mode (`auto` / `quiet` / `turbo` / vendor-specific)
65367
65925
  * — empty string when no preset is active or supported. */
65368
65926
  preset: z.string(),
@@ -65483,7 +66041,7 @@ var FeatureProbeStatusSchema = z.object({
65483
66041
  /** Camera/firmware model string. `null` when the firmware doesn't expose it. */
65484
66042
  model: z.string().nullable(),
65485
66043
  /** Channel count for NVR/Hub devices; `1` for standalone cameras; `null` pre-probe. */
65486
- channelCount: z.number().nullable(),
66044
+ channelCount: z.number().nullable().meta(readingMeta("count")),
65487
66045
  /**
65488
66046
  * Ms epoch of the last SUCCESSFUL probe. `0` before the first probe
65489
66047
  * completes — drivers' `getAccessoryChildren()` should treat zero as
@@ -65606,9 +66164,54 @@ var gasCapability = {
65606
66164
  * whether persisting is worth a SQLite commit. */
65607
66165
  volatileStateFields: ["lastChangedAt"]
65608
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
+ };
65609
66212
  var HeatingValveStatusSchema = z.object({
65610
66213
  /** Radiator valve opening 0 (closed) .. 100 (open). Null = not yet known. */
65611
- position: z.number().min(0).max(100).nullable(),
66214
+ position: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
65612
66215
  /** Ms epoch when the slice was last updated (push or poll). */
65613
66216
  lastFetchedAt: z.number()
65614
66217
  });
@@ -65651,9 +66254,9 @@ var HumidifierStatusSchema = z.object({
65651
66254
  /** Whether the humidifier is currently on. */
65652
66255
  on: z.boolean(),
65653
66256
  /** Current measured relative humidity (0..100). Null when not reported. */
65654
- currentHumidity: z.number().min(0).max(100).nullable(),
66257
+ currentHumidity: z.number().min(0).max(100).nullable().meta(readingMeta("humidity")),
65655
66258
  /** Target relative humidity (0..100). Null when no setpoint surface. */
65656
- targetHumidity: z.number().min(0).max(100).nullable(),
66259
+ targetHumidity: z.number().min(0).max(100).nullable().meta(readingMeta("humidity")),
65657
66260
  /** Active mode (`auto` / `normal` / `baby` / vendor). Null when the
65658
66261
  * device has no mode surface. */
65659
66262
  mode: z.string().nullable(),
@@ -65663,9 +66266,9 @@ var HumidifierStatusSchema = z.object({
65663
66266
  * `idle` / `off`). Null when the device doesn't report it. */
65664
66267
  action: z.string().nullable(),
65665
66268
  /** HA `min_humidity` attribute. Null → UI uses 0. */
65666
- minHumidity: z.number().nullable(),
66269
+ minHumidity: z.number().nullable().meta(readingMeta("humidity")),
65667
66270
  /** HA `max_humidity` attribute. Null → UI uses 100. */
65668
- maxHumidity: z.number().nullable(),
66271
+ maxHumidity: z.number().nullable().meta(readingMeta("humidity")),
65669
66272
  /** Ms epoch when the slice was last updated. */
65670
66273
  lastChangedAt: z.number()
65671
66274
  });
@@ -65735,22 +66338,13 @@ var humidifierCapability = {
65735
66338
  * `sourceInfo`.
65736
66339
  */
65737
66340
  var HumiditySensorStatusSchema = z.object({
65738
- /** Current relative humidity, 0..100. */
65739
- percent: z.number().min(0).max(100).meta({
65740
- unit: "%",
66341
+ /** Current relative humidity, 0..100. `null` when the sample was refused (D393). */
66342
+ percent: z.number().min(0).max(100).nullable().meta(readingMeta("humidity", {
66343
+ precision: 0,
65741
66344
  label: "Humidity"
65742
- }),
66345
+ })),
65743
66346
  /** Ms epoch when the slice was last updated. */
65744
- lastFetchedAt: z.number(),
65745
- /** Live display unit from the upstream source (e.g. HA
65746
- * `attributes.unit_of_measurement`). The UI prefers this over the
65747
- * role's canonical unit. Absent → fall back to the canonical unit. */
65748
- unit: z.string().optional(),
65749
- /** Suggested decimal places for numeric display.
65750
- * Populated live from the upstream source when provided (e.g. HA
65751
- * `attributes.suggested_display_precision`). Falls back to
65752
- * auto-formatting when absent. */
65753
- precision: z.number().int().min(0).max(10).optional()
66347
+ lastFetchedAt: z.number()
65754
66348
  });
65755
66349
  var humiditySensorCapability = {
65756
66350
  name: "humidity-sensor",
@@ -65781,12 +66375,7 @@ var humiditySensorCapability = {
65781
66375
  } } },
65782
66376
  /** Clock fields: written, but excluded from the compare that decides
65783
66377
  * whether persisting is worth a SQLite commit. */
65784
- volatileStateFields: ["lastFetchedAt"],
65785
- customize: {
65786
- version: 1,
65787
- advanced: "derive",
65788
- targets: []
65789
- }
66378
+ volatileStateFields: ["lastFetchedAt"]
65790
66379
  };
65791
66380
  /**
65792
66381
  * Image display cap. Models a single still image exposed by an integration —
@@ -65807,7 +66396,7 @@ var ImageStatusSchema = z.object({
65807
66396
  * entity exposes no `entity_picture` (yet). */
65808
66397
  url: z.string().nullable(),
65809
66398
  /** Ms epoch of the upstream last-updated timestamp. Null at cold-start. */
65810
- lastUpdated: z.number().nullable()
66399
+ lastUpdated: z.number().nullable().meta(readingMeta("none"))
65811
66400
  });
65812
66401
  var imageCapability = {
65813
66402
  name: "image",
@@ -65895,19 +66484,19 @@ var NormalizedRangeSchema = z.object({
65895
66484
  */
65896
66485
  var ImageSettingsStatusSchema = z.object({
65897
66486
  /** Normalized 0–100. */
65898
- brightness: z.number().optional(),
66487
+ brightness: z.number().optional().meta(readingMeta("none")),
65899
66488
  /** Normalized 0–100. */
65900
- contrast: z.number().optional(),
66489
+ contrast: z.number().optional().meta(readingMeta("none")),
65901
66490
  /** Normalized 0–100. */
65902
- saturation: z.number().optional(),
66491
+ saturation: z.number().optional().meta(readingMeta("none")),
65903
66492
  /** Normalized 0–100. */
65904
- sharpness: z.number().optional(),
66493
+ sharpness: z.number().optional().meta(readingMeta("none")),
65905
66494
  mirror: z.boolean().optional(),
65906
66495
  flip: z.boolean().optional(),
65907
66496
  rotate: ImageRotateSchema.optional(),
65908
66497
  whiteBalance: WhiteBalanceModeSchema.optional(),
65909
66498
  /** Manual white-balance warmth, NORMALIZED 0–100. Meaningful only when `whiteBalance === 'manual'`. */
65910
- warmth: z.number().optional(),
66499
+ warmth: z.number().optional().meta(readingMeta("none")),
65911
66500
  exposureMode: ExposureModeSchema.optional(),
65912
66501
  backlightMode: BacklightModeSchema.optional(),
65913
66502
  lastFetchedAt: z.number()
@@ -66204,10 +66793,10 @@ var IntercomAbilitySchema = z.object({
66204
66793
  /** Codecs accepted by firmware, in preference order. Driver-authored strings. */
66205
66794
  codecs: z.array(z.string()),
66206
66795
  /** Sample rate (Hz) the firmware expects. Reolink typical: 16000. */
66207
- sampleRate: z.number(),
66796
+ sampleRate: z.number().meta(readingMeta("none")),
66208
66797
  duplex: z.enum(["half", "full"]),
66209
66798
  /** Max in-flight audio backlog before drop (ms). Server uses this to bound latency. */
66210
- maxBacklogMs: z.number()
66799
+ maxBacklogMs: z.number().meta(readingMeta("duration-ms"))
66211
66800
  });
66212
66801
  var IntercomStatusSchema = z.object({
66213
66802
  /** True while a talk session is open. */
@@ -66434,13 +67023,13 @@ var LawnMowerControlStatusSchema = z.object({
66434
67023
  activity: LawnMowerActivitySchema,
66435
67024
  /** 0..100 battery percentage. Null when the device has no battery
66436
67025
  * reading. */
66437
- batteryLevel: z.number().min(0).max(100).nullable(),
67026
+ batteryLevel: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66438
67027
  /** 0..100 mowing-completion percentage of the current task, or null when no
66439
67028
  * task is active / progress is unavailable. */
66440
- progressPercent: z.number().min(0).max(100).nullable(),
67029
+ progressPercent: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
66441
67030
  /** Current device/event code (dynamic — mostly status, sometimes an error),
66442
67031
  * or null when unknown. */
66443
- currentCode: z.number().nullable(),
67032
+ currentCode: z.number().nullable().meta(readingMeta("none")),
66444
67033
  /** Human label for {@link currentCode}, or null when undecodable. */
66445
67034
  currentCodeLabel: z.string().nullable(),
66446
67035
  /** Severity of {@link currentCode}. `error` (and often `warning`) warrants UI
@@ -67164,7 +67753,7 @@ var MediaPlayerStatusSchema = z.object({
67164
67753
  state: MediaPlayerStateSchema,
67165
67754
  /** Volume as 0..100 inclusive. Null when the device has no volume
67166
67755
  * surface. Pair with `DeviceFeature.MediaPlayerVolume`. */
67167
- volumeLevel: z.number().min(0).max(100).nullable(),
67756
+ volumeLevel: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
67168
67757
  /** Mute toggle distinct from volume=0. Null when no mute surface.
67169
67758
  * Pair with `DeviceFeature.MediaPlayerMute`. */
67170
67759
  isMuted: z.boolean().nullable(),
@@ -67177,10 +67766,10 @@ var MediaPlayerStatusSchema = z.object({
67177
67766
  currentMedia: MediaInfoSchema.nullable(),
67178
67767
  /** Current playback position in ms. Null when not seekable or
67179
67768
  * nothing is playing. Pair with `DeviceFeature.MediaPlayerSeek`. */
67180
- positionMs: z.number().int().nonnegative().nullable(),
67769
+ positionMs: z.number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
67181
67770
  /** Total duration of the current media in ms. Null when unknown
67182
67771
  * (live stream) or nothing is playing. */
67183
- durationMs: z.number().int().nonnegative().nullable(),
67772
+ durationMs: z.number().int().nonnegative().nullable().meta(readingMeta("duration-ms")),
67184
67773
  /** Shuffle toggle. Null when no shuffle surface.
67185
67774
  * Pair with `DeviceFeature.MediaPlayerShuffle`. */
67186
67775
  shuffle: z.boolean().nullable(),
@@ -67550,7 +68139,7 @@ var motionCustomize = {
67550
68139
  eventName: "onMotionChanged"
67551
68140
  },
67552
68141
  deviceTypes: [DeviceType.Camera],
67553
- apply: "replace",
68142
+ apply: "add",
67554
68143
  applyAuthority: {
67555
68144
  kind: "camera-sensors",
67556
68145
  timeoutField: "motionCooldownMs"
@@ -67647,7 +68236,7 @@ var MotionStatusSchema = z.object({
67647
68236
  * it, every provider seeded it at boot, and every session left it
67648
68237
  * `detected: true` after its detach.
67649
68238
  */
67650
- autoClearAfterMs: z.number().nullable()
68239
+ autoClearAfterMs: z.number().nullable().meta(readingMeta("duration-ms"))
67651
68240
  });
67652
68241
  /**
67653
68242
  * Payload of `motion.onMotionChanged` event + the corresponding bus
@@ -67684,12 +68273,18 @@ var motionCapability = {
67684
68273
  methods: {
67685
68274
  /**
67686
68275
  * Pull the current motion state synchronously. Convenience shortcut
67687
- * for consumers that don't need the full status object; equivalent
67688
- * to `getStatus()?.detected ?? false`. Will likely be folded into
67689
- * `getStatus` once the auto-injected status surface lands in every
67690
- * 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.
67691
68278
  */
67692
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
+ } },
67693
68288
  events: {
67694
68289
  /**
67695
68290
  * Fires every time the runner transitions a camera between
@@ -67829,7 +68424,7 @@ var MotionZoneRegionSchema = z.object({
67829
68424
  * the grid region(s). */
67830
68425
  var MotionZoneStatusSchema = z.object({
67831
68426
  enabled: z.boolean(),
67832
- sensitivity: z.number(),
68427
+ sensitivity: z.number().meta(readingMeta("none")),
67833
68428
  /** Grid region(s). Today exactly one `grid` shape. */
67834
68429
  regions: z.array(MotionZoneRegionSchema),
67835
68430
  lastFetchedAt: z.number()
@@ -68201,7 +68796,7 @@ var NavigationStatusSchema = z.object({
68201
68796
  following: z.boolean(),
68202
68797
  flash: z.boolean(),
68203
68798
  lightMode: NavigationLightModeSchema,
68204
- lightLevel: z.number().min(40).max(100),
68799
+ lightLevel: z.number().min(40).max(100).meta(readingMeta("none")),
68205
68800
  /** Ms epoch when the slice was last updated. */
68206
68801
  lastChangedAt: z.number()
68207
68802
  });
@@ -68335,13 +68930,13 @@ var NetworkLinkStatusSchema = z.object({
68335
68930
  * one whose reading has not landed must not be drawn at 0 %. Consumers
68336
68931
  * SKIP a null rather than coerce it.
68337
68932
  */
68338
- signalPercent: z.number().min(0).max(100).nullable(),
68933
+ signalPercent: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
68339
68934
  /** Raw received signal strength in dBm, when the firmware reports one. */
68340
- rssiDbm: z.number().optional(),
68935
+ rssiDbm: z.number().optional().meta(readingMeta("signal-level")),
68341
68936
  /** Network name of a wireless link, when the firmware reports it. */
68342
68937
  ssid: z.string().optional(),
68343
68938
  /** Ms epoch of the last observation. Lets consumers reason about freshness. */
68344
- lastUpdated: z.number()
68939
+ lastUpdated: z.number().meta(readingMeta("none"))
68345
68940
  });
68346
68941
  var networkLinkCapability = {
68347
68942
  name: "network-link",
@@ -68791,7 +69386,7 @@ var NotifierStatusSchema = z.object({
68791
69386
  lastError: z.string().nullable(),
68792
69387
  /** Number of deliveries currently buffered server-side (rate-limited
68793
69388
  * notifiers may queue). 0 when the provider sends synchronously. */
68794
- queueDepth: z.number().int().nonnegative(),
69389
+ queueDepth: z.number().int().nonnegative().meta(readingMeta("count")),
68795
69390
  /** Per-feature capability matrix — authoritative source for the
68796
69391
  * compose-form field gating. */
68797
69392
  supports: NotifierSupportsSchema
@@ -68883,11 +69478,9 @@ onSent: { data: z.object({
68883
69478
  * cap fits (HA `sensor` whose `device_class` we don't have a typed cap
68884
69479
  * for: water flow, distance, weight, frequency, signal strength, …).
68885
69480
  *
68886
- * The `unit` and `precision` fields carry the live display metadata that
68887
- * the upstream source provides with each state push (HA
68888
- * `attributes.unit_of_measurement` / `attributes.suggested_display_precision`).
68889
- * The UI reads them directly from the slice — they are the single source
68890
- * of truth for generic numeric rendering and do NOT live on `sourceInfo`.
69481
+ * The quantity is not on this slice. It is chosen when the device is
69482
+ * adopted and stored once on `SourceInfo.quantity` (D944). `value` is in
69483
+ * that quantity's base unit. `null` is a refused sample (D393).
68891
69484
  *
68892
69485
  * Prefer the typed alternatives (`temperature-sensor`, `humidity-sensor`,
68893
69486
  * `ambient-light-sensor`, `pressure-sensor`, `power-meter`,
@@ -68896,15 +69489,8 @@ onSent: { data: z.object({
68896
69489
  * numeric values land here.
68897
69490
  */
68898
69491
  var NumericSensorStatusSchema = z.object({
68899
- value: z.number(),
68900
- /** Display unit-of-measurement (e.g. 'dBm', 's', 'rpm', 'steps').
68901
- * Populated live from the upstream source on each state push.
68902
- * Absent for unitless sensors. */
68903
- unit: z.string().optional(),
68904
- /** Suggested decimal places for numeric display.
68905
- * Populated live from the upstream source when provided.
68906
- * Falls back to auto-formatting when absent. */
68907
- precision: z.number().int().min(0).max(10).optional(),
69492
+ /** Reading in the device quantity's base unit. `null` when refused (D393). */
69493
+ value: z.number().nullable().meta(readingMeta("device")),
68908
69494
  /** Ms epoch when the slice was last updated. */
68909
69495
  lastFetchedAt: z.number()
68910
69496
  });
@@ -68928,11 +69514,11 @@ var numericSensorCapability = {
68928
69514
  */
68929
69515
  durability: "restored",
68930
69516
  /**
68931
- * History (D850): recorded series. The unit is per device, read from the sibling `unit` field.
69517
+ * History (D850): recorded series. The unit is the device quantity's base,
69518
+ * resolved when the series is read — the cap has no single unit.
68932
69519
  */
68933
69520
  history: { fields: { value: {
68934
69521
  kind: "gauge",
68935
- unitFrom: "unit",
68936
69522
  minIntervalMs: 3e4
68937
69523
  } } },
68938
69524
  /** Clock fields: written, but excluded from the compare that decides
@@ -69333,23 +69919,26 @@ var PetFeederStatusSchema = z.object({
69333
69919
  /** Food currently in the bowl (grams). Null when the device has not
69334
69920
  * reported a reading yet. On dual-hopper models this is the combined
69335
69921
  * bowl reading; per-hopper levels live in `food1`/`food2`. */
69336
- foodLevel: z.number().nullable(),
69922
+ foodLevel: z.number().nullable().meta(readingMeta("none")),
69337
69923
  /** Hopper-1 food level (grams) on dual-hopper feeders; null on
69338
69924
  * single-hopper models. */
69339
- food1: z.number().nullable(),
69925
+ food1: z.number().nullable().meta(readingMeta("none")),
69340
69926
  /** Hopper-2 food level (grams) on dual-hopper feeders; null on
69341
69927
  * single-hopper models. */
69342
- food2: z.number().nullable(),
69928
+ food2: z.number().nullable().meta(readingMeta("none")),
69343
69929
  /** Derived low-food flag — mirrors the HA `food_level` binary_sensor
69344
69930
  * (`device_class: problem`, on = low). True when the bowl is empty /
69345
69931
  * below the feeder's low threshold. */
69346
69932
  lowFood: z.boolean(),
69347
69933
  /** Battery charge 0..100 (%). Null on mains-powered models or when the
69348
69934
  * device has no battery reading. */
69349
- batteryPower: z.number().min(0).max(100).nullable(),
69350
- /** Days of desiccant life remaining. Null when the model has no
69351
- * desiccant sensor. */
69352
- desiccantLeftDays: z.number().nullable(),
69935
+ batteryPower: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
69936
+ /**
69937
+ * Desiccant life remaining, stored in seconds (duration base, D944).
69938
+ * Providers convert from the days the feeder reports. Null when the
69939
+ * model has no desiccant sensor.
69940
+ */
69941
+ desiccantLeftDays: z.number().nullable().meta(readingMeta("duration")),
69353
69942
  /** True while a feed is in progress. */
69354
69943
  feeding: z.boolean(),
69355
69944
  /** Decoded connectivity / power status (HA petkit device-status enum).
@@ -69360,7 +69949,7 @@ var PetFeederStatusSchema = z.object({
69360
69949
  * with `errorCode` for consumers that want the raw integer. */
69361
69950
  error: z.string().nullable(),
69362
69951
  /** Raw device error code (0 / null = no error). */
69363
- errorCode: z.number().nullable(),
69952
+ errorCode: z.number().nullable().meta(readingMeta("none")),
69364
69953
  /** True for D4S/D4SH dual-hopper hardware — gates the per-hopper UI. */
69365
69954
  isDualHopper: z.boolean(),
69366
69955
  /** Child-lock (manual-lock) setting — buttons on the unit are disabled. */
@@ -69370,7 +69959,7 @@ var PetFeederStatusSchema = z.object({
69370
69959
  /** Play a chime when dispensing. */
69371
69960
  feedSound: z.boolean(),
69372
69961
  /** Speaker / prompt volume level (device-scaled integer). */
69373
- volume: z.number(),
69962
+ volume: z.number().meta(readingMeta("none")),
69374
69963
  /** Ms epoch when the slice was last refreshed from the cloud. */
69375
69964
  lastFetchedAt: z.number()
69376
69965
  });
@@ -69489,7 +70078,7 @@ var petFeederCapability = {
69489
70078
  },
69490
70079
  desiccantLeftDays: {
69491
70080
  kind: "gauge",
69492
- unit: "days"
70081
+ unit: "s"
69493
70082
  }
69494
70083
  } }
69495
70084
  };
@@ -69950,36 +70539,19 @@ var platformProbeCapability = {
69950
70539
  */
69951
70540
  var PowerMeterStatusSchema = z.object({
69952
70541
  /** Instantaneous power draw in watts. */
69953
- watts: z.number().optional().meta({
69954
- unit: "W",
69955
- label: "Power"
69956
- }),
70542
+ watts: z.number().optional().meta(readingMeta("power", { label: "Power" })),
69957
70543
  /** Cumulative energy in kilowatt-hours since the meter was reset. */
69958
- kwhTotal: z.number().optional().meta({
69959
- unit: "kWh",
69960
- label: "Energy"
69961
- }),
70544
+ kwhTotal: z.number().optional().meta(readingMeta("energy", { label: "Energy" })),
69962
70545
  /** Voltage in volts. */
69963
- volts: z.number().optional(),
70546
+ volts: z.number().optional().meta(readingMeta("voltage")),
69964
70547
  /** Current in amperes. */
69965
- amps: z.number().optional(),
69966
- /** Apparent power in volt-amperes (W × power-factor reciprocal). */
69967
- va: z.number().optional(),
70548
+ amps: z.number().optional().meta(readingMeta("current")),
70549
+ /** Apparent power in volt-amperes. */
70550
+ va: z.number().optional().meta(readingMeta("apparent-power")),
69968
70551
  /** Power factor (0..1) if reported. */
69969
- powerFactor: z.number().min(0).max(1).optional(),
70552
+ powerFactor: z.number().min(0).max(1).optional().meta(readingMeta("ratio")),
69970
70553
  /** Ms epoch when the slice was last updated. */
69971
- lastFetchedAt: z.number(),
69972
- /** Live display unit of the single metric this slice carries (e.g. HA
69973
- * `attributes.unit_of_measurement` → 'V' / 'A' / 'W' / 'kW' / 'kWh').
69974
- * Each upstream `sensor.*` entity surfaces ONE device_class, so one
69975
- * unit per slice is unambiguous. The UI prefers this over the role's
69976
- * canonical unit so a 'kW' / 'Wh' feed renders verbatim. */
69977
- unit: z.string().optional(),
69978
- /** Suggested decimal places for numeric display.
69979
- * Populated live from the upstream source when provided (e.g. HA
69980
- * `attributes.suggested_display_precision`). Falls back to
69981
- * auto-formatting when absent. */
69982
- precision: z.number().int().min(0).max(10).optional()
70554
+ lastFetchedAt: z.number()
69983
70555
  });
69984
70556
  var powerMeterCapability = {
69985
70557
  name: "power-meter",
@@ -70043,12 +70615,7 @@ var powerMeterCapability = {
70043
70615
  } },
70044
70616
  /** Clock fields: written, but excluded from the compare that decides
70045
70617
  * whether persisting is worth a SQLite commit. */
70046
- volatileStateFields: ["lastFetchedAt"],
70047
- customize: {
70048
- version: 1,
70049
- advanced: "derive",
70050
- targets: []
70051
- }
70618
+ volatileStateFields: ["lastFetchedAt"]
70052
70619
  };
70053
70620
  /**
70054
70621
  * Presence cap. Models HA `person.*` and `device_tracker.*` entities
@@ -70068,11 +70635,11 @@ var powerMeterCapability = {
70068
70635
  */
70069
70636
  var GpsLocationSchema = z.object({
70070
70637
  /** Latitude in decimal degrees, -90..90. */
70071
- latitude: z.number().min(-90).max(90),
70638
+ latitude: z.number().min(-90).max(90).meta(readingMeta("angle")),
70072
70639
  /** Longitude in decimal degrees, -180..180. */
70073
- longitude: z.number().min(-180).max(180),
70640
+ longitude: z.number().min(-180).max(180).meta(readingMeta("angle")),
70074
70641
  /** Reported accuracy in meters (lower = better). */
70075
- accuracyMeters: z.number().nonnegative()
70642
+ accuracyMeters: z.number().nonnegative().meta(readingMeta("length"))
70076
70643
  });
70077
70644
  var PresenceStatusSchema = z.object({
70078
70645
  /** `home` / `not_home` / any user-defined zone name. */
@@ -70084,7 +70651,7 @@ var PresenceStatusSchema = z.object({
70084
70651
  gps: GpsLocationSchema.nullable(),
70085
70652
  /** Optional battery level of the tracking device (0..100). Null
70086
70653
  * when not reported. */
70087
- batteryPercent: z.number().min(0).max(100).nullable(),
70654
+ batteryPercent: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
70088
70655
  /** Ms epoch when the slice was last updated. */
70089
70656
  lastChangedAt: z.number()
70090
70657
  });
@@ -70134,19 +70701,10 @@ var presenceCapability = {
70134
70701
  * `sourceInfo`.
70135
70702
  */
70136
70703
  var PressureSensorStatusSchema = z.object({
70137
- /** Current pressure in hPa. */
70138
- hpa: z.number(),
70704
+ /** Current pressure in hPa. `null` when the sample was refused (D393). */
70705
+ hpa: z.number().nullable().meta(readingMeta("pressure", { precision: 0 })),
70139
70706
  /** Ms epoch when the slice was last updated. */
70140
- lastFetchedAt: z.number(),
70141
- /** Live display unit from the upstream source (e.g. HA
70142
- * `attributes.unit_of_measurement`). The UI prefers this over the
70143
- * role's canonical unit. Absent → fall back to the canonical unit. */
70144
- unit: z.string().optional(),
70145
- /** Suggested decimal places for numeric display.
70146
- * Populated live from the upstream source when provided (e.g. HA
70147
- * `attributes.suggested_display_precision`). Falls back to
70148
- * auto-formatting when absent. */
70149
- precision: z.number().int().min(0).max(10).optional()
70707
+ lastFetchedAt: z.number()
70150
70708
  });
70151
70709
  var pressureSensorCapability = {
70152
70710
  name: "pressure-sensor",
@@ -70687,8 +71245,8 @@ var ptzCapability = {
70687
71245
  var AutotrackTargetTypeSchema = z.string().describe("Vendor target string (people/vehicle/pet); empty = camera default");
70688
71246
  var PtzAutotrackSettingsSchema = z.object({
70689
71247
  targetType: AutotrackTargetTypeSchema,
70690
- stopDelaySeconds: z.number().int().min(0).max(300),
70691
- disappearDelaySeconds: z.number().int().min(0).max(300)
71248
+ stopDelaySeconds: z.number().int().min(0).max(300).meta(readingMeta("duration")),
71249
+ disappearDelaySeconds: z.number().int().min(0).max(300).meta(readingMeta("duration"))
70692
71250
  });
70693
71251
  /**
70694
71252
  * One option the camera advertises as a valid `targetType`. Drivers
@@ -70842,7 +71400,7 @@ var RunningStateStatusSchema = z.object({
70842
71400
  /** null = unknown — never coerced to false (D393). */
70843
71401
  running: z.boolean().nullable(),
70844
71402
  /** Epoch ms the current boolean began; null while `running` is null. */
70845
- since: z.number().nullable(),
71403
+ since: z.number().nullable().meta(readingMeta("none")),
70846
71404
  source: RunningStateSourceSchema.meta({ label: "Source" }),
70847
71405
  phase: z.string().nullable(),
70848
71406
  /** null until a reading has been published (D393). */
@@ -70857,7 +71415,6 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
70857
71415
  /** Generic Customize target for the running boolean (D936). */
70858
71416
  var runningCustomize = {
70859
71417
  version: 1,
70860
- advanced: "derive",
70861
71418
  targets: [{
70862
71419
  id: "running",
70863
71420
  cardinality: "scalar",
@@ -70877,7 +71434,7 @@ var runningCustomize = {
70877
71434
  DeviceType.Thermostat,
70878
71435
  DeviceType.PetFeeder
70879
71436
  ],
70880
- apply: "add-or-replace",
71437
+ apply: "add",
70881
71438
  path: "running",
70882
71439
  sources: [
70883
71440
  {
@@ -72516,9 +73073,9 @@ var RecordTriggerSchema = z.enum([
72516
73073
  */
72517
73074
  var RecordWindowSchema = z.object({
72518
73075
  trigger: RecordTriggerSchema,
72519
- day: z.number().int().min(0).max(6),
72520
- startMinute: z.number().int().min(0).max(1439),
72521
- endMinute: z.number().int().min(1).max(1440)
73076
+ day: z.number().int().min(0).max(6).meta(readingMeta("none")),
73077
+ startMinute: z.number().int().min(0).max(1439).meta(readingMeta("none")),
73078
+ endMinute: z.number().int().min(1).max(1440).meta(readingMeta("none"))
72522
73079
  });
72523
73080
  /** Status of one physical volume, as the camera itself describes it. */
72524
73081
  var OnboardStorageVolumeSchema = z.object({
@@ -72617,10 +73174,10 @@ var RecordingOnboardStatusSchema = z.object({
72617
73174
  /** Master "record to the card at all" switch. */
72618
73175
  enabled: z.boolean().nullable(),
72619
73176
  overwriteWhenFull: z.boolean().nullable(),
72620
- preRecordSec: z.number().nullable(),
72621
- postRecordSec: z.number().nullable(),
73177
+ preRecordSec: z.number().nullable().meta(readingMeta("duration")),
73178
+ postRecordSec: z.number().nullable().meta(readingMeta("duration")),
72622
73179
  /** Length of one recorded file, in minutes. */
72623
- segmentMinutes: z.number().nullable(),
73180
+ segmentMinutes: z.number().nullable().meta(readingMeta("none")),
72624
73181
  /** The primary track's weekly windows, flattened. */
72625
73182
  windows: z.array(RecordWindowSchema),
72626
73183
  /**
@@ -72636,7 +73193,7 @@ var RecordingOnboardStatusSchema = z.object({
72636
73193
  * provider reporting a non-zero count also reports the schedule as not
72637
73194
  * writable.
72638
73195
  */
72639
- unreadableWindows: z.number(),
73196
+ unreadableWindows: z.number().meta(readingMeta("count")),
72640
73197
  /**
72641
73198
  * The camera is scheduled to record and has NO usable storage.
72642
73199
  *
@@ -73502,7 +74059,7 @@ var CamStreamDescriptorSchema = z.object({
73502
74059
  url: z.string().optional(),
73503
74060
  codec: z.string().optional(),
73504
74061
  resolution: CamStreamResolutionSchema.optional(),
73505
- fps: z.number().positive().optional(),
74062
+ fps: z.number().positive().optional().meta(readingMeta("rate")),
73506
74063
  label: z.string().optional(),
73507
74064
  /** Device-level features (e.g. `battery-operated`) — drives broker policy. */
73508
74065
  deviceFeatures: z.array(z.string()).optional(),
@@ -73589,18 +74146,18 @@ var StreamProfileSchema = z.enum([
73589
74146
  ]);
73590
74147
  /** The current encoding config of one stream profile. */
73591
74148
  var StreamProfileConfigSchema = z.object({
73592
- width: z.number(),
73593
- height: z.number(),
74149
+ width: z.number().meta(readingMeta("none")),
74150
+ height: z.number().meta(readingMeta("none")),
73594
74151
  codec: z.enum(["h264", "h265"]),
73595
- framerate: z.number(),
73596
- bitrate: z.number(),
74152
+ framerate: z.number().meta(readingMeta("rate")),
74153
+ bitrate: z.number().meta(readingMeta("bitrate")),
73597
74154
  bitrateMode: z.enum(["vbr", "cbr"]).optional(),
73598
74155
  encoderProfile: z.enum([
73599
74156
  "high",
73600
74157
  "main",
73601
74158
  "baseline"
73602
74159
  ]).optional(),
73603
- gop: z.number().optional(),
74160
+ gop: z.number().optional().meta(readingMeta("none")),
73604
74161
  /**
73605
74162
  * Whether THIS profile currently carries an audio track. READ-ONLY here.
73606
74163
  *
@@ -74078,12 +74635,7 @@ var switchCapability = {
74078
74635
  label: "On",
74079
74636
  kind: "boolean"
74080
74637
  }
74081
- }] },
74082
- customize: {
74083
- version: 1,
74084
- advanced: "derive",
74085
- targets: []
74086
- }
74638
+ }] }
74087
74639
  };
74088
74640
  /** C0 controls, DEL and the C1 range: none of them belongs in a name. */
74089
74641
  function hasControlCharacter(value) {
@@ -74793,35 +75345,21 @@ var tamperCapability = {
74793
75345
  * entries with `device_class: temperature` and any future native
74794
75346
  * thermometer.
74795
75347
  *
74796
- * Unit normalisation: providers convert to Celsius before storing.
74797
- * The slice value is always Celsius so cross-cap aggregators
74798
- * (climate-control's `currentTemp`, energy analytics) can compose
74799
- * without per-source unit fixups. The canonical display unit (`°C`) is
74800
- * a descriptor constant in the UI (ROLE_DESCRIPTOR), not stored in
74801
- * `sourceInfo`.
75348
+ * The number is Celsius (D944). Providers convert on ingest. A failed
75349
+ * conversion is `null`, never a guess. Display unit is a read-time preference.
74802
75350
  *
74803
75351
  * Status `lastFetchedAt` lets staleness-aware consumers detect a
74804
75352
  * frozen feed (provider hung) distinct from a "temperature hasn't
74805
75353
  * changed" steady state.
74806
75354
  */
74807
75355
  var TemperatureSensorStatusSchema = z.object({
74808
- /** Current temperature in Celsius. */
74809
- celsius: z.number().meta({
74810
- unit: "°C",
75356
+ /** Current temperature in Celsius. `null` when the sample was refused (D393). */
75357
+ celsius: z.number().nullable().meta(readingMeta("temperature", {
75358
+ precision: 1,
74811
75359
  label: "Temperature"
74812
- }),
75360
+ })),
74813
75361
  /** Ms epoch when the slice was last updated (push or poll). */
74814
- lastFetchedAt: z.number(),
74815
- /** Live display unit from the upstream source (e.g. HA
74816
- * `attributes.unit_of_measurement`). The UI prefers this over the
74817
- * role's canonical unit, so a Fahrenheit feed renders '°F' not '°C'.
74818
- * Absent → the UI falls back to the role's canonical unit. */
74819
- unit: z.string().optional(),
74820
- /** Suggested decimal places for numeric display.
74821
- * Populated live from the upstream source when provided (e.g. HA
74822
- * `attributes.suggested_display_precision`). Falls back to
74823
- * auto-formatting when absent. */
74824
- precision: z.number().int().min(0).max(10).optional()
75362
+ lastFetchedAt: z.number()
74825
75363
  });
74826
75364
  var temperatureSensorCapability = {
74827
75365
  name: "temperature-sensor",
@@ -74852,12 +75390,7 @@ var temperatureSensorCapability = {
74852
75390
  } } },
74853
75391
  /** Clock fields: written, but excluded from the compare that decides
74854
75392
  * whether persisting is worth a SQLite commit. */
74855
- volatileStateFields: ["lastFetchedAt"],
74856
- customize: {
74857
- version: 1,
74858
- advanced: "derive",
74859
- targets: []
74860
- }
75393
+ volatileStateFields: ["lastFetchedAt"]
74861
75394
  };
74862
75395
  /**
74863
75396
  * toast — system-scoped singleton capability that streams toast
@@ -75203,16 +75736,16 @@ var VacuumControlStatusSchema = z.object({
75203
75736
  state: VacuumStateSchema,
75204
75737
  /** 0..100 battery percentage. Null when the device has no battery
75205
75738
  * reading. */
75206
- batteryLevel: z.number().min(0).max(100).nullable(),
75739
+ batteryLevel: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
75207
75740
  /** Current fan-speed token (provider-verbatim). Null when unknown or
75208
75741
  * the vacuum has no speed control. */
75209
75742
  fanSpeed: z.string().nullable(),
75210
75743
  /** Speed tokens the hardware accepts — drives the UI selector. */
75211
75744
  availableFanSpeeds: z.array(z.string()),
75212
75745
  /** 0..100 cleaning-completion percentage of the current task, or null. */
75213
- progressPercent: z.number().min(0).max(100).nullable(),
75746
+ progressPercent: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
75214
75747
  /** Current error code (0 / null = no error). */
75215
- errorCode: z.number().nullable(),
75748
+ errorCode: z.number().nullable().meta(readingMeta("none")),
75216
75749
  /** Human label for {@link errorCode}, or null when none / undecodable. */
75217
75750
  errorLabel: z.string().nullable(),
75218
75751
  /** Ms epoch when the slice was last updated. */
@@ -75308,7 +75841,7 @@ var ValveStatusSchema = z.object({
75308
75841
  state: ValveStateSchema,
75309
75842
  /** 0 = fully closed, 100 = fully open. Null when the device has no
75310
75843
  * intermediate position surface. */
75311
- position: z.number().min(0).max(100).nullable(),
75844
+ position: z.number().min(0).max(100).nullable().meta(readingMeta("percentage")),
75312
75845
  /** Ms epoch when the slice was last updated. */
75313
75846
  lastChangedAt: z.number()
75314
75847
  });
@@ -75416,9 +75949,9 @@ var vibrationCapability = {
75416
75949
  */
75417
75950
  var WaterHeaterStatusSchema = z.object({
75418
75951
  /** Current measured temperature. Null when not reported. */
75419
- currentTemp: z.number().nullable(),
75952
+ currentTemp: z.number().nullable().meta(readingMeta("temperature")),
75420
75953
  /** Target temperature setpoint. Null when no setpoint surface. */
75421
- targetTemp: z.number().nullable(),
75954
+ targetTemp: z.number().nullable().meta(readingMeta("temperature")),
75422
75955
  /** Active operation mode = HA `state` (`eco` / `electric` / `gas` /
75423
75956
  * `heat_pump` / `high_demand` / `performance` / `off`). Null when the
75424
75957
  * device reports an unknown state. */
@@ -75429,9 +75962,9 @@ var WaterHeaterStatusSchema = z.object({
75429
75962
  * has no away surface. */
75430
75963
  away: z.boolean().nullable(),
75431
75964
  /** HA `min_temp` attribute. Null when not reported. */
75432
- minTemp: z.number().nullable(),
75965
+ minTemp: z.number().nullable().meta(readingMeta("temperature")),
75433
75966
  /** HA `max_temp` attribute. Null when not reported. */
75434
- maxTemp: z.number().nullable(),
75967
+ maxTemp: z.number().nullable().meta(readingMeta("temperature")),
75435
75968
  /** Ms epoch when the slice was last updated. */
75436
75969
  lastChangedAt: z.number()
75437
75970
  });
@@ -75516,25 +76049,18 @@ var waterHeaterCapability = {
75516
76049
  * Forecast deferred — current conditions only for v1.
75517
76050
  */
75518
76051
  var WeatherStatusSchema = z.object({
75519
- /** Verbatim HA condition state (`sunny`, `cloudy`, `rainy`, …). Null
75520
- * when no condition has been reported yet. */
76052
+ /** Verbatim condition state (`sunny`, `cloudy`, `rainy`, …). Null when none yet. */
75521
76053
  condition: z.string().nullable(),
75522
- /** Current temperature in the reported unit. Null when not provided. */
75523
- temperature: z.number().nullable(),
75524
- /** Temperature unit string (e.g. `°C` / `°F`). Null when not provided. */
75525
- temperatureUnit: z.string().nullable(),
76054
+ /** Current temperature in Celsius. Null when not provided or refused. */
76055
+ temperature: z.number().nullable().meta(readingMeta("temperature", { precision: 1 })),
75526
76056
  /** Relative humidity (0..100). Null when not provided. */
75527
- humidity: z.number().min(0).max(100).nullable(),
75528
- /** Barometric pressure in the reported unit. Null when not provided. */
75529
- pressure: z.number().nullable(),
75530
- /** Pressure unit string (e.g. `hPa` / `inHg`). Null when not provided. */
75531
- pressureUnit: z.string().nullable(),
75532
- /** Wind speed in the reported unit. Null when not provided. */
75533
- windSpeed: z.number().nullable(),
75534
- /** Wind-speed unit string (e.g. `km/h` / `mph`). Null when not provided. */
75535
- windSpeedUnit: z.string().nullable(),
76057
+ humidity: z.number().min(0).max(100).nullable().meta(readingMeta("humidity")),
76058
+ /** Barometric pressure in hPa. Null when not provided or refused. */
76059
+ pressure: z.number().nullable().meta(readingMeta("pressure")),
76060
+ /** Wind speed in metres per second. Null when not provided or refused. */
76061
+ windSpeed: z.number().nullable().meta(readingMeta("speed")),
75536
76062
  /** Wind bearing in degrees (0..360, meteorological). Null when not provided. */
75537
- windBearing: z.number().nullable(),
76063
+ windBearing: z.number().nullable().meta(readingMeta("angle")),
75538
76064
  /** Ms epoch when the slice was last updated. */
75539
76065
  lastFetchedAt: z.number()
75540
76066
  });
@@ -75562,12 +76088,12 @@ var weatherCapability = {
75562
76088
  */
75563
76089
  durability: "session",
75564
76090
  /**
75565
- * History (D850): recorded series. Units follow the sibling `*Unit` fields.
76091
+ * History (D850): recorded series. Numbers are the quantity base (D944).
75566
76092
  */
75567
76093
  history: { fields: {
75568
76094
  temperature: {
75569
76095
  kind: "gauge",
75570
- unitFrom: "temperatureUnit",
76096
+ unit: "°C",
75571
76097
  deadband: .2
75572
76098
  },
75573
76099
  humidity: {
@@ -75577,12 +76103,12 @@ var weatherCapability = {
75577
76103
  },
75578
76104
  pressure: {
75579
76105
  kind: "gauge",
75580
- unitFrom: "pressureUnit",
76106
+ unit: "hPa",
75581
76107
  deadband: .5
75582
76108
  },
75583
76109
  windSpeed: {
75584
76110
  kind: "gauge",
75585
- unitFrom: "windSpeedUnit",
76111
+ unit: "m/s",
75586
76112
  deadband: .5
75587
76113
  }
75588
76114
  } }
@@ -76666,6 +77192,103 @@ function createDeviceExtensionProvider(options) {
76666
77192
  }
76667
77193
  };
76668
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
+ }
76669
77292
  /**
76670
77293
  * Frozen unit → spec table. `•` in the doc marks each dimension's canonical
76671
77294
  * unit (`factor: 1, offset: 0`). °F derives from `°C = (°F − 32) × 5/9` ⇒
@@ -76793,6 +77416,11 @@ var UNIT_TABLE = {
76793
77416
  factor: 1609.344,
76794
77417
  offset: 0
76795
77418
  },
77419
+ "m²": {
77420
+ dimension: "area",
77421
+ factor: 1,
77422
+ offset: 0
77423
+ },
76796
77424
  lx: {
76797
77425
  dimension: "illuminance",
76798
77426
  factor: 1,
@@ -76897,6 +77525,121 @@ var UNIT_TABLE = {
76897
77525
  dimension: "percentage",
76898
77526
  factor: 1,
76899
77527
  offset: 0
77528
+ },
77529
+ L: {
77530
+ dimension: "volume",
77531
+ factor: 1,
77532
+ offset: 0
77533
+ },
77534
+ mL: {
77535
+ dimension: "volume",
77536
+ factor: .001,
77537
+ offset: 0
77538
+ },
77539
+ gal: {
77540
+ dimension: "volume",
77541
+ factor: 3.785411784,
77542
+ offset: 0
77543
+ },
77544
+ s: {
77545
+ dimension: "duration",
77546
+ factor: 1,
77547
+ offset: 0
77548
+ },
77549
+ ms: {
77550
+ dimension: "duration",
77551
+ factor: .001,
77552
+ offset: 0
77553
+ },
77554
+ min: {
77555
+ dimension: "duration",
77556
+ factor: 60,
77557
+ offset: 0
77558
+ },
77559
+ h: {
77560
+ dimension: "duration",
77561
+ factor: 3600,
77562
+ offset: 0
77563
+ },
77564
+ d: {
77565
+ dimension: "duration",
77566
+ factor: 86400,
77567
+ offset: 0
77568
+ },
77569
+ B: {
77570
+ dimension: "information",
77571
+ factor: 1,
77572
+ offset: 0
77573
+ },
77574
+ KiB: {
77575
+ dimension: "information",
77576
+ factor: 1024,
77577
+ offset: 0
77578
+ },
77579
+ MiB: {
77580
+ dimension: "information",
77581
+ factor: 1024 ** 2,
77582
+ offset: 0
77583
+ },
77584
+ GiB: {
77585
+ dimension: "information",
77586
+ factor: 1024 ** 3,
77587
+ offset: 0
77588
+ },
77589
+ TiB: {
77590
+ dimension: "information",
77591
+ factor: 1024 ** 4,
77592
+ offset: 0
77593
+ },
77594
+ kB: {
77595
+ dimension: "information",
77596
+ factor: 1e3,
77597
+ offset: 0
77598
+ },
77599
+ MB: {
77600
+ dimension: "information",
77601
+ factor: 1e6,
77602
+ offset: 0
77603
+ },
77604
+ GB: {
77605
+ dimension: "information",
77606
+ factor: 1e9,
77607
+ offset: 0
77608
+ },
77609
+ "°": {
77610
+ dimension: "angle",
77611
+ factor: 1,
77612
+ offset: 0
77613
+ },
77614
+ dBFS: {
77615
+ dimension: "sound-level",
77616
+ factor: 1,
77617
+ offset: 0
77618
+ },
77619
+ dBm: {
77620
+ dimension: "signal-level",
77621
+ factor: 1,
77622
+ offset: 0
77623
+ },
77624
+ mired: {
77625
+ dimension: "colour-temperature",
77626
+ factor: 1,
77627
+ offset: 0
77628
+ },
77629
+ kbps: {
77630
+ dimension: "bitrate",
77631
+ factor: 1,
77632
+ offset: 0
77633
+ },
77634
+ Mbps: {
77635
+ dimension: "bitrate",
77636
+ factor: 1e3,
77637
+ offset: 0
77638
+ },
77639
+ fps: {
77640
+ dimension: "rate",
77641
+ factor: 1,
77642
+ offset: 0
76900
77643
  }
76901
77644
  };
76902
77645
  /** Resolve a unit's `UnitSpec`, or `undefined` when the spelling is unknown. */
@@ -77051,11 +77794,45 @@ function expressionOutcome(source, read, now, stateful) {
77051
77794
  deadline: deadlines.length > 0 ? Math.min(...deadlines) : null
77052
77795
  };
77053
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
+ }
77054
77830
  function evaluateCompositionField(input) {
77055
77831
  const { source } = input;
77056
77832
  switch (source.kind) {
77057
77833
  case "from": return fromReading(input.read(source), source);
77058
77834
  case "expression": return expressionOutcome(source, input.read, input.now, input.stateful);
77835
+ case "aggregate": return aggregateField(source, input.read);
77059
77836
  case "code": return {
77060
77837
  state: "unavailable",
77061
77838
  value: null,
@@ -77402,6 +78179,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
77402
78179
  featureProbe: featureProbeCapability,
77403
78180
  flood: floodCapability,
77404
78181
  gas: gasCapability,
78182
+ groupStatus: groupStatusCapability,
77405
78183
  heatingValve: heatingValveCapability,
77406
78184
  humidifier: humidifierCapability,
77407
78185
  humiditySensor: humiditySensorCapability,
@@ -81664,6 +82442,13 @@ var METHOD_ACCESS_MAP = Object.freeze({
81664
82442
  access: "view",
81665
82443
  role: "viewer"
81666
82444
  },
82445
+ "groupStatus.getStatus": {
82446
+ capName: "group-status",
82447
+ capScope: "device",
82448
+ addonId: null,
82449
+ access: "view",
82450
+ role: "viewer"
82451
+ },
81667
82452
  "heatingValve.getStatus": {
81668
82453
  capName: "heating-valve",
81669
82454
  capScope: "device",
@@ -91486,6 +92271,33 @@ var SENSOR_CAP_VOCABULARY = {
91486
92271
  icon: "flame"
91487
92272
  }]
91488
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
+ },
91489
92301
  "humidity-sensor": {
91490
92302
  scope: "device",
91491
92303
  noun: t("Humidity", "Umidità"),
@@ -91519,7 +92331,6 @@ var SENSOR_CAP_VOCABULARY = {
91519
92331
  field: "value",
91520
92332
  quantity: t("the reading", "il valore"),
91521
92333
  label: t("Reading", "Valore"),
91522
- unitField: "unit",
91523
92334
  icon: "gauge"
91524
92335
  }]
91525
92336
  },
@@ -91655,7 +92466,7 @@ var SENSOR_CAP_VOCABULARY = {
91655
92466
  field: "temperature",
91656
92467
  quantity: t("the outdoor temperature", "la temperatura esterna"),
91657
92468
  label: t("Temperature", "Temperatura"),
91658
- unitField: "temperatureUnit",
92469
+ unit: "°C",
91659
92470
  icon: "thermometer"
91660
92471
  },
91661
92472
  {
@@ -91671,7 +92482,7 @@ var SENSOR_CAP_VOCABULARY = {
91671
92482
  field: "windSpeed",
91672
92483
  quantity: t("the wind speed", "la velocità del vento"),
91673
92484
  label: t("Wind", "Vento"),
91674
- unitField: "windSpeedUnit",
92485
+ unit: "m/s",
91675
92486
  icon: "wind"
91676
92487
  },
91677
92488
  {
@@ -91679,7 +92490,7 @@ var SENSOR_CAP_VOCABULARY = {
91679
92490
  field: "pressure",
91680
92491
  quantity: t("the air pressure", "la pressione atmosferica"),
91681
92492
  label: t("Pressure", "Pressione"),
91682
- unitField: "pressureUnit",
92493
+ unit: "hPa",
91683
92494
  icon: "gauge"
91684
92495
  }
91685
92496
  ]
@@ -92023,6 +92834,7 @@ var CAP_NAMES_WITH_STATUS = [
92023
92834
  "feature-probe",
92024
92835
  "flood",
92025
92836
  "gas",
92837
+ "group-status",
92026
92838
  "heating-valve",
92027
92839
  "humidifier",
92028
92840
  "humidity-sensor",
@@ -92152,6 +92964,7 @@ var ALL_CAPABILITY_DEFINITIONS = [
92152
92964
  filesystemBrowseCapability,
92153
92965
  floodCapability,
92154
92966
  gasCapability,
92967
+ groupStatusCapability,
92155
92968
  heatingValveCapability,
92156
92969
  humidifierCapability,
92157
92970
  humiditySensorCapability,
@@ -93226,6 +94039,11 @@ Object.freeze({
93226
94039
  form: "single",
93227
94040
  optional: false
93228
94041
  }],
94042
+ "groupStatus.getStatus": [{
94043
+ name: "deviceId",
94044
+ form: "single",
94045
+ optional: false
94046
+ }],
93229
94047
  "heatingValve.getStatus": [{
93230
94048
  name: "deviceId",
93231
94049
  form: "single",
@@ -95035,7 +95853,7 @@ Object.freeze({
95035
95853
  });
95036
95854
  /**
95037
95855
  * Per-cap runtime-state policy, projected from every `*.cap.ts` that declares
95038
- * `runtimeState`. 72 caps: 38 `restored`, 34 `session`.
95856
+ * `runtimeState`. 73 caps: 38 `restored`, 35 `session`.
95039
95857
  *
95040
95858
  * A cap absent from this map has no runtime-state slice at all. A cap PRESENT
95041
95859
  * with `durability: 'session'` has one and has decided not to keep it.
@@ -95153,6 +95971,10 @@ var RUNTIME_STATE_POLICY = {
95153
95971
  durability: "restored",
95154
95972
  volatileFields: ["lastChangedAt"]
95155
95973
  },
95974
+ "group-status": {
95975
+ durability: "session",
95976
+ volatileFields: []
95977
+ },
95156
95978
  "heating-valve": {
95157
95979
  durability: "session",
95158
95980
  volatileFields: ["lastFetchedAt"]
@@ -95347,7 +96169,7 @@ var SESSION_ONLY_POLICY = {
95347
96169
  };
95348
96170
  /**
95349
96171
  * Cap history catalog, projected from every `*.cap.ts` `history` block.
95350
- * 41 caps with series (74 series), 30 caps answered `'none'`.
96172
+ * 41 caps with series (75 series), 31 caps answered `'none'`.
95351
96173
  * Field keys are `runtimeState` paths (`a.b` into fixed objects only).
95352
96174
  */
95353
96175
  var HISTORY_CATALOG = {
@@ -95364,12 +96186,12 @@ var HISTORY_CATALOG = {
95364
96186
  },
95365
96187
  "pm25": {
95366
96188
  kind: "gauge",
95367
- unit: "ug/m3",
96189
+ unit: "µg/m³",
95368
96190
  deadband: 1
95369
96191
  },
95370
96192
  "pm10": {
95371
96193
  kind: "gauge",
95372
- unit: "ug/m3",
96194
+ unit: "µg/m³",
95373
96195
  deadband: 1
95374
96196
  },
95375
96197
  "aqi": {
@@ -95399,10 +96221,16 @@ var HISTORY_CATALOG = {
95399
96221
  deadbandPct: 10,
95400
96222
  minIntervalMs: 6e4
95401
96223
  } },
95402
- "battery": { "percentage": {
95403
- kind: "gauge",
95404
- unit: "%"
95405
- } },
96224
+ "battery": {
96225
+ "percentage": {
96226
+ kind: "gauge",
96227
+ unit: "%"
96228
+ },
96229
+ "volts": {
96230
+ kind: "gauge",
96231
+ unit: "V"
96232
+ }
96233
+ },
95406
96234
  "binary": { "on": { kind: "boolean" } },
95407
96235
  "brightness": { "percentage": {
95408
96236
  kind: "gauge",
@@ -95486,7 +96314,7 @@ var HISTORY_CATALOG = {
95486
96314
  },
95487
96315
  "enum-sensor": { "value": {
95488
96316
  kind: "enum",
95489
- skipWhenSet: "format"
96317
+ skipWhenSet: "timestamp"
95490
96318
  } },
95491
96319
  "fan-control": {
95492
96320
  "percentage": {
@@ -95559,7 +96387,6 @@ var HISTORY_CATALOG = {
95559
96387
  },
95560
96388
  "numeric-sensor": { "value": {
95561
96389
  kind: "gauge",
95562
- unitFrom: "unit",
95563
96390
  minIntervalMs: 3e4
95564
96391
  } },
95565
96392
  "pet-feeder": {
@@ -95573,7 +96400,7 @@ var HISTORY_CATALOG = {
95573
96400
  },
95574
96401
  "desiccantLeftDays": {
95575
96402
  kind: "gauge",
95576
- unit: "days"
96403
+ unit: "s"
95577
96404
  }
95578
96405
  },
95579
96406
  "power-meter": {
@@ -95677,7 +96504,7 @@ var HISTORY_CATALOG = {
95677
96504
  "weather": {
95678
96505
  "temperature": {
95679
96506
  kind: "gauge",
95680
- unitFrom: "temperatureUnit",
96507
+ unit: "°C",
95681
96508
  deadband: .2
95682
96509
  },
95683
96510
  "humidity": {
@@ -95687,12 +96514,12 @@ var HISTORY_CATALOG = {
95687
96514
  },
95688
96515
  "pressure": {
95689
96516
  kind: "gauge",
95690
- unitFrom: "pressureUnit",
96517
+ unit: "hPa",
95691
96518
  deadband: .5
95692
96519
  },
95693
96520
  "windSpeed": {
95694
96521
  kind: "gauge",
95695
- unitFrom: "windSpeedUnit",
96522
+ unit: "m/s",
95696
96523
  deadband: .5
95697
96524
  }
95698
96525
  }
@@ -95726,7 +96553,6 @@ var HISTORY_ITEM_CATALOG = { "consumables": {
95726
96553
  },
95727
96554
  "remaining.value": {
95728
96555
  kind: "gauge",
95729
- unitFrom: "remaining.unit",
95730
96556
  deadband: 1,
95731
96557
  displayName: "Remaining"
95732
96558
  }
@@ -97462,4 +98288,4 @@ function armRemovalReconcile(opts) {
97462
98288
  return () => clearInterval(timer);
97463
98289
  }
97464
98290
  //#endregion
97465
- export { PreferenceDeviceIdSchema as $, historyItemFieldPath as $i, planComposition as $n, DeviceRole as $r, deviceExtensionCapability as $t, DEVICE_SAN_USER_PREFIX as A, StoredRecordKindDeclarationSchema as Ai, keychainAdminCapability as An, streamCatalogCapability as Ar, buildDeviceSignatureMessage as At, HISTORY_ITEM_CATALOG as B, buildSecretRef as Bi, mayReadLocation as Bn, COMPOSER_ADDON_ID as Br, coreBlockComposition as Bt, DATA_STORE_DATABASES as C, sleep as Ca, ReadinessTimeoutError as Ci, isCollectionArrayMethod as Cn, snapshotCapability as Cr, armRemovalReconcile as Ct, DEVICE_HISTORY_ENERGY_KEY as D, userError as Da, RecordUniqueViolationError as Di, isObjectInput as Dn, storageMigrationCapability as Dr, batteryCapability as Dt, DEVICE_ENROLLMENT_TTL_MS as E, transformSecretWrite as Ea, RecordTooLargeError as Ei, isLocationEnabled as En, storageCapability as Er, bareAddonId as Et, DeviceTombstoneSchema as F, asJsonObject as Fi, localNetworkCapability as Fn, validateCustomAddress as Fr, checkRoleAddress as Ft, KeychainResetRecordSchema as G, createEvent as Gi, motionCapability as Gn, compositionFeatureEnabled as Gr, dataStoreProviderCapability as Gt, HUB_MDNS_TXT_KEYS as H, collectSecretLeaves as Hi, memoizePinnedFfmpeg as Hn, cloneStatefulExpressionMemory as Hr, coreBlockSourceKind as Ht, FIRST_LEVEL_MACRO_CLASSES as I, asNumber as Ii, locationIconKey as In, vectorDimFromBase64 as Ir, composerCapability as It, METHOD_ACCESS_MAP as J, defineRecordKinds as Ji, objectInputDeclaresAddonId as Jn, compositionSourceKey as Jr, decodeVectorBase64 as Jt, LOG_LEVEL_RANK as K, customAction as Ki, normalizeCoreBlockWrite as Kn, compositionItemFieldPath as Kr, databaseSnapshotCapability as Kt, FieldClaimModeSchema as L, asString$1 as Li, logDestinationCapability as Ln, vectorIndexDimConflictError as Lr, compositionFieldKey as Lt, DataStoreDatabaseSchema as M, UnindexedFilterError as Mi, legacyAutoLinkIds as Mn, supportedRuntimes as Mr, calendarsCapability as Mt, DeclaredDevices as N, UnknownRecordKindError as Ni, legacyModeOf as Nn, userManagementCapability as Nr, cameraGridLayoutCapability as Nt, DEVICE_NONCE_TTL_MS as O, withoutCallerStamp as Oa, RevConflictError as Oi, isVoidInput as On, storageOccupancyCapability as Or, buildCapabilityLookup as Ot, DeviceStatusSchema as P, WELL_KNOWN_TAB_MAP as Pi, lifecycleJobSchema as Pn, validateComposition as Pr, canonicalIconKey as Pt, PREFERENCES_MAX_VALUE_BYTES as Q, formatSecretScope as Qi, pickAppLinkHub as Qn, DeviceFeature as Qr, deviceCredentialsCapability as Qt, FieldClaimSchema as R, assertTransactScope as Ri, logLevelAtMost as Rn, vectorStoreCapability as Rr, compositionSourceRefs as Rt, DATABASE_SNAPSHOT_TIMEOUT_MS as S, secretTreeFromSchema as Sa, ReadinessRegistry as Si, isArrayOutputSchema as Sn, siteZoneKey as Sr, alertsCapability as St, DERIVED_CAMERA_SILENCED_CAP_NAMES as T, toSecretFieldView as Ta, RecordNotFoundError as Ti, isDefaultAppConnectionEndpoint as Tn, storageAdminCapability as Tr, backupCapability as Tt, KEYCHAIN_RESET_TRIGGER_ACTOR as U, collectSecretMarkerRefs as Ui, metricsProviderCapability as Un, composedDeviceRef as Ur, coreBlocksCapability as Ut, HUB_MDNS_SERVICE_TYPE as V, collectSecretConfigKeys as Vi, mayWriteToLocation as Vn, buildFfmpegArgs as Vr, coreBlockIdFromAddonId as Vt, KeychainAuditEntrySchema as W, collectZodSecretPaths as Wi, modeMayWrite as Wn, composedDeviceStableId as Wr, createDeviceExtensionProvider as Wt, MOTION_CLOSE_AFTER_MS as X, emitReadiness as Xi, parseAcceleratorFactsJson as Xn, disarmRoseMemory as Xr, deriveBatteryPresence as Xt, MINUTES_PER_DAY as Y, emitDownForOwnedCaps as Yi, objectInputDeclaresRequiredDeviceId as Yn, createStatefulExpressionMemory as Yr, defineExtensionSlotProvider as Yt, NetworkTunnelStartedDataSchema as Z, expandCapMethods as Zi, parseStreamParamsFormPatch as Zn, toExpressionValue as Zr, describeComposableCaps as Zt, CatalogFileSchema as _, recordSecretRef as _a, RECORD_DELETE_BATCH as _i, gbToBytes as _n, scopesAllowAddon as _r, addonPagesCapability as _t, BaseDevice as a, isSecretMarker as aa, DATAPLANE_SECRET_HEADER as ai, enumerateInferenceDevices as an, recordsCapability as ar, STORAGE_ACCESS_FALLBACK as at, DATABASE_SNAPSHOT_PARTIAL_SUFFIX as b, scopeKey as ba, REMOVE_LEAF as bi, isAlertEpisodeSeverity as bn, settingsStoreCapability as br, alertEpisodeId as bt, CAMSTACK_APP_LINK_ORIGIN as c, makeSecretMarker as ca, DeclarationRefusedError as ci, evaluateCompositionField as cn, resolveAppConnections as cr, ServerNameSchema as ct, COMPOSED_ITEM_FIXED_FIELDS as d, normalizeNodeId as da, HISTORY_DEFAULT_MIN_INTERVAL_MS as di, expressionContainsBuiltinCall as dn, resolveCoreBlockFiles as dr, StorageLocationModeSchema as dt, hydrateSchema as ea, DeviceType as ei, deviceHistoryCapability as en, platformProbeCapability as er, PreferenceNamespaceSchema as et, COMPOSER_RUNNER_ID as f, normalizeSecretOwner as fa, InvalidRecordError as fi, extractNestedAddonId as fn, resolveHubAddressRoles as fr, StorageLocationTypeSchema as ft, CalendarPreferencesSchema as g, readinessKey as ga, RECORD_BATCH_MAX_BYTES as gi, formatMinutes as gn, runtimeStatePolicyFor as gr, UserRecordSchema as gt, CREDENTIAL_TOKEN_AUDIENCE as h, parseSecretRef as ha, RECORD_BATCH_MAX as hi, formatDeviceRevoker as hn, resolveNotificationTapAddress as hr, UNATTRIBUTED_BUCKET_KEY as ht, AlertSchema as i, isSecretClear as ia, CALLER_STAMPED_CAPABILITIES as ii, deviceStatusCapability as in, recordEntriesCapability as ir, RecordingStatusSchema as it, DEVICE_TOMBSTONE_RETENTION_MS as j, TransactRefusedError as ji, keychainCapability as jn, streamQualityLabel as jr, buildStreamParamsConfigSchema as jt, DEVICE_SAN_CREDENTIAL_PREFIX as k, EventCategory as ka, SECRET_CLEAR as ki, kebabToCamel as kn, storageProviderCapability as kr, buildDeviceEnrollmentLink as kt, CAP_NAMES_WITH_STATUS as l, mapSecretLeaves as la, FilterRequiredError as li, evictionPolicyOfLocation as ln, resolveBucketMs as lr, SsoBridgeClaimsSchema as lt, CORE_BLOCK_ADDON_PREFIX as m, parseJsonUnknown as ma, PlaintextSecretError as mi, foldSnapshotByFunction as mn, resolveMethodAuth as mr, TokenScopeSchema as mt, AGENT_MDNS_TXT_KEYS as n, isEvent as na, BaseAddon as ni, deviceRemovalHookCapability as nn, procedureAuthKey as nr, RECORDING_SOURCE_CAMSTACK_ADDON as nt, BatteryStatusSchema as o, isSecretMarkerLike as oa, DEVICE_SETTINGS_CONTRIBUTION_METHODS as oi, enumerateItemArrayFields as on, reducePoints as or, STREAM_PROFILE_META as ot, CORE_BLOCKS_ADDON_ID as p, parseJsonObject as pa, NoCallerError as pi, filesystemBrowseCapability as pn, resolveLocationMode as pr, StorageMigrationJobSchema as pt, LegacyStorageLocationDefaultSchema as q, defineCustomActions as qi, normalizeUnit as qn, compositionSliceKey as qr, declarationOwnerNodeId as qt, ALL_CAPABILITY_DEFINITIONS as r, isKeychainStoredField as ra, BatchTooLargeError as ri, deviceStateCapability as rn, ptzAutotrackCapability as rr, ROOT_BUCKET_KEY as rt, CAMERA_SWITCH_ORDER as s, isSecretRefOwnedBy as sa, DEVICE_STATUS_METHOD as si, enumerateSchemaFields as sn, replacedFieldKeys as sr, ScopedTokenSchema as st, AGENT_MDNS_SERVICE_TYPE as t, isDeviceConfigCap as ta, errMsg as ti, deviceManagerCapability as tn, problemsBlockSave as tr, PreferenceValueSchema as tt, CATALOG_LANGS as u, nodePin as ua, ForeignSecretMarkerError as ui, expandContainer as un, resolveCoreBlockEntry as ur, StorageCleanupJobSchema as ut, CoreBlockSchema as v, resolveCapMount as va, RECORD_SWEEP_INTERVAL_MS as vi, interpolate as vn, scopesAllowDeviceCap as vr, addonSettingsCapability as vt, DAY_NAMES as w, toNodeId as wa, RecordKindDeclarationSchema as wi, isCredentialTokenPayload as wn, splitTranslationRef as wr, authProviderCapability as wt, DATABASE_SNAPSHOT_SUFFIX as x, secretTreeFromPaths as xa, RUNTIME_DEFAULTS as xi, isAppLinkBase as xn, shortHostname as xr, alertEpisodePayload as xt, CredentialTokenClaimsSchema as y, resolveSecretMarkers as ya, REDACTED_SECRET as yi, isAlertEpisodeOpen as yn, scoreRuntimes as yr, addonWidgetsCapability as yt, HISTORY_CATALOG as z, assertValidDeclaration as zi, looseSchema as zn, withLocationMode as zr, coreBlockAddonId as zt };
98291
+ export { PreferenceDeviceIdSchema as $, historyItemFieldPath as $i, planComposition as $n, DeviceRole as $r, deviceExtensionCapability as $t, DEVICE_SAN_USER_PREFIX as A, withoutCallerStamp as Aa, StoredRecordKindDeclarationSchema as Ai, keychainAdminCapability as An, streamCatalogCapability as Ar, buildDeviceSignatureMessage as At, HISTORY_ITEM_CATALOG as B, buildSecretRef as Bi, mayReadLocation as Bn, COMPOSER_ADDON_ID as Br, coreBlockComposition as Bt, DATA_STORE_DATABASES as C, secretTreeFromPaths as Ca, ReadinessTimeoutError as Ci, isCollectionArrayMethod as Cn, snapshotCapability as Cr, armRemovalReconcile as Ct, DEVICE_HISTORY_ENERGY_KEY as D, toSecretFieldView as Da, RecordUniqueViolationError as Di, isObjectInput as Dn, storageMigrationCapability as Dr, batteryCapability as Dt, DEVICE_ENROLLMENT_TTL_MS as E, toNodeId as Ea, RecordTooLargeError as Ei, isLocationEnabled as En, storageCapability as Er, bareAddonId as Et, DeviceTombstoneSchema as F, asJsonObject as Fi, localNetworkCapability as Fn, validateCustomAddress as Fr, checkRoleAddress as Ft, KeychainResetRecordSchema as G, createEvent as Gi, motionCapability as Gn, compositionFeatureEnabled as Gr, dataStoreProviderCapability as Gt, HUB_MDNS_TXT_KEYS as H, collectSecretLeaves as Hi, memoizePinnedFfmpeg as Hn, cloneStatefulExpressionMemory as Hr, coreBlockSourceKind as Ht, FIRST_LEVEL_MACRO_CLASSES as I, asNumber as Ii, locationIconKey as In, vectorDimFromBase64 as Ir, composerCapability as It, METHOD_ACCESS_MAP as J, defineRecordKinds as Ji, objectInputDeclaresAddonId as Jn, compositionSourceKey as Jr, decodeVectorBase64 as Jt, LOG_LEVEL_RANK as K, customAction as Ki, normalizeCoreBlockWrite as Kn, compositionItemFieldPath as Kr, databaseSnapshotCapability as Kt, FieldClaimModeSchema as L, asString$1 as Li, logDestinationCapability as Ln, vectorIndexDimConflictError as Lr, compositionFieldKey as Lt, DataStoreDatabaseSchema as M, UnindexedFilterError as Mi, legacyAutoLinkIds as Mn, supportedRuntimes as Mr, calendarsCapability as Mt, DeclaredDevices as N, UnknownRecordKindError as Ni, legacyModeOf as Nn, userManagementCapability as Nr, cameraGridLayoutCapability as Nt, DEVICE_NONCE_TTL_MS as O, transformSecretWrite as Oa, RevConflictError as Oi, isVoidInput as On, storageOccupancyCapability as Or, buildCapabilityLookup as Ot, DeviceStatusSchema as P, WELL_KNOWN_TAB_MAP as Pi, lifecycleJobSchema as Pn, validateComposition as Pr, canonicalIconKey as Pt, PREFERENCES_MAX_VALUE_BYTES as Q, formatSecretScope as Qi, pickAppLinkHub as Qn, DeviceFeature as Qr, deviceCredentialsCapability as Qt, FieldClaimSchema as R, assertTransactScope as Ri, logLevelAtMost as Rn, vectorStoreCapability as Rr, compositionSourceRefs as Rt, DATABASE_SNAPSHOT_TIMEOUT_MS as S, scopeKey as Sa, ReadinessRegistry as Si, isArrayOutputSchema as Sn, siteZoneKey as Sr, alertsCapability as St, DERIVED_CAMERA_SILENCED_CAP_NAMES as T, sleep as Ta, RecordNotFoundError as Ti, isDefaultAppConnectionEndpoint as Tn, storageAdminCapability as Tr, backupCapability as Tt, KEYCHAIN_RESET_TRIGGER_ACTOR as U, collectSecretMarkerRefs as Ui, metricsProviderCapability as Un, composedDeviceRef as Ur, coreBlocksCapability as Ut, HUB_MDNS_SERVICE_TYPE as V, collectSecretConfigKeys as Vi, mayWriteToLocation as Vn, buildFfmpegArgs as Vr, coreBlockIdFromAddonId as Vt, KeychainAuditEntrySchema as W, collectZodSecretPaths as Wi, modeMayWrite as Wn, composedDeviceStableId as Wr, createDeviceExtensionProvider as Wt, MOTION_CLOSE_AFTER_MS as X, emitReadiness as Xi, parseAcceleratorFactsJson as Xn, disarmRoseMemory as Xr, deriveBatteryPresence as Xt, MINUTES_PER_DAY as Y, emitDownForOwnedCaps as Yi, objectInputDeclaresRequiredDeviceId as Yn, createStatefulExpressionMemory as Yr, defineExtensionSlotProvider as Yt, NetworkTunnelStartedDataSchema as Z, expandCapMethods as Zi, parseStreamParamsFormPatch as Zn, toExpressionValue as Zr, describeComposableCaps as Zt, CatalogFileSchema as _, parseSecretRef as _a, RECORD_DELETE_BATCH as _i, gbToBytes as _n, scopesAllowAddon as _r, addonPagesCapability as _t, BaseDevice as a, isReadingQuantity as aa, DATAPLANE_SECRET_HEADER as ai, enumerateInferenceDevices as an, recordsCapability as ar, STORAGE_ACCESS_FALLBACK as at, DATABASE_SNAPSHOT_PARTIAL_SUFFIX as b, resolveCapMount as ba, REMOVE_LEAF as bi, isAlertEpisodeSeverity as bn, settingsStoreCapability as br, alertEpisodeId as bt, CAMSTACK_APP_LINK_ORIGIN as c, isSecretMarkerLike as ca, DeclarationRefusedError as ci, evaluateCompositionField as cn, resolveAppConnections as cr, ServerNameSchema as ct, COMPOSED_ITEM_FIXED_FIELDS as d, mapSecretLeaves as da, HISTORY_DEFAULT_MIN_INTERVAL_MS as di, expressionContainsBuiltinCall as dn, resolveCoreBlockFiles as dr, StorageLocationModeSchema as dt, historyUnitFor as ea, DeviceType as ei, deviceHistoryCapability as en, platformProbeCapability as er, PreferenceNamespaceSchema as et, COMPOSER_RUNNER_ID as f, nodePin as fa, InvalidRecordError as fi, extractNestedAddonId as fn, resolveHubAddressRoles as fr, StorageLocationTypeSchema as ft, CalendarPreferencesSchema as g, parseJsonUnknown as ga, RECORD_BATCH_MAX_BYTES as gi, formatMinutes as gn, runtimeStatePolicyFor as gr, UserRecordSchema as gt, CREDENTIAL_TOKEN_AUDIENCE as h, parseJsonObject as ha, RECORD_BATCH_MAX as hi, formatDeviceRevoker as hn, resolveNotificationTapAddress as hr, UNATTRIBUTED_BUCKET_KEY as ht, AlertSchema as i, isKeychainStoredField as ia, CALLER_STAMPED_CAPABILITIES as ii, deviceStatusCapability as in, recordEntriesCapability as ir, RecordingStatusSchema as it, DEVICE_TOMBSTONE_RETENTION_MS as j, EventCategory as ja, TransactRefusedError as ji, keychainCapability as jn, streamQualityLabel as jr, buildStreamParamsConfigSchema as jt, DEVICE_SAN_CREDENTIAL_PREFIX as k, userError as ka, SECRET_CLEAR as ki, kebabToCamel as kn, storageProviderCapability as kr, buildDeviceEnrollmentLink as kt, CAP_NAMES_WITH_STATUS as l, isSecretRefOwnedBy as la, FilterRequiredError as li, evictionPolicyOfLocation as ln, resolveBucketMs as lr, SsoBridgeClaimsSchema as lt, CORE_BLOCK_ADDON_PREFIX as m, normalizeSecretOwner as ma, PlaintextSecretError as mi, foldSnapshotByFunction as mn, resolveMethodAuth as mr, TokenScopeSchema as mt, AGENT_MDNS_TXT_KEYS as n, isDeviceConfigCap as na, BaseAddon as ni, deviceRemovalHookCapability as nn, procedureAuthKey as nr, RECORDING_SOURCE_CAMSTACK_ADDON as nt, BatteryStatusSchema as o, isSecretClear as oa, DEVICE_SETTINGS_CONTRIBUTION_METHODS as oi, enumerateItemArrayFields as on, reducePoints as or, STREAM_PROFILE_META as ot, CORE_BLOCKS_ADDON_ID as p, normalizeNodeId as pa, NoCallerError as pi, filesystemBrowseCapability as pn, resolveLocationMode as pr, StorageMigrationJobSchema as pt, LegacyStorageLocationDefaultSchema as q, defineCustomActions as qi, normalizeUnit as qn, compositionSliceKey as qr, declarationOwnerNodeId as qt, ALL_CAPABILITY_DEFINITIONS as r, isEvent as ra, BatchTooLargeError as ri, deviceStateCapability as rn, ptzAutotrackCapability as rr, ROOT_BUCKET_KEY as rt, CAMERA_SWITCH_ORDER as s, isSecretMarker as sa, DEVICE_STATUS_METHOD as si, enumerateSchemaFields as sn, replacedFieldKeys as sr, ScopedTokenSchema as st, AGENT_MDNS_SERVICE_TYPE as t, hydrateSchema as ta, errMsg as ti, deviceManagerCapability as tn, problemsBlockSave as tr, PreferenceValueSchema as tt, CATALOG_LANGS as u, makeSecretMarker as ua, ForeignSecretMarkerError as ui, expandContainer as un, resolveCoreBlockEntry as ur, StorageCleanupJobSchema as ut, CoreBlockSchema as v, readinessKey as va, RECORD_SWEEP_INTERVAL_MS as vi, interpolate as vn, scopesAllowDeviceCap as vr, addonSettingsCapability as vt, DAY_NAMES as w, secretTreeFromSchema as wa, RecordKindDeclarationSchema as wi, isCredentialTokenPayload as wn, splitTranslationRef as wr, authProviderCapability as wt, DATABASE_SNAPSHOT_SUFFIX as x, resolveSecretMarkers as xa, RUNTIME_DEFAULTS as xi, isAppLinkBase as xn, shortHostname as xr, alertEpisodePayload as xt, CredentialTokenClaimsSchema as y, recordSecretRef as ya, REDACTED_SECRET as yi, isAlertEpisodeOpen as yn, scoreRuntimes as yr, addonWidgetsCapability as yt, HISTORY_CATALOG as z, assertValidDeclaration as zi, looseSchema as zn, withLocationMode as zr, coreBlockAddonId as zt };