@camstack/addon-provider-homeassistant 1.2.643 → 1.2.644

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.
@@ -1,5 +1,5 @@
1
1
  require("node:crypto");
2
- //#region ../types/dist/event-category-DmZDPDf_.mjs
2
+ //#region ../types/dist/event-category-CaURIwQA.mjs
3
3
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
4
4
  EventCategory["SystemBoot"] = "system.boot";
5
5
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -715,6 +715,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
715
715
  EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
716
716
  /** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
717
717
  EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
718
+ /** A migration failed or was blocked; the Alert Center re-reads the ledger. */
719
+ EventCategory["MigrationFailed"] = "migration.failed";
720
+ /** A migration applied or a failure cleared; the Alert Center re-reads the ledger. */
721
+ EventCategory["MigrationApplied"] = "migration.applied";
718
722
  EventCategory["DeviceUpdated"] = "device.updated";
719
723
  /**
720
724
  * Transport-level connectivity. Emitted by the device driver when
@@ -777,6 +781,378 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
777
781
  return EventCategory;
778
782
  }({});
779
783
  //#endregion
784
+ //#region ../types/dist/device-type-CEqjmUnR.mjs
785
+ /**
786
+ import { errMsg } from '@camstack/types'
787
+ * Extract a human-readable message from an unknown error value.
788
+ * Replaces the ubiquitous `errMsg(err)` pattern.
789
+ */
790
+ function errMsg(err) {
791
+ if (err instanceof Error) return err.message;
792
+ if (typeof err === "string") return err;
793
+ return String(err);
794
+ }
795
+ var DeviceType = /* @__PURE__ */ function(DeviceType) {
796
+ DeviceType["Camera"] = "camera";
797
+ DeviceType["Hub"] = "hub";
798
+ DeviceType["Light"] = "light";
799
+ DeviceType["Siren"] = "siren";
800
+ DeviceType["Switch"] = "switch";
801
+ DeviceType["Sensor"] = "sensor";
802
+ DeviceType["Thermostat"] = "thermostat";
803
+ /** Air-conditioner / heat-pump climate device (HVAC) — shares the
804
+ * `climate-control` cap surface with `Thermostat` but renders a
805
+ * dedicated AC-appropriate control UI (mode chips, fan speed,
806
+ * independent vertical/horizontal swing). Sources: native Gree, and
807
+ * reusable by other AC integrations. */
808
+ DeviceType["Climate"] = "climate";
809
+ DeviceType["Button"] = "button";
810
+ /** Generic stateless event emitter — carries a device's EXACT declared
811
+ * event vocabulary verbatim (no normalization). Installed with the
812
+ * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
813
+ * HA bus events (e.g. `zha_event`, generic). */
814
+ DeviceType["EventEmitter"] = "event-emitter";
815
+ /** Firmware/software update entity — current vs available version,
816
+ * updatable flag, update state, and an install action. Installed with
817
+ * the `update` cap. Sources: Homematic firmware-update channels (and
818
+ * reusable by other providers, e.g. HA `update.*` entities). */
819
+ DeviceType["Update"] = "update";
820
+ DeviceType["Generic"] = "generic";
821
+ /** Generic notification delivery target (HA `notify.<service>`, future
822
+ * Telegram / Discord / ntfy / SMTP, …). One device per delivery
823
+ * endpoint; the `notifier` cap defines the send surface. */
824
+ DeviceType["Notifier"] = "notifier";
825
+ /** Pre-recorded action sequence with optional parameters
826
+ * (HA `script.*`). Runnable via `script-runner` cap. */
827
+ DeviceType["Script"] = "script";
828
+ /** Automation rule (HA `automation.*`) — enable/disable + manual
829
+ * trigger surface exposed via `automation-control` cap. */
830
+ DeviceType["Automation"] = "automation";
831
+ /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
832
+ DeviceType["Lock"] = "lock";
833
+ /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
834
+ * `valve.*`). `cover` cap with sub-roles for variant. */
835
+ DeviceType["Cover"] = "cover";
836
+ /** Pipe / water / gas valve with open/close/stop and optional
837
+ * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
838
+ * modelled on the same open/closed lifecycle. */
839
+ DeviceType["Valve"] = "valve";
840
+ /** Humidifier / dehumidifier with on/off + target humidity + mode
841
+ * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
842
+ * modelled on the same target / mode lifecycle. */
843
+ DeviceType["Humidifier"] = "humidifier";
844
+ /** Water heater / boiler with target temperature + operation mode +
845
+ * away mode (HA `water_heater.*`). `water-heater` cap — a
846
+ * climate-family actuator. */
847
+ DeviceType["WaterHeater"] = "water-heater";
848
+ /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
849
+ DeviceType["Fan"] = "fan";
850
+ /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
851
+ * the camera surface — those use `Camera`. `media-player` cap. */
852
+ DeviceType["MediaPlayer"] = "media-player";
853
+ /** Security panel / alarm system (HA `alarm_control_panel.*`).
854
+ * `alarm-panel` cap. */
855
+ DeviceType["AlarmPanel"] = "alarm-panel";
856
+ /** Generic user-settable input (HA `number` / `input_number` / `select`
857
+ * / `input_select` / `text` / `input_text` / `input_datetime`).
858
+ * Sub-type via `DeviceRole`: NumericControl / SelectControl /
859
+ * TextControl / DateTimeControl. */
860
+ DeviceType["Control"] = "control";
861
+ /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
862
+ * `presence` cap. */
863
+ DeviceType["Presence"] = "presence";
864
+ /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
865
+ * `weather` cap. */
866
+ DeviceType["Weather"] = "weather";
867
+ /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
868
+ DeviceType["Vacuum"] = "vacuum";
869
+ /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
870
+ * `lawn-mower-control` cap. */
871
+ DeviceType["LawnMower"] = "lawn-mower";
872
+ /** Physical HA device group — parent container for entity-children
873
+ * adopted from a single HA device entry. Not renderable as a
874
+ * standalone device; exists only to anchor child entities. */
875
+ DeviceType["Container"] = "container";
876
+ /** Single still-image entity (HA `image.*`). Read-only display of an
877
+ * `entity_picture` signed URL the browser loads directly. `image` cap. */
878
+ DeviceType["Image"] = "image";
879
+ /** Smart pet feeder — cloud-connected food dispenser with a bowl food
880
+ * level, battery, desiccant life, feeding state and manual-feed /
881
+ * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
882
+ * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
883
+ * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
884
+ * integrations sharing the same food/desiccant/hopper surface. */
885
+ DeviceType["PetFeeder"] = "pet-feeder";
886
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
887
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
888
+ * of a container and is BUILT as a composed device (D967). */
889
+ DeviceType["PetFountain"] = "pet-fountain";
890
+ /** Built-in dishwasher — program lifecycle, door / remote-start gates,
891
+ * salt and rinse-aid levels. Installed with the `dishwasher` cap.
892
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
893
+ * A plug-built dishwasher carries running-state and power instead. */
894
+ DeviceType["Dishwasher"] = "dishwasher";
895
+ /** Washing machine. A plug-built one is running while its power stays
896
+ * above a threshold. No program cap — that surface is the dishwasher's. */
897
+ DeviceType["Washer"] = "washer";
898
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
899
+ DeviceType["Dryer"] = "dryer";
900
+ /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
901
+ * other radio-to-IP bridges). It carries the box's own facts (firmware,
902
+ * address, connection state) and the actions the box really supports
903
+ * (`reboot`, `device-admin-link`). Products are NOT its children — they are
904
+ * adopted beside it — and its connection settings stay on the integration. */
905
+ DeviceType["Gateway"] = "gateway";
906
+ return DeviceType;
907
+ }({});
908
+ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
909
+ DeviceFeature["BatteryOperated"] = "battery-operated";
910
+ DeviceFeature["Rebootable"] = "rebootable";
911
+ /**
912
+ * Device supports an on-demand re-sync of its derived spec with its
913
+ * upstream source — drives the generic Re-sync button. The owning
914
+ * provider implements the action via the `device-adoption.resync` cap.
915
+ */
916
+ DeviceFeature["Resyncable"] = "resyncable";
917
+ DeviceFeature["NativeSnapshot"] = "native-snapshot";
918
+ DeviceFeature["DoorbellButton"] = "doorbell-button";
919
+ DeviceFeature["TwoWayAudio"] = "two-way-audio";
920
+ DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
921
+ /**
922
+ * Camera supports the on-firmware autotrack subsystem (subject-
923
+ * following). Distinct from `PanTiltZoom` because not every PTZ
924
+ * camera ships autotrack — the admin UI uses this flag to gate
925
+ * the autotrack toggle / settings card without re-deriving from
926
+ * the cap registry. Mirrors `ptz-autotrack` cap registration:
927
+ * driver sets this feature when probe confirms the firmware
928
+ * surface, and registers the cap in the same code path.
929
+ */
930
+ DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
931
+ /**
932
+ * Accessory exposes a "trigger on motion" toggle — the parent camera's
933
+ * motion detection automatically activates this device. Mirrors
934
+ * `motion-trigger` cap registration: drivers set this feature in the
935
+ * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
936
+ *
937
+ * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
938
+ * fast scalar without binding fetch), notifier rules, and `listAll`
939
+ * filters that want "all devices with on-motion behaviour".
940
+ */
941
+ DeviceFeature["MotionTrigger"] = "motion-trigger";
942
+ /** Light supports rgb-triplet color via `color` cap. */
943
+ DeviceFeature["LightColorRgb"] = "light-color-rgb";
944
+ /** Light supports HSV color via `color` cap. */
945
+ DeviceFeature["LightColorHsv"] = "light-color-hsv";
946
+ /** Light supports color-temperature (mired) via `color` cap. */
947
+ DeviceFeature["LightColorMired"] = "light-color-mired";
948
+ /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
949
+ * targetHigh). Gates the range slider UI. */
950
+ DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
951
+ /** Thermostat exposes target humidity and/or current humidity
952
+ * readings. Gates the humidity controls. */
953
+ DeviceFeature["ClimateHumidity"] = "climate-humidity";
954
+ /** Thermostat exposes a fan-mode selector. */
955
+ DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
956
+ /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
957
+ DeviceFeature["ClimatePreset"] = "climate-preset";
958
+ /** Thermostat exposes a vertical louver swing toggle. Gates the
959
+ * vertical-swing switch in the climate UI. Independent of horizontal. */
960
+ DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
961
+ /** Thermostat exposes a horizontal louver swing toggle. Gates the
962
+ * horizontal-swing switch in the climate UI. Independent of vertical. */
963
+ DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
964
+ /** Cover exposes intermediate position control (0..100). Gates the
965
+ * position slider UI. */
966
+ DeviceFeature["CoverPositionable"] = "cover-positionable";
967
+ /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
968
+ DeviceFeature["CoverTilt"] = "cover-tilt";
969
+ /** Valve exposes intermediate position control (0..100). Gates the
970
+ * position slider / drag surface UI. */
971
+ DeviceFeature["ValvePositionable"] = "valve-positionable";
972
+ /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
973
+ DeviceFeature["FanSpeed"] = "fan-speed";
974
+ /** Fan exposes a preset mode selector. */
975
+ DeviceFeature["FanPreset"] = "fan-preset";
976
+ /** Fan exposes blade direction (forward/reverse) — typical of
977
+ * ceiling fans. */
978
+ DeviceFeature["FanDirection"] = "fan-direction";
979
+ /** Fan exposes an oscillation toggle. */
980
+ DeviceFeature["FanOscillating"] = "fan-oscillating";
981
+ /** Lock requires a PIN code on lock/unlock. Gates the code-entry
982
+ * field on the UI lock-controls panel. */
983
+ DeviceFeature["LockPinRequired"] = "lock-pin-required";
984
+ /** Lock supports a latch-release ("open door") action distinct from
985
+ * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
986
+ * `supported_features`. Gates the Open Door button in the UI. */
987
+ DeviceFeature["LockOpen"] = "lock-open";
988
+ /** Media player exposes a seek-to-position surface. */
989
+ DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
990
+ /** Media player exposes a volume-level setter. */
991
+ DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
992
+ /** Media player exposes a mute toggle distinct from volume=0. */
993
+ DeviceFeature["MediaPlayerMute"] = "media-player-mute";
994
+ /** Media player exposes a shuffle toggle. */
995
+ DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
996
+ /** Media player exposes a repeat mode (off / all / one). */
997
+ DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
998
+ /** Media player exposes a source / input selector. */
999
+ DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
1000
+ /** Media player exposes a play-arbitrary-media surface (URL / id). */
1001
+ DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
1002
+ /** Media player exposes next-track. */
1003
+ DeviceFeature["MediaPlayerNext"] = "media-player-next";
1004
+ /** Media player exposes previous-track. */
1005
+ DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
1006
+ /** Media player exposes stop distinct from pause. */
1007
+ DeviceFeature["MediaPlayerStop"] = "media-player-stop";
1008
+ /** Alarm panel requires a PIN code on arm/disarm. */
1009
+ DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
1010
+ /** Presence device carries GPS coordinates (lat/lng/accuracy) in
1011
+ * addition to a textual location. */
1012
+ DeviceFeature["PresenceGps"] = "presence-gps";
1013
+ /** Notifier accepts an inline / URL image attachment. */
1014
+ DeviceFeature["NotifierImage"] = "notifier-image";
1015
+ /** Notifier accepts a priority hint (high/normal/low). */
1016
+ DeviceFeature["NotifierPriority"] = "notifier-priority";
1017
+ /** Notifier accepts a free-form `data` payload for platform-specific
1018
+ * fields. */
1019
+ DeviceFeature["NotifierData"] = "notifier-data";
1020
+ /** Notifier supports interactive action buttons / callbacks. */
1021
+ DeviceFeature["NotifierActions"] = "notifier-actions";
1022
+ /** Notifier supports per-call recipient targeting (multi-user). */
1023
+ DeviceFeature["NotifierRecipients"] = "notifier-recipients";
1024
+ /** Script runner accepts a variables map on each run invocation. */
1025
+ DeviceFeature["ScriptVariables"] = "script-variables";
1026
+ /** Automation `trigger` accepts a skipCondition flag — fires the
1027
+ * automation's actions while bypassing its condition block. */
1028
+ DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
1029
+ /** Robot vacuum exposes a live cleaning map (image child). Gates the
1030
+ * map tile in the vacuum UI. */
1031
+ DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
1032
+ /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
1033
+ * switches group. */
1034
+ DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
1035
+ /** Robot mower exposes a live mowing map (SVG image child). Gates the
1036
+ * mower map tile. */
1037
+ DeviceFeature["MowerHasMap"] = "mower-has-map";
1038
+ /** Robot mower exposes targeted mowing (all-area / zones / edges /
1039
+ * spots) — gates the Mow action + map selectors. */
1040
+ DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
1041
+ return DeviceFeature;
1042
+ }({});
1043
+ /**
1044
+ * Semantic role a device plays within its parent. Populated by driver
1045
+ * addons when creating accessory devices (Reolink siren/floodlight/
1046
+ * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
1047
+ * admin UI to pick icons, labels, and widgets — a `Switch` with
1048
+ * `role: Floodlight` renders as a bulb with a brightness slider,
1049
+ * whereas a `Switch` with `role: Siren` renders as a klaxon.
1050
+ *
1051
+ * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
1052
+ * sqlite as a nullable TEXT column — old rows keep working unchanged.
1053
+ */
1054
+ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
1055
+ DeviceRole["Siren"] = "siren";
1056
+ DeviceRole["Floodlight"] = "floodlight";
1057
+ DeviceRole["Spotlight"] = "spotlight";
1058
+ DeviceRole["PirSensor"] = "pir-sensor";
1059
+ DeviceRole["Chime"] = "chime";
1060
+ DeviceRole["Autotrack"] = "autotrack";
1061
+ DeviceRole["Nightvision"] = "nightvision";
1062
+ DeviceRole["PrivacyMask"] = "privacy-mask";
1063
+ DeviceRole["Doorbell"] = "doorbell";
1064
+ /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
1065
+ DeviceRole["CoverBlind"] = "cover-blind";
1066
+ /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
1067
+ DeviceRole["CoverShutter"] = "cover-shutter";
1068
+ /** Exterior awning. HA `device_class: awning`. */
1069
+ DeviceRole["CoverAwning"] = "cover-awning";
1070
+ /** Curtain / drape. HA `device_class: curtain`. */
1071
+ DeviceRole["CoverCurtain"] = "cover-curtain";
1072
+ /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
1073
+ DeviceRole["CoverGarage"] = "cover-garage";
1074
+ /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
1075
+ DeviceRole["CoverGate"] = "cover-gate";
1076
+ /** Door. HA `device_class: door`. A security actuator: exports gate it. */
1077
+ DeviceRole["CoverDoor"] = "cover-door";
1078
+ /** Window. HA `device_class: window`. */
1079
+ DeviceRole["CoverWindow"] = "cover-window";
1080
+ /** Virtual HA toggle (input_boolean.*) — distinguishable from a
1081
+ * real Switch device for UI rendering / export adapters. */
1082
+ DeviceRole["BinaryHelper"] = "binary-helper";
1083
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
1084
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
1085
+ DeviceRole["Outlet"] = "outlet";
1086
+ /** Generic motion / occupancy / moving event source. Distinct from
1087
+ * the camera accessory PirSensor role: that one is a camera child;
1088
+ * this is a standalone HA / 3rd-party motion sensor. */
1089
+ DeviceRole["MotionSensor"] = "motion-sensor";
1090
+ DeviceRole["ContactSensor"] = "contact-sensor";
1091
+ DeviceRole["LeakSensor"] = "leak-sensor";
1092
+ DeviceRole["SmokeSensor"] = "smoke-sensor";
1093
+ DeviceRole["COSensor"] = "co-sensor";
1094
+ DeviceRole["GasSensor"] = "gas-sensor";
1095
+ DeviceRole["TamperSensor"] = "tamper-sensor";
1096
+ DeviceRole["VibrationSensor"] = "vibration-sensor";
1097
+ DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
1098
+ DeviceRole["SoundSensor"] = "sound-sensor";
1099
+ /** Fallback for `binary_sensor` without a known `device_class`. */
1100
+ DeviceRole["BinarySensor"] = "binary-sensor";
1101
+ DeviceRole["TemperatureSensor"] = "temperature-sensor";
1102
+ DeviceRole["HumiditySensor"] = "humidity-sensor";
1103
+ DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
1104
+ DeviceRole["PressureSensor"] = "pressure-sensor";
1105
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
1106
+ DeviceRole["WindSensor"] = "wind-sensor";
1107
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
1108
+ DeviceRole["RainSensor"] = "rain-sensor";
1109
+ /** UV index (weather-station `uv-sensor` cap). */
1110
+ DeviceRole["UvSensor"] = "uv-sensor";
1111
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
1112
+ * Distinct from AmbientLightSensor (lux). */
1113
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
1114
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
1115
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
1116
+ DeviceRole["PowerSensor"] = "power-sensor";
1117
+ DeviceRole["EnergySensor"] = "energy-sensor";
1118
+ DeviceRole["VoltageSensor"] = "voltage-sensor";
1119
+ DeviceRole["CurrentSensor"] = "current-sensor";
1120
+ DeviceRole["AirQualitySensor"] = "air-quality-sensor";
1121
+ /** Battery level (numeric % via `sensor` OR low-bool via
1122
+ * `binary_sensor` — the cap distinguishes via the value type). */
1123
+ DeviceRole["BatterySensor"] = "battery-sensor";
1124
+ /** Fallback for `sensor` numeric without a known `device_class`. */
1125
+ DeviceRole["NumericSensor"] = "numeric-sensor";
1126
+ /** String / enum state (HA `sensor` with `state_class: enum` or
1127
+ * `attributes.options`). */
1128
+ DeviceRole["EnumSensor"] = "enum-sensor";
1129
+ /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
1130
+ * or `date`). The slice carries the raw ISO string verbatim (hosted on
1131
+ * the `enum-sensor` cap); the UI renders it locale-formatted. */
1132
+ DeviceRole["DateTimeSensor"] = "datetime-sensor";
1133
+ /** Last-resort fallback when nothing else matches. */
1134
+ DeviceRole["GenericSensor"] = "generic-sensor";
1135
+ DeviceRole["NumericControl"] = "numeric-control";
1136
+ DeviceRole["SelectControl"] = "select-control";
1137
+ DeviceRole["TextControl"] = "text-control";
1138
+ DeviceRole["DateTimeControl"] = "datetime-control";
1139
+ /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
1140
+ * rich features (image, priority, channel routing). */
1141
+ DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
1142
+ /** Chat / messaging service (HA `notify.telegram_*`,
1143
+ * `notify.discord_*`, etc.). */
1144
+ DeviceRole["MessagingNotifier"] = "messaging-notifier";
1145
+ /** Email-based delivery (HA `notify.smtp`, etc.). */
1146
+ DeviceRole["EmailNotifier"] = "email-notifier";
1147
+ /** Fallback when the notifier service name doesn't match a known
1148
+ * pattern. */
1149
+ DeviceRole["GenericNotifier"] = "generic-notifier";
1150
+ /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
1151
+ * cap. A `DeviceType.Generic` device. */
1152
+ DeviceRole["IrTransmitter"] = "ir-transmitter";
1153
+ return DeviceRole;
1154
+ }({});
1155
+ //#endregion
780
1156
  //#region ../../node_modules/zod/v4/core/core.js
