@camstack/addon-agent-ui 1.2.305 → 1.2.307

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.
package/dist/addon.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import path from "node:path";
2
2
  import { fileURLToPath } from "node:url";
3
- //#region ../types/dist/event-category-DmZDPDf_.mjs
3
+ //#region ../types/dist/event-category-CaURIwQA.mjs
4
4
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
5
5
  EventCategory["SystemBoot"] = "system.boot";
6
6
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -716,6 +716,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
716
716
  EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
717
717
  /** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
718
718
  EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
719
+ /** A migration failed or was blocked; the Alert Center re-reads the ledger. */
720
+ EventCategory["MigrationFailed"] = "migration.failed";
721
+ /** A migration applied or a failure cleared; the Alert Center re-reads the ledger. */
722
+ EventCategory["MigrationApplied"] = "migration.applied";
719
723
  EventCategory["DeviceUpdated"] = "device.updated";
720
724
  /**
721
725
  * Transport-level connectivity. Emitted by the device driver when
@@ -778,6 +782,378 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
778
782
  return EventCategory;
779
783
  }({});
780
784
  //#endregion
785
+ //#region ../types/dist/device-type-CEqjmUnR.mjs
786
+ /**
787
+ import { errMsg } from '@camstack/types'
788
+ * Extract a human-readable message from an unknown error value.
789
+ * Replaces the ubiquitous `errMsg(err)` pattern.
790
+ */
791
+ function errMsg(err) {
792
+ if (err instanceof Error) return err.message;
793
+ if (typeof err === "string") return err;
794
+ return String(err);
795
+ }
796
+ var DeviceType = /* @__PURE__ */ function(DeviceType) {
797
+ DeviceType["Camera"] = "camera";
798
+ DeviceType["Hub"] = "hub";
799
+ DeviceType["Light"] = "light";
800
+ DeviceType["Siren"] = "siren";
801
+ DeviceType["Switch"] = "switch";
802
+ DeviceType["Sensor"] = "sensor";
803
+ DeviceType["Thermostat"] = "thermostat";
804
+ /** Air-conditioner / heat-pump climate device (HVAC) — shares the
805
+ * `climate-control` cap surface with `Thermostat` but renders a
806
+ * dedicated AC-appropriate control UI (mode chips, fan speed,
807
+ * independent vertical/horizontal swing). Sources: native Gree, and
808
+ * reusable by other AC integrations. */
809
+ DeviceType["Climate"] = "climate";
810
+ DeviceType["Button"] = "button";
811
+ /** Generic stateless event emitter — carries a device's EXACT declared
812
+ * event vocabulary verbatim (no normalization). Installed with the
813
+ * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
814
+ * HA bus events (e.g. `zha_event`, generic). */
815
+ DeviceType["EventEmitter"] = "event-emitter";
816
+ /** Firmware/software update entity — current vs available version,
817
+ * updatable flag, update state, and an install action. Installed with
818
+ * the `update` cap. Sources: Homematic firmware-update channels (and
819
+ * reusable by other providers, e.g. HA `update.*` entities). */
820
+ DeviceType["Update"] = "update";
821
+ DeviceType["Generic"] = "generic";
822
+ /** Generic notification delivery target (HA `notify.<service>`, future
823
+ * Telegram / Discord / ntfy / SMTP, …). One device per delivery
824
+ * endpoint; the `notifier` cap defines the send surface. */
825
+ DeviceType["Notifier"] = "notifier";
826
+ /** Pre-recorded action sequence with optional parameters
827
+ * (HA `script.*`). Runnable via `script-runner` cap. */
828
+ DeviceType["Script"] = "script";
829
+ /** Automation rule (HA `automation.*`) — enable/disable + manual
830
+ * trigger surface exposed via `automation-control` cap. */
831
+ DeviceType["Automation"] = "automation";
832
+ /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
833
+ DeviceType["Lock"] = "lock";
834
+ /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
835
+ * `valve.*`). `cover` cap with sub-roles for variant. */
836
+ DeviceType["Cover"] = "cover";
837
+ /** Pipe / water / gas valve with open/close/stop and optional
838
+ * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
839
+ * modelled on the same open/closed lifecycle. */
840
+ DeviceType["Valve"] = "valve";
841
+ /** Humidifier / dehumidifier with on/off + target humidity + mode
842
+ * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
843
+ * modelled on the same target / mode lifecycle. */
844
+ DeviceType["Humidifier"] = "humidifier";
845
+ /** Water heater / boiler with target temperature + operation mode +
846
+ * away mode (HA `water_heater.*`). `water-heater` cap — a
847
+ * climate-family actuator. */
848
+ DeviceType["WaterHeater"] = "water-heater";
849
+ /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
850
+ DeviceType["Fan"] = "fan";
851
+ /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
852
+ * the camera surface — those use `Camera`. `media-player` cap. */
853
+ DeviceType["MediaPlayer"] = "media-player";
854
+ /** Security panel / alarm system (HA `alarm_control_panel.*`).
855
+ * `alarm-panel` cap. */
856
+ DeviceType["AlarmPanel"] = "alarm-panel";
857
+ /** Generic user-settable input (HA `number` / `input_number` / `select`
858
+ * / `input_select` / `text` / `input_text` / `input_datetime`).
859
+ * Sub-type via `DeviceRole`: NumericControl / SelectControl /
860
+ * TextControl / DateTimeControl. */
861
+ DeviceType["Control"] = "control";
862
+ /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
863
+ * `presence` cap. */
864
+ DeviceType["Presence"] = "presence";
865
+ /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
866
+ * `weather` cap. */
867
+ DeviceType["Weather"] = "weather";
868
+ /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
869
+ DeviceType["Vacuum"] = "vacuum";
870
+ /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
871
+ * `lawn-mower-control` cap. */
872
+ DeviceType["LawnMower"] = "lawn-mower";
873
+ /** Physical HA device group — parent container for entity-children
874
+ * adopted from a single HA device entry. Not renderable as a
875
+ * standalone device; exists only to anchor child entities. */
876
+ DeviceType["Container"] = "container";
877
+ /** Single still-image entity (HA `image.*`). Read-only display of an
878
+ * `entity_picture` signed URL the browser loads directly. `image` cap. */
879
+ DeviceType["Image"] = "image";
880
+ /** Smart pet feeder — cloud-connected food dispenser with a bowl food
881
+ * level, battery, desiccant life, feeding state and manual-feed /
882
+ * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
883
+ * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
884
+ * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
885
+ * integrations sharing the same food/desiccant/hopper surface. */
886
+ DeviceType["PetFeeder"] = "pet-feeder";
887
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
888
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
889
+ * of a container and is BUILT as a composed device (D967). */
890
+ DeviceType["PetFountain"] = "pet-fountain";
891
+ /** Built-in dishwasher — program lifecycle, door / remote-start gates,
892
+ * salt and rinse-aid levels. Installed with the `dishwasher` cap.
893
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
894
+ * A plug-built dishwasher carries running-state and power instead. */
895
+ DeviceType["Dishwasher"] = "dishwasher";
896
+ /** Washing machine. A plug-built one is running while its power stays
897
+ * above a threshold. No program cap — that surface is the dishwasher's. */
898
+ DeviceType["Washer"] = "washer";
899
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
900
+ DeviceType["Dryer"] = "dryer";
901
+ /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
902
+ * other radio-to-IP bridges). It carries the box's own facts (firmware,
903
+ * address, connection state) and the actions the box really supports
904
+ * (`reboot`, `device-admin-link`). Products are NOT its children — they are
905
+ * adopted beside it — and its connection settings stay on the integration. */
906
+ DeviceType["Gateway"] = "gateway";
907
+ return DeviceType;
908
+ }({});
909
+ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
910
+ DeviceFeature["BatteryOperated"] = "battery-operated";
911
+ DeviceFeature["Rebootable"] = "rebootable";
912
+ /**
913
+ * Device supports an on-demand re-sync of its derived spec with its
914
+ * upstream source — drives the generic Re-sync button. The owning
915
+ * provider implements the action via the `device-adoption.resync` cap.
916
+ */
917
+ DeviceFeature["Resyncable"] = "resyncable";
918
+ DeviceFeature["NativeSnapshot"] = "native-snapshot";
919
+ DeviceFeature["DoorbellButton"] = "doorbell-button";
920
+ DeviceFeature["TwoWayAudio"] = "two-way-audio";
921
+ DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
922
+ /**
923
+ * Camera supports the on-firmware autotrack subsystem (subject-
924
+ * following). Distinct from `PanTiltZoom` because not every PTZ
925
+ * camera ships autotrack — the admin UI uses this flag to gate
926
+ * the autotrack toggle / settings card without re-deriving from
927
+ * the cap registry. Mirrors `ptz-autotrack` cap registration:
928
+ * driver sets this feature when probe confirms the firmware
929
+ * surface, and registers the cap in the same code path.
930
+ */
931
+ DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
932
+ /**
933
+ * Accessory exposes a "trigger on motion" toggle — the parent camera's
934
+ * motion detection automatically activates this device. Mirrors
935
+ * `motion-trigger` cap registration: drivers set this feature in the
936
+ * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
937
+ *
938
+ * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
939
+ * fast scalar without binding fetch), notifier rules, and `listAll`
940
+ * filters that want "all devices with on-motion behaviour".
941
+ */
942
+ DeviceFeature["MotionTrigger"] = "motion-trigger";
943
+ /** Light supports rgb-triplet color via `color` cap. */
944
+ DeviceFeature["LightColorRgb"] = "light-color-rgb";
945
+ /** Light supports HSV color via `color` cap. */
946
+ DeviceFeature["LightColorHsv"] = "light-color-hsv";
947
+ /** Light supports color-temperature (mired) via `color` cap. */
948
+ DeviceFeature["LightColorMired"] = "light-color-mired";
949
+ /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
950
+ * targetHigh). Gates the range slider UI. */
951
+ DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
952
+ /** Thermostat exposes target humidity and/or current humidity
953
+ * readings. Gates the humidity controls. */
954
+ DeviceFeature["ClimateHumidity"] = "climate-humidity";
955
+ /** Thermostat exposes a fan-mode selector. */
956
+ DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
957
+ /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
958
+ DeviceFeature["ClimatePreset"] = "climate-preset";
959
+ /** Thermostat exposes a vertical louver swing toggle. Gates the
960
+ * vertical-swing switch in the climate UI. Independent of horizontal. */
961
+ DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
962
+ /** Thermostat exposes a horizontal louver swing toggle. Gates the
963
+ * horizontal-swing switch in the climate UI. Independent of vertical. */
964
+ DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
965
+ /** Cover exposes intermediate position control (0..100). Gates the
966
+ * position slider UI. */
967
+ DeviceFeature["CoverPositionable"] = "cover-positionable";
968
+ /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
969
+ DeviceFeature["CoverTilt"] = "cover-tilt";
970
+ /** Valve exposes intermediate position control (0..100). Gates the
971
+ * position slider / drag surface UI. */
972
+ DeviceFeature["ValvePositionable"] = "valve-positionable";
973
+ /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
974
+ DeviceFeature["FanSpeed"] = "fan-speed";
975
+ /** Fan exposes a preset mode selector. */
976
+ DeviceFeature["FanPreset"] = "fan-preset";
977
+ /** Fan exposes blade direction (forward/reverse) — typical of
978
+ * ceiling fans. */
979
+ DeviceFeature["FanDirection"] = "fan-direction";
980
+ /** Fan exposes an oscillation toggle. */
981
+ DeviceFeature["FanOscillating"] = "fan-oscillating";
982
+ /** Lock requires a PIN code on lock/unlock. Gates the code-entry
983
+ * field on the UI lock-controls panel. */
984
+ DeviceFeature["LockPinRequired"] = "lock-pin-required";
985
+ /** Lock supports a latch-release ("open door") action distinct from
986
+ * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
987
+ * `supported_features`. Gates the Open Door button in the UI. */
988
+ DeviceFeature["LockOpen"] = "lock-open";
989
+ /** Media player exposes a seek-to-position surface. */
990
+ DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
991
+ /** Media player exposes a volume-level setter. */
992
+ DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
993
+ /** Media player exposes a mute toggle distinct from volume=0. */
994
+ DeviceFeature["MediaPlayerMute"] = "media-player-mute";
995
+ /** Media player exposes a shuffle toggle. */
996
+ DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
997
+ /** Media player exposes a repeat mode (off / all / one). */
998
+ DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
999
+ /** Media player exposes a source / input selector. */
1000
+ DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
1001
+ /** Media player exposes a play-arbitrary-media surface (URL / id). */
1002
+ DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
1003
+ /** Media player exposes next-track. */
1004
+ DeviceFeature["MediaPlayerNext"] = "media-player-next";
1005
+ /** Media player exposes previous-track. */
1006
+ DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
1007
+ /** Media player exposes stop distinct from pause. */
1008
+ DeviceFeature["MediaPlayerStop"] = "media-player-stop";
1009
+ /** Alarm panel requires a PIN code on arm/disarm. */
1010
+ DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
1011
+ /** Presence device carries GPS coordinates (lat/lng/accuracy) in
1012
+ * addition to a textual location. */
1013
+ DeviceFeature["PresenceGps"] = "presence-gps";
1014
+ /** Notifier accepts an inline / URL image attachment. */
1015
+ DeviceFeature["NotifierImage"] = "notifier-image";
1016
+ /** Notifier accepts a priority hint (high/normal/low). */
1017
+ DeviceFeature["NotifierPriority"] = "notifier-priority";
1018
+ /** Notifier accepts a free-form `data` payload for platform-specific
1019
+ * fields. */
1020
+ DeviceFeature["NotifierData"] = "notifier-data";
1021
+ /** Notifier supports interactive action buttons / callbacks. */
1022
+ DeviceFeature["NotifierActions"] = "notifier-actions";
1023
+ /** Notifier supports per-call recipient targeting (multi-user). */
1024
+ DeviceFeature["NotifierRecipients"] = "notifier-recipients";
1025
+ /** Script runner accepts a variables map on each run invocation. */
1026
+ DeviceFeature["ScriptVariables"] = "script-variables";
1027
+ /** Automation `trigger` accepts a skipCondition flag — fires the
1028
+ * automation's actions while bypassing its condition block. */
1029
+ DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
1030
+ /** Robot vacuum exposes a live cleaning map (image child). Gates the
1031
+ * map tile in the vacuum UI. */
1032
+ DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
1033
+ /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
1034
+ * switches group. */
1035
+ DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
1036
+ /** Robot mower exposes a live mowing map (SVG image child). Gates the
1037
+ * mower map tile. */
1038
+ DeviceFeature["MowerHasMap"] = "mower-has-map";
1039
+ /** Robot mower exposes targeted mowing (all-area / zones / edges /
1040
+ * spots) — gates the Mow action + map selectors. */
1041
+ DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
1042
+ return DeviceFeature;
1043
+ }({});
1044
+ /**
1045
+ * Semantic role a device plays within its parent. Populated by driver
1046
+ * addons when creating accessory devices (Reolink siren/floodlight/
1047
+ * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
1048
+ * admin UI to pick icons, labels, and widgets — a `Switch` with
1049
+ * `role: Floodlight` renders as a bulb with a brightness slider,
1050
+ * whereas a `Switch` with `role: Siren` renders as a klaxon.
1051
+ *
1052
+ * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
1053
+ * sqlite as a nullable TEXT column — old rows keep working unchanged.
1054
+ */
1055
+ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
1056
+ DeviceRole["Siren"] = "siren";
1057
+ DeviceRole["Floodlight"] = "floodlight";
1058
+ DeviceRole["Spotlight"] = "spotlight";
1059
+ DeviceRole["PirSensor"] = "pir-sensor";
1060
+ DeviceRole["Chime"] = "chime";
1061
+ DeviceRole["Autotrack"] = "autotrack";
1062
+ DeviceRole["Nightvision"] = "nightvision";
1063
+ DeviceRole["PrivacyMask"] = "privacy-mask";
1064
+ DeviceRole["Doorbell"] = "doorbell";
1065
+ /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
1066
+ DeviceRole["CoverBlind"] = "cover-blind";
1067
+ /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
1068
+ DeviceRole["CoverShutter"] = "cover-shutter";
1069
+ /** Exterior awning. HA `device_class: awning`. */
1070
+ DeviceRole["CoverAwning"] = "cover-awning";
1071
+ /** Curtain / drape. HA `device_class: curtain`. */
1072
+ DeviceRole["CoverCurtain"] = "cover-curtain";
1073
+ /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
1074
+ DeviceRole["CoverGarage"] = "cover-garage";
1075
+ /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
1076
+ DeviceRole["CoverGate"] = "cover-gate";
1077
+ /** Door. HA `device_class: door`. A security actuator: exports gate it. */
1078
+ DeviceRole["CoverDoor"] = "cover-door";
1079
+ /** Window. HA `device_class: window`. */
1080
+ DeviceRole["CoverWindow"] = "cover-window";
1081
+ /** Virtual HA toggle (input_boolean.*) — distinguishable from a
1082
+ * real Switch device for UI rendering / export adapters. */
1083
+ DeviceRole["BinaryHelper"] = "binary-helper";
1084
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
1085
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
1086
+ DeviceRole["Outlet"] = "outlet";
1087
+ /** Generic motion / occupancy / moving event source. Distinct from
1088
+ * the camera accessory PirSensor role: that one is a camera child;
1089
+ * this is a standalone HA / 3rd-party motion sensor. */
1090
+ DeviceRole["MotionSensor"] = "motion-sensor";
1091
+ DeviceRole["ContactSensor"] = "contact-sensor";
1092
+ DeviceRole["LeakSensor"] = "leak-sensor";
1093
+ DeviceRole["SmokeSensor"] = "smoke-sensor";
1094
+ DeviceRole["COSensor"] = "co-sensor";
1095
+ DeviceRole["GasSensor"] = "gas-sensor";
1096
+ DeviceRole["TamperSensor"] = "tamper-sensor";
1097
+ DeviceRole["VibrationSensor"] = "vibration-sensor";
1098
+ DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
1099
+ DeviceRole["SoundSensor"] = "sound-sensor";
1100
+ /** Fallback for `binary_sensor` without a known `device_class`. */
1101
+ DeviceRole["BinarySensor"] = "binary-sensor";
1102
+ DeviceRole["TemperatureSensor"] = "temperature-sensor";
1103
+ DeviceRole["HumiditySensor"] = "humidity-sensor";
1104
+ DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
1105
+ DeviceRole["PressureSensor"] = "pressure-sensor";
1106
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
1107
+ DeviceRole["WindSensor"] = "wind-sensor";
1108
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
1109
+ DeviceRole["RainSensor"] = "rain-sensor";
1110
+ /** UV index (weather-station `uv-sensor` cap). */
1111
+ DeviceRole["UvSensor"] = "uv-sensor";
1112
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
1113
+ * Distinct from AmbientLightSensor (lux). */
1114
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
1115
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
1116
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
1117
+ DeviceRole["PowerSensor"] = "power-sensor";
1118
+ DeviceRole["EnergySensor"] = "energy-sensor";
1119
+ DeviceRole["VoltageSensor"] = "voltage-sensor";
1120
+ DeviceRole["CurrentSensor"] = "current-sensor";
1121
+ DeviceRole["AirQualitySensor"] = "air-quality-sensor";
1122
+ /** Battery level (numeric % via `sensor` OR low-bool via
1123
+ * `binary_sensor` — the cap distinguishes via the value type). */
1124
+ DeviceRole["BatterySensor"] = "battery-sensor";
1125
+ /** Fallback for `sensor` numeric without a known `device_class`. */
1126
+ DeviceRole["NumericSensor"] = "numeric-sensor";
1127
+ /** String / enum state (HA `sensor` with `state_class: enum` or
1128
+ * `attributes.options`). */
1129
+ DeviceRole["EnumSensor"] = "enum-sensor";
1130
+ /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
1131
+ * or `date`). The slice carries the raw ISO string verbatim (hosted on
1132
+ * the `enum-sensor` cap); the UI renders it locale-formatted. */
1133
+ DeviceRole["DateTimeSensor"] = "datetime-sensor";
1134
+ /** Last-resort fallback when nothing else matches. */
1135
+ DeviceRole["GenericSensor"] = "generic-sensor";
1136
+ DeviceRole["NumericControl"] = "numeric-control";
1137
+ DeviceRole["SelectControl"] = "select-control";
1138
+ DeviceRole["TextControl"] = "text-control";
1139
+ DeviceRole["DateTimeControl"] = "datetime-control";
1140
+ /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
1141
+ * rich features (image, priority, channel routing). */
1142
+ DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
1143
+ /** Chat / messaging service (HA `notify.telegram_*`,
1144
+ * `notify.discord_*`, etc.). */
1145
+ DeviceRole["MessagingNotifier"] = "messaging-notifier";
1146
+ /** Email-based delivery (HA `notify.smtp`, etc.). */
1147
+ DeviceRole["EmailNotifier"] = "email-notifier";
1148
+ /** Fallback when the notifier service name doesn't match a known
1149
+ * pattern. */
1150
+ DeviceRole["GenericNotifier"] = "generic-notifier";
1151
+ /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
1152
+ * cap. A `DeviceType.Generic` device. */
1153
+ DeviceRole["IrTransmitter"] = "ir-transmitter";
1154
+ return DeviceRole;
1155
+ }({});
1156
+ //#endregion
781
1157
  //#region ../../node_modules/zod/v4/core/core.js
