@camstack/addon-post-analysis 1.2.655 → 1.2.657

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 (16) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/{clip-model-registry-_b3KHYFm.mjs → clip-model-registry-OHEqXA-p.mjs} +2439 -1117
  3. package/dist/{clip-model-registry-C-en5vSD.js → clip-model-registry-zi2g3vrD.js} +2438 -1116
  4. package/dist/embedding-encoder/index.js +5 -5
  5. package/dist/embedding-encoder/index.mjs +4 -4
  6. package/dist/pipeline-analytics/_stub.js +2 -2
  7. package/dist/pipeline-analytics/{_virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-CF-Mts9j.mjs → _virtual_mf-localSharedImportMap___mfe_internal__addon_pipeline_analytics_widgets-B15uv0HK.mjs} +3 -3
  8. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-1DyKMo-9.mjs +26 -0
  9. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-BwWOFFOc.mjs +26 -0
  10. package/dist/pipeline-analytics/{hostInit-BSfz5s98.mjs → hostInit-BAuJ8QZH.mjs} +3 -3
  11. package/dist/pipeline-analytics/index.js +25 -7
  12. package/dist/pipeline-analytics/index.mjs +24 -6
  13. package/dist/pipeline-analytics/remoteEntry.js +1 -1
  14. package/package.json +10 -3
  15. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_types__loadShare__.js-C7OJ7wek.mjs +0 -26
  16. package/dist/pipeline-analytics/_virtual_mf___mfe_internal__addon_pipeline_analytics_widgets__loadShare___mf_0_camstack_mf_1_ui_mf_2_library__loadShare__.js-DXQUFp7P.mjs +0 -26
@@ -30,7 +30,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
30
30
  }) : target, mod));
31
31
  //#endregion
32
32
  let node_crypto = require("node:crypto");
33
- //#region ../types/dist/event-category-DmZDPDf_.mjs
33
+ //#region ../types/dist/event-category-CaURIwQA.mjs
34
34
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
35
35
  EventCategory["SystemBoot"] = "system.boot";
36
36
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -746,6 +746,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
746
746
  EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
747
747
  /** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
748
748
  EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
749
+ /** A migration failed or was blocked; the Alert Center re-reads the ledger. */
750
+ EventCategory["MigrationFailed"] = "migration.failed";
751
+ /** A migration applied or a failure cleared; the Alert Center re-reads the ledger. */
752
+ EventCategory["MigrationApplied"] = "migration.applied";
749
753
  EventCategory["DeviceUpdated"] = "device.updated";
750
754
  /**
751
755
  * Transport-level connectivity. Emitted by the device driver when
@@ -808,6 +812,378 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
808
812
  return EventCategory;
809
813
  }({});
810
814
  //#endregion
815
+ //#region ../types/dist/device-type-CEqjmUnR.mjs
816
+ /**
817
+ import { errMsg } from '@camstack/types'
818
+ * Extract a human-readable message from an unknown error value.
819
+ * Replaces the ubiquitous `errMsg(err)` pattern.
820
+ */
821
+ function errMsg(err) {
822
+ if (err instanceof Error) return err.message;
823
+ if (typeof err === "string") return err;
824
+ return String(err);
825
+ }
826
+ var DeviceType = /* @__PURE__ */ function(DeviceType) {
827
+ DeviceType["Camera"] = "camera";
828
+ DeviceType["Hub"] = "hub";
829
+ DeviceType["Light"] = "light";
830
+ DeviceType["Siren"] = "siren";
831
+ DeviceType["Switch"] = "switch";
832
+ DeviceType["Sensor"] = "sensor";
833
+ DeviceType["Thermostat"] = "thermostat";
834
+ /** Air-conditioner / heat-pump climate device (HVAC) — shares the
835
+ * `climate-control` cap surface with `Thermostat` but renders a
836
+ * dedicated AC-appropriate control UI (mode chips, fan speed,
837
+ * independent vertical/horizontal swing). Sources: native Gree, and
838
+ * reusable by other AC integrations. */
839
+ DeviceType["Climate"] = "climate";
840
+ DeviceType["Button"] = "button";
841
+ /** Generic stateless event emitter — carries a device's EXACT declared
842
+ * event vocabulary verbatim (no normalization). Installed with the
843
+ * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
844
+ * HA bus events (e.g. `zha_event`, generic). */
845
+ DeviceType["EventEmitter"] = "event-emitter";
846
+ /** Firmware/software update entity — current vs available version,
847
+ * updatable flag, update state, and an install action. Installed with
848
+ * the `update` cap. Sources: Homematic firmware-update channels (and
849
+ * reusable by other providers, e.g. HA `update.*` entities). */
850
+ DeviceType["Update"] = "update";
851
+ DeviceType["Generic"] = "generic";
852
+ /** Generic notification delivery target (HA `notify.<service>`, future
853
+ * Telegram / Discord / ntfy / SMTP, …). One device per delivery
854
+ * endpoint; the `notifier` cap defines the send surface. */
855
+ DeviceType["Notifier"] = "notifier";
856
+ /** Pre-recorded action sequence with optional parameters
857
+ * (HA `script.*`). Runnable via `script-runner` cap. */
858
+ DeviceType["Script"] = "script";
859
+ /** Automation rule (HA `automation.*`) — enable/disable + manual
860
+ * trigger surface exposed via `automation-control` cap. */
861
+ DeviceType["Automation"] = "automation";
862
+ /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
863
+ DeviceType["Lock"] = "lock";
864
+ /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
865
+ * `valve.*`). `cover` cap with sub-roles for variant. */
866
+ DeviceType["Cover"] = "cover";
867
+ /** Pipe / water / gas valve with open/close/stop and optional
868
+ * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
869
+ * modelled on the same open/closed lifecycle. */
870
+ DeviceType["Valve"] = "valve";
871
+ /** Humidifier / dehumidifier with on/off + target humidity + mode
872
+ * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
873
+ * modelled on the same target / mode lifecycle. */
874
+ DeviceType["Humidifier"] = "humidifier";
875
+ /** Water heater / boiler with target temperature + operation mode +
876
+ * away mode (HA `water_heater.*`). `water-heater` cap — a
877
+ * climate-family actuator. */
878
+ DeviceType["WaterHeater"] = "water-heater";
879
+ /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
880
+ DeviceType["Fan"] = "fan";
881
+ /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
882
+ * the camera surface — those use `Camera`. `media-player` cap. */
883
+ DeviceType["MediaPlayer"] = "media-player";
884
+ /** Security panel / alarm system (HA `alarm_control_panel.*`).
885
+ * `alarm-panel` cap. */
886
+ DeviceType["AlarmPanel"] = "alarm-panel";
887
+ /** Generic user-settable input (HA `number` / `input_number` / `select`
888
+ * / `input_select` / `text` / `input_text` / `input_datetime`).
889
+ * Sub-type via `DeviceRole`: NumericControl / SelectControl /
890
+ * TextControl / DateTimeControl. */
891
+ DeviceType["Control"] = "control";
892
+ /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
893
+ * `presence` cap. */
894
+ DeviceType["Presence"] = "presence";
895
+ /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
896
+ * `weather` cap. */
897
+ DeviceType["Weather"] = "weather";
898
+ /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
899
+ DeviceType["Vacuum"] = "vacuum";
900
+ /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
901
+ * `lawn-mower-control` cap. */
902
+ DeviceType["LawnMower"] = "lawn-mower";
903
+ /** Physical HA device group — parent container for entity-children
904
+ * adopted from a single HA device entry. Not renderable as a
905
+ * standalone device; exists only to anchor child entities. */
906
+ DeviceType["Container"] = "container";
907
+ /** Single still-image entity (HA `image.*`). Read-only display of an
908
+ * `entity_picture` signed URL the browser loads directly. `image` cap. */
909
+ DeviceType["Image"] = "image";
910
+ /** Smart pet feeder — cloud-connected food dispenser with a bowl food
911
+ * level, battery, desiccant life, feeding state and manual-feed /
912
+ * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
913
+ * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
914
+ * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
915
+ * integrations sharing the same food/desiccant/hopper surface. */
916
+ DeviceType["PetFeeder"] = "pet-feeder";
917
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
918
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
919
+ * of a container and is BUILT as a composed device (D967). */
920
+ DeviceType["PetFountain"] = "pet-fountain";
921
+ /** Built-in dishwasher — program lifecycle, door / remote-start gates,
922
+ * salt and rinse-aid levels. Installed with the `dishwasher` cap.
923
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
924
+ * A plug-built dishwasher carries running-state and power instead. */
925
+ DeviceType["Dishwasher"] = "dishwasher";
926
+ /** Washing machine. A plug-built one is running while its power stays
927
+ * above a threshold. No program cap — that surface is the dishwasher's. */
928
+ DeviceType["Washer"] = "washer";
929
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
930
+ DeviceType["Dryer"] = "dryer";
931
+ /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
932
+ * other radio-to-IP bridges). It carries the box's own facts (firmware,
933
+ * address, connection state) and the actions the box really supports
934
+ * (`reboot`, `device-admin-link`). Products are NOT its children — they are
935
+ * adopted beside it — and its connection settings stay on the integration. */
936
+ DeviceType["Gateway"] = "gateway";
937
+ return DeviceType;
938
+ }({});
939
+ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
940
+ DeviceFeature["BatteryOperated"] = "battery-operated";
941
+ DeviceFeature["Rebootable"] = "rebootable";
942
+ /**
943
+ * Device supports an on-demand re-sync of its derived spec with its
944
+ * upstream source — drives the generic Re-sync button. The owning
945
+ * provider implements the action via the `device-adoption.resync` cap.
946
+ */
947
+ DeviceFeature["Resyncable"] = "resyncable";
948
+ DeviceFeature["NativeSnapshot"] = "native-snapshot";
949
+ DeviceFeature["DoorbellButton"] = "doorbell-button";
950
+ DeviceFeature["TwoWayAudio"] = "two-way-audio";
951
+ DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
952
+ /**
953
+ * Camera supports the on-firmware autotrack subsystem (subject-
954
+ * following). Distinct from `PanTiltZoom` because not every PTZ
955
+ * camera ships autotrack — the admin UI uses this flag to gate
956
+ * the autotrack toggle / settings card without re-deriving from
957
+ * the cap registry. Mirrors `ptz-autotrack` cap registration:
958
+ * driver sets this feature when probe confirms the firmware
959
+ * surface, and registers the cap in the same code path.
960
+ */
961
+ DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
962
+ /**
963
+ * Accessory exposes a "trigger on motion" toggle — the parent camera's
964
+ * motion detection automatically activates this device. Mirrors
965
+ * `motion-trigger` cap registration: drivers set this feature in the
966
+ * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
967
+ *
968
+ * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
969
+ * fast scalar without binding fetch), notifier rules, and `listAll`
970
+ * filters that want "all devices with on-motion behaviour".
971
+ */
972
+ DeviceFeature["MotionTrigger"] = "motion-trigger";
973
+ /** Light supports rgb-triplet color via `color` cap. */
974
+ DeviceFeature["LightColorRgb"] = "light-color-rgb";
975
+ /** Light supports HSV color via `color` cap. */
976
+ DeviceFeature["LightColorHsv"] = "light-color-hsv";
977
+ /** Light supports color-temperature (mired) via `color` cap. */
978
+ DeviceFeature["LightColorMired"] = "light-color-mired";
979
+ /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
980
+ * targetHigh). Gates the range slider UI. */
981
+ DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
982
+ /** Thermostat exposes target humidity and/or current humidity
983
+ * readings. Gates the humidity controls. */
984
+ DeviceFeature["ClimateHumidity"] = "climate-humidity";
985
+ /** Thermostat exposes a fan-mode selector. */
986
+ DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
987
+ /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
988
+ DeviceFeature["ClimatePreset"] = "climate-preset";
989
+ /** Thermostat exposes a vertical louver swing toggle. Gates the
990
+ * vertical-swing switch in the climate UI. Independent of horizontal. */
991
+ DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
992
+ /** Thermostat exposes a horizontal louver swing toggle. Gates the
993
+ * horizontal-swing switch in the climate UI. Independent of vertical. */
994
+ DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
995
+ /** Cover exposes intermediate position control (0..100). Gates the
996
+ * position slider UI. */
997
+ DeviceFeature["CoverPositionable"] = "cover-positionable";
998
+ /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
999
+ DeviceFeature["CoverTilt"] = "cover-tilt";
1000
+ /** Valve exposes intermediate position control (0..100). Gates the
1001
+ * position slider / drag surface UI. */
1002
+ DeviceFeature["ValvePositionable"] = "valve-positionable";
1003
+ /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
1004
+ DeviceFeature["FanSpeed"] = "fan-speed";
1005
+ /** Fan exposes a preset mode selector. */
1006
+ DeviceFeature["FanPreset"] = "fan-preset";
1007
+ /** Fan exposes blade direction (forward/reverse) — typical of
1008
+ * ceiling fans. */
1009
+ DeviceFeature["FanDirection"] = "fan-direction";
1010
+ /** Fan exposes an oscillation toggle. */
1011
+ DeviceFeature["FanOscillating"] = "fan-oscillating";
1012
+ /** Lock requires a PIN code on lock/unlock. Gates the code-entry
1013
+ * field on the UI lock-controls panel. */
1014
+ DeviceFeature["LockPinRequired"] = "lock-pin-required";
1015
+ /** Lock supports a latch-release ("open door") action distinct from
1016
+ * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
1017
+ * `supported_features`. Gates the Open Door button in the UI. */
1018
+ DeviceFeature["LockOpen"] = "lock-open";
1019
+ /** Media player exposes a seek-to-position surface. */
1020
+ DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
1021
+ /** Media player exposes a volume-level setter. */
1022
+ DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
1023
+ /** Media player exposes a mute toggle distinct from volume=0. */
1024
+ DeviceFeature["MediaPlayerMute"] = "media-player-mute";
1025
+ /** Media player exposes a shuffle toggle. */
1026
+ DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
1027
+ /** Media player exposes a repeat mode (off / all / one). */
1028
+ DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
1029
+ /** Media player exposes a source / input selector. */
1030
+ DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
1031
+ /** Media player exposes a play-arbitrary-media surface (URL / id). */
1032
+ DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
1033
+ /** Media player exposes next-track. */
1034
+ DeviceFeature["MediaPlayerNext"] = "media-player-next";
1035
+ /** Media player exposes previous-track. */
1036
+ DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
1037
+ /** Media player exposes stop distinct from pause. */
1038
+ DeviceFeature["MediaPlayerStop"] = "media-player-stop";
1039
+ /** Alarm panel requires a PIN code on arm/disarm. */
1040
+ DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
1041
+ /** Presence device carries GPS coordinates (lat/lng/accuracy) in
1042
+ * addition to a textual location. */
1043
+ DeviceFeature["PresenceGps"] = "presence-gps";
1044
+ /** Notifier accepts an inline / URL image attachment. */
1045
+ DeviceFeature["NotifierImage"] = "notifier-image";
1046
+ /** Notifier accepts a priority hint (high/normal/low). */
1047
+ DeviceFeature["NotifierPriority"] = "notifier-priority";
1048
+ /** Notifier accepts a free-form `data` payload for platform-specific
1049
+ * fields. */
1050
+ DeviceFeature["NotifierData"] = "notifier-data";
1051
+ /** Notifier supports interactive action buttons / callbacks. */
1052
+ DeviceFeature["NotifierActions"] = "notifier-actions";
1053
+ /** Notifier supports per-call recipient targeting (multi-user). */
1054
+ DeviceFeature["NotifierRecipients"] = "notifier-recipients";
1055
+ /** Script runner accepts a variables map on each run invocation. */
1056
+ DeviceFeature["ScriptVariables"] = "script-variables";
1057
+ /** Automation `trigger` accepts a skipCondition flag — fires the
1058
+ * automation's actions while bypassing its condition block. */
1059
+ DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
1060
+ /** Robot vacuum exposes a live cleaning map (image child). Gates the
1061
+ * map tile in the vacuum UI. */
1062
+ DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
1063
+ /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
1064
+ * switches group. */
1065
+ DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
1066
+ /** Robot mower exposes a live mowing map (SVG image child). Gates the
1067
+ * mower map tile. */
1068
+ DeviceFeature["MowerHasMap"] = "mower-has-map";
1069
+ /** Robot mower exposes targeted mowing (all-area / zones / edges /
1070
+ * spots) — gates the Mow action + map selectors. */
1071
+ DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
1072
+ return DeviceFeature;
1073
+ }({});
1074
+ /**
1075
+ * Semantic role a device plays within its parent. Populated by driver
1076
+ * addons when creating accessory devices (Reolink siren/floodlight/
1077
+ * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
1078
+ * admin UI to pick icons, labels, and widgets — a `Switch` with
1079
+ * `role: Floodlight` renders as a bulb with a brightness slider,
1080
+ * whereas a `Switch` with `role: Siren` renders as a klaxon.
1081
+ *
1082
+ * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
1083
+ * sqlite as a nullable TEXT column — old rows keep working unchanged.
1084
+ */
1085
+ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
1086
+ DeviceRole["Siren"] = "siren";
1087
+ DeviceRole["Floodlight"] = "floodlight";
1088
+ DeviceRole["Spotlight"] = "spotlight";
1089
+ DeviceRole["PirSensor"] = "pir-sensor";
1090
+ DeviceRole["Chime"] = "chime";
1091
+ DeviceRole["Autotrack"] = "autotrack";
1092
+ DeviceRole["Nightvision"] = "nightvision";
1093
+ DeviceRole["PrivacyMask"] = "privacy-mask";
1094
+ DeviceRole["Doorbell"] = "doorbell";
1095
+ /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
1096
+ DeviceRole["CoverBlind"] = "cover-blind";
1097
+ /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
1098
+ DeviceRole["CoverShutter"] = "cover-shutter";
1099
+ /** Exterior awning. HA `device_class: awning`. */
1100
+ DeviceRole["CoverAwning"] = "cover-awning";
1101
+ /** Curtain / drape. HA `device_class: curtain`. */
1102
+ DeviceRole["CoverCurtain"] = "cover-curtain";
1103
+ /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
1104
+ DeviceRole["CoverGarage"] = "cover-garage";
1105
+ /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
1106
+ DeviceRole["CoverGate"] = "cover-gate";
1107
+ /** Door. HA `device_class: door`. A security actuator: exports gate it. */
1108
+ DeviceRole["CoverDoor"] = "cover-door";
1109
+ /** Window. HA `device_class: window`. */
1110
+ DeviceRole["CoverWindow"] = "cover-window";
1111
+ /** Virtual HA toggle (input_boolean.*) — distinguishable from a
1112
+ * real Switch device for UI rendering / export adapters. */
1113
+ DeviceRole["BinaryHelper"] = "binary-helper";
1114
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
1115
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
1116
+ DeviceRole["Outlet"] = "outlet";
1117
+ /** Generic motion / occupancy / moving event source. Distinct from
1118
+ * the camera accessory PirSensor role: that one is a camera child;
1119
+ * this is a standalone HA / 3rd-party motion sensor. */
1120
+ DeviceRole["MotionSensor"] = "motion-sensor";
1121
+ DeviceRole["ContactSensor"] = "contact-sensor";
1122
+ DeviceRole["LeakSensor"] = "leak-sensor";
1123
+ DeviceRole["SmokeSensor"] = "smoke-sensor";
1124
+ DeviceRole["COSensor"] = "co-sensor";
1125
+ DeviceRole["GasSensor"] = "gas-sensor";
1126
+ DeviceRole["TamperSensor"] = "tamper-sensor";
1127
+ DeviceRole["VibrationSensor"] = "vibration-sensor";
1128
+ DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
1129
+ DeviceRole["SoundSensor"] = "sound-sensor";
1130
+ /** Fallback for `binary_sensor` without a known `device_class`. */
1131
+ DeviceRole["BinarySensor"] = "binary-sensor";
1132
+ DeviceRole["TemperatureSensor"] = "temperature-sensor";
1133
+ DeviceRole["HumiditySensor"] = "humidity-sensor";
1134
+ DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
1135
+ DeviceRole["PressureSensor"] = "pressure-sensor";
1136
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
1137
+ DeviceRole["WindSensor"] = "wind-sensor";
1138
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
1139
+ DeviceRole["RainSensor"] = "rain-sensor";
1140
+ /** UV index (weather-station `uv-sensor` cap). */
1141
+ DeviceRole["UvSensor"] = "uv-sensor";
1142
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
1143
+ * Distinct from AmbientLightSensor (lux). */
1144
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
1145
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
1146
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
1147
+ DeviceRole["PowerSensor"] = "power-sensor";
1148
+ DeviceRole["EnergySensor"] = "energy-sensor";
1149
+ DeviceRole["VoltageSensor"] = "voltage-sensor";
1150
+ DeviceRole["CurrentSensor"] = "current-sensor";
1151
+ DeviceRole["AirQualitySensor"] = "air-quality-sensor";
1152
+ /** Battery level (numeric % via `sensor` OR low-bool via
1153
+ * `binary_sensor` — the cap distinguishes via the value type). */
1154
+ DeviceRole["BatterySensor"] = "battery-sensor";
1155
+ /** Fallback for `sensor` numeric without a known `device_class`. */
1156
+ DeviceRole["NumericSensor"] = "numeric-sensor";
1157
+ /** String / enum state (HA `sensor` with `state_class: enum` or
1158
+ * `attributes.options`). */
1159
+ DeviceRole["EnumSensor"] = "enum-sensor";
1160
+ /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
1161
+ * or `date`). The slice carries the raw ISO string verbatim (hosted on
1162
+ * the `enum-sensor` cap); the UI renders it locale-formatted. */
1163
+ DeviceRole["DateTimeSensor"] = "datetime-sensor";
1164
+ /** Last-resort fallback when nothing else matches. */
1165
+ DeviceRole["GenericSensor"] = "generic-sensor";
1166
+ DeviceRole["NumericControl"] = "numeric-control";
1167
+ DeviceRole["SelectControl"] = "select-control";
1168
+ DeviceRole["TextControl"] = "text-control";
1169
+ DeviceRole["DateTimeControl"] = "datetime-control";
1170
+ /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
1171
+ * rich features (image, priority, channel routing). */
1172
+ DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
1173
+ /** Chat / messaging service (HA `notify.telegram_*`,
1174
+ * `notify.discord_*`, etc.). */
1175
+ DeviceRole["MessagingNotifier"] = "messaging-notifier";
1176
+ /** Email-based delivery (HA `notify.smtp`, etc.). */
1177
+ DeviceRole["EmailNotifier"] = "email-notifier";
1178
+ /** Fallback when the notifier service name doesn't match a known
1179
+ * pattern. */
1180
+ DeviceRole["GenericNotifier"] = "generic-notifier";
1181
+ /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
1182
+ * cap. A `DeviceType.Generic` device. */
1183
+ DeviceRole["IrTransmitter"] = "ir-transmitter";
1184
+ return DeviceRole;
1185
+ }({});
1186
+ //#endregion
811
1187
  //#region ../../node_modules/zod/v4/core/core.js