781
1157
  var _a$1;
782
1158
  function $constructor(name, initializer, params) {
@@ -5588,7 +5964,7 @@ var ZodIssueCode = {
5588
5964
  var ZodFirstPartyTypeKind;
5589
5965
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5590
5966
  //#endregion
5591
- //#region ../types/dist/sleep-dco0-jRo.mjs
5967
+ //#region ../types/dist/sleep-CSYuxssh.mjs
5592
5968
  /**
5593
5969
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5594
5970
  * window to float samples (D455).
@@ -6104,6 +6480,102 @@ function emitReadiness(bus, params) {
6104
6480
  ts
6105
6481
  }));
6106
6482
  }
6483
+ var NOT_RUN = {
6484
+ declared: false,
6485
+ snapshot: null,
6486
+ applied: [],
6487
+ failed: [],
6488
+ blocked: [],
6489
+ skipped: [],
6490
+ blockOwner: []
6491
+ };
6492
+ /**
6493
+ * Offer `migrations` to the engine and run what it says to run, in its order. Never
6494
+ * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6495
+ * because the hub (and this addon's boot) must not hang on a migration.
6496
+ */
6497
+ async function runAddonMigrations(host, migrations, logger) {
6498
+ const first = migrations[0];
6499
+ if (first === void 0) return NOT_RUN;
6500
+ const owner = first.meta.owner;
6501
+ const log = logger.withTags({ migrationOwner: owner });
6502
+ const byId = new Map(migrations.map((m) => [m.meta.id, m]));
6503
+ let declaration;
6504
+ try {
6505
+ declaration = await host.declareOwner({
6506
+ owner,
6507
+ metas: migrations.map((m) => m.meta)
6508
+ });
6509
+ } catch (err) {
6510
+ log.error("the migration engine refused or could not take this addon catalogue — none ran", { meta: { error: errMsg(err) } });
6511
+ return {
6512
+ ...NOT_RUN,
6513
+ blockOwner: migrations.filter((m) => m.meta.onFailure === "block-owner").map((m) => m.meta.id)
6514
+ };
6515
+ }
6516
+ const applied = [];
6517
+ const failed = [];
6518
+ const blocked = [];
6519
+ const skipped = [];
6520
+ const settle = (id, status) => {
6521
+ if (status === "applied") applied.push(id);
6522
+ else if (status === "failed") failed.push(id);
6523
+ else if (status === "blocked") blocked.push(id);
6524
+ else skipped.push(id);
6525
+ };
6526
+ for (const step of declaration.steps) {
6527
+ const bound = byId.get(step.id);
6528
+ if (bound === void 0 || step.action === "skip") {
6529
+ skipped.push(step.id);
6530
+ continue;
6531
+ }
6532
+ const tags = { migrationId: step.id };
6533
+ try {
6534
+ const gate = await host.startMigration({ id: step.id });
6535
+ if (!gate.proceed) {
6536
+ log.warn("migration not run", {
6537
+ tags,
6538
+ meta: {
6539
+ state: gate.state,
6540
+ reason: gate.reason
6541
+ }
6542
+ });
6543
+ settle(step.id, gate.state === "pending" ? "blocked" : gate.state);
6544
+ continue;
6545
+ }
6546
+ const result = await bound.execute();
6547
+ const outcome = await host.finishMigration({
6548
+ id: step.id,
6549
+ result
6550
+ });
6551
+ if (outcome.status === "applied") log.info("migration applied", {
6552
+ tags,
6553
+ meta: { rowsChanged: outcome.rowsChanged }
6554
+ });
6555
+ else log.error("migration failed", {
6556
+ tags,
6557
+ meta: { error: outcome.error }
6558
+ });
6559
+ settle(step.id, outcome.status);
6560
+ } catch (err) {
6561
+ log.error("migration could not be completed with the engine", {
6562
+ tags,
6563
+ meta: { error: errMsg(err) }
6564
+ });
6565
+ failed.push(step.id);
6566
+ }
6567
+ }
6568
+ const blockOwner = [...failed, ...blocked].filter((id) => byId.get(id)?.meta.onFailure === "block-owner");
6569
+ return {
6570
+ declared: true,
6571
+ snapshot: declaration.snapshot,
6572
+ applied,
6573
+ failed,
6574
+ blocked,
6575
+ skipped,
6576
+ blockOwner
6577
+ };
6578
+ }
6107
6579
  /**
6108
6580
  * Build a {@link DurableState} over a single store key. Transport-agnostic:
6109
6581
  * `read`/`write` are the addon-store or device-store accessors. The whole
@@ -7159,6 +7631,28 @@ var BaseAddon = class {
7159
7631
  return normalized;
7160
7632
  }
7161
7633
  /**
7634
+ * Run this addon's migration catalogue (D987 M3) through the hub's `migrations`
7635
+ * engine. Call it at the point of the boot where the data must already have the new
7636
+ * shape — `onInitialize`, or `onRestoreDevices` for a migration that needs the
7637
+ * restored devices — with the catalogue bound to the addon's ports
7638
+ * (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
7639
+ *
7640
+ * An engine that cannot be reached or refuses the catalogue is logged and runs
7641
+ * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7642
+ * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7643
+ * serve data whose shape it cannot trust; `tolerate` migrations never do.
7644
+ */
7645
+ async runMigrations(migrations) {
7646
+ const api = this.ctx.api.migrations;
7647
+ const report = await runAddonMigrations({
7648
+ declareOwner: (input) => api.declareOwner.mutate(input),
7649
+ startMigration: (input) => api.startMigration.mutate(input),
7650
+ finishMigration: (input) => api.finishMigration.mutate(input)
7651
+ }, migrations, this.ctx.logger);
7652
+ 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`);
7653
+ return report;
7654
+ }
7655
+ /**
7162
7656
  * Called by the isolated-process runner AFTER `broker.start()`.
7163
7657
  * In-process addons never need this because the hub broker is already
7164
7658
  * running when `initialize()` fires. For forked children the broker
@@ -8456,6 +8950,98 @@ function defineEntryKind(name, spec) {
8456
8950
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8457
8951
  return defined;
8458
8952
  }
8953
+ object({
8954
+ field: string().min(1),
8955
+ on: literal("advance")
8956
+ });
8957
+ /**
8958
+ * Output schema shared by the contribution + live methods.
8959
+ *
8960
+ * Mirrors the `ConfigUISchemaWithValues` shape (sections[] + optional
8961
+ * tabs[]) without importing from `../interfaces/config-ui.js` — a
8962
+ * concrete-but-lenient Zod object keeps tRPC output inference happy
8963
+ * (using `z.unknown()` here collapses unrelated router branches to
8964
+ * `unknown` when the generator re-inlines the huge AppRouter type).
8965
+ *
8966
+ * `.passthrough()` on sections/fields accepts whatever FormBuilder
8967
+ * extensions the caller adds (showWhen, displayScale, …) without
8968
+ * rebuilding every time a new field kind is introduced.
8969
+ */
8970
+ var ContributionSectionSchema = object({
8971
+ id: string(),
8972
+ title: string(),
8973
+ description: string().optional(),
8974
+ style: _enum(["card", "accordion"]).optional(),
8975
+ defaultCollapsed: boolean().optional(),
8976
+ columns: union([
8977
+ literal(1),
8978
+ literal(2),
8979
+ literal(3),
8980
+ literal(4)
8981
+ ]).optional(),
8982
+ tab: string().optional(),
8983
+ location: _enum([
8984
+ "settings",
8985
+ "top-tab",
8986
+ "hero"
8987
+ ]).optional(),
8988
+ order: number().optional(),
8989
+ fields: array(any())
8990
+ });
8991
+ object({
8992
+ tabs: array(object({
8993
+ id: string(),
8994
+ label: string(),
8995
+ icon: string(),
8996
+ order: number().optional()
8997
+ })).optional(),
8998
+ sections: array(ContributionSectionSchema)
8999
+ }).nullable();
9000
+ object({ deviceId: number() }), object({ deviceId: number() }), object({
9001
+ deviceId: number(),
9002
+ patch: record(string(), unknown())
9003
+ }), object({ success: literal(true) });
9004
+ object({ deviceId: number() }), unknown().nullable();
9005
+ function method(input, output, options) {
9006
+ return {
9007
+ input,
9008
+ output,
9009
+ kind: options?.kind ?? "query",
9010
+ ...options?.auth !== void 0 ? { auth: options.auth } : {},
9011
+ ...options?.access !== void 0 ? { access: options.access } : {},
9012
+ ...options?.role !== void 0 ? { role: options.role } : {},
9013
+ ...options?.caller !== void 0 ? { caller: options.caller } : {},
9014
+ timeoutMs: options?.timeoutMs
9015
+ };
9016
+ }
9017
+ /**
9018
+ * A wrapper/system-only method: served exclusively by the cap's system-level
9019
+ * provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
9020
+ * driver natives don't stub out a wrapper concern (e.g. a cross-device cache
9021
+ * overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
9022
+ */
9023
+ function systemMethod(input, output, options) {
9024
+ return {
9025
+ ...method(input, output, options),
9026
+ systemOnly: true
9027
+ };
9028
+ }
9029
+ /**
9030
+ * A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
9031
+ * on `InferProvider`. The `providerOptional: true` literal is what
9032
+ * `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
9033
+ * for when this is the honest answer and when it is a soft stub.
9034
+ */
9035
+ function optionalMethod(input, output, options) {
9036
+ return {
9037
+ ...method(input, output, options),
9038
+ providerOptional: true
9039
+ };
9040
+ }
9041
+ /** Shorthand to define an event schema */
9042
+ function event(data) {
9043
+ return { data };
9044
+ }
8459
9045
  var READING_QUANTITIES = [
8460
9046
  "temperature",
8461
9047
  "pressure",
@@ -8848,7 +9434,7 @@ var ProfileRtspEntrySchema = object({
8848
9434
  /**
8849
9435
  * The model weight formats a catalog entry can ship. Schema-free on purpose:
8850
9436
  * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
8851
- * (the model downloader in `@camstack/system`) needs only the list and must
9437
+ * (the model downloader in `@camstack/server/system`) needs only the list and must
8852
9438
  * reach it through `@camstack/types/addon` without building the cap schema
8853
9439
  * graph (D28).
8854
9440
  */
@@ -8860,98 +9446,6 @@ var MODEL_FORMATS = [
8860
9446
  "pt",
8861
9447
  "gguf"
8862
9448
  ];
8863
- object({
8864
- field: string().min(1),
8865
- on: literal("advance")
8866
- });
8867
- /**
8868
- * Output schema shared by the contribution + live methods.
8869
- *
8870
- * Mirrors the `ConfigUISchemaWithValues` shape (sections[] + optional
8871
- * tabs[]) without importing from `../interfaces/config-ui.js` — a
8872
- * concrete-but-lenient Zod object keeps tRPC output inference happy
8873
- * (using `z.unknown()` here collapses unrelated router branches to
8874
- * `unknown` when the generator re-inlines the huge AppRouter type).
8875
- *
8876
- * `.passthrough()` on sections/fields accepts whatever FormBuilder
8877
- * extensions the caller adds (showWhen, displayScale, …) without
8878
- * rebuilding every time a new field kind is introduced.
8879
- */
8880
- var ContributionSectionSchema = object({
8881
- id: string(),
8882
- title: string(),
8883
- description: string().optional(),
8884
- style: _enum(["card", "accordion"]).optional(),
8885
- defaultCollapsed: boolean().optional(),
8886
- columns: union([
8887
- literal(1),
8888
- literal(2),
8889
- literal(3),
8890
- literal(4)
8891
- ]).optional(),
8892
- tab: string().optional(),
8893
- location: _enum([
8894
- "settings",
8895
- "top-tab",
8896
- "hero"
8897
- ]).optional(),
8898
- order: number().optional(),
8899
- fields: array(any())
8900
- });
8901
- object({
8902
- tabs: array(object({
8903
- id: string(),
8904
- label: string(),
8905
- icon: string(),
8906
- order: number().optional()
8907
- })).optional(),
8908
- sections: array(ContributionSectionSchema)
8909
- }).nullable();
8910
- object({ deviceId: number() }), object({ deviceId: number() }), object({
8911
- deviceId: number(),
8912
- patch: record(string(), unknown())
8913
- }), object({ success: literal(true) });
8914
- object({ deviceId: number() }), unknown().nullable();
8915
- function method(input, output, options) {
8916
- return {
8917
- input,
8918
- output,
8919
- kind: options?.kind ?? "query",
8920
- ...options?.auth !== void 0 ? { auth: options.auth } : {},
8921
- ...options?.access !== void 0 ? { access: options.access } : {},
8922
- ...options?.role !== void 0 ? { role: options.role } : {},
8923
- ...options?.caller !== void 0 ? { caller: options.caller } : {},
8924
- timeoutMs: options?.timeoutMs
8925
- };
8926
- }
8927
- /**
8928
- * A wrapper/system-only method: served exclusively by the cap's system-level
8929
- * provider (`InferProvider`), and OPTIONAL on `InferNativeProvider` so per-device
8930
- * driver natives don't stub out a wrapper concern (e.g. a cross-device cache
8931
- * overview). The `systemOnly: true` literal is what `InferNativeProvider` keys on.
8932
- */
8933
- function systemMethod(input, output, options) {
8934
- return {
8935
- ...method(input, output, options),
8936
- systemOnly: true
8937
- };
8938
- }
8939
- /**
8940
- * A method a SOURCE of a collection cap may legitimately not serve — OPTIONAL
8941
- * on `InferProvider`. The `providerOptional: true` literal is what
8942
- * `InferProvider` keys on; see {@link CapabilityMethodSchema.providerOptional}
8943
- * for when this is the honest answer and when it is a soft stub.
8944
- */
8945
- function optionalMethod(input, output, options) {
8946
- return {
8947
- ...method(input, output, options),
8948
- providerOptional: true
8949
- };
8950
- }
8951
- /** Shorthand to define an event schema */
8952
- function event(data) {
8953
- return { data };
8954
- }
8955
9449
  /**
8956
9450
  * Per-call node pinning for `ctx.api` capability calls.
8957
9451
  *
@@ -9252,6 +9746,7 @@ var CAP_NAMES = Object.freeze([
9252
9746
  "mesh-network",
9253
9747
  "meter",
9254
9748
  "metrics-provider",
9749
+ "migrations",
9255
9750
  "model-convert",
9256
9751
  "model-distributor",
9257
9752
  "motion",
@@ -11154,379 +11649,7 @@ object({
11154
11649
  params: record(string(), union([string(), number()])).optional()
11155
11650
  });
11156
11651
  //#endregion
11157
- //#region ../types/dist/err-msg-BD5buxvT.mjs
11158
- var DeviceType = /* @__PURE__ */ function(DeviceType) {
11159
- DeviceType["Camera"] = "camera";
11160
- DeviceType["Hub"] = "hub";
11161
- DeviceType["Light"] = "light";
11162
- DeviceType["Siren"] = "siren";
11163
- DeviceType["Switch"] = "switch";
11164
- DeviceType["Sensor"] = "sensor";
11165
- DeviceType["Thermostat"] = "thermostat";
11166
- /** Air-conditioner / heat-pump climate device (HVAC) — shares the
11167
- * `climate-control` cap surface with `Thermostat` but renders a
11168
- * dedicated AC-appropriate control UI (mode chips, fan speed,
11169
- * independent vertical/horizontal swing). Sources: native Gree, and
11170
- * reusable by other AC integrations. */
11171
- DeviceType["Climate"] = "climate";
11172
- DeviceType["Button"] = "button";
11173
- /** Generic stateless event emitter — carries a device's EXACT declared
11174
- * event vocabulary verbatim (no normalization). Installed with the
11175
- * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
11176
- * HA bus events (e.g. `zha_event`, generic). */
11177
- DeviceType["EventEmitter"] = "event-emitter";
11178
- /** Firmware/software update entity — current vs available version,
11179
- * updatable flag, update state, and an install action. Installed with
11180
- * the `update` cap. Sources: Homematic firmware-update channels (and
11181
- * reusable by other providers, e.g. HA `update.*` entities). */
11182
- DeviceType["Update"] = "update";
11183
- DeviceType["Generic"] = "generic";
11184
- /** Generic notification delivery target (HA `notify.<service>`, future
11185
- * Telegram / Discord / ntfy / SMTP, …). One device per delivery
11186
- * endpoint; the `notifier` cap defines the send surface. */
11187
- DeviceType["Notifier"] = "notifier";
11188
- /** Pre-recorded action sequence with optional parameters
11189
- * (HA `script.*`). Runnable via `script-runner` cap. */
11190
- DeviceType["Script"] = "script";
11191
- /** Automation rule (HA `automation.*`) — enable/disable + manual
11192
- * trigger surface exposed via `automation-control` cap. */
11193
- DeviceType["Automation"] = "automation";
11194
- /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
11195
- DeviceType["Lock"] = "lock";
11196
- /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
11197
- * `valve.*`). `cover` cap with sub-roles for variant. */
11198
- DeviceType["Cover"] = "cover";
11199
- /** Pipe / water / gas valve with open/close/stop and optional
11200
- * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
11201
- * modelled on the same open/closed lifecycle. */
11202
- DeviceType["Valve"] = "valve";
11203
- /** Humidifier / dehumidifier with on/off + target humidity + mode
11204
- * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
11205
- * modelled on the same target / mode lifecycle. */
11206
- DeviceType["Humidifier"] = "humidifier";
11207
- /** Water heater / boiler with target temperature + operation mode +
11208
- * away mode (HA `water_heater.*`). `water-heater` cap — a
11209
- * climate-family actuator. */
11210
- DeviceType["WaterHeater"] = "water-heater";
11211
- /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
11212
- DeviceType["Fan"] = "fan";
11213
- /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
11214
- * the camera surface — those use `Camera`. `media-player` cap. */
11215
- DeviceType["MediaPlayer"] = "media-player";
11216
- /** Security panel / alarm system (HA `alarm_control_panel.*`).
11217
- * `alarm-panel` cap. */
11218
- DeviceType["AlarmPanel"] = "alarm-panel";
11219
- /** Generic user-settable input (HA `number` / `input_number` / `select`
11220
- * / `input_select` / `text` / `input_text` / `input_datetime`).
11221
- * Sub-type via `DeviceRole`: NumericControl / SelectControl /
11222
- * TextControl / DateTimeControl. */
11223
- DeviceType["Control"] = "control";
11224
- /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
11225
- * `presence` cap. */
11226
- DeviceType["Presence"] = "presence";
11227
- /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
11228
- * `weather` cap. */
11229
- DeviceType["Weather"] = "weather";
11230
- /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
11231
- DeviceType["Vacuum"] = "vacuum";
11232
- /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
11233
- * `lawn-mower-control` cap. */
11234
- DeviceType["LawnMower"] = "lawn-mower";
11235
- /** Physical HA device group — parent container for entity-children
11236
- * adopted from a single HA device entry. Not renderable as a
11237
- * standalone device; exists only to anchor child entities. */
11238
- DeviceType["Container"] = "container";
11239
- /** Single still-image entity (HA `image.*`). Read-only display of an
11240
- * `entity_picture` signed URL the browser loads directly. `image` cap. */
11241
- DeviceType["Image"] = "image";
11242
- /** Smart pet feeder — cloud-connected food dispenser with a bowl food
11243
- * level, battery, desiccant life, feeding state and manual-feed /
11244
- * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
11245
- * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
11246
- * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
11247
- * integrations sharing the same food/desiccant/hopper surface. */
11248
- DeviceType["PetFeeder"] = "pet-feeder";
11249
- /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
11250
- * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
11251
- * of a container and is BUILT as a composed device (D967). */
11252
- DeviceType["PetFountain"] = "pet-fountain";
11253
- /** Built-in dishwasher — program lifecycle, door / remote-start gates,
11254
- * salt and rinse-aid levels. Installed with the `dishwasher` cap.
11255
- * Sources: native Bosch Home Connect, reusable by similar integrations.
11256
- * A plug-built dishwasher carries running-state and power instead. */
11257
- DeviceType["Dishwasher"] = "dishwasher";
11258
- /** Washing machine. A plug-built one is running while its power stays
11259
- * above a threshold. No program cap — that surface is the dishwasher's. */
11260
- DeviceType["Washer"] = "washer";
11261
- /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
11262
- DeviceType["Dryer"] = "dryer";
11263
- /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
11264
- * other radio-to-IP bridges). It carries the box's own facts (firmware,
11265
- * address, connection state) and the actions the box really supports
11266
- * (`reboot`, `device-admin-link`). Products are NOT its children — they are
11267
- * adopted beside it — and its connection settings stay on the integration. */
11268
- DeviceType["Gateway"] = "gateway";
11269
- return DeviceType;
11270
- }({});
11271
- var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
11272
- DeviceFeature["BatteryOperated"] = "battery-operated";
11273
- DeviceFeature["Rebootable"] = "rebootable";
11274
- /**
11275
- * Device supports an on-demand re-sync of its derived spec with its
11276
- * upstream source — drives the generic Re-sync button. The owning
11277
- * provider implements the action via the `device-adoption.resync` cap.
11278
- */
11279
- DeviceFeature["Resyncable"] = "resyncable";
11280
- DeviceFeature["NativeSnapshot"] = "native-snapshot";
11281
- DeviceFeature["DoorbellButton"] = "doorbell-button";
11282
- DeviceFeature["TwoWayAudio"] = "two-way-audio";
11283
- DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
11284
- /**
11285
- * Camera supports the on-firmware autotrack subsystem (subject-
11286
- * following). Distinct from `PanTiltZoom` because not every PTZ
11287
- * camera ships autotrack — the admin UI uses this flag to gate
11288
- * the autotrack toggle / settings card without re-deriving from
11289
- * the cap registry. Mirrors `ptz-autotrack` cap registration:
11290
- * driver sets this feature when probe confirms the firmware
11291
- * surface, and registers the cap in the same code path.
11292
- */
11293
- DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
11294
- /**
11295
- * Accessory exposes a "trigger on motion" toggle — the parent camera's
11296
- * motion detection automatically activates this device. Mirrors
11297
- * `motion-trigger` cap registration: drivers set this feature in the
11298
- * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
11299
- *
11300
- * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
11301
- * fast scalar without binding fetch), notifier rules, and `listAll`
11302
- * filters that want "all devices with on-motion behaviour".
11303
- */
11304
- DeviceFeature["MotionTrigger"] = "motion-trigger";
11305
- /** Light supports rgb-triplet color via `color` cap. */
11306
- DeviceFeature["LightColorRgb"] = "light-color-rgb";
11307
- /** Light supports HSV color via `color` cap. */
11308
- DeviceFeature["LightColorHsv"] = "light-color-hsv";
11309
- /** Light supports color-temperature (mired) via `color` cap. */
11310
- DeviceFeature["LightColorMired"] = "light-color-mired";
11311
- /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
11312
- * targetHigh). Gates the range slider UI. */
11313
- DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
11314
- /** Thermostat exposes target humidity and/or current humidity
11315
- * readings. Gates the humidity controls. */
11316
- DeviceFeature["ClimateHumidity"] = "climate-humidity";
11317
- /** Thermostat exposes a fan-mode selector. */
11318
- DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
11319
- /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
11320
- DeviceFeature["ClimatePreset"] = "climate-preset";
11321
- /** Thermostat exposes a vertical louver swing toggle. Gates the
11322
- * vertical-swing switch in the climate UI. Independent of horizontal. */
11323
- DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
11324
- /** Thermostat exposes a horizontal louver swing toggle. Gates the
11325
- * horizontal-swing switch in the climate UI. Independent of vertical. */
11326
- DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
11327
- /** Cover exposes intermediate position control (0..100). Gates the
11328
- * position slider UI. */
11329
- DeviceFeature["CoverPositionable"] = "cover-positionable";
11330
- /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
11331
- DeviceFeature["CoverTilt"] = "cover-tilt";
11332
- /** Valve exposes intermediate position control (0..100). Gates the
11333
- * position slider / drag surface UI. */
11334
- DeviceFeature["ValvePositionable"] = "valve-positionable";
11335
- /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
11336
- DeviceFeature["FanSpeed"] = "fan-speed";
11337
- /** Fan exposes a preset mode selector. */
11338
- DeviceFeature["FanPreset"] = "fan-preset";
11339
- /** Fan exposes blade direction (forward/reverse) — typical of
11340
- * ceiling fans. */
11341
- DeviceFeature["FanDirection"] = "fan-direction";
11342
- /** Fan exposes an oscillation toggle. */
11343
- DeviceFeature["FanOscillating"] = "fan-oscillating";
11344
- /** Lock requires a PIN code on lock/unlock. Gates the code-entry
11345
- * field on the UI lock-controls panel. */
11346
- DeviceFeature["LockPinRequired"] = "lock-pin-required";
11347
- /** Lock supports a latch-release ("open door") action distinct from
11348
- * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
11349
- * `supported_features`. Gates the Open Door button in the UI. */
11350
- DeviceFeature["LockOpen"] = "lock-open";
11351
- /** Media player exposes a seek-to-position surface. */
11352
- DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
11353
- /** Media player exposes a volume-level setter. */
11354
- DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
11355
- /** Media player exposes a mute toggle distinct from volume=0. */
11356
- DeviceFeature["MediaPlayerMute"] = "media-player-mute";
11357
- /** Media player exposes a shuffle toggle. */
11358
- DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
11359
- /** Media player exposes a repeat mode (off / all / one). */
11360
- DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
11361
- /** Media player exposes a source / input selector. */
11362
- DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
11363
- /** Media player exposes a play-arbitrary-media surface (URL / id). */
11364
- DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
11365
- /** Media player exposes next-track. */
11366
- DeviceFeature["MediaPlayerNext"] = "media-player-next";
11367
- /** Media player exposes previous-track. */
11368
- DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
11369
- /** Media player exposes stop distinct from pause. */
11370
- DeviceFeature["MediaPlayerStop"] = "media-player-stop";
11371
- /** Alarm panel requires a PIN code on arm/disarm. */
11372
- DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
11373
- /** Presence device carries GPS coordinates (lat/lng/accuracy) in
11374
- * addition to a textual location. */
11375
- DeviceFeature["PresenceGps"] = "presence-gps";
11376
- /** Notifier accepts an inline / URL image attachment. */
11377
- DeviceFeature["NotifierImage"] = "notifier-image";
11378
- /** Notifier accepts a priority hint (high/normal/low). */
11379
- DeviceFeature["NotifierPriority"] = "notifier-priority";
11380
- /** Notifier accepts a free-form `data` payload for platform-specific
11381
- * fields. */
11382
- DeviceFeature["NotifierData"] = "notifier-data";
11383
- /** Notifier supports interactive action buttons / callbacks. */
11384
- DeviceFeature["NotifierActions"] = "notifier-actions";
11385
- /** Notifier supports per-call recipient targeting (multi-user). */
11386
- DeviceFeature["NotifierRecipients"] = "notifier-recipients";
11387
- /** Script runner accepts a variables map on each run invocation. */
11388
- DeviceFeature["ScriptVariables"] = "script-variables";
11389
- /** Automation `trigger` accepts a skipCondition flag — fires the
11390
- * automation's actions while bypassing its condition block. */
11391
- DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
11392
- /** Robot vacuum exposes a live cleaning map (image child). Gates the
11393
- * map tile in the vacuum UI. */
11394
- DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
11395
- /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
11396
- * switches group. */
11397
- DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
11398
- /** Robot mower exposes a live mowing map (SVG image child). Gates the
11399
- * mower map tile. */
11400
- DeviceFeature["MowerHasMap"] = "mower-has-map";
11401
- /** Robot mower exposes targeted mowing (all-area / zones / edges /
11402
- * spots) — gates the Mow action + map selectors. */
11403
- DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
11404
- return DeviceFeature;
11405
- }({});
11406
- /**
11407
- * Semantic role a device plays within its parent. Populated by driver
11408
- * addons when creating accessory devices (Reolink siren/floodlight/
11409
- * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
11410
- * admin UI to pick icons, labels, and widgets — a `Switch` with
11411
- * `role: Floodlight` renders as a bulb with a brightness slider,
11412
- * whereas a `Switch` with `role: Siren` renders as a klaxon.
11413
- *
11414
- * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
11415
- * sqlite as a nullable TEXT column — old rows keep working unchanged.
11416
- */
11417
- var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11418
- DeviceRole["Siren"] = "siren";
11419
- DeviceRole["Floodlight"] = "floodlight";
11420
- DeviceRole["Spotlight"] = "spotlight";
11421
- DeviceRole["PirSensor"] = "pir-sensor";
11422
- DeviceRole["Chime"] = "chime";
11423
- DeviceRole["Autotrack"] = "autotrack";
11424
- DeviceRole["Nightvision"] = "nightvision";
11425
- DeviceRole["PrivacyMask"] = "privacy-mask";
11426
- DeviceRole["Doorbell"] = "doorbell";
11427
- /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
11428
- DeviceRole["CoverBlind"] = "cover-blind";
11429
- /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
11430
- DeviceRole["CoverShutter"] = "cover-shutter";
11431
- /** Exterior awning. HA `device_class: awning`. */
11432
- DeviceRole["CoverAwning"] = "cover-awning";
11433
- /** Curtain / drape. HA `device_class: curtain`. */
11434
- DeviceRole["CoverCurtain"] = "cover-curtain";
11435
- /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
11436
- DeviceRole["CoverGarage"] = "cover-garage";
11437
- /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
11438
- DeviceRole["CoverGate"] = "cover-gate";
11439
- /** Door. HA `device_class: door`. A security actuator: exports gate it. */
11440
- DeviceRole["CoverDoor"] = "cover-door";
11441
- /** Window. HA `device_class: window`. */
11442
- DeviceRole["CoverWindow"] = "cover-window";
11443
- /** Virtual HA toggle (input_boolean.*) — distinguishable from a
11444
- * real Switch device for UI rendering / export adapters. */
11445
- DeviceRole["BinaryHelper"] = "binary-helper";
11446
- /** A smart plug: a `switch` whose source says it is an outlet (HA
11447
- * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
11448
- DeviceRole["Outlet"] = "outlet";
11449
- /** Generic motion / occupancy / moving event source. Distinct from
11450
- * the camera accessory PirSensor role: that one is a camera child;
11451
- * this is a standalone HA / 3rd-party motion sensor. */
11452
- DeviceRole["MotionSensor"] = "motion-sensor";
11453
- DeviceRole["ContactSensor"] = "contact-sensor";
11454
- DeviceRole["LeakSensor"] = "leak-sensor";
11455
- DeviceRole["SmokeSensor"] = "smoke-sensor";
11456
- DeviceRole["COSensor"] = "co-sensor";
11457
- DeviceRole["GasSensor"] = "gas-sensor";
11458
- DeviceRole["TamperSensor"] = "tamper-sensor";
11459
- DeviceRole["VibrationSensor"] = "vibration-sensor";
11460
- DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
11461
- DeviceRole["SoundSensor"] = "sound-sensor";
11462
- /** Fallback for `binary_sensor` without a known `device_class`. */
11463
- DeviceRole["BinarySensor"] = "binary-sensor";
11464
- DeviceRole["TemperatureSensor"] = "temperature-sensor";
11465
- DeviceRole["HumiditySensor"] = "humidity-sensor";
11466
- DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
11467
- DeviceRole["PressureSensor"] = "pressure-sensor";
11468
- /** Wind speed or direction (weather-station `wind-sensor` cap). */
11469
- DeviceRole["WindSensor"] = "wind-sensor";
11470
- /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
11471
- DeviceRole["RainSensor"] = "rain-sensor";
11472
- /** UV index (weather-station `uv-sensor` cap). */
11473
- DeviceRole["UvSensor"] = "uv-sensor";
11474
- /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
11475
- * Distinct from AmbientLightSensor (lux). */
11476
- DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
11477
- /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
11478
- DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
11479
- DeviceRole["PowerSensor"] = "power-sensor";
11480
- DeviceRole["EnergySensor"] = "energy-sensor";
11481
- DeviceRole["VoltageSensor"] = "voltage-sensor";
11482
- DeviceRole["CurrentSensor"] = "current-sensor";
11483
- DeviceRole["AirQualitySensor"] = "air-quality-sensor";
11484
- /** Battery level (numeric % via `sensor` OR low-bool via
11485
- * `binary_sensor` — the cap distinguishes via the value type). */
11486
- DeviceRole["BatterySensor"] = "battery-sensor";
11487
- /** Fallback for `sensor` numeric without a known `device_class`. */
11488
- DeviceRole["NumericSensor"] = "numeric-sensor";
11489
- /** String / enum state (HA `sensor` with `state_class: enum` or
11490
- * `attributes.options`). */
11491
- DeviceRole["EnumSensor"] = "enum-sensor";
11492
- /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
11493
- * or `date`). The slice carries the raw ISO string verbatim (hosted on
11494
- * the `enum-sensor` cap); the UI renders it locale-formatted. */
11495
- DeviceRole["DateTimeSensor"] = "datetime-sensor";
11496
- /** Last-resort fallback when nothing else matches. */
11497
- DeviceRole["GenericSensor"] = "generic-sensor";
11498
- DeviceRole["NumericControl"] = "numeric-control";
11499
- DeviceRole["SelectControl"] = "select-control";
11500
- DeviceRole["TextControl"] = "text-control";
11501
- DeviceRole["DateTimeControl"] = "datetime-control";
11502
- /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
11503
- * rich features (image, priority, channel routing). */
11504
- DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
11505
- /** Chat / messaging service (HA `notify.telegram_*`,
11506
- * `notify.discord_*`, etc.). */
11507
- DeviceRole["MessagingNotifier"] = "messaging-notifier";
11508
- /** Email-based delivery (HA `notify.smtp`, etc.). */
11509
- DeviceRole["EmailNotifier"] = "email-notifier";
11510
- /** Fallback when the notifier service name doesn't match a known
11511
- * pattern. */
11512
- DeviceRole["GenericNotifier"] = "generic-notifier";
11513
- /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
11514
- * cap. A `DeviceType.Generic` device. */
11515
- DeviceRole["IrTransmitter"] = "ir-transmitter";
11516
- return DeviceRole;
11517
- }({});
11518
- /**
11519
- import { errMsg } from '@camstack/types'
11520
- * Extract a human-readable message from an unknown error value.
11521
- * Replaces the ubiquitous `errMsg(err)` pattern.
11522
- */
11523
- function errMsg(err) {
11524
- if (err instanceof Error) return err.message;
11525
- if (typeof err === "string") return err;
11526
- return String(err);
11527
- }
11528
- //#endregion
11529
- //#region ../types/dist/composition-C7WdThmP.mjs
11652
+ //#region ../types/dist/composition-BBzVf2xd.mjs
11530
11653
  /**
11531
11654
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11532
11655
  *
@@ -12745,6 +12868,71 @@ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
12745
12868
  var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
12746
12869
  new Set(EVENT_OWNER_TYPES);
12747
12870
  /**
12871
+ * What a published package needs from the rest of the install (D989).
12872
+ *
12873
+ * One published framework package (`@camstack/server`) carries the kernel, the
12874
+ * builtins and the capability contracts, so "which addon runs on which
12875
+ * framework" is a question of version, and the answer is DECLARED by the
12876
+ * package that knows it:
12877
+ *
12878
+ * - an addon says the oldest server it runs on (`requires.server: ">=X"`);
12879
+ * - the server says the oldest addon it works with
12880
+ * (`requires.addons: { "@camstack/addon-x": ">=Y" }`) and the oldest shell
12881
+ * around it (`requires.wrapper: { desktop, image }`);
12882
+ * - a wrapper (the desktop app, the container starter) says which server
12883
+ * range it can host, and reports its own kind and version.
12884
+ *
12885
+ * The publish workflow STAMPS these after the version bump
12886
+ * (`scripts/ci/stamp-requires.mjs`); nobody types a number by hand.
12887
+ *
12888
+ * A requirement that cannot be checked is NOT met: a shell that reports no
12889
+ * version counts as below the minimum, and an unreadable running version is
12890
+ * named in the result (a failed read is never a pass).
12891
+ */
12892
+ /** `>=MAJOR.MINOR.PATCH[-prerelease]` — the only range shape a requirement uses. */
12893
+ var MinVersionSchema = string().regex(/^>=\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, "a minimum is written \">=X.Y.Z\"");
12894
+ object({
12895
+ /** The oldest `@camstack/server` this package runs on. */
12896
+ server: MinVersionSchema.optional(),
12897
+ /** The oldest installed version of each named addon package. */
12898
+ addons: record(string(), MinVersionSchema).optional(),
12899
+ /** The oldest shell around the server, per shell kind. */
12900
+ wrapper: object({
12901
+ desktop: MinVersionSchema.optional(),
12902
+ image: MinVersionSchema.optional()
12903
+ }).optional()
12904
+ });
12905
+ /** One unmet requirement; each names the versions on both sides. */
12906
+ var UnmetRequirementSchema = discriminatedUnion("kind", [
12907
+ object({
12908
+ kind: literal("server-too-old"),
12909
+ required: string(),
12910
+ /** `null` — the running server version could not be read. */
12911
+ running: string().nullable()
12912
+ }),
12913
+ object({
12914
+ kind: literal("addon-too-old"),
12915
+ packageName: string(),
12916
+ required: string(),
12917
+ /** `null` — the addon is not installed (or its version is unreadable). */
12918
+ running: string().nullable()
12919
+ }),
12920
+ object({
12921
+ kind: literal("wrapper-too-old"),
12922
+ wrapper: _enum(["desktop", "image"]),
12923
+ required: string(),
12924
+ /** `null` — the wrapper reported no version. */
12925
+ running: string().nullable()
12926
+ })
12927
+ ]);
12928
+ var RequiredAddonUpdateSchema = object({
12929
+ packageName: string(),
12930
+ /** The minimum the target server declares. */
12931
+ required: string(),
12932
+ /** What this node has; `null` when the addon is not installed. */
12933
+ installed: string().nullable()
12934
+ });
12935
+ /**
12748
12936
  * Which integration instance a settings form is for. An addon that serves several instances
12749
12937
  * (`instanceMode: multiple`) can show a different form per instance — an NVR integration
12750
12938
  * needs its connection, the default integration needs the addon's own sections.
@@ -13363,6 +13551,613 @@ var CameraSwitchGroupSchema = object({
13363
13551
  fetchedAt: number()
13364
13552
  });
13365
13553
  /**
13554
+ * Query filter for settings-store collections.
13555
+ */
13556
+ var QueryFilterSchema = object({
13557
+ where: record(string(), unknown()).optional(),
13558
+ whereIn: record(string(), array(unknown())).optional(),
13559
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
13560
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
13561
+ whereNot: record(string(), unknown()).optional(),
13562
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
13563
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
13564
+ whereNotIn: record(string(), array(unknown())).optional(),
13565
+ orderBy: object({
13566
+ field: string(),
13567
+ direction: _enum(["asc", "desc"])
13568
+ }).optional(),
13569
+ limit: number().optional(),
13570
+ offset: number().optional()
13571
+ });
13572
+ /**
13573
+ * The predicate half of a filter, for BULK MUTATIONS.
13574
+ *
13575
+ * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
13576
+ * meaning for a statement that rewrites a set, and accepting them would invite
13577
+ * a caller to believe `limit` bounds the damage. Every field is optional here
13578
+ * only so the shape stays composable — the implementation REJECTS a filter
13579
+ * that compiles to no predicate, because that is the whole collection.
13580
+ */
13581
+ var MutationFilterSchema = object({
13582
+ where: record(string(), unknown()).optional(),
13583
+ whereIn: record(string(), array(unknown())).optional(),
13584
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
13585
+ whereNot: record(string(), unknown()).optional(),
13586
+ whereNotIn: record(string(), array(unknown())).optional()
13587
+ });
13588
+ /**
13589
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
13590
+ *
13591
+ * `as` names the slot in the result, so the SAME column may be asked twice with
13592
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
13593
+ * a `Record<column, op>` shape could not express.
13594
+ */
13595
+ var AggregateFieldSchema = object({
13596
+ /** Result key. */
13597
+ as: string().min(1),
13598
+ /** Column to aggregate. Must be a real column of a declared collection. */
13599
+ field: string().min(1),
13600
+ op: _enum([
13601
+ "sum",
13602
+ "min",
13603
+ "max"
13604
+ ])
13605
+ });
13606
+ /**
13607
+ * `COUNT(*)` plus one number per requested field.
13608
+ *
13609
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
13610
+ * that really is 0 are different facts, and an accounting caller that renders
13611
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
13612
+ */
13613
+ var AggregateResultSchema = object({
13614
+ count: number().int(),
13615
+ values: record(string(), number().nullable())
13616
+ });
13617
+ /** A single stored record: `{ id, data }`. */
13618
+ var SettingsRecordSchema = object({
13619
+ id: string(),
13620
+ data: record(string(), unknown())
13621
+ });
13622
+ /**
13623
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
13624
+ * optional.
13625
+ *
13626
+ * A separate schema rather than loosening the shared one: every other method
13627
+ * on this cap addresses a row BY its id, and making that field optional
13628
+ * everywhere would turn a forgotten key into a silently generated one on
13629
+ * `update` and `delete` as well.
13630
+ */
13631
+ var BulkRecordSchema = object({
13632
+ id: string().optional(),
13633
+ data: record(string(), unknown())
13634
+ });
13635
+ /**
13636
+ * The database FILES one `sqlite-settings` engine process owns (D851).
13637
+ * Closed on purpose: a file is a thing the engine opens, backs up and gives a
13638
+ * serving thread, so a new one is a code change, never a string a caller
13639
+ * invents. `settings` is `camstack.db` and the default for every collection
13640
+ * that names no file.
13641
+ */
13642
+ var DataStoreDatabaseSchema = _enum([
13643
+ "settings",
13644
+ "events",
13645
+ "history"
13646
+ ]);
13647
+ DataStoreDatabaseSchema.options;
13648
+ /**
13649
+ * One write inside a {@link settingsStoreCapability.methods.transact} call.
13650
+ * The three that a "replace this set, bump that revision" write needs, plus
13651
+ * the batched upsert a rollup writer needs.
13652
+ */
13653
+ var TransactOpSchema = discriminatedUnion("op", [
13654
+ object({
13655
+ op: literal("deleteWhere"),
13656
+ collection: string(),
13657
+ filter: MutationFilterSchema
13658
+ }),
13659
+ object({
13660
+ op: literal("insertMany"),
13661
+ collection: string(),
13662
+ records: array(BulkRecordSchema).readonly()
13663
+ }),
13664
+ object({
13665
+ op: literal("update"),
13666
+ collection: string(),
13667
+ id: string(),
13668
+ data: record(string(), unknown())
13669
+ }),
13670
+ object({
13671
+ op: literal("upsertMany"),
13672
+ collection: string(),
13673
+ conflict: array(string()).readonly(),
13674
+ records: array(BulkRecordSchema).readonly()
13675
+ })
13676
+ ]);
13677
+ /**
13678
+ * The precondition of a transaction: the row `key` of `collection` must hold
13679
+ * `equals` in `column`, read INSIDE the transaction.
13680
+ */
13681
+ var TransactGuardSchema = object({
13682
+ collection: string(),
13683
+ key: string(),
13684
+ column: string(),
13685
+ equals: unknown()
13686
+ });
13687
+ /**
13688
+ * Column declaration for a structured (SQL-backed) collection.
13689
+ *
13690
+ * Logical types — the backend translates each to the matching SQLite
13691
+ * storage class and handles per-type marshaling:
13692
+ * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
13693
+ * - `JSON` — TEXT under the hood; serialised on write, parsed on read
13694
+ * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
13695
+ */
13696
+ var CollectionColumnSchema = object({
13697
+ name: string(),
13698
+ type: _enum([
13699
+ "TEXT",
13700
+ "INTEGER",
13701
+ "REAL",
13702
+ "JSON",
13703
+ "BOOLEAN"
13704
+ ]),
13705
+ primaryKey: boolean().optional(),
13706
+ notNull: boolean().optional(),
13707
+ unique: boolean().optional(),
13708
+ /**
13709
+ * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
13710
+ * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
13711
+ * collection surface simply cannot express their existing tables — which is
13712
+ * why they were still on the uncapped `table*` API.
13713
+ */
13714
+ defaultValue: union([
13715
+ string(),
13716
+ number(),
13717
+ boolean()
13718
+ ]).optional()
13719
+ });
13720
+ var CollectionIndexSchema = object({
13721
+ name: string(),
13722
+ columns: array(string()).readonly(),
13723
+ unique: boolean().optional()
13724
+ });
13725
+ /**
13726
+ * The input of `declareCollection` — ONE schema for the door and for the
13727
+ * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
13728
+ * the payload untouched).
13729
+ *
13730
+ * - `database` — the file the collection lives in (D851). Absent means
13731
+ * `settings`. A declaration naming a file other than the one the table is
13732
+ * already in is REFUSED by the engine, never moved.
13733
+ * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
13734
+ * as `primaryKey: true` on that column. A composite-keyed collection has no
13735
+ * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
13736
+ * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
13737
+ * address one row by id are refused.
13738
+ * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
13739
+ */
13740
+ var DeclareCollectionInputSchema = object({
13741
+ namespace: string().optional(),
13742
+ collection: string(),
13743
+ columns: array(CollectionColumnSchema).readonly(),
13744
+ indexes: array(CollectionIndexSchema).readonly().optional(),
13745
+ database: DataStoreDatabaseSchema.optional(),
13746
+ primaryKey: array(string()).readonly().optional(),
13747
+ withoutRowid: boolean().optional()
13748
+ });
13749
+ method(object({
13750
+ namespace: string().optional(),
13751
+ collection: string(),
13752
+ key: string()
13753
+ }), unknown(), { auth: "admin" }), method(object({
13754
+ namespace: string().optional(),
13755
+ collection: string(),
13756
+ key: string(),
13757
+ value: unknown()
13758
+ }), _void(), {
13759
+ kind: "mutation",
13760
+ auth: "admin"
13761
+ }), method(object({
13762
+ namespace: string().optional(),
13763
+ collection: string(),
13764
+ filter: QueryFilterSchema.optional(),
13765
+ columns: array(string()).readonly().optional()
13766
+ }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
13767
+ namespace: string().optional(),
13768
+ collection: string(),
13769
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
13770
+ * index on the collection. Never empty. */
13771
+ conflict: array(string()).readonly(),
13772
+ record: BulkRecordSchema
13773
+ }), object({
13774
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
13775
+ * row's own id on an update. */
13776
+ id: union([string(), number()]) }), {
13777
+ kind: "mutation",
13778
+ auth: "admin"
13779
+ }), method(object({
13780
+ namespace: string().optional(),
13781
+ collection: string(),
13782
+ conflict: array(string()).readonly(),
13783
+ records: array(BulkRecordSchema).readonly()
13784
+ }), object({ upserted: number().int() }), {
13785
+ kind: "mutation",
13786
+ auth: "admin"
13787
+ }), method(object({
13788
+ namespace: string().optional(),
13789
+ collection: string(),
13790
+ record: object({
13791
+ id: string().optional(),
13792
+ data: record(string(), unknown())
13793
+ })
13794
+ }), object({
13795
+ /**
13796
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
13797
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
13798
+ * primary key — which is the only place an auto key is knowable.
13799
+ */
13800
+ id: union([string(), number()]) }), {
13801
+ kind: "mutation",
13802
+ auth: "admin"
13803
+ }), method(object({
13804
+ namespace: string().optional(),
13805
+ collection: string(),
13806
+ records: array(BulkRecordSchema).readonly()
13807
+ }), object({ inserted: number().int() }), {
13808
+ kind: "mutation",
13809
+ auth: "admin"
13810
+ }), method(object({
13811
+ namespace: string().optional(),
13812
+ guard: TransactGuardSchema.optional(),
13813
+ ops: array(TransactOpSchema).readonly()
13814
+ }), object({
13815
+ applied: boolean(),
13816
+ current: unknown().optional()
13817
+ }), {
13818
+ kind: "mutation",
13819
+ auth: "admin"
13820
+ }), method(object({
13821
+ namespace: string().optional(),
13822
+ collection: string(),
13823
+ id: string(),
13824
+ data: record(string(), unknown())
13825
+ }), _void(), {
13826
+ kind: "mutation",
13827
+ auth: "admin"
13828
+ }), method(object({
13829
+ namespace: string().optional(),
13830
+ collection: string(),
13831
+ key: string()
13832
+ }), _void(), {
13833
+ kind: "mutation",
13834
+ auth: "admin"
13835
+ }), method(object({
13836
+ namespace: string().optional(),
13837
+ collection: string(),
13838
+ filter: MutationFilterSchema
13839
+ }), object({ deleted: number().int() }), {
13840
+ kind: "mutation",
13841
+ auth: "admin"
13842
+ }), method(object({
13843
+ namespace: string().optional(),
13844
+ collection: string(),
13845
+ filter: MutationFilterSchema,
13846
+ data: record(string(), unknown())
13847
+ }), object({ updated: number().int() }), {
13848
+ kind: "mutation",
13849
+ auth: "admin"
13850
+ }), method(object({
13851
+ namespace: string().optional(),
13852
+ collection: string(),
13853
+ filter: QueryFilterSchema.optional()
13854
+ }), number(), { auth: "admin" }), method(object({
13855
+ namespace: string().optional(),
13856
+ collection: string(),
13857
+ fields: array(AggregateFieldSchema).readonly(),
13858
+ filter: QueryFilterSchema.optional()
13859
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
13860
+ namespace: string().optional(),
13861
+ collection: string(),
13862
+ field: string(),
13863
+ bucketSize: number().int().positive(),
13864
+ origin: number().int(),
13865
+ filter: QueryFilterSchema.optional()
13866
+ }), array(object({
13867
+ bucket: number().int(),
13868
+ count: number().int()
13869
+ })).readonly(), { auth: "admin" }), method(object({
13870
+ namespace: string().optional(),
13871
+ collection: string()
13872
+ }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
13873
+ kind: "mutation",
13874
+ auth: "admin"
13875
+ });
13876
+ 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>\"");
13877
+ var MigrationLevelSchema = _enum([
13878
+ "hub-store",
13879
+ "addon-state",
13880
+ "node-config",
13881
+ "viewer",
13882
+ "addon-internal"
13883
+ ]);
13884
+ var MigrationStoreSchema = _enum([
13885
+ "settings",
13886
+ "events",
13887
+ "history",
13888
+ "node-local",
13889
+ "addon-files"
13890
+ ]);
13891
+ var MigrationScopeSchema = _enum(["cluster", "per-node"]);
13892
+ /** What depends on a migration while it is pending: `block-owner` is the default. */
13893
+ var MigrationOnFailureSchema = _enum(["block-owner", "tolerate"]);
13894
+ /**
13895
+ * `sql` is a hub-only body over the owner's connection. `plan` returns edits the
13896
+ * engine applies inside the migration's own transaction (hub-owned: computed in
13897
+ * that transaction; addon-owned: computed by the owner and shipped to the engine).
13898
+ * `procedure` is addon-owned work that is not SQL (keychain, integration registry,
13899
+ * device tombstones); the ledger records its outcome and it must be idempotent.
13900
+ */
13901
+ var MigrationBodyKindSchema = _enum([
13902
+ "sql",
13903
+ "plan",
13904
+ "procedure"
13905
+ ]);
13906
+ var MigrationMetaSchema = object({
13907
+ id: MigrationIdSchema,
13908
+ /** `hub`, an addon id, `node` or `viewer`. */
13909
+ owner: string().regex(/^[a-z0-9-]+$/),
13910
+ level: MigrationLevelSchema,
13911
+ /** Strictly increasing per owner: the order within an owner. */
13912
+ seq: number().int().positive(),
13913
+ /** Ids of any owner: the order across owners. */
13914
+ dependsOn: array(MigrationIdSchema),
13915
+ store: MigrationStoreSchema,
13916
+ scope: MigrationScopeSchema,
13917
+ onFailure: MigrationOnFailureSchema,
13918
+ bodyKind: MigrationBodyKindSchema,
13919
+ description: string().min(1),
13920
+ /** The marked field the migration's spec must assert survived. */
13921
+ fixtureMark: string().min(1)
13922
+ }).refine((m) => m.id.startsWith(`${m.owner}/`), {
13923
+ message: "the id must start with \"<owner>/\"",
13924
+ path: ["id"]
13925
+ });
13926
+ /** A cycle in the dependency graph, named by the ids on it. */
13927
+ var MigrationCycleError = class extends Error {
13928
+ ids;
13929
+ constructor(ids) {
13930
+ super(`migration dependsOn cycle: ${ids.join(" -> ")}`);
13931
+ this.ids = ids;
13932
+ this.name = "MigrationCycleError";
13933
+ }
13934
+ };
13935
+ /**
13936
+ * Topological order over `dependsOn` plus the implicit edge "a lower `seq` of an
13937
+ * owner runs first". Ties are broken by `(owner, seq)`. Throws
13938
+ * {@link MigrationCycleError} on a cycle. A `dependsOn` id that is not in the list
13939
+ * is ignored here — the catalogue schema is what refuses it.
13940
+ */
13941
+ function orderMigrations(items) {
13942
+ const byId = new Map(items.map((item) => [item.id, item]));
13943
+ const deps = /* @__PURE__ */ new Map();
13944
+ const ownerSorted = /* @__PURE__ */ new Map();
13945
+ for (const item of items) {
13946
+ const list = ownerSorted.get(item.owner) ?? [];
13947
+ list.push(item);
13948
+ ownerSorted.set(item.owner, list);
13949
+ }
13950
+ for (const item of items) deps.set(item.id, new Set(item.dependsOn.filter((id) => byId.has(id))));
13951
+ for (const list of ownerSorted.values()) {
13952
+ list.sort((a, b) => a.seq - b.seq);
13953
+ for (let i = 1; i < list.length; i++) {
13954
+ const previous = list[i - 1];
13955
+ const current = list[i];
13956
+ if (previous !== void 0 && current !== void 0) deps.get(current.id)?.add(previous.id);
13957
+ }
13958
+ }
13959
+ const tie = (a, b) => a.owner < b.owner ? -1 : a.owner > b.owner ? 1 : a.seq - b.seq;
13960
+ const done = /* @__PURE__ */ new Set();
13961
+ const ordered = [];
13962
+ let remaining = [...items];
13963
+ while (remaining.length > 0) {
13964
+ const ready = remaining.filter((item) => [...deps.get(item.id) ?? []].every((dep) => done.has(dep)));
13965
+ if (ready.length === 0) throw new MigrationCycleError(remaining.map((item) => item.id).sort());
13966
+ ready.sort(tie);
13967
+ const next = ready[0];
13968
+ if (next === void 0) break;
13969
+ ordered.push(next);
13970
+ done.add(next.id);
13971
+ remaining = remaining.filter((item) => item.id !== next.id);
13972
+ }
13973
+ return ordered;
13974
+ }
13975
+ /** Every way a list of migrations (in declaration order) is not a catalogue. */
13976
+ function catalogueIssues(items) {
13977
+ const issues = [];
13978
+ const seen = /* @__PURE__ */ new Set();
13979
+ const known = new Set(items.map((item) => item.id));
13980
+ const lastSeq = /* @__PURE__ */ new Map();
13981
+ const seqUsed = /* @__PURE__ */ new Map();
13982
+ for (const item of items) {
13983
+ if (seen.has(item.id)) issues.push({
13984
+ code: "duplicate-id",
13985
+ ids: [item.id],
13986
+ message: `duplicate id ${item.id}`
13987
+ });
13988
+ seen.add(item.id);
13989
+ const used = seqUsed.get(item.owner) ?? /* @__PURE__ */ new Set();
13990
+ if (used.has(item.seq)) issues.push({
13991
+ code: "duplicate-seq",
13992
+ ids: [item.id],
13993
+ message: `owner ${item.owner} has two migrations with seq ${item.seq}`
13994
+ });
13995
+ else if (item.seq < (lastSeq.get(item.owner) ?? 0)) issues.push({
13996
+ code: "seq-order",
13997
+ ids: [item.id],
13998
+ message: `seq ${item.seq} of ${item.id} does not increase in declaration order (owner ${item.owner})`
13999
+ });
14000
+ used.add(item.seq);
14001
+ seqUsed.set(item.owner, used);
14002
+ lastSeq.set(item.owner, Math.max(item.seq, lastSeq.get(item.owner) ?? 0));
14003
+ for (const dep of item.dependsOn) if (!known.has(dep)) issues.push({
14004
+ code: "unknown-dependency",
14005
+ ids: [item.id, dep],
14006
+ message: `${item.id} dependsOn unknown id ${dep}`
14007
+ });
14008
+ }
14009
+ try {
14010
+ orderMigrations(items);
14011
+ } catch (err) {
14012
+ if (err instanceof MigrationCycleError) issues.push({
14013
+ code: "cycle",
14014
+ ids: err.ids,
14015
+ message: err.message
14016
+ });
14017
+ else throw err;
14018
+ }
14019
+ return issues;
14020
+ }
14021
+ array(MigrationMetaSchema).superRefine((items, ctx) => {
14022
+ for (const issue of catalogueIssues(items)) ctx.addIssue({
14023
+ code: "custom",
14024
+ message: `${issue.code}: ${issue.message}`
14025
+ });
14026
+ });
14027
+ /**
14028
+ * The canonical JSON (`preconditionOf`) of the value a plan read; the apply step
14029
+ * re-reads, and a plan whose read no longer matches is refused (the migration is
14030
+ * `failed`, nothing it edited is kept).
14031
+ */
14032
+ var PreconditionSchema = string().min(1).optional();
14033
+ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
14034
+ object({
14035
+ op: literal("setKeys"),
14036
+ scope: string().min(1),
14037
+ values: record(string(), unknown()),
14038
+ precondition: PreconditionSchema
14039
+ }),
14040
+ object({
14041
+ op: literal("deleteKeys"),
14042
+ scope: string().min(1),
14043
+ keys: array(string().min(1)).min(1),
14044
+ precondition: PreconditionSchema
14045
+ }),
14046
+ object({
14047
+ op: literal("putRow"),
14048
+ table: string().min(1),
14049
+ key: record(string(), unknown()),
14050
+ row: record(string(), unknown()),
14051
+ precondition: PreconditionSchema
14052
+ }),
14053
+ object({
14054
+ op: literal("deleteRow"),
14055
+ table: string().min(1),
14056
+ key: record(string(), unknown()),
14057
+ precondition: PreconditionSchema
14058
+ }),
14059
+ object({
14060
+ op: literal("upsertRecord"),
14061
+ kind: string().min(1),
14062
+ recordKey: string().min(1),
14063
+ value: unknown(),
14064
+ precondition: PreconditionSchema
14065
+ }),
14066
+ object({
14067
+ op: literal("deleteRecord"),
14068
+ kind: string().min(1),
14069
+ recordKey: string().min(1),
14070
+ precondition: PreconditionSchema
14071
+ })
14072
+ ])) });
14073
+ /**
14074
+ * What a `procedure` body reports. `already-done` means the rerun observed the
14075
+ * work finished (a crash between the work and the ledger row, or a manual fix)
14076
+ * and did none of it again; the ledger records `applied` either way.
14077
+ */
14078
+ var MigrationProcedureResultSchema = object({
14079
+ outcome: _enum(["done", "already-done"]),
14080
+ summary: record(string(), unknown())
14081
+ });
14082
+ /** What an owner hands the engine when one of its migrations ran. */
14083
+ var MigrationOwnerResultSchema = discriminatedUnion("kind", [
14084
+ object({
14085
+ kind: literal("plan"),
14086
+ plan: MigrationPlanSchema
14087
+ }),
14088
+ object({
14089
+ kind: literal("procedure"),
14090
+ result: MigrationProcedureResultSchema
14091
+ }),
14092
+ object({
14093
+ kind: literal("failed"),
14094
+ error: string().min(1)
14095
+ })
14096
+ ]);
14097
+ /**
14098
+ * `held`: a restore of a pre-migration snapshot undid this id; it does not rerun
14099
+ * until the operator retries or a new code version ships.
14100
+ * `skipped-newer`: the database knows an id this code does not.
14101
+ */
14102
+ var MigrationStatusSchema = _enum([
14103
+ "applied",
14104
+ "failed",
14105
+ "blocked",
14106
+ "running",
14107
+ "held",
14108
+ "skipped-newer"
14109
+ ]);
14110
+ var MigrationLedgerEntrySchema = object({
14111
+ id: string(),
14112
+ database: DataStoreDatabaseSchema,
14113
+ owner: string(),
14114
+ level: MigrationLevelSchema,
14115
+ seq: number().int().nonnegative(),
14116
+ status: MigrationStatusSchema,
14117
+ attempts: number().int().nonnegative(),
14118
+ appliedAt: number().nullable(),
14119
+ codeVersion: string().nullable(),
14120
+ /** What the apply changed: rows per table, as the engine counted them. */
14121
+ planSummary: record(string(), unknown()).nullable(),
14122
+ progress: record(string(), unknown()).nullable(),
14123
+ error: string().nullable(),
14124
+ /** Why it is `blocked` or `held` (`dependency-failed`, `snapshot-refused`, …). */
14125
+ reason: string().nullable(),
14126
+ nodeId: string().nullable(),
14127
+ bootId: string().nullable()
14128
+ });
14129
+ /** A catalogued migration that is not applied, with where it stands. */
14130
+ var MigrationPendingEntrySchema = object({
14131
+ meta: MigrationMetaSchema,
14132
+ database: DataStoreDatabaseSchema,
14133
+ state: _enum([
14134
+ "pending",
14135
+ "failed",
14136
+ "blocked",
14137
+ "held"
14138
+ ]),
14139
+ reason: string().nullable()
14140
+ });
14141
+ /** Rows per table before and after a dry run, and what it would have changed. */
14142
+ var MigrationDryRunResultSchema = object({
14143
+ id: string(),
14144
+ ok: boolean(),
14145
+ error: string().nullable(),
14146
+ rowsChanged: number().int().nonnegative(),
14147
+ tables: array(object({
14148
+ table: string(),
14149
+ before: number().int().nonnegative(),
14150
+ after: number().int().nonnegative()
14151
+ }))
14152
+ });
14153
+ /** One of an owner's migrations, as the engine tells the owner to treat it this boot. */
14154
+ var MigrationOwnerStepSchema = object({
14155
+ id: MigrationIdSchema,
14156
+ action: _enum(["run", "skip"]),
14157
+ /** The ledger state the engine found: `pending` has no row yet. */
14158
+ state: union([literal("pending"), MigrationStatusSchema])
14159
+ });
14160
+ /**
13366
14161
  * The trainer contract — what the hub hands the cloud trainer and what it
13367
14162
  * gets back (cloud training, D805+).
13368
14163
  *
@@ -20353,6 +21148,7 @@ var SYSTEM_CAP_VOCABULARY = {
20353
21148
  "login-method": system(IDENTITY),
20354
21149
  "mesh-network": system(NETWORK),
20355
21150
  "metrics-provider": system(CLUSTER),
21151
+ migrations: system(INTERNAL),
20356
21152
  "model-convert": system(ML),
20357
21153
  "model-distributor": system(ML),
20358
21154
  "mqtt-broker": system(NETWORK),
@@ -40555,7 +41351,8 @@ function normalizeUnit(raw) {
40555
41351
  if (lower === "dbm") return "dBm";
40556
41352
  if (lower === "dbfs") return "dBFS";
40557
41353
  if (lower === "inhg") return "inHg";
40558
- if (lower === "mm/h") return "mm/h";
41354
+ if (lower === "mm/h" || lower === "mm/hr") return "mm/h";
41355
+ if (lower === "in/h" || lower === "in/hr") return "in/h";
40559
41356
  if (lower === "w/m²" || lower === "w/m2") return "W/m²";
40560
41357
  if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
40561
41358
  if (lower === "kbps") return "kbps";
@@ -42949,7 +43746,7 @@ function pruneDeletedEntities(selection, live, log, exporter) {
42949
43746
  * rebuilds without manual reload.
42950
43747
  *
42951
43748
  * The hub-local builtin `addon-pages-aggregator` (see
42952
- * `@camstack/system/builtins/addon-pages-aggregator`) registers the
43749
+ * `@camstack/server/system/builtins/addon-pages-aggregator`) registers the
42953
43750
  * provider. Splitting the public aggregator from the raw collection
42954
43751
  * keeps both ends in codegen — there's no hand-written
42955
43752
  * `addon-pages.router.ts` wrapper anymore.
@@ -43039,7 +43836,7 @@ var addonRoutesCapability = {
43039
43836
  */