782
1158
  var _a$1;
783
1159
  function $constructor(name, initializer, params) {
@@ -5456,7 +5832,7 @@ var ZodIssueCode = {
5456
5832
  var ZodFirstPartyTypeKind;
5457
5833
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5458
5834
  //#endregion
5459
- //#region ../types/dist/sleep-DSVWZfJC.mjs
5835
+ //#region ../types/dist/sleep-CSYuxssh.mjs
5460
5836
  /**
5461
5837
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5462
5838
  * window to float samples (D455).
@@ -5972,6 +6348,102 @@ function emitReadiness(bus, params) {
5972
6348
  ts
5973
6349
  }));
5974
6350
  }
6351
+ var NOT_RUN = {
6352
+ declared: false,
6353
+ snapshot: null,
6354
+ applied: [],
6355
+ failed: [],
6356
+ blocked: [],
6357
+ skipped: [],
6358
+ blockOwner: []
6359
+ };
6360
+ /**
6361
+ * Offer `migrations` to the engine and run what it says to run, in its order. Never
6362
+ * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6363
+ * because the hub (and this addon's boot) must not hang on a migration.
6364
+ */
6365
+ async function runAddonMigrations(host, migrations, logger) {
6366
+ const first = migrations[0];
6367
+ if (first === void 0) return NOT_RUN;
6368
+ const owner = first.meta.owner;
6369
+ const log = logger.withTags({ migrationOwner: owner });
6370
+ const byId = new Map(migrations.map((m) => [m.meta.id, m]));
6371
+ let declaration;
6372
+ try {
6373
+ declaration = await host.declareOwner({
6374
+ owner,
6375
+ metas: migrations.map((m) => m.meta)
6376
+ });
6377
+ } catch (err) {
6378
+ log.error("the migration engine refused or could not take this addon catalogue — none ran", { meta: { error: errMsg(err) } });
6379
+ return {
6380
+ ...NOT_RUN,
6381
+ blockOwner: migrations.filter((m) => m.meta.onFailure === "block-owner").map((m) => m.meta.id)
6382
+ };
6383
+ }
6384
+ const applied = [];
6385
+ const failed = [];
6386
+ const blocked = [];
6387
+ const skipped = [];
6388
+ const settle = (id, status) => {
6389
+ if (status === "applied") applied.push(id);
6390
+ else if (status === "failed") failed.push(id);
6391
+ else if (status === "blocked") blocked.push(id);
6392
+ else skipped.push(id);
6393
+ };
6394
+ for (const step of declaration.steps) {
6395
+ const bound = byId.get(step.id);
6396
+ if (bound === void 0 || step.action === "skip") {
6397
+ skipped.push(step.id);
6398
+ continue;
6399
+ }
6400
+ const tags = { migrationId: step.id };
6401
+ try {
6402
+ const gate = await host.startMigration({ id: step.id });
6403
+ if (!gate.proceed) {
6404
+ log.warn("migration not run", {
6405
+ tags,
6406
+ meta: {
6407
+ state: gate.state,
6408
+ reason: gate.reason
6409
+ }
6410
+ });
6411
+ settle(step.id, gate.state === "pending" ? "blocked" : gate.state);
6412
+ continue;
6413
+ }
6414
+ const result = await bound.execute();
6415
+ const outcome = await host.finishMigration({
6416
+ id: step.id,
6417
+ result
6418
+ });
6419
+ if (outcome.status === "applied") log.info("migration applied", {
6420
+ tags,
6421
+ meta: { rowsChanged: outcome.rowsChanged }
6422
+ });
6423
+ else log.error("migration failed", {
6424
+ tags,
6425
+ meta: { error: outcome.error }
6426
+ });
6427
+ settle(step.id, outcome.status);
6428
+ } catch (err) {
6429
+ log.error("migration could not be completed with the engine", {
6430
+ tags,
6431
+ meta: { error: errMsg(err) }
6432
+ });
6433
+ failed.push(step.id);
6434
+ }
6435
+ }
6436
+ const blockOwner = [...failed, ...blocked].filter((id) => byId.get(id)?.meta.onFailure === "block-owner");
6437
+ return {
6438
+ declared: true,
6439
+ snapshot: declaration.snapshot,
6440
+ applied,
6441
+ failed,
6442
+ blocked,
6443
+ skipped,
6444
+ blockOwner
6445
+ };
6446
+ }
5975
6447
  /**
5976
6448
  * Build a {@link DurableState} over a single store key. Transport-agnostic:
5977
6449
  * `read`/`write` are the addon-store or device-store accessors. The whole
@@ -7027,6 +7499,28 @@ var BaseAddon = class {
7027
7499
  return normalized;
7028
7500
  }
7029
7501
  /**
7502
+ * Run this addon's migration catalogue (D987 M3) through the hub's `migrations`
7503
+ * engine. Call it at the point of the boot where the data must already have the new
7504
+ * shape — `onInitialize`, or `onRestoreDevices` for a migration that needs the
7505
+ * restored devices — with the catalogue bound to the addon's ports
7506
+ * (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
7507
+ *
7508
+ * An engine that cannot be reached or refuses the catalogue is logged and runs
7509
+ * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7510
+ * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7511
+ * serve data whose shape it cannot trust; `tolerate` migrations never do.
7512
+ */
7513
+ async runMigrations(migrations) {
7514
+ const api = this.ctx.api.migrations;
7515
+ const report = await runAddonMigrations({
7516
+ declareOwner: (input) => api.declareOwner.mutate(input),
7517
+ startMigration: (input) => api.startMigration.mutate(input),
7518
+ finishMigration: (input) => api.finishMigration.mutate(input)
7519
+ }, migrations, this.ctx.logger);
7520
+ 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`);
7521
+ return report;
7522
+ }
7523
+ /**
7030
7524
  * Called by the isolated-process runner AFTER `broker.start()`.
7031
7525
  * In-process addons never need this because the hub broker is already
7032
7526
  * running when `initialize()` fires. For forked children the broker
@@ -8069,407 +8563,6 @@ var RecordKindDeclarationSchema = object({
8069
8563
  }).strict();
8070
8564
  /** The kind names: lower-case, dash separated, short enough for an index name. */
8071
8565
  var RecordKindNameSchema = string().regex(/^[a-z][a-z0-9-]{0,47}$/);
8072
- var READING_QUANTITIES = [
8073
- "temperature",
8074
- "pressure",
8075
- "speed",
8076
- "precipitation",
8077
- "precipitation-rate",
8078
- "length",
8079
- "area",
8080
- "illuminance",
8081
- "irradiance",
8082
- "power",
8083
- "energy",
8084
- "voltage",
8085
- "current",
8086
- "apparent-power",
8087
- "humidity",
8088
- "percentage",
8089
- "concentration-volume",
8090
- "concentration-mass",
8091
- "concentration-ppb",
8092
- "volume",
8093
- "duration",
8094
- "duration-ms",
8095
- "information",
8096
- "sound-level",
8097
- "signal-level",
8098
- "colour-temperature",
8099
- "angle",
8100
- "bitrate",
8101
- "rate",
8102
- "mass",
8103
- "index",
8104
- "ratio",
8105
- "count",
8106
- "none",
8107
- "device"
8108
- ];
8109
- var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8110
- new Set(READING_QUANTITIES);
8111
- /**
8112
- * Stored unit symbol for a quantity. `none` means the number is dimensionless
8113
- * (or an index / ratio / count). `device` means the cap has no single unit.
8114
- */
8115
- var QUANTITY_BASE = {
8116
- temperature: "°C",
8117
- pressure: "hPa",
8118
- speed: "m/s",
8119
- precipitation: "mm",
8120
- "precipitation-rate": "mm/h",
8121
- length: "m",
8122
- area: "m²",
8123
- illuminance: "lx",
8124
- irradiance: "W/m²",
8125
- power: "W",
8126
- energy: "kWh",
8127
- voltage: "V",
8128
- current: "A",
8129
- "apparent-power": "VA",
8130
- humidity: "%",
8131
- percentage: "%",
8132
- "concentration-volume": "ppm",
8133
- "concentration-mass": "µg/m³",
8134
- "concentration-ppb": "ppb",
8135
- volume: "L",
8136
- duration: "s",
8137
- "duration-ms": "ms",
8138
- information: "B",
8139
- "sound-level": "dBFS",
8140
- "signal-level": "dBm",
8141
- "colour-temperature": "mired",
8142
- angle: "°",
8143
- bitrate: "kbps",
8144
- rate: "fps",
8145
- mass: "kg",
8146
- index: "none",
8147
- ratio: "none",
8148
- count: "none",
8149
- none: "none",
8150
- device: "device"
8151
- };
8152
- /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8153
- function baseUnitOf(quantity) {
8154
- const symbol = QUANTITY_BASE[quantity];
8155
- if (symbol === "none" || symbol === "device") return void 0;
8156
- return symbol;
8157
- }
8158
- function readingMeta(quantity, extras) {
8159
- return {
8160
- quantity,
8161
- ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8162
- ...extras?.label !== void 0 ? { label: extras.label } : {}
8163
- };
8164
- }
8165
- /** Shared Zod schemas used across streaming capabilities. */
8166
- var CamProfileSchema = _enum([
8167
- "high",
8168
- "mid",
8169
- "low"
8170
- ]);
8171
- /**
8172
- * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
8173
- *
8174
- * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
8175
- * that consumer owns". It lives beside `CamProfile` because a rung is the only
8176
- * operator-facing way to name a stream: a cam-stream id is broker-internal and
8177
- * moves when a slot is reassigned, so a stored id silently stops meaning what
8178
- * the operator picked. Today's one user is the audio analyser, whose rule is
8179
- * "the lowest AVAILABLE native rung" (D537).
8180
- */
8181
- var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
8182
- var CamStreamKindSchema = _enum([
8183
- "pull-rtsp",
8184
- "pull-rtmp",
8185
- "pull-http",
8186
- "pull-flv",
8187
- "pull-rfc4571",
8188
- "push-annexb",
8189
- "derived"
8190
- ]);
8191
- var CamStreamResolutionSchema = object({
8192
- width: number().int().positive().meta(readingMeta("none")),
8193
- height: number().int().positive().meta(readingMeta("none"))
8194
- });
8195
- var CameraStreamSchema = object({
8196
- /** Stable, provider-assigned id unique within the (deviceId) scope. */
8197
- camStreamId: string().min(1),
8198
- deviceId: number().int().nonnegative(),
8199
- kind: CamStreamKindSchema,
8200
- /** Required for pull-* kinds. Ignored for push-annexb. */
8201
- url: string().optional(),
8202
- codec: string().optional(),
8203
- resolution: CamStreamResolutionSchema.optional(),
8204
- fps: number().positive().optional(),
8205
- /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
8206
- label: string().optional(),
8207
- /**
8208
- * Device-level features the publisher advertised (e.g. `battery-operated`).
8209
- * The broker, snapshot orchestrator, and prebuffer manager all consult
8210
- * this list to derive policy — relaxed stall watchdog for battery
8211
- * cams, prebuffer off by default, longer snapshot rate-limit, etc.
8212
- *
8213
- * Single source of truth replacing per-stream flags like the
8214
- * historical `allowStall`: if the publisher knows the camera is
8215
- * battery-powered, every downstream service derives the right policy
8216
- * from this list.
8217
- */
8218
- deviceFeatures: array(string()).optional(),
8219
- /**
8220
- * Whether this stream participates in the broker's automatic profile
8221
- * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
8222
- * use `false` when they want a stream to be SELECTABLE in the UI but not
8223
- * picked by default — e.g. Reolink publishes its native Baichuan streams
8224
- * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
8225
- * mirrors as `autoEligible: false` (still pickable per slot, just not
8226
- * the auto choice). Manual `assignProfile` calls remain valid for
8227
- * non-eligible streams.
8228
- */
8229
- autoEligible: boolean().optional(),
8230
- /**
8231
- * The profile slot the PUBLISHER says this stream is: `low` for a stream
8232
- * whose only sensible role is the low-bandwidth one, `high` for a main
8233
- * stream. Consulted by the broker's automatic ranking
8234
- * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
8235
- * an operator's manual assignment still wins, and an absent hint is the
8236
- * pixel-ranked default every camera had before this field existed.
8237
- *
8238
- * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
8239
- * storage class is written for on its own (`recordings` is high|mid,
8240
- * `recordingsLow` is low) and the one profile scrub does not read. The robot
8241
- * camera's single 720p relay is a `low` in every respect except the slot the
8242
- * heuristic put it in.
8243
- */
8244
- profileHint: CamProfileSchema.optional(),
8245
- /**
8246
- * Transport-specific opaque metadata. The broker passes it through to
8247
- * the source reader without inspecting it. Currently used by
8248
- * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
8249
- * route RTP packets to the right depacketizer without an in-band
8250
- * DESCRIBE phase). Other kinds typically leave it undefined.
8251
- */
8252
- metadata: record(string(), unknown()).optional()
8253
- });
8254
- var ProfileSlotStatusSchema = _enum([
8255
- "unassigned",
8256
- "idle",
8257
- "connecting",
8258
- "streaming",
8259
- "error"
8260
- ]);
8261
- var ProfileSlotSchema = object({
8262
- deviceId: number().int().nonnegative(),
8263
- profile: CamProfileSchema,
8264
- /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
8265
- brokerId: string(),
8266
- /** `null` when the profile is unassigned. */
8267
- sourceCamStreamId: string().nullable(),
8268
- status: ProfileSlotStatusSchema,
8269
- resolution: CamStreamResolutionSchema.optional(),
8270
- codec: string().optional(),
8271
- preBufferSec: number().nonnegative().optional(),
8272
- errorMessage: string().optional()
8273
- });
8274
- /**
8275
- * Zod schema for StreamSourceEntry — the canonical stream descriptor
8276
- * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
8277
- */
8278
- var StreamSourceEntrySchema$1 = object({
8279
- id: string(),
8280
- label: string(),
8281
- protocol: _enum([
8282
- "rtsp",
8283
- "rtmp",
8284
- "annexb",
8285
- "http-mjpeg",
8286
- "webrtc",
8287
- "custom"
8288
- ]),
8289
- url: string().optional(),
8290
- resolution: object({
8291
- width: number(),
8292
- height: number()
8293
- }).readonly().optional(),
8294
- fps: number().optional(),
8295
- bitrate: number().optional(),
8296
- codec: string().optional(),
8297
- profileHint: CamProfileSchema.optional()
8298
- });
8299
- object({
8300
- type: string(),
8301
- url: string(),
8302
- videoCodec: string().optional(),
8303
- audioCodec: string().optional(),
8304
- metadata: record(string(), unknown()).readonly().optional()
8305
- });
8306
- /**
8307
- * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
8308
- * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
8309
- * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
8310
- * (the runner's native-crop and pipeline-executor inputs) can carry handles
8311
- * over tRPC / Moleculer.
8312
- *
8313
- * The `satisfies` assertion below pins this schema to the interface: if a
8314
- * field is added to / removed from `FrameHandle` without a matching schema
8315
- * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
8316
- * `FrameHandle` and the build fails — no silent drift.
8317
- */
8318
- var FrameHandleSchema = object({
8319
- shmId: string(),
8320
- slot: number().int().nonnegative(),
8321
- seq: number().int().nonnegative(),
8322
- width: number().int().positive(),
8323
- height: number().int().positive(),
8324
- format: _enum([
8325
- "jpeg",
8326
- "rgb",
8327
- "bgr",
8328
- "yuv420",
8329
- "gray"
8330
- ]),
8331
- pts: number(),
8332
- byteLength: number().int().nonnegative(),
8333
- nodeId: string(),
8334
- slotCount: number().int().positive()
8335
- });
8336
- /**
8337
- * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
8338
- * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
8339
- * sample bytes plus the track parameters a consumer needs to interpret them.
8340
- *
8341
- * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
8342
- * Float32 PCM), so unlike video frames they ship their bytes INLINE over
8343
- * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
8344
- * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
8345
- * it.
8346
- */
8347
- var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
8348
- var DecodedAudioChunkSchema = object({
8349
- data: _instanceof(Uint8Array),
8350
- sampleRate: number().int().positive(),
8351
- channels: number().int().positive(),
8352
- timestamp: number(),
8353
- /**
8354
- * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
8355
- * byte, for any peer that never heard of this field. A coded window
8356
- * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
8357
- * subscription that DECLARED it accepts one, so absence can never mean
8358
- * "coded bytes a consumer will read as floats" (D455).
8359
- */
8360
- format: AudioChunkFormatSchema.optional()
8361
- });
8362
- /**
8363
- * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
8364
- * its bytes inline: a consumer asks the broker for the decoded audio-chunk
8365
- * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
8366
- * the consumer drains via `pullAudioChunks`.
8367
- */
8368
- var SubscribeAudioChunksInputSchema = object({
8369
- brokerId: string(),
8370
- /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
8371
- tag: string().optional(),
8372
- /**
8373
- * Byte formats this subscriber can READ, best first. The broker serves the
8374
- * chunk's own format when it is in this list and expands to `f32le`
8375
- * otherwise, so a subscriber is never handed bytes it cannot interpret.
8376
- *
8377
- * Absent (or without the source format) means `f32le` — the behaviour every
8378
- * subscriber had before D455, unchanged. This is the negotiation half of
8379
- * the source-bytes lever: it is what lets the broker and its consumers
8380
- * deploy one at a time across three nodes.
8381
- */
8382
- accept: array(AudioChunkFormatSchema).readonly().optional()
8383
- });
8384
- /** Result of `stream-broker.subscribeAudioChunks`. */
8385
- var SubscribeAudioChunksResultSchema = object({
8386
- /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
8387
- subscriptionId: string() });
8388
- var BrokerStatsSchema = object({
8389
- status: _enum([
8390
- "idle",
8391
- "connecting",
8392
- "streaming",
8393
- "error",
8394
- "stopped"
8395
- ]),
8396
- inputFps: number(),
8397
- encodedSubscribers: number(),
8398
- uptimeMs: number(),
8399
- bitrateKbps: number(),
8400
- idrIntervalMs: number(),
8401
- codec: string().optional(),
8402
- totalBytes: number(),
8403
- packetCount: number(),
8404
- rtspClients: number(),
8405
- pipeClients: number(),
8406
- preBufferSec: number(),
8407
- preBufferMs: number(),
8408
- preBufferPackets: number(),
8409
- /**
8410
- * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
8411
- * when the stream has no audio track or the broker is in cold start.
8412
- * `supported = false` means the codec was detected but the local
8413
- * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
8414
- * AAC pipeline wired). Surfaced in the UI device-overview so operators
8415
- * can pre-pick the audio-analysis model that matches the codec.
8416
- */
8417
- audio: object({
8418
- codec: string(),
8419
- sampleRate: number(),
8420
- channels: number(),
8421
- supported: boolean()
8422
- }).nullable().optional(),
8423
- /**
8424
- * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
8425
- * distributing none of the device's audio, on live or recording.
8426
- *
8427
- * Present so a silent camera can be told apart from a broken one on the
8428
- * stream panel itself, without cross-referencing the switch group: a
8429
- * broker holding an `audio` track descriptor while `audioMuted` is true is
8430
- * working exactly as asked. `audioMutedDropped` counts the audio units
8431
- * thrown away since the current dial — it is how you confirm from stats
8432
- * alone that the mute is on the packet path and not merely persisted.
8433
- */
8434
- audioMuted: boolean().optional(),
8435
- audioMutedDropped: number().optional()
8436
- });
8437
- /**
8438
- * Exporter-facing "profile restream" entry. Returned by
8439
- * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
8440
- * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
8441
- * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
8442
- * RTSP restream that aliases the profile's assigned source broker, so
8443
- * every consumer dialling a profile converges on the same single pull.
8444
- * `codec`/`resolution` describe the assigned SOURCE camStream (for the
8445
- * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
8446
- * (raw per-camStream, live-view only).
8447
- */
8448
- var ProfileRtspEntrySchema = object({
8449
- profile: CamProfileSchema,
8450
- /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
8451
- brokerId: string(),
8452
- url: string(),
8453
- mutedUrl: string(),
8454
- enabled: boolean(),
8455
- codec: string().optional(),
8456
- resolution: CamStreamResolutionSchema.optional()
8457
- });
8458
- /**
8459
- * The model weight formats a catalog entry can ship. Schema-free on purpose:
8460
- * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
8461
- * (the model downloader in `@camstack/system`) needs only the list and must
8462
- * reach it through `@camstack/types/addon` without building the cap schema
8463
- * graph (D28).
8464
- */
8465
- var MODEL_FORMATS = [
8466
- "onnx",
8467
- "coreml",
8468
- "openvino",
8469
- "tflite",
8470
- "pt",
8471
- "gguf"
8472
- ];
8473
8566
  object({
8474
8567
  field: string().min(1),
8475
8568
  on: literal("advance")
@@ -8558,6 +8651,407 @@ function optionalMethod(input, output, options) {
8558
8651
  providerOptional: true
8559
8652
  };
8560
8653
  }
8654
+ var READING_QUANTITIES = [
8655
+ "temperature",
8656
+ "pressure",
8657
+ "speed",
8658
+ "precipitation",
8659
+ "precipitation-rate",
8660
+ "length",
8661
+ "area",
8662
+ "illuminance",
8663
+ "irradiance",
8664
+ "power",
8665
+ "energy",
8666
+ "voltage",
8667
+ "current",
8668
+ "apparent-power",
8669
+ "humidity",
8670
+ "percentage",
8671
+ "concentration-volume",
8672
+ "concentration-mass",
8673
+ "concentration-ppb",
8674
+ "volume",
8675
+ "duration",
8676
+ "duration-ms",
8677
+ "information",
8678
+ "sound-level",
8679
+ "signal-level",
8680
+ "colour-temperature",
8681
+ "angle",
8682
+ "bitrate",
8683
+ "rate",
8684
+ "mass",
8685
+ "index",
8686
+ "ratio",
8687
+ "count",
8688
+ "none",
8689
+ "device"
8690
+ ];
8691
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8692
+ new Set(READING_QUANTITIES);
8693
+ /**
8694
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
8695
+ * (or an index / ratio / count). `device` means the cap has no single unit.
8696
+ */
8697
+ var QUANTITY_BASE = {
8698
+ temperature: "°C",
8699
+ pressure: "hPa",
8700
+ speed: "m/s",
8701
+ precipitation: "mm",
8702
+ "precipitation-rate": "mm/h",
8703
+ length: "m",
8704
+ area: "m²",
8705
+ illuminance: "lx",
8706
+ irradiance: "W/m²",
8707
+ power: "W",
8708
+ energy: "kWh",
8709
+ voltage: "V",
8710
+ current: "A",
8711
+ "apparent-power": "VA",
8712
+ humidity: "%",
8713
+ percentage: "%",
8714
+ "concentration-volume": "ppm",
8715
+ "concentration-mass": "µg/m³",
8716
+ "concentration-ppb": "ppb",
8717
+ volume: "L",
8718
+ duration: "s",
8719
+ "duration-ms": "ms",
8720
+ information: "B",
8721
+ "sound-level": "dBFS",
8722
+ "signal-level": "dBm",
8723
+ "colour-temperature": "mired",
8724
+ angle: "°",
8725
+ bitrate: "kbps",
8726
+ rate: "fps",
8727
+ mass: "kg",
8728
+ index: "none",
8729
+ ratio: "none",
8730
+ count: "none",
8731
+ none: "none",
8732
+ device: "device"
8733
+ };
8734
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8735
+ function baseUnitOf(quantity) {
8736
+ const symbol = QUANTITY_BASE[quantity];
8737
+ if (symbol === "none" || symbol === "device") return void 0;
8738
+ return symbol;
8739
+ }
8740
+ function readingMeta(quantity, extras) {
8741
+ return {
8742
+ quantity,
8743
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8744
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
8745
+ };
8746
+ }
8747
+ /** Shared Zod schemas used across streaming capabilities. */
8748
+ var CamProfileSchema = _enum([
8749
+ "high",
8750
+ "mid",
8751
+ "low"
8752
+ ]);
8753
+ /**
8754
+ * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
8755
+ *
8756
+ * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
8757
+ * that consumer owns". It lives beside `CamProfile` because a rung is the only
8758
+ * operator-facing way to name a stream: a cam-stream id is broker-internal and
8759
+ * moves when a slot is reassigned, so a stored id silently stops meaning what
8760
+ * the operator picked. Today's one user is the audio analyser, whose rule is
8761
+ * "the lowest AVAILABLE native rung" (D537).
8762
+ */
8763
+ var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
8764
+ var CamStreamKindSchema = _enum([
8765
+ "pull-rtsp",
8766
+ "pull-rtmp",
8767
+ "pull-http",
8768
+ "pull-flv",
8769
+ "pull-rfc4571",
8770
+ "push-annexb",
8771
+ "derived"
8772
+ ]);
8773
+ var CamStreamResolutionSchema = object({
8774
+ width: number().int().positive().meta(readingMeta("none")),
8775
+ height: number().int().positive().meta(readingMeta("none"))
8776
+ });
8777
+ var CameraStreamSchema = object({
8778
+ /** Stable, provider-assigned id unique within the (deviceId) scope. */
8779
+ camStreamId: string().min(1),
8780
+ deviceId: number().int().nonnegative(),
8781
+ kind: CamStreamKindSchema,
8782
+ /** Required for pull-* kinds. Ignored for push-annexb. */
8783
+ url: string().optional(),
8784
+ codec: string().optional(),
8785
+ resolution: CamStreamResolutionSchema.optional(),
8786
+ fps: number().positive().optional(),
8787
+ /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
8788
+ label: string().optional(),
8789
+ /**
8790
+ * Device-level features the publisher advertised (e.g. `battery-operated`).
8791
+ * The broker, snapshot orchestrator, and prebuffer manager all consult
8792
+ * this list to derive policy — relaxed stall watchdog for battery
8793
+ * cams, prebuffer off by default, longer snapshot rate-limit, etc.
8794
+ *
8795
+ * Single source of truth replacing per-stream flags like the
8796
+ * historical `allowStall`: if the publisher knows the camera is
8797
+ * battery-powered, every downstream service derives the right policy
8798
+ * from this list.
8799
+ */
8800
+ deviceFeatures: array(string()).optional(),
8801
+ /**
8802
+ * Whether this stream participates in the broker's automatic profile
8803
+ * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
8804
+ * use `false` when they want a stream to be SELECTABLE in the UI but not
8805
+ * picked by default — e.g. Reolink publishes its native Baichuan streams
8806
+ * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
8807
+ * mirrors as `autoEligible: false` (still pickable per slot, just not
8808
+ * the auto choice). Manual `assignProfile` calls remain valid for
8809
+ * non-eligible streams.
8810
+ */
8811
+ autoEligible: boolean().optional(),
8812
+ /**
8813
+ * The profile slot the PUBLISHER says this stream is: `low` for a stream
8814
+ * whose only sensible role is the low-bandwidth one, `high` for a main
8815
+ * stream. Consulted by the broker's automatic ranking
8816
+ * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
8817
+ * an operator's manual assignment still wins, and an absent hint is the
8818
+ * pixel-ranked default every camera had before this field existed.
8819
+ *
8820
+ * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
8821
+ * storage class is written for on its own (`recordings` is high|mid,
8822
+ * `recordingsLow` is low) and the one profile scrub does not read. The robot
8823
+ * camera's single 720p relay is a `low` in every respect except the slot the
8824
+ * heuristic put it in.
8825
+ */
8826
+ profileHint: CamProfileSchema.optional(),
8827
+ /**
8828
+ * Transport-specific opaque metadata. The broker passes it through to
8829
+ * the source reader without inspecting it. Currently used by
8830
+ * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
8831
+ * route RTP packets to the right depacketizer without an in-band
8832
+ * DESCRIBE phase). Other kinds typically leave it undefined.
8833
+ */
8834
+ metadata: record(string(), unknown()).optional()
8835
+ });
8836
+ var ProfileSlotStatusSchema = _enum([
8837
+ "unassigned",
8838
+ "idle",
8839
+ "connecting",
8840
+ "streaming",
8841
+ "error"
8842
+ ]);
8843
+ var ProfileSlotSchema = object({
8844
+ deviceId: number().int().nonnegative(),
8845
+ profile: CamProfileSchema,
8846
+ /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
8847
+ brokerId: string(),
8848
+ /** `null` when the profile is unassigned. */
8849
+ sourceCamStreamId: string().nullable(),
8850
+ status: ProfileSlotStatusSchema,
8851
+ resolution: CamStreamResolutionSchema.optional(),
8852
+ codec: string().optional(),
8853
+ preBufferSec: number().nonnegative().optional(),
8854
+ errorMessage: string().optional()
8855
+ });
8856
+ /**
8857
+ * Zod schema for StreamSourceEntry — the canonical stream descriptor
8858
+ * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
8859
+ */
8860
+ var StreamSourceEntrySchema$1 = object({
8861
+ id: string(),
8862
+ label: string(),
8863
+ protocol: _enum([
8864
+ "rtsp",
8865
+ "rtmp",
8866
+ "annexb",
8867
+ "http-mjpeg",
8868
+ "webrtc",
8869
+ "custom"
8870
+ ]),
8871
+ url: string().optional(),
8872
+ resolution: object({
8873
+ width: number(),
8874
+ height: number()
8875
+ }).readonly().optional(),
8876
+ fps: number().optional(),
8877
+ bitrate: number().optional(),
8878
+ codec: string().optional(),
8879
+ profileHint: CamProfileSchema.optional()
8880
+ });
8881
+ object({
8882
+ type: string(),
8883
+ url: string(),
8884
+ videoCodec: string().optional(),
8885
+ audioCodec: string().optional(),
8886
+ metadata: record(string(), unknown()).readonly().optional()
8887
+ });
8888
+ /**
8889
+ * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
8890
+ * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
8891
+ * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
8892
+ * (the runner's native-crop and pipeline-executor inputs) can carry handles
8893
+ * over tRPC / Moleculer.
8894
+ *
8895
+ * The `satisfies` assertion below pins this schema to the interface: if a
8896
+ * field is added to / removed from `FrameHandle` without a matching schema
8897
+ * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
8898
+ * `FrameHandle` and the build fails — no silent drift.
8899
+ */
8900
+ var FrameHandleSchema = object({
8901
+ shmId: string(),
8902
+ slot: number().int().nonnegative(),
8903
+ seq: number().int().nonnegative(),
8904
+ width: number().int().positive(),
8905
+ height: number().int().positive(),
8906
+ format: _enum([
8907
+ "jpeg",
8908
+ "rgb",
8909
+ "bgr",
8910
+ "yuv420",
8911
+ "gray"
8912
+ ]),
8913
+ pts: number(),
8914
+ byteLength: number().int().nonnegative(),
8915
+ nodeId: string(),
8916
+ slotCount: number().int().positive()
8917
+ });
8918
+ /**
8919
+ * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
8920
+ * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
8921
+ * sample bytes plus the track parameters a consumer needs to interpret them.
8922
+ *
8923
+ * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
8924
+ * Float32 PCM), so unlike video frames they ship their bytes INLINE over
8925
+ * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
8926
+ * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
8927
+ * it.
8928
+ */
8929
+ var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
8930
+ var DecodedAudioChunkSchema = object({
8931
+ data: _instanceof(Uint8Array),
8932
+ sampleRate: number().int().positive(),
8933
+ channels: number().int().positive(),
8934
+ timestamp: number(),
8935
+ /**
8936
+ * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
8937
+ * byte, for any peer that never heard of this field. A coded window
8938
+ * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
8939
+ * subscription that DECLARED it accepts one, so absence can never mean
8940
+ * "coded bytes a consumer will read as floats" (D455).
8941
+ */
8942
+ format: AudioChunkFormatSchema.optional()
8943
+ });
8944
+ /**
8945
+ * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
8946
+ * its bytes inline: a consumer asks the broker for the decoded audio-chunk
8947
+ * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
8948
+ * the consumer drains via `pullAudioChunks`.
8949
+ */
8950
+ var SubscribeAudioChunksInputSchema = object({
8951
+ brokerId: string(),
8952
+ /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
8953
+ tag: string().optional(),
8954
+ /**
8955
+ * Byte formats this subscriber can READ, best first. The broker serves the
8956
+ * chunk's own format when it is in this list and expands to `f32le`
8957
+ * otherwise, so a subscriber is never handed bytes it cannot interpret.
8958
+ *
8959
+ * Absent (or without the source format) means `f32le` — the behaviour every
8960
+ * subscriber had before D455, unchanged. This is the negotiation half of
8961
+ * the source-bytes lever: it is what lets the broker and its consumers
8962
+ * deploy one at a time across three nodes.
8963
+ */
8964
+ accept: array(AudioChunkFormatSchema).readonly().optional()
8965
+ });
8966
+ /** Result of `stream-broker.subscribeAudioChunks`. */
8967
+ var SubscribeAudioChunksResultSchema = object({
8968
+ /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
8969
+ subscriptionId: string() });
8970
+ var BrokerStatsSchema = object({
8971
+ status: _enum([
8972
+ "idle",
8973
+ "connecting",
8974
+ "streaming",
8975
+ "error",
8976
+ "stopped"
8977
+ ]),
8978
+ inputFps: number(),
8979
+ encodedSubscribers: number(),
8980
+ uptimeMs: number(),
8981
+ bitrateKbps: number(),
8982
+ idrIntervalMs: number(),
8983
+ codec: string().optional(),
8984
+ totalBytes: number(),
8985
+ packetCount: number(),
8986
+ rtspClients: number(),
8987
+ pipeClients: number(),
8988
+ preBufferSec: number(),
8989
+ preBufferMs: number(),
8990
+ preBufferPackets: number(),
8991
+ /**
8992
+ * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
8993
+ * when the stream has no audio track or the broker is in cold start.
8994
+ * `supported = false` means the codec was detected but the local
8995
+ * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
8996
+ * AAC pipeline wired). Surfaced in the UI device-overview so operators
8997
+ * can pre-pick the audio-analysis model that matches the codec.
8998
+ */
8999
+ audio: object({
9000
+ codec: string(),
9001
+ sampleRate: number(),
9002
+ channels: number(),
9003
+ supported: boolean()
9004
+ }).nullable().optional(),
9005
+ /**
9006
+ * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
9007
+ * distributing none of the device's audio, on live or recording.
9008
+ *
9009
+ * Present so a silent camera can be told apart from a broken one on the
9010
+ * stream panel itself, without cross-referencing the switch group: a
9011
+ * broker holding an `audio` track descriptor while `audioMuted` is true is
9012
+ * working exactly as asked. `audioMutedDropped` counts the audio units
9013
+ * thrown away since the current dial — it is how you confirm from stats
9014
+ * alone that the mute is on the packet path and not merely persisted.
9015
+ */
9016
+ audioMuted: boolean().optional(),
9017
+ audioMutedDropped: number().optional()
9018
+ });
9019
+ /**
9020
+ * Exporter-facing "profile restream" entry. Returned by
9021
+ * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
9022
+ * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
9023
+ * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
9024
+ * RTSP restream that aliases the profile's assigned source broker, so
9025
+ * every consumer dialling a profile converges on the same single pull.
9026
+ * `codec`/`resolution` describe the assigned SOURCE camStream (for the
9027
+ * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
9028
+ * (raw per-camStream, live-view only).
9029
+ */
9030
+ var ProfileRtspEntrySchema = object({
9031
+ profile: CamProfileSchema,
9032
+ /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
9033
+ brokerId: string(),
9034
+ url: string(),
9035
+ mutedUrl: string(),
9036
+ enabled: boolean(),
9037
+ codec: string().optional(),
9038
+ resolution: CamStreamResolutionSchema.optional()
9039
+ });
9040
+ /**
9041
+ * The model weight formats a catalog entry can ship. Schema-free on purpose:
9042
+ * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
9043
+ * (the model downloader in `@camstack/server/system`) needs only the list and must
9044
+ * reach it through `@camstack/types/addon` without building the cap schema
9045
+ * graph (D28).
9046
+ */
9047
+ var MODEL_FORMATS = [
9048
+ "onnx",
9049
+ "coreml",
9050
+ "openvino",
9051
+ "tflite",
9052
+ "pt",
9053
+ "gguf"
9054
+ ];
8561
9055
  /**
8562
9056
  * `addon-pages-source` — collection cap exposing per-provider raw page
8563
9057
  * declarations. Every addon that contributes a UI page registers a
@@ -8817,6 +9311,7 @@ var CAP_NAMES = Object.freeze([
8817
9311
  "mesh-network",
8818
9312
  "meter",
8819
9313
  "metrics-provider",
9314
+ "migrations",
8820
9315
  "model-convert",
8821
9316
  "model-distributor",
8822
9317
  "motion",
@@ -8849,6 +9344,7 @@ var CAP_NAMES = Object.freeze([
8849
9344
  "plate-gallery",
8850
9345
  "platform-probe",
8851
9346
  "power-meter",
9347
+ "power-source",
8852
9348
  "presence",
8853
9349
  "pressure-sensor",
8854
9350
  "privacy-mask",
@@ -10200,6 +10696,13 @@ Object.freeze({
10200
10696
  "valueType": "number"
10201
10697
  }
10202
10698
  ],
10699
+ "power-source": [{
10700
+ "path": "switchOn",
10701
+ "valueType": "boolean"
10702
+ }, {
10703
+ "path": "offSeconds",
10704
+ "valueType": "number"
10705
+ }],
10203
10706
  "presence": [
10204
10707
  {
10205
10708
  "path": "state",
@@ -10711,369 +11214,7 @@ object({
10711
11214
  params: record(string(), union([string(), number()])).optional()
10712
11215
  });
10713
11216
  //#endregion
10714
- //#region ../types/dist/err-msg-BD5buxvT.mjs
10715
- var DeviceType = /* @__PURE__ */ function(DeviceType) {
10716
- DeviceType["Camera"] = "camera";
10717
- DeviceType["Hub"] = "hub";
10718
- DeviceType["Light"] = "light";
10719
- DeviceType["Siren"] = "siren";
10720
- DeviceType["Switch"] = "switch";
10721
- DeviceType["Sensor"] = "sensor";
10722
- DeviceType["Thermostat"] = "thermostat";
10723
- /** Air-conditioner / heat-pump climate device (HVAC) — shares the
10724
- * `climate-control` cap surface with `Thermostat` but renders a
10725
- * dedicated AC-appropriate control UI (mode chips, fan speed,
10726
- * independent vertical/horizontal swing). Sources: native Gree, and
10727
- * reusable by other AC integrations. */
10728
- DeviceType["Climate"] = "climate";
10729
- DeviceType["Button"] = "button";
10730
- /** Generic stateless event emitter — carries a device's EXACT declared
10731
- * event vocabulary verbatim (no normalization). Installed with the
10732
- * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
10733
- * HA bus events (e.g. `zha_event`, generic). */
10734
- DeviceType["EventEmitter"] = "event-emitter";
10735
- /** Firmware/software update entity — current vs available version,
10736
- * updatable flag, update state, and an install action. Installed with
10737
- * the `update` cap. Sources: Homematic firmware-update channels (and
10738
- * reusable by other providers, e.g. HA `update.*` entities). */
10739
- DeviceType["Update"] = "update";
10740
- DeviceType["Generic"] = "generic";
10741
- /** Generic notification delivery target (HA `notify.<service>`, future
10742
- * Telegram / Discord / ntfy / SMTP, …). One device per delivery
10743
- * endpoint; the `notifier` cap defines the send surface. */
10744
- DeviceType["Notifier"] = "notifier";
10745
- /** Pre-recorded action sequence with optional parameters
10746
- * (HA `script.*`). Runnable via `script-runner` cap. */
10747
- DeviceType["Script"] = "script";
10748
- /** Automation rule (HA `automation.*`) — enable/disable + manual
10749
- * trigger surface exposed via `automation-control` cap. */
10750
- DeviceType["Automation"] = "automation";
10751
- /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
10752
- DeviceType["Lock"] = "lock";
10753
- /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
10754
- * `valve.*`). `cover` cap with sub-roles for variant. */
10755
- DeviceType["Cover"] = "cover";
10756
- /** Pipe / water / gas valve with open/close/stop and optional
10757
- * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
10758
- * modelled on the same open/closed lifecycle. */
10759
- DeviceType["Valve"] = "valve";
10760
- /** Humidifier / dehumidifier with on/off + target humidity + mode
10761
- * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
10762
- * modelled on the same target / mode lifecycle. */
10763
- DeviceType["Humidifier"] = "humidifier";
10764
- /** Water heater / boiler with target temperature + operation mode +
10765
- * away mode (HA `water_heater.*`). `water-heater` cap — a
10766
- * climate-family actuator. */
10767
- DeviceType["WaterHeater"] = "water-heater";
10768
- /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
10769
- DeviceType["Fan"] = "fan";
10770
- /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
10771
- * the camera surface — those use `Camera`. `media-player` cap. */
10772
- DeviceType["MediaPlayer"] = "media-player";
10773
- /** Security panel / alarm system (HA `alarm_control_panel.*`).
10774
- * `alarm-panel` cap. */
10775
- DeviceType["AlarmPanel"] = "alarm-panel";
10776
- /** Generic user-settable input (HA `number` / `input_number` / `select`
10777
- * / `input_select` / `text` / `input_text` / `input_datetime`).
10778
- * Sub-type via `DeviceRole`: NumericControl / SelectControl /
10779
- * TextControl / DateTimeControl. */
10780
- DeviceType["Control"] = "control";
10781
- /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
10782
- * `presence` cap. */
10783
- DeviceType["Presence"] = "presence";
10784
- /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
10785
- * `weather` cap. */
10786
- DeviceType["Weather"] = "weather";
10787
- /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
10788
- DeviceType["Vacuum"] = "vacuum";
10789
- /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
10790
- * `lawn-mower-control` cap. */
10791
- DeviceType["LawnMower"] = "lawn-mower";
10792
- /** Physical HA device group — parent container for entity-children
10793
- * adopted from a single HA device entry. Not renderable as a
10794
- * standalone device; exists only to anchor child entities. */
10795
- DeviceType["Container"] = "container";
10796
- /** Single still-image entity (HA `image.*`). Read-only display of an
10797
- * `entity_picture` signed URL the browser loads directly. `image` cap. */
10798
- DeviceType["Image"] = "image";
10799
- /** Smart pet feeder — cloud-connected food dispenser with a bowl food
10800
- * level, battery, desiccant life, feeding state and manual-feed /
10801
- * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
10802
- * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
10803
- * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
10804
- * integrations sharing the same food/desiccant/hopper surface. */
10805
- DeviceType["PetFeeder"] = "pet-feeder";
10806
- /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
10807
- * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
10808
- * of a container and is BUILT as a composed device (D967). */
10809
- DeviceType["PetFountain"] = "pet-fountain";
10810
- /** Built-in dishwasher — program lifecycle, door / remote-start gates,
10811
- * salt and rinse-aid levels. Installed with the `dishwasher` cap.
10812
- * Sources: native Bosch Home Connect, reusable by similar integrations.
10813
- * A plug-built dishwasher carries running-state and power instead. */
10814
- DeviceType["Dishwasher"] = "dishwasher";
10815
- /** Washing machine. A plug-built one is running while its power stays
10816
- * above a threshold. No program cap — that surface is the dishwasher's. */
10817
- DeviceType["Washer"] = "washer";
10818
- /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
10819
- DeviceType["Dryer"] = "dryer";
10820
- /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
10821
- * other radio-to-IP bridges). It carries the box's own facts (firmware,
10822
- * address, connection state) and the actions the box really supports
10823
- * (`reboot`, `device-admin-link`). Products are NOT its children — they are
10824
- * adopted beside it — and its connection settings stay on the integration. */
10825
- DeviceType["Gateway"] = "gateway";
10826
- return DeviceType;
10827
- }({});
10828
- var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
10829
- DeviceFeature["BatteryOperated"] = "battery-operated";
10830
- DeviceFeature["Rebootable"] = "rebootable";
10831
- /**
10832
- * Device supports an on-demand re-sync of its derived spec with its
10833
- * upstream source — drives the generic Re-sync button. The owning
10834
- * provider implements the action via the `device-adoption.resync` cap.
10835
- */
10836
- DeviceFeature["Resyncable"] = "resyncable";
10837
- DeviceFeature["NativeSnapshot"] = "native-snapshot";
10838
- DeviceFeature["DoorbellButton"] = "doorbell-button";
10839
- DeviceFeature["TwoWayAudio"] = "two-way-audio";
10840
- DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
10841
- /**
10842
- * Camera supports the on-firmware autotrack subsystem (subject-
10843
- * following). Distinct from `PanTiltZoom` because not every PTZ
10844
- * camera ships autotrack — the admin UI uses this flag to gate
10845
- * the autotrack toggle / settings card without re-deriving from
10846
- * the cap registry. Mirrors `ptz-autotrack` cap registration:
10847
- * driver sets this feature when probe confirms the firmware
10848
- * surface, and registers the cap in the same code path.
10849
- */
10850
- DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
10851
- /**
10852
- * Accessory exposes a "trigger on motion" toggle — the parent camera's
10853
- * motion detection automatically activates this device. Mirrors
10854
- * `motion-trigger` cap registration: drivers set this feature in the
10855
- * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
10856
- *
10857
- * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
10858
- * fast scalar without binding fetch), notifier rules, and `listAll`
10859
- * filters that want "all devices with on-motion behaviour".
10860
- */
10861
- DeviceFeature["MotionTrigger"] = "motion-trigger";
10862
- /** Light supports rgb-triplet color via `color` cap. */
10863
- DeviceFeature["LightColorRgb"] = "light-color-rgb";
10864
- /** Light supports HSV color via `color` cap. */
10865
- DeviceFeature["LightColorHsv"] = "light-color-hsv";
10866
- /** Light supports color-temperature (mired) via `color` cap. */
10867
- DeviceFeature["LightColorMired"] = "light-color-mired";
10868
- /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
10869
- * targetHigh). Gates the range slider UI. */
10870
- DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
10871
- /** Thermostat exposes target humidity and/or current humidity
10872
- * readings. Gates the humidity controls. */
10873
- DeviceFeature["ClimateHumidity"] = "climate-humidity";
10874
- /** Thermostat exposes a fan-mode selector. */
10875
- DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
10876
- /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
10877
- DeviceFeature["ClimatePreset"] = "climate-preset";
10878
- /** Thermostat exposes a vertical louver swing toggle. Gates the
10879
- * vertical-swing switch in the climate UI. Independent of horizontal. */
10880
- DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
10881
- /** Thermostat exposes a horizontal louver swing toggle. Gates the
10882
- * horizontal-swing switch in the climate UI. Independent of vertical. */
10883
- DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
10884
- /** Cover exposes intermediate position control (0..100). Gates the
10885
- * position slider UI. */
10886
- DeviceFeature["CoverPositionable"] = "cover-positionable";
10887
- /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
10888
- DeviceFeature["CoverTilt"] = "cover-tilt";
10889
- /** Valve exposes intermediate position control (0..100). Gates the
10890
- * position slider / drag surface UI. */
10891
- DeviceFeature["ValvePositionable"] = "valve-positionable";
10892
- /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
10893
- DeviceFeature["FanSpeed"] = "fan-speed";
10894
- /** Fan exposes a preset mode selector. */
10895
- DeviceFeature["FanPreset"] = "fan-preset";
10896
- /** Fan exposes blade direction (forward/reverse) — typical of
10897
- * ceiling fans. */
10898
- DeviceFeature["FanDirection"] = "fan-direction";
10899
- /** Fan exposes an oscillation toggle. */
10900
- DeviceFeature["FanOscillating"] = "fan-oscillating";
10901
- /** Lock requires a PIN code on lock/unlock. Gates the code-entry
10902
- * field on the UI lock-controls panel. */
10903
- DeviceFeature["LockPinRequired"] = "lock-pin-required";
10904
- /** Lock supports a latch-release ("open door") action distinct from
10905
- * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
10906
- * `supported_features`. Gates the Open Door button in the UI. */
10907
- DeviceFeature["LockOpen"] = "lock-open";
10908
- /** Media player exposes a seek-to-position surface. */
10909
- DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
10910
- /** Media player exposes a volume-level setter. */
10911
- DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
10912
- /** Media player exposes a mute toggle distinct from volume=0. */
10913
- DeviceFeature["MediaPlayerMute"] = "media-player-mute";
10914
- /** Media player exposes a shuffle toggle. */
10915
- DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
10916
- /** Media player exposes a repeat mode (off / all / one). */
10917
- DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
10918
- /** Media player exposes a source / input selector. */
10919
- DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
10920
- /** Media player exposes a play-arbitrary-media surface (URL / id). */
10921
- DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
10922
- /** Media player exposes next-track. */
10923
- DeviceFeature["MediaPlayerNext"] = "media-player-next";
10924
- /** Media player exposes previous-track. */
10925
- DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
10926
- /** Media player exposes stop distinct from pause. */
10927
- DeviceFeature["MediaPlayerStop"] = "media-player-stop";
10928
- /** Alarm panel requires a PIN code on arm/disarm. */
10929
- DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
10930
- /** Presence device carries GPS coordinates (lat/lng/accuracy) in
10931
- * addition to a textual location. */
10932
- DeviceFeature["PresenceGps"] = "presence-gps";
10933
- /** Notifier accepts an inline / URL image attachment. */
10934
- DeviceFeature["NotifierImage"] = "notifier-image";
10935
- /** Notifier accepts a priority hint (high/normal/low). */
10936
- DeviceFeature["NotifierPriority"] = "notifier-priority";
10937
- /** Notifier accepts a free-form `data` payload for platform-specific
10938
- * fields. */
10939
- DeviceFeature["NotifierData"] = "notifier-data";
10940
- /** Notifier supports interactive action buttons / callbacks. */
10941
- DeviceFeature["NotifierActions"] = "notifier-actions";
10942
- /** Notifier supports per-call recipient targeting (multi-user). */
10943
- DeviceFeature["NotifierRecipients"] = "notifier-recipients";
10944
- /** Script runner accepts a variables map on each run invocation. */
10945
- DeviceFeature["ScriptVariables"] = "script-variables";
10946
- /** Automation `trigger` accepts a skipCondition flag — fires the
10947
- * automation's actions while bypassing its condition block. */
10948
- DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
10949
- /** Robot vacuum exposes a live cleaning map (image child). Gates the
10950
- * map tile in the vacuum UI. */
10951
- DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
10952
- /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
10953
- * switches group. */
10954
- DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
10955
- /** Robot mower exposes a live mowing map (SVG image child). Gates the
10956
- * mower map tile. */
10957
- DeviceFeature["MowerHasMap"] = "mower-has-map";
10958
- /** Robot mower exposes targeted mowing (all-area / zones / edges /
10959
- * spots) — gates the Mow action + map selectors. */
10960
- DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
10961
- return DeviceFeature;
10962
- }({});
10963
- /**
10964
- * Semantic role a device plays within its parent. Populated by driver
10965
- * addons when creating accessory devices (Reolink siren/floodlight/
10966
- * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
10967
- * admin UI to pick icons, labels, and widgets — a `Switch` with
10968
- * `role: Floodlight` renders as a bulb with a brightness slider,
10969
- * whereas a `Switch` with `role: Siren` renders as a klaxon.
10970
- *
10971
- * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
10972
- * sqlite as a nullable TEXT column — old rows keep working unchanged.
10973
- */
10974
- var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
10975
- DeviceRole["Siren"] = "siren";
10976
- DeviceRole["Floodlight"] = "floodlight";
10977
- DeviceRole["Spotlight"] = "spotlight";
10978
- DeviceRole["PirSensor"] = "pir-sensor";
10979
- DeviceRole["Chime"] = "chime";
10980
- DeviceRole["Autotrack"] = "autotrack";
10981
- DeviceRole["Nightvision"] = "nightvision";
10982
- DeviceRole["PrivacyMask"] = "privacy-mask";
10983
- DeviceRole["Doorbell"] = "doorbell";
10984
- /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
10985
- DeviceRole["CoverBlind"] = "cover-blind";
10986
- /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
10987
- DeviceRole["CoverShutter"] = "cover-shutter";
10988
- /** Exterior awning. HA `device_class: awning`. */
10989
- DeviceRole["CoverAwning"] = "cover-awning";
10990
- /** Curtain / drape. HA `device_class: curtain`. */
10991
- DeviceRole["CoverCurtain"] = "cover-curtain";
10992
- /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
10993
- DeviceRole["CoverGarage"] = "cover-garage";
10994
- /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
10995
- DeviceRole["CoverGate"] = "cover-gate";
10996
- /** Door. HA `device_class: door`. A security actuator: exports gate it. */
10997
- DeviceRole["CoverDoor"] = "cover-door";
10998
- /** Window. HA `device_class: window`. */
10999
- DeviceRole["CoverWindow"] = "cover-window";
11000
- /** Virtual HA toggle (input_boolean.*) — distinguishable from a
11001
- * real Switch device for UI rendering / export adapters. */
11002
- DeviceRole["BinaryHelper"] = "binary-helper";
11003
- /** A smart plug: a `switch` whose source says it is an outlet (HA
11004
- * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
11005
- DeviceRole["Outlet"] = "outlet";
11006
- /** Generic motion / occupancy / moving event source. Distinct from
11007
- * the camera accessory PirSensor role: that one is a camera child;
11008
- * this is a standalone HA / 3rd-party motion sensor. */
11009
- DeviceRole["MotionSensor"] = "motion-sensor";
11010
- DeviceRole["ContactSensor"] = "contact-sensor";
11011
- DeviceRole["LeakSensor"] = "leak-sensor";
11012
- DeviceRole["SmokeSensor"] = "smoke-sensor";
11013
- DeviceRole["COSensor"] = "co-sensor";
11014
- DeviceRole["GasSensor"] = "gas-sensor";
11015
- DeviceRole["TamperSensor"] = "tamper-sensor";
11016
- DeviceRole["VibrationSensor"] = "vibration-sensor";
11017
- DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
11018
- DeviceRole["SoundSensor"] = "sound-sensor";
11019
- /** Fallback for `binary_sensor` without a known `device_class`. */
11020
- DeviceRole["BinarySensor"] = "binary-sensor";
11021
- DeviceRole["TemperatureSensor"] = "temperature-sensor";
11022
- DeviceRole["HumiditySensor"] = "humidity-sensor";
11023
- DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
11024
- DeviceRole["PressureSensor"] = "pressure-sensor";
11025
- /** Wind speed or direction (weather-station `wind-sensor` cap). */
11026
- DeviceRole["WindSensor"] = "wind-sensor";
11027
- /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
11028
- DeviceRole["RainSensor"] = "rain-sensor";
11029
- /** UV index (weather-station `uv-sensor` cap). */
11030
- DeviceRole["UvSensor"] = "uv-sensor";
11031
- /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
11032
- * Distinct from AmbientLightSensor (lux). */
11033
- DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
11034
- /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
11035
- DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
11036
- DeviceRole["PowerSensor"] = "power-sensor";
11037
- DeviceRole["EnergySensor"] = "energy-sensor";
11038
- DeviceRole["VoltageSensor"] = "voltage-sensor";
11039
- DeviceRole["CurrentSensor"] = "current-sensor";
11040
- DeviceRole["AirQualitySensor"] = "air-quality-sensor";
11041
- /** Battery level (numeric % via `sensor` OR low-bool via
11042
- * `binary_sensor` — the cap distinguishes via the value type). */
11043
- DeviceRole["BatterySensor"] = "battery-sensor";
11044
- /** Fallback for `sensor` numeric without a known `device_class`. */
11045
- DeviceRole["NumericSensor"] = "numeric-sensor";
11046
- /** String / enum state (HA `sensor` with `state_class: enum` or
11047
- * `attributes.options`). */
11048
- DeviceRole["EnumSensor"] = "enum-sensor";
11049
- /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
11050
- * or `date`). The slice carries the raw ISO string verbatim (hosted on
11051
- * the `enum-sensor` cap); the UI renders it locale-formatted. */
11052
- DeviceRole["DateTimeSensor"] = "datetime-sensor";
11053
- /** Last-resort fallback when nothing else matches. */
11054
- DeviceRole["GenericSensor"] = "generic-sensor";
11055
- DeviceRole["NumericControl"] = "numeric-control";
11056
- DeviceRole["SelectControl"] = "select-control";
11057
- DeviceRole["TextControl"] = "text-control";
11058
- DeviceRole["DateTimeControl"] = "datetime-control";
11059
- /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
11060
- * rich features (image, priority, channel routing). */
11061
- DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
11062
- /** Chat / messaging service (HA `notify.telegram_*`,
11063
- * `notify.discord_*`, etc.). */
11064
- DeviceRole["MessagingNotifier"] = "messaging-notifier";
11065
- /** Email-based delivery (HA `notify.smtp`, etc.). */
11066
- DeviceRole["EmailNotifier"] = "email-notifier";
11067
- /** Fallback when the notifier service name doesn't match a known
11068
- * pattern. */
11069
- DeviceRole["GenericNotifier"] = "generic-notifier";
11070
- /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
11071
- * cap. A `DeviceType.Generic` device. */
11072
- DeviceRole["IrTransmitter"] = "ir-transmitter";
11073
- return DeviceRole;
11074
- }({});
11075
- //#endregion
11076
- //#region ../types/dist/composition-C7WdThmP.mjs
11217
+ //#region ../types/dist/composition-BBzVf2xd.mjs
11077
11218
  /**
11078
11219
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11079
11220
  *
@@ -12292,6 +12433,71 @@ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
12292
12433
  var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
12293
12434
  new Set(EVENT_OWNER_TYPES);
12294
12435
  /**
12436
+ * What a published package needs from the rest of the install (D989).
12437
+ *
12438
+ * One published framework package (`@camstack/server`) carries the kernel, the
12439
+ * builtins and the capability contracts, so "which addon runs on which
12440
+ * framework" is a question of version, and the answer is DECLARED by the
12441
+ * package that knows it:
12442
+ *
12443
+ * - an addon says the oldest server it runs on (`requires.server: ">=X"`);
12444
+ * - the server says the oldest addon it works with
12445
+ * (`requires.addons: { "@camstack/addon-x": ">=Y" }`) and the oldest shell
12446
+ * around it (`requires.wrapper: { desktop, image }`);
12447
+ * - a wrapper (the desktop app, the container starter) says which server
12448
+ * range it can host, and reports its own kind and version.
12449
+ *
12450
+ * The publish workflow STAMPS these after the version bump
12451
+ * (`scripts/ci/stamp-requires.mjs`); nobody types a number by hand.
12452
+ *
12453
+ * A requirement that cannot be checked is NOT met: a shell that reports no
12454
+ * version counts as below the minimum, and an unreadable running version is
12455
+ * named in the result (a failed read is never a pass).
12456
+ */
12457
+ /** `>=MAJOR.MINOR.PATCH[-prerelease]` — the only range shape a requirement uses. */
12458
+ var MinVersionSchema = string().regex(/^>=\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, "a minimum is written \">=X.Y.Z\"");
12459
+ object({
12460
+ /** The oldest `@camstack/server` this package runs on. */
12461
+ server: MinVersionSchema.optional(),
12462
+ /** The oldest installed version of each named addon package. */
12463
+ addons: record(string(), MinVersionSchema).optional(),
12464
+ /** The oldest shell around the server, per shell kind. */
12465
+ wrapper: object({
12466
+ desktop: MinVersionSchema.optional(),
12467
+ image: MinVersionSchema.optional()
12468
+ }).optional()
12469
+ });
12470
+ /** One unmet requirement; each names the versions on both sides. */
12471
+ var UnmetRequirementSchema = discriminatedUnion("kind", [
12472
+ object({
12473
+ kind: literal("server-too-old"),
12474
+ required: string(),
12475
+ /** `null` — the running server version could not be read. */
12476
+ running: string().nullable()
12477
+ }),
12478
+ object({
12479
+ kind: literal("addon-too-old"),
12480
+ packageName: string(),
12481
+ required: string(),
12482
+ /** `null` — the addon is not installed (or its version is unreadable). */
12483
+ running: string().nullable()
12484
+ }),
12485
+ object({
12486
+ kind: literal("wrapper-too-old"),
12487
+ wrapper: _enum(["desktop", "image"]),
12488
+ required: string(),
12489
+ /** `null` — the wrapper reported no version. */
12490
+ running: string().nullable()
12491
+ })
12492
+ ]);
12493
+ var RequiredAddonUpdateSchema = object({
12494
+ packageName: string(),
12495
+ /** The minimum the target server declares. */
12496
+ required: string(),
12497
+ /** What this node has; `null` when the addon is not installed. */
12498
+ installed: string().nullable()
12499
+ });
12500
+ /**
12295
12501
  * Which integration instance a settings form is for. An addon that serves several instances
12296
12502
  * (`instanceMode: multiple`) can show a different form per instance — an NVR integration
12297
12503
  * needs its connection, the default integration needs the addon's own sections.
@@ -12766,6 +12972,613 @@ var CameraSwitchGroupSchema = object({
12766
12972
  fetchedAt: number()
12767
12973
  });
12768
12974
  /**
12975
+ * Query filter for settings-store collections.
12976
+ */
12977
+ var QueryFilterSchema = object({
12978
+ where: record(string(), unknown()).optional(),
12979
+ whereIn: record(string(), array(unknown())).optional(),
12980
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
12981
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
12982
+ whereNot: record(string(), unknown()).optional(),
12983
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
12984
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
12985
+ whereNotIn: record(string(), array(unknown())).optional(),
12986
+ orderBy: object({
12987
+ field: string(),
12988
+ direction: _enum(["asc", "desc"])
12989
+ }).optional(),
12990
+ limit: number().optional(),
12991
+ offset: number().optional()
12992
+ });
12993
+ /**
12994
+ * The predicate half of a filter, for BULK MUTATIONS.
12995
+ *
12996
+ * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
12997
+ * meaning for a statement that rewrites a set, and accepting them would invite
12998
+ * a caller to believe `limit` bounds the damage. Every field is optional here
12999
+ * only so the shape stays composable — the implementation REJECTS a filter
13000
+ * that compiles to no predicate, because that is the whole collection.
13001
+ */
13002
+ var MutationFilterSchema = object({
13003
+ where: record(string(), unknown()).optional(),
13004
+ whereIn: record(string(), array(unknown())).optional(),
13005
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
13006
+ whereNot: record(string(), unknown()).optional(),
13007
+ whereNotIn: record(string(), array(unknown())).optional()
13008
+ });
13009
+ /**
13010
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
13011
+ *
13012
+ * `as` names the slot in the result, so the SAME column may be asked twice with
13013
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
13014
+ * a `Record<column, op>` shape could not express.
13015
+ */
13016
+ var AggregateFieldSchema = object({
13017
+ /** Result key. */
13018
+ as: string().min(1),
13019
+ /** Column to aggregate. Must be a real column of a declared collection. */
13020
+ field: string().min(1),
13021
+ op: _enum([
13022
+ "sum",
13023
+ "min",
13024
+ "max"
13025
+ ])
13026
+ });
13027
+ /**
13028
+ * `COUNT(*)` plus one number per requested field.
13029
+ *
13030
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
13031
+ * that really is 0 are different facts, and an accounting caller that renders
13032
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
13033
+ */
13034
+ var AggregateResultSchema = object({
13035
+ count: number().int(),
13036
+ values: record(string(), number().nullable())
13037
+ });
13038
+ /** A single stored record: `{ id, data }`. */
13039
+ var SettingsRecordSchema = object({
13040
+ id: string(),
13041
+ data: record(string(), unknown())
13042
+ });
13043
+ /**
13044
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
13045
+ * optional.
13046
+ *
13047
+ * A separate schema rather than loosening the shared one: every other method
13048
+ * on this cap addresses a row BY its id, and making that field optional
13049
+ * everywhere would turn a forgotten key into a silently generated one on
13050
+ * `update` and `delete` as well.
13051
+ */
13052
+ var BulkRecordSchema = object({
13053
+ id: string().optional(),
13054
+ data: record(string(), unknown())
13055
+ });
13056
+ /**
13057
+ * The database FILES one `sqlite-settings` engine process owns (D851).
13058
+ * Closed on purpose: a file is a thing the engine opens, backs up and gives a
13059
+ * serving thread, so a new one is a code change, never a string a caller
13060
+ * invents. `settings` is `camstack.db` and the default for every collection
13061
+ * that names no file.
13062
+ */
13063
+ var DataStoreDatabaseSchema = _enum([
13064
+ "settings",
13065
+ "events",
13066
+ "history"
13067
+ ]);
13068
+ DataStoreDatabaseSchema.options;
13069
+ /**
13070
+ * One write inside a {@link settingsStoreCapability.methods.transact} call.
13071
+ * The three that a "replace this set, bump that revision" write needs, plus
13072
+ * the batched upsert a rollup writer needs.
13073
+ */
13074
+ var TransactOpSchema = discriminatedUnion("op", [
13075
+ object({
13076
+ op: literal("deleteWhere"),
13077
+ collection: string(),
13078
+ filter: MutationFilterSchema
13079
+ }),
13080
+ object({
13081
+ op: literal("insertMany"),
13082
+ collection: string(),
13083
+ records: array(BulkRecordSchema).readonly()
13084
+ }),
13085
+ object({
13086
+ op: literal("update"),
13087
+ collection: string(),
13088
+ id: string(),
13089
+ data: record(string(), unknown())
13090
+ }),
13091
+ object({
13092
+ op: literal("upsertMany"),
13093
+ collection: string(),
13094
+ conflict: array(string()).readonly(),
13095
+ records: array(BulkRecordSchema).readonly()
13096
+ })
13097
+ ]);
13098
+ /**
13099
+ * The precondition of a transaction: the row `key` of `collection` must hold
13100
+ * `equals` in `column`, read INSIDE the transaction.
13101
+ */
13102
+ var TransactGuardSchema = object({
13103
+ collection: string(),
13104
+ key: string(),
13105
+ column: string(),
13106
+ equals: unknown()
13107
+ });
13108
+ /**
13109
+ * Column declaration for a structured (SQL-backed) collection.
13110
+ *
13111
+ * Logical types — the backend translates each to the matching SQLite
13112
+ * storage class and handles per-type marshaling:
13113
+ * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
13114
+ * - `JSON` — TEXT under the hood; serialised on write, parsed on read
13115
+ * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
13116
+ */
13117
+ var CollectionColumnSchema = object({
13118
+ name: string(),
13119
+ type: _enum([
13120
+ "TEXT",
13121
+ "INTEGER",
13122
+ "REAL",
13123
+ "JSON",
13124
+ "BOOLEAN"
13125
+ ]),
13126
+ primaryKey: boolean().optional(),
13127
+ notNull: boolean().optional(),
13128
+ unique: boolean().optional(),
13129
+ /**
13130
+ * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
13131
+ * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
13132
+ * collection surface simply cannot express their existing tables — which is
13133
+ * why they were still on the uncapped `table*` API.
13134
+ */
13135
+ defaultValue: union([
13136
+ string(),
13137
+ number(),
13138
+ boolean()
13139
+ ]).optional()
13140
+ });
13141
+ var CollectionIndexSchema = object({
13142
+ name: string(),
13143
+ columns: array(string()).readonly(),
13144
+ unique: boolean().optional()
13145
+ });
13146
+ /**
13147
+ * The input of `declareCollection` — ONE schema for the door and for the
13148
+ * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
13149
+ * the payload untouched).
13150
+ *
13151
+ * - `database` — the file the collection lives in (D851). Absent means
13152
+ * `settings`. A declaration naming a file other than the one the table is
13153
+ * already in is REFUSED by the engine, never moved.
13154
+ * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
13155
+ * as `primaryKey: true` on that column. A composite-keyed collection has no
13156
+ * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
13157
+ * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
13158
+ * address one row by id are refused.
13159
+ * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
13160
+ */
13161
+ var DeclareCollectionInputSchema = object({
13162
+ namespace: string().optional(),
13163
+ collection: string(),
13164
+ columns: array(CollectionColumnSchema).readonly(),
13165
+ indexes: array(CollectionIndexSchema).readonly().optional(),
13166
+ database: DataStoreDatabaseSchema.optional(),
13167
+ primaryKey: array(string()).readonly().optional(),
13168
+ withoutRowid: boolean().optional()
13169
+ });
13170
+ method(object({
13171
+ namespace: string().optional(),
13172
+ collection: string(),
13173
+ key: string()
13174
+ }), unknown(), { auth: "admin" }), method(object({
13175
+ namespace: string().optional(),
13176
+ collection: string(),
13177
+ key: string(),
13178
+ value: unknown()
13179
+ }), _void(), {
13180
+ kind: "mutation",
13181
+ auth: "admin"
13182
+ }), method(object({
13183
+ namespace: string().optional(),
13184
+ collection: string(),
13185
+ filter: QueryFilterSchema.optional(),
13186
+ columns: array(string()).readonly().optional()
13187
+ }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
13188
+ namespace: string().optional(),
13189
+ collection: string(),
13190
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
13191
+ * index on the collection. Never empty. */
13192
+ conflict: array(string()).readonly(),
13193
+ record: BulkRecordSchema
13194
+ }), object({
13195
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
13196
+ * row's own id on an update. */
13197
+ id: union([string(), number()]) }), {
13198
+ kind: "mutation",
13199
+ auth: "admin"
13200
+ }), method(object({
13201
+ namespace: string().optional(),
13202
+ collection: string(),
13203
+ conflict: array(string()).readonly(),
13204
+ records: array(BulkRecordSchema).readonly()
13205
+ }), object({ upserted: number().int() }), {
13206
+ kind: "mutation",
13207
+ auth: "admin"
13208
+ }), method(object({
13209
+ namespace: string().optional(),
13210
+ collection: string(),
13211
+ record: object({
13212
+ id: string().optional(),
13213
+ data: record(string(), unknown())
13214
+ })
13215
+ }), object({
13216
+ /**
13217
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
13218
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
13219
+ * primary key — which is the only place an auto key is knowable.
13220
+ */
13221
+ id: union([string(), number()]) }), {
13222
+ kind: "mutation",
13223
+ auth: "admin"
13224
+ }), method(object({
13225
+ namespace: string().optional(),
13226
+ collection: string(),
13227
+ records: array(BulkRecordSchema).readonly()
13228
+ }), object({ inserted: number().int() }), {
13229
+ kind: "mutation",
13230
+ auth: "admin"
13231
+ }), method(object({
13232
+ namespace: string().optional(),
13233
+ guard: TransactGuardSchema.optional(),
13234
+ ops: array(TransactOpSchema).readonly()
13235
+ }), object({
13236
+ applied: boolean(),
13237
+ current: unknown().optional()
13238
+ }), {
13239
+ kind: "mutation",
13240
+ auth: "admin"
13241
+ }), method(object({
13242
+ namespace: string().optional(),
13243
+ collection: string(),
13244
+ id: string(),
13245
+ data: record(string(), unknown())
13246
+ }), _void(), {
13247
+ kind: "mutation",
13248
+ auth: "admin"
13249
+ }), method(object({
13250
+ namespace: string().optional(),
13251
+ collection: string(),
13252
+ key: string()
13253
+ }), _void(), {
13254
+ kind: "mutation",
13255
+ auth: "admin"
13256
+ }), method(object({
13257
+ namespace: string().optional(),
13258
+ collection: string(),
13259
+ filter: MutationFilterSchema
13260
+ }), object({ deleted: number().int() }), {
13261
+ kind: "mutation",
13262
+ auth: "admin"
13263
+ }), method(object({
13264
+ namespace: string().optional(),
13265
+ collection: string(),
13266
+ filter: MutationFilterSchema,
13267
+ data: record(string(), unknown())
13268
+ }), object({ updated: number().int() }), {
13269
+ kind: "mutation",
13270
+ auth: "admin"
13271
+ }), method(object({
13272
+ namespace: string().optional(),
13273
+ collection: string(),
13274
+ filter: QueryFilterSchema.optional()
13275
+ }), number(), { auth: "admin" }), method(object({
13276
+ namespace: string().optional(),
13277
+ collection: string(),
13278
+ fields: array(AggregateFieldSchema).readonly(),
13279
+ filter: QueryFilterSchema.optional()
13280
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
13281
+ namespace: string().optional(),
13282
+ collection: string(),
13283
+ field: string(),
13284
+ bucketSize: number().int().positive(),
13285
+ origin: number().int(),
13286
+ filter: QueryFilterSchema.optional()
13287
+ }), array(object({
13288
+ bucket: number().int(),
13289
+ count: number().int()
13290
+ })).readonly(), { auth: "admin" }), method(object({
13291
+ namespace: string().optional(),
13292
+ collection: string()
13293
+ }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
13294
+ kind: "mutation",
13295
+ auth: "admin"
13296
+ });
13297
+ 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>\"");
13298
+ var MigrationLevelSchema = _enum([
13299
+ "hub-store",
13300
+ "addon-state",
13301
+ "node-config",
13302
+ "viewer",
13303
+ "addon-internal"
13304
+ ]);
13305
+ var MigrationStoreSchema = _enum([
13306
+ "settings",
13307
+ "events",
13308
+ "history",
13309
+ "node-local",
13310
+ "addon-files"
13311
+ ]);
13312
+ var MigrationScopeSchema = _enum(["cluster", "per-node"]);
13313
+ /** What depends on a migration while it is pending: `block-owner` is the default. */
13314
+ var MigrationOnFailureSchema = _enum(["block-owner", "tolerate"]);
13315
+ /**
13316
+ * `sql` is a hub-only body over the owner's connection. `plan` returns edits the
13317
+ * engine applies inside the migration's own transaction (hub-owned: computed in
13318
+ * that transaction; addon-owned: computed by the owner and shipped to the engine).
13319
+ * `procedure` is addon-owned work that is not SQL (keychain, integration registry,
13320
+ * device tombstones); the ledger records its outcome and it must be idempotent.
13321
+ */
13322
+ var MigrationBodyKindSchema = _enum([
13323
+ "sql",
13324
+ "plan",
13325
+ "procedure"
13326
+ ]);
13327
+ var MigrationMetaSchema = object({
13328
+ id: MigrationIdSchema,
13329
+ /** `hub`, an addon id, `node` or `viewer`. */
13330
+ owner: string().regex(/^[a-z0-9-]+$/),
13331
+ level: MigrationLevelSchema,
13332
+ /** Strictly increasing per owner: the order within an owner. */
13333
+ seq: number().int().positive(),
13334
+ /** Ids of any owner: the order across owners. */
13335
+ dependsOn: array(MigrationIdSchema),
13336
+ store: MigrationStoreSchema,
13337
+ scope: MigrationScopeSchema,
13338
+ onFailure: MigrationOnFailureSchema,
13339
+ bodyKind: MigrationBodyKindSchema,
13340
+ description: string().min(1),
13341
+ /** The marked field the migration's spec must assert survived. */
13342
+ fixtureMark: string().min(1)
13343
+ }).refine((m) => m.id.startsWith(`${m.owner}/`), {
13344
+ message: "the id must start with \"<owner>/\"",
13345
+ path: ["id"]
13346
+ });
13347
+ /** A cycle in the dependency graph, named by the ids on it. */
13348
+ var MigrationCycleError = class extends Error {
13349
+ ids;
13350
+ constructor(ids) {
13351
+ super(`migration dependsOn cycle: ${ids.join(" -> ")}`);
13352
+ this.ids = ids;
13353
+ this.name = "MigrationCycleError";
13354
+ }
13355
+ };
13356
+ /**
13357
+ * Topological order over `dependsOn` plus the implicit edge "a lower `seq` of an
13358
+ * owner runs first". Ties are broken by `(owner, seq)`. Throws
13359
+ * {@link MigrationCycleError} on a cycle. A `dependsOn` id that is not in the list
13360
+ * is ignored here — the catalogue schema is what refuses it.
13361
+ */
13362
+ function orderMigrations(items) {
13363
+ const byId = new Map(items.map((item) => [item.id, item]));
13364
+ const deps = /* @__PURE__ */ new Map();
13365
+ const ownerSorted = /* @__PURE__ */ new Map();
13366
+ for (const item of items) {
13367
+ const list = ownerSorted.get(item.owner) ?? [];
13368
+ list.push(item);
13369
+ ownerSorted.set(item.owner, list);
13370
+ }
13371
+ for (const item of items) deps.set(item.id, new Set(item.dependsOn.filter((id) => byId.has(id))));
13372
+ for (const list of ownerSorted.values()) {
13373
+ list.sort((a, b) => a.seq - b.seq);
13374
+ for (let i = 1; i < list.length; i++) {
13375
+ const previous = list[i - 1];
13376
+ const current = list[i];
13377
+ if (previous !== void 0 && current !== void 0) deps.get(current.id)?.add(previous.id);
13378
+ }
13379
+ }
13380
+ const tie = (a, b) => a.owner < b.owner ? -1 : a.owner > b.owner ? 1 : a.seq - b.seq;
13381
+ const done = /* @__PURE__ */ new Set();
13382
+ const ordered = [];
13383
+ let remaining = [...items];
13384
+ while (remaining.length > 0) {
13385
+ const ready = remaining.filter((item) => [...deps.get(item.id) ?? []].every((dep) => done.has(dep)));
13386
+ if (ready.length === 0) throw new MigrationCycleError(remaining.map((item) => item.id).sort());
13387
+ ready.sort(tie);
13388
+ const next = ready[0];
13389
+ if (next === void 0) break;
13390
+ ordered.push(next);
13391
+ done.add(next.id);
13392
+ remaining = remaining.filter((item) => item.id !== next.id);
13393
+ }
13394
+ return ordered;
13395
+ }
13396
+ /** Every way a list of migrations (in declaration order) is not a catalogue. */
13397
+ function catalogueIssues(items) {
13398
+ const issues = [];
13399
+ const seen = /* @__PURE__ */ new Set();
13400
+ const known = new Set(items.map((item) => item.id));
13401
+ const lastSeq = /* @__PURE__ */ new Map();
13402
+ const seqUsed = /* @__PURE__ */ new Map();
13403
+ for (const item of items) {
13404
+ if (seen.has(item.id)) issues.push({
13405
+ code: "duplicate-id",
13406
+ ids: [item.id],
13407
+ message: `duplicate id ${item.id}`
13408
+ });
13409
+ seen.add(item.id);
13410
+ const used = seqUsed.get(item.owner) ?? /* @__PURE__ */ new Set();
13411
+ if (used.has(item.seq)) issues.push({
13412
+ code: "duplicate-seq",
13413
+ ids: [item.id],
13414
+ message: `owner ${item.owner} has two migrations with seq ${item.seq}`
13415
+ });
13416
+ else if (item.seq < (lastSeq.get(item.owner) ?? 0)) issues.push({
13417
+ code: "seq-order",
13418
+ ids: [item.id],
13419
+ message: `seq ${item.seq} of ${item.id} does not increase in declaration order (owner ${item.owner})`
13420
+ });
13421
+ used.add(item.seq);
13422
+ seqUsed.set(item.owner, used);
13423
+ lastSeq.set(item.owner, Math.max(item.seq, lastSeq.get(item.owner) ?? 0));
13424
+ for (const dep of item.dependsOn) if (!known.has(dep)) issues.push({
13425
+ code: "unknown-dependency",
13426
+ ids: [item.id, dep],
13427
+ message: `${item.id} dependsOn unknown id ${dep}`
13428
+ });
13429
+ }
13430
+ try {
13431
+ orderMigrations(items);
13432
+ } catch (err) {
13433
+ if (err instanceof MigrationCycleError) issues.push({
13434
+ code: "cycle",
13435
+ ids: err.ids,
13436
+ message: err.message
13437
+ });
13438
+ else throw err;
13439
+ }
13440
+ return issues;
13441
+ }
13442
+ array(MigrationMetaSchema).superRefine((items, ctx) => {
13443
+ for (const issue of catalogueIssues(items)) ctx.addIssue({
13444
+ code: "custom",
13445
+ message: `${issue.code}: ${issue.message}`
13446
+ });
13447
+ });
13448
+ /**
13449
+ * The canonical JSON (`preconditionOf`) of the value a plan read; the apply step
13450
+ * re-reads, and a plan whose read no longer matches is refused (the migration is
13451
+ * `failed`, nothing it edited is kept).
13452
+ */
13453
+ var PreconditionSchema = string().min(1).optional();
13454
+ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
13455
+ object({
13456
+ op: literal("setKeys"),
13457
+ scope: string().min(1),
13458
+ values: record(string(), unknown()),
13459
+ precondition: PreconditionSchema
13460
+ }),
13461
+ object({
13462
+ op: literal("deleteKeys"),
13463
+ scope: string().min(1),
13464
+ keys: array(string().min(1)).min(1),
13465
+ precondition: PreconditionSchema
13466
+ }),
13467
+ object({
13468
+ op: literal("putRow"),
13469
+ table: string().min(1),
13470
+ key: record(string(), unknown()),
13471
+ row: record(string(), unknown()),
13472
+ precondition: PreconditionSchema
13473
+ }),
13474
+ object({
13475
+ op: literal("deleteRow"),
13476
+ table: string().min(1),
13477
+ key: record(string(), unknown()),
13478
+ precondition: PreconditionSchema
13479
+ }),
13480
+ object({
13481
+ op: literal("upsertRecord"),
13482
+ kind: string().min(1),
13483
+ recordKey: string().min(1),
13484
+ value: unknown(),
13485
+ precondition: PreconditionSchema
13486
+ }),
13487
+ object({
13488
+ op: literal("deleteRecord"),
13489
+ kind: string().min(1),
13490
+ recordKey: string().min(1),
13491
+ precondition: PreconditionSchema
13492
+ })
13493
+ ])) });
13494
+ /**
13495
+ * What a `procedure` body reports. `already-done` means the rerun observed the
13496
+ * work finished (a crash between the work and the ledger row, or a manual fix)
13497
+ * and did none of it again; the ledger records `applied` either way.
13498
+ */
13499
+ var MigrationProcedureResultSchema = object({
13500
+ outcome: _enum(["done", "already-done"]),
13501
+ summary: record(string(), unknown())
13502
+ });
13503
+ /** What an owner hands the engine when one of its migrations ran. */
13504
+ var MigrationOwnerResultSchema = discriminatedUnion("kind", [
13505
+ object({
13506
+ kind: literal("plan"),
13507
+ plan: MigrationPlanSchema
13508
+ }),
13509
+ object({
13510
+ kind: literal("procedure"),
13511
+ result: MigrationProcedureResultSchema
13512
+ }),
13513
+ object({
13514
+ kind: literal("failed"),
13515
+ error: string().min(1)
13516
+ })
13517
+ ]);
13518
+ /**
13519
+ * `held`: a restore of a pre-migration snapshot undid this id; it does not rerun
13520
+ * until the operator retries or a new code version ships.
13521
+ * `skipped-newer`: the database knows an id this code does not.
13522
+ */
13523
+ var MigrationStatusSchema = _enum([
13524
+ "applied",
13525
+ "failed",
13526
+ "blocked",
13527
+ "running",
13528
+ "held",
13529
+ "skipped-newer"
13530
+ ]);
13531
+ var MigrationLedgerEntrySchema = object({
13532
+ id: string(),
13533
+ database: DataStoreDatabaseSchema,
13534
+ owner: string(),
13535
+ level: MigrationLevelSchema,
13536
+ seq: number().int().nonnegative(),
13537
+ status: MigrationStatusSchema,
13538
+ attempts: number().int().nonnegative(),
13539
+ appliedAt: number().nullable(),
13540
+ codeVersion: string().nullable(),
13541
+ /** What the apply changed: rows per table, as the engine counted them. */
13542
+ planSummary: record(string(), unknown()).nullable(),
13543
+ progress: record(string(), unknown()).nullable(),
13544
+ error: string().nullable(),
13545
+ /** Why it is `blocked` or `held` (`dependency-failed`, `snapshot-refused`, …). */
13546
+ reason: string().nullable(),
13547
+ nodeId: string().nullable(),
13548
+ bootId: string().nullable()
13549
+ });
13550
+ /** A catalogued migration that is not applied, with where it stands. */
13551
+ var MigrationPendingEntrySchema = object({
13552
+ meta: MigrationMetaSchema,
13553
+ database: DataStoreDatabaseSchema,
13554
+ state: _enum([
13555
+ "pending",
13556
+ "failed",
13557
+ "blocked",
13558
+ "held"
13559
+ ]),
13560
+ reason: string().nullable()
13561
+ });
13562
+ /** Rows per table before and after a dry run, and what it would have changed. */
13563
+ var MigrationDryRunResultSchema = object({
13564
+ id: string(),
13565
+ ok: boolean(),
13566
+ error: string().nullable(),
13567
+ rowsChanged: number().int().nonnegative(),
13568
+ tables: array(object({
13569
+ table: string(),
13570
+ before: number().int().nonnegative(),
13571
+ after: number().int().nonnegative()
13572
+ }))
13573
+ });
13574
+ /** One of an owner's migrations, as the engine tells the owner to treat it this boot. */
13575
+ var MigrationOwnerStepSchema = object({
13576
+ id: MigrationIdSchema,
13577
+ action: _enum(["run", "skip"]),
13578
+ /** The ledger state the engine found: `pending` has no row yet. */
13579
+ state: union([literal("pending"), MigrationStatusSchema])
13580
+ });
13581
+ /**
12769
13582
  * The trainer contract — what the hub hands the cloud trainer and what it
12770
13583
  * gets back (cloud training, D805+).
12771
13584
  *
@@ -16168,6 +16981,41 @@ var ACTUATOR_CAP_VOCABULARY = {
16168
16981
  icon: "droplets"
16169
16982
  }]
16170
16983
  },
16984
+ "power-source": {
16985
+ scope: "device",
16986
+ noun: t("Power source", "Alimentazione"),
16987
+ actions: [
16988
+ {
16989
+ id: "powerSource.on",
16990
+ method: "powerOn",
16991
+ label: t("Power on", "Dai corrente"),
16992
+ phrase: t("Power on {device}", "Dai corrente a {device}"),
16993
+ icon: "power"
16994
+ },
16995
+ {
16996
+ id: "powerSource.off",
16997
+ method: "powerOff",
16998
+ label: t("Power off", "Togli corrente"),
16999
+ phrase: t("Cut the power of {device}", "Togli la corrente a {device}"),
17000
+ icon: "power-off"
17001
+ },
17002
+ {
17003
+ id: "powerSource.forceRestart",
17004
+ method: "forceRestart",
17005
+ label: t("Force restart", "Riavvio forzato"),
17006
+ phrase: t("Force restart {device} by cutting its power", "Riavvia {device} togliendo la corrente"),
17007
+ icon: "refresh-cw"
17008
+ }
17009
+ ],
17010
+ state: [{
17011
+ kind: "boolean",
17012
+ field: "switchOn",
17013
+ label: t("Power switch on", "Presa accesa"),
17014
+ whenTrue: t("When the power of {device} comes on", "Quando arriva la corrente a {device}"),
17015
+ whenFalse: t("When the power of {device} is cut", "Quando manca la corrente a {device}"),
17016
+ icon: "power"
17017
+ }]
17018
+ },
16171
17019
  "lock-control": {
16172
17020
  scope: "device",
16173
17021
  noun: t("Lock", "Serratura"),
@@ -16643,7 +17491,7 @@ var ACTUATOR_CAP_VOCABULARY = {
16643
17491
  method: "setDirection",
16644
17492
  label: t("Spin forward", "Rotazione avanti"),
16645
17493
  phrase: t("Spin {device} forward", "Fai girare {device} in avanti"),
16646
- icon: "rotate-cw",
17494
+ icon: "refresh-cw",
16647
17495
  effect: {
16648
17496
  field: "direction",
16649
17497
  value: "forward"
@@ -19721,6 +20569,7 @@ var SYSTEM_CAP_VOCABULARY = {
19721
20569
  "login-method": system(IDENTITY),
19722
20570
  "mesh-network": system(NETWORK),
19723
20571
  "metrics-provider": system(CLUSTER),
20572
+ migrations: system(INTERNAL),
19724
20573
  "model-convert": system(ML),
19725
20574
  "model-distributor": system(ML),
19726
20575
  "mqtt-broker": system(NETWORK),
@@ -42004,7 +42853,7 @@ method(object({}), array(TargetKindSchema)), method(object({}), array(TargetSche
42004
42853
  * rebuilds without manual reload.
42005
42854
  *
42006
42855
  * The hub-local builtin `addon-pages-aggregator` (see
42007
- * `@camstack/system/builtins/addon-pages-aggregator`) registers the
42856
+ * `@camstack/server/system/builtins/addon-pages-aggregator`) registers the
42008
42857
  * provider. Splitting the public aggregator from the raw collection
42009
42858
  * keeps both ends in codegen — there's no hand-written
42010
42859
  * `addon-pages.router.ts` wrapper anymore.
@@ -42304,7 +43153,7 @@ method(_void(), array(WidgetMetadataSchema).readonly(), { auth: "admin" });
42304
43153
  * manual reload — same scheme used by `addon-pages`.
42305
43154
  *
42306
43155
  * The hub-local builtin `addon-widgets-aggregator` (see
42307
- * `@camstack/system/builtins/addon-widgets-aggregator`) registers the
43156
+ * `@camstack/server/system/builtins/addon-widgets-aggregator`) registers the
42308
43157
  * provider. Splitting the public aggregator from the raw collection
42309
43158
  * keeps both ends in codegen — there's no hand-written wrapper.
42310
43159
  */
@@ -42665,329 +43514,6 @@ method(object({
42665
43514
  password: string()
42666
43515
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
42667
43516
  /**
42668
- * Query filter for settings-store collections.
42669
- */
42670
- var QueryFilterSchema = object({
42671
- where: record(string(), unknown()).optional(),
42672
- whereIn: record(string(), array(unknown())).optional(),
42673
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
42674
- /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
42675
- whereNot: record(string(), unknown()).optional(),
42676
- /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
42677
- * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
42678
- whereNotIn: record(string(), array(unknown())).optional(),
42679
- orderBy: object({
42680
- field: string(),
42681
- direction: _enum(["asc", "desc"])
42682
- }).optional(),
42683
- limit: number().optional(),
42684
- offset: number().optional()
42685
- });
42686
- /**
42687
- * The predicate half of a filter, for BULK MUTATIONS.
42688
- *
42689
- * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
42690
- * meaning for a statement that rewrites a set, and accepting them would invite
42691
- * a caller to believe `limit` bounds the damage. Every field is optional here
42692
- * only so the shape stays composable — the implementation REJECTS a filter
42693
- * that compiles to no predicate, because that is the whole collection.
42694
- */
42695
- var MutationFilterSchema = object({
42696
- where: record(string(), unknown()).optional(),
42697
- whereIn: record(string(), array(unknown())).optional(),
42698
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
42699
- whereNot: record(string(), unknown()).optional(),
42700
- whereNotIn: record(string(), array(unknown())).optional()
42701
- });
42702
- /**
42703
- * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
42704
- *
42705
- * `as` names the slot in the result, so the SAME column may be asked twice with
42706
- * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
42707
- * a `Record<column, op>` shape could not express.
42708
- */
42709
- var AggregateFieldSchema = object({
42710
- /** Result key. */
42711
- as: string().min(1),
42712
- /** Column to aggregate. Must be a real column of a declared collection. */
42713
- field: string().min(1),
42714
- op: _enum([
42715
- "sum",
42716
- "min",
42717
- "max"
42718
- ])
42719
- });
42720
- /**
42721
- * `COUNT(*)` plus one number per requested field.
42722
- *
42723
- * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
42724
- * that really is 0 are different facts, and an accounting caller that renders
42725
- * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
42726
- */
42727
- var AggregateResultSchema = object({
42728
- count: number().int(),
42729
- values: record(string(), number().nullable())
42730
- });
42731
- /** A single stored record: `{ id, data }`. */
42732
- var SettingsRecordSchema = object({
42733
- id: string(),
42734
- data: record(string(), unknown())
42735
- });
42736
- /**
42737
- * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
42738
- * optional.
42739
- *
42740
- * A separate schema rather than loosening the shared one: every other method
42741
- * on this cap addresses a row BY its id, and making that field optional
42742
- * everywhere would turn a forgotten key into a silently generated one on
42743
- * `update` and `delete` as well.
42744
- */
42745
- var BulkRecordSchema = object({
42746
- id: string().optional(),
42747
- data: record(string(), unknown())
42748
- });
42749
- /**
42750
- * The database FILES one `sqlite-settings` engine process owns (D851).
42751
- * Closed on purpose: a file is a thing the engine opens, backs up and gives a
42752
- * serving thread, so a new one is a code change, never a string a caller
42753
- * invents. `settings` is `camstack.db` and the default for every collection
42754
- * that names no file.
42755
- */
42756
- var DataStoreDatabaseSchema = _enum([
42757
- "settings",
42758
- "events",
42759
- "history"
42760
- ]);
42761
- DataStoreDatabaseSchema.options;
42762
- /**
42763
- * One write inside a {@link settingsStoreCapability.methods.transact} call.
42764
- * The three that a "replace this set, bump that revision" write needs, plus
42765
- * the batched upsert a rollup writer needs.
42766
- */
42767
- var TransactOpSchema = discriminatedUnion("op", [
42768
- object({
42769
- op: literal("deleteWhere"),
42770
- collection: string(),
42771
- filter: MutationFilterSchema
42772
- }),
42773
- object({
42774
- op: literal("insertMany"),
42775
- collection: string(),
42776
- records: array(BulkRecordSchema).readonly()
42777
- }),
42778
- object({
42779
- op: literal("update"),
42780
- collection: string(),
42781
- id: string(),
42782
- data: record(string(), unknown())
42783
- }),
42784
- object({
42785
- op: literal("upsertMany"),
42786
- collection: string(),
42787
- conflict: array(string()).readonly(),
42788
- records: array(BulkRecordSchema).readonly()
42789
- })
42790
- ]);
42791
- /**
42792
- * The precondition of a transaction: the row `key` of `collection` must hold
42793
- * `equals` in `column`, read INSIDE the transaction.
42794
- */
42795
- var TransactGuardSchema = object({
42796
- collection: string(),
42797
- key: string(),
42798
- column: string(),
42799
- equals: unknown()
42800
- });
42801
- /**
42802
- * Column declaration for a structured (SQL-backed) collection.
42803
- *
42804
- * Logical types — the backend translates each to the matching SQLite
42805
- * storage class and handles per-type marshaling:
42806
- * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
42807
- * - `JSON` — TEXT under the hood; serialised on write, parsed on read
42808
- * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
42809
- */
42810
- var CollectionColumnSchema = object({
42811
- name: string(),
42812
- type: _enum([
42813
- "TEXT",
42814
- "INTEGER",
42815
- "REAL",
42816
- "JSON",
42817
- "BOOLEAN"
42818
- ]),
42819
- primaryKey: boolean().optional(),
42820
- notNull: boolean().optional(),
42821
- unique: boolean().optional(),
42822
- /**
42823
- * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
42824
- * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
42825
- * collection surface simply cannot express their existing tables — which is
42826
- * why they were still on the uncapped `table*` API.
42827
- */
42828
- defaultValue: union([
42829
- string(),
42830
- number(),
42831
- boolean()
42832
- ]).optional()
42833
- });
42834
- var CollectionIndexSchema = object({
42835
- name: string(),
42836
- columns: array(string()).readonly(),
42837
- unique: boolean().optional()
42838
- });
42839
- /**
42840
- * The input of `declareCollection` — ONE schema for the door and for the
42841
- * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
42842
- * the payload untouched).
42843
- *
42844
- * - `database` — the file the collection lives in (D851). Absent means
42845
- * `settings`. A declaration naming a file other than the one the table is
42846
- * already in is REFUSED by the engine, never moved.
42847
- * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
42848
- * as `primaryKey: true` on that column. A composite-keyed collection has no
42849
- * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
42850
- * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
42851
- * address one row by id are refused.
42852
- * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
42853
- */
42854
- var DeclareCollectionInputSchema = object({
42855
- namespace: string().optional(),
42856
- collection: string(),
42857
- columns: array(CollectionColumnSchema).readonly(),
42858
- indexes: array(CollectionIndexSchema).readonly().optional(),
42859
- database: DataStoreDatabaseSchema.optional(),
42860
- primaryKey: array(string()).readonly().optional(),
42861
- withoutRowid: boolean().optional()
42862
- });
42863
- method(object({
42864
- namespace: string().optional(),
42865
- collection: string(),
42866
- key: string()
42867
- }), unknown(), { auth: "admin" }), method(object({
42868
- namespace: string().optional(),
42869
- collection: string(),
42870
- key: string(),
42871
- value: unknown()
42872
- }), _void(), {
42873
- kind: "mutation",
42874
- auth: "admin"
42875
- }), method(object({
42876
- namespace: string().optional(),
42877
- collection: string(),
42878
- filter: QueryFilterSchema.optional(),
42879
- columns: array(string()).readonly().optional()
42880
- }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
42881
- namespace: string().optional(),
42882
- collection: string(),
42883
- /** Declared columns identifying the slot; must be covered by a UNIQUE
42884
- * index on the collection. Never empty. */
42885
- conflict: array(string()).readonly(),
42886
- record: BulkRecordSchema
42887
- }), object({
42888
- /** The id the row HAS: assigned by SQLite on an insert, or the existing
42889
- * row's own id on an update. */
42890
- id: union([string(), number()]) }), {
42891
- kind: "mutation",
42892
- auth: "admin"
42893
- }), method(object({
42894
- namespace: string().optional(),
42895
- collection: string(),
42896
- conflict: array(string()).readonly(),
42897
- records: array(BulkRecordSchema).readonly()
42898
- }), object({ upserted: number().int() }), {
42899
- kind: "mutation",
42900
- auth: "admin"
42901
- }), method(object({
42902
- namespace: string().optional(),
42903
- collection: string(),
42904
- record: object({
42905
- id: string().optional(),
42906
- data: record(string(), unknown())
42907
- })
42908
- }), object({
42909
- /**
42910
- * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
42911
- * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
42912
- * primary key — which is the only place an auto key is knowable.
42913
- */
42914
- id: union([string(), number()]) }), {
42915
- kind: "mutation",
42916
- auth: "admin"
42917
- }), method(object({
42918
- namespace: string().optional(),
42919
- collection: string(),
42920
- records: array(BulkRecordSchema).readonly()
42921
- }), object({ inserted: number().int() }), {
42922
- kind: "mutation",
42923
- auth: "admin"
42924
- }), method(object({
42925
- namespace: string().optional(),
42926
- guard: TransactGuardSchema.optional(),
42927
- ops: array(TransactOpSchema).readonly()
42928
- }), object({
42929
- applied: boolean(),
42930
- current: unknown().optional()
42931
- }), {
42932
- kind: "mutation",
42933
- auth: "admin"
42934
- }), method(object({
42935
- namespace: string().optional(),
42936
- collection: string(),
42937
- id: string(),
42938
- data: record(string(), unknown())
42939
- }), _void(), {
42940
- kind: "mutation",
42941
- auth: "admin"
42942
- }), method(object({
42943
- namespace: string().optional(),
42944
- collection: string(),
42945
- key: string()
42946
- }), _void(), {
42947
- kind: "mutation",
42948
- auth: "admin"
42949
- }), method(object({
42950
- namespace: string().optional(),
42951
- collection: string(),
42952
- filter: MutationFilterSchema
42953
- }), object({ deleted: number().int() }), {
42954
- kind: "mutation",
42955
- auth: "admin"
42956
- }), method(object({
42957
- namespace: string().optional(),
42958
- collection: string(),
42959
- filter: MutationFilterSchema,
42960
- data: record(string(), unknown())
42961
- }), object({ updated: number().int() }), {
42962
- kind: "mutation",
42963
- auth: "admin"
42964
- }), method(object({
42965
- namespace: string().optional(),
42966
- collection: string(),
42967
- filter: QueryFilterSchema.optional()
42968
- }), number(), { auth: "admin" }), method(object({
42969
- namespace: string().optional(),
42970
- collection: string(),
42971
- fields: array(AggregateFieldSchema).readonly(),
42972
- filter: QueryFilterSchema.optional()
42973
- }), AggregateResultSchema, { auth: "admin" }), method(object({
42974
- namespace: string().optional(),
42975
- collection: string(),
42976
- field: string(),
42977
- bucketSize: number().int().positive(),
42978
- origin: number().int(),
42979
- filter: QueryFilterSchema.optional()
42980
- }), array(object({
42981
- bucket: number().int(),
42982
- count: number().int()
42983
- })).readonly(), { auth: "admin" }), method(object({
42984
- namespace: string().optional(),
42985
- collection: string()
42986
- }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
42987
- kind: "mutation",
42988
- auth: "admin"
42989
- });
42990
- /**
42991
43517
  * How long one snapshot may take end to end: the pinned copy of the whole
42992
43518
  * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
42993
43519
  * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
@@ -44795,7 +45321,7 @@ method(object({
44795
45321
  kind: "mutation",
44796
45322
  auth: "admin"
44797
45323
  }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
44798
- 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.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;
45324
+ 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;
44799
45325
  var NUMBER = {
44800
45326
  kind: "fixed",
44801
45327
  result: ["number"]
@@ -45806,6 +46332,57 @@ id: union([string(), number()]) }), {
45806
46332
  kind: "mutation",
45807
46333
  auth: "admin"
45808
46334
  });
46335
+ 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({
46336
+ id: string().min(1),
46337
+ error: string().min(1)
46338
+ }), MigrationLedgerEntrySchema, {
46339
+ kind: "mutation",
46340
+ auth: "admin"
46341
+ }), method(object({ id: string().min(1) }), object({ entries: array(MigrationLedgerEntrySchema) }), {
46342
+ kind: "mutation",
46343
+ auth: "admin"
46344
+ }), method(object({
46345
+ database: DataStoreDatabaseSchema,
46346
+ snapshotId: string().min(1)
46347
+ }), object({
46348
+ scheduled: boolean(),
46349
+ held: array(string()),
46350
+ restartRequired: boolean()
46351
+ }), {
46352
+ kind: "mutation",
46353
+ auth: "admin"
46354
+ }), method(object({
46355
+ owner: string().min(1),
46356
+ metas: array(MigrationMetaSchema)
46357
+ }), object({
46358
+ bootId: string(),
46359
+ snapshot: _enum([
46360
+ "none",
46361
+ "written",
46362
+ "refused"
46363
+ ]),
46364
+ steps: array(MigrationOwnerStepSchema)
46365
+ }), {
46366
+ kind: "mutation",
46367
+ auth: "admin"
46368
+ }), method(object({ id: string().min(1) }), object({
46369
+ proceed: boolean(),
46370
+ state: union([literal("pending"), MigrationStatusSchema]),
46371
+ reason: string().nullable()
46372
+ }), {
46373
+ kind: "mutation",
46374
+ auth: "admin"
46375
+ }), method(object({
46376
+ id: string().min(1),
46377
+ result: MigrationOwnerResultSchema
46378
+ }), object({
46379
+ status: MigrationStatusSchema,
46380
+ error: string().nullable(),
46381
+ rowsChanged: number().int().nonnegative()
46382
+ }), {
46383
+ kind: "mutation",
46384
+ auth: "admin"
46385
+ });
45809
46386
  /**
45810
46387
  * records - an addon's own facts and documents (D881): ledgers, audit rows,
45811
46388
  * config documents, mirrors. The third storage API next to `ctx.settings`
@@ -60308,7 +60885,18 @@ var ServerUpdateActionResultSchema = object({
60308
60885
  * store (D877), present when the pre-flight refused the update
60309
60886
  * (`reason: 'addon-would-refuse'`). Optional for version skew.
60310
60887
  */
60311
- blockers: array(UpdateBlockerSchema).optional()
60888
+ blockers: array(UpdateBlockerSchema).optional(),
60889
+ /**
60890
+ * The minimums the target server declares and this node does not meet
60891
+ * (D989), present when the update was refused with
60892
+ * `reason: 'requirements-unmet'`. Each names the versions on both sides.
60893
+ */
60894
+ unmet: array(UnmetRequirementSchema).optional(),
60895
+ /**
60896
+ * The addons this node has below the minimum the staged server declares —
60897
+ * the ones to update WITH the server. Present on an accepted update.
60898
+ */
60899
+ requiredAddonUpdates: array(RequiredAddonUpdateSchema).optional()
60312
60900
  });
60313
60901
  var LaunchOverridesSchema = object({
60314
60902
  /** The persisted heap ceiling in MB, or `null` when none is set. */
@@ -62819,7 +63407,9 @@ var PackageUpdateSchema = object({
62819
63407
  latestVersion: string(),
62820
63408
  category: _enum(["addon", "core"]),
62821
63409
  requiresRestart: boolean(),
62822
- isSystem: boolean()
63410
+ isSystem: boolean(),
63411
+ /** Held: what the newer version needs and this node does not meet (D989). */
63412
+ blockedBy: array(UnmetRequirementSchema).readonly().optional()
62823
63413
  });
62824
63414
  var PackageVersionInfoSchema = object({
62825
63415
  version: string(),
@@ -63863,19 +64453,19 @@ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status:
63863
64453
  reason: _enum(["unknown-template", "not-a-media-player"]),
63864
64454
  detail: string()
63865
64455
  })]);
63866
- var deviceInput = object({ deviceId: number().int().nonnegative() });
64456
+ var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
63867
64457
  DeviceType.MediaPlayer;
63868
- method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
64458
+ method(deviceInput$1, RemoteConfigSchema, { auth: "admin" }), method(deviceInput$1.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
63869
64459
  kind: "mutation",
63870
64460
  auth: "admin"
63871
- }), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
64461
+ }), method(deviceInput$1, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput$1.extend({
63872
64462
  role: RemoteRoleSchema,
63873
64463
  /** Sends in a row (volume steps). */
63874
64464
  repeat: number().int().min(1).max(50).default(1)
63875
64465
  }), RemoteSendResultSchema, {
63876
64466
  kind: "mutation",
63877
64467
  auth: "protected"
63878
- }), method(deviceInput.extend({
64468
+ }), method(deviceInput$1.extend({
63879
64469
  command: RemoteMediaCommandSchema,
63880
64470
  muted: boolean().optional(),
63881
64471
  source: string().optional(),
@@ -66358,7 +66948,7 @@ DeviceType.LawnMower, method(object({ deviceId: number().int().nonnegative() }),
66358
66948
  * clients — they reverse-connect to the hub. Exposing their interfaces
66359
66949
  * via the same surface would leak internal topology with no upside.
66360
66950
  *
66361
- * Implementation in `@camstack/system/builtins/local-network/`.
66951
+ * Implementation in `@camstack/server/system/builtins/local-network/`.
66362
66952
  */
66363
66953
  /** Coarse classification derived from the interface name + IP range. */
66364
66954
  var InterfaceKindEnum = _enum([
@@ -66787,6 +67377,49 @@ DeviceType.Lock, method(object({
66787
67377
  kind: "mutation",
66788
67378
  auth: "protected"
66789
67379
  });
67380
+ var PowerSourceRefusalReasonSchema = _enum([
67381
+ "no-power-source",
67382
+ "switch-missing",
67383
+ "switch-offline",
67384
+ "switch-unknown",
67385
+ "restart-in-progress",
67386
+ "switch-call-failed"
67387
+ ]);
67388
+ var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
67389
+ accepted: literal(false),
67390
+ reason: PowerSourceRefusalReasonSchema,
67391
+ /** One line for the operator, naming the switch. */
67392
+ message: string()
67393
+ })]);
67394
+ var PowerSourceInfoSchema = object({
67395
+ /** False when the device has no power source linked; the rest is then empty. */
67396
+ linked: boolean(),
67397
+ switchDeviceId: number().nullable(),
67398
+ switchName: string().nullable(),
67399
+ /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
67400
+ switchOn: boolean().nullable(),
67401
+ switchOnline: boolean().nullable(),
67402
+ restartInProgress: boolean(),
67403
+ offSeconds: number().int()
67404
+ });
67405
+ object({
67406
+ /** Null while the switch has not reported: unknown is never `false` (D393). */
67407
+ switchOn: boolean().nullable(),
67408
+ offSeconds: number().int(),
67409
+ lastFetchedAt: number()
67410
+ });
67411
+ 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;
67412
+ var deviceInput = object({ deviceId: number().int().nonnegative() });
67413
+ systemMethod(deviceInput, PowerSourceInfoSchema, { auth: "admin" }), systemMethod(deviceInput, PowerSourceActionResultSchema, {
67414
+ kind: "mutation",
67415
+ auth: "admin"
67416
+ }), systemMethod(deviceInput, PowerSourceActionResultSchema, {
67417
+ kind: "mutation",
67418
+ auth: "admin"
67419
+ }), systemMethod(deviceInput, PowerSourceActionResultSchema, {
67420
+ kind: "mutation",
67421
+ auth: "admin"
67422
+ });
66790
67423
  object({
66791
67424
  value: number().nullable().meta(readingMeta("device", { label: "Total" })),
66792
67425
  lastFetchedAt: number()
@@ -67595,7 +68228,7 @@ DeviceType.Camera, DeviceType.Sensor, DeviceType.Button, DeviceType.Switch, Devi
67595
68228
  * jitter, and observed/peak bandwidth per device + per client.
67596
68229
  *
67597
68230
  * Implementation lives in the server's `NetworkQualityService` (thin
67598
- * wrapper over the shared `NetworkQualityTracker` from `@camstack/system`).
68231
+ * wrapper over the shared `NetworkQualityTracker` from `@camstack/server/system`).
67599
68232
  * The provider is registered from `trpc.router.ts` against the existing
67600
68233
  * service instance — no addon owns this state.
67601
68234
  *
@@ -77707,6 +78340,69 @@ Object.freeze({
77707
78340
  access: "view",
77708
78341
  role: "admin"
77709
78342
  },
78343
+ "migrations.declareOwner": {
78344
+ capName: "migrations",
78345
+ capScope: "system",
78346
+ addonId: null,
78347
+ access: "create",
78348
+ role: "admin"
78349
+ },
78350
+ "migrations.dryRun": {
78351
+ capName: "migrations",
78352
+ capScope: "system",
78353
+ addonId: null,
78354
+ access: "view",
78355
+ role: "admin"
78356
+ },
78357
+ "migrations.finishMigration": {
78358
+ capName: "migrations",
78359
+ capScope: "system",
78360
+ addonId: null,
78361
+ access: "create",
78362
+ role: "admin"
78363
+ },
78364
+ "migrations.listLedger": {
78365
+ capName: "migrations",
78366
+ capScope: "system",
78367
+ addonId: null,
78368
+ access: "view",
78369
+ role: "admin"
78370
+ },
78371
+ "migrations.listPending": {
78372
+ capName: "migrations",
78373
+ capScope: "system",
78374
+ addonId: null,
78375
+ access: "view",
78376
+ role: "admin"
78377
+ },
78378
+ "migrations.markFailed": {
78379
+ capName: "migrations",
78380
+ capScope: "system",
78381
+ addonId: null,
78382
+ access: "create",
78383
+ role: "admin"
78384
+ },
78385
+ "migrations.restorePreMigration": {
78386
+ capName: "migrations",
78387
+ capScope: "system",
78388
+ addonId: null,
78389
+ access: "create",
78390
+ role: "admin"
78391
+ },
78392
+ "migrations.retry": {
78393
+ capName: "migrations",
78394
+ capScope: "system",
78395
+ addonId: null,
78396
+ access: "create",
78397
+ role: "admin"
78398
+ },
78399
+ "migrations.startMigration": {
78400
+ capName: "migrations",
78401
+ capScope: "system",
78402
+ addonId: null,
78403
+ access: "create",
78404
+ role: "admin"
78405
+ },
77710
78406
  "modelConvert.convert": {
77711
78407
  capName: "model-convert",
77712
78408
  capScope: "system",
@@ -80339,6 +81035,34 @@ Object.freeze({
80339
81035
  access: "view",
80340
81036
  role: "viewer"
80341
81037
  },
81038
+ "powerSource.forceRestart": {
81039
+ capName: "power-source",
81040
+ capScope: "device",
81041
+ addonId: null,
81042
+ access: "create",
81043
+ role: "admin"
81044
+ },
81045
+ "powerSource.getStatus": {
81046
+ capName: "power-source",
81047
+ capScope: "device",
81048
+ addonId: null,
81049
+ access: "view",
81050
+ role: "admin"
81051
+ },
81052
+ "powerSource.powerOff": {
81053
+ capName: "power-source",
81054
+ capScope: "device",
81055
+ addonId: null,
81056
+ access: "create",
81057
+ role: "admin"
81058
+ },
81059
+ "powerSource.powerOn": {
81060
+ capName: "power-source",
81061
+ capScope: "device",
81062
+ addonId: null,
81063
+ access: "create",
81064
+ role: "admin"
81065
+ },
80342
81066
  "presence.getStatus": {
80343
81067
  capName: "presence",
80344
81068
  capScope: "device",
@@ -85487,6 +86211,26 @@ Object.freeze({
85487
86211
  form: "single",
85488
86212
  optional: false
85489
86213
  }],
86214
+ "powerSource.forceRestart": [{
86215
+ name: "deviceId",
86216
+ form: "single",
86217
+ optional: false
86218
+ }],
86219
+ "powerSource.getStatus": [{
86220
+ name: "deviceId",
86221
+ form: "single",
86222
+ optional: false
86223
+ }],
86224
+ "powerSource.powerOff": [{
86225
+ name: "deviceId",
86226
+ form: "single",
86227
+ optional: false
86228
+ }],
86229
+ "powerSource.powerOn": [{
86230
+ name: "deviceId",
86231
+ form: "single",
86232
+ optional: false
86233
+ }],
85490
86234
  "presence.getStatus": [{
85491
86235
  name: "deviceId",
85492
86236
  form: "single",
@@ -87686,7 +88430,7 @@ var AgentUIAddon = class extends BaseAddon {
87686
88430
  capability: adminUiCapability,
87687
88431
  provider: {
87688
88432
  getStaticDir: async () => ({ staticDir: path.resolve(__dirname) }),
87689
- getVersion: async () => ({ version: "1.2.305" })
88433
+ getVersion: async () => ({ version: "1.2.307" })
87690
88434
  }
87691
88435
  }];
87692
88436
  }