812
1188
  var _a$1;
813
1189
  function $constructor(name, initializer, params) {
@@ -5676,7 +6052,7 @@ var ZodIssueCode = {
5676
6052
  var ZodFirstPartyTypeKind;
5677
6053
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5678
6054
  //#endregion
5679
- //#region ../types/dist/sleep-CeyhrcOk.mjs
6055
+ //#region ../types/dist/sleep-CSYuxssh.mjs
5680
6056
  /**
5681
6057
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5682
6058
  * window to float samples (D455).
@@ -6192,6 +6568,102 @@ function emitReadiness(bus, params) {
6192
6568
  ts
6193
6569
  }));
6194
6570
  }
6571
+ var NOT_RUN = {
6572
+ declared: false,
6573
+ snapshot: null,
6574
+ applied: [],
6575
+ failed: [],
6576
+ blocked: [],
6577
+ skipped: [],
6578
+ blockOwner: []
6579
+ };
6580
+ /**
6581
+ * Offer `migrations` to the engine and run what it says to run, in its order. Never
6582
+ * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6583
+ * because the hub (and this addon's boot) must not hang on a migration.
6584
+ */
6585
+ async function runAddonMigrations(host, migrations, logger) {
6586
+ const first = migrations[0];
6587
+ if (first === void 0) return NOT_RUN;
6588
+ const owner = first.meta.owner;
6589
+ const log = logger.withTags({ migrationOwner: owner });
6590
+ const byId = new Map(migrations.map((m) => [m.meta.id, m]));
6591
+ let declaration;
6592
+ try {
6593
+ declaration = await host.declareOwner({
6594
+ owner,
6595
+ metas: migrations.map((m) => m.meta)
6596
+ });
6597
+ } catch (err) {
6598
+ log.error("the migration engine refused or could not take this addon catalogue — none ran", { meta: { error: errMsg(err) } });
6599
+ return {
6600
+ ...NOT_RUN,
6601
+ blockOwner: migrations.filter((m) => m.meta.onFailure === "block-owner").map((m) => m.meta.id)
6602
+ };
6603
+ }
6604
+ const applied = [];
6605
+ const failed = [];
6606
+ const blocked = [];
6607
+ const skipped = [];
6608
+ const settle = (id, status) => {
6609
+ if (status === "applied") applied.push(id);
6610
+ else if (status === "failed") failed.push(id);
6611
+ else if (status === "blocked") blocked.push(id);
6612
+ else skipped.push(id);
6613
+ };
6614
+ for (const step of declaration.steps) {
6615
+ const bound = byId.get(step.id);
6616
+ if (bound === void 0 || step.action === "skip") {
6617
+ skipped.push(step.id);
6618
+ continue;
6619
+ }
6620
+ const tags = { migrationId: step.id };
6621
+ try {
6622
+ const gate = await host.startMigration({ id: step.id });
6623
+ if (!gate.proceed) {
6624
+ log.warn("migration not run", {
6625
+ tags,
6626
+ meta: {
6627
+ state: gate.state,
6628
+ reason: gate.reason
6629
+ }
6630
+ });
6631
+ settle(step.id, gate.state === "pending" ? "blocked" : gate.state);
6632
+ continue;
6633
+ }
6634
+ const result = await bound.execute();
6635
+ const outcome = await host.finishMigration({
6636
+ id: step.id,
6637
+ result
6638
+ });
6639
+ if (outcome.status === "applied") log.info("migration applied", {
6640
+ tags,
6641
+ meta: { rowsChanged: outcome.rowsChanged }
6642
+ });
6643
+ else log.error("migration failed", {
6644
+ tags,
6645
+ meta: { error: outcome.error }
6646
+ });
6647
+ settle(step.id, outcome.status);
6648
+ } catch (err) {
6649
+ log.error("migration could not be completed with the engine", {
6650
+ tags,
6651
+ meta: { error: errMsg(err) }
6652
+ });
6653
+ failed.push(step.id);
6654
+ }
6655
+ }
6656
+ const blockOwner = [...failed, ...blocked].filter((id) => byId.get(id)?.meta.onFailure === "block-owner");
6657
+ return {
6658
+ declared: true,
6659
+ snapshot: declaration.snapshot,
6660
+ applied,
6661
+ failed,
6662
+ blocked,
6663
+ skipped,
6664
+ blockOwner
6665
+ };
6666
+ }
6195
6667
  /**
6196
6668
  * Build a {@link DurableState} over a single store key. Transport-agnostic:
6197
6669
  * `read`/`write` are the addon-store or device-store accessors. The whole
@@ -7247,6 +7719,28 @@ var BaseAddon = class {
7247
7719
  return normalized;
7248
7720
  }
7249
7721
  /**
7722
+ * Run this addon's migration catalogue (D987 M3) through the hub's `migrations`
7723
+ * engine. Call it at the point of the boot where the data must already have the new
7724
+ * shape — `onInitialize`, or `onRestoreDevices` for a migration that needs the
7725
+ * restored devices — with the catalogue bound to the addon's ports
7726
+ * (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
7727
+ *
7728
+ * An engine that cannot be reached or refuses the catalogue is logged and runs
7729
+ * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7730
+ * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7731
+ * serve data whose shape it cannot trust; `tolerate` migrations never do.
7732
+ */
7733
+ async runMigrations(migrations) {
7734
+ const api = this.ctx.api.migrations;
7735
+ const report = await runAddonMigrations({
7736
+ declareOwner: (input) => api.declareOwner.mutate(input),
7737
+ startMigration: (input) => api.startMigration.mutate(input),
7738
+ finishMigration: (input) => api.finishMigration.mutate(input)
7739
+ }, migrations, this.ctx.logger);
7740
+ if (report.blockOwner.length > 0) throw new Error(`migration(s) ${report.blockOwner.join(", ")} did not apply and block this addon — fix or retry them (migrations.retry), then restart it`);
7741
+ return report;
7742
+ }
7743
+ /**
7250
7744
  * Called by the isolated-process runner AFTER `broker.start()`.
7251
7745
  * In-process addons never need this because the hub broker is already
7252
7746
  * running when `initialize()` fires. For forked children the broker
@@ -7379,6 +7873,26 @@ var BaseAddon = class {
7379
7873
  * alternative — skip it — turns a rename into a surface that quietly
7380
7874
  * empties, which looks exactly like an addon with nothing to configure.
7381
7875
  */
7876
+ /**
7877
+ * The form schema of ONE integration instance, or `null` to use {@link globalSettingsSchema}.
7878
+ *
7879
+ * Override it only when an `instanceMode: multiple` addon's instances differ: an NVR integration
7880
+ * needs its connection fields, the default integration the addon's own sections. It is the
7881
+ * SHAPE of the form only — the values live in `integrations.getSettings`, and the write path is
7882
+ * unchanged (`integrations.setSettings` for an account integration).
7883
+ */
7884
+ async integrationSettingsSchema(_instance) {
7885
+ return null;
7886
+ }
7887
+ /**
7888
+ * What the create wizard and the integration edit page render for one instance: the addon's
7889
+ * per-instance schema when it declares one, else its global schema (what they always showed).
7890
+ */
7891
+ async getIntegrationFormSchema(instance, nodeId) {
7892
+ const scoped = await this.integrationSettingsSchema(instance);
7893
+ if (scoped === null) return this.getGlobalSettings(void 0, void 0, nodeId);
7894
+ return hydrateSchema(scoped, {}, { inlineSecretsAsViews: true });
7895
+ }
7382
7896
  async getIntegrationSettings(nodeId) {
7383
7897
  const declared = this.integrationSettingSections();
7384
7898
  if (declared.length === 0) return null;
@@ -8482,407 +8996,6 @@ function defineRecordKinds(specs) {
8482
8996
  }
8483
8997
  return out;
8484
8998
  }
8485
- var READING_QUANTITIES = [
8486
- "temperature",
8487
- "pressure",
8488
- "speed",
8489
- "precipitation",
8490
- "precipitation-rate",
8491
- "length",
8492
- "area",
8493
- "illuminance",
8494
- "irradiance",
8495
- "power",
8496
- "energy",
8497
- "voltage",
8498
- "current",
8499
- "apparent-power",
8500
- "humidity",
8501
- "percentage",
8502
- "concentration-volume",
8503
- "concentration-mass",
8504
- "concentration-ppb",
8505
- "volume",
8506
- "duration",
8507
- "duration-ms",
8508
- "information",
8509
- "sound-level",
8510
- "signal-level",
8511
- "colour-temperature",
8512
- "angle",
8513
- "bitrate",
8514
- "rate",
8515
- "mass",
8516
- "index",
8517
- "ratio",
8518
- "count",
8519
- "none",
8520
- "device"
8521
- ];
8522
- var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8523
- new Set(READING_QUANTITIES);
8524
- /**
8525
- * Stored unit symbol for a quantity. `none` means the number is dimensionless
8526
- * (or an index / ratio / count). `device` means the cap has no single unit.
8527
- */
8528
- var QUANTITY_BASE = {
8529
- temperature: "°C",
8530
- pressure: "hPa",
8531
- speed: "m/s",
8532
- precipitation: "mm",
8533
- "precipitation-rate": "mm/h",
8534
- length: "m",
8535
- area: "m²",
8536
- illuminance: "lx",
8537
- irradiance: "W/m²",
8538
- power: "W",
8539
- energy: "kWh",
8540
- voltage: "V",
8541
- current: "A",
8542
- "apparent-power": "VA",
8543
- humidity: "%",
8544
- percentage: "%",
8545
- "concentration-volume": "ppm",
8546
- "concentration-mass": "µg/m³",
8547
- "concentration-ppb": "ppb",
8548
- volume: "L",
8549
- duration: "s",
8550
- "duration-ms": "ms",
8551
- information: "B",
8552
- "sound-level": "dBFS",
8553
- "signal-level": "dBm",
8554
- "colour-temperature": "mired",
8555
- angle: "°",
8556
- bitrate: "kbps",
8557
- rate: "fps",
8558
- mass: "kg",
8559
- index: "none",
8560
- ratio: "none",
8561
- count: "none",
8562
- none: "none",
8563
- device: "device"
8564
- };
8565
- /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8566
- function baseUnitOf(quantity) {
8567
- const symbol = QUANTITY_BASE[quantity];
8568
- if (symbol === "none" || symbol === "device") return void 0;
8569
- return symbol;
8570
- }
8571
- function readingMeta(quantity, extras) {
8572
- return {
8573
- quantity,
8574
- ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8575
- ...extras?.label !== void 0 ? { label: extras.label } : {}
8576
- };
8577
- }
8578
- /** Shared Zod schemas used across streaming capabilities. */
8579
- var CamProfileSchema = _enum([
8580
- "high",
8581
- "mid",
8582
- "low"
8583
- ]);
8584
- /**
8585
- * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
8586
- *
8587
- * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
8588
- * that consumer owns". It lives beside `CamProfile` because a rung is the only
8589
- * operator-facing way to name a stream: a cam-stream id is broker-internal and
8590
- * moves when a slot is reassigned, so a stored id silently stops meaning what
8591
- * the operator picked. Today's one user is the audio analyser, whose rule is
8592
- * "the lowest AVAILABLE native rung" (D537).
8593
- */
8594
- var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
8595
- var CamStreamKindSchema = _enum([
8596
- "pull-rtsp",
8597
- "pull-rtmp",
8598
- "pull-http",
8599
- "pull-flv",
8600
- "pull-rfc4571",
8601
- "push-annexb",
8602
- "derived"
8603
- ]);
8604
- var CamStreamResolutionSchema = object({
8605
- width: number().int().positive().meta(readingMeta("none")),
8606
- height: number().int().positive().meta(readingMeta("none"))
8607
- });
8608
- var CameraStreamSchema = object({
8609
- /** Stable, provider-assigned id unique within the (deviceId) scope. */
8610
- camStreamId: string().min(1),
8611
- deviceId: number().int().nonnegative(),
8612
- kind: CamStreamKindSchema,
8613
- /** Required for pull-* kinds. Ignored for push-annexb. */
8614
- url: string().optional(),
8615
- codec: string().optional(),
8616
- resolution: CamStreamResolutionSchema.optional(),
8617
- fps: number().positive().optional(),
8618
- /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
8619
- label: string().optional(),
8620
- /**
8621
- * Device-level features the publisher advertised (e.g. `battery-operated`).
8622
- * The broker, snapshot orchestrator, and prebuffer manager all consult
8623
- * this list to derive policy — relaxed stall watchdog for battery
8624
- * cams, prebuffer off by default, longer snapshot rate-limit, etc.
8625
- *
8626
- * Single source of truth replacing per-stream flags like the
8627
- * historical `allowStall`: if the publisher knows the camera is
8628
- * battery-powered, every downstream service derives the right policy
8629
- * from this list.
8630
- */
8631
- deviceFeatures: array(string()).optional(),
8632
- /**
8633
- * Whether this stream participates in the broker's automatic profile
8634
- * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
8635
- * use `false` when they want a stream to be SELECTABLE in the UI but not
8636
- * picked by default — e.g. Reolink publishes its native Baichuan streams
8637
- * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
8638
- * mirrors as `autoEligible: false` (still pickable per slot, just not
8639
- * the auto choice). Manual `assignProfile` calls remain valid for
8640
- * non-eligible streams.
8641
- */
8642
- autoEligible: boolean().optional(),
8643
- /**
8644
- * The profile slot the PUBLISHER says this stream is: `low` for a stream
8645
- * whose only sensible role is the low-bandwidth one, `high` for a main
8646
- * stream. Consulted by the broker's automatic ranking
8647
- * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
8648
- * an operator's manual assignment still wins, and an absent hint is the
8649
- * pixel-ranked default every camera had before this field existed.
8650
- *
8651
- * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
8652
- * storage class is written for on its own (`recordings` is high|mid,
8653
- * `recordingsLow` is low) and the one profile scrub does not read. The robot
8654
- * camera's single 720p relay is a `low` in every respect except the slot the
8655
- * heuristic put it in.
8656
- */
8657
- profileHint: CamProfileSchema.optional(),
8658
- /**
8659
- * Transport-specific opaque metadata. The broker passes it through to
8660
- * the source reader without inspecting it. Currently used by
8661
- * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
8662
- * route RTP packets to the right depacketizer without an in-band
8663
- * DESCRIBE phase). Other kinds typically leave it undefined.
8664
- */
8665
- metadata: record(string(), unknown()).optional()
8666
- });
8667
- var ProfileSlotStatusSchema = _enum([
8668
- "unassigned",
8669
- "idle",
8670
- "connecting",
8671
- "streaming",
8672
- "error"
8673
- ]);
8674
- var ProfileSlotSchema = object({
8675
- deviceId: number().int().nonnegative(),
8676
- profile: CamProfileSchema,
8677
- /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
8678
- brokerId: string(),
8679
- /** `null` when the profile is unassigned. */
8680
- sourceCamStreamId: string().nullable(),
8681
- status: ProfileSlotStatusSchema,
8682
- resolution: CamStreamResolutionSchema.optional(),
8683
- codec: string().optional(),
8684
- preBufferSec: number().nonnegative().optional(),
8685
- errorMessage: string().optional()
8686
- });
8687
- /**
8688
- * Zod schema for StreamSourceEntry — the canonical stream descriptor
8689
- * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
8690
- */
8691
- var StreamSourceEntrySchema$1 = object({
8692
- id: string(),
8693
- label: string(),
8694
- protocol: _enum([
8695
- "rtsp",
8696
- "rtmp",
8697
- "annexb",
8698
- "http-mjpeg",
8699
- "webrtc",
8700
- "custom"
8701
- ]),
8702
- url: string().optional(),
8703
- resolution: object({
8704
- width: number(),
8705
- height: number()
8706
- }).readonly().optional(),
8707
- fps: number().optional(),
8708
- bitrate: number().optional(),
8709
- codec: string().optional(),
8710
- profileHint: CamProfileSchema.optional()
8711
- });
8712
- object({
8713
- type: string(),
8714
- url: string(),
8715
- videoCodec: string().optional(),
8716
- audioCodec: string().optional(),
8717
- metadata: record(string(), unknown()).readonly().optional()
8718
- });
8719
- /**
8720
- * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
8721
- * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
8722
- * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
8723
- * (the runner's native-crop and pipeline-executor inputs) can carry handles
8724
- * over tRPC / Moleculer.
8725
- *
8726
- * The `satisfies` assertion below pins this schema to the interface: if a
8727
- * field is added to / removed from `FrameHandle` without a matching schema
8728
- * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
8729
- * `FrameHandle` and the build fails — no silent drift.
8730
- */
8731
- var FrameHandleSchema = object({
8732
- shmId: string(),
8733
- slot: number().int().nonnegative(),
8734
- seq: number().int().nonnegative(),
8735
- width: number().int().positive(),
8736
- height: number().int().positive(),
8737
- format: _enum([
8738
- "jpeg",
8739
- "rgb",
8740
- "bgr",
8741
- "yuv420",
8742
- "gray"
8743
- ]),
8744
- pts: number(),
8745
- byteLength: number().int().nonnegative(),
8746
- nodeId: string(),
8747
- slotCount: number().int().positive()
8748
- });
8749
- /**
8750
- * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
8751
- * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
8752
- * sample bytes plus the track parameters a consumer needs to interpret them.
8753
- *
8754
- * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
8755
- * Float32 PCM), so unlike video frames they ship their bytes INLINE over
8756
- * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
8757
- * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
8758
- * it.
8759
- */
8760
- var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
8761
- var DecodedAudioChunkSchema = object({
8762
- data: _instanceof(Uint8Array),
8763
- sampleRate: number().int().positive(),
8764
- channels: number().int().positive(),
8765
- timestamp: number(),
8766
- /**
8767
- * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
8768
- * byte, for any peer that never heard of this field. A coded window
8769
- * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
8770
- * subscription that DECLARED it accepts one, so absence can never mean
8771
- * "coded bytes a consumer will read as floats" (D455).
8772
- */
8773
- format: AudioChunkFormatSchema.optional()
8774
- });
8775
- /**
8776
- * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
8777
- * its bytes inline: a consumer asks the broker for the decoded audio-chunk
8778
- * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
8779
- * the consumer drains via `pullAudioChunks`.
8780
- */
8781
- var SubscribeAudioChunksInputSchema = object({
8782
- brokerId: string(),
8783
- /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
8784
- tag: string().optional(),
8785
- /**
8786
- * Byte formats this subscriber can READ, best first. The broker serves the
8787
- * chunk's own format when it is in this list and expands to `f32le`
8788
- * otherwise, so a subscriber is never handed bytes it cannot interpret.
8789
- *
8790
- * Absent (or without the source format) means `f32le` — the behaviour every
8791
- * subscriber had before D455, unchanged. This is the negotiation half of
8792
- * the source-bytes lever: it is what lets the broker and its consumers
8793
- * deploy one at a time across three nodes.
8794
- */
8795
- accept: array(AudioChunkFormatSchema).readonly().optional()
8796
- });
8797
- /** Result of `stream-broker.subscribeAudioChunks`. */
8798
- var SubscribeAudioChunksResultSchema = object({
8799
- /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
8800
- subscriptionId: string() });
8801
- var BrokerStatsSchema = object({
8802
- status: _enum([
8803
- "idle",
8804
- "connecting",
8805
- "streaming",
8806
- "error",
8807
- "stopped"
8808
- ]),
8809
- inputFps: number(),
8810
- encodedSubscribers: number(),
8811
- uptimeMs: number(),
8812
- bitrateKbps: number(),
8813
- idrIntervalMs: number(),
8814
- codec: string().optional(),
8815
- totalBytes: number(),
8816
- packetCount: number(),
8817
- rtspClients: number(),
8818
- pipeClients: number(),
8819
- preBufferSec: number(),
8820
- preBufferMs: number(),
8821
- preBufferPackets: number(),
8822
- /**
8823
- * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
8824
- * when the stream has no audio track or the broker is in cold start.
8825
- * `supported = false` means the codec was detected but the local
8826
- * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
8827
- * AAC pipeline wired). Surfaced in the UI device-overview so operators
8828
- * can pre-pick the audio-analysis model that matches the codec.
8829
- */
8830
- audio: object({
8831
- codec: string(),
8832
- sampleRate: number(),
8833
- channels: number(),
8834
- supported: boolean()
8835
- }).nullable().optional(),
8836
- /**
8837
- * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
8838
- * distributing none of the device's audio, on live or recording.
8839
- *
8840
- * Present so a silent camera can be told apart from a broken one on the
8841
- * stream panel itself, without cross-referencing the switch group: a
8842
- * broker holding an `audio` track descriptor while `audioMuted` is true is
8843
- * working exactly as asked. `audioMutedDropped` counts the audio units
8844
- * thrown away since the current dial — it is how you confirm from stats
8845
- * alone that the mute is on the packet path and not merely persisted.
8846
- */
8847
- audioMuted: boolean().optional(),
8848
- audioMutedDropped: number().optional()
8849
- });
8850
- /**
8851
- * Exporter-facing "profile restream" entry. Returned by
8852
- * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
8853
- * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
8854
- * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
8855
- * RTSP restream that aliases the profile's assigned source broker, so
8856
- * every consumer dialling a profile converges on the same single pull.
8857
- * `codec`/`resolution` describe the assigned SOURCE camStream (for the
8858
- * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
8859
- * (raw per-camStream, live-view only).
8860
- */
8861
- var ProfileRtspEntrySchema = object({
8862
- profile: CamProfileSchema,
8863
- /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
8864
- brokerId: string(),
8865
- url: string(),
8866
- mutedUrl: string(),
8867
- enabled: boolean(),
8868
- codec: string().optional(),
8869
- resolution: CamStreamResolutionSchema.optional()
8870
- });
8871
- /**
8872
- * The model weight formats a catalog entry can ship. Schema-free on purpose:
8873
- * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
8874
- * (the model downloader in `@camstack/system`) needs only the list and must
8875
- * reach it through `@camstack/types/addon` without building the cap schema
8876
- * graph (D28).
8877
- */
8878
- var MODEL_FORMATS = [
8879
- "onnx",
8880
- "coreml",
8881
- "openvino",
8882
- "tflite",
8883
- "pt",
8884
- "gguf"
8885
- ];
8886
8999
  object({
8887
9000
  field: string().min(1),
8888
9001
  on: literal("advance")
@@ -8975,6 +9088,407 @@ function optionalMethod(input, output, options) {
8975
9088
  function event(data) {
8976
9089
  return { data };
8977
9090
  }
9091
+ var READING_QUANTITIES = [
9092
+ "temperature",
9093
+ "pressure",
9094
+ "speed",
9095
+ "precipitation",
9096
+ "precipitation-rate",
9097
+ "length",
9098
+ "area",
9099
+ "illuminance",
9100
+ "irradiance",
9101
+ "power",
9102
+ "energy",
9103
+ "voltage",
9104
+ "current",
9105
+ "apparent-power",
9106
+ "humidity",
9107
+ "percentage",
9108
+ "concentration-volume",
9109
+ "concentration-mass",
9110
+ "concentration-ppb",
9111
+ "volume",
9112
+ "duration",
9113
+ "duration-ms",
9114
+ "information",
9115
+ "sound-level",
9116
+ "signal-level",
9117
+ "colour-temperature",
9118
+ "angle",
9119
+ "bitrate",
9120
+ "rate",
9121
+ "mass",
9122
+ "index",
9123
+ "ratio",
9124
+ "count",
9125
+ "none",
9126
+ "device"
9127
+ ];
9128
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
9129
+ new Set(READING_QUANTITIES);
9130
+ /**
9131
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
9132
+ * (or an index / ratio / count). `device` means the cap has no single unit.
9133
+ */
9134
+ var QUANTITY_BASE = {
9135
+ temperature: "°C",
9136
+ pressure: "hPa",
9137
+ speed: "m/s",
9138
+ precipitation: "mm",
9139
+ "precipitation-rate": "mm/h",
9140
+ length: "m",
9141
+ area: "m²",
9142
+ illuminance: "lx",
9143
+ irradiance: "W/m²",
9144
+ power: "W",
9145
+ energy: "kWh",
9146
+ voltage: "V",
9147
+ current: "A",
9148
+ "apparent-power": "VA",
9149
+ humidity: "%",
9150
+ percentage: "%",
9151
+ "concentration-volume": "ppm",
9152
+ "concentration-mass": "µg/m³",
9153
+ "concentration-ppb": "ppb",
9154
+ volume: "L",
9155
+ duration: "s",
9156
+ "duration-ms": "ms",
9157
+ information: "B",
9158
+ "sound-level": "dBFS",
9159
+ "signal-level": "dBm",
9160
+ "colour-temperature": "mired",
9161
+ angle: "°",
9162
+ bitrate: "kbps",
9163
+ rate: "fps",
9164
+ mass: "kg",
9165
+ index: "none",
9166
+ ratio: "none",
9167
+ count: "none",
9168
+ none: "none",
9169
+ device: "device"
9170
+ };
9171
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
9172
+ function baseUnitOf(quantity) {
9173
+ const symbol = QUANTITY_BASE[quantity];
9174
+ if (symbol === "none" || symbol === "device") return void 0;
9175
+ return symbol;
9176
+ }
9177
+ function readingMeta(quantity, extras) {
9178
+ return {
9179
+ quantity,
9180
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
9181
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
9182
+ };
9183
+ }
9184
+ /** Shared Zod schemas used across streaming capabilities. */
9185
+ var CamProfileSchema = _enum([
9186
+ "high",
9187
+ "mid",
9188
+ "low"
9189
+ ]);
9190
+ /**
9191
+ * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
9192
+ *
9193
+ * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
9194
+ * that consumer owns". It lives beside `CamProfile` because a rung is the only
9195
+ * operator-facing way to name a stream: a cam-stream id is broker-internal and
9196
+ * moves when a slot is reassigned, so a stored id silently stops meaning what
9197
+ * the operator picked. Today's one user is the audio analyser, whose rule is
9198
+ * "the lowest AVAILABLE native rung" (D537).
9199
+ */
9200
+ var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
9201
+ var CamStreamKindSchema = _enum([
9202
+ "pull-rtsp",
9203
+ "pull-rtmp",
9204
+ "pull-http",
9205
+ "pull-flv",
9206
+ "pull-rfc4571",
9207
+ "push-annexb",
9208
+ "derived"
9209
+ ]);
9210
+ var CamStreamResolutionSchema = object({
9211
+ width: number().int().positive().meta(readingMeta("none")),
9212
+ height: number().int().positive().meta(readingMeta("none"))
9213
+ });
9214
+ var CameraStreamSchema = object({
9215
+ /** Stable, provider-assigned id unique within the (deviceId) scope. */
9216
+ camStreamId: string().min(1),
9217
+ deviceId: number().int().nonnegative(),
9218
+ kind: CamStreamKindSchema,
9219
+ /** Required for pull-* kinds. Ignored for push-annexb. */
9220
+ url: string().optional(),
9221
+ codec: string().optional(),
9222
+ resolution: CamStreamResolutionSchema.optional(),
9223
+ fps: number().positive().optional(),
9224
+ /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
9225
+ label: string().optional(),
9226
+ /**
9227
+ * Device-level features the publisher advertised (e.g. `battery-operated`).
9228
+ * The broker, snapshot orchestrator, and prebuffer manager all consult
9229
+ * this list to derive policy — relaxed stall watchdog for battery
9230
+ * cams, prebuffer off by default, longer snapshot rate-limit, etc.
9231
+ *
9232
+ * Single source of truth replacing per-stream flags like the
9233
+ * historical `allowStall`: if the publisher knows the camera is
9234
+ * battery-powered, every downstream service derives the right policy
9235
+ * from this list.
9236
+ */
9237
+ deviceFeatures: array(string()).optional(),
9238
+ /**
9239
+ * Whether this stream participates in the broker's automatic profile
9240
+ * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
9241
+ * use `false` when they want a stream to be SELECTABLE in the UI but not
9242
+ * picked by default — e.g. Reolink publishes its native Baichuan streams
9243
+ * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
9244
+ * mirrors as `autoEligible: false` (still pickable per slot, just not
9245
+ * the auto choice). Manual `assignProfile` calls remain valid for
9246
+ * non-eligible streams.
9247
+ */
9248
+ autoEligible: boolean().optional(),
9249
+ /**
9250
+ * The profile slot the PUBLISHER says this stream is: `low` for a stream
9251
+ * whose only sensible role is the low-bandwidth one, `high` for a main
9252
+ * stream. Consulted by the broker's automatic ranking
9253
+ * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
9254
+ * an operator's manual assignment still wins, and an absent hint is the
9255
+ * pixel-ranked default every camera had before this field existed.
9256
+ *
9257
+ * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
9258
+ * storage class is written for on its own (`recordings` is high|mid,
9259
+ * `recordingsLow` is low) and the one profile scrub does not read. The robot
9260
+ * camera's single 720p relay is a `low` in every respect except the slot the
9261
+ * heuristic put it in.
9262
+ */
9263
+ profileHint: CamProfileSchema.optional(),
9264
+ /**
9265
+ * Transport-specific opaque metadata. The broker passes it through to
9266
+ * the source reader without inspecting it. Currently used by
9267
+ * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
9268
+ * route RTP packets to the right depacketizer without an in-band
9269
+ * DESCRIBE phase). Other kinds typically leave it undefined.
9270
+ */
9271
+ metadata: record(string(), unknown()).optional()
9272
+ });
9273
+ var ProfileSlotStatusSchema = _enum([
9274
+ "unassigned",
9275
+ "idle",
9276
+ "connecting",
9277
+ "streaming",
9278
+ "error"
9279
+ ]);
9280
+ var ProfileSlotSchema = object({
9281
+ deviceId: number().int().nonnegative(),
9282
+ profile: CamProfileSchema,
9283
+ /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
9284
+ brokerId: string(),
9285
+ /** `null` when the profile is unassigned. */
9286
+ sourceCamStreamId: string().nullable(),
9287
+ status: ProfileSlotStatusSchema,
9288
+ resolution: CamStreamResolutionSchema.optional(),
9289
+ codec: string().optional(),
9290
+ preBufferSec: number().nonnegative().optional(),
9291
+ errorMessage: string().optional()
9292
+ });
9293
+ /**
9294
+ * Zod schema for StreamSourceEntry — the canonical stream descriptor
9295
+ * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
9296
+ */
9297
+ var StreamSourceEntrySchema$1 = object({
9298
+ id: string(),
9299
+ label: string(),
9300
+ protocol: _enum([
9301
+ "rtsp",
9302
+ "rtmp",
9303
+ "annexb",
9304
+ "http-mjpeg",
9305
+ "webrtc",
9306
+ "custom"
9307
+ ]),
9308
+ url: string().optional(),
9309
+ resolution: object({
9310
+ width: number(),
9311
+ height: number()
9312
+ }).readonly().optional(),
9313
+ fps: number().optional(),
9314
+ bitrate: number().optional(),
9315
+ codec: string().optional(),
9316
+ profileHint: CamProfileSchema.optional()
9317
+ });
9318
+ object({
9319
+ type: string(),
9320
+ url: string(),
9321
+ videoCodec: string().optional(),
9322
+ audioCodec: string().optional(),
9323
+ metadata: record(string(), unknown()).readonly().optional()
9324
+ });
9325
+ /**
9326
+ * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
9327
+ * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
9328
+ * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
9329
+ * (the runner's native-crop and pipeline-executor inputs) can carry handles
9330
+ * over tRPC / Moleculer.
9331
+ *
9332
+ * The `satisfies` assertion below pins this schema to the interface: if a
9333
+ * field is added to / removed from `FrameHandle` without a matching schema
9334
+ * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
9335
+ * `FrameHandle` and the build fails — no silent drift.
9336
+ */
9337
+ var FrameHandleSchema = object({
9338
+ shmId: string(),
9339
+ slot: number().int().nonnegative(),
9340
+ seq: number().int().nonnegative(),
9341
+ width: number().int().positive(),
9342
+ height: number().int().positive(),
9343
+ format: _enum([
9344
+ "jpeg",
9345
+ "rgb",
9346
+ "bgr",
9347
+ "yuv420",
9348
+ "gray"
9349
+ ]),
9350
+ pts: number(),
9351
+ byteLength: number().int().nonnegative(),
9352
+ nodeId: string(),
9353
+ slotCount: number().int().positive()
9354
+ });
9355
+ /**
9356
+ * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
9357
+ * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
9358
+ * sample bytes plus the track parameters a consumer needs to interpret them.
9359
+ *
9360
+ * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
9361
+ * Float32 PCM), so unlike video frames they ship their bytes INLINE over
9362
+ * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
9363
+ * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
9364
+ * it.
9365
+ */
9366
+ var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
9367
+ var DecodedAudioChunkSchema = object({
9368
+ data: _instanceof(Uint8Array),
9369
+ sampleRate: number().int().positive(),
9370
+ channels: number().int().positive(),
9371
+ timestamp: number(),
9372
+ /**
9373
+ * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
9374
+ * byte, for any peer that never heard of this field. A coded window
9375
+ * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
9376
+ * subscription that DECLARED it accepts one, so absence can never mean
9377
+ * "coded bytes a consumer will read as floats" (D455).
9378
+ */
9379
+ format: AudioChunkFormatSchema.optional()
9380
+ });
9381
+ /**
9382
+ * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
9383
+ * its bytes inline: a consumer asks the broker for the decoded audio-chunk
9384
+ * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
9385
+ * the consumer drains via `pullAudioChunks`.
9386
+ */
9387
+ var SubscribeAudioChunksInputSchema = object({
9388
+ brokerId: string(),
9389
+ /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
9390
+ tag: string().optional(),
9391
+ /**
9392
+ * Byte formats this subscriber can READ, best first. The broker serves the
9393
+ * chunk's own format when it is in this list and expands to `f32le`
9394
+ * otherwise, so a subscriber is never handed bytes it cannot interpret.
9395
+ *
9396
+ * Absent (or without the source format) means `f32le` — the behaviour every
9397
+ * subscriber had before D455, unchanged. This is the negotiation half of
9398
+ * the source-bytes lever: it is what lets the broker and its consumers
9399
+ * deploy one at a time across three nodes.
9400
+ */
9401
+ accept: array(AudioChunkFormatSchema).readonly().optional()
9402
+ });
9403
+ /** Result of `stream-broker.subscribeAudioChunks`. */
9404
+ var SubscribeAudioChunksResultSchema = object({
9405
+ /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
9406
+ subscriptionId: string() });
9407
+ var BrokerStatsSchema = object({
9408
+ status: _enum([
9409
+ "idle",
9410
+ "connecting",
9411
+ "streaming",
9412
+ "error",
9413
+ "stopped"
9414
+ ]),
9415
+ inputFps: number(),
9416
+ encodedSubscribers: number(),
9417
+ uptimeMs: number(),
9418
+ bitrateKbps: number(),
9419
+ idrIntervalMs: number(),
9420
+ codec: string().optional(),
9421
+ totalBytes: number(),
9422
+ packetCount: number(),
9423
+ rtspClients: number(),
9424
+ pipeClients: number(),
9425
+ preBufferSec: number(),
9426
+ preBufferMs: number(),
9427
+ preBufferPackets: number(),
9428
+ /**
9429
+ * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
9430
+ * when the stream has no audio track or the broker is in cold start.
9431
+ * `supported = false` means the codec was detected but the local
9432
+ * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
9433
+ * AAC pipeline wired). Surfaced in the UI device-overview so operators
9434
+ * can pre-pick the audio-analysis model that matches the codec.
9435
+ */
9436
+ audio: object({
9437
+ codec: string(),
9438
+ sampleRate: number(),
9439
+ channels: number(),
9440
+ supported: boolean()
9441
+ }).nullable().optional(),
9442
+ /**
9443
+ * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
9444
+ * distributing none of the device's audio, on live or recording.
9445
+ *
9446
+ * Present so a silent camera can be told apart from a broken one on the
9447
+ * stream panel itself, without cross-referencing the switch group: a
9448
+ * broker holding an `audio` track descriptor while `audioMuted` is true is
9449
+ * working exactly as asked. `audioMutedDropped` counts the audio units
9450
+ * thrown away since the current dial — it is how you confirm from stats
9451
+ * alone that the mute is on the packet path and not merely persisted.
9452
+ */
9453
+ audioMuted: boolean().optional(),
9454
+ audioMutedDropped: number().optional()
9455
+ });
9456
+ /**
9457
+ * Exporter-facing "profile restream" entry. Returned by
9458
+ * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
9459
+ * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
9460
+ * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
9461
+ * RTSP restream that aliases the profile's assigned source broker, so
9462
+ * every consumer dialling a profile converges on the same single pull.
9463
+ * `codec`/`resolution` describe the assigned SOURCE camStream (for the
9464
+ * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
9465
+ * (raw per-camStream, live-view only).
9466
+ */
9467
+ var ProfileRtspEntrySchema = object({
9468
+ profile: CamProfileSchema,
9469
+ /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
9470
+ brokerId: string(),
9471
+ url: string(),
9472
+ mutedUrl: string(),
9473
+ enabled: boolean(),
9474
+ codec: string().optional(),
9475
+ resolution: CamStreamResolutionSchema.optional()
9476
+ });
9477
+ /**
9478
+ * The model weight formats a catalog entry can ship. Schema-free on purpose:
9479
+ * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
9480
+ * (the model downloader in `@camstack/server/system`) needs only the list and must
9481
+ * reach it through `@camstack/types/addon` without building the cap schema
9482
+ * graph (D28).
9483
+ */
9484
+ var MODEL_FORMATS = [
9485
+ "onnx",
9486
+ "coreml",
9487
+ "openvino",
9488
+ "tflite",
9489
+ "pt",
9490
+ "gguf"
9491
+ ];
8978
9492
  /**
8979
9493
  * Per-call node pinning for `ctx.api` capability calls.
8980
9494
  *
@@ -9298,6 +9812,7 @@ var CAP_NAMES = Object.freeze([
9298
9812
  "mesh-network",
9299
9813
  "meter",
9300
9814
  "metrics-provider",
9815
+ "migrations",
9301
9816
  "model-convert",
9302
9817
  "model-distributor",
9303
9818
  "motion",
@@ -9330,6 +9845,7 @@ var CAP_NAMES = Object.freeze([
9330
9845
  "plate-gallery",
9331
9846
  "platform-probe",
9332
9847
  "power-meter",
9848
+ "power-source",
9333
9849
  "presence",
9334
9850
  "pressure-sensor",
9335
9851
  "privacy-mask",
@@ -9347,6 +9863,7 @@ var CAP_NAMES = Object.freeze([
9347
9863
  "remote-layout-components",
9348
9864
  "running-state",
9349
9865
  "scene-monitor",
9866
+ "schedulers",
9350
9867
  "script-runner",
9351
9868
  "server-management",
9352
9869
  "settings-store",
@@ -10292,7 +10809,8 @@ Object.freeze({
10292
10809
  "unlocked",
10293
10810
  "locking",
10294
10811
  "unlocking",
10295
- "jammed"
10812
+ "jammed",
10813
+ "door-open"
10296
10814
  ]
10297
10815
  }],
10298
10816
  "media-player": [
@@ -10679,6 +11197,13 @@ Object.freeze({
10679
11197
  "valueType": "number"
10680
11198
  }
10681
11199
  ],
11200
+ "power-source": [{
11201
+ "path": "switchOn",
11202
+ "valueType": "boolean"
11203
+ }, {
11204
+ "path": "offSeconds",
11205
+ "valueType": "number"
11206
+ }],
10682
11207
  "presence": [
10683
11208
  {
10684
11209
  "path": "state",
@@ -11192,7 +11717,7 @@ Object.freeze({
11192
11717
  * can answer "may a rule actuate this?" without importing the schema barrel
11193
11718
  * (~144MB RSS per runner, D28).
11194
11719
  *
11195
- * Coverage: 97 device-scoped capabilities.
11720
+ * Coverage: 98 device-scoped capabilities.
11196
11721
  */
11197
11722
  var DEVICE_SCOPED_CAPS = new Set([
11198
11723
  "accessories",
@@ -11261,6 +11786,7 @@ var DEVICE_SCOPED_CAPS = new Set([
11261
11786
  "pet-fountain",
11262
11787
  "pipeline-analytics",
11263
11788
  "power-meter",
11789
+ "power-source",
11264
11790
  "presence",
11265
11791
  "pressure-sensor",
11266
11792
  "privacy-mask",
@@ -11337,379 +11863,7 @@ function sleep(ms) {
11337
11863
  return new Promise((resolve) => setTimeout(resolve, Math.max(0, ms)));
11338
11864
  }
11339
11865
  //#endregion
11340
- //#region ../types/dist/err-msg-BD5buxvT.mjs
11341
- var DeviceType = /* @__PURE__ */ function(DeviceType) {
11342
- DeviceType["Camera"] = "camera";
11343
- DeviceType["Hub"] = "hub";
11344
- DeviceType["Light"] = "light";
11345
- DeviceType["Siren"] = "siren";
11346
- DeviceType["Switch"] = "switch";
11347
- DeviceType["Sensor"] = "sensor";
11348
- DeviceType["Thermostat"] = "thermostat";
11349
- /** Air-conditioner / heat-pump climate device (HVAC) — shares the
11350
- * `climate-control` cap surface with `Thermostat` but renders a
11351
- * dedicated AC-appropriate control UI (mode chips, fan speed,
11352
- * independent vertical/horizontal swing). Sources: native Gree, and
11353
- * reusable by other AC integrations. */
11354
- DeviceType["Climate"] = "climate";
11355
- DeviceType["Button"] = "button";
11356
- /** Generic stateless event emitter — carries a device's EXACT declared
11357
- * event vocabulary verbatim (no normalization). Installed with the
11358
- * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
11359
- * HA bus events (e.g. `zha_event`, generic). */
11360
- DeviceType["EventEmitter"] = "event-emitter";
11361
- /** Firmware/software update entity — current vs available version,
11362
- * updatable flag, update state, and an install action. Installed with
11363
- * the `update` cap. Sources: Homematic firmware-update channels (and
11364
- * reusable by other providers, e.g. HA `update.*` entities). */
11365
- DeviceType["Update"] = "update";
11366
- DeviceType["Generic"] = "generic";
11367
- /** Generic notification delivery target (HA `notify.<service>`, future
11368
- * Telegram / Discord / ntfy / SMTP, …). One device per delivery
11369
- * endpoint; the `notifier` cap defines the send surface. */
11370
- DeviceType["Notifier"] = "notifier";
11371
- /** Pre-recorded action sequence with optional parameters
11372
- * (HA `script.*`). Runnable via `script-runner` cap. */
11373
- DeviceType["Script"] = "script";
11374
- /** Automation rule (HA `automation.*`) — enable/disable + manual
11375
- * trigger surface exposed via `automation-control` cap. */
11376
- DeviceType["Automation"] = "automation";
11377
- /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
11378
- DeviceType["Lock"] = "lock";
11379
- /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
11380
- * `valve.*`). `cover` cap with sub-roles for variant. */
11381
- DeviceType["Cover"] = "cover";
11382
- /** Pipe / water / gas valve with open/close/stop and optional
11383
- * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
11384
- * modelled on the same open/closed lifecycle. */
11385
- DeviceType["Valve"] = "valve";
11386
- /** Humidifier / dehumidifier with on/off + target humidity + mode
11387
- * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
11388
- * modelled on the same target / mode lifecycle. */
11389
- DeviceType["Humidifier"] = "humidifier";
11390
- /** Water heater / boiler with target temperature + operation mode +
11391
- * away mode (HA `water_heater.*`). `water-heater` cap — a
11392
- * climate-family actuator. */
11393
- DeviceType["WaterHeater"] = "water-heater";
11394
- /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
11395
- DeviceType["Fan"] = "fan";
11396
- /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
11397
- * the camera surface — those use `Camera`. `media-player` cap. */
11398
- DeviceType["MediaPlayer"] = "media-player";
11399
- /** Security panel / alarm system (HA `alarm_control_panel.*`).
11400
- * `alarm-panel` cap. */
11401
- DeviceType["AlarmPanel"] = "alarm-panel";
11402
- /** Generic user-settable input (HA `number` / `input_number` / `select`
11403
- * / `input_select` / `text` / `input_text` / `input_datetime`).
11404
- * Sub-type via `DeviceRole`: NumericControl / SelectControl /
11405
- * TextControl / DateTimeControl. */
11406
- DeviceType["Control"] = "control";
11407
- /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
11408
- * `presence` cap. */
11409
- DeviceType["Presence"] = "presence";
11410
- /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
11411
- * `weather` cap. */
11412
- DeviceType["Weather"] = "weather";
11413
- /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
11414
- DeviceType["Vacuum"] = "vacuum";
11415
- /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
11416
- * `lawn-mower-control` cap. */
11417
- DeviceType["LawnMower"] = "lawn-mower";
11418
- /** Physical HA device group — parent container for entity-children
11419
- * adopted from a single HA device entry. Not renderable as a
11420
- * standalone device; exists only to anchor child entities. */
11421
- DeviceType["Container"] = "container";
11422
- /** Single still-image entity (HA `image.*`). Read-only display of an
11423
- * `entity_picture` signed URL the browser loads directly. `image` cap. */
11424
- DeviceType["Image"] = "image";
11425
- /** Smart pet feeder — cloud-connected food dispenser with a bowl food
11426
- * level, battery, desiccant life, feeding state and manual-feed /
11427
- * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
11428
- * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
11429
- * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
11430
- * integrations sharing the same food/desiccant/hopper surface. */
11431
- DeviceType["PetFeeder"] = "pet-feeder";
11432
- /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
11433
- * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
11434
- * of a container and is BUILT as a composed device (D967). */
11435
- DeviceType["PetFountain"] = "pet-fountain";
11436
- /** Built-in dishwasher — program lifecycle, door / remote-start gates,
11437
- * salt and rinse-aid levels. Installed with the `dishwasher` cap.
11438
- * Sources: native Bosch Home Connect, reusable by similar integrations.
11439
- * A plug-built dishwasher carries running-state and power instead. */
11440
- DeviceType["Dishwasher"] = "dishwasher";
11441
- /** Washing machine. A plug-built one is running while its power stays
11442
- * above a threshold. No program cap — that surface is the dishwasher's. */
11443
- DeviceType["Washer"] = "washer";
11444
- /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
11445
- DeviceType["Dryer"] = "dryer";
11446
- /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
11447
- * other radio-to-IP bridges). It carries the box's own facts (firmware,
11448
- * address, connection state) and the actions the box really supports
11449
- * (`reboot`, `device-admin-link`). Products are NOT its children — they are
11450
- * adopted beside it — and its connection settings stay on the integration. */
11451
- DeviceType["Gateway"] = "gateway";
11452
- return DeviceType;
11453
- }({});
11454
- var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
11455
- DeviceFeature["BatteryOperated"] = "battery-operated";
11456
- DeviceFeature["Rebootable"] = "rebootable";
11457
- /**
11458
- * Device supports an on-demand re-sync of its derived spec with its
11459
- * upstream source — drives the generic Re-sync button. The owning
11460
- * provider implements the action via the `device-adoption.resync` cap.
11461
- */
11462
- DeviceFeature["Resyncable"] = "resyncable";
11463
- DeviceFeature["NativeSnapshot"] = "native-snapshot";
11464
- DeviceFeature["DoorbellButton"] = "doorbell-button";
11465
- DeviceFeature["TwoWayAudio"] = "two-way-audio";
11466
- DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
11467
- /**
11468
- * Camera supports the on-firmware autotrack subsystem (subject-
11469
- * following). Distinct from `PanTiltZoom` because not every PTZ
11470
- * camera ships autotrack — the admin UI uses this flag to gate
11471
- * the autotrack toggle / settings card without re-deriving from
11472
- * the cap registry. Mirrors `ptz-autotrack` cap registration:
11473
- * driver sets this feature when probe confirms the firmware
11474
- * surface, and registers the cap in the same code path.
11475
- */
11476
- DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
11477
- /**
11478
- * Accessory exposes a "trigger on motion" toggle — the parent camera's
11479
- * motion detection automatically activates this device. Mirrors
11480
- * `motion-trigger` cap registration: drivers set this feature in the
11481
- * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
11482
- *
11483
- * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
11484
- * fast scalar without binding fetch), notifier rules, and `listAll`
11485
- * filters that want "all devices with on-motion behaviour".
11486
- */
11487
- DeviceFeature["MotionTrigger"] = "motion-trigger";
11488
- /** Light supports rgb-triplet color via `color` cap. */
11489
- DeviceFeature["LightColorRgb"] = "light-color-rgb";
11490
- /** Light supports HSV color via `color` cap. */
11491
- DeviceFeature["LightColorHsv"] = "light-color-hsv";
11492
- /** Light supports color-temperature (mired) via `color` cap. */
11493
- DeviceFeature["LightColorMired"] = "light-color-mired";
11494
- /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
11495
- * targetHigh). Gates the range slider UI. */
11496
- DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
11497
- /** Thermostat exposes target humidity and/or current humidity
11498
- * readings. Gates the humidity controls. */
11499
- DeviceFeature["ClimateHumidity"] = "climate-humidity";
11500
- /** Thermostat exposes a fan-mode selector. */
11501
- DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
11502
- /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
11503
- DeviceFeature["ClimatePreset"] = "climate-preset";
11504
- /** Thermostat exposes a vertical louver swing toggle. Gates the
11505
- * vertical-swing switch in the climate UI. Independent of horizontal. */
11506
- DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
11507
- /** Thermostat exposes a horizontal louver swing toggle. Gates the
11508
- * horizontal-swing switch in the climate UI. Independent of vertical. */
11509
- DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
11510
- /** Cover exposes intermediate position control (0..100). Gates the
11511
- * position slider UI. */
11512
- DeviceFeature["CoverPositionable"] = "cover-positionable";
11513
- /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
11514
- DeviceFeature["CoverTilt"] = "cover-tilt";
11515
- /** Valve exposes intermediate position control (0..100). Gates the
11516
- * position slider / drag surface UI. */
11517
- DeviceFeature["ValvePositionable"] = "valve-positionable";
11518
- /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
11519
- DeviceFeature["FanSpeed"] = "fan-speed";
11520
- /** Fan exposes a preset mode selector. */
11521
- DeviceFeature["FanPreset"] = "fan-preset";
11522
- /** Fan exposes blade direction (forward/reverse) — typical of
11523
- * ceiling fans. */
11524
- DeviceFeature["FanDirection"] = "fan-direction";
11525
- /** Fan exposes an oscillation toggle. */
11526
- DeviceFeature["FanOscillating"] = "fan-oscillating";
11527
- /** Lock requires a PIN code on lock/unlock. Gates the code-entry
11528
- * field on the UI lock-controls panel. */
11529
- DeviceFeature["LockPinRequired"] = "lock-pin-required";
11530
- /** Lock supports a latch-release ("open door") action distinct from
11531
- * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
11532
- * `supported_features`. Gates the Open Door button in the UI. */
11533
- DeviceFeature["LockOpen"] = "lock-open";
11534
- /** Media player exposes a seek-to-position surface. */
11535
- DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
11536
- /** Media player exposes a volume-level setter. */
11537
- DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
11538
- /** Media player exposes a mute toggle distinct from volume=0. */
11539
- DeviceFeature["MediaPlayerMute"] = "media-player-mute";
11540
- /** Media player exposes a shuffle toggle. */
11541
- DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
11542
- /** Media player exposes a repeat mode (off / all / one). */
11543
- DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
11544
- /** Media player exposes a source / input selector. */
11545
- DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
11546
- /** Media player exposes a play-arbitrary-media surface (URL / id). */
11547
- DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
11548
- /** Media player exposes next-track. */
11549
- DeviceFeature["MediaPlayerNext"] = "media-player-next";
11550
- /** Media player exposes previous-track. */
11551
- DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
11552
- /** Media player exposes stop distinct from pause. */
11553
- DeviceFeature["MediaPlayerStop"] = "media-player-stop";
11554
- /** Alarm panel requires a PIN code on arm/disarm. */
11555
- DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
11556
- /** Presence device carries GPS coordinates (lat/lng/accuracy) in
11557
- * addition to a textual location. */
11558
- DeviceFeature["PresenceGps"] = "presence-gps";
11559
- /** Notifier accepts an inline / URL image attachment. */
11560
- DeviceFeature["NotifierImage"] = "notifier-image";
11561
- /** Notifier accepts a priority hint (high/normal/low). */
11562
- DeviceFeature["NotifierPriority"] = "notifier-priority";
11563
- /** Notifier accepts a free-form `data` payload for platform-specific
11564
- * fields. */
11565
- DeviceFeature["NotifierData"] = "notifier-data";
11566
- /** Notifier supports interactive action buttons / callbacks. */
11567
- DeviceFeature["NotifierActions"] = "notifier-actions";
11568
- /** Notifier supports per-call recipient targeting (multi-user). */
11569
- DeviceFeature["NotifierRecipients"] = "notifier-recipients";
11570
- /** Script runner accepts a variables map on each run invocation. */
11571
- DeviceFeature["ScriptVariables"] = "script-variables";
11572
- /** Automation `trigger` accepts a skipCondition flag — fires the
11573
- * automation's actions while bypassing its condition block. */
11574
- DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
11575
- /** Robot vacuum exposes a live cleaning map (image child). Gates the
11576
- * map tile in the vacuum UI. */
11577
- DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
11578
- /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
11579
- * switches group. */
11580
- DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
11581
- /** Robot mower exposes a live mowing map (SVG image child). Gates the
11582
- * mower map tile. */
11583
- DeviceFeature["MowerHasMap"] = "mower-has-map";
11584
- /** Robot mower exposes targeted mowing (all-area / zones / edges /
11585
- * spots) — gates the Mow action + map selectors. */
11586
- DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
11587
- return DeviceFeature;
11588
- }({});
11589
- /**
11590
- * Semantic role a device plays within its parent. Populated by driver
11591
- * addons when creating accessory devices (Reolink siren/floodlight/
11592
- * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
11593
- * admin UI to pick icons, labels, and widgets — a `Switch` with
11594
- * `role: Floodlight` renders as a bulb with a brightness slider,
11595
- * whereas a `Switch` with `role: Siren` renders as a klaxon.
11596
- *
11597
- * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
11598
- * sqlite as a nullable TEXT column — old rows keep working unchanged.
11599
- */
11600
- var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11601
- DeviceRole["Siren"] = "siren";
11602
- DeviceRole["Floodlight"] = "floodlight";
11603
- DeviceRole["Spotlight"] = "spotlight";
11604
- DeviceRole["PirSensor"] = "pir-sensor";
11605
- DeviceRole["Chime"] = "chime";
11606
- DeviceRole["Autotrack"] = "autotrack";
11607
- DeviceRole["Nightvision"] = "nightvision";
11608
- DeviceRole["PrivacyMask"] = "privacy-mask";
11609
- DeviceRole["Doorbell"] = "doorbell";
11610
- /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
11611
- DeviceRole["CoverBlind"] = "cover-blind";
11612
- /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
11613
- DeviceRole["CoverShutter"] = "cover-shutter";
11614
- /** Exterior awning. HA `device_class: awning`. */
11615
- DeviceRole["CoverAwning"] = "cover-awning";
11616
- /** Curtain / drape. HA `device_class: curtain`. */
11617
- DeviceRole["CoverCurtain"] = "cover-curtain";
11618
- /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
11619
- DeviceRole["CoverGarage"] = "cover-garage";
11620
- /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
11621
- DeviceRole["CoverGate"] = "cover-gate";
11622
- /** Door. HA `device_class: door`. A security actuator: exports gate it. */
11623
- DeviceRole["CoverDoor"] = "cover-door";
11624
- /** Window. HA `device_class: window`. */
11625
- DeviceRole["CoverWindow"] = "cover-window";
11626
- /** Virtual HA toggle (input_boolean.*) — distinguishable from a
11627
- * real Switch device for UI rendering / export adapters. */
11628
- DeviceRole["BinaryHelper"] = "binary-helper";
11629
- /** A smart plug: a `switch` whose source says it is an outlet (HA
11630
- * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
11631
- DeviceRole["Outlet"] = "outlet";
11632
- /** Generic motion / occupancy / moving event source. Distinct from
11633
- * the camera accessory PirSensor role: that one is a camera child;
11634
- * this is a standalone HA / 3rd-party motion sensor. */
11635
- DeviceRole["MotionSensor"] = "motion-sensor";
11636
- DeviceRole["ContactSensor"] = "contact-sensor";
11637
- DeviceRole["LeakSensor"] = "leak-sensor";
11638
- DeviceRole["SmokeSensor"] = "smoke-sensor";
11639
- DeviceRole["COSensor"] = "co-sensor";
11640
- DeviceRole["GasSensor"] = "gas-sensor";
11641
- DeviceRole["TamperSensor"] = "tamper-sensor";
11642
- DeviceRole["VibrationSensor"] = "vibration-sensor";
11643
- DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
11644
- DeviceRole["SoundSensor"] = "sound-sensor";
11645
- /** Fallback for `binary_sensor` without a known `device_class`. */
11646
- DeviceRole["BinarySensor"] = "binary-sensor";
11647
- DeviceRole["TemperatureSensor"] = "temperature-sensor";
11648
- DeviceRole["HumiditySensor"] = "humidity-sensor";
11649
- DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
11650
- DeviceRole["PressureSensor"] = "pressure-sensor";
11651
- /** Wind speed or direction (weather-station `wind-sensor` cap). */
11652
- DeviceRole["WindSensor"] = "wind-sensor";
11653
- /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
11654
- DeviceRole["RainSensor"] = "rain-sensor";
11655
- /** UV index (weather-station `uv-sensor` cap). */
11656
- DeviceRole["UvSensor"] = "uv-sensor";
11657
- /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
11658
- * Distinct from AmbientLightSensor (lux). */
11659
- DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
11660
- /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
11661
- DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
11662
- DeviceRole["PowerSensor"] = "power-sensor";
11663
- DeviceRole["EnergySensor"] = "energy-sensor";
11664
- DeviceRole["VoltageSensor"] = "voltage-sensor";
11665
- DeviceRole["CurrentSensor"] = "current-sensor";
11666
- DeviceRole["AirQualitySensor"] = "air-quality-sensor";
11667
- /** Battery level (numeric % via `sensor` OR low-bool via
11668
- * `binary_sensor` — the cap distinguishes via the value type). */
11669
- DeviceRole["BatterySensor"] = "battery-sensor";
11670
- /** Fallback for `sensor` numeric without a known `device_class`. */
11671
- DeviceRole["NumericSensor"] = "numeric-sensor";
11672
- /** String / enum state (HA `sensor` with `state_class: enum` or
11673
- * `attributes.options`). */
11674
- DeviceRole["EnumSensor"] = "enum-sensor";
11675
- /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
11676
- * or `date`). The slice carries the raw ISO string verbatim (hosted on
11677
- * the `enum-sensor` cap); the UI renders it locale-formatted. */
11678
- DeviceRole["DateTimeSensor"] = "datetime-sensor";
11679
- /** Last-resort fallback when nothing else matches. */
11680
- DeviceRole["GenericSensor"] = "generic-sensor";
11681
- DeviceRole["NumericControl"] = "numeric-control";
11682
- DeviceRole["SelectControl"] = "select-control";
11683
- DeviceRole["TextControl"] = "text-control";
11684
- DeviceRole["DateTimeControl"] = "datetime-control";
11685
- /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
11686
- * rich features (image, priority, channel routing). */
11687
- DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
11688
- /** Chat / messaging service (HA `notify.telegram_*`,
11689
- * `notify.discord_*`, etc.). */
11690
- DeviceRole["MessagingNotifier"] = "messaging-notifier";
11691
- /** Email-based delivery (HA `notify.smtp`, etc.). */
11692
- DeviceRole["EmailNotifier"] = "email-notifier";
11693
- /** Fallback when the notifier service name doesn't match a known
11694
- * pattern. */
11695
- DeviceRole["GenericNotifier"] = "generic-notifier";
11696
- /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
11697
- * cap. A `DeviceType.Generic` device. */
11698
- DeviceRole["IrTransmitter"] = "ir-transmitter";
11699
- return DeviceRole;
11700
- }({});
11701
- /**
11702
- import { errMsg } from '@camstack/types'
11703
- * Extract a human-readable message from an unknown error value.
11704
- * Replaces the ubiquitous `errMsg(err)` pattern.
11705
- */
11706
- function errMsg(err) {
11707
- if (err instanceof Error) return err.message;
11708
- if (typeof err === "string") return err;
11709
- return String(err);
11710
- }
11711
- //#endregion
11712
- //#region ../types/dist/composition-C7WdThmP.mjs
11866
+ //#region ../types/dist/composition-BBzVf2xd.mjs
11713
11867
  /**
11714
11868
  * Deterministic SHA-256 hash of an arbitrary serialisable value. The
11715
11869
  * canonical form sorts object keys alphabetically at every depth so two
@@ -13103,6 +13257,90 @@ var EVENT_OWNER_TYPE_SET = new Set(EVENT_OWNER_TYPES);
13103
13257
  function isEventOwnerType(ownerType) {
13104
13258
  return EVENT_OWNER_TYPE_SET.has(ownerType);
13105
13259
  }
13260
+ /**
13261
+ * What a published package needs from the rest of the install (D989).
13262
+ *
13263
+ * One published framework package (`@camstack/server`) carries the kernel, the
13264
+ * builtins and the capability contracts, so "which addon runs on which
13265
+ * framework" is a question of version, and the answer is DECLARED by the
13266
+ * package that knows it:
13267
+ *
13268
+ * - an addon says the oldest server it runs on (`requires.server: ">=X"`);
13269
+ * - the server says the oldest addon it works with
13270
+ * (`requires.addons: { "@camstack/addon-x": ">=Y" }`) and the oldest shell
13271
+ * around it (`requires.wrapper: { desktop, image }`);
13272
+ * - a wrapper (the desktop app, the container starter) says which server
13273
+ * range it can host, and reports its own kind and version.
13274
+ *
13275
+ * The publish workflow STAMPS these after the version bump
13276
+ * (`scripts/ci/stamp-requires.mjs`); nobody types a number by hand.
13277
+ *
13278
+ * A requirement that cannot be checked is NOT met: a shell that reports no
13279
+ * version counts as below the minimum, and an unreadable running version is
13280
+ * named in the result (a failed read is never a pass).
13281
+ */
13282
+ /** `>=MAJOR.MINOR.PATCH[-prerelease]` — the only range shape a requirement uses. */
13283
+ var MinVersionSchema = string().regex(/^>=\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, "a minimum is written \">=X.Y.Z\"");
13284
+ object({
13285
+ /** The oldest `@camstack/server` this package runs on. */
13286
+ server: MinVersionSchema.optional(),
13287
+ /** The oldest installed version of each named addon package. */
13288
+ addons: record(string(), MinVersionSchema).optional(),
13289
+ /** The oldest shell around the server, per shell kind. */
13290
+ wrapper: object({
13291
+ desktop: MinVersionSchema.optional(),
13292
+ image: MinVersionSchema.optional()
13293
+ }).optional()
13294
+ });
13295
+ /** One unmet requirement; each names the versions on both sides. */
13296
+ var UnmetRequirementSchema = discriminatedUnion("kind", [
13297
+ object({
13298
+ kind: literal("server-too-old"),
13299
+ required: string(),
13300
+ /** `null` — the running server version could not be read. */
13301
+ running: string().nullable()
13302
+ }),
13303
+ object({
13304
+ kind: literal("addon-too-old"),
13305
+ packageName: string(),
13306
+ required: string(),
13307
+ /** `null` — the addon is not installed (or its version is unreadable). */
13308
+ running: string().nullable()
13309
+ }),
13310
+ object({
13311
+ kind: literal("wrapper-too-old"),
13312
+ wrapper: _enum(["desktop", "image"]),
13313
+ required: string(),
13314
+ /** `null` — the wrapper reported no version. */
13315
+ running: string().nullable()
13316
+ })
13317
+ ]);
13318
+ var RequiredAddonUpdateSchema = object({
13319
+ packageName: string(),
13320
+ /** The minimum the target server declares. */
13321
+ required: string(),
13322
+ /** What this node has; `null` when the addon is not installed. */
13323
+ installed: string().nullable()
13324
+ });
13325
+ /**
13326
+ * Which integration instance a settings form is for. An addon that serves several instances
13327
+ * (`instanceMode: multiple`) can show a different form per instance — an NVR integration
13328
+ * needs its connection, the default integration needs the addon's own sections.
13329
+ *
13330
+ * - `integrationId` is `null` in the create wizard: nothing exists yet, the operator is
13331
+ * setting up a NEW instance.
13332
+ * - `mode` is the wizard mode of the integration type (`getAvailableTypes`).
13333
+ * - `candidateStableId` is the discovery candidate a wizard was seeded from, if any.
13334
+ */
13335
+ var IntegrationInstanceRefSchema = object({
13336
+ integrationId: string().nullable(),
13337
+ mode: _enum([
13338
+ "standalone",
13339
+ "account",
13340
+ "broker"
13341
+ ]),
13342
+ candidateStableId: string().optional()
13343
+ });
13106
13344
  var EncodeProfileSchema = object({
13107
13345
  video: object({
13108
13346
  codec: _enum([
@@ -13670,6 +13908,613 @@ var CameraSwitchGroupSchema = object({
13670
13908
  */
13671
13909
  var DETECTION_PIPELINE_CAP_NAME = "detection-pipeline";
13672
13910
  /**
13911
+ * Query filter for settings-store collections.
13912
+ */
13913
+ var QueryFilterSchema = object({
13914
+ where: record(string(), unknown()).optional(),
13915
+ whereIn: record(string(), array(unknown())).optional(),
13916
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
13917
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
13918
+ whereNot: record(string(), unknown()).optional(),
13919
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
13920
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
13921
+ whereNotIn: record(string(), array(unknown())).optional(),
13922
+ orderBy: object({
13923
+ field: string(),
13924
+ direction: _enum(["asc", "desc"])
13925
+ }).optional(),
13926
+ limit: number().optional(),
13927
+ offset: number().optional()
13928
+ });
13929
+ /**
13930
+ * The predicate half of a filter, for BULK MUTATIONS.
13931
+ *
13932
+ * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
13933
+ * meaning for a statement that rewrites a set, and accepting them would invite
13934
+ * a caller to believe `limit` bounds the damage. Every field is optional here
13935
+ * only so the shape stays composable — the implementation REJECTS a filter
13936
+ * that compiles to no predicate, because that is the whole collection.
13937
+ */
13938
+ var MutationFilterSchema = object({
13939
+ where: record(string(), unknown()).optional(),
13940
+ whereIn: record(string(), array(unknown())).optional(),
13941
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
13942
+ whereNot: record(string(), unknown()).optional(),
13943
+ whereNotIn: record(string(), array(unknown())).optional()
13944
+ });
13945
+ /**
13946
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
13947
+ *
13948
+ * `as` names the slot in the result, so the SAME column may be asked twice with
13949
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
13950
+ * a `Record<column, op>` shape could not express.
13951
+ */
13952
+ var AggregateFieldSchema = object({
13953
+ /** Result key. */
13954
+ as: string().min(1),
13955
+ /** Column to aggregate. Must be a real column of a declared collection. */
13956
+ field: string().min(1),
13957
+ op: _enum([
13958
+ "sum",
13959
+ "min",
13960
+ "max"
13961
+ ])
13962
+ });
13963
+ /**
13964
+ * `COUNT(*)` plus one number per requested field.
13965
+ *
13966
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
13967
+ * that really is 0 are different facts, and an accounting caller that renders
13968
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
13969
+ */
13970
+ var AggregateResultSchema = object({
13971
+ count: number().int(),
13972
+ values: record(string(), number().nullable())
13973
+ });
13974
+ /** A single stored record: `{ id, data }`. */
13975
+ var SettingsRecordSchema = object({
13976
+ id: string(),
13977
+ data: record(string(), unknown())
13978
+ });
13979
+ /**
13980
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
13981
+ * optional.
13982
+ *
13983
+ * A separate schema rather than loosening the shared one: every other method
13984
+ * on this cap addresses a row BY its id, and making that field optional
13985
+ * everywhere would turn a forgotten key into a silently generated one on
13986
+ * `update` and `delete` as well.
13987
+ */
13988
+ var BulkRecordSchema = object({
13989
+ id: string().optional(),
13990
+ data: record(string(), unknown())
13991
+ });
13992
+ /**
13993
+ * The database FILES one `sqlite-settings` engine process owns (D851).
13994
+ * Closed on purpose: a file is a thing the engine opens, backs up and gives a
13995
+ * serving thread, so a new one is a code change, never a string a caller
13996
+ * invents. `settings` is `camstack.db` and the default for every collection
13997
+ * that names no file.
13998
+ */
13999
+ var DataStoreDatabaseSchema = _enum([
14000
+ "settings",
14001
+ "events",
14002
+ "history"
14003
+ ]);
14004
+ DataStoreDatabaseSchema.options;
14005
+ /**
14006
+ * One write inside a {@link settingsStoreCapability.methods.transact} call.
14007
+ * The three that a "replace this set, bump that revision" write needs, plus
14008
+ * the batched upsert a rollup writer needs.
14009
+ */
14010
+ var TransactOpSchema = discriminatedUnion("op", [
14011
+ object({
14012
+ op: literal("deleteWhere"),
14013
+ collection: string(),
14014
+ filter: MutationFilterSchema
14015
+ }),
14016
+ object({
14017
+ op: literal("insertMany"),
14018
+ collection: string(),
14019
+ records: array(BulkRecordSchema).readonly()
14020
+ }),
14021
+ object({
14022
+ op: literal("update"),
14023
+ collection: string(),
14024
+ id: string(),
14025
+ data: record(string(), unknown())
14026
+ }),
14027
+ object({
14028
+ op: literal("upsertMany"),
14029
+ collection: string(),
14030
+ conflict: array(string()).readonly(),
14031
+ records: array(BulkRecordSchema).readonly()
14032
+ })
14033
+ ]);
14034
+ /**
14035
+ * The precondition of a transaction: the row `key` of `collection` must hold
14036
+ * `equals` in `column`, read INSIDE the transaction.
14037
+ */
14038
+ var TransactGuardSchema = object({
14039
+ collection: string(),
14040
+ key: string(),
14041
+ column: string(),
14042
+ equals: unknown()
14043
+ });
14044
+ /**
14045
+ * Column declaration for a structured (SQL-backed) collection.
14046
+ *
14047
+ * Logical types — the backend translates each to the matching SQLite
14048
+ * storage class and handles per-type marshaling:
14049
+ * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
14050
+ * - `JSON` — TEXT under the hood; serialised on write, parsed on read
14051
+ * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
14052
+ */
14053
+ var CollectionColumnSchema = object({
14054
+ name: string(),
14055
+ type: _enum([
14056
+ "TEXT",
14057
+ "INTEGER",
14058
+ "REAL",
14059
+ "JSON",
14060
+ "BOOLEAN"
14061
+ ]),
14062
+ primaryKey: boolean().optional(),
14063
+ notNull: boolean().optional(),
14064
+ unique: boolean().optional(),
14065
+ /**
14066
+ * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
14067
+ * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
14068
+ * collection surface simply cannot express their existing tables — which is
14069
+ * why they were still on the uncapped `table*` API.
14070
+ */
14071
+ defaultValue: union([
14072
+ string(),
14073
+ number(),
14074
+ boolean()
14075
+ ]).optional()
14076
+ });
14077
+ var CollectionIndexSchema = object({
14078
+ name: string(),
14079
+ columns: array(string()).readonly(),
14080
+ unique: boolean().optional()
14081
+ });
14082
+ /**
14083
+ * The input of `declareCollection` — ONE schema for the door and for the
14084
+ * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
14085
+ * the payload untouched).
14086
+ *
14087
+ * - `database` — the file the collection lives in (D851). Absent means
14088
+ * `settings`. A declaration naming a file other than the one the table is
14089
+ * already in is REFUSED by the engine, never moved.
14090
+ * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
14091
+ * as `primaryKey: true` on that column. A composite-keyed collection has no
14092
+ * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
14093
+ * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
14094
+ * address one row by id are refused.
14095
+ * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
14096
+ */
14097
+ var DeclareCollectionInputSchema = object({
14098
+ namespace: string().optional(),
14099
+ collection: string(),
14100
+ columns: array(CollectionColumnSchema).readonly(),
14101
+ indexes: array(CollectionIndexSchema).readonly().optional(),
14102
+ database: DataStoreDatabaseSchema.optional(),
14103
+ primaryKey: array(string()).readonly().optional(),
14104
+ withoutRowid: boolean().optional()
14105
+ });
14106
+ method(object({
14107
+ namespace: string().optional(),
14108
+ collection: string(),
14109
+ key: string()
14110
+ }), unknown(), { auth: "admin" }), method(object({
14111
+ namespace: string().optional(),
14112
+ collection: string(),
14113
+ key: string(),
14114
+ value: unknown()
14115
+ }), _void(), {
14116
+ kind: "mutation",
14117
+ auth: "admin"
14118
+ }), method(object({
14119
+ namespace: string().optional(),
14120
+ collection: string(),
14121
+ filter: QueryFilterSchema.optional(),
14122
+ columns: array(string()).readonly().optional()
14123
+ }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
14124
+ namespace: string().optional(),
14125
+ collection: string(),
14126
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
14127
+ * index on the collection. Never empty. */
14128
+ conflict: array(string()).readonly(),
14129
+ record: BulkRecordSchema
14130
+ }), object({
14131
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
14132
+ * row's own id on an update. */
14133
+ id: union([string(), number()]) }), {
14134
+ kind: "mutation",
14135
+ auth: "admin"
14136
+ }), method(object({
14137
+ namespace: string().optional(),
14138
+ collection: string(),
14139
+ conflict: array(string()).readonly(),
14140
+ records: array(BulkRecordSchema).readonly()
14141
+ }), object({ upserted: number().int() }), {
14142
+ kind: "mutation",
14143
+ auth: "admin"
14144
+ }), method(object({
14145
+ namespace: string().optional(),
14146
+ collection: string(),
14147
+ record: object({
14148
+ id: string().optional(),
14149
+ data: record(string(), unknown())
14150
+ })
14151
+ }), object({
14152
+ /**
14153
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
14154
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
14155
+ * primary key — which is the only place an auto key is knowable.
14156
+ */
14157
+ id: union([string(), number()]) }), {
14158
+ kind: "mutation",
14159
+ auth: "admin"
14160
+ }), method(object({
14161
+ namespace: string().optional(),
14162
+ collection: string(),
14163
+ records: array(BulkRecordSchema).readonly()
14164
+ }), object({ inserted: number().int() }), {
14165
+ kind: "mutation",
14166
+ auth: "admin"
14167
+ }), method(object({
14168
+ namespace: string().optional(),
14169
+ guard: TransactGuardSchema.optional(),
14170
+ ops: array(TransactOpSchema).readonly()
14171
+ }), object({
14172
+ applied: boolean(),
14173
+ current: unknown().optional()
14174
+ }), {
14175
+ kind: "mutation",
14176
+ auth: "admin"
14177
+ }), method(object({
14178
+ namespace: string().optional(),
14179
+ collection: string(),
14180
+ id: string(),
14181
+ data: record(string(), unknown())
14182
+ }), _void(), {
14183
+ kind: "mutation",
14184
+ auth: "admin"
14185
+ }), method(object({
14186
+ namespace: string().optional(),
14187
+ collection: string(),
14188
+ key: string()
14189
+ }), _void(), {
14190
+ kind: "mutation",
14191
+ auth: "admin"
14192
+ }), method(object({
14193
+ namespace: string().optional(),
14194
+ collection: string(),
14195
+ filter: MutationFilterSchema
14196
+ }), object({ deleted: number().int() }), {
14197
+ kind: "mutation",
14198
+ auth: "admin"
14199
+ }), method(object({
14200
+ namespace: string().optional(),
14201
+ collection: string(),
14202
+ filter: MutationFilterSchema,
14203
+ data: record(string(), unknown())
14204
+ }), object({ updated: number().int() }), {
14205
+ kind: "mutation",
14206
+ auth: "admin"
14207
+ }), method(object({
14208
+ namespace: string().optional(),
14209
+ collection: string(),
14210
+ filter: QueryFilterSchema.optional()
14211
+ }), number(), { auth: "admin" }), method(object({
14212
+ namespace: string().optional(),
14213
+ collection: string(),
14214
+ fields: array(AggregateFieldSchema).readonly(),
14215
+ filter: QueryFilterSchema.optional()
14216
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
14217
+ namespace: string().optional(),
14218
+ collection: string(),
14219
+ field: string(),
14220
+ bucketSize: number().int().positive(),
14221
+ origin: number().int(),
14222
+ filter: QueryFilterSchema.optional()
14223
+ }), array(object({
14224
+ bucket: number().int(),
14225
+ count: number().int()
14226
+ })).readonly(), { auth: "admin" }), method(object({
14227
+ namespace: string().optional(),
14228
+ collection: string()
14229
+ }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
14230
+ kind: "mutation",
14231
+ auth: "admin"
14232
+ });
14233
+ var MigrationIdSchema = string().regex(/^[a-z0-9-]+\/\d{4}-\d{2}-\d{2}-[a-z0-9-]+$/, "a migration id is \"<owner>/<yyyy-mm-dd>-<slug>\"");
14234
+ var MigrationLevelSchema = _enum([
14235
+ "hub-store",
14236
+ "addon-state",
14237
+ "node-config",
14238
+ "viewer",
14239
+ "addon-internal"
14240
+ ]);
14241
+ var MigrationStoreSchema = _enum([
14242
+ "settings",
14243
+ "events",
14244
+ "history",
14245
+ "node-local",
14246
+ "addon-files"
14247
+ ]);
14248
+ var MigrationScopeSchema = _enum(["cluster", "per-node"]);
14249
+ /** What depends on a migration while it is pending: `block-owner` is the default. */
14250
+ var MigrationOnFailureSchema = _enum(["block-owner", "tolerate"]);
14251
+ /**
14252
+ * `sql` is a hub-only body over the owner's connection. `plan` returns edits the
14253
+ * engine applies inside the migration's own transaction (hub-owned: computed in
14254
+ * that transaction; addon-owned: computed by the owner and shipped to the engine).
14255
+ * `procedure` is addon-owned work that is not SQL (keychain, integration registry,
14256
+ * device tombstones); the ledger records its outcome and it must be idempotent.
14257
+ */
14258
+ var MigrationBodyKindSchema = _enum([
14259
+ "sql",
14260
+ "plan",
14261
+ "procedure"
14262
+ ]);
14263
+ var MigrationMetaSchema = object({
14264
+ id: MigrationIdSchema,
14265
+ /** `hub`, an addon id, `node` or `viewer`. */
14266
+ owner: string().regex(/^[a-z0-9-]+$/),
14267
+ level: MigrationLevelSchema,
14268
+ /** Strictly increasing per owner: the order within an owner. */
14269
+ seq: number().int().positive(),
14270
+ /** Ids of any owner: the order across owners. */
14271
+ dependsOn: array(MigrationIdSchema),
14272
+ store: MigrationStoreSchema,
14273
+ scope: MigrationScopeSchema,
14274
+ onFailure: MigrationOnFailureSchema,
14275
+ bodyKind: MigrationBodyKindSchema,
14276
+ description: string().min(1),
14277
+ /** The marked field the migration's spec must assert survived. */
14278
+ fixtureMark: string().min(1)
14279
+ }).refine((m) => m.id.startsWith(`${m.owner}/`), {
14280
+ message: "the id must start with \"<owner>/\"",
14281
+ path: ["id"]
14282
+ });
14283
+ /** A cycle in the dependency graph, named by the ids on it. */
14284
+ var MigrationCycleError = class extends Error {
14285
+ ids;
14286
+ constructor(ids) {
14287
+ super(`migration dependsOn cycle: ${ids.join(" -> ")}`);
14288
+ this.ids = ids;
14289
+ this.name = "MigrationCycleError";
14290
+ }
14291
+ };
14292
+ /**
14293
+ * Topological order over `dependsOn` plus the implicit edge "a lower `seq` of an
14294
+ * owner runs first". Ties are broken by `(owner, seq)`. Throws
14295
+ * {@link MigrationCycleError} on a cycle. A `dependsOn` id that is not in the list
14296
+ * is ignored here — the catalogue schema is what refuses it.
14297
+ */
14298
+ function orderMigrations(items) {
14299
+ const byId = new Map(items.map((item) => [item.id, item]));
14300
+ const deps = /* @__PURE__ */ new Map();
14301
+ const ownerSorted = /* @__PURE__ */ new Map();
14302
+ for (const item of items) {
14303
+ const list = ownerSorted.get(item.owner) ?? [];
14304
+ list.push(item);
14305
+ ownerSorted.set(item.owner, list);
14306
+ }
14307
+ for (const item of items) deps.set(item.id, new Set(item.dependsOn.filter((id) => byId.has(id))));
14308
+ for (const list of ownerSorted.values()) {
14309
+ list.sort((a, b) => a.seq - b.seq);
14310
+ for (let i = 1; i < list.length; i++) {
14311
+ const previous = list[i - 1];
14312
+ const current = list[i];
14313
+ if (previous !== void 0 && current !== void 0) deps.get(current.id)?.add(previous.id);
14314
+ }
14315
+ }
14316
+ const tie = (a, b) => a.owner < b.owner ? -1 : a.owner > b.owner ? 1 : a.seq - b.seq;
14317
+ const done = /* @__PURE__ */ new Set();
14318
+ const ordered = [];
14319
+ let remaining = [...items];
14320
+ while (remaining.length > 0) {
14321
+ const ready = remaining.filter((item) => [...deps.get(item.id) ?? []].every((dep) => done.has(dep)));
14322
+ if (ready.length === 0) throw new MigrationCycleError(remaining.map((item) => item.id).sort());
14323
+ ready.sort(tie);
14324
+ const next = ready[0];
14325
+ if (next === void 0) break;
14326
+ ordered.push(next);
14327
+ done.add(next.id);
14328
+ remaining = remaining.filter((item) => item.id !== next.id);
14329
+ }
14330
+ return ordered;
14331
+ }
14332
+ /** Every way a list of migrations (in declaration order) is not a catalogue. */
14333
+ function catalogueIssues(items) {
14334
+ const issues = [];
14335
+ const seen = /* @__PURE__ */ new Set();
14336
+ const known = new Set(items.map((item) => item.id));
14337
+ const lastSeq = /* @__PURE__ */ new Map();
14338
+ const seqUsed = /* @__PURE__ */ new Map();
14339
+ for (const item of items) {
14340
+ if (seen.has(item.id)) issues.push({
14341
+ code: "duplicate-id",
14342
+ ids: [item.id],
14343
+ message: `duplicate id ${item.id}`
14344
+ });
14345
+ seen.add(item.id);
14346
+ const used = seqUsed.get(item.owner) ?? /* @__PURE__ */ new Set();
14347
+ if (used.has(item.seq)) issues.push({
14348
+ code: "duplicate-seq",
14349
+ ids: [item.id],
14350
+ message: `owner ${item.owner} has two migrations with seq ${item.seq}`
14351
+ });
14352
+ else if (item.seq < (lastSeq.get(item.owner) ?? 0)) issues.push({
14353
+ code: "seq-order",
14354
+ ids: [item.id],
14355
+ message: `seq ${item.seq} of ${item.id} does not increase in declaration order (owner ${item.owner})`
14356
+ });
14357
+ used.add(item.seq);
14358
+ seqUsed.set(item.owner, used);
14359
+ lastSeq.set(item.owner, Math.max(item.seq, lastSeq.get(item.owner) ?? 0));
14360
+ for (const dep of item.dependsOn) if (!known.has(dep)) issues.push({
14361
+ code: "unknown-dependency",
14362
+ ids: [item.id, dep],
14363
+ message: `${item.id} dependsOn unknown id ${dep}`
14364
+ });
14365
+ }
14366
+ try {
14367
+ orderMigrations(items);
14368
+ } catch (err) {
14369
+ if (err instanceof MigrationCycleError) issues.push({
14370
+ code: "cycle",
14371
+ ids: err.ids,
14372
+ message: err.message
14373
+ });
14374
+ else throw err;
14375
+ }
14376
+ return issues;
14377
+ }
14378
+ array(MigrationMetaSchema).superRefine((items, ctx) => {
14379
+ for (const issue of catalogueIssues(items)) ctx.addIssue({
14380
+ code: "custom",
14381
+ message: `${issue.code}: ${issue.message}`
14382
+ });
14383
+ });
14384
+ /**
14385
+ * The canonical JSON (`preconditionOf`) of the value a plan read; the apply step
14386
+ * re-reads, and a plan whose read no longer matches is refused (the migration is
14387
+ * `failed`, nothing it edited is kept).
14388
+ */
14389
+ var PreconditionSchema = string().min(1).optional();
14390
+ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
14391
+ object({
14392
+ op: literal("setKeys"),
14393
+ scope: string().min(1),
14394
+ values: record(string(), unknown()),
14395
+ precondition: PreconditionSchema
14396
+ }),
14397
+ object({
14398
+ op: literal("deleteKeys"),
14399
+ scope: string().min(1),
14400
+ keys: array(string().min(1)).min(1),
14401
+ precondition: PreconditionSchema
14402
+ }),
14403
+ object({
14404
+ op: literal("putRow"),
14405
+ table: string().min(1),
14406
+ key: record(string(), unknown()),
14407
+ row: record(string(), unknown()),
14408
+ precondition: PreconditionSchema
14409
+ }),
14410
+ object({
14411
+ op: literal("deleteRow"),
14412
+ table: string().min(1),
14413
+ key: record(string(), unknown()),
14414
+ precondition: PreconditionSchema
14415
+ }),
14416
+ object({
14417
+ op: literal("upsertRecord"),
14418
+ kind: string().min(1),
14419
+ recordKey: string().min(1),
14420
+ value: unknown(),
14421
+ precondition: PreconditionSchema
14422
+ }),
14423
+ object({
14424
+ op: literal("deleteRecord"),
14425
+ kind: string().min(1),
14426
+ recordKey: string().min(1),
14427
+ precondition: PreconditionSchema
14428
+ })
14429
+ ])) });
14430
+ /**
14431
+ * What a `procedure` body reports. `already-done` means the rerun observed the
14432
+ * work finished (a crash between the work and the ledger row, or a manual fix)
14433
+ * and did none of it again; the ledger records `applied` either way.
14434
+ */
14435
+ var MigrationProcedureResultSchema = object({
14436
+ outcome: _enum(["done", "already-done"]),
14437
+ summary: record(string(), unknown())
14438
+ });
14439
+ /** What an owner hands the engine when one of its migrations ran. */
14440
+ var MigrationOwnerResultSchema = discriminatedUnion("kind", [
14441
+ object({
14442
+ kind: literal("plan"),
14443
+ plan: MigrationPlanSchema
14444
+ }),
14445
+ object({
14446
+ kind: literal("procedure"),
14447
+ result: MigrationProcedureResultSchema
14448
+ }),
14449
+ object({
14450
+ kind: literal("failed"),
14451
+ error: string().min(1)
14452
+ })
14453
+ ]);
14454
+ /**
14455
+ * `held`: a restore of a pre-migration snapshot undid this id; it does not rerun
14456
+ * until the operator retries or a new code version ships.
14457
+ * `skipped-newer`: the database knows an id this code does not.
14458
+ */
14459
+ var MigrationStatusSchema = _enum([
14460
+ "applied",
14461
+ "failed",
14462
+ "blocked",
14463
+ "running",
14464
+ "held",
14465
+ "skipped-newer"
14466
+ ]);
14467
+ var MigrationLedgerEntrySchema = object({
14468
+ id: string(),
14469
+ database: DataStoreDatabaseSchema,
14470
+ owner: string(),
14471
+ level: MigrationLevelSchema,
14472
+ seq: number().int().nonnegative(),
14473
+ status: MigrationStatusSchema,
14474
+ attempts: number().int().nonnegative(),
14475
+ appliedAt: number().nullable(),
14476
+ codeVersion: string().nullable(),
14477
+ /** What the apply changed: rows per table, as the engine counted them. */
14478
+ planSummary: record(string(), unknown()).nullable(),
14479
+ progress: record(string(), unknown()).nullable(),
14480
+ error: string().nullable(),
14481
+ /** Why it is `blocked` or `held` (`dependency-failed`, `snapshot-refused`, …). */
14482
+ reason: string().nullable(),
14483
+ nodeId: string().nullable(),
14484
+ bootId: string().nullable()
14485
+ });
14486
+ /** A catalogued migration that is not applied, with where it stands. */
14487
+ var MigrationPendingEntrySchema = object({
14488
+ meta: MigrationMetaSchema,
14489
+ database: DataStoreDatabaseSchema,
14490
+ state: _enum([
14491
+ "pending",
14492
+ "failed",
14493
+ "blocked",
14494
+ "held"
14495
+ ]),
14496
+ reason: string().nullable()
14497
+ });
14498
+ /** Rows per table before and after a dry run, and what it would have changed. */
14499
+ var MigrationDryRunResultSchema = object({
14500
+ id: string(),
14501
+ ok: boolean(),
14502
+ error: string().nullable(),
14503
+ rowsChanged: number().int().nonnegative(),
14504
+ tables: array(object({
14505
+ table: string(),
14506
+ before: number().int().nonnegative(),
14507
+ after: number().int().nonnegative()
14508
+ }))
14509
+ });
14510
+ /** One of an owner's migrations, as the engine tells the owner to treat it this boot. */
14511
+ var MigrationOwnerStepSchema = object({
14512
+ id: MigrationIdSchema,
14513
+ action: _enum(["run", "skip"]),
14514
+ /** The ledger state the engine found: `pending` has no row yet. */
14515
+ state: union([literal("pending"), MigrationStatusSchema])
14516
+ });
14517
+ /**
13673
14518
  * The trainer contract — what the hub hands the cloud trainer and what it
13674
14519
  * gets back (cloud training, D805+).
13675
14520
  *
@@ -17705,6 +18550,41 @@ var ACTUATOR_CAP_VOCABULARY = {
17705
18550
  icon: "droplets"
17706
18551
  }]
17707
18552
  },
18553
+ "power-source": {
18554
+ scope: "device",
18555
+ noun: t("Power source", "Alimentazione"),
18556
+ actions: [
18557
+ {
18558
+ id: "powerSource.on",
18559
+ method: "powerOn",
18560
+ label: t("Power on", "Dai corrente"),
18561
+ phrase: t("Power on {device}", "Dai corrente a {device}"),
18562
+ icon: "power"
18563
+ },
18564
+ {
18565
+ id: "powerSource.off",
18566
+ method: "powerOff",
18567
+ label: t("Power off", "Togli corrente"),
18568
+ phrase: t("Cut the power of {device}", "Togli la corrente a {device}"),
18569
+ icon: "power-off"
18570
+ },
18571
+ {
18572
+ id: "powerSource.forceRestart",
18573
+ method: "forceRestart",
18574
+ label: t("Force restart", "Riavvio forzato"),
18575
+ phrase: t("Force restart {device} by cutting its power", "Riavvia {device} togliendo la corrente"),
18576
+ icon: "refresh-cw"
18577
+ }
18578
+ ],
18579
+ state: [{
18580
+ kind: "boolean",
18581
+ field: "switchOn",
18582
+ label: t("Power switch on", "Presa accesa"),
18583
+ whenTrue: t("When the power of {device} comes on", "Quando arriva la corrente a {device}"),
18584
+ whenFalse: t("When the power of {device} is cut", "Quando manca la corrente a {device}"),
18585
+ icon: "power"
18586
+ }]
18587
+ },
17708
18588
  "lock-control": {
17709
18589
  scope: "device",
17710
18590
  noun: t("Lock", "Serratura"),
@@ -17786,6 +18666,11 @@ var ACTUATOR_CAP_VOCABULARY = {
17786
18666
  value: "jammed",
17787
18667
  label: t("Jammed", "Inceppata"),
17788
18668
  phrase: t("When {device} jams", "Quando {device} si inceppa")
18669
+ },
18670
+ {
18671
+ value: "door-open",
18672
+ label: t("Door open", "Porta aperta"),
18673
+ phrase: t("When the door of {device} opens", "Quando la porta di {device} si apre")
17789
18674
  }
17790
18675
  ],
17791
18676
  icon: "lock"
@@ -18175,7 +19060,7 @@ var ACTUATOR_CAP_VOCABULARY = {
18175
19060
  method: "setDirection",
18176
19061
  label: t("Spin forward", "Rotazione avanti"),
18177
19062
  phrase: t("Spin {device} forward", "Fai girare {device} in avanti"),
18178
- icon: "rotate-cw",
19063
+ icon: "refresh-cw",
18179
19064
  effect: {
18180
19065
  field: "direction",
18181
19066
  value: "forward"
@@ -21253,6 +22138,7 @@ var SYSTEM_CAP_VOCABULARY = {
21253
22138
  "login-method": system(IDENTITY),
21254
22139
  "mesh-network": system(NETWORK),
21255
22140
  "metrics-provider": system(CLUSTER),
22141
+ migrations: system(INTERNAL),
21256
22142
  "model-convert": system(ML),
21257
22143
  "model-distributor": system(ML),
21258
22144
  "mqtt-broker": system(NETWORK),
@@ -21276,6 +22162,7 @@ var SYSTEM_CAP_VOCABULARY = {
21276
22162
  records: system(INTERNAL),
21277
22163
  remote: system("the per-device remote setup — authored in the Customize remote editor, sent through the remote UI"),
21278
22164
  "remote-layout-components": system("the palette of the remote template editor — read-only data, never actuated"),
22165
+ schedulers: system("time schedulers — authored on their own page, not device actions"),
21279
22166
  "server-management": system(CLUSTER),
21280
22167
  "settings-store": system(INTERNAL),
21281
22168
  "smtp-provider": system(INTERNAL),
@@ -42399,6 +43286,122 @@ var AutomationWhenSchema = lazy(() => discriminatedUnion("kind", [
42399
43286
  StationaryPresentLeafSchema,
42400
43287
  ExpressionLeafSchema
42401
43288
  ]));
43289
+ var EVENT_KINDS = new Set([
43290
+ "device-becomes",
43291
+ "device-event",
43292
+ "schedule",
43293
+ "manual",
43294
+ "interval",
43295
+ "detection",
43296
+ "stationary"
43297
+ ]);
43298
+ function isEventLeaf(node) {
43299
+ return EVENT_KINDS.has(node.kind);
43300
+ }
43301
+ function isLeaf(node) {
43302
+ return node.kind !== "all" && node.kind !== "any" && node.kind !== "not";
43303
+ }
43304
+ /** Depth of a tree, root container = 1. A bare leaf is depth 1. */
43305
+ function whenDepth(node) {
43306
+ if (isLeaf(node)) return 1;
43307
+ if (node.kind === "not") return 1 + whenDepth(node.child);
43308
+ let deepest = 0;
43309
+ for (const child of node.children) deepest = Math.max(deepest, whenDepth(child));
43310
+ return 1 + deepest;
43311
+ }
43312
+ /** Leaves only — containers do not count toward the leaf budget. */
43313
+ function countWhenLeaves(node) {
43314
+ if (isLeaf(node)) return 1;
43315
+ if (node.kind === "not") return countWhenLeaves(node.child);
43316
+ let total = 0;
43317
+ for (const child of node.children) total += countWhenLeaves(child);
43318
+ return total;
43319
+ }
43320
+ /** Every leaf with its path (`when`, `when.0`, `when.0.1`, `when.0` under a `not`). */
43321
+ function forEachLeaf(node, visit, path = "when") {
43322
+ if (isLeaf(node)) {
43323
+ visit(node, path);
43324
+ return;
43325
+ }
43326
+ if (node.kind === "not") {
43327
+ forEachLeaf(node.child, visit, `${path}.0`);
43328
+ return;
43329
+ }
43330
+ node.children.forEach((child, i) => {
43331
+ forEachLeaf(child, visit, `${path}.${i}`);
43332
+ });
43333
+ }
43334
+ function hasEventLeaves(node) {
43335
+ let found = false;
43336
+ forEachLeaf(node, (leaf) => {
43337
+ if (isEventLeaf(leaf)) found = true;
43338
+ });
43339
+ return found;
43340
+ }
43341
+ function hasEmptyContainer(node) {
43342
+ if (isLeaf(node)) return false;
43343
+ if (node.kind === "not") return hasEmptyContainer(node.child);
43344
+ if (node.children.length === 0) return true;
43345
+ return node.children.some(hasEmptyContainer);
43346
+ }
43347
+ var LEAF_WORDS = {
43348
+ "device-becomes": "a device value that becomes",
43349
+ "device-event": "a device event",
43350
+ schedule: "a schedule",
43351
+ manual: "a manual run",
43352
+ interval: "an interval",
43353
+ detection: "a detection",
43354
+ stationary: "a parked object appearing or leaving"
43355
+ };
43356
+ function say(kind) {
43357
+ return LEAF_WORDS[kind] ?? kind;
43358
+ }
43359
+ /**
43360
+ * The cheapest set of event leaves that makes `node` true, with negation pushed
43361
+ * down to the leaves first (De Morgan), so `not(any(A, B))` and `all(not A,
43362
+ * not B)` are the same shape to the check. A negated event is its own error: an
43363
+ * event is an instant, "the event did not occur" is true at every other moment.
43364
+ */
43365
+ function eventNeed(node, path, negated) {
43366
+ if (isLeaf(node)) {
43367
+ if (!isEventLeaf(node)) return { paths: [] };
43368
+ if (negated) return { error: `"not" is applied to ${say(node.kind)} (${path}) — an event is an instant, so "it did not happen" is true at every other moment and can never start a run. Negate a state ("is") instead, or remove the "not"` };
43369
+ return { paths: [{
43370
+ path,
43371
+ kind: node.kind
43372
+ }] };
43373
+ }
43374
+ if (node.kind === "not") return eventNeed(node.child, `${path}.0`, !negated);
43375
+ const conjunctive = node.kind === "all" !== negated;
43376
+ const parts = [];
43377
+ for (let i = 0; i < node.children.length; i += 1) {
43378
+ const child = node.children[i];
43379
+ if (child === void 0) continue;
43380
+ const need = eventNeed(child, `${path}.${i}`, negated);
43381
+ if ("error" in need) return need;
43382
+ parts.push(need);
43383
+ }
43384
+ if (conjunctive) return { paths: parts.flatMap((p) => p.paths) };
43385
+ let best;
43386
+ for (const part of parts) if (best === void 0 || part.paths.length < best.paths.length) best = part;
43387
+ return best ?? { paths: [] };
43388
+ }
43389
+ /**
43390
+ * Author-time bounds and sense check of a `when` tree. Returns `null` when it
43391
+ * is legal, else a message the editor shows verbatim.
43392
+ */
43393
+ function validateWhenTree(when) {
43394
+ if (hasEmptyContainer(when)) return "a group is empty — group existing rows instead of leaving a container to fill";
43395
+ const depth = whenDepth(when);
43396
+ if (depth > 4) return `the rules nest ${depth} levels deep — the limit is 4. "not (any of …)" is the same as "all of (not …)", which is one level shallower`;
43397
+ const leaves = countWhenLeaves(when);
43398
+ if (leaves > 32) return `the rules use ${leaves} rows — the limit is 32`;
43399
+ const need = eventNeed(when, "when", false);
43400
+ if ("error" in need) return need.error;
43401
+ const [first, second] = need.paths;
43402
+ if (first !== void 0 && second !== void 0) return `${say(first.kind)} (${first.path}) and ${say(second.kind)} (${second.path}) would both have to happen at the same instant, and an event happens at one moment — this can never run. Use "any of" to start on either, or turn one of them into a state ("is") that must already hold`;
43403
+ return null;
43404
+ }
42402
43405
  /**
42403
43406
  * The recipe shape before the single `when` tree: a flat list of triggers
42404
43407
  * (OR'd, each an instant) and an optional condition tree. Stored recipes —
@@ -42754,6 +43757,187 @@ function convertLegacyRecipe(legacy) {
42754
43757
  var AutomationRecipeSchema = union([CurrentAutomationRecipeSchema, LegacyAutomationRecipeSchema.transform(convertLegacyRecipe)]);
42755
43758
  new Set(Object.values(COCO_TO_MACRO.mapping));
42756
43759
  /**
43760
+ * Schedulers (D983) — an entity that performs actions over the day.
43761
+ *
43762
+ * A scheduler has profiles; a profile has weekdays and a timeline of POINTS.
43763
+ * A point is an `edge` (run once at its time), a `hold` (from this point until
43764
+ * the next point of the profile, keep the steps' effects true) or a `release`
43765
+ * (end the current hold without acting). The scheduler AND each profile carry
43766
+ * a manual `enabled` flag and an optional `activeWhen`: a `when` STATE tree
43767
+ * (facts only — an event leaf is an instant and cannot be "in force").
43768
+ *
43769
+ * This file is the data half: the schemas, and the author-time rules that
43770
+ * decide whether a scheduler can be saved at all (a hold needs an action that
43771
+ * leads to a state; a sun point needs the site location). It holds no runtime.
43772
+ */
43773
+ /** A `when` tree that is a STATE: no event leaf, and legal as a tree. */
43774
+ var SchedulerStateTreeSchema = AutomationWhenSchema.superRefine((when, ctx) => {
43775
+ if (hasEventLeaves(when)) {
43776
+ ctx.addIssue({
43777
+ code: "custom",
43778
+ message: "a scheduler condition is a state — an event (\"becomes\", \"detects\", \"at a time\") is an instant and cannot be in force"
43779
+ });
43780
+ return;
43781
+ }
43782
+ const problem = validateWhenTree(when);
43783
+ if (problem !== null) ctx.addIssue({
43784
+ code: "custom",
43785
+ message: problem
43786
+ });
43787
+ });
43788
+ /** A point's moment: a clock time or a sun event. A one-shot date is not a position on a day. */
43789
+ var SchedulerTimeSpecSchema = TimeSpecSchema.refine((spec) => spec.kind !== "date", { message: "a scheduler point is a clock time or a sun event — a one-shot date has no place on a timeline" });
43790
+ var SchedulerPointModeSchema = _enum([
43791
+ "edge",
43792
+ "hold",
43793
+ "release"
43794
+ ]);
43795
+ /** What a hold does when someone changes the device by hand. */
43796
+ var HoldOverrideSchema = discriminatedUnion("mode", [object({ mode: literal("respect") }), object({
43797
+ mode: literal("reassert"),
43798
+ /** How long the manual state is left alone before the hold puts its own back. */
43799
+ graceMs: number().int().min(0).max(24 * 36e5)
43800
+ })]);
43801
+ /** A missed edge up to this late still runs. */
43802
+ var DEFAULT_MAX_LATE_MS = 10 * 6e4;
43803
+ var SchedulerPointSchema = object({
43804
+ id: string().min(1).max(64),
43805
+ at: SchedulerTimeSpecSchema,
43806
+ mode: SchedulerPointModeSchema,
43807
+ /** The actions of an `edge` or `hold`; a `release` has none. */
43808
+ steps: array(ActionStepSchema).max(20).default([]),
43809
+ /** Checked when the point runs (an edge) or is applied (a hold). */
43810
+ conditions: SchedulerStateTreeSchema.optional(),
43811
+ /** Hold only; absent means `respect`. */
43812
+ override: HoldOverrideSchema.optional(),
43813
+ /** Hold only: re-applications per window; absent means {@link DEFAULT_MAX_HOLD_ATTEMPTS}. */
43814
+ maxHoldAttempts: number().int().min(1).max(20).optional()
43815
+ });
43816
+ var SchedulerProfileSchema = object({
43817
+ id: string().min(1).max(64),
43818
+ name: string().min(1).max(120),
43819
+ enabled: boolean().default(true),
43820
+ activeWhen: SchedulerStateTreeSchema.optional(),
43821
+ days: array(WeekdayKeySchema).min(1).max(7),
43822
+ points: array(SchedulerPointSchema).max(50)
43823
+ });
43824
+ /** What an operator writes. */
43825
+ var SchedulerInputSchema = object({
43826
+ name: string().min(1).max(120),
43827
+ enabled: boolean().default(true),
43828
+ activeWhen: SchedulerStateTreeSchema.optional(),
43829
+ /** Higher wins when two holds want the same device in different states. */
43830
+ priority: number().int().min(0).max(100).default(0),
43831
+ maxLateMs: number().int().min(1e3).max(24 * 36e5).default(DEFAULT_MAX_LATE_MS),
43832
+ profiles: array(SchedulerProfileSchema).max(20)
43833
+ });
43834
+ var SchedulerSchema = SchedulerInputSchema.extend({
43835
+ id: string().min(1),
43836
+ createdAt: number(),
43837
+ updatedAt: number()
43838
+ });
43839
+ /** Every outcome a scheduler reports to the runs ledger (`AutomationRunTrace.scheduler`). */
43840
+ var SchedulerOutcomeSchema = _enum([
43841
+ "done",
43842
+ "missed",
43843
+ "inactive",
43844
+ "condition-false",
43845
+ "failed",
43846
+ "held",
43847
+ "gave-up",
43848
+ "overridden",
43849
+ "suspended-manual"
43850
+ ]);
43851
+ _enum([
43852
+ "not-found",
43853
+ "invalid-scheduler",
43854
+ "invalid-date"
43855
+ ]);
43856
+ var LOCAL_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}$/;
43857
+ /** One point of the day, placed. `at` is `null` when the sun never crosses that day. */
43858
+ var SchedulerPreviewPointSchema = object({
43859
+ pointId: string(),
43860
+ mode: SchedulerPointModeSchema,
43861
+ at: number().nullable(),
43862
+ /** A hold: when its window ends (the next point, else the end of the day). */
43863
+ until: number().nullable(),
43864
+ /** The point's steps are held on a different scheduler's claim at this time — see `conflicts`. */
43865
+ stepCount: number().int()
43866
+ });
43867
+ var SchedulerPreviewSchema = object({
43868
+ schedulerId: string(),
43869
+ /** The site-local day previewed. */
43870
+ date: string().regex(LOCAL_DATE_PATTERN),
43871
+ timeZone: string(),
43872
+ /** Why no profile is in force, or `null` when one is. */
43873
+ idleReason: string().nullable(),
43874
+ /** The profile in force that day: first in list order whose rules hold. */
43875
+ profileId: string().nullable(),
43876
+ points: array(SchedulerPreviewPointSchema)
43877
+ });
43878
+ /** Two holds that want the same device in different states at the same time. */
43879
+ var SchedulerConflictSchema = object({
43880
+ deviceId: number().int(),
43881
+ cap: string(),
43882
+ field: string(),
43883
+ mine: object({
43884
+ profileId: string(),
43885
+ pointId: string(),
43886
+ value: union([
43887
+ string(),
43888
+ number(),
43889
+ boolean()
43890
+ ]),
43891
+ priority: number().int()
43892
+ }),
43893
+ other: object({
43894
+ schedulerId: string(),
43895
+ schedulerName: string(),
43896
+ profileId: string(),
43897
+ pointId: string(),
43898
+ value: union([
43899
+ string(),
43900
+ number(),
43901
+ boolean()
43902
+ ]),
43903
+ priority: number().int()
43904
+ }),
43905
+ /** The overlapping stretch, as site-local minutes of the day. */
43906
+ fromMinute: number().int(),
43907
+ toMinute: number().int(),
43908
+ /** `mine` | `other` | `tie` — who holds when it happens (a tie: the one that started first). */
43909
+ winner: _enum([
43910
+ "mine",
43911
+ "other",
43912
+ "tie"
43913
+ ])
43914
+ });
43915
+ var HoldTargetStatusSchema = _enum([
43916
+ "pending",
43917
+ "held",
43918
+ "suspended-manual",
43919
+ "gave-up",
43920
+ "overridden"
43921
+ ]);
43922
+ var SchedulerStatusSchema = object({
43923
+ schedulerId: string(),
43924
+ profileId: string().nullable(),
43925
+ /** The hold in force right now, with where each target stands. */
43926
+ hold: object({
43927
+ pointId: string(),
43928
+ until: number(),
43929
+ /** `true` while the scheduler or profile `activeWhen` is false. */
43930
+ suspendedInactive: boolean(),
43931
+ targets: array(object({
43932
+ deviceId: number().int(),
43933
+ cap: string(),
43934
+ field: string(),
43935
+ status: HoldTargetStatusSchema,
43936
+ attempts: number().int()
43937
+ }))
43938
+ }).nullable()
43939
+ });
43940
+ /**
42757
43941
  * Run traces — what an automation (or, later, an Action run) DID, node by node,
42758
43942
  * so the editor can paint every block green or red with its reason.
42759
43943
  *
@@ -42889,7 +44073,13 @@ var AutomationRunTraceSchema = object({
42889
44073
  /** How many identical consecutive skips this trace stands for (>= 1). */
42890
44074
  repeat: number().int().min(1),
42891
44075
  /** When the latest of those repeats happened. */
42892
- lastAt: number()
44076
+ lastAt: number(),
44077
+ /**
44078
+ * A scheduler point's own outcome (D983). `outcome` carries the closest
44079
+ * automation outcome so a reader that does not know schedulers still reads
44080
+ * the trace; this field says exactly what happened.
44081
+ */
44082
+ scheduler: SchedulerOutcomeSchema.optional()
42893
44083
  });
42894
44084
  /**
42895
44085
  * What the hub's `runs` ledger holds for one owner (`automation:<blockId>` or
@@ -43514,7 +44704,7 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
43514
44704
  * rebuilds without manual reload.
43515
44705
  *
43516
44706
  * The hub-local builtin `addon-pages-aggregator` (see
43517
- * `@camstack/system/builtins/addon-pages-aggregator`) registers the
44707
+ * `@camstack/server/system/builtins/addon-pages-aggregator`) registers the
43518
44708
  * provider. Splitting the public aggregator from the raw collection
43519
44709
  * keeps both ends in codegen — there's no hand-written
43520
44710
  * `addon-pages.router.ts` wrapper anymore.
@@ -43640,6 +44830,10 @@ method(object({
43640
44830
  }), method(object({
43641
44831
  addonId: string(),
43642
44832
  nodeId: string().optional()
44833
+ }), SettingsSchemaWithValuesSchema.nullable()), method(object({
44834
+ addonId: string(),
44835
+ nodeId: string().optional(),
44836
+ instance: IntegrationInstanceRefSchema
43643
44837
  }), SettingsSchemaWithValuesSchema.nullable()), method(object({
43644
44838
  addonId: string(),
43645
44839
  nodeId: string().optional()
@@ -43829,7 +45023,7 @@ listWidgets: method(_void(), array(WidgetMetadataSchema).readonly(), { auth: "ad
43829
45023
  * manual reload — same scheme used by `addon-pages`.
43830
45024
  *
43831
45025
  * The hub-local builtin `addon-widgets-aggregator` (see
43832
- * `@camstack/system/builtins/addon-widgets-aggregator`) registers the
45026
+ * `@camstack/server/system/builtins/addon-widgets-aggregator`) registers the
43833
45027
  * provider. Splitting the public aggregator from the raw collection
43834
45028
  * keeps both ends in codegen — there's no hand-written wrapper.
43835
45029
  */
@@ -44190,329 +45384,6 @@ method(object({
44190
45384
  password: string()
44191
45385
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
44192
45386
  /**
44193
- * Query filter for settings-store collections.
44194
- */
44195
- var QueryFilterSchema = object({
44196
- where: record(string(), unknown()).optional(),
44197
- whereIn: record(string(), array(unknown())).optional(),
44198
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
44199
- /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
44200
- whereNot: record(string(), unknown()).optional(),
44201
- /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
44202
- * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
44203
- whereNotIn: record(string(), array(unknown())).optional(),
44204
- orderBy: object({
44205
- field: string(),
44206
- direction: _enum(["asc", "desc"])
44207
- }).optional(),
44208
- limit: number().optional(),
44209
- offset: number().optional()
44210
- });
44211
- /**
44212
- * The predicate half of a filter, for BULK MUTATIONS.
44213
- *
44214
- * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
44215
- * meaning for a statement that rewrites a set, and accepting them would invite
44216
- * a caller to believe `limit` bounds the damage. Every field is optional here
44217
- * only so the shape stays composable — the implementation REJECTS a filter
44218
- * that compiles to no predicate, because that is the whole collection.
44219
- */
44220
- var MutationFilterSchema = object({
44221
- where: record(string(), unknown()).optional(),
44222
- whereIn: record(string(), array(unknown())).optional(),
44223
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
44224
- whereNot: record(string(), unknown()).optional(),
44225
- whereNotIn: record(string(), array(unknown())).optional()
44226
- });
44227
- /**
44228
- * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
44229
- *
44230
- * `as` names the slot in the result, so the SAME column may be asked twice with
44231
- * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
44232
- * a `Record<column, op>` shape could not express.
44233
- */
44234
- var AggregateFieldSchema = object({
44235
- /** Result key. */
44236
- as: string().min(1),
44237
- /** Column to aggregate. Must be a real column of a declared collection. */
44238
- field: string().min(1),
44239
- op: _enum([
44240
- "sum",
44241
- "min",
44242
- "max"
44243
- ])
44244
- });
44245
- /**
44246
- * `COUNT(*)` plus one number per requested field.
44247
- *
44248
- * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
44249
- * that really is 0 are different facts, and an accounting caller that renders
44250
- * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
44251
- */
44252
- var AggregateResultSchema = object({
44253
- count: number().int(),
44254
- values: record(string(), number().nullable())
44255
- });
44256
- /** A single stored record: `{ id, data }`. */
44257
- var SettingsRecordSchema = object({
44258
- id: string(),
44259
- data: record(string(), unknown())
44260
- });
44261
- /**
44262
- * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
44263
- * optional.
44264
- *
44265
- * A separate schema rather than loosening the shared one: every other method
44266
- * on this cap addresses a row BY its id, and making that field optional
44267
- * everywhere would turn a forgotten key into a silently generated one on
44268
- * `update` and `delete` as well.
44269
- */
44270
- var BulkRecordSchema = object({
44271
- id: string().optional(),
44272
- data: record(string(), unknown())
44273
- });
44274
- /**
44275
- * The database FILES one `sqlite-settings` engine process owns (D851).
44276
- * Closed on purpose: a file is a thing the engine opens, backs up and gives a
44277
- * serving thread, so a new one is a code change, never a string a caller
44278
- * invents. `settings` is `camstack.db` and the default for every collection
44279
- * that names no file.
44280
- */
44281
- var DataStoreDatabaseSchema = _enum([
44282
- "settings",
44283
- "events",
44284
- "history"
44285
- ]);
44286
- DataStoreDatabaseSchema.options;
44287
- /**
44288
- * One write inside a {@link settingsStoreCapability.methods.transact} call.
44289
- * The three that a "replace this set, bump that revision" write needs, plus
44290
- * the batched upsert a rollup writer needs.
44291
- */
44292
- var TransactOpSchema = discriminatedUnion("op", [
44293
- object({
44294
- op: literal("deleteWhere"),
44295
- collection: string(),
44296
- filter: MutationFilterSchema
44297
- }),
44298
- object({
44299
- op: literal("insertMany"),
44300
- collection: string(),
44301
- records: array(BulkRecordSchema).readonly()
44302
- }),
44303
- object({
44304
- op: literal("update"),
44305
- collection: string(),
44306
- id: string(),
44307
- data: record(string(), unknown())
44308
- }),
44309
- object({
44310
- op: literal("upsertMany"),
44311
- collection: string(),
44312
- conflict: array(string()).readonly(),
44313
- records: array(BulkRecordSchema).readonly()
44314
- })
44315
- ]);
44316
- /**
44317
- * The precondition of a transaction: the row `key` of `collection` must hold
44318
- * `equals` in `column`, read INSIDE the transaction.
44319
- */
44320
- var TransactGuardSchema = object({
44321
- collection: string(),
44322
- key: string(),
44323
- column: string(),
44324
- equals: unknown()
44325
- });
44326
- /**
44327
- * Column declaration for a structured (SQL-backed) collection.
44328
- *
44329
- * Logical types — the backend translates each to the matching SQLite
44330
- * storage class and handles per-type marshaling:
44331
- * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
44332
- * - `JSON` — TEXT under the hood; serialised on write, parsed on read
44333
- * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
44334
- */
44335
- var CollectionColumnSchema = object({
44336
- name: string(),
44337
- type: _enum([
44338
- "TEXT",
44339
- "INTEGER",
44340
- "REAL",
44341
- "JSON",
44342
- "BOOLEAN"
44343
- ]),
44344
- primaryKey: boolean().optional(),
44345
- notNull: boolean().optional(),
44346
- unique: boolean().optional(),
44347
- /**
44348
- * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
44349
- * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
44350
- * collection surface simply cannot express their existing tables — which is
44351
- * why they were still on the uncapped `table*` API.
44352
- */
44353
- defaultValue: union([
44354
- string(),
44355
- number(),
44356
- boolean()
44357
- ]).optional()
44358
- });
44359
- var CollectionIndexSchema = object({
44360
- name: string(),
44361
- columns: array(string()).readonly(),
44362
- unique: boolean().optional()
44363
- });
44364
- /**
44365
- * The input of `declareCollection` — ONE schema for the door and for the
44366
- * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
44367
- * the payload untouched).
44368
- *
44369
- * - `database` — the file the collection lives in (D851). Absent means
44370
- * `settings`. A declaration naming a file other than the one the table is
44371
- * already in is REFUSED by the engine, never moved.
44372
- * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
44373
- * as `primaryKey: true` on that column. A composite-keyed collection has no
44374
- * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
44375
- * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
44376
- * address one row by id are refused.
44377
- * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
44378
- */
44379
- var DeclareCollectionInputSchema = object({
44380
- namespace: string().optional(),
44381
- collection: string(),
44382
- columns: array(CollectionColumnSchema).readonly(),
44383
- indexes: array(CollectionIndexSchema).readonly().optional(),
44384
- database: DataStoreDatabaseSchema.optional(),
44385
- primaryKey: array(string()).readonly().optional(),
44386
- withoutRowid: boolean().optional()
44387
- });
44388
- method(object({
44389
- namespace: string().optional(),
44390
- collection: string(),
44391
- key: string()
44392
- }), unknown(), { auth: "admin" }), method(object({
44393
- namespace: string().optional(),
44394
- collection: string(),
44395
- key: string(),
44396
- value: unknown()
44397
- }), _void(), {
44398
- kind: "mutation",
44399
- auth: "admin"
44400
- }), method(object({
44401
- namespace: string().optional(),
44402
- collection: string(),
44403
- filter: QueryFilterSchema.optional(),
44404
- columns: array(string()).readonly().optional()
44405
- }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
44406
- namespace: string().optional(),
44407
- collection: string(),
44408
- /** Declared columns identifying the slot; must be covered by a UNIQUE
44409
- * index on the collection. Never empty. */
44410
- conflict: array(string()).readonly(),
44411
- record: BulkRecordSchema
44412
- }), object({
44413
- /** The id the row HAS: assigned by SQLite on an insert, or the existing
44414
- * row's own id on an update. */
44415
- id: union([string(), number()]) }), {
44416
- kind: "mutation",
44417
- auth: "admin"
44418
- }), method(object({
44419
- namespace: string().optional(),
44420
- collection: string(),
44421
- conflict: array(string()).readonly(),
44422
- records: array(BulkRecordSchema).readonly()
44423
- }), object({ upserted: number().int() }), {
44424
- kind: "mutation",
44425
- auth: "admin"
44426
- }), method(object({
44427
- namespace: string().optional(),
44428
- collection: string(),
44429
- record: object({
44430
- id: string().optional(),
44431
- data: record(string(), unknown())
44432
- })
44433
- }), object({
44434
- /**
44435
- * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
44436
- * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
44437
- * primary key — which is the only place an auto key is knowable.
44438
- */
44439
- id: union([string(), number()]) }), {
44440
- kind: "mutation",
44441
- auth: "admin"
44442
- }), method(object({
44443
- namespace: string().optional(),
44444
- collection: string(),
44445
- records: array(BulkRecordSchema).readonly()
44446
- }), object({ inserted: number().int() }), {
44447
- kind: "mutation",
44448
- auth: "admin"
44449
- }), method(object({
44450
- namespace: string().optional(),
44451
- guard: TransactGuardSchema.optional(),
44452
- ops: array(TransactOpSchema).readonly()
44453
- }), object({
44454
- applied: boolean(),
44455
- current: unknown().optional()
44456
- }), {
44457
- kind: "mutation",
44458
- auth: "admin"
44459
- }), method(object({
44460
- namespace: string().optional(),
44461
- collection: string(),
44462
- id: string(),
44463
- data: record(string(), unknown())
44464
- }), _void(), {
44465
- kind: "mutation",
44466
- auth: "admin"
44467
- }), method(object({
44468
- namespace: string().optional(),
44469
- collection: string(),
44470
- key: string()
44471
- }), _void(), {
44472
- kind: "mutation",
44473
- auth: "admin"
44474
- }), method(object({
44475
- namespace: string().optional(),
44476
- collection: string(),
44477
- filter: MutationFilterSchema
44478
- }), object({ deleted: number().int() }), {
44479
- kind: "mutation",
44480
- auth: "admin"
44481
- }), method(object({
44482
- namespace: string().optional(),
44483
- collection: string(),
44484
- filter: MutationFilterSchema,
44485
- data: record(string(), unknown())
44486
- }), object({ updated: number().int() }), {
44487
- kind: "mutation",
44488
- auth: "admin"
44489
- }), method(object({
44490
- namespace: string().optional(),
44491
- collection: string(),
44492
- filter: QueryFilterSchema.optional()
44493
- }), number(), { auth: "admin" }), method(object({
44494
- namespace: string().optional(),
44495
- collection: string(),
44496
- fields: array(AggregateFieldSchema).readonly(),
44497
- filter: QueryFilterSchema.optional()
44498
- }), AggregateResultSchema, { auth: "admin" }), method(object({
44499
- namespace: string().optional(),
44500
- collection: string(),
44501
- field: string(),
44502
- bucketSize: number().int().positive(),
44503
- origin: number().int(),
44504
- filter: QueryFilterSchema.optional()
44505
- }), array(object({
44506
- bucket: number().int(),
44507
- count: number().int()
44508
- })).readonly(), { auth: "admin" }), method(object({
44509
- namespace: string().optional(),
44510
- collection: string()
44511
- }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
44512
- kind: "mutation",
44513
- auth: "admin"
44514
- });
44515
- /**
44516
45387
  * How long one snapshot may take end to end: the pinned copy of the whole
44517
45388
  * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
44518
45389
  * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
@@ -46430,6 +47301,7 @@ method(object({
46430
47301
  kind: "mutation",
46431
47302
  auth: "admin"
46432
47303
  }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
47304
+ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Lock, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Gateway, DeviceType.Hub, DeviceType.Camera, DeviceType.Sensor, DeviceType.MediaPlayer, DeviceType.Vacuum, DeviceType.Generic, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.PetFountain, DeviceType.Fan, DeviceType.Humidifier, DeviceType.Climate, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
46433
47305
  var NUMBER = {
46434
47306
  kind: "fixed",
46435
47307
  result: ["number"]
@@ -47447,6 +48319,57 @@ id: union([string(), number()]) }), {
47447
48319
  kind: "mutation",
47448
48320
  auth: "admin"
47449
48321
  });
48322
+ method(object({}), object({ entries: array(MigrationLedgerEntrySchema) }), { auth: "admin" }), method(object({ owner: string().optional() }), object({ pending: array(MigrationPendingEntrySchema) }), { auth: "admin" }), method(object({ id: string().min(1) }), MigrationDryRunResultSchema, { auth: "admin" }), method(object({
48323
+ id: string().min(1),
48324
+ error: string().min(1)
48325
+ }), MigrationLedgerEntrySchema, {
48326
+ kind: "mutation",
48327
+ auth: "admin"
48328
+ }), method(object({ id: string().min(1) }), object({ entries: array(MigrationLedgerEntrySchema) }), {
48329
+ kind: "mutation",
48330
+ auth: "admin"
48331
+ }), method(object({
48332
+ database: DataStoreDatabaseSchema,
48333
+ snapshotId: string().min(1)
48334
+ }), object({
48335
+ scheduled: boolean(),
48336
+ held: array(string()),
48337
+ restartRequired: boolean()
48338
+ }), {
48339
+ kind: "mutation",
48340
+ auth: "admin"
48341
+ }), method(object({
48342
+ owner: string().min(1),
48343
+ metas: array(MigrationMetaSchema)
48344
+ }), object({
48345
+ bootId: string(),
48346
+ snapshot: _enum([
48347
+ "none",
48348
+ "written",
48349
+ "refused"
48350
+ ]),
48351
+ steps: array(MigrationOwnerStepSchema)
48352
+ }), {
48353
+ kind: "mutation",
48354
+ auth: "admin"
48355
+ }), method(object({ id: string().min(1) }), object({
48356
+ proceed: boolean(),
48357
+ state: union([literal("pending"), MigrationStatusSchema]),
48358
+ reason: string().nullable()
48359
+ }), {
48360
+ kind: "mutation",
48361
+ auth: "admin"
48362
+ }), method(object({
48363
+ id: string().min(1),
48364
+ result: MigrationOwnerResultSchema
48365
+ }), object({
48366
+ status: MigrationStatusSchema,
48367
+ error: string().nullable(),
48368
+ rowsChanged: number().int().nonnegative()
48369
+ }), {
48370
+ kind: "mutation",
48371
+ auth: "admin"
48372
+ });
47450
48373
  /**
47451
48374
  * records - an addon's own facts and documents (D881): ledgers, audit rows,
47452
48375
  * config documents, mirrors. The third storage API next to `ctx.settings`
@@ -64113,7 +65036,18 @@ var ServerUpdateActionResultSchema = object({
64113
65036
  * store (D877), present when the pre-flight refused the update
64114
65037
  * (`reason: 'addon-would-refuse'`). Optional for version skew.
64115
65038
  */
64116
- blockers: array(UpdateBlockerSchema).optional()
65039
+ blockers: array(UpdateBlockerSchema).optional(),
65040
+ /**
65041
+ * The minimums the target server declares and this node does not meet
65042
+ * (D989), present when the update was refused with
65043
+ * `reason: 'requirements-unmet'`. Each names the versions on both sides.
65044
+ */
65045
+ unmet: array(UnmetRequirementSchema).optional(),
65046
+ /**
65047
+ * The addons this node has below the minimum the staged server declares —
65048
+ * the ones to update WITH the server. Present on an accepted update.
65049
+ */
65050
+ requiredAddonUpdates: array(RequiredAddonUpdateSchema).optional()
64117
65051
  });
64118
65052
  var LaunchOverridesSchema = object({
64119
65053
  /** The persisted heap ceiling in MB, or `null` when none is set. */
@@ -66691,7 +67625,9 @@ var PackageUpdateSchema = object({
66691
67625
  latestVersion: string(),
66692
67626
  category: _enum(["addon", "core"]),
66693
67627
  requiresRestart: boolean(),
66694
- isSystem: boolean()
67628
+ isSystem: boolean(),
67629
+ /** Held: what the newer version needs and this node does not meet (D989). */
67630
+ blockedBy: array(UnmetRequirementSchema).readonly().optional()
66695
67631
  });
66696
67632
  var PackageVersionInfoSchema = object({
66697
67633
  version: string(),
@@ -67251,6 +68187,43 @@ method(object({}), array(ActionEntrySchema), { auth: "admin" }), method(object({
67251
68187
  auth: "admin"
67252
68188
  });
67253
68189
  /**
68190
+ * schedulers — timelines of edge and hold points that perform actions over the
68191
+ * day (D983). Provided by the core-blocks builtin on the hub, beside `actions`:
68192
+ * the same runs ledger (owner `scheduler:<id>`), the same target resolver, the
68193
+ * same step runner.
68194
+ *
68195
+ * The hub runs ONE timer for the next point across every scheduler. A save is
68196
+ * refused with its reason (`[invalid-scheduler] …`) when a hold has a step that
68197
+ * leads to no state, or a sun point has no site location to read.
68198
+ */
68199
+ var SchedulerIdInput = object({ schedulerId: string().min(1) });
68200
+ method(object({}), array(SchedulerSchema), { auth: "admin" }), method(SchedulerIdInput, object({ scheduler: SchedulerSchema.nullable() }), { auth: "admin" }), method(object({ scheduler: SchedulerInputSchema }), object({ scheduler: SchedulerSchema }), {
68201
+ kind: "mutation",
68202
+ auth: "admin"
68203
+ }), method(object({
68204
+ schedulerId: string().min(1),
68205
+ scheduler: SchedulerInputSchema
68206
+ }), object({ scheduler: SchedulerSchema }), {
68207
+ kind: "mutation",
68208
+ auth: "admin"
68209
+ }), method(SchedulerIdInput, _void(), {
68210
+ kind: "mutation",
68211
+ auth: "admin"
68212
+ }), method(object({
68213
+ schedulerId: string().min(1),
68214
+ profileId: string().min(1).optional(),
68215
+ enabled: boolean()
68216
+ }), object({ scheduler: SchedulerSchema }), {
68217
+ kind: "mutation",
68218
+ auth: "admin"
68219
+ }), method(object({
68220
+ schedulerId: string().min(1),
68221
+ date: string().regex(LOCAL_DATE_PATTERN).optional()
68222
+ }), SchedulerPreviewSchema, { auth: "admin" }), method(object({
68223
+ scheduler: SchedulerInputSchema,
68224
+ schedulerId: string().min(1).optional()
68225
+ }), object({ conflicts: array(SchedulerConflictSchema) }), { auth: "admin" }), method(object({}), array(SchedulerStatusSchema), { auth: "admin" }), method(SchedulerIdInput, object({ traces: array(AutomationRunTraceSchema) }), { auth: "admin" });
68226
+ /**
67254
68227
  * actions — the ONE surface for runnable actions, for the UI and for
67255
68228
  * automations. Provided by the core-blocks builtin on the hub.
67256
68229
  *
@@ -68110,19 +69083,19 @@ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status:
68110
69083
  reason: _enum(["unknown-template", "not-a-media-player"]),
68111
69084
  detail: string()
68112
69085
  })]);
68113
- var deviceInput = object({ deviceId: number().int().nonnegative() });
69086
+ var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
68114
69087
  DeviceType.MediaPlayer;
68115
- method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
69088
+ method(deviceInput$1, RemoteConfigSchema, { auth: "admin" }), method(deviceInput$1.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
68116
69089
  kind: "mutation",
68117
69090
  auth: "admin"
68118
- }), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
69091
+ }), method(deviceInput$1, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput$1.extend({
68119
69092
  role: RemoteRoleSchema,
68120
69093
  /** Sends in a row (volume steps). */
68121
69094
  repeat: number().int().min(1).max(50).default(1)
68122
69095
  }), RemoteSendResultSchema, {
68123
69096
  kind: "mutation",
68124
69097
  auth: "protected"
68125
- }), method(deviceInput.extend({
69098
+ }), method(deviceInput$1.extend({
68126
69099
  command: RemoteMediaCommandSchema,
68127
69100
  muted: boolean().optional(),
68128
69101
  source: string().optional(),
@@ -71556,6 +72529,8 @@ var IntegrationWithStateSchema = object({
71556
72529
  name: string(),
71557
72530
  enabled: boolean(),
71558
72531
  info: record(string(), unknown()).readonly(),
72532
+ /** The discovery candidate this integration was set up from (D984). */
72533
+ originStableId: string().optional(),
71559
72534
  createdAt: number(),
71560
72535
  updatedAt: number(),
71561
72536
  processState: string()
@@ -71566,6 +72541,8 @@ var IntegrationLiteSchema = object({
71566
72541
  name: string(),
71567
72542
  enabled: boolean(),
71568
72543
  info: record(string(), unknown()).readonly(),
72544
+ /** The discovery candidate this integration was set up from (D984). */
72545
+ originStableId: string().optional(),
71569
72546
  createdAt: number(),
71570
72547
  updatedAt: number()
71571
72548
  });
@@ -71663,6 +72640,8 @@ var CreateIntegrationInputSchema = object({
71663
72640
  name: string(),
71664
72641
  enabled: boolean().optional(),
71665
72642
  info: record(string(), unknown()).optional(),
72643
+ /** `stableId` of the discovery candidate the form was seeded from (D984). */
72644
+ originStableId: string().optional(),
71666
72645
  settings: record(string(), unknown()).optional(),
71667
72646
  /** Keys of `settings` the form's schema declares secret (by type) — kept in
71668
72647
  * the keychain, not the table (D787). */
@@ -72034,7 +73013,7 @@ var lawnMowerControlCapability = {
72034
73013
  * clients — they reverse-connect to the hub. Exposing their interfaces
72035
73014
  * via the same surface would leak internal topology with no upside.
72036
73015
  *
72037
- * Implementation in `@camstack/system/builtins/local-network/`.
73016
+ * Implementation in `@camstack/server/system/builtins/local-network/`.
72038
73017
  */
72039
73018
  /** Coarse classification derived from the interface name + IP range. */
72040
73019
  var InterfaceKindEnum = _enum([
@@ -72443,12 +73422,22 @@ method(_void(), ListResultSchema), method(_void(), PreferredSchema), method(obje
72443
73422
  * reports `LockEntityFeature.OPEN` (bit value 1) in HA
72444
73423
  * `supported_features`.
72445
73424
  */
73425
+ /**
73426
+ * `door-open` (D984): the linked door contact reads open, so the bolt cannot be
73427
+ * thrown. It is the lock's REAL reported state, written into the mirrored slice
73428
+ * by the door-contact Customize extension — never a provider's own value, and
73429
+ * never a projection a reader has to compute. Precedence: a native `jammed`
73430
+ * wins; otherwise an open contact reports `door-open` whatever the native state
73431
+ * says (`locked` included); a closed or unknown contact leaves the native state.
73432
+ * A `lock()` is refused while the state is `door-open`; `unlock()` never is.
73433
+ */
72446
73434
  var LockStateSchema = _enum([
72447
73435
  "locked",
72448
73436
  "unlocked",
72449
73437
  "locking",
72450
73438
  "unlocking",
72451
- "jammed"
73439
+ "jammed",
73440
+ "door-open"
72452
73441
  ]);
72453
73442
  var LockControlStatusSchema = object({
72454
73443
  /** Lifecycle state of the lock. `jammed` means the motor reported
@@ -72457,6 +73446,38 @@ var LockControlStatusSchema = object({
72457
73446
  /** Ms epoch when the slice was last updated. */
72458
73447
  lastChangedAt: number()
72459
73448
  });
73449
+ /**
73450
+ * "Door contact" Customize extension (D984): the ONE extension that replaces a
73451
+ * native field. `state` is listed in `overridable` for this target, so the
73452
+ * validator and the guard accept it and nothing else.
73453
+ */
73454
+ var lockControlCustomize = {
73455
+ version: 1,
73456
+ overridable: [{
73457
+ path: "state",
73458
+ by: "door-contact"
73459
+ }],
73460
+ targets: [{
73461
+ id: "door-contact",
73462
+ cardinality: "scalar",
73463
+ vocabulary: {
73464
+ cap: "lock-control",
73465
+ stateField: "state"
73466
+ },
73467
+ deviceTypes: [DeviceType.Lock],
73468
+ apply: "replace",
73469
+ path: "state",
73470
+ override: {
73471
+ whenSourceTrue: "door-open",
73472
+ unlessNative: ["jammed"]
73473
+ },
73474
+ sources: [{
73475
+ kind: "binary",
73476
+ transforms: [{ kind: "identity" }]
73477
+ }],
73478
+ sourceReport: { status: "assignment-status" }
73479
+ }]
73480
+ };
72460
73481
  var lockControlCapability = {
72461
73482
  name: "lock-control",
72462
73483
  scope: "device",
@@ -72502,6 +73523,7 @@ var lockControlCapability = {
72502
73523
  * transitions.
72503
73524
  */
72504
73525
  runtimeState: LockControlStatusSchema,
73526
+ customize: lockControlCustomize,
72505
73527
  /**
72506
73528
  * Runtime-state durability: **restored** — a lock left locked must still read locked.
72507
73529
  *
@@ -72518,6 +73540,117 @@ var lockControlCapability = {
72518
73540
  * whether persisting is worth a SQLite commit. */
72519
73541
  volatileStateFields: ["lastChangedAt"]
72520
73542
  };
73543
+ var PowerSourceRefusalReasonSchema = _enum([
73544
+ "no-power-source",
73545
+ "switch-missing",
73546
+ "switch-offline",
73547
+ "switch-unknown",
73548
+ "restart-in-progress",
73549
+ "switch-call-failed"
73550
+ ]);
73551
+ var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
73552
+ accepted: literal(false),
73553
+ reason: PowerSourceRefusalReasonSchema,
73554
+ /** One line for the operator, naming the switch. */
73555
+ message: string()
73556
+ })]);
73557
+ var PowerSourceInfoSchema = object({
73558
+ /** False when the device has no power source linked; the rest is then empty. */
73559
+ linked: boolean(),
73560
+ switchDeviceId: number().nullable(),
73561
+ switchName: string().nullable(),
73562
+ /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
73563
+ switchOn: boolean().nullable(),
73564
+ switchOnline: boolean().nullable(),
73565
+ restartInProgress: boolean(),
73566
+ offSeconds: number().int()
73567
+ });
73568
+ /** The composed slice: the switch state the link mirrors, plus the stored off time. */
73569
+ var PowerSourceStateSchema = object({
73570
+ /** Null while the switch has not reported: unknown is never `false` (D393). */
73571
+ switchOn: boolean().nullable(),
73572
+ offSeconds: number().int(),
73573
+ lastFetchedAt: number()
73574
+ });
73575
+ var POWER_SOURCE_DEVICE_TYPES = [
73576
+ DeviceType.Gateway,
73577
+ DeviceType.Hub,
73578
+ DeviceType.Camera,
73579
+ DeviceType.Sensor,
73580
+ DeviceType.MediaPlayer,
73581
+ DeviceType.Vacuum,
73582
+ DeviceType.Generic,
73583
+ DeviceType.Washer,
73584
+ DeviceType.Dryer,
73585
+ DeviceType.Dishwasher,
73586
+ DeviceType.PetFountain,
73587
+ DeviceType.Fan,
73588
+ DeviceType.Humidifier,
73589
+ DeviceType.Climate,
73590
+ DeviceType.WaterHeater
73591
+ ];
73592
+ var powerSourceCustomize = {
73593
+ version: 1,
73594
+ targets: [{
73595
+ id: "switch",
73596
+ cardinality: "scalar",
73597
+ vocabulary: {
73598
+ cap: "power-source",
73599
+ stateField: "switchOn"
73600
+ },
73601
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
73602
+ apply: "add",
73603
+ path: "switchOn",
73604
+ sources: [{
73605
+ kind: "switch",
73606
+ transforms: [{ kind: "identity" }]
73607
+ }],
73608
+ companions: [{
73609
+ kind: "constant",
73610
+ path: "offSeconds",
73611
+ value: 10,
73612
+ when: "add"
73613
+ }, {
73614
+ kind: "clock",
73615
+ path: "lastFetchedAt",
73616
+ when: "always"
73617
+ }],
73618
+ sourceReport: { status: "assignment-status" }
73619
+ }]
73620
+ };
73621
+ var deviceInput = object({ deviceId: number().int().nonnegative() });
73622
+ var powerSourceCapability = {
73623
+ name: "power-source",
73624
+ scope: "device",
73625
+ mode: "singleton",
73626
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
73627
+ methods: {
73628
+ getStatus: systemMethod(deviceInput, PowerSourceInfoSchema, { auth: "admin" }),
73629
+ powerOn: systemMethod(deviceInput, PowerSourceActionResultSchema, {
73630
+ kind: "mutation",
73631
+ auth: "admin"
73632
+ }),
73633
+ powerOff: systemMethod(deviceInput, PowerSourceActionResultSchema, {
73634
+ kind: "mutation",
73635
+ auth: "admin"
73636
+ }),
73637
+ forceRestart: systemMethod(deviceInput, PowerSourceActionResultSchema, {
73638
+ kind: "mutation",
73639
+ auth: "admin"
73640
+ })
73641
+ },
73642
+ runtimeState: PowerSourceStateSchema,
73643
+ customize: powerSourceCustomize,
73644
+ /**
73645
+ * Runtime-state durability: **session** — mirrored from the linked switch on
73646
+ * its next report; a restored value would outlive the reading it names.
73647
+ *
73648
+ * See `RuntimeStateDurability`. Enforced by
73649
+ * `scripts/check-runtime-state-durability.ts`.
73650
+ */
73651
+ durability: "session",
73652
+ history: "none"
73653
+ };
72521
73654
  /**
72522
73655
  * A monotonic total between resets (D944). Energy uses `power-meter.energy`
72523
73656
  * so dashboards keep one series; every other instantaneous reading lands here.
@@ -73823,7 +74956,7 @@ onStatusChanged: { data: object({
73823
74956
  * jitter, and observed/peak bandwidth per device + per client.
73824
74957
  *
73825
74958
  * Implementation lives in the server's `NetworkQualityService` (thin
73826
- * wrapper over the shared `NetworkQualityTracker` from `@camstack/system`).
74959
+ * wrapper over the shared `NetworkQualityTracker` from `@camstack/server/system`).
73827
74960
  * The provider is registered from `trpc.router.ts` against the existing
73828
74961
  * service instance — no addon owns this state.
73829
74962
  *
@@ -76009,7 +77142,7 @@ var ptzAutotrackCapability = {
76009
77142
  * - **Native**: the camera's own firmware autotrack, registered per device by
76010
77143
  * the driver when the probe says it exists (Reolink: yes; Hikvision:
76011
77144
  * model-dependent).
76012
- * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
77145
+ * - **Wrapper**: the emulated implementation (`@camstack/server/system`'s `autotrack` builtin),
76013
77146
  * which follows a subject with `ptz.move` pulses driven by the detection
76014
77147
  * pipeline. `defaultActive: false` — the operator binds it per camera in
76015
77148
  * the bindings UI.
@@ -81016,7 +82149,6 @@ discriminatedUnion("kind", [object({
81016
82149
  })]);
81017
82150
  DeviceFeature.FanSpeed, DeviceFeature.FanPreset, DeviceFeature.FanDirection, DeviceFeature.FanOscillating, DeviceFeature.CoverPositionable, DeviceFeature.CoverTilt, DeviceFeature.ValvePositionable, DeviceFeature.MediaPlayerVolume, DeviceFeature.ClimateFanMode;
81018
82151
  SOURCE_CAPS.filter((cap) => cap !== "switch" && cap !== "motion");
81019
- DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, DeviceType.PetFeeder, DeviceType.Container, DeviceType.Thermostat, DeviceType.EventEmitter, DeviceType.Camera, DeviceType.Hub, DeviceType.Light, DeviceType.Siren, DeviceType.Switch, DeviceType.Sensor, DeviceType.Thermostat, DeviceType.Climate, DeviceType.Button, DeviceType.EventEmitter, DeviceType.Update, DeviceType.Generic, DeviceType.Notifier, DeviceType.Script, DeviceType.Automation, DeviceType.Lock, DeviceType.Cover, DeviceType.Valve, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.Fan, DeviceType.MediaPlayer, DeviceType.AlarmPanel, DeviceType.Control, DeviceType.Presence, DeviceType.Weather, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.Container, DeviceType.Image, DeviceType.PetFeeder, DeviceType.PetFountain, DeviceType.Dishwasher, DeviceType.Washer, DeviceType.Dryer, DeviceType.Gateway, DeviceType.Camera, DeviceType.Camera, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.Switch, DeviceType.Light, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.Humidifier, DeviceType.WaterHeater, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.PetFeeder, DeviceType.MediaPlayer, DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, DeviceType.Vacuum, DeviceType.LawnMower, DeviceType.MediaPlayer, DeviceType.WaterHeater, DeviceType.Humidifier, DeviceType.Fan, DeviceType.Climate, DeviceType.Thermostat, DeviceType.PetFeeder;
81020
82152
  String.raw`rose\(active(\d+), changed(\d+), (\d+(?:\.\d+)?)\)`;
81021
82153
  DeviceRole.BinarySensor, DeviceRole.MotionSensor;
81022
82154
  new Set(CAP_NAMES);
@@ -81422,6 +82554,7 @@ var DEVICE_LOCAL_STATE_CAPS = {
81422
82554
  petFeeder: petFeederCapability,
81423
82555
  petFountain: petFountainCapability,
81424
82556
  powerMeter: powerMeterCapability,
82557
+ powerSource: powerSourceCapability,
81425
82558
  presence: presenceCapability,
81426
82559
  pressureSensor: pressureSensorCapability,
81427
82560
  privacyMask: privacyMaskCapability,
@@ -82597,6 +83730,13 @@ Object.freeze({
82597
83730
  access: "view",
82598
83731
  role: "admin"
82599
83732
  },
83733
+ "addonSettings.getIntegrationFormSchema": {
83734
+ capName: "addon-settings",
83735
+ capScope: "system",
83736
+ addonId: null,
83737
+ access: "view",
83738
+ role: "admin"
83739
+ },
82600
83740
  "addonSettings.getIntegrationSettings": {
82601
83741
  capName: "addon-settings",
82602
83742
  capScope: "system",
@@ -86685,6 +87825,69 @@ Object.freeze({
86685
87825
  access: "view",
86686
87826
  role: "admin"
86687
87827
  },
87828
+ "migrations.declareOwner": {
87829
+ capName: "migrations",
87830
+ capScope: "system",
87831
+ addonId: null,
87832
+ access: "create",
87833
+ role: "admin"
87834
+ },
87835
+ "migrations.dryRun": {
87836
+ capName: "migrations",
87837
+ capScope: "system",
87838
+ addonId: null,
87839
+ access: "view",
87840
+ role: "admin"
87841
+ },
87842
+ "migrations.finishMigration": {
87843
+ capName: "migrations",
87844
+ capScope: "system",
87845
+ addonId: null,
87846
+ access: "create",
87847
+ role: "admin"
87848
+ },
87849
+ "migrations.listLedger": {
87850
+ capName: "migrations",
87851
+ capScope: "system",
87852
+ addonId: null,
87853
+ access: "view",
87854
+ role: "admin"
87855
+ },
87856
+ "migrations.listPending": {
87857
+ capName: "migrations",
87858
+ capScope: "system",
87859
+ addonId: null,
87860
+ access: "view",
87861
+ role: "admin"
87862
+ },
87863
+ "migrations.markFailed": {
87864
+ capName: "migrations",
87865
+ capScope: "system",
87866
+ addonId: null,
87867
+ access: "create",
87868
+ role: "admin"
87869
+ },
87870
+ "migrations.restorePreMigration": {
87871
+ capName: "migrations",
87872
+ capScope: "system",
87873
+ addonId: null,
87874
+ access: "create",
87875
+ role: "admin"
87876
+ },
87877
+ "migrations.retry": {
87878
+ capName: "migrations",
87879
+ capScope: "system",
87880
+ addonId: null,
87881
+ access: "create",
87882
+ role: "admin"
87883
+ },
87884
+ "migrations.startMigration": {
87885
+ capName: "migrations",
87886
+ capScope: "system",
87887
+ addonId: null,
87888
+ access: "create",
87889
+ role: "admin"
87890
+ },
86688
87891
  "modelConvert.convert": {
86689
87892
  capName: "model-convert",
86690
87893
  capScope: "system",
@@ -89317,6 +90520,34 @@ Object.freeze({
89317
90520
  access: "view",
89318
90521
  role: "viewer"
89319
90522
  },
90523
+ "powerSource.forceRestart": {
90524
+ capName: "power-source",
90525
+ capScope: "device",
90526
+ addonId: null,
90527
+ access: "create",
90528
+ role: "admin"
90529
+ },
90530
+ "powerSource.getStatus": {
90531
+ capName: "power-source",
90532
+ capScope: "device",
90533
+ addonId: null,
90534
+ access: "view",
90535
+ role: "admin"
90536
+ },
90537
+ "powerSource.powerOff": {
90538
+ capName: "power-source",
90539
+ capScope: "device",
90540
+ addonId: null,
90541
+ access: "create",
90542
+ role: "admin"
90543
+ },
90544
+ "powerSource.powerOn": {
90545
+ capName: "power-source",
90546
+ capScope: "device",
90547
+ addonId: null,
90548
+ access: "create",
90549
+ role: "admin"
90550
+ },
89320
90551
  "presence.getStatus": {
89321
90552
  capName: "presence",
89322
90553
  capScope: "device",
@@ -90129,6 +91360,76 @@ Object.freeze({
90129
91360
  access: "create",
90130
91361
  role: "feature:scenes"
90131
91362
  },
91363
+ "schedulers.conflicts": {
91364
+ capName: "schedulers",
91365
+ capScope: "system",
91366
+ addonId: null,
91367
+ access: "view",
91368
+ role: "admin"
91369
+ },
91370
+ "schedulers.create": {
91371
+ capName: "schedulers",
91372
+ capScope: "system",
91373
+ addonId: null,
91374
+ access: "create",
91375
+ role: "admin"
91376
+ },
91377
+ "schedulers.delete": {
91378
+ capName: "schedulers",
91379
+ capScope: "system",
91380
+ addonId: null,
91381
+ access: "delete",
91382
+ role: "admin"
91383
+ },
91384
+ "schedulers.get": {
91385
+ capName: "schedulers",
91386
+ capScope: "system",
91387
+ addonId: null,
91388
+ access: "view",
91389
+ role: "admin"
91390
+ },
91391
+ "schedulers.getStatus": {
91392
+ capName: "schedulers",
91393
+ capScope: "system",
91394
+ addonId: null,
91395
+ access: "view",
91396
+ role: "admin"
91397
+ },
91398
+ "schedulers.getTraces": {
91399
+ capName: "schedulers",
91400
+ capScope: "system",
91401
+ addonId: null,
91402
+ access: "view",
91403
+ role: "admin"
91404
+ },
91405
+ "schedulers.list": {
91406
+ capName: "schedulers",
91407
+ capScope: "system",
91408
+ addonId: null,
91409
+ access: "view",
91410
+ role: "admin"
91411
+ },
91412
+ "schedulers.preview": {
91413
+ capName: "schedulers",
91414
+ capScope: "system",
91415
+ addonId: null,
91416
+ access: "view",
91417
+ role: "admin"
91418
+ },
91419
+ "schedulers.setEnabled": {
91420
+ capName: "schedulers",
91421
+ capScope: "system",
91422
+ addonId: null,
91423
+ access: "create",
91424
+ role: "admin"
91425
+ },
91426
+ "schedulers.update": {
91427
+ capName: "schedulers",
91428
+ capScope: "system",
91429
+ addonId: null,
91430
+ access: "create",
91431
+ role: "admin"
91432
+ },
90132
91433
  "scriptRunner.getStatus": {
90133
91434
  capName: "script-runner",
90134
91435
  capScope: "device",
@@ -94395,6 +95696,26 @@ Object.freeze({
94395
95696
  form: "single",
94396
95697
  optional: false
94397
95698
  }],
95699
+ "powerSource.forceRestart": [{
95700
+ name: "deviceId",
95701
+ form: "single",
95702
+ optional: false
95703
+ }],
95704
+ "powerSource.getStatus": [{
95705
+ name: "deviceId",
95706
+ form: "single",
95707
+ optional: false
95708
+ }],
95709
+ "powerSource.powerOff": [{
95710
+ name: "deviceId",
95711
+ form: "single",
95712
+ optional: false
95713
+ }],
95714
+ "powerSource.powerOn": [{
95715
+ name: "deviceId",
95716
+ form: "single",
95717
+ optional: false
95718
+ }],
94398
95719
  "presence.getStatus": [{
94399
95720
  name: "deviceId",
94400
95721
  form: "single",
@@ -98714,6 +100035,7 @@ new Set([
98714
100035
  "locking",
98715
100036
  "unlocking",
98716
100037
  "jammed",
100038
+ "door-open",
98717
100039
  "on",
98718
100040
  "off",
98719
100041
  "home",