43040
43837
  invoke: method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" })
43041
43838
  },
43042
- /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
43839
+ /** BIG PLAN 2: declarative mount hint — read by `@camstack/server/system` `buildCapRouters`. */
43043
43840
  mount: { kind: "skip" }
43044
43841
  };
43045
43842
  var ConfigTabDeclarationSchema = object({
@@ -43268,7 +44065,7 @@ method(_void(), array(WidgetMetadataSchema).readonly(), { auth: "admin" });
43268
44065
  * manual reload — same scheme used by `addon-pages`.
43269
44066
  *
43270
44067
  * The hub-local builtin `addon-widgets-aggregator` (see
43271
- * `@camstack/system/builtins/addon-widgets-aggregator`) registers the
44068
+ * `@camstack/server/system/builtins/addon-widgets-aggregator`) registers the
43272
44069
  * provider. Splitting the public aggregator from the raw collection
43273
44070
  * keeps both ends in codegen — there's no hand-written wrapper.
43274
44071
  */
@@ -43629,329 +44426,6 @@ method(object({
43629
44426
  password: string()
43630
44427
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
43631
44428
  /**
43632
- * Query filter for settings-store collections.
43633
- */
43634
- var QueryFilterSchema = object({
43635
- where: record(string(), unknown()).optional(),
43636
- whereIn: record(string(), array(unknown())).optional(),
43637
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
43638
- /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
43639
- whereNot: record(string(), unknown()).optional(),
43640
- /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
43641
- * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
43642
- whereNotIn: record(string(), array(unknown())).optional(),
43643
- orderBy: object({
43644
- field: string(),
43645
- direction: _enum(["asc", "desc"])
43646
- }).optional(),
43647
- limit: number().optional(),
43648
- offset: number().optional()
43649
- });
43650
- /**
43651
- * The predicate half of a filter, for BULK MUTATIONS.
43652
- *
43653
- * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
43654
- * meaning for a statement that rewrites a set, and accepting them would invite
43655
- * a caller to believe `limit` bounds the damage. Every field is optional here
43656
- * only so the shape stays composable — the implementation REJECTS a filter
43657
- * that compiles to no predicate, because that is the whole collection.
43658
- */
43659
- var MutationFilterSchema = object({
43660
- where: record(string(), unknown()).optional(),
43661
- whereIn: record(string(), array(unknown())).optional(),
43662
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
43663
- whereNot: record(string(), unknown()).optional(),
43664
- whereNotIn: record(string(), array(unknown())).optional()
43665
- });
43666
- /**
43667
- * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
43668
- *
43669
- * `as` names the slot in the result, so the SAME column may be asked twice with
43670
- * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
43671
- * a `Record<column, op>` shape could not express.
43672
- */
43673
- var AggregateFieldSchema = object({
43674
- /** Result key. */
43675
- as: string().min(1),
43676
- /** Column to aggregate. Must be a real column of a declared collection. */
43677
- field: string().min(1),
43678
- op: _enum([
43679
- "sum",
43680
- "min",
43681
- "max"
43682
- ])
43683
- });
43684
- /**
43685
- * `COUNT(*)` plus one number per requested field.
43686
- *
43687
- * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
43688
- * that really is 0 are different facts, and an accounting caller that renders
43689
- * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
43690
- */
43691
- var AggregateResultSchema = object({
43692
- count: number().int(),
43693
- values: record(string(), number().nullable())
43694
- });
43695
- /** A single stored record: `{ id, data }`. */
43696
- var SettingsRecordSchema = object({
43697
- id: string(),
43698
- data: record(string(), unknown())
43699
- });
43700
- /**
43701
- * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
43702
- * optional.
43703
- *
43704
- * A separate schema rather than loosening the shared one: every other method
43705
- * on this cap addresses a row BY its id, and making that field optional
43706
- * everywhere would turn a forgotten key into a silently generated one on
43707
- * `update` and `delete` as well.
43708
- */
43709
- var BulkRecordSchema = object({
43710
- id: string().optional(),
43711
- data: record(string(), unknown())
43712
- });
43713
- /**
43714
- * The database FILES one `sqlite-settings` engine process owns (D851).
43715
- * Closed on purpose: a file is a thing the engine opens, backs up and gives a
43716
- * serving thread, so a new one is a code change, never a string a caller
43717
- * invents. `settings` is `camstack.db` and the default for every collection
43718
- * that names no file.
43719
- */
43720
- var DataStoreDatabaseSchema = _enum([
43721
- "settings",
43722
- "events",
43723
- "history"
43724
- ]);
43725
- DataStoreDatabaseSchema.options;
43726
- /**
43727
- * One write inside a {@link settingsStoreCapability.methods.transact} call.
43728
- * The three that a "replace this set, bump that revision" write needs, plus
43729
- * the batched upsert a rollup writer needs.
43730
- */
43731
- var TransactOpSchema = discriminatedUnion("op", [
43732
- object({
43733
- op: literal("deleteWhere"),
43734
- collection: string(),
43735
- filter: MutationFilterSchema
43736
- }),
43737
- object({
43738
- op: literal("insertMany"),
43739
- collection: string(),
43740
- records: array(BulkRecordSchema).readonly()
43741
- }),
43742
- object({
43743
- op: literal("update"),
43744
- collection: string(),
43745
- id: string(),
43746
- data: record(string(), unknown())
43747
- }),
43748
- object({
43749
- op: literal("upsertMany"),
43750
- collection: string(),
43751
- conflict: array(string()).readonly(),
43752
- records: array(BulkRecordSchema).readonly()
43753
- })
43754
- ]);
43755
- /**
43756
- * The precondition of a transaction: the row `key` of `collection` must hold
43757
- * `equals` in `column`, read INSIDE the transaction.
43758
- */
43759
- var TransactGuardSchema = object({
43760
- collection: string(),
43761
- key: string(),
43762
- column: string(),
43763
- equals: unknown()
43764
- });
43765
- /**
43766
- * Column declaration for a structured (SQL-backed) collection.
43767
- *
43768
- * Logical types — the backend translates each to the matching SQLite
43769
- * storage class and handles per-type marshaling:
43770
- * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
43771
- * - `JSON` — TEXT under the hood; serialised on write, parsed on read
43772
- * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
43773
- */
43774
- var CollectionColumnSchema = object({
43775
- name: string(),
43776
- type: _enum([
43777
- "TEXT",
43778
- "INTEGER",
43779
- "REAL",
43780
- "JSON",
43781
- "BOOLEAN"
43782
- ]),
43783
- primaryKey: boolean().optional(),
43784
- notNull: boolean().optional(),
43785
- unique: boolean().optional(),
43786
- /**
43787
- * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
43788
- * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
43789
- * collection surface simply cannot express their existing tables — which is
43790
- * why they were still on the uncapped `table*` API.
43791
- */
43792
- defaultValue: union([
43793
- string(),
43794
- number(),
43795
- boolean()
43796
- ]).optional()
43797
- });
43798
- var CollectionIndexSchema = object({
43799
- name: string(),
43800
- columns: array(string()).readonly(),
43801
- unique: boolean().optional()
43802
- });
43803
- /**
43804
- * The input of `declareCollection` — ONE schema for the door and for the
43805
- * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
43806
- * the payload untouched).
43807
- *
43808
- * - `database` — the file the collection lives in (D851). Absent means
43809
- * `settings`. A declaration naming a file other than the one the table is
43810
- * already in is REFUSED by the engine, never moved.
43811
- * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
43812
- * as `primaryKey: true` on that column. A composite-keyed collection has no
43813
- * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
43814
- * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
43815
- * address one row by id are refused.
43816
- * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
43817
- */
43818
- var DeclareCollectionInputSchema = object({
43819
- namespace: string().optional(),
43820
- collection: string(),
43821
- columns: array(CollectionColumnSchema).readonly(),
43822
- indexes: array(CollectionIndexSchema).readonly().optional(),
43823
- database: DataStoreDatabaseSchema.optional(),
43824
- primaryKey: array(string()).readonly().optional(),
43825
- withoutRowid: boolean().optional()
43826
- });
43827
- method(object({
43828
- namespace: string().optional(),
43829
- collection: string(),
43830
- key: string()
43831
- }), unknown(), { auth: "admin" }), method(object({
43832
- namespace: string().optional(),
43833
- collection: string(),
43834
- key: string(),
43835
- value: unknown()
43836
- }), _void(), {
43837
- kind: "mutation",
43838
- auth: "admin"
43839
- }), method(object({
43840
- namespace: string().optional(),
43841
- collection: string(),
43842
- filter: QueryFilterSchema.optional(),
43843
- columns: array(string()).readonly().optional()
43844
- }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
43845
- namespace: string().optional(),
43846
- collection: string(),
43847
- /** Declared columns identifying the slot; must be covered by a UNIQUE
43848
- * index on the collection. Never empty. */
43849
- conflict: array(string()).readonly(),
43850
- record: BulkRecordSchema
43851
- }), object({
43852
- /** The id the row HAS: assigned by SQLite on an insert, or the existing
43853
- * row's own id on an update. */
43854
- id: union([string(), number()]) }), {
43855
- kind: "mutation",
43856
- auth: "admin"
43857
- }), method(object({
43858
- namespace: string().optional(),
43859
- collection: string(),
43860
- conflict: array(string()).readonly(),
43861
- records: array(BulkRecordSchema).readonly()
43862
- }), object({ upserted: number().int() }), {
43863
- kind: "mutation",
43864
- auth: "admin"
43865
- }), method(object({
43866
- namespace: string().optional(),
43867
- collection: string(),
43868
- record: object({
43869
- id: string().optional(),
43870
- data: record(string(), unknown())
43871
- })
43872
- }), object({
43873
- /**
43874
- * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
43875
- * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
43876
- * primary key — which is the only place an auto key is knowable.
43877
- */
43878
- id: union([string(), number()]) }), {
43879
- kind: "mutation",
43880
- auth: "admin"
43881
- }), method(object({
43882
- namespace: string().optional(),
43883
- collection: string(),
43884
- records: array(BulkRecordSchema).readonly()
43885
- }), object({ inserted: number().int() }), {
43886
- kind: "mutation",
43887
- auth: "admin"
43888
- }), method(object({
43889
- namespace: string().optional(),
43890
- guard: TransactGuardSchema.optional(),
43891
- ops: array(TransactOpSchema).readonly()
43892
- }), object({
43893
- applied: boolean(),
43894
- current: unknown().optional()
43895
- }), {
43896
- kind: "mutation",
43897
- auth: "admin"
43898
- }), method(object({
43899
- namespace: string().optional(),
43900
- collection: string(),
43901
- id: string(),
43902
- data: record(string(), unknown())
43903
- }), _void(), {
43904
- kind: "mutation",
43905
- auth: "admin"
43906
- }), method(object({
43907
- namespace: string().optional(),
43908
- collection: string(),
43909
- key: string()
43910
- }), _void(), {
43911
- kind: "mutation",
43912
- auth: "admin"
43913
- }), method(object({
43914
- namespace: string().optional(),
43915
- collection: string(),
43916
- filter: MutationFilterSchema
43917
- }), object({ deleted: number().int() }), {
43918
- kind: "mutation",
43919
- auth: "admin"
43920
- }), method(object({
43921
- namespace: string().optional(),
43922
- collection: string(),
43923
- filter: MutationFilterSchema,
43924
- data: record(string(), unknown())
43925
- }), object({ updated: number().int() }), {
43926
- kind: "mutation",
43927
- auth: "admin"
43928
- }), method(object({
43929
- namespace: string().optional(),
43930
- collection: string(),
43931
- filter: QueryFilterSchema.optional()
43932
- }), number(), { auth: "admin" }), method(object({
43933
- namespace: string().optional(),
43934
- collection: string(),
43935
- fields: array(AggregateFieldSchema).readonly(),
43936
- filter: QueryFilterSchema.optional()
43937
- }), AggregateResultSchema, { auth: "admin" }), method(object({
43938
- namespace: string().optional(),
43939
- collection: string(),
43940
- field: string(),
43941
- bucketSize: number().int().positive(),
43942
- origin: number().int(),
43943
- filter: QueryFilterSchema.optional()
43944
- }), array(object({
43945
- bucket: number().int(),
43946
- count: number().int()
43947
- })).readonly(), { auth: "admin" }), method(object({
43948
- namespace: string().optional(),
43949
- collection: string()
43950
- }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
43951
- kind: "mutation",
43952
- auth: "admin"
43953
- });
43954
- /**
43955
44429
  * How long one snapshot may take end to end: the pinned copy of the whole
43956
44430
  * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
43957
44431
  * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
@@ -45914,7 +46388,7 @@ method(object({
45914
46388
  kind: "mutation",
45915
46389
  auth: "admin"
45916
46390
  }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
45917
- 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.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;
46391
+ 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;
45918
46392
  var NUMBER = {
45919
46393
  kind: "fixed",
45920
46394
  result: ["number"]
@@ -46925,6 +47399,57 @@ id: union([string(), number()]) }), {
46925
47399
  kind: "mutation",
46926
47400
  auth: "admin"
46927
47401
  });
47402
+ 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({
47403
+ id: string().min(1),
47404
+ error: string().min(1)
47405
+ }), MigrationLedgerEntrySchema, {
47406
+ kind: "mutation",
47407
+ auth: "admin"
47408
+ }), method(object({ id: string().min(1) }), object({ entries: array(MigrationLedgerEntrySchema) }), {
47409
+ kind: "mutation",
47410
+ auth: "admin"
47411
+ }), method(object({
47412
+ database: DataStoreDatabaseSchema,
47413
+ snapshotId: string().min(1)
47414
+ }), object({
47415
+ scheduled: boolean(),
47416
+ held: array(string()),
47417
+ restartRequired: boolean()
47418
+ }), {
47419
+ kind: "mutation",
47420
+ auth: "admin"
47421
+ }), method(object({
47422
+ owner: string().min(1),
47423
+ metas: array(MigrationMetaSchema)
47424
+ }), object({
47425
+ bootId: string(),
47426
+ snapshot: _enum([
47427
+ "none",
47428
+ "written",
47429
+ "refused"
47430
+ ]),
47431
+ steps: array(MigrationOwnerStepSchema)
47432
+ }), {
47433
+ kind: "mutation",
47434
+ auth: "admin"
47435
+ }), method(object({ id: string().min(1) }), object({
47436
+ proceed: boolean(),
47437
+ state: union([literal("pending"), MigrationStatusSchema]),
47438
+ reason: string().nullable()
47439
+ }), {
47440
+ kind: "mutation",
47441
+ auth: "admin"
47442
+ }), method(object({
47443
+ id: string().min(1),
47444
+ result: MigrationOwnerResultSchema
47445
+ }), object({
47446
+ status: MigrationStatusSchema,
47447
+ error: string().nullable(),
47448
+ rowsChanged: number().int().nonnegative()
47449
+ }), {
47450
+ kind: "mutation",
47451
+ auth: "admin"
47452
+ });
46928
47453
  /**
46929
47454
  * records - an addon's own facts and documents (D881): ledgers, audit rows,
46930
47455
  * config documents, mirrors. The third storage API next to `ctx.settings`
@@ -61868,7 +62393,18 @@ var ServerUpdateActionResultSchema = object({
61868
62393
  * store (D877), present when the pre-flight refused the update
61869
62394
  * (`reason: 'addon-would-refuse'`). Optional for version skew.
61870
62395
  */
61871
- blockers: array(UpdateBlockerSchema).optional()
62396
+ blockers: array(UpdateBlockerSchema).optional(),
62397
+ /**
62398
+ * The minimums the target server declares and this node does not meet
62399
+ * (D989), present when the update was refused with
62400
+ * `reason: 'requirements-unmet'`. Each names the versions on both sides.
62401
+ */
62402
+ unmet: array(UnmetRequirementSchema).optional(),
62403
+ /**
62404
+ * The addons this node has below the minimum the staged server declares —
62405
+ * the ones to update WITH the server. Present on an accepted update.
62406
+ */
62407
+ requiredAddonUpdates: array(RequiredAddonUpdateSchema).optional()
61872
62408
  });
61873
62409
  var LaunchOverridesSchema = object({
61874
62410
  /** The persisted heap ceiling in MB, or `null` when none is set. */
@@ -64484,7 +65020,9 @@ var PackageUpdateSchema = object({
64484
65020
  latestVersion: string(),
64485
65021
  category: _enum(["addon", "core"]),
64486
65022
  requiresRestart: boolean(),
64487
- isSystem: boolean()
65023
+ isSystem: boolean(),
65024
+ /** Held: what the newer version needs and this node does not meet (D989). */
65025
+ blockedBy: array(UnmetRequirementSchema).readonly().optional()
64488
65026
  });
64489
65027
  var PackageVersionInfoSchema = object({
64490
65028
  version: string(),
@@ -69818,7 +70356,7 @@ var lawnMowerControlCapability = {
69818
70356
  * clients — they reverse-connect to the hub. Exposing their interfaces
69819
70357
  * via the same surface would leak internal topology with no upside.
69820
70358
  *
69821
- * Implementation in `@camstack/system/builtins/local-network/`.
70359
+ * Implementation in `@camstack/server/system/builtins/local-network/`.
69822
70360
  */
69823
70361
  /** Coarse classification derived from the interface name + IP range. */
69824
70362
  var InterfaceKindEnum = _enum([
@@ -70354,7 +70892,15 @@ var POWER_SOURCE_DEVICE_TYPES = [
70354
70892
  DeviceType.Sensor,
70355
70893
  DeviceType.MediaPlayer,
70356
70894
  DeviceType.Vacuum,
70357
- DeviceType.Generic
70895
+ DeviceType.Generic,
70896
+ DeviceType.Washer,
70897
+ DeviceType.Dryer,
70898
+ DeviceType.Dishwasher,
70899
+ DeviceType.PetFountain,
70900
+ DeviceType.Fan,
70901
+ DeviceType.Humidifier,
70902
+ DeviceType.Climate,
70903
+ DeviceType.WaterHeater
70358
70904
  ];
70359
70905
  var powerSourceCustomize = {
70360
70906
  version: 1,
@@ -71698,7 +72244,7 @@ onStatusChanged: { data: object({
71698
72244
  * jitter, and observed/peak bandwidth per device + per client.
71699
72245
  *
71700
72246
  * Implementation lives in the server's `NetworkQualityService` (thin
71701
- * wrapper over the shared `NetworkQualityTracker` from `@camstack/system`).
72247
+ * wrapper over the shared `NetworkQualityTracker` from `@camstack/server/system`).
71702
72248
  * The provider is registered from `trpc.router.ts` against the existing
71703
72249
  * service instance — no addon owns this state.
71704
72250
  *
@@ -73849,7 +74395,7 @@ var ptzAutotrackCapability = {
73849
74395
  * - **Native**: the camera's own firmware autotrack, registered per device by
73850
74396
  * the driver when the probe says it exists (Reolink: yes; Hikvision:
73851
74397
  * model-dependent).
73852
- * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
74398
+ * - **Wrapper**: the emulated implementation (`@camstack/server/system`'s `autotrack` builtin),
73853
74399
  * which follows a subject with `ptz.move` pulses driven by the detection
73854
74400
  * pipeline. `defaultActive: false` — the operator binds it per camera in
73855
74401
  * the bindings UI.
@@ -84968,6 +85514,69 @@ Object.freeze({
84968
85514
  access: "view",
84969
85515
  role: "admin"
84970
85516
  },
85517
+ "migrations.declareOwner": {
85518
+ capName: "migrations",
85519
+ capScope: "system",
85520
+ addonId: null,
85521
+ access: "create",
85522
+ role: "admin"
85523
+ },
85524
+ "migrations.dryRun": {
85525
+ capName: "migrations",
85526
+ capScope: "system",
85527
+ addonId: null,
85528
+ access: "view",
85529
+ role: "admin"
85530
+ },
85531
+ "migrations.finishMigration": {
85532
+ capName: "migrations",
85533
+ capScope: "system",
85534
+ addonId: null,
85535
+ access: "create",
85536
+ role: "admin"
85537
+ },
85538
+ "migrations.listLedger": {
85539
+ capName: "migrations",
85540
+ capScope: "system",
85541
+ addonId: null,
85542
+ access: "view",
85543
+ role: "admin"
85544
+ },
85545
+ "migrations.listPending": {
85546
+ capName: "migrations",
85547
+ capScope: "system",
85548
+ addonId: null,
85549
+ access: "view",
85550
+ role: "admin"
85551
+ },
85552
+ "migrations.markFailed": {
85553
+ capName: "migrations",
85554
+ capScope: "system",
85555
+ addonId: null,
85556
+ access: "create",
85557
+ role: "admin"
85558
+ },
85559
+ "migrations.restorePreMigration": {
85560
+ capName: "migrations",
85561
+ capScope: "system",
85562
+ addonId: null,
85563
+ access: "create",
85564
+ role: "admin"
85565
+ },
85566
+ "migrations.retry": {
85567
+ capName: "migrations",
85568
+ capScope: "system",
85569
+ addonId: null,
85570
+ access: "create",
85571
+ role: "admin"
85572
+ },
85573
+ "migrations.startMigration": {
85574
+ capName: "migrations",
85575
+ capScope: "system",
85576
+ addonId: null,
85577
+ access: "create",
85578
+ role: "admin"
85579
+ },
84971
85580
  "modelConvert.convert": {
84972
85581
  capName: "model-convert",
84973
85582
  capScope: "system",