@camstack/addon-provider-homeassistant 1.2.643 → 1.2.645

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,4 +1,4 @@
1
- //#region ../types/dist/event-category-DmZDPDf_.mjs
1
+ //#region ../types/dist/event-category-BmPm98n0.mjs
2
2
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
3
3
  EventCategory["SystemBoot"] = "system.boot";
4
4
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -482,6 +482,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
482
482
  EventCategory["RunningStarted"] = "running.started";
483
483
  /** Mirrors `runningStateCapability.events.onFinished`. */
484
484
  EventCategory["RunningFinished"] = "running.finished";
485
+ /** Mirrors `deviceRunsCapability.events.onRunOpened` - a durable run record opened. */
486
+ EventCategory["DeviceRunOpened"] = "device-run.opened";
487
+ /** Mirrors `deviceRunsCapability.events.onRunClosed` - a durable run record closed. */
488
+ EventCategory["DeviceRunClosed"] = "device-run.closed";
485
489
  /**
486
490
  * Cap event fired by every device that registers the `event-emitter`
487
491
  * capability. Mirrors `eventEmitterCapability.events.onEvent`. Carries
@@ -714,6 +718,10 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
714
718
  EventCategory["NotificationRuleBroken"] = "notification.rule-broken";
715
719
  /** The clear of {@link NotificationRuleBroken}: the rule loads and names nothing deleted. */
716
720
  EventCategory["NotificationRuleRepaired"] = "notification.rule-repaired";
721
+ /** A migration failed or was blocked; the Alert Center re-reads the ledger. */
722
+ EventCategory["MigrationFailed"] = "migration.failed";
723
+ /** A migration applied or a failure cleared; the Alert Center re-reads the ledger. */
724
+ EventCategory["MigrationApplied"] = "migration.applied";
717
725
  EventCategory["DeviceUpdated"] = "device.updated";
718
726
  /**
719
727
  * Transport-level connectivity. Emitted by the device driver when
@@ -776,6 +784,378 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
776
784
  return EventCategory;
777
785
  }({});
778
786
  //#endregion
787
+ //#region ../types/dist/device-type-CEqjmUnR.mjs
788
+ /**
789
+ import { errMsg } from '@camstack/types'
790
+ * Extract a human-readable message from an unknown error value.
791
+ * Replaces the ubiquitous `errMsg(err)` pattern.
792
+ */
793
+ function errMsg(err) {
794
+ if (err instanceof Error) return err.message;
795
+ if (typeof err === "string") return err;
796
+ return String(err);
797
+ }
798
+ var DeviceType = /* @__PURE__ */ function(DeviceType) {
799
+ DeviceType["Camera"] = "camera";
800
+ DeviceType["Hub"] = "hub";
801
+ DeviceType["Light"] = "light";
802
+ DeviceType["Siren"] = "siren";
803
+ DeviceType["Switch"] = "switch";
804
+ DeviceType["Sensor"] = "sensor";
805
+ DeviceType["Thermostat"] = "thermostat";
806
+ /** Air-conditioner / heat-pump climate device (HVAC) — shares the
807
+ * `climate-control` cap surface with `Thermostat` but renders a
808
+ * dedicated AC-appropriate control UI (mode chips, fan speed,
809
+ * independent vertical/horizontal swing). Sources: native Gree, and
810
+ * reusable by other AC integrations. */
811
+ DeviceType["Climate"] = "climate";
812
+ DeviceType["Button"] = "button";
813
+ /** Generic stateless event emitter — carries a device's EXACT declared
814
+ * event vocabulary verbatim (no normalization). Installed with the
815
+ * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
816
+ * HA bus events (e.g. `zha_event`, generic). */
817
+ DeviceType["EventEmitter"] = "event-emitter";
818
+ /** Firmware/software update entity — current vs available version,
819
+ * updatable flag, update state, and an install action. Installed with
820
+ * the `update` cap. Sources: Homematic firmware-update channels (and
821
+ * reusable by other providers, e.g. HA `update.*` entities). */
822
+ DeviceType["Update"] = "update";
823
+ DeviceType["Generic"] = "generic";
824
+ /** Generic notification delivery target (HA `notify.<service>`, future
825
+ * Telegram / Discord / ntfy / SMTP, …). One device per delivery
826
+ * endpoint; the `notifier` cap defines the send surface. */
827
+ DeviceType["Notifier"] = "notifier";
828
+ /** Pre-recorded action sequence with optional parameters
829
+ * (HA `script.*`). Runnable via `script-runner` cap. */
830
+ DeviceType["Script"] = "script";
831
+ /** Automation rule (HA `automation.*`) — enable/disable + manual
832
+ * trigger surface exposed via `automation-control` cap. */
833
+ DeviceType["Automation"] = "automation";
834
+ /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
835
+ DeviceType["Lock"] = "lock";
836
+ /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
837
+ * `valve.*`). `cover` cap with sub-roles for variant. */
838
+ DeviceType["Cover"] = "cover";
839
+ /** Pipe / water / gas valve with open/close/stop and optional
840
+ * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
841
+ * modelled on the same open/closed lifecycle. */
842
+ DeviceType["Valve"] = "valve";
843
+ /** Humidifier / dehumidifier with on/off + target humidity + mode
844
+ * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
845
+ * modelled on the same target / mode lifecycle. */
846
+ DeviceType["Humidifier"] = "humidifier";
847
+ /** Water heater / boiler with target temperature + operation mode +
848
+ * away mode (HA `water_heater.*`). `water-heater` cap — a
849
+ * climate-family actuator. */
850
+ DeviceType["WaterHeater"] = "water-heater";
851
+ /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
852
+ DeviceType["Fan"] = "fan";
853
+ /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
854
+ * the camera surface — those use `Camera`. `media-player` cap. */
855
+ DeviceType["MediaPlayer"] = "media-player";
856
+ /** Security panel / alarm system (HA `alarm_control_panel.*`).
857
+ * `alarm-panel` cap. */
858
+ DeviceType["AlarmPanel"] = "alarm-panel";
859
+ /** Generic user-settable input (HA `number` / `input_number` / `select`
860
+ * / `input_select` / `text` / `input_text` / `input_datetime`).
861
+ * Sub-type via `DeviceRole`: NumericControl / SelectControl /
862
+ * TextControl / DateTimeControl. */
863
+ DeviceType["Control"] = "control";
864
+ /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
865
+ * `presence` cap. */
866
+ DeviceType["Presence"] = "presence";
867
+ /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
868
+ * `weather` cap. */
869
+ DeviceType["Weather"] = "weather";
870
+ /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
871
+ DeviceType["Vacuum"] = "vacuum";
872
+ /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
873
+ * `lawn-mower-control` cap. */
874
+ DeviceType["LawnMower"] = "lawn-mower";
875
+ /** Physical HA device group — parent container for entity-children
876
+ * adopted from a single HA device entry. Not renderable as a
877
+ * standalone device; exists only to anchor child entities. */
878
+ DeviceType["Container"] = "container";
879
+ /** Single still-image entity (HA `image.*`). Read-only display of an
880
+ * `entity_picture` signed URL the browser loads directly. `image` cap. */
881
+ DeviceType["Image"] = "image";
882
+ /** Smart pet feeder — cloud-connected food dispenser with a bowl food
883
+ * level, battery, desiccant life, feeding state and manual-feed /
884
+ * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
885
+ * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
886
+ * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
887
+ * integrations sharing the same food/desiccant/hopper surface. */
888
+ DeviceType["PetFeeder"] = "pet-feeder";
889
+ /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
890
+ * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
891
+ * of a container and is BUILT as a composed device (D967). */
892
+ DeviceType["PetFountain"] = "pet-fountain";
893
+ /** Built-in dishwasher — program lifecycle, door / remote-start gates,
894
+ * salt and rinse-aid levels. Installed with the `dishwasher` cap.
895
+ * Sources: native Bosch Home Connect, reusable by similar integrations.
896
+ * A plug-built dishwasher carries running-state and power instead. */
897
+ DeviceType["Dishwasher"] = "dishwasher";
898
+ /** Washing machine. A plug-built one is running while its power stays
899
+ * above a threshold. No program cap — that surface is the dishwasher's. */
900
+ DeviceType["Washer"] = "washer";
901
+ /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
902
+ DeviceType["Dryer"] = "dryer";
903
+ /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
904
+ * other radio-to-IP bridges). It carries the box's own facts (firmware,
905
+ * address, connection state) and the actions the box really supports
906
+ * (`reboot`, `device-admin-link`). Products are NOT its children — they are
907
+ * adopted beside it — and its connection settings stay on the integration. */
908
+ DeviceType["Gateway"] = "gateway";
909
+ return DeviceType;
910
+ }({});
911
+ var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
912
+ DeviceFeature["BatteryOperated"] = "battery-operated";
913
+ DeviceFeature["Rebootable"] = "rebootable";
914
+ /**
915
+ * Device supports an on-demand re-sync of its derived spec with its
916
+ * upstream source — drives the generic Re-sync button. The owning
917
+ * provider implements the action via the `device-adoption.resync` cap.
918
+ */
919
+ DeviceFeature["Resyncable"] = "resyncable";
920
+ DeviceFeature["NativeSnapshot"] = "native-snapshot";
921
+ DeviceFeature["DoorbellButton"] = "doorbell-button";
922
+ DeviceFeature["TwoWayAudio"] = "two-way-audio";
923
+ DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
924
+ /**
925
+ * Camera supports the on-firmware autotrack subsystem (subject-
926
+ * following). Distinct from `PanTiltZoom` because not every PTZ
927
+ * camera ships autotrack — the admin UI uses this flag to gate
928
+ * the autotrack toggle / settings card without re-deriving from
929
+ * the cap registry. Mirrors `ptz-autotrack` cap registration:
930
+ * driver sets this feature when probe confirms the firmware
931
+ * surface, and registers the cap in the same code path.
932
+ */
933
+ DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
934
+ /**
935
+ * Accessory exposes a "trigger on motion" toggle — the parent camera's
936
+ * motion detection automatically activates this device. Mirrors
937
+ * `motion-trigger` cap registration: drivers set this feature in the
938
+ * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
939
+ *
940
+ * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
941
+ * fast scalar without binding fetch), notifier rules, and `listAll`
942
+ * filters that want "all devices with on-motion behaviour".
943
+ */
944
+ DeviceFeature["MotionTrigger"] = "motion-trigger";
945
+ /** Light supports rgb-triplet color via `color` cap. */
946
+ DeviceFeature["LightColorRgb"] = "light-color-rgb";
947
+ /** Light supports HSV color via `color` cap. */
948
+ DeviceFeature["LightColorHsv"] = "light-color-hsv";
949
+ /** Light supports color-temperature (mired) via `color` cap. */
950
+ DeviceFeature["LightColorMired"] = "light-color-mired";
951
+ /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
952
+ * targetHigh). Gates the range slider UI. */
953
+ DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
954
+ /** Thermostat exposes target humidity and/or current humidity
955
+ * readings. Gates the humidity controls. */
956
+ DeviceFeature["ClimateHumidity"] = "climate-humidity";
957
+ /** Thermostat exposes a fan-mode selector. */
958
+ DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
959
+ /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
960
+ DeviceFeature["ClimatePreset"] = "climate-preset";
961
+ /** Thermostat exposes a vertical louver swing toggle. Gates the
962
+ * vertical-swing switch in the climate UI. Independent of horizontal. */
963
+ DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
964
+ /** Thermostat exposes a horizontal louver swing toggle. Gates the
965
+ * horizontal-swing switch in the climate UI. Independent of vertical. */
966
+ DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
967
+ /** Cover exposes intermediate position control (0..100). Gates the
968
+ * position slider UI. */
969
+ DeviceFeature["CoverPositionable"] = "cover-positionable";
970
+ /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
971
+ DeviceFeature["CoverTilt"] = "cover-tilt";
972
+ /** Valve exposes intermediate position control (0..100). Gates the
973
+ * position slider / drag surface UI. */
974
+ DeviceFeature["ValvePositionable"] = "valve-positionable";
975
+ /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
976
+ DeviceFeature["FanSpeed"] = "fan-speed";
977
+ /** Fan exposes a preset mode selector. */
978
+ DeviceFeature["FanPreset"] = "fan-preset";
979
+ /** Fan exposes blade direction (forward/reverse) — typical of
980
+ * ceiling fans. */
981
+ DeviceFeature["FanDirection"] = "fan-direction";
982
+ /** Fan exposes an oscillation toggle. */
983
+ DeviceFeature["FanOscillating"] = "fan-oscillating";
984
+ /** Lock requires a PIN code on lock/unlock. Gates the code-entry
985
+ * field on the UI lock-controls panel. */
986
+ DeviceFeature["LockPinRequired"] = "lock-pin-required";
987
+ /** Lock supports a latch-release ("open door") action distinct from
988
+ * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
989
+ * `supported_features`. Gates the Open Door button in the UI. */
990
+ DeviceFeature["LockOpen"] = "lock-open";
991
+ /** Media player exposes a seek-to-position surface. */
992
+ DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
993
+ /** Media player exposes a volume-level setter. */
994
+ DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
995
+ /** Media player exposes a mute toggle distinct from volume=0. */
996
+ DeviceFeature["MediaPlayerMute"] = "media-player-mute";
997
+ /** Media player exposes a shuffle toggle. */
998
+ DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
999
+ /** Media player exposes a repeat mode (off / all / one). */
1000
+ DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
1001
+ /** Media player exposes a source / input selector. */
1002
+ DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
1003
+ /** Media player exposes a play-arbitrary-media surface (URL / id). */
1004
+ DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
1005
+ /** Media player exposes next-track. */
1006
+ DeviceFeature["MediaPlayerNext"] = "media-player-next";
1007
+ /** Media player exposes previous-track. */
1008
+ DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
1009
+ /** Media player exposes stop distinct from pause. */
1010
+ DeviceFeature["MediaPlayerStop"] = "media-player-stop";
1011
+ /** Alarm panel requires a PIN code on arm/disarm. */
1012
+ DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
1013
+ /** Presence device carries GPS coordinates (lat/lng/accuracy) in
1014
+ * addition to a textual location. */
1015
+ DeviceFeature["PresenceGps"] = "presence-gps";
1016
+ /** Notifier accepts an inline / URL image attachment. */
1017
+ DeviceFeature["NotifierImage"] = "notifier-image";
1018
+ /** Notifier accepts a priority hint (high/normal/low). */
1019
+ DeviceFeature["NotifierPriority"] = "notifier-priority";
1020
+ /** Notifier accepts a free-form `data` payload for platform-specific
1021
+ * fields. */
1022
+ DeviceFeature["NotifierData"] = "notifier-data";
1023
+ /** Notifier supports interactive action buttons / callbacks. */
1024
+ DeviceFeature["NotifierActions"] = "notifier-actions";
1025
+ /** Notifier supports per-call recipient targeting (multi-user). */
1026
+ DeviceFeature["NotifierRecipients"] = "notifier-recipients";
1027
+ /** Script runner accepts a variables map on each run invocation. */
1028
+ DeviceFeature["ScriptVariables"] = "script-variables";
1029
+ /** Automation `trigger` accepts a skipCondition flag — fires the
1030
+ * automation's actions while bypassing its condition block. */
1031
+ DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
1032
+ /** Robot vacuum exposes a live cleaning map (image child). Gates the
1033
+ * map tile in the vacuum UI. */
1034
+ DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
1035
+ /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
1036
+ * switches group. */
1037
+ DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
1038
+ /** Robot mower exposes a live mowing map (SVG image child). Gates the
1039
+ * mower map tile. */
1040
+ DeviceFeature["MowerHasMap"] = "mower-has-map";
1041
+ /** Robot mower exposes targeted mowing (all-area / zones / edges /
1042
+ * spots) — gates the Mow action + map selectors. */
1043
+ DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
1044
+ return DeviceFeature;
1045
+ }({});
1046
+ /**
1047
+ * Semantic role a device plays within its parent. Populated by driver
1048
+ * addons when creating accessory devices (Reolink siren/floodlight/
1049
+ * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
1050
+ * admin UI to pick icons, labels, and widgets — a `Switch` with
1051
+ * `role: Floodlight` renders as a bulb with a brightness slider,
1052
+ * whereas a `Switch` with `role: Siren` renders as a klaxon.
1053
+ *
1054
+ * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
1055
+ * sqlite as a nullable TEXT column — old rows keep working unchanged.
1056
+ */
1057
+ var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
1058
+ DeviceRole["Siren"] = "siren";
1059
+ DeviceRole["Floodlight"] = "floodlight";
1060
+ DeviceRole["Spotlight"] = "spotlight";
1061
+ DeviceRole["PirSensor"] = "pir-sensor";
1062
+ DeviceRole["Chime"] = "chime";
1063
+ DeviceRole["Autotrack"] = "autotrack";
1064
+ DeviceRole["Nightvision"] = "nightvision";
1065
+ DeviceRole["PrivacyMask"] = "privacy-mask";
1066
+ DeviceRole["Doorbell"] = "doorbell";
1067
+ /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
1068
+ DeviceRole["CoverBlind"] = "cover-blind";
1069
+ /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
1070
+ DeviceRole["CoverShutter"] = "cover-shutter";
1071
+ /** Exterior awning. HA `device_class: awning`. */
1072
+ DeviceRole["CoverAwning"] = "cover-awning";
1073
+ /** Curtain / drape. HA `device_class: curtain`. */
1074
+ DeviceRole["CoverCurtain"] = "cover-curtain";
1075
+ /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
1076
+ DeviceRole["CoverGarage"] = "cover-garage";
1077
+ /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
1078
+ DeviceRole["CoverGate"] = "cover-gate";
1079
+ /** Door. HA `device_class: door`. A security actuator: exports gate it. */
1080
+ DeviceRole["CoverDoor"] = "cover-door";
1081
+ /** Window. HA `device_class: window`. */
1082
+ DeviceRole["CoverWindow"] = "cover-window";
1083
+ /** Virtual HA toggle (input_boolean.*) — distinguishable from a
1084
+ * real Switch device for UI rendering / export adapters. */
1085
+ DeviceRole["BinaryHelper"] = "binary-helper";
1086
+ /** A smart plug: a `switch` whose source says it is an outlet (HA
1087
+ * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
1088
+ DeviceRole["Outlet"] = "outlet";
1089
+ /** Generic motion / occupancy / moving event source. Distinct from
1090
+ * the camera accessory PirSensor role: that one is a camera child;
1091
+ * this is a standalone HA / 3rd-party motion sensor. */
1092
+ DeviceRole["MotionSensor"] = "motion-sensor";
1093
+ DeviceRole["ContactSensor"] = "contact-sensor";
1094
+ DeviceRole["LeakSensor"] = "leak-sensor";
1095
+ DeviceRole["SmokeSensor"] = "smoke-sensor";
1096
+ DeviceRole["COSensor"] = "co-sensor";
1097
+ DeviceRole["GasSensor"] = "gas-sensor";
1098
+ DeviceRole["TamperSensor"] = "tamper-sensor";
1099
+ DeviceRole["VibrationSensor"] = "vibration-sensor";
1100
+ DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
1101
+ DeviceRole["SoundSensor"] = "sound-sensor";
1102
+ /** Fallback for `binary_sensor` without a known `device_class`. */
1103
+ DeviceRole["BinarySensor"] = "binary-sensor";
1104
+ DeviceRole["TemperatureSensor"] = "temperature-sensor";
1105
+ DeviceRole["HumiditySensor"] = "humidity-sensor";
1106
+ DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
1107
+ DeviceRole["PressureSensor"] = "pressure-sensor";
1108
+ /** Wind speed or direction (weather-station `wind-sensor` cap). */
1109
+ DeviceRole["WindSensor"] = "wind-sensor";
1110
+ /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
1111
+ DeviceRole["RainSensor"] = "rain-sensor";
1112
+ /** UV index (weather-station `uv-sensor` cap). */
1113
+ DeviceRole["UvSensor"] = "uv-sensor";
1114
+ /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
1115
+ * Distinct from AmbientLightSensor (lux). */
1116
+ DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
1117
+ /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
1118
+ DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
1119
+ DeviceRole["PowerSensor"] = "power-sensor";
1120
+ DeviceRole["EnergySensor"] = "energy-sensor";
1121
+ DeviceRole["VoltageSensor"] = "voltage-sensor";
1122
+ DeviceRole["CurrentSensor"] = "current-sensor";
1123
+ DeviceRole["AirQualitySensor"] = "air-quality-sensor";
1124
+ /** Battery level (numeric % via `sensor` OR low-bool via
1125
+ * `binary_sensor` — the cap distinguishes via the value type). */
1126
+ DeviceRole["BatterySensor"] = "battery-sensor";
1127
+ /** Fallback for `sensor` numeric without a known `device_class`. */
1128
+ DeviceRole["NumericSensor"] = "numeric-sensor";
1129
+ /** String / enum state (HA `sensor` with `state_class: enum` or
1130
+ * `attributes.options`). */
1131
+ DeviceRole["EnumSensor"] = "enum-sensor";
1132
+ /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
1133
+ * or `date`). The slice carries the raw ISO string verbatim (hosted on
1134
+ * the `enum-sensor` cap); the UI renders it locale-formatted. */
1135
+ DeviceRole["DateTimeSensor"] = "datetime-sensor";
1136
+ /** Last-resort fallback when nothing else matches. */
1137
+ DeviceRole["GenericSensor"] = "generic-sensor";
1138
+ DeviceRole["NumericControl"] = "numeric-control";
1139
+ DeviceRole["SelectControl"] = "select-control";
1140
+ DeviceRole["TextControl"] = "text-control";
1141
+ DeviceRole["DateTimeControl"] = "datetime-control";
1142
+ /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
1143
+ * rich features (image, priority, channel routing). */
1144
+ DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
1145
+ /** Chat / messaging service (HA `notify.telegram_*`,
1146
+ * `notify.discord_*`, etc.). */
1147
+ DeviceRole["MessagingNotifier"] = "messaging-notifier";
1148
+ /** Email-based delivery (HA `notify.smtp`, etc.). */
1149
+ DeviceRole["EmailNotifier"] = "email-notifier";
1150
+ /** Fallback when the notifier service name doesn't match a known
1151
+ * pattern. */
1152
+ DeviceRole["GenericNotifier"] = "generic-notifier";
1153
+ /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
1154
+ * cap. A `DeviceType.Generic` device. */
1155
+ DeviceRole["IrTransmitter"] = "ir-transmitter";
1156
+ return DeviceRole;
1157
+ }({});
1158
+ //#endregion
779
1159
  //#region ../../node_modules/zod/v4/core/core.js
780
1160
  var _a$1;
781
1161
  function $constructor(name, initializer, params) {
@@ -5587,7 +5967,7 @@ var ZodIssueCode = {
5587
5967
  var ZodFirstPartyTypeKind;
5588
5968
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5589
5969
  //#endregion
5590
- //#region ../types/dist/sleep-dco0-jRo.mjs
5970
+ //#region ../types/dist/sleep-BKzhQTSm.mjs
5591
5971
  /**
5592
5972
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5593
5973
  * window to float samples (D455).
@@ -6103,6 +6483,102 @@ function emitReadiness(bus, params) {
6103
6483
  ts
6104
6484
  }));
6105
6485
  }
6486
+ var NOT_RUN = {
6487
+ declared: false,
6488
+ snapshot: null,
6489
+ applied: [],
6490
+ failed: [],
6491
+ blocked: [],
6492
+ skipped: [],
6493
+ blockOwner: []
6494
+ };
6495
+ /**
6496
+ * Offer `migrations` to the engine and run what it says to run, in its order. Never
6497
+ * throws: an unreachable or refusing engine is logged and reported `declared: false`,
6498
+ * because the hub (and this addon's boot) must not hang on a migration.
6499
+ */
6500
+ async function runAddonMigrations(host, migrations, logger) {
6501
+ const first = migrations[0];
6502
+ if (first === void 0) return NOT_RUN;
6503
+ const owner = first.meta.owner;
6504
+ const log = logger.withTags({ migrationOwner: owner });
6505
+ const byId = new Map(migrations.map((m) => [m.meta.id, m]));
6506
+ let declaration;
6507
+ try {
6508
+ declaration = await host.declareOwner({
6509
+ owner,
6510
+ metas: migrations.map((m) => m.meta)
6511
+ });
6512
+ } catch (err) {
6513
+ log.error("the migration engine refused or could not take this addon catalogue — none ran", { meta: { error: errMsg(err) } });
6514
+ return {
6515
+ ...NOT_RUN,
6516
+ blockOwner: migrations.filter((m) => m.meta.onFailure === "block-owner").map((m) => m.meta.id)
6517
+ };
6518
+ }
6519
+ const applied = [];
6520
+ const failed = [];
6521
+ const blocked = [];
6522
+ const skipped = [];
6523
+ const settle = (id, status) => {
6524
+ if (status === "applied") applied.push(id);
6525
+ else if (status === "failed") failed.push(id);
6526
+ else if (status === "blocked") blocked.push(id);
6527
+ else skipped.push(id);
6528
+ };
6529
+ for (const step of declaration.steps) {
6530
+ const bound = byId.get(step.id);
6531
+ if (bound === void 0 || step.action === "skip") {
6532
+ skipped.push(step.id);
6533
+ continue;
6534
+ }
6535
+ const tags = { migrationId: step.id };
6536
+ try {
6537
+ const gate = await host.startMigration({ id: step.id });
6538
+ if (!gate.proceed) {
6539
+ log.warn("migration not run", {
6540
+ tags,
6541
+ meta: {
6542
+ state: gate.state,
6543
+ reason: gate.reason
6544
+ }
6545
+ });
6546
+ settle(step.id, gate.state === "pending" ? "blocked" : gate.state);
6547
+ continue;
6548
+ }
6549
+ const result = await bound.execute();
6550
+ const outcome = await host.finishMigration({
6551
+ id: step.id,
6552
+ result
6553
+ });
6554
+ if (outcome.status === "applied") log.info("migration applied", {
6555
+ tags,
6556
+ meta: { rowsChanged: outcome.rowsChanged }
6557
+ });
6558
+ else log.error("migration failed", {
6559
+ tags,
6560
+ meta: { error: outcome.error }
6561
+ });
6562
+ settle(step.id, outcome.status);
6563
+ } catch (err) {
6564
+ log.error("migration could not be completed with the engine", {
6565
+ tags,
6566
+ meta: { error: errMsg(err) }
6567
+ });
6568
+ failed.push(step.id);
6569
+ }
6570
+ }
6571
+ const blockOwner = [...failed, ...blocked].filter((id) => byId.get(id)?.meta.onFailure === "block-owner");
6572
+ return {
6573
+ declared: true,
6574
+ snapshot: declaration.snapshot,
6575
+ applied,
6576
+ failed,
6577
+ blocked,
6578
+ skipped,
6579
+ blockOwner
6580
+ };
6581
+ }
6106
6582
  /**
6107
6583
  * Build a {@link DurableState} over a single store key. Transport-agnostic:
6108
6584
  * `read`/`write` are the addon-store or device-store accessors. The whole
@@ -7158,6 +7634,28 @@ var BaseAddon = class {
7158
7634
  return normalized;
7159
7635
  }
7160
7636
  /**
7637
+ * Run this addon's migration catalogue (D987 M3) through the hub's `migrations`
7638
+ * engine. Call it at the point of the boot where the data must already have the new
7639
+ * shape — `onInitialize`, or `onRestoreDevices` for a migration that needs the
7640
+ * restored devices — with the catalogue bound to the addon's ports
7641
+ * (`defineAddonMigrations(ADDON_MIGRATIONS, ports)`).
7642
+ *
7643
+ * An engine that cannot be reached or refuses the catalogue is logged and runs
7644
+ * nothing; the boot continues. A migration that FAILED (or was blocked by one)
7645
+ * and declares `onFailure: 'block-owner'` makes this throw, so the addon does not
7646
+ * serve data whose shape it cannot trust; `tolerate` migrations never do.
7647
+ */
7648
+ async runMigrations(migrations) {
7649
+ const api = this.ctx.api.migrations;
7650
+ const report = await runAddonMigrations({
7651
+ declareOwner: (input) => api.declareOwner.mutate(input),
7652
+ startMigration: (input) => api.startMigration.mutate(input),
7653
+ finishMigration: (input) => api.finishMigration.mutate(input)
7654
+ }, migrations, this.ctx.logger);
7655
+ 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`);
7656
+ return report;
7657
+ }
7658
+ /**
7161
7659
  * Called by the isolated-process runner AFTER `broker.start()`.
7162
7660
  * In-process addons never need this because the hub broker is already
7163
7661
  * running when `initialize()` fires. For forked children the broker
@@ -8455,410 +8953,6 @@ function defineEntryKind(name, spec) {
8455
8953
  if (defined === void 0) throw new DeclarationRefusedError(`entry kind "${name}" was not built`);
8456
8954
  return defined;
8457
8955
  }
8458
- var READING_QUANTITIES = [
8459
- "temperature",
8460
- "pressure",
8461
- "speed",
8462
- "precipitation",
8463
- "precipitation-rate",
8464
- "length",
8465
- "area",
8466
- "illuminance",
8467
- "irradiance",
8468
- "power",
8469
- "energy",
8470
- "voltage",
8471
- "current",
8472
- "apparent-power",
8473
- "humidity",
8474
- "percentage",
8475
- "concentration-volume",
8476
- "concentration-mass",
8477
- "concentration-ppb",
8478
- "volume",
8479
- "duration",
8480
- "duration-ms",
8481
- "information",
8482
- "sound-level",
8483
- "signal-level",
8484
- "colour-temperature",
8485
- "angle",
8486
- "bitrate",
8487
- "rate",
8488
- "mass",
8489
- "index",
8490
- "ratio",
8491
- "count",
8492
- "none",
8493
- "device"
8494
- ];
8495
- var ReadingQuantitySchema = _enum(READING_QUANTITIES);
8496
- var QUANTITY_SET = new Set(READING_QUANTITIES);
8497
- function isReadingQuantity(value) {
8498
- return typeof value === "string" && QUANTITY_SET.has(value);
8499
- }
8500
- /**
8501
- * Stored unit symbol for a quantity. `none` means the number is dimensionless
8502
- * (or an index / ratio / count). `device` means the cap has no single unit.
8503
- */
8504
- var QUANTITY_BASE = {
8505
- temperature: "°C",
8506
- pressure: "hPa",
8507
- speed: "m/s",
8508
- precipitation: "mm",
8509
- "precipitation-rate": "mm/h",
8510
- length: "m",
8511
- area: "m²",
8512
- illuminance: "lx",
8513
- irradiance: "W/m²",
8514
- power: "W",
8515
- energy: "kWh",
8516
- voltage: "V",
8517
- current: "A",
8518
- "apparent-power": "VA",
8519
- humidity: "%",
8520
- percentage: "%",
8521
- "concentration-volume": "ppm",
8522
- "concentration-mass": "µg/m³",
8523
- "concentration-ppb": "ppb",
8524
- volume: "L",
8525
- duration: "s",
8526
- "duration-ms": "ms",
8527
- information: "B",
8528
- "sound-level": "dBFS",
8529
- "signal-level": "dBm",
8530
- "colour-temperature": "mired",
8531
- angle: "°",
8532
- bitrate: "kbps",
8533
- rate: "fps",
8534
- mass: "kg",
8535
- index: "none",
8536
- ratio: "none",
8537
- count: "none",
8538
- none: "none",
8539
- device: "device"
8540
- };
8541
- /** The stored unit, or `undefined` when the quantity has no unit symbol. */
8542
- function baseUnitOf(quantity) {
8543
- const symbol = QUANTITY_BASE[quantity];
8544
- if (symbol === "none" || symbol === "device") return void 0;
8545
- return symbol;
8546
- }
8547
- function readingMeta(quantity, extras) {
8548
- return {
8549
- quantity,
8550
- ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
8551
- ...extras?.label !== void 0 ? { label: extras.label } : {}
8552
- };
8553
- }
8554
- /** Shared Zod schemas used across streaming capabilities. */
8555
- var CamProfileSchema = _enum([
8556
- "high",
8557
- "mid",
8558
- "low"
8559
- ]);
8560
- /**
8561
- * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
8562
- *
8563
- * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
8564
- * that consumer owns". It lives beside `CamProfile` because a rung is the only
8565
- * operator-facing way to name a stream: a cam-stream id is broker-internal and
8566
- * moves when a slot is reassigned, so a stored id silently stops meaning what
8567
- * the operator picked. Today's one user is the audio analyser, whose rule is
8568
- * "the lowest AVAILABLE native rung" (D537).
8569
- */
8570
- var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
8571
- var CamStreamKindSchema = _enum([
8572
- "pull-rtsp",
8573
- "pull-rtmp",
8574
- "pull-http",
8575
- "pull-flv",
8576
- "pull-rfc4571",
8577
- "push-annexb",
8578
- "derived"
8579
- ]);
8580
- var CamStreamResolutionSchema = object({
8581
- width: number().int().positive().meta(readingMeta("none")),
8582
- height: number().int().positive().meta(readingMeta("none"))
8583
- });
8584
- var CameraStreamSchema = object({
8585
- /** Stable, provider-assigned id unique within the (deviceId) scope. */
8586
- camStreamId: string().min(1),
8587
- deviceId: number().int().nonnegative(),
8588
- kind: CamStreamKindSchema,
8589
- /** Required for pull-* kinds. Ignored for push-annexb. */
8590
- url: string().optional(),
8591
- codec: string().optional(),
8592
- resolution: CamStreamResolutionSchema.optional(),
8593
- fps: number().positive().optional(),
8594
- /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
8595
- label: string().optional(),
8596
- /**
8597
- * Device-level features the publisher advertised (e.g. `battery-operated`).
8598
- * The broker, snapshot orchestrator, and prebuffer manager all consult
8599
- * this list to derive policy — relaxed stall watchdog for battery
8600
- * cams, prebuffer off by default, longer snapshot rate-limit, etc.
8601
- *
8602
- * Single source of truth replacing per-stream flags like the
8603
- * historical `allowStall`: if the publisher knows the camera is
8604
- * battery-powered, every downstream service derives the right policy
8605
- * from this list.
8606
- */
8607
- deviceFeatures: array(string()).optional(),
8608
- /**
8609
- * Whether this stream participates in the broker's automatic profile
8610
- * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
8611
- * use `false` when they want a stream to be SELECTABLE in the UI but not
8612
- * picked by default — e.g. Reolink publishes its native Baichuan streams
8613
- * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
8614
- * mirrors as `autoEligible: false` (still pickable per slot, just not
8615
- * the auto choice). Manual `assignProfile` calls remain valid for
8616
- * non-eligible streams.
8617
- */
8618
- autoEligible: boolean().optional(),
8619
- /**
8620
- * The profile slot the PUBLISHER says this stream is: `low` for a stream
8621
- * whose only sensible role is the low-bandwidth one, `high` for a main
8622
- * stream. Consulted by the broker's automatic ranking
8623
- * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
8624
- * an operator's manual assignment still wins, and an absent hint is the
8625
- * pixel-ranked default every camera had before this field existed.
8626
- *
8627
- * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
8628
- * storage class is written for on its own (`recordings` is high|mid,
8629
- * `recordingsLow` is low) and the one profile scrub does not read. The robot
8630
- * camera's single 720p relay is a `low` in every respect except the slot the
8631
- * heuristic put it in.
8632
- */
8633
- profileHint: CamProfileSchema.optional(),
8634
- /**
8635
- * Transport-specific opaque metadata. The broker passes it through to
8636
- * the source reader without inspecting it. Currently used by
8637
- * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
8638
- * route RTP packets to the right depacketizer without an in-band
8639
- * DESCRIBE phase). Other kinds typically leave it undefined.
8640
- */
8641
- metadata: record(string(), unknown()).optional()
8642
- });
8643
- var ProfileSlotStatusSchema = _enum([
8644
- "unassigned",
8645
- "idle",
8646
- "connecting",
8647
- "streaming",
8648
- "error"
8649
- ]);
8650
- var ProfileSlotSchema = object({
8651
- deviceId: number().int().nonnegative(),
8652
- profile: CamProfileSchema,
8653
- /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
8654
- brokerId: string(),
8655
- /** `null` when the profile is unassigned. */
8656
- sourceCamStreamId: string().nullable(),
8657
- status: ProfileSlotStatusSchema,
8658
- resolution: CamStreamResolutionSchema.optional(),
8659
- codec: string().optional(),
8660
- preBufferSec: number().nonnegative().optional(),
8661
- errorMessage: string().optional()
8662
- });
8663
- /**
8664
- * Zod schema for StreamSourceEntry — the canonical stream descriptor
8665
- * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
8666
- */
8667
- var StreamSourceEntrySchema$1 = object({
8668
- id: string(),
8669
- label: string(),
8670
- protocol: _enum([
8671
- "rtsp",
8672
- "rtmp",
8673
- "annexb",
8674
- "http-mjpeg",
8675
- "webrtc",
8676
- "custom"
8677
- ]),
8678
- url: string().optional(),
8679
- resolution: object({
8680
- width: number(),
8681
- height: number()
8682
- }).readonly().optional(),
8683
- fps: number().optional(),
8684
- bitrate: number().optional(),
8685
- codec: string().optional(),
8686
- profileHint: CamProfileSchema.optional()
8687
- });
8688
- object({
8689
- type: string(),
8690
- url: string(),
8691
- videoCodec: string().optional(),
8692
- audioCodec: string().optional(),
8693
- metadata: record(string(), unknown()).readonly().optional()
8694
- });
8695
- /**
8696
- * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
8697
- * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
8698
- * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
8699
- * (the runner's native-crop and pipeline-executor inputs) can carry handles
8700
- * over tRPC / Moleculer.
8701
- *
8702
- * The `satisfies` assertion below pins this schema to the interface: if a
8703
- * field is added to / removed from `FrameHandle` without a matching schema
8704
- * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
8705
- * `FrameHandle` and the build fails — no silent drift.
8706
- */
8707
- var FrameHandleSchema = object({
8708
- shmId: string(),
8709
- slot: number().int().nonnegative(),
8710
- seq: number().int().nonnegative(),
8711
- width: number().int().positive(),
8712
- height: number().int().positive(),
8713
- format: _enum([
8714
- "jpeg",
8715
- "rgb",
8716
- "bgr",
8717
- "yuv420",
8718
- "gray"
8719
- ]),
8720
- pts: number(),
8721
- byteLength: number().int().nonnegative(),
8722
- nodeId: string(),
8723
- slotCount: number().int().positive()
8724
- });
8725
- /**
8726
- * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
8727
- * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
8728
- * sample bytes plus the track parameters a consumer needs to interpret them.
8729
- *
8730
- * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
8731
- * Float32 PCM), so unlike video frames they ship their bytes INLINE over
8732
- * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
8733
- * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
8734
- * it.
8735
- */
8736
- var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
8737
- var DecodedAudioChunkSchema = object({
8738
- data: _instanceof(Uint8Array),
8739
- sampleRate: number().int().positive(),
8740
- channels: number().int().positive(),
8741
- timestamp: number(),
8742
- /**
8743
- * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
8744
- * byte, for any peer that never heard of this field. A coded window
8745
- * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
8746
- * subscription that DECLARED it accepts one, so absence can never mean
8747
- * "coded bytes a consumer will read as floats" (D455).
8748
- */
8749
- format: AudioChunkFormatSchema.optional()
8750
- });
8751
- /**
8752
- * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
8753
- * its bytes inline: a consumer asks the broker for the decoded audio-chunk
8754
- * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
8755
- * the consumer drains via `pullAudioChunks`.
8756
- */
8757
- var SubscribeAudioChunksInputSchema = object({
8758
- brokerId: string(),
8759
- /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
8760
- tag: string().optional(),
8761
- /**
8762
- * Byte formats this subscriber can READ, best first. The broker serves the
8763
- * chunk's own format when it is in this list and expands to `f32le`
8764
- * otherwise, so a subscriber is never handed bytes it cannot interpret.
8765
- *
8766
- * Absent (or without the source format) means `f32le` — the behaviour every
8767
- * subscriber had before D455, unchanged. This is the negotiation half of
8768
- * the source-bytes lever: it is what lets the broker and its consumers
8769
- * deploy one at a time across three nodes.
8770
- */
8771
- accept: array(AudioChunkFormatSchema).readonly().optional()
8772
- });
8773
- /** Result of `stream-broker.subscribeAudioChunks`. */
8774
- var SubscribeAudioChunksResultSchema = object({
8775
- /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
8776
- subscriptionId: string() });
8777
- var BrokerStatsSchema = object({
8778
- status: _enum([
8779
- "idle",
8780
- "connecting",
8781
- "streaming",
8782
- "error",
8783
- "stopped"
8784
- ]),
8785
- inputFps: number(),
8786
- encodedSubscribers: number(),
8787
- uptimeMs: number(),
8788
- bitrateKbps: number(),
8789
- idrIntervalMs: number(),
8790
- codec: string().optional(),
8791
- totalBytes: number(),
8792
- packetCount: number(),
8793
- rtspClients: number(),
8794
- pipeClients: number(),
8795
- preBufferSec: number(),
8796
- preBufferMs: number(),
8797
- preBufferPackets: number(),
8798
- /**
8799
- * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
8800
- * when the stream has no audio track or the broker is in cold start.
8801
- * `supported = false` means the codec was detected but the local
8802
- * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
8803
- * AAC pipeline wired). Surfaced in the UI device-overview so operators
8804
- * can pre-pick the audio-analysis model that matches the codec.
8805
- */
8806
- audio: object({
8807
- codec: string(),
8808
- sampleRate: number(),
8809
- channels: number(),
8810
- supported: boolean()
8811
- }).nullable().optional(),
8812
- /**
8813
- * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
8814
- * distributing none of the device's audio, on live or recording.
8815
- *
8816
- * Present so a silent camera can be told apart from a broken one on the
8817
- * stream panel itself, without cross-referencing the switch group: a
8818
- * broker holding an `audio` track descriptor while `audioMuted` is true is
8819
- * working exactly as asked. `audioMutedDropped` counts the audio units
8820
- * thrown away since the current dial — it is how you confirm from stats
8821
- * alone that the mute is on the packet path and not merely persisted.
8822
- */
8823
- audioMuted: boolean().optional(),
8824
- audioMutedDropped: number().optional()
8825
- });
8826
- /**
8827
- * Exporter-facing "profile restream" entry. Returned by
8828
- * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
8829
- * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
8830
- * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
8831
- * RTSP restream that aliases the profile's assigned source broker, so
8832
- * every consumer dialling a profile converges on the same single pull.
8833
- * `codec`/`resolution` describe the assigned SOURCE camStream (for the
8834
- * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
8835
- * (raw per-camStream, live-view only).
8836
- */
8837
- var ProfileRtspEntrySchema = object({
8838
- profile: CamProfileSchema,
8839
- /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
8840
- brokerId: string(),
8841
- url: string(),
8842
- mutedUrl: string(),
8843
- enabled: boolean(),
8844
- codec: string().optional(),
8845
- resolution: CamStreamResolutionSchema.optional()
8846
- });
8847
- /**
8848
- * The model weight formats a catalog entry can ship. Schema-free on purpose:
8849
- * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
8850
- * (the model downloader in `@camstack/system`) needs only the list and must
8851
- * reach it through `@camstack/types/addon` without building the cap schema
8852
- * graph (D28).
8853
- */
8854
- var MODEL_FORMATS = [
8855
- "onnx",
8856
- "coreml",
8857
- "openvino",
8858
- "tflite",
8859
- "pt",
8860
- "gguf"
8861
- ];
8862
8956
  object({
8863
8957
  field: string().min(1),
8864
8958
  on: literal("advance")
@@ -8951,6 +9045,410 @@ function optionalMethod(input, output, options) {
8951
9045
  function event(data) {
8952
9046
  return { data };
8953
9047
  }
9048
+ var READING_QUANTITIES = [
9049
+ "temperature",
9050
+ "pressure",
9051
+ "speed",
9052
+ "precipitation",
9053
+ "precipitation-rate",
9054
+ "length",
9055
+ "area",
9056
+ "illuminance",
9057
+ "irradiance",
9058
+ "power",
9059
+ "energy",
9060
+ "voltage",
9061
+ "current",
9062
+ "apparent-power",
9063
+ "humidity",
9064
+ "percentage",
9065
+ "concentration-volume",
9066
+ "concentration-mass",
9067
+ "concentration-ppb",
9068
+ "volume",
9069
+ "duration",
9070
+ "duration-ms",
9071
+ "information",
9072
+ "sound-level",
9073
+ "signal-level",
9074
+ "colour-temperature",
9075
+ "angle",
9076
+ "bitrate",
9077
+ "rate",
9078
+ "mass",
9079
+ "index",
9080
+ "ratio",
9081
+ "count",
9082
+ "none",
9083
+ "device"
9084
+ ];
9085
+ var ReadingQuantitySchema = _enum(READING_QUANTITIES);
9086
+ var QUANTITY_SET = new Set(READING_QUANTITIES);
9087
+ function isReadingQuantity(value) {
9088
+ return typeof value === "string" && QUANTITY_SET.has(value);
9089
+ }
9090
+ /**
9091
+ * Stored unit symbol for a quantity. `none` means the number is dimensionless
9092
+ * (or an index / ratio / count). `device` means the cap has no single unit.
9093
+ */
9094
+ var QUANTITY_BASE = {
9095
+ temperature: "°C",
9096
+ pressure: "hPa",
9097
+ speed: "m/s",
9098
+ precipitation: "mm",
9099
+ "precipitation-rate": "mm/h",
9100
+ length: "m",
9101
+ area: "m²",
9102
+ illuminance: "lx",
9103
+ irradiance: "W/m²",
9104
+ power: "W",
9105
+ energy: "kWh",
9106
+ voltage: "V",
9107
+ current: "A",
9108
+ "apparent-power": "VA",
9109
+ humidity: "%",
9110
+ percentage: "%",
9111
+ "concentration-volume": "ppm",
9112
+ "concentration-mass": "µg/m³",
9113
+ "concentration-ppb": "ppb",
9114
+ volume: "L",
9115
+ duration: "s",
9116
+ "duration-ms": "ms",
9117
+ information: "B",
9118
+ "sound-level": "dBFS",
9119
+ "signal-level": "dBm",
9120
+ "colour-temperature": "mired",
9121
+ angle: "°",
9122
+ bitrate: "kbps",
9123
+ rate: "fps",
9124
+ mass: "kg",
9125
+ index: "none",
9126
+ ratio: "none",
9127
+ count: "none",
9128
+ none: "none",
9129
+ device: "device"
9130
+ };
9131
+ /** The stored unit, or `undefined` when the quantity has no unit symbol. */
9132
+ function baseUnitOf(quantity) {
9133
+ const symbol = QUANTITY_BASE[quantity];
9134
+ if (symbol === "none" || symbol === "device") return void 0;
9135
+ return symbol;
9136
+ }
9137
+ function readingMeta(quantity, extras) {
9138
+ return {
9139
+ quantity,
9140
+ ...extras?.precision !== void 0 ? { precision: extras.precision } : {},
9141
+ ...extras?.label !== void 0 ? { label: extras.label } : {}
9142
+ };
9143
+ }
9144
+ /** Shared Zod schemas used across streaming capabilities. */
9145
+ var CamProfileSchema = _enum([
9146
+ "high",
9147
+ "mid",
9148
+ "low"
9149
+ ]);
9150
+ /**
9151
+ * Which rung a per-camera consumer listens on: an explicit profile, or `auto`.
9152
+ *
9153
+ * `auto` is a REAL value, not an absent one — it means "resolve it by the rule
9154
+ * that consumer owns". It lives beside `CamProfile` because a rung is the only
9155
+ * operator-facing way to name a stream: a cam-stream id is broker-internal and
9156
+ * moves when a slot is reassigned, so a stored id silently stops meaning what
9157
+ * the operator picked. Today's one user is the audio analyser, whose rule is
9158
+ * "the lowest AVAILABLE native rung" (D537).
9159
+ */
9160
+ var AudioStreamPickSchema = union([CamProfileSchema, literal("auto")]);
9161
+ var CamStreamKindSchema = _enum([
9162
+ "pull-rtsp",
9163
+ "pull-rtmp",
9164
+ "pull-http",
9165
+ "pull-flv",
9166
+ "pull-rfc4571",
9167
+ "push-annexb",
9168
+ "derived"
9169
+ ]);
9170
+ var CamStreamResolutionSchema = object({
9171
+ width: number().int().positive().meta(readingMeta("none")),
9172
+ height: number().int().positive().meta(readingMeta("none"))
9173
+ });
9174
+ var CameraStreamSchema = object({
9175
+ /** Stable, provider-assigned id unique within the (deviceId) scope. */
9176
+ camStreamId: string().min(1),
9177
+ deviceId: number().int().nonnegative(),
9178
+ kind: CamStreamKindSchema,
9179
+ /** Required for pull-* kinds. Ignored for push-annexb. */
9180
+ url: string().optional(),
9181
+ codec: string().optional(),
9182
+ resolution: CamStreamResolutionSchema.optional(),
9183
+ fps: number().positive().optional(),
9184
+ /** Human label surfaced in the Admin UI "Camera Stream" dropdown. */
9185
+ label: string().optional(),
9186
+ /**
9187
+ * Device-level features the publisher advertised (e.g. `battery-operated`).
9188
+ * The broker, snapshot orchestrator, and prebuffer manager all consult
9189
+ * this list to derive policy — relaxed stall watchdog for battery
9190
+ * cams, prebuffer off by default, longer snapshot rate-limit, etc.
9191
+ *
9192
+ * Single source of truth replacing per-stream flags like the
9193
+ * historical `allowStall`: if the publisher knows the camera is
9194
+ * battery-powered, every downstream service derives the right policy
9195
+ * from this list.
9196
+ */
9197
+ deviceFeatures: array(string()).optional(),
9198
+ /**
9199
+ * Whether this stream participates in the broker's automatic profile
9200
+ * assignment (`computeInitialAssignment`). Defaults to `true`. Publishers
9201
+ * use `false` when they want a stream to be SELECTABLE in the UI but not
9202
+ * picked by default — e.g. Reolink publishes its native Baichuan streams
9203
+ * as `autoEligible: true` (the recommended path) and its RTSP / RTMP
9204
+ * mirrors as `autoEligible: false` (still pickable per slot, just not
9205
+ * the auto choice). Manual `assignProfile` calls remain valid for
9206
+ * non-eligible streams.
9207
+ */
9208
+ autoEligible: boolean().optional(),
9209
+ /**
9210
+ * The profile slot the PUBLISHER says this stream is: `low` for a stream
9211
+ * whose only sensible role is the low-bandwidth one, `high` for a main
9212
+ * stream. Consulted by the broker's automatic ranking
9213
+ * (`rankInitialAssignment`) BEFORE the pixel-count heuristic, and only there:
9214
+ * an operator's manual assignment still wins, and an absent hint is the
9215
+ * pixel-ranked default every camera had before this field existed.
9216
+ *
9217
+ * Why it exists: a camera with ONE stream ranked to `mid` — the slot no
9218
+ * storage class is written for on its own (`recordings` is high|mid,
9219
+ * `recordingsLow` is low) and the one profile scrub does not read. The robot
9220
+ * camera's single 720p relay is a `low` in every respect except the slot the
9221
+ * heuristic put it in.
9222
+ */
9223
+ profileHint: CamProfileSchema.optional(),
9224
+ /**
9225
+ * Transport-specific opaque metadata. The broker passes it through to
9226
+ * the source reader without inspecting it. Currently used by
9227
+ * `pull-rfc4571` streams to carry the upstream SDP (so the reader can
9228
+ * route RTP packets to the right depacketizer without an in-band
9229
+ * DESCRIBE phase). Other kinds typically leave it undefined.
9230
+ */
9231
+ metadata: record(string(), unknown()).optional()
9232
+ });
9233
+ var ProfileSlotStatusSchema = _enum([
9234
+ "unassigned",
9235
+ "idle",
9236
+ "connecting",
9237
+ "streaming",
9238
+ "error"
9239
+ ]);
9240
+ var ProfileSlotSchema = object({
9241
+ deviceId: number().int().nonnegative(),
9242
+ profile: CamProfileSchema,
9243
+ /** Broker id the rest of the system addresses: `${deviceId}/${profile}`. */
9244
+ brokerId: string(),
9245
+ /** `null` when the profile is unassigned. */
9246
+ sourceCamStreamId: string().nullable(),
9247
+ status: ProfileSlotStatusSchema,
9248
+ resolution: CamStreamResolutionSchema.optional(),
9249
+ codec: string().optional(),
9250
+ preBufferSec: number().nonnegative().optional(),
9251
+ errorMessage: string().optional()
9252
+ });
9253
+ /**
9254
+ * Zod schema for StreamSourceEntry — the canonical stream descriptor
9255
+ * exposed by ICameraDevice.getStreamSources() and consumed by the broker.
9256
+ */
9257
+ var StreamSourceEntrySchema$1 = object({
9258
+ id: string(),
9259
+ label: string(),
9260
+ protocol: _enum([
9261
+ "rtsp",
9262
+ "rtmp",
9263
+ "annexb",
9264
+ "http-mjpeg",
9265
+ "webrtc",
9266
+ "custom"
9267
+ ]),
9268
+ url: string().optional(),
9269
+ resolution: object({
9270
+ width: number(),
9271
+ height: number()
9272
+ }).readonly().optional(),
9273
+ fps: number().optional(),
9274
+ bitrate: number().optional(),
9275
+ codec: string().optional(),
9276
+ profileHint: CamProfileSchema.optional()
9277
+ });
9278
+ object({
9279
+ type: string(),
9280
+ url: string(),
9281
+ videoCodec: string().optional(),
9282
+ audioCodec: string().optional(),
9283
+ metadata: record(string(), unknown()).readonly().optional()
9284
+ });
9285
+ /**
9286
+ * Wire schema for `FrameHandle` — a zero-pixel, serialisable reference to a
9287
+ * frame in a shared-memory ring (Phase 5 / D9). Mirrors the `FrameHandle` TS
9288
+ * interface in `interfaces/frame-handle.ts` field-for-field so a cap method
9289
+ * (the runner's native-crop and pipeline-executor inputs) can carry handles
9290
+ * over tRPC / Moleculer.
9291
+ *
9292
+ * The `satisfies` assertion below pins this schema to the interface: if a
9293
+ * field is added to / removed from `FrameHandle` without a matching schema
9294
+ * edit, `z.infer<typeof FrameHandleSchema>` stops being assignable to
9295
+ * `FrameHandle` and the build fails — no silent drift.
9296
+ */
9297
+ var FrameHandleSchema = object({
9298
+ shmId: string(),
9299
+ slot: number().int().nonnegative(),
9300
+ seq: number().int().nonnegative(),
9301
+ width: number().int().positive(),
9302
+ height: number().int().positive(),
9303
+ format: _enum([
9304
+ "jpeg",
9305
+ "rgb",
9306
+ "bgr",
9307
+ "yuv420",
9308
+ "gray"
9309
+ ]),
9310
+ pts: number(),
9311
+ byteLength: number().int().nonnegative(),
9312
+ nodeId: string(),
9313
+ slotCount: number().int().positive()
9314
+ });
9315
+ /**
9316
+ * Wire schema for a decoded audio chunk (Phase 5 / D9). Mirrors the
9317
+ * `DecodedAudioChunk` TS interface in `interfaces/stream-broker.ts`: PCM
9318
+ * sample bytes plus the track parameters a consumer needs to interpret them.
9319
+ *
9320
+ * Audio chunks are tiny (a ~500ms AudioCodecSession window is a few KB of
9321
+ * Float32 PCM), so unlike video frames they ship their bytes INLINE over
9322
+ * tRPC / Moleculer — no shared-memory plane. `data` is typed `Uint8Array`
9323
+ * (the wire-serialisable supertype of `Buffer`); a `Buffer` is assignable to
9324
+ * it.
9325
+ */
9326
+ var AudioChunkFormatSchema = _enum(AUDIO_CHUNK_FORMATS);
9327
+ var DecodedAudioChunkSchema = object({
9328
+ data: _instanceof(Uint8Array),
9329
+ sampleRate: number().int().positive(),
9330
+ channels: number().int().positive(),
9331
+ timestamp: number(),
9332
+ /**
9333
+ * Byte format of `data`. ABSENT MEANS `f32le` — today's bytes, byte for
9334
+ * byte, for any peer that never heard of this field. A coded window
9335
+ * (`pcmu` / `pcma`, one byte per sample) is only ever emitted to a
9336
+ * subscription that DECLARED it accepts one, so absence can never mean
9337
+ * "coded bytes a consumer will read as floats" (D455).
9338
+ */
9339
+ format: AudioChunkFormatSchema.optional()
9340
+ });
9341
+ /**
9342
+ * Input for `stream-broker.subscribeAudioChunks` (Phase 5 / D9). Audio ships
9343
+ * its bytes inline: a consumer asks the broker for the decoded audio-chunk
9344
+ * stream of one broker; the broker keeps a per-subscription bounded FIFO queue
9345
+ * the consumer drains via `pullAudioChunks`.
9346
+ */
9347
+ var SubscribeAudioChunksInputSchema = object({
9348
+ brokerId: string(),
9349
+ /** Short caller-identity tag (`audio-analyzer`, …) for `listClients`. */
9350
+ tag: string().optional(),
9351
+ /**
9352
+ * Byte formats this subscriber can READ, best first. The broker serves the
9353
+ * chunk's own format when it is in this list and expands to `f32le`
9354
+ * otherwise, so a subscriber is never handed bytes it cannot interpret.
9355
+ *
9356
+ * Absent (or without the source format) means `f32le` — the behaviour every
9357
+ * subscriber had before D455, unchanged. This is the negotiation half of
9358
+ * the source-bytes lever: it is what lets the broker and its consumers
9359
+ * deploy one at a time across three nodes.
9360
+ */
9361
+ accept: array(AudioChunkFormatSchema).readonly().optional()
9362
+ });
9363
+ /** Result of `stream-broker.subscribeAudioChunks`. */
9364
+ var SubscribeAudioChunksResultSchema = object({
9365
+ /** Opaque id passed to `pullAudioChunks` / `unsubscribeAudioChunks`. */
9366
+ subscriptionId: string() });
9367
+ var BrokerStatsSchema = object({
9368
+ status: _enum([
9369
+ "idle",
9370
+ "connecting",
9371
+ "streaming",
9372
+ "error",
9373
+ "stopped"
9374
+ ]),
9375
+ inputFps: number(),
9376
+ encodedSubscribers: number(),
9377
+ uptimeMs: number(),
9378
+ bitrateKbps: number(),
9379
+ idrIntervalMs: number(),
9380
+ codec: string().optional(),
9381
+ totalBytes: number(),
9382
+ packetCount: number(),
9383
+ rtspClients: number(),
9384
+ pipeClients: number(),
9385
+ preBufferSec: number(),
9386
+ preBufferMs: number(),
9387
+ preBufferPackets: number(),
9388
+ /**
9389
+ * Detected audio track parameters from the RTSP DESCRIBE / SDP. `null`
9390
+ * when the stream has no audio track or the broker is in cold start.
9391
+ * `supported = false` means the codec was detected but the local
9392
+ * decoder pipeline cannot produce PCM chunks (e.g. AAC without the
9393
+ * AAC pipeline wired). Surfaced in the UI device-overview so operators
9394
+ * can pre-pick the audio-analysis model that matches the codec.
9395
+ */
9396
+ audio: object({
9397
+ codec: string(),
9398
+ sampleRate: number(),
9399
+ channels: number(),
9400
+ supported: boolean()
9401
+ }).nullable().optional(),
9402
+ /**
9403
+ * BROKER-SIDE AUDIO MUTE (D83). `true` = this broker is deliberately
9404
+ * distributing none of the device's audio, on live or recording.
9405
+ *
9406
+ * Present so a silent camera can be told apart from a broken one on the
9407
+ * stream panel itself, without cross-referencing the switch group: a
9408
+ * broker holding an `audio` track descriptor while `audioMuted` is true is
9409
+ * working exactly as asked. `audioMutedDropped` counts the audio units
9410
+ * thrown away since the current dial — it is how you confirm from stats
9411
+ * alone that the mute is on the packet path and not merely persisted.
9412
+ */
9413
+ audioMuted: boolean().optional(),
9414
+ audioMutedDropped: number().optional()
9415
+ });
9416
+ /**
9417
+ * Exporter-facing "profile restream" entry. Returned by
9418
+ * `cameraStreams.getProfileRtspEntries` — one per ASSIGNED profile slot
9419
+ * (high/mid/low). `brokerId` is the PROFILE-keyed broker id
9420
+ * (`${deviceId}/${profile}`, e.g. `15/high`); its `url` is a broker
9421
+ * RTSP restream that aliases the profile's assigned source broker, so
9422
+ * every consumer dialling a profile converges on the same single pull.
9423
+ * `codec`/`resolution` describe the assigned SOURCE camStream (for the
9424
+ * `auto` resolution-closest pick). Distinct from `RtspRestreamEntry`
9425
+ * (raw per-camStream, live-view only).
9426
+ */
9427
+ var ProfileRtspEntrySchema = object({
9428
+ profile: CamProfileSchema,
9429
+ /** Profile-keyed broker id, format `${deviceId}/${profile}` (e.g. `"15/high"`). */
9430
+ brokerId: string(),
9431
+ url: string(),
9432
+ mutedUrl: string(),
9433
+ enabled: boolean(),
9434
+ codec: string().optional(),
9435
+ resolution: CamStreamResolutionSchema.optional()
9436
+ });
9437
+ /**
9438
+ * The model weight formats a catalog entry can ship. Schema-free on purpose:
9439
+ * `types/models.ts` builds Zod schemas from it, but a runner-closure consumer
9440
+ * (the model downloader in `@camstack/server/system`) needs only the list and must
9441
+ * reach it through `@camstack/types/addon` without building the cap schema
9442
+ * graph (D28).
9443
+ */
9444
+ var MODEL_FORMATS = [
9445
+ "onnx",
9446
+ "coreml",
9447
+ "openvino",
9448
+ "tflite",
9449
+ "pt",
9450
+ "gguf"
9451
+ ];
8954
9452
  /**
8955
9453
  * Per-call node pinning for `ctx.api` capability calls.
8956
9454
  *
@@ -9210,6 +9708,7 @@ var CAP_NAMES = Object.freeze([
9210
9708
  "device-ops",
9211
9709
  "device-provider",
9212
9710
  "device-removal-hook",
9711
+ "device-runs",
9213
9712
  "device-state",
9214
9713
  "device-status",
9215
9714
  "dishwasher",
@@ -9251,6 +9750,7 @@ var CAP_NAMES = Object.freeze([
9251
9750
  "mesh-network",
9252
9751
  "meter",
9253
9752
  "metrics-provider",
9753
+ "migrations",
9254
9754
  "model-convert",
9255
9755
  "model-distributor",
9256
9756
  "motion",
@@ -11153,379 +11653,7 @@ object({
11153
11653
  params: record(string(), union([string(), number()])).optional()
11154
11654
  });
11155
11655
  //#endregion
11156
- //#region ../types/dist/err-msg-BD5buxvT.mjs
11157
- var DeviceType = /* @__PURE__ */ function(DeviceType) {
11158
- DeviceType["Camera"] = "camera";
11159
- DeviceType["Hub"] = "hub";
11160
- DeviceType["Light"] = "light";
11161
- DeviceType["Siren"] = "siren";
11162
- DeviceType["Switch"] = "switch";
11163
- DeviceType["Sensor"] = "sensor";
11164
- DeviceType["Thermostat"] = "thermostat";
11165
- /** Air-conditioner / heat-pump climate device (HVAC) — shares the
11166
- * `climate-control` cap surface with `Thermostat` but renders a
11167
- * dedicated AC-appropriate control UI (mode chips, fan speed,
11168
- * independent vertical/horizontal swing). Sources: native Gree, and
11169
- * reusable by other AC integrations. */
11170
- DeviceType["Climate"] = "climate";
11171
- DeviceType["Button"] = "button";
11172
- /** Generic stateless event emitter — carries a device's EXACT declared
11173
- * event vocabulary verbatim (no normalization). Installed with the
11174
- * `event-emitter` cap. Sources: HA `event.*` entities (structured) and
11175
- * HA bus events (e.g. `zha_event`, generic). */
11176
- DeviceType["EventEmitter"] = "event-emitter";
11177
- /** Firmware/software update entity — current vs available version,
11178
- * updatable flag, update state, and an install action. Installed with
11179
- * the `update` cap. Sources: Homematic firmware-update channels (and
11180
- * reusable by other providers, e.g. HA `update.*` entities). */
11181
- DeviceType["Update"] = "update";
11182
- DeviceType["Generic"] = "generic";
11183
- /** Generic notification delivery target (HA `notify.<service>`, future
11184
- * Telegram / Discord / ntfy / SMTP, …). One device per delivery
11185
- * endpoint; the `notifier` cap defines the send surface. */
11186
- DeviceType["Notifier"] = "notifier";
11187
- /** Pre-recorded action sequence with optional parameters
11188
- * (HA `script.*`). Runnable via `script-runner` cap. */
11189
- DeviceType["Script"] = "script";
11190
- /** Automation rule (HA `automation.*`) — enable/disable + manual
11191
- * trigger surface exposed via `automation-control` cap. */
11192
- DeviceType["Automation"] = "automation";
11193
- /** Door / smart lock device (HA `lock.*`). `lock-control` cap. */
11194
- DeviceType["Lock"] = "lock";
11195
- /** Window covering, blinds, garage door, valve, etc. (HA `cover.*`,
11196
- * `valve.*`). `cover` cap with sub-roles for variant. */
11197
- DeviceType["Cover"] = "cover";
11198
- /** Pipe / water / gas valve with open/close/stop and optional
11199
- * position (HA `valve.*`). `valve` cap — a cover-sibling actuator
11200
- * modelled on the same open/closed lifecycle. */
11201
- DeviceType["Valve"] = "valve";
11202
- /** Humidifier / dehumidifier with on/off + target humidity + mode
11203
- * (HA `humidifier.*`). `humidifier` cap — a climate-family actuator
11204
- * modelled on the same target / mode lifecycle. */
11205
- DeviceType["Humidifier"] = "humidifier";
11206
- /** Water heater / boiler with target temperature + operation mode +
11207
- * away mode (HA `water_heater.*`). `water-heater` cap — a
11208
- * climate-family actuator. */
11209
- DeviceType["WaterHeater"] = "water-heater";
11210
- /** Ceiling / standing / exhaust fan (HA `fan.*`). `fan-control` cap. */
11211
- DeviceType["Fan"] = "fan";
11212
- /** Audio / video playback endpoint (HA `media_player.*`). Disjoint from
11213
- * the camera surface — those use `Camera`. `media-player` cap. */
11214
- DeviceType["MediaPlayer"] = "media-player";
11215
- /** Security panel / alarm system (HA `alarm_control_panel.*`).
11216
- * `alarm-panel` cap. */
11217
- DeviceType["AlarmPanel"] = "alarm-panel";
11218
- /** Generic user-settable input (HA `number` / `input_number` / `select`
11219
- * / `input_select` / `text` / `input_text` / `input_datetime`).
11220
- * Sub-type via `DeviceRole`: NumericControl / SelectControl /
11221
- * TextControl / DateTimeControl. */
11222
- DeviceType["Control"] = "control";
11223
- /** Person / device-tracker presence (HA `person.*`, `device_tracker.*`).
11224
- * `presence` cap. */
11225
- DeviceType["Presence"] = "presence";
11226
- /** Weather provider (HA `weather.*`). Tier-3, low MVP priority.
11227
- * `weather` cap. */
11228
- DeviceType["Weather"] = "weather";
11229
- /** Robot vacuum (HA `vacuum.*`). Tier-3. `vacuum-control` cap. */
11230
- DeviceType["Vacuum"] = "vacuum";
11231
- /** Robotic lawn mower (HA `lawn_mower.*`). Tier-3.
11232
- * `lawn-mower-control` cap. */
11233
- DeviceType["LawnMower"] = "lawn-mower";
11234
- /** Physical HA device group — parent container for entity-children
11235
- * adopted from a single HA device entry. Not renderable as a
11236
- * standalone device; exists only to anchor child entities. */
11237
- DeviceType["Container"] = "container";
11238
- /** Single still-image entity (HA `image.*`). Read-only display of an
11239
- * `entity_picture` signed URL the browser loads directly. `image` cap. */
11240
- DeviceType["Image"] = "image";
11241
- /** Smart pet feeder — cloud-connected food dispenser with a bowl food
11242
- * level, battery, desiccant life, feeding state and manual-feed /
11243
- * call-pet / maintenance actions. Installed with the `pet-feeder` cap;
11244
- * dual-hopper models (D4S/D4SH) expose per-hopper portions. Sources:
11245
- * native PetKit (`nodepetkit` `FeederDevice`), reusable by other feeder
11246
- * integrations sharing the same food/desiccant/hopper surface. */
11247
- DeviceType["PetFeeder"] = "pet-feeder";
11248
- /** Pet water fountain — tank level, water drunk, pump and its duty cycle.
11249
- * Installed with the `pet-fountain` cap. Reaches CamStack as loose entities
11250
- * of a container and is BUILT as a composed device (D967). */
11251
- DeviceType["PetFountain"] = "pet-fountain";
11252
- /** Built-in dishwasher — program lifecycle, door / remote-start gates,
11253
- * salt and rinse-aid levels. Installed with the `dishwasher` cap.
11254
- * Sources: native Bosch Home Connect, reusable by similar integrations.
11255
- * A plug-built dishwasher carries running-state and power instead. */
11256
- DeviceType["Dishwasher"] = "dishwasher";
11257
- /** Washing machine. A plug-built one is running while its power stays
11258
- * above a threshold. No program cap — that surface is the dishwasher's. */
11259
- DeviceType["Washer"] = "washer";
11260
- /** Tumble dryer. Same plug-built running surface as {@link Washer}. */
11261
- DeviceType["Dryer"] = "dryer";
11262
- /** The physical bridge box of a vendor ecosystem (Velux KLF-200, and later
11263
- * other radio-to-IP bridges). It carries the box's own facts (firmware,
11264
- * address, connection state) and the actions the box really supports
11265
- * (`reboot`, `device-admin-link`). Products are NOT its children — they are
11266
- * adopted beside it — and its connection settings stay on the integration. */
11267
- DeviceType["Gateway"] = "gateway";
11268
- return DeviceType;
11269
- }({});
11270
- var DeviceFeature = /* @__PURE__ */ function(DeviceFeature) {
11271
- DeviceFeature["BatteryOperated"] = "battery-operated";
11272
- DeviceFeature["Rebootable"] = "rebootable";
11273
- /**
11274
- * Device supports an on-demand re-sync of its derived spec with its
11275
- * upstream source — drives the generic Re-sync button. The owning
11276
- * provider implements the action via the `device-adoption.resync` cap.
11277
- */
11278
- DeviceFeature["Resyncable"] = "resyncable";
11279
- DeviceFeature["NativeSnapshot"] = "native-snapshot";
11280
- DeviceFeature["DoorbellButton"] = "doorbell-button";
11281
- DeviceFeature["TwoWayAudio"] = "two-way-audio";
11282
- DeviceFeature["PanTiltZoom"] = "pan-tilt-zoom";
11283
- /**
11284
- * Camera supports the on-firmware autotrack subsystem (subject-
11285
- * following). Distinct from `PanTiltZoom` because not every PTZ
11286
- * camera ships autotrack — the admin UI uses this flag to gate
11287
- * the autotrack toggle / settings card without re-deriving from
11288
- * the cap registry. Mirrors `ptz-autotrack` cap registration:
11289
- * driver sets this feature when probe confirms the firmware
11290
- * surface, and registers the cap in the same code path.
11291
- */
11292
- DeviceFeature["PtzAutotrack"] = "ptz-autotrack";
11293
- /**
11294
- * Accessory exposes a "trigger on motion" toggle — the parent camera's
11295
- * motion detection automatically activates this device. Mirrors
11296
- * `motion-trigger` cap registration: drivers set this feature in the
11297
- * same code path that calls `ctx.registerNativeCap(motionTriggerCapability, ...)`.
11298
- *
11299
- * Used by admin UI (gate the in-hero `MotionTriggerToggle` against a
11300
- * fast scalar without binding fetch), notifier rules, and `listAll`
11301
- * filters that want "all devices with on-motion behaviour".
11302
- */
11303
- DeviceFeature["MotionTrigger"] = "motion-trigger";
11304
- /** Light supports rgb-triplet color via `color` cap. */
11305
- DeviceFeature["LightColorRgb"] = "light-color-rgb";
11306
- /** Light supports HSV color via `color` cap. */
11307
- DeviceFeature["LightColorHsv"] = "light-color-hsv";
11308
- /** Light supports color-temperature (mired) via `color` cap. */
11309
- DeviceFeature["LightColorMired"] = "light-color-mired";
11310
- /** Thermostat supports a `heat_cool` dual setpoint (targetLow +
11311
- * targetHigh). Gates the range slider UI. */
11312
- DeviceFeature["ClimateDualSetpoint"] = "climate-dual-setpoint";
11313
- /** Thermostat exposes target humidity and/or current humidity
11314
- * readings. Gates the humidity controls. */
11315
- DeviceFeature["ClimateHumidity"] = "climate-humidity";
11316
- /** Thermostat exposes a fan-mode selector. */
11317
- DeviceFeature["ClimateFanMode"] = "climate-fan-mode";
11318
- /** Thermostat exposes preset modes (eco / away / sleep / vendor). */
11319
- DeviceFeature["ClimatePreset"] = "climate-preset";
11320
- /** Thermostat exposes a vertical louver swing toggle. Gates the
11321
- * vertical-swing switch in the climate UI. Independent of horizontal. */
11322
- DeviceFeature["ClimateSwingVertical"] = "climate-swing-vertical";
11323
- /** Thermostat exposes a horizontal louver swing toggle. Gates the
11324
- * horizontal-swing switch in the climate UI. Independent of vertical. */
11325
- DeviceFeature["ClimateSwingHorizontal"] = "climate-swing-horizontal";
11326
- /** Cover exposes intermediate position control (0..100). Gates the
11327
- * position slider UI. */
11328
- DeviceFeature["CoverPositionable"] = "cover-positionable";
11329
- /** Cover exposes slat-tilt control. Gates the tilt slider UI. */
11330
- DeviceFeature["CoverTilt"] = "cover-tilt";
11331
- /** Valve exposes intermediate position control (0..100). Gates the
11332
- * position slider / drag surface UI. */
11333
- DeviceFeature["ValvePositionable"] = "valve-positionable";
11334
- /** Fan exposes a speed-percentage setter. Gates the speed slider UI. */
11335
- DeviceFeature["FanSpeed"] = "fan-speed";
11336
- /** Fan exposes a preset mode selector. */
11337
- DeviceFeature["FanPreset"] = "fan-preset";
11338
- /** Fan exposes blade direction (forward/reverse) — typical of
11339
- * ceiling fans. */
11340
- DeviceFeature["FanDirection"] = "fan-direction";
11341
- /** Fan exposes an oscillation toggle. */
11342
- DeviceFeature["FanOscillating"] = "fan-oscillating";
11343
- /** Lock requires a PIN code on lock/unlock. Gates the code-entry
11344
- * field on the UI lock-controls panel. */
11345
- DeviceFeature["LockPinRequired"] = "lock-pin-required";
11346
- /** Lock supports a latch-release ("open door") action distinct from
11347
- * unlock. Mirrors HA `LockEntityFeature.OPEN` (bit 1) in
11348
- * `supported_features`. Gates the Open Door button in the UI. */
11349
- DeviceFeature["LockOpen"] = "lock-open";
11350
- /** Media player exposes a seek-to-position surface. */
11351
- DeviceFeature["MediaPlayerSeek"] = "media-player-seek";
11352
- /** Media player exposes a volume-level setter. */
11353
- DeviceFeature["MediaPlayerVolume"] = "media-player-volume";
11354
- /** Media player exposes a mute toggle distinct from volume=0. */
11355
- DeviceFeature["MediaPlayerMute"] = "media-player-mute";
11356
- /** Media player exposes a shuffle toggle. */
11357
- DeviceFeature["MediaPlayerShuffle"] = "media-player-shuffle";
11358
- /** Media player exposes a repeat mode (off / all / one). */
11359
- DeviceFeature["MediaPlayerRepeat"] = "media-player-repeat";
11360
- /** Media player exposes a source / input selector. */
11361
- DeviceFeature["MediaPlayerSelectSource"] = "media-player-select-source";
11362
- /** Media player exposes a play-arbitrary-media surface (URL / id). */
11363
- DeviceFeature["MediaPlayerPlayMedia"] = "media-player-play-media";
11364
- /** Media player exposes next-track. */
11365
- DeviceFeature["MediaPlayerNext"] = "media-player-next";
11366
- /** Media player exposes previous-track. */
11367
- DeviceFeature["MediaPlayerPrevious"] = "media-player-previous";
11368
- /** Media player exposes stop distinct from pause. */
11369
- DeviceFeature["MediaPlayerStop"] = "media-player-stop";
11370
- /** Alarm panel requires a PIN code on arm/disarm. */
11371
- DeviceFeature["AlarmPinRequired"] = "alarm-pin-required";
11372
- /** Presence device carries GPS coordinates (lat/lng/accuracy) in
11373
- * addition to a textual location. */
11374
- DeviceFeature["PresenceGps"] = "presence-gps";
11375
- /** Notifier accepts an inline / URL image attachment. */
11376
- DeviceFeature["NotifierImage"] = "notifier-image";
11377
- /** Notifier accepts a priority hint (high/normal/low). */
11378
- DeviceFeature["NotifierPriority"] = "notifier-priority";
11379
- /** Notifier accepts a free-form `data` payload for platform-specific
11380
- * fields. */
11381
- DeviceFeature["NotifierData"] = "notifier-data";
11382
- /** Notifier supports interactive action buttons / callbacks. */
11383
- DeviceFeature["NotifierActions"] = "notifier-actions";
11384
- /** Notifier supports per-call recipient targeting (multi-user). */
11385
- DeviceFeature["NotifierRecipients"] = "notifier-recipients";
11386
- /** Script runner accepts a variables map on each run invocation. */
11387
- DeviceFeature["ScriptVariables"] = "script-variables";
11388
- /** Automation `trigger` accepts a skipCondition flag — fires the
11389
- * automation's actions while bypassing its condition block. */
11390
- DeviceFeature["AutomationSkipCondition"] = "automation-skip-condition";
11391
- /** Robot vacuum exposes a live cleaning map (image child). Gates the
11392
- * map tile in the vacuum UI. */
11393
- DeviceFeature["VacuumHasMap"] = "vacuum-has-map";
11394
- /** Robot vacuum exposes AI obstacle-detection toggles. Gates the AI
11395
- * switches group. */
11396
- DeviceFeature["VacuumHasAi"] = "vacuum-has-ai";
11397
- /** Robot mower exposes a live mowing map (SVG image child). Gates the
11398
- * mower map tile. */
11399
- DeviceFeature["MowerHasMap"] = "mower-has-map";
11400
- /** Robot mower exposes targeted mowing (all-area / zones / edges /
11401
- * spots) — gates the Mow action + map selectors. */
11402
- DeviceFeature["MowerHasTargetedMowing"] = "mower-has-targeted-mowing";
11403
- return DeviceFeature;
11404
- }({});
11405
- /**
11406
- * Semantic role a device plays within its parent. Populated by driver
11407
- * addons when creating accessory devices (Reolink siren/floodlight/
11408
- * PIR/chime/autotrack/doorbell, ONVIF relay outputs, …). Used by the
11409
- * admin UI to pick icons, labels, and widgets — a `Switch` with
11410
- * `role: Floodlight` renders as a bulb with a brightness slider,
11411
- * whereas a `Switch` with `role: Siren` renders as a klaxon.
11412
- *
11413
- * Undefined for top-level devices (cameras, NVRs, hubs). Persisted in
11414
- * sqlite as a nullable TEXT column — old rows keep working unchanged.
11415
- */
11416
- var DeviceRole = /* @__PURE__ */ function(DeviceRole) {
11417
- DeviceRole["Siren"] = "siren";
11418
- DeviceRole["Floodlight"] = "floodlight";
11419
- DeviceRole["Spotlight"] = "spotlight";
11420
- DeviceRole["PirSensor"] = "pir-sensor";
11421
- DeviceRole["Chime"] = "chime";
11422
- DeviceRole["Autotrack"] = "autotrack";
11423
- DeviceRole["Nightvision"] = "nightvision";
11424
- DeviceRole["PrivacyMask"] = "privacy-mask";
11425
- DeviceRole["Doorbell"] = "doorbell";
11426
- /** Interior blind / shade / roller blind. HA `device_class: blind|shade`. */
11427
- DeviceRole["CoverBlind"] = "cover-blind";
11428
- /** Exterior roller shutter. HA `device_class: shutter`; Homematic BROLL. */
11429
- DeviceRole["CoverShutter"] = "cover-shutter";
11430
- /** Exterior awning. HA `device_class: awning`. */
11431
- DeviceRole["CoverAwning"] = "cover-awning";
11432
- /** Curtain / drape. HA `device_class: curtain`. */
11433
- DeviceRole["CoverCurtain"] = "cover-curtain";
11434
- /** Garage door. HA `device_class: garage`. A security actuator: exports gate it. */
11435
- DeviceRole["CoverGarage"] = "cover-garage";
11436
- /** Gate. HA `device_class: gate`. A security actuator: exports gate it. */
11437
- DeviceRole["CoverGate"] = "cover-gate";
11438
- /** Door. HA `device_class: door`. A security actuator: exports gate it. */
11439
- DeviceRole["CoverDoor"] = "cover-door";
11440
- /** Window. HA `device_class: window`. */
11441
- DeviceRole["CoverWindow"] = "cover-window";
11442
- /** Virtual HA toggle (input_boolean.*) — distinguishable from a
11443
- * real Switch device for UI rendering / export adapters. */
11444
- DeviceRole["BinaryHelper"] = "binary-helper";
11445
- /** A smart plug: a `switch` whose source says it is an outlet (HA
11446
- * `device_class: outlet`). The Alexa export shows it as a SMARTPLUG. */
11447
- DeviceRole["Outlet"] = "outlet";
11448
- /** Generic motion / occupancy / moving event source. Distinct from
11449
- * the camera accessory PirSensor role: that one is a camera child;
11450
- * this is a standalone HA / 3rd-party motion sensor. */
11451
- DeviceRole["MotionSensor"] = "motion-sensor";
11452
- DeviceRole["ContactSensor"] = "contact-sensor";
11453
- DeviceRole["LeakSensor"] = "leak-sensor";
11454
- DeviceRole["SmokeSensor"] = "smoke-sensor";
11455
- DeviceRole["COSensor"] = "co-sensor";
11456
- DeviceRole["GasSensor"] = "gas-sensor";
11457
- DeviceRole["TamperSensor"] = "tamper-sensor";
11458
- DeviceRole["VibrationSensor"] = "vibration-sensor";
11459
- DeviceRole["ConnectivitySensor"] = "connectivity-sensor";
11460
- DeviceRole["SoundSensor"] = "sound-sensor";
11461
- /** Fallback for `binary_sensor` without a known `device_class`. */
11462
- DeviceRole["BinarySensor"] = "binary-sensor";
11463
- DeviceRole["TemperatureSensor"] = "temperature-sensor";
11464
- DeviceRole["HumiditySensor"] = "humidity-sensor";
11465
- DeviceRole["AmbientLightSensor"] = "ambient-light-sensor";
11466
- DeviceRole["PressureSensor"] = "pressure-sensor";
11467
- /** Wind speed or direction (weather-station `wind-sensor` cap). */
11468
- DeviceRole["WindSensor"] = "wind-sensor";
11469
- /** Rain accumulation or rate (weather-station `rain-sensor` cap). */
11470
- DeviceRole["RainSensor"] = "rain-sensor";
11471
- /** UV index (weather-station `uv-sensor` cap). */
11472
- DeviceRole["UvSensor"] = "uv-sensor";
11473
- /** Solar irradiance W/m² (weather-station `solar-radiation-sensor` cap).
11474
- * Distinct from AmbientLightSensor (lux). */
11475
- DeviceRole["SolarRadiationSensor"] = "solar-radiation-sensor";
11476
- /** Soil moisture % (garden/weather `soil-moisture-sensor` cap). */
11477
- DeviceRole["SoilMoistureSensor"] = "soil-moisture-sensor";
11478
- DeviceRole["PowerSensor"] = "power-sensor";
11479
- DeviceRole["EnergySensor"] = "energy-sensor";
11480
- DeviceRole["VoltageSensor"] = "voltage-sensor";
11481
- DeviceRole["CurrentSensor"] = "current-sensor";
11482
- DeviceRole["AirQualitySensor"] = "air-quality-sensor";
11483
- /** Battery level (numeric % via `sensor` OR low-bool via
11484
- * `binary_sensor` — the cap distinguishes via the value type). */
11485
- DeviceRole["BatterySensor"] = "battery-sensor";
11486
- /** Fallback for `sensor` numeric without a known `device_class`. */
11487
- DeviceRole["NumericSensor"] = "numeric-sensor";
11488
- /** String / enum state (HA `sensor` with `state_class: enum` or
11489
- * `attributes.options`). */
11490
- DeviceRole["EnumSensor"] = "enum-sensor";
11491
- /** Date / timestamp state (HA `sensor` with `device_class: timestamp`
11492
- * or `date`). The slice carries the raw ISO string verbatim (hosted on
11493
- * the `enum-sensor` cap); the UI renders it locale-formatted. */
11494
- DeviceRole["DateTimeSensor"] = "datetime-sensor";
11495
- /** Last-resort fallback when nothing else matches. */
11496
- DeviceRole["GenericSensor"] = "generic-sensor";
11497
- DeviceRole["NumericControl"] = "numeric-control";
11498
- DeviceRole["SelectControl"] = "select-control";
11499
- DeviceRole["TextControl"] = "text-control";
11500
- DeviceRole["DateTimeControl"] = "datetime-control";
11501
- /** Mobile push notifier (HA `notify.mobile_app_*`) — supports
11502
- * rich features (image, priority, channel routing). */
11503
- DeviceRole["MobilePushNotifier"] = "mobile-push-notifier";
11504
- /** Chat / messaging service (HA `notify.telegram_*`,
11505
- * `notify.discord_*`, etc.). */
11506
- DeviceRole["MessagingNotifier"] = "messaging-notifier";
11507
- /** Email-based delivery (HA `notify.smtp`, etc.). */
11508
- DeviceRole["EmailNotifier"] = "email-notifier";
11509
- /** Fallback when the notifier service name doesn't match a known
11510
- * pattern. */
11511
- DeviceRole["GenericNotifier"] = "generic-notifier";
11512
- /** Emits a pulse train (infrared or RF) on request — the `ir-transmitter`
11513
- * cap. A `DeviceType.Generic` device. */
11514
- DeviceRole["IrTransmitter"] = "ir-transmitter";
11515
- return DeviceRole;
11516
- }({});
11517
- /**
11518
- import { errMsg } from '@camstack/types'
11519
- * Extract a human-readable message from an unknown error value.
11520
- * Replaces the ubiquitous `errMsg(err)` pattern.
11521
- */
11522
- function errMsg(err) {
11523
- if (err instanceof Error) return err.message;
11524
- if (typeof err === "string") return err;
11525
- return String(err);
11526
- }
11527
- //#endregion
11528
- //#region ../types/dist/composition-C7WdThmP.mjs
11656
+ //#region ../types/dist/composition-BiKWlUCc.mjs
11529
11657
  /**
11530
11658
  * The ffmpeg build every CamStack node downloads, per platform and arch.
11531
11659
  *
@@ -12458,7 +12586,15 @@ var HelperSpecSchema = discriminatedUnion("kind", [
12458
12586
  thresholdWatts: number().positive(),
12459
12587
  startDwellMs: number().int().nonnegative(),
12460
12588
  stopDwellMs: number().int().nonnegative(),
12461
- member: HelperMemberSchema
12589
+ member: HelperMemberSchema,
12590
+ /**
12591
+ * The plug that powers the appliance (`switch.on`). Optional: when set, the
12592
+ * composition carries a `power-source` feature, so the hub can cut and restore
12593
+ * that plug (power-source link of a NEW-target composition).
12594
+ */
12595
+ powerSwitch: HelperMemberSchema.optional(),
12596
+ /** How long a forced restart keeps the plug off; the cap's default when unset. */
12597
+ offSeconds: number().optional()
12462
12598
  }),
12463
12599
  object({
12464
12600
  kind: literal("statistic"),
@@ -12744,6 +12880,71 @@ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
12744
12880
  var MediaPresenceOwnerKindSchema = _enum([...EVENT_OWNER_TYPES, "track"]);
12745
12881
  new Set(EVENT_OWNER_TYPES);
12746
12882
  /**
12883
+ * What a published package needs from the rest of the install (D989).
12884
+ *
12885
+ * One published framework package (`@camstack/server`) carries the kernel, the
12886
+ * builtins and the capability contracts, so "which addon runs on which
12887
+ * framework" is a question of version, and the answer is DECLARED by the
12888
+ * package that knows it:
12889
+ *
12890
+ * - an addon says the oldest server it runs on (`requires.server: ">=X"`);
12891
+ * - the server says the oldest addon it works with
12892
+ * (`requires.addons: { "@camstack/addon-x": ">=Y" }`) and the oldest shell
12893
+ * around it (`requires.wrapper: { desktop, image }`);
12894
+ * - a wrapper (the desktop app, the container starter) says which server
12895
+ * range it can host, and reports its own kind and version.
12896
+ *
12897
+ * The publish workflow STAMPS these after the version bump
12898
+ * (`scripts/ci/stamp-requires.mjs`); nobody types a number by hand.
12899
+ *
12900
+ * A requirement that cannot be checked is NOT met: a shell that reports no
12901
+ * version counts as below the minimum, and an unreadable running version is
12902
+ * named in the result (a failed read is never a pass).
12903
+ */
12904
+ /** `>=MAJOR.MINOR.PATCH[-prerelease]` — the only range shape a requirement uses. */
12905
+ var MinVersionSchema = string().regex(/^>=\d+\.\d+\.\d+(-[0-9A-Za-z.-]+)?$/, "a minimum is written \">=X.Y.Z\"");
12906
+ object({
12907
+ /** The oldest `@camstack/server` this package runs on. */
12908
+ server: MinVersionSchema.optional(),
12909
+ /** The oldest installed version of each named addon package. */
12910
+ addons: record(string(), MinVersionSchema).optional(),
12911
+ /** The oldest shell around the server, per shell kind. */
12912
+ wrapper: object({
12913
+ desktop: MinVersionSchema.optional(),
12914
+ image: MinVersionSchema.optional()
12915
+ }).optional()
12916
+ });
12917
+ /** One unmet requirement; each names the versions on both sides. */
12918
+ var UnmetRequirementSchema = discriminatedUnion("kind", [
12919
+ object({
12920
+ kind: literal("server-too-old"),
12921
+ required: string(),
12922
+ /** `null` — the running server version could not be read. */
12923
+ running: string().nullable()
12924
+ }),
12925
+ object({
12926
+ kind: literal("addon-too-old"),
12927
+ packageName: string(),
12928
+ required: string(),
12929
+ /** `null` — the addon is not installed (or its version is unreadable). */
12930
+ running: string().nullable()
12931
+ }),
12932
+ object({
12933
+ kind: literal("wrapper-too-old"),
12934
+ wrapper: _enum(["desktop", "image"]),
12935
+ required: string(),
12936
+ /** `null` — the wrapper reported no version. */
12937
+ running: string().nullable()
12938
+ })
12939
+ ]);
12940
+ var RequiredAddonUpdateSchema = object({
12941
+ packageName: string(),
12942
+ /** The minimum the target server declares. */
12943
+ required: string(),
12944
+ /** What this node has; `null` when the addon is not installed. */
12945
+ installed: string().nullable()
12946
+ });
12947
+ /**
12747
12948
  * Which integration instance a settings form is for. An addon that serves several instances
12748
12949
  * (`instanceMode: multiple`) can show a different form per instance — an NVR integration
12749
12950
  * needs its connection, the default integration needs the addon's own sections.
@@ -13362,6 +13563,619 @@ var CameraSwitchGroupSchema = object({
13362
13563
  fetchedAt: number()
13363
13564
  });
13364
13565
  /**
13566
+ * Query filter for settings-store collections.
13567
+ */
13568
+ var QueryFilterSchema = object({
13569
+ where: record(string(), unknown()).optional(),
13570
+ whereIn: record(string(), array(unknown())).optional(),
13571
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
13572
+ /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
13573
+ whereNot: record(string(), unknown()).optional(),
13574
+ /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
13575
+ * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
13576
+ whereNotIn: record(string(), array(unknown())).optional(),
13577
+ orderBy: object({
13578
+ field: string(),
13579
+ direction: _enum(["asc", "desc"])
13580
+ }).optional(),
13581
+ limit: number().optional(),
13582
+ offset: number().optional()
13583
+ });
13584
+ /**
13585
+ * The predicate half of a filter, for BULK MUTATIONS.
13586
+ *
13587
+ * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
13588
+ * meaning for a statement that rewrites a set, and accepting them would invite
13589
+ * a caller to believe `limit` bounds the damage. Every field is optional here
13590
+ * only so the shape stays composable — the implementation REJECTS a filter
13591
+ * that compiles to no predicate, because that is the whole collection.
13592
+ */
13593
+ var MutationFilterSchema = object({
13594
+ where: record(string(), unknown()).optional(),
13595
+ whereIn: record(string(), array(unknown())).optional(),
13596
+ whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
13597
+ whereNot: record(string(), unknown()).optional(),
13598
+ whereNotIn: record(string(), array(unknown())).optional()
13599
+ });
13600
+ /**
13601
+ * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
13602
+ *
13603
+ * `as` names the slot in the result, so the SAME column may be asked twice with
13604
+ * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
13605
+ * a `Record<column, op>` shape could not express.
13606
+ */
13607
+ var AggregateFieldSchema = object({
13608
+ /** Result key. */
13609
+ as: string().min(1),
13610
+ /** Column to aggregate. Must be a real column of a declared collection. */
13611
+ field: string().min(1),
13612
+ op: _enum([
13613
+ "sum",
13614
+ "min",
13615
+ "max"
13616
+ ])
13617
+ });
13618
+ /**
13619
+ * `COUNT(*)` plus one number per requested field.
13620
+ *
13621
+ * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
13622
+ * that really is 0 are different facts, and an accounting caller that renders
13623
+ * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
13624
+ */
13625
+ var AggregateResultSchema = object({
13626
+ count: number().int(),
13627
+ values: record(string(), number().nullable())
13628
+ });
13629
+ /** A single stored record: `{ id, data }`. */
13630
+ var SettingsRecordSchema = object({
13631
+ id: string(),
13632
+ data: record(string(), unknown())
13633
+ });
13634
+ /**
13635
+ * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
13636
+ * optional.
13637
+ *
13638
+ * A separate schema rather than loosening the shared one: every other method
13639
+ * on this cap addresses a row BY its id, and making that field optional
13640
+ * everywhere would turn a forgotten key into a silently generated one on
13641
+ * `update` and `delete` as well.
13642
+ */
13643
+ var BulkRecordSchema = object({
13644
+ id: string().optional(),
13645
+ data: record(string(), unknown())
13646
+ });
13647
+ /**
13648
+ * The database FILES one `sqlite-settings` engine process owns (D851).
13649
+ * Closed on purpose: a file is a thing the engine opens, backs up and gives a
13650
+ * serving thread, so a new one is a code change, never a string a caller
13651
+ * invents. `settings` is `camstack.db` and the default for every collection
13652
+ * that names no file.
13653
+ */
13654
+ var DataStoreDatabaseSchema = _enum([
13655
+ "settings",
13656
+ "events",
13657
+ "history"
13658
+ ]);
13659
+ DataStoreDatabaseSchema.options;
13660
+ /**
13661
+ * One write inside a {@link settingsStoreCapability.methods.transact} call.
13662
+ * The three that a "replace this set, bump that revision" write needs, plus
13663
+ * the batched upsert a rollup writer needs.
13664
+ */
13665
+ var TransactOpSchema = discriminatedUnion("op", [
13666
+ object({
13667
+ op: literal("deleteWhere"),
13668
+ collection: string(),
13669
+ filter: MutationFilterSchema
13670
+ }),
13671
+ object({
13672
+ op: literal("insertMany"),
13673
+ collection: string(),
13674
+ records: array(BulkRecordSchema).readonly()
13675
+ }),
13676
+ object({
13677
+ op: literal("update"),
13678
+ collection: string(),
13679
+ id: string(),
13680
+ data: record(string(), unknown())
13681
+ }),
13682
+ object({
13683
+ op: literal("upsertMany"),
13684
+ collection: string(),
13685
+ conflict: array(string()).readonly(),
13686
+ records: array(BulkRecordSchema).readonly()
13687
+ })
13688
+ ]);
13689
+ /**
13690
+ * The precondition of a transaction: the row `key` of `collection` must hold
13691
+ * `equals` in `column`, read INSIDE the transaction.
13692
+ */
13693
+ var TransactGuardSchema = object({
13694
+ collection: string(),
13695
+ key: string(),
13696
+ column: string(),
13697
+ equals: unknown()
13698
+ });
13699
+ /**
13700
+ * Column declaration for a structured (SQL-backed) collection.
13701
+ *
13702
+ * Logical types — the backend translates each to the matching SQLite
13703
+ * storage class and handles per-type marshaling:
13704
+ * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
13705
+ * - `JSON` — TEXT under the hood; serialised on write, parsed on read
13706
+ * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
13707
+ */
13708
+ var CollectionColumnSchema = object({
13709
+ name: string(),
13710
+ type: _enum([
13711
+ "TEXT",
13712
+ "INTEGER",
13713
+ "REAL",
13714
+ "JSON",
13715
+ "BOOLEAN"
13716
+ ]),
13717
+ primaryKey: boolean().optional(),
13718
+ notNull: boolean().optional(),
13719
+ unique: boolean().optional(),
13720
+ /**
13721
+ * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
13722
+ * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
13723
+ * collection surface simply cannot express their existing tables — which is
13724
+ * why they were still on the uncapped `table*` API.
13725
+ */
13726
+ defaultValue: union([
13727
+ string(),
13728
+ number(),
13729
+ boolean()
13730
+ ]).optional()
13731
+ });
13732
+ var CollectionIndexSchema = object({
13733
+ name: string(),
13734
+ columns: array(string()).readonly(),
13735
+ unique: boolean().optional()
13736
+ });
13737
+ /**
13738
+ * The input of `declareCollection` — ONE schema for the door and for the
13739
+ * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
13740
+ * the payload untouched).
13741
+ *
13742
+ * - `database` — the file the collection lives in (D851). Absent means
13743
+ * `settings`. A declaration naming a file other than the one the table is
13744
+ * already in is REFUSED by the engine, never moved.
13745
+ * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
13746
+ * as `primaryKey: true` on that column. A composite-keyed collection has no
13747
+ * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
13748
+ * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
13749
+ * address one row by id are refused.
13750
+ * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
13751
+ */
13752
+ var DeclareCollectionInputSchema = object({
13753
+ namespace: string().optional(),
13754
+ collection: string(),
13755
+ columns: array(CollectionColumnSchema).readonly(),
13756
+ indexes: array(CollectionIndexSchema).readonly().optional(),
13757
+ database: DataStoreDatabaseSchema.optional(),
13758
+ primaryKey: array(string()).readonly().optional(),
13759
+ withoutRowid: boolean().optional()
13760
+ });
13761
+ method(object({
13762
+ namespace: string().optional(),
13763
+ collection: string(),
13764
+ key: string()
13765
+ }), unknown(), { auth: "admin" }), method(object({
13766
+ namespace: string().optional(),
13767
+ collection: string(),
13768
+ key: string(),
13769
+ value: unknown()
13770
+ }), _void(), {
13771
+ kind: "mutation",
13772
+ auth: "admin"
13773
+ }), method(object({
13774
+ namespace: string().optional(),
13775
+ collection: string(),
13776
+ filter: QueryFilterSchema.optional(),
13777
+ columns: array(string()).readonly().optional()
13778
+ }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
13779
+ namespace: string().optional(),
13780
+ collection: string(),
13781
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
13782
+ * index on the collection. Never empty. */
13783
+ conflict: array(string()).readonly(),
13784
+ record: BulkRecordSchema
13785
+ }), object({
13786
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
13787
+ * row's own id on an update. */
13788
+ id: union([string(), number()]) }), {
13789
+ kind: "mutation",
13790
+ auth: "admin"
13791
+ }), method(object({
13792
+ namespace: string().optional(),
13793
+ collection: string(),
13794
+ conflict: array(string()).readonly(),
13795
+ records: array(BulkRecordSchema).readonly()
13796
+ }), object({ upserted: number().int() }), {
13797
+ kind: "mutation",
13798
+ auth: "admin"
13799
+ }), method(object({
13800
+ namespace: string().optional(),
13801
+ collection: string(),
13802
+ record: object({
13803
+ id: string().optional(),
13804
+ data: record(string(), unknown())
13805
+ })
13806
+ }), object({
13807
+ /**
13808
+ * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
13809
+ * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
13810
+ * primary key — which is the only place an auto key is knowable.
13811
+ */
13812
+ id: union([string(), number()]) }), {
13813
+ kind: "mutation",
13814
+ auth: "admin"
13815
+ }), method(object({
13816
+ namespace: string().optional(),
13817
+ collection: string(),
13818
+ records: array(BulkRecordSchema).readonly()
13819
+ }), object({ inserted: number().int() }), {
13820
+ kind: "mutation",
13821
+ auth: "admin"
13822
+ }), method(object({
13823
+ namespace: string().optional(),
13824
+ guard: TransactGuardSchema.optional(),
13825
+ ops: array(TransactOpSchema).readonly()
13826
+ }), object({
13827
+ applied: boolean(),
13828
+ current: unknown().optional()
13829
+ }), {
13830
+ kind: "mutation",
13831
+ auth: "admin"
13832
+ }), method(object({
13833
+ namespace: string().optional(),
13834
+ collection: string(),
13835
+ id: string(),
13836
+ data: record(string(), unknown())
13837
+ }), _void(), {
13838
+ kind: "mutation",
13839
+ auth: "admin"
13840
+ }), method(object({
13841
+ namespace: string().optional(),
13842
+ collection: string(),
13843
+ key: string()
13844
+ }), _void(), {
13845
+ kind: "mutation",
13846
+ auth: "admin"
13847
+ }), method(object({
13848
+ namespace: string().optional(),
13849
+ collection: string(),
13850
+ filter: MutationFilterSchema
13851
+ }), object({ deleted: number().int() }), {
13852
+ kind: "mutation",
13853
+ auth: "admin"
13854
+ }), method(object({
13855
+ namespace: string().optional(),
13856
+ collection: string(),
13857
+ filter: MutationFilterSchema,
13858
+ data: record(string(), unknown())
13859
+ }), object({ updated: number().int() }), {
13860
+ kind: "mutation",
13861
+ auth: "admin"
13862
+ }), method(object({
13863
+ namespace: string().optional(),
13864
+ collection: string(),
13865
+ filter: QueryFilterSchema.optional()
13866
+ }), number(), { auth: "admin" }), method(object({
13867
+ namespace: string().optional(),
13868
+ collection: string(),
13869
+ fields: array(AggregateFieldSchema).readonly(),
13870
+ filter: QueryFilterSchema.optional()
13871
+ }), AggregateResultSchema, { auth: "admin" }), method(object({
13872
+ namespace: string().optional(),
13873
+ collection: string(),
13874
+ field: string(),
13875
+ bucketSize: number().int().positive(),
13876
+ origin: number().int(),
13877
+ filter: QueryFilterSchema.optional()
13878
+ }), array(object({
13879
+ bucket: number().int(),
13880
+ count: number().int()
13881
+ })).readonly(), { auth: "admin" }), method(object({
13882
+ namespace: string().optional(),
13883
+ collection: string()
13884
+ }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
13885
+ kind: "mutation",
13886
+ auth: "admin"
13887
+ });
13888
+ 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>\"");
13889
+ var MigrationLevelSchema = _enum([
13890
+ "hub-store",
13891
+ "addon-state",
13892
+ "node-config",
13893
+ "viewer",
13894
+ "addon-internal"
13895
+ ]);
13896
+ var MigrationStoreSchema = _enum([
13897
+ "settings",
13898
+ "events",
13899
+ "history",
13900
+ "node-local",
13901
+ "addon-files"
13902
+ ]);
13903
+ var MigrationScopeSchema = _enum(["cluster", "per-node"]);
13904
+ /** What depends on a migration while it is pending: `block-owner` is the default. */
13905
+ var MigrationOnFailureSchema = _enum(["block-owner", "tolerate"]);
13906
+ /**
13907
+ * `sql` is a hub-only body over the owner's connection. `plan` returns edits the
13908
+ * engine applies inside the migration's own transaction (hub-owned: computed in
13909
+ * that transaction; addon-owned: computed by the owner and shipped to the engine).
13910
+ * `procedure` is addon-owned work that is not SQL (keychain, integration registry,
13911
+ * device tombstones); the ledger records its outcome and it must be idempotent.
13912
+ */
13913
+ var MigrationBodyKindSchema = _enum([
13914
+ "sql",
13915
+ "plan",
13916
+ "procedure"
13917
+ ]);
13918
+ var MigrationMetaSchema = object({
13919
+ id: MigrationIdSchema,
13920
+ /** `hub`, an addon id, `node` or `viewer`. */
13921
+ owner: string().regex(/^[a-z0-9-]+$/),
13922
+ level: MigrationLevelSchema,
13923
+ /** Strictly increasing per owner: the order within an owner. */
13924
+ seq: number().int().positive(),
13925
+ /** Ids of any owner: the order across owners. */
13926
+ dependsOn: array(MigrationIdSchema),
13927
+ store: MigrationStoreSchema,
13928
+ scope: MigrationScopeSchema,
13929
+ onFailure: MigrationOnFailureSchema,
13930
+ bodyKind: MigrationBodyKindSchema,
13931
+ description: string().min(1),
13932
+ /** The marked field the migration's spec must assert survived. */
13933
+ fixtureMark: string().min(1)
13934
+ }).refine((m) => m.id.startsWith(`${m.owner}/`), {
13935
+ message: "the id must start with \"<owner>/\"",
13936
+ path: ["id"]
13937
+ });
13938
+ /** A cycle in the dependency graph, named by the ids on it. */
13939
+ var MigrationCycleError = class extends Error {
13940
+ ids;
13941
+ constructor(ids) {
13942
+ super(`migration dependsOn cycle: ${ids.join(" -> ")}`);
13943
+ this.ids = ids;
13944
+ this.name = "MigrationCycleError";
13945
+ }
13946
+ };
13947
+ /**
13948
+ * Topological order over `dependsOn` plus the implicit edge "a lower `seq` of an
13949
+ * owner runs first". Ties are broken by `(owner, seq)`. Throws
13950
+ * {@link MigrationCycleError} on a cycle. A `dependsOn` id that is not in the list
13951
+ * is ignored here — the catalogue schema is what refuses it.
13952
+ */
13953
+ function orderMigrations(items) {
13954
+ const byId = new Map(items.map((item) => [item.id, item]));
13955
+ const deps = /* @__PURE__ */ new Map();
13956
+ const ownerSorted = /* @__PURE__ */ new Map();
13957
+ for (const item of items) {
13958
+ const list = ownerSorted.get(item.owner) ?? [];
13959
+ list.push(item);
13960
+ ownerSorted.set(item.owner, list);
13961
+ }
13962
+ for (const item of items) deps.set(item.id, new Set(item.dependsOn.filter((id) => byId.has(id))));
13963
+ for (const list of ownerSorted.values()) {
13964
+ list.sort((a, b) => a.seq - b.seq);
13965
+ for (let i = 1; i < list.length; i++) {
13966
+ const previous = list[i - 1];
13967
+ const current = list[i];
13968
+ if (previous !== void 0 && current !== void 0) deps.get(current.id)?.add(previous.id);
13969
+ }
13970
+ }
13971
+ const tie = (a, b) => a.owner < b.owner ? -1 : a.owner > b.owner ? 1 : a.seq - b.seq;
13972
+ const done = /* @__PURE__ */ new Set();
13973
+ const ordered = [];
13974
+ let remaining = [...items];
13975
+ while (remaining.length > 0) {
13976
+ const ready = remaining.filter((item) => [...deps.get(item.id) ?? []].every((dep) => done.has(dep)));
13977
+ if (ready.length === 0) throw new MigrationCycleError(remaining.map((item) => item.id).sort());
13978
+ ready.sort(tie);
13979
+ const next = ready[0];
13980
+ if (next === void 0) break;
13981
+ ordered.push(next);
13982
+ done.add(next.id);
13983
+ remaining = remaining.filter((item) => item.id !== next.id);
13984
+ }
13985
+ return ordered;
13986
+ }
13987
+ /** Every way a list of migrations (in declaration order) is not a catalogue. */
13988
+ function catalogueIssues(items) {
13989
+ const issues = [];
13990
+ const seen = /* @__PURE__ */ new Set();
13991
+ const known = new Set(items.map((item) => item.id));
13992
+ const lastSeq = /* @__PURE__ */ new Map();
13993
+ const seqUsed = /* @__PURE__ */ new Map();
13994
+ for (const item of items) {
13995
+ if (seen.has(item.id)) issues.push({
13996
+ code: "duplicate-id",
13997
+ ids: [item.id],
13998
+ message: `duplicate id ${item.id}`
13999
+ });
14000
+ seen.add(item.id);
14001
+ const used = seqUsed.get(item.owner) ?? /* @__PURE__ */ new Set();
14002
+ if (used.has(item.seq)) issues.push({
14003
+ code: "duplicate-seq",
14004
+ ids: [item.id],
14005
+ message: `owner ${item.owner} has two migrations with seq ${item.seq}`
14006
+ });
14007
+ else if (item.seq < (lastSeq.get(item.owner) ?? 0)) issues.push({
14008
+ code: "seq-order",
14009
+ ids: [item.id],
14010
+ message: `seq ${item.seq} of ${item.id} does not increase in declaration order (owner ${item.owner})`
14011
+ });
14012
+ used.add(item.seq);
14013
+ seqUsed.set(item.owner, used);
14014
+ lastSeq.set(item.owner, Math.max(item.seq, lastSeq.get(item.owner) ?? 0));
14015
+ for (const dep of item.dependsOn) if (!known.has(dep)) issues.push({
14016
+ code: "unknown-dependency",
14017
+ ids: [item.id, dep],
14018
+ message: `${item.id} dependsOn unknown id ${dep}`
14019
+ });
14020
+ }
14021
+ try {
14022
+ orderMigrations(items);
14023
+ } catch (err) {
14024
+ if (err instanceof MigrationCycleError) issues.push({
14025
+ code: "cycle",
14026
+ ids: err.ids,
14027
+ message: err.message
14028
+ });
14029
+ else throw err;
14030
+ }
14031
+ return issues;
14032
+ }
14033
+ array(MigrationMetaSchema).superRefine((items, ctx) => {
14034
+ for (const issue of catalogueIssues(items)) ctx.addIssue({
14035
+ code: "custom",
14036
+ message: `${issue.code}: ${issue.message}`
14037
+ });
14038
+ });
14039
+ /**
14040
+ * The canonical JSON (`preconditionOf`) of the value a plan read; the apply step
14041
+ * re-reads, and a plan whose read no longer matches is refused (the migration is
14042
+ * `failed`, nothing it edited is kept).
14043
+ */
14044
+ var PreconditionSchema = string().min(1).optional();
14045
+ var MigrationPlanSchema = object({ edits: array(discriminatedUnion("op", [
14046
+ object({
14047
+ op: literal("setKeys"),
14048
+ scope: string().min(1),
14049
+ values: record(string(), unknown()),
14050
+ precondition: PreconditionSchema
14051
+ }),
14052
+ object({
14053
+ op: literal("deleteKeys"),
14054
+ scope: string().min(1),
14055
+ keys: array(string().min(1)).min(1),
14056
+ precondition: PreconditionSchema
14057
+ }),
14058
+ object({
14059
+ op: literal("putRow"),
14060
+ table: string().min(1),
14061
+ key: record(string(), unknown()),
14062
+ row: record(string(), unknown()),
14063
+ precondition: PreconditionSchema
14064
+ }),
14065
+ object({
14066
+ op: literal("deleteRow"),
14067
+ table: string().min(1),
14068
+ key: record(string(), unknown()),
14069
+ precondition: PreconditionSchema
14070
+ }),
14071
+ object({
14072
+ op: literal("upsertRecord"),
14073
+ kind: string().min(1),
14074
+ recordKey: string().min(1),
14075
+ value: unknown(),
14076
+ precondition: PreconditionSchema
14077
+ }),
14078
+ object({
14079
+ op: literal("deleteRecord"),
14080
+ kind: string().min(1),
14081
+ recordKey: string().min(1),
14082
+ precondition: PreconditionSchema
14083
+ })
14084
+ ])) });
14085
+ /**
14086
+ * What a `procedure` body reports. `already-done` means the rerun observed the
14087
+ * work finished (a crash between the work and the ledger row, or a manual fix)
14088
+ * and did none of it again; the ledger records `applied` either way.
14089
+ */
14090
+ var MigrationProcedureResultSchema = object({
14091
+ outcome: _enum(["done", "already-done"]),
14092
+ summary: record(string(), unknown())
14093
+ });
14094
+ /** What an owner hands the engine when one of its migrations ran. */
14095
+ var MigrationOwnerResultSchema = discriminatedUnion("kind", [
14096
+ object({
14097
+ kind: literal("plan"),
14098
+ plan: MigrationPlanSchema
14099
+ }),
14100
+ object({
14101
+ kind: literal("procedure"),
14102
+ result: MigrationProcedureResultSchema
14103
+ }),
14104
+ object({
14105
+ kind: literal("failed"),
14106
+ error: string().min(1)
14107
+ })
14108
+ ]);
14109
+ /**
14110
+ * `held`: a restore of a pre-migration snapshot undid this id; it does not rerun
14111
+ * until the operator retries or a new code version ships.
14112
+ * `skipped-newer`: the database knows an id this code does not.
14113
+ */
14114
+ var MigrationStatusSchema = _enum([
14115
+ "applied",
14116
+ "failed",
14117
+ "blocked",
14118
+ "running",
14119
+ "held",
14120
+ "skipped-newer"
14121
+ ]);
14122
+ var MigrationLedgerEntrySchema = object({
14123
+ id: string(),
14124
+ database: DataStoreDatabaseSchema,
14125
+ owner: string(),
14126
+ level: MigrationLevelSchema,
14127
+ seq: number().int().nonnegative(),
14128
+ status: MigrationStatusSchema,
14129
+ attempts: number().int().nonnegative(),
14130
+ appliedAt: number().nullable(),
14131
+ codeVersion: string().nullable(),
14132
+ /** What the apply changed: rows per table, as the engine counted them. */
14133
+ planSummary: record(string(), unknown()).nullable(),
14134
+ progress: record(string(), unknown()).nullable(),
14135
+ error: string().nullable(),
14136
+ /** Why it is `blocked` or `held` (`dependency-failed`, `snapshot-refused`, …). */
14137
+ reason: string().nullable(),
14138
+ nodeId: string().nullable(),
14139
+ bootId: string().nullable(),
14140
+ /**
14141
+ * The catalogued meta of this id (description, body kind, store, `dependsOn`…),
14142
+ * joined by the engine from the catalogues it knows this boot. Absent when no
14143
+ * catalogue lists the id (a newer database).
14144
+ */
14145
+ meta: MigrationMetaSchema.optional()
14146
+ });
14147
+ /** A catalogued migration that is not applied, with where it stands. */
14148
+ var MigrationPendingEntrySchema = object({
14149
+ meta: MigrationMetaSchema,
14150
+ database: DataStoreDatabaseSchema,
14151
+ state: _enum([
14152
+ "pending",
14153
+ "failed",
14154
+ "blocked",
14155
+ "held"
14156
+ ]),
14157
+ reason: string().nullable()
14158
+ });
14159
+ /** Rows per table before and after a dry run, and what it would have changed. */
14160
+ var MigrationDryRunResultSchema = object({
14161
+ id: string(),
14162
+ ok: boolean(),
14163
+ error: string().nullable(),
14164
+ rowsChanged: number().int().nonnegative(),
14165
+ tables: array(object({
14166
+ table: string(),
14167
+ before: number().int().nonnegative(),
14168
+ after: number().int().nonnegative()
14169
+ }))
14170
+ });
14171
+ /** One of an owner's migrations, as the engine tells the owner to treat it this boot. */
14172
+ var MigrationOwnerStepSchema = object({
14173
+ id: MigrationIdSchema,
14174
+ action: _enum(["run", "skip"]),
14175
+ /** The ledger state the engine found: `pending` has no row yet. */
14176
+ state: union([literal("pending"), MigrationStatusSchema])
14177
+ });
14178
+ /**
13365
14179
  * The trainer contract — what the hub hands the cloud trainer and what it
13366
14180
  * gets back (cloud training, D805+).
13367
14181
  *
@@ -19135,6 +19949,14 @@ var APPLIANCE_CAP_VOCABULARY = {
19135
19949
  }
19136
19950
  } }
19137
19951
  },
19952
+ "device-runs": {
19953
+ scope: "device",
19954
+ noun: t("Runs", "Esecuzioni"),
19955
+ events: {
19956
+ onRunOpened: { notOperatorFacing: "the durable record of a run that the running-state start flip already announces" },
19957
+ onRunClosed: { notOperatorFacing: "the durable record of a run that the running-state finish flip already announces" }
19958
+ }
19959
+ },
19138
19960
  update: {
19139
19961
  scope: "device",
19140
19962
  noun: t("Firmware update", "Aggiornamento firmware"),
@@ -20352,6 +21174,7 @@ var SYSTEM_CAP_VOCABULARY = {
20352
21174
  "login-method": system(IDENTITY),
20353
21175
  "mesh-network": system(NETWORK),
20354
21176
  "metrics-provider": system(CLUSTER),
21177
+ migrations: system(INTERNAL),
20355
21178
  "model-convert": system(ML),
20356
21179
  "model-distributor": system(ML),
20357
21180
  "mqtt-broker": system(NETWORK),
@@ -40554,7 +41377,8 @@ function normalizeUnit(raw) {
40554
41377
  if (lower === "dbm") return "dBm";
40555
41378
  if (lower === "dbfs") return "dBFS";
40556
41379
  if (lower === "inhg") return "inHg";
40557
- if (lower === "mm/h") return "mm/h";
41380
+ if (lower === "mm/h" || lower === "mm/hr") return "mm/h";
41381
+ if (lower === "in/h" || lower === "in/hr") return "in/h";
40558
41382
  if (lower === "w/m²" || lower === "w/m2") return "W/m²";
40559
41383
  if (lower === "°" || lower === "deg" || lower === "degree" || lower === "degrees") return "°";
40560
41384
  if (lower === "kbps") return "kbps";
@@ -42948,7 +43772,7 @@ function pruneDeletedEntities(selection, live, log, exporter) {
42948
43772
  * rebuilds without manual reload.
42949
43773
  *
42950
43774
  * The hub-local builtin `addon-pages-aggregator` (see
42951
- * `@camstack/system/builtins/addon-pages-aggregator`) registers the
43775
+ * `@camstack/server/system/builtins/addon-pages-aggregator`) registers the
42952
43776
  * provider. Splitting the public aggregator from the raw collection
42953
43777
  * keeps both ends in codegen — there's no hand-written
42954
43778
  * `addon-pages.router.ts` wrapper anymore.
@@ -43038,7 +43862,7 @@ var addonRoutesCapability = {
43038
43862
  */
43039
43863
  invoke: method(InvokeRequestSchema, InvokeReplyEnvelopeSchema, { kind: "mutation" })
43040
43864
  },
43041
- /** BIG PLAN 2: declarative mount hint — read by `@camstack/system` `buildCapRouters`. */
43865
+ /** BIG PLAN 2: declarative mount hint — read by `@camstack/server/system` `buildCapRouters`. */
43042
43866
  mount: { kind: "skip" }
43043
43867
  };
43044
43868
  var ConfigTabDeclarationSchema = object({
@@ -43267,7 +44091,7 @@ method(_void(), array(WidgetMetadataSchema).readonly(), { auth: "admin" });
43267
44091
  * manual reload — same scheme used by `addon-pages`.
43268
44092
  *
43269
44093
  * The hub-local builtin `addon-widgets-aggregator` (see
43270
- * `@camstack/system/builtins/addon-widgets-aggregator`) registers the
44094
+ * `@camstack/server/system/builtins/addon-widgets-aggregator`) registers the
43271
44095
  * provider. Splitting the public aggregator from the raw collection
43272
44096
  * keeps both ends in codegen — there's no hand-written wrapper.
43273
44097
  */
@@ -43628,329 +44452,6 @@ method(object({
43628
44452
  password: string()
43629
44453
  }), AuthResultSchema.nullable(), { kind: "mutation" }), method(object({ state: string() }), string()), method(record(string(), string()), AuthResultSchema, { kind: "mutation" }), method(object({ token: string() }), AuthResultSchema.nullable());
43630
44454
  /**
43631
- * Query filter for settings-store collections.
43632
- */
43633
- var QueryFilterSchema = object({
43634
- where: record(string(), unknown()).optional(),
43635
- whereIn: record(string(), array(unknown())).optional(),
43636
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
43637
- /** NULL-safe exclusion: matches rows whose field is NULL OR != the value. */
43638
- whereNot: record(string(), unknown()).optional(),
43639
- /** NULL-safe exclusion of a SET — `whereNot` for more than one value. An
43640
- * empty list excludes nothing. See `QueryFilter.whereNotIn`. */
43641
- whereNotIn: record(string(), array(unknown())).optional(),
43642
- orderBy: object({
43643
- field: string(),
43644
- direction: _enum(["asc", "desc"])
43645
- }).optional(),
43646
- limit: number().optional(),
43647
- offset: number().optional()
43648
- });
43649
- /**
43650
- * The predicate half of a filter, for BULK MUTATIONS.
43651
- *
43652
- * Deliberately not `QueryFilterSchema`: `orderBy` / `limit` / `offset` have no
43653
- * meaning for a statement that rewrites a set, and accepting them would invite
43654
- * a caller to believe `limit` bounds the damage. Every field is optional here
43655
- * only so the shape stays composable — the implementation REJECTS a filter
43656
- * that compiles to no predicate, because that is the whole collection.
43657
- */
43658
- var MutationFilterSchema = object({
43659
- where: record(string(), unknown()).optional(),
43660
- whereIn: record(string(), array(unknown())).optional(),
43661
- whereBetween: record(string(), tuple([unknown(), unknown()])).optional(),
43662
- whereNot: record(string(), unknown()).optional(),
43663
- whereNotIn: record(string(), array(unknown())).optional()
43664
- });
43665
- /**
43666
- * One scalar an {@link settingsStoreCapability.methods.aggregate} call asks for.
43667
- *
43668
- * `as` names the slot in the result, so the SAME column may be asked twice with
43669
- * two operations (`MIN(startMs)` and `MAX(startMs)` in one round trip) — which
43670
- * a `Record<column, op>` shape could not express.
43671
- */
43672
- var AggregateFieldSchema = object({
43673
- /** Result key. */
43674
- as: string().min(1),
43675
- /** Column to aggregate. Must be a real column of a declared collection. */
43676
- field: string().min(1),
43677
- op: _enum([
43678
- "sum",
43679
- "min",
43680
- "max"
43681
- ])
43682
- });
43683
- /**
43684
- * `COUNT(*)` plus one number per requested field.
43685
- *
43686
- * `null` means NO ROW MATCHED, never zero: a `SUM` over an empty set and a sum
43687
- * that really is 0 are different facts, and an accounting caller that renders
43688
- * "0 bytes, oldest = 0" for "nothing here" reports a lie about a disk.
43689
- */
43690
- var AggregateResultSchema = object({
43691
- count: number().int(),
43692
- values: record(string(), number().nullable())
43693
- });
43694
- /** A single stored record: `{ id, data }`. */
43695
- var SettingsRecordSchema = object({
43696
- id: string(),
43697
- data: record(string(), unknown())
43698
- });
43699
- /**
43700
- * One record of a BULK insert — {@link SettingsRecordSchema} with the id made
43701
- * optional.
43702
- *
43703
- * A separate schema rather than loosening the shared one: every other method
43704
- * on this cap addresses a row BY its id, and making that field optional
43705
- * everywhere would turn a forgotten key into a silently generated one on
43706
- * `update` and `delete` as well.
43707
- */
43708
- var BulkRecordSchema = object({
43709
- id: string().optional(),
43710
- data: record(string(), unknown())
43711
- });
43712
- /**
43713
- * The database FILES one `sqlite-settings` engine process owns (D851).
43714
- * Closed on purpose: a file is a thing the engine opens, backs up and gives a
43715
- * serving thread, so a new one is a code change, never a string a caller
43716
- * invents. `settings` is `camstack.db` and the default for every collection
43717
- * that names no file.
43718
- */
43719
- var DataStoreDatabaseSchema = _enum([
43720
- "settings",
43721
- "events",
43722
- "history"
43723
- ]);
43724
- DataStoreDatabaseSchema.options;
43725
- /**
43726
- * One write inside a {@link settingsStoreCapability.methods.transact} call.
43727
- * The three that a "replace this set, bump that revision" write needs, plus
43728
- * the batched upsert a rollup writer needs.
43729
- */
43730
- var TransactOpSchema = discriminatedUnion("op", [
43731
- object({
43732
- op: literal("deleteWhere"),
43733
- collection: string(),
43734
- filter: MutationFilterSchema
43735
- }),
43736
- object({
43737
- op: literal("insertMany"),
43738
- collection: string(),
43739
- records: array(BulkRecordSchema).readonly()
43740
- }),
43741
- object({
43742
- op: literal("update"),
43743
- collection: string(),
43744
- id: string(),
43745
- data: record(string(), unknown())
43746
- }),
43747
- object({
43748
- op: literal("upsertMany"),
43749
- collection: string(),
43750
- conflict: array(string()).readonly(),
43751
- records: array(BulkRecordSchema).readonly()
43752
- })
43753
- ]);
43754
- /**
43755
- * The precondition of a transaction: the row `key` of `collection` must hold
43756
- * `equals` in `column`, read INSIDE the transaction.
43757
- */
43758
- var TransactGuardSchema = object({
43759
- collection: string(),
43760
- key: string(),
43761
- column: string(),
43762
- equals: unknown()
43763
- });
43764
- /**
43765
- * Column declaration for a structured (SQL-backed) collection.
43766
- *
43767
- * Logical types — the backend translates each to the matching SQLite
43768
- * storage class and handles per-type marshaling:
43769
- * - `TEXT` / `INTEGER` / `REAL` — native SQLite types, pass-through
43770
- * - `JSON` — TEXT under the hood; serialised on write, parsed on read
43771
- * - `BOOLEAN` — INTEGER 0/1 under the hood; coerced both directions
43772
- */
43773
- var CollectionColumnSchema = object({
43774
- name: string(),
43775
- type: _enum([
43776
- "TEXT",
43777
- "INTEGER",
43778
- "REAL",
43779
- "JSON",
43780
- "BOOLEAN"
43781
- ]),
43782
- primaryKey: boolean().optional(),
43783
- notNull: boolean().optional(),
43784
- unique: boolean().optional(),
43785
- /**
43786
- * Column DEFAULT. Required for B2: the four `ensureTable` consumers declare
43787
- * columns like `enabled INTEGER NOT NULL DEFAULT 1`, and without this the
43788
- * collection surface simply cannot express their existing tables — which is
43789
- * why they were still on the uncapped `table*` API.
43790
- */
43791
- defaultValue: union([
43792
- string(),
43793
- number(),
43794
- boolean()
43795
- ]).optional()
43796
- });
43797
- var CollectionIndexSchema = object({
43798
- name: string(),
43799
- columns: array(string()).readonly(),
43800
- unique: boolean().optional()
43801
- });
43802
- /**
43803
- * The input of `declareCollection` — ONE schema for the door and for the
43804
- * engine behind it, so the two surfaces cannot drift (the dispatcher forwards
43805
- * the payload untouched).
43806
- *
43807
- * - `database` — the file the collection lives in (D851). Absent means
43808
- * `settings`. A declaration naming a file other than the one the table is
43809
- * already in is REFUSED by the engine, never moved.
43810
- * - `primaryKey` — a COMPOSITE key, as column names. A single name is the same
43811
- * as `primaryKey: true` on that column. A composite-keyed collection has no
43812
- * `id`: it is written with `insertMany` / `upsertMany` / `transact` and read
43813
- * with `query` / `count` / `aggregate` / `deleteWhere`; the calls that
43814
- * address one row by id are refused.
43815
- * - `withoutRowid` — emit `WITHOUT ROWID`. Requires a primary key.
43816
- */
43817
- var DeclareCollectionInputSchema = object({
43818
- namespace: string().optional(),
43819
- collection: string(),
43820
- columns: array(CollectionColumnSchema).readonly(),
43821
- indexes: array(CollectionIndexSchema).readonly().optional(),
43822
- database: DataStoreDatabaseSchema.optional(),
43823
- primaryKey: array(string()).readonly().optional(),
43824
- withoutRowid: boolean().optional()
43825
- });
43826
- method(object({
43827
- namespace: string().optional(),
43828
- collection: string(),
43829
- key: string()
43830
- }), unknown(), { auth: "admin" }), method(object({
43831
- namespace: string().optional(),
43832
- collection: string(),
43833
- key: string(),
43834
- value: unknown()
43835
- }), _void(), {
43836
- kind: "mutation",
43837
- auth: "admin"
43838
- }), method(object({
43839
- namespace: string().optional(),
43840
- collection: string(),
43841
- filter: QueryFilterSchema.optional(),
43842
- columns: array(string()).readonly().optional()
43843
- }), array(SettingsRecordSchema).readonly(), { auth: "admin" }), method(object({
43844
- namespace: string().optional(),
43845
- collection: string(),
43846
- /** Declared columns identifying the slot; must be covered by a UNIQUE
43847
- * index on the collection. Never empty. */
43848
- conflict: array(string()).readonly(),
43849
- record: BulkRecordSchema
43850
- }), object({
43851
- /** The id the row HAS: assigned by SQLite on an insert, or the existing
43852
- * row's own id on an update. */
43853
- id: union([string(), number()]) }), {
43854
- kind: "mutation",
43855
- auth: "admin"
43856
- }), method(object({
43857
- namespace: string().optional(),
43858
- collection: string(),
43859
- conflict: array(string()).readonly(),
43860
- records: array(BulkRecordSchema).readonly()
43861
- }), object({ upserted: number().int() }), {
43862
- kind: "mutation",
43863
- auth: "admin"
43864
- }), method(object({
43865
- namespace: string().optional(),
43866
- collection: string(),
43867
- record: object({
43868
- id: string().optional(),
43869
- data: record(string(), unknown())
43870
- })
43871
- }), object({
43872
- /**
43873
- * The id the row ACTUALLY got (D473): the one supplied, the UUID minted
43874
- * for an absent one, or the ROWID SQLite assigned on an `INTEGER`
43875
- * primary key — which is the only place an auto key is knowable.
43876
- */
43877
- id: union([string(), number()]) }), {
43878
- kind: "mutation",
43879
- auth: "admin"
43880
- }), method(object({
43881
- namespace: string().optional(),
43882
- collection: string(),
43883
- records: array(BulkRecordSchema).readonly()
43884
- }), object({ inserted: number().int() }), {
43885
- kind: "mutation",
43886
- auth: "admin"
43887
- }), method(object({
43888
- namespace: string().optional(),
43889
- guard: TransactGuardSchema.optional(),
43890
- ops: array(TransactOpSchema).readonly()
43891
- }), object({
43892
- applied: boolean(),
43893
- current: unknown().optional()
43894
- }), {
43895
- kind: "mutation",
43896
- auth: "admin"
43897
- }), method(object({
43898
- namespace: string().optional(),
43899
- collection: string(),
43900
- id: string(),
43901
- data: record(string(), unknown())
43902
- }), _void(), {
43903
- kind: "mutation",
43904
- auth: "admin"
43905
- }), method(object({
43906
- namespace: string().optional(),
43907
- collection: string(),
43908
- key: string()
43909
- }), _void(), {
43910
- kind: "mutation",
43911
- auth: "admin"
43912
- }), method(object({
43913
- namespace: string().optional(),
43914
- collection: string(),
43915
- filter: MutationFilterSchema
43916
- }), object({ deleted: number().int() }), {
43917
- kind: "mutation",
43918
- auth: "admin"
43919
- }), method(object({
43920
- namespace: string().optional(),
43921
- collection: string(),
43922
- filter: MutationFilterSchema,
43923
- data: record(string(), unknown())
43924
- }), object({ updated: number().int() }), {
43925
- kind: "mutation",
43926
- auth: "admin"
43927
- }), method(object({
43928
- namespace: string().optional(),
43929
- collection: string(),
43930
- filter: QueryFilterSchema.optional()
43931
- }), number(), { auth: "admin" }), method(object({
43932
- namespace: string().optional(),
43933
- collection: string(),
43934
- fields: array(AggregateFieldSchema).readonly(),
43935
- filter: QueryFilterSchema.optional()
43936
- }), AggregateResultSchema, { auth: "admin" }), method(object({
43937
- namespace: string().optional(),
43938
- collection: string(),
43939
- field: string(),
43940
- bucketSize: number().int().positive(),
43941
- origin: number().int(),
43942
- filter: QueryFilterSchema.optional()
43943
- }), array(object({
43944
- bucket: number().int(),
43945
- count: number().int()
43946
- })).readonly(), { auth: "admin" }), method(object({
43947
- namespace: string().optional(),
43948
- collection: string()
43949
- }), boolean(), { auth: "admin" }), method(DeclareCollectionInputSchema, _void(), {
43950
- kind: "mutation",
43951
- auth: "admin"
43952
- });
43953
- /**
43954
44455
  * How long one snapshot may take end to end: the pinned copy of the whole
43955
44456
  * database plus `PRAGMA integrity_check` over the copy, on the hub's slowest
43956
44457
  * disk. ~800 MB at a pessimistic 10 MB/s is under 3 minutes; the bound is
@@ -45913,7 +46414,7 @@ method(object({
45913
46414
  kind: "mutation",
45914
46415
  auth: "admin"
45915
46416
  }), method(object({ blockIds: array(string()) }), object({ states: array(CompositionBlockStateSchema) }), { auth: "admin" });
45916
- 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;
46417
+ 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;
45917
46418
  var NUMBER = {
45918
46419
  kind: "fixed",
45919
46420
  result: ["number"]
@@ -46360,6 +46861,117 @@ function actuator(input) {
46360
46861
  };
46361
46862
  }
46362
46863
  new Map(GROUP_TEMPLATES.map((template) => [template.id, template]));
46864
+ var PowerSourceRefusalReasonSchema = _enum([
46865
+ "no-power-source",
46866
+ "switch-missing",
46867
+ "switch-offline",
46868
+ "switch-unknown",
46869
+ "restart-in-progress",
46870
+ "switch-call-failed"
46871
+ ]);
46872
+ var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
46873
+ accepted: literal(false),
46874
+ reason: PowerSourceRefusalReasonSchema,
46875
+ /** One line for the operator, naming the switch. */
46876
+ message: string()
46877
+ })]);
46878
+ var PowerSourceInfoSchema = object({
46879
+ /** False when the device has no power source linked; the rest is then empty. */
46880
+ linked: boolean(),
46881
+ switchDeviceId: number().nullable(),
46882
+ switchName: string().nullable(),
46883
+ /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
46884
+ switchOn: boolean().nullable(),
46885
+ switchOnline: boolean().nullable(),
46886
+ restartInProgress: boolean(),
46887
+ offSeconds: number().int()
46888
+ });
46889
+ /** The composed slice: the switch state the link mirrors, plus the stored off time. */
46890
+ var PowerSourceStateSchema = object({
46891
+ /** Null while the switch has not reported: unknown is never `false` (D393). */
46892
+ switchOn: boolean().nullable(),
46893
+ offSeconds: number().int(),
46894
+ lastFetchedAt: number()
46895
+ });
46896
+ var POWER_SOURCE_DEVICE_TYPES = [
46897
+ DeviceType.Gateway,
46898
+ DeviceType.Hub,
46899
+ DeviceType.Camera,
46900
+ DeviceType.Sensor,
46901
+ DeviceType.MediaPlayer,
46902
+ DeviceType.Vacuum,
46903
+ DeviceType.Generic,
46904
+ DeviceType.Washer,
46905
+ DeviceType.Dryer,
46906
+ DeviceType.Dishwasher,
46907
+ DeviceType.PetFountain,
46908
+ DeviceType.Fan,
46909
+ DeviceType.Humidifier,
46910
+ DeviceType.Climate,
46911
+ DeviceType.WaterHeater
46912
+ ];
46913
+ var powerSourceCustomize = {
46914
+ version: 1,
46915
+ targets: [{
46916
+ id: "switch",
46917
+ cardinality: "scalar",
46918
+ vocabulary: {
46919
+ cap: "power-source",
46920
+ stateField: "switchOn"
46921
+ },
46922
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
46923
+ apply: "add",
46924
+ path: "switchOn",
46925
+ sources: [{
46926
+ kind: "switch",
46927
+ transforms: [{ kind: "identity" }]
46928
+ }],
46929
+ companions: [{
46930
+ kind: "constant",
46931
+ path: "offSeconds",
46932
+ value: 10,
46933
+ when: "add"
46934
+ }, {
46935
+ kind: "clock",
46936
+ path: "lastFetchedAt",
46937
+ when: "always"
46938
+ }],
46939
+ sourceReport: { status: "assignment-status" }
46940
+ }]
46941
+ };
46942
+ var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
46943
+ var powerSourceCapability = {
46944
+ name: "power-source",
46945
+ scope: "device",
46946
+ mode: "singleton",
46947
+ deviceTypes: POWER_SOURCE_DEVICE_TYPES,
46948
+ methods: {
46949
+ getStatus: systemMethod(deviceInput$1, PowerSourceInfoSchema, { auth: "admin" }),
46950
+ powerOn: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
46951
+ kind: "mutation",
46952
+ auth: "admin"
46953
+ }),
46954
+ powerOff: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
46955
+ kind: "mutation",
46956
+ auth: "admin"
46957
+ }),
46958
+ forceRestart: systemMethod(deviceInput$1, PowerSourceActionResultSchema, {
46959
+ kind: "mutation",
46960
+ auth: "admin"
46961
+ })
46962
+ },
46963
+ runtimeState: PowerSourceStateSchema,
46964
+ customize: powerSourceCustomize,
46965
+ /**
46966
+ * Runtime-state durability: **session** — mirrored from the linked switch on
46967
+ * its next report; a restored value would outlive the reading it names.
46968
+ *
46969
+ * See `RuntimeStateDurability`. Enforced by
46970
+ * `scripts/check-runtime-state-durability.ts`.
46971
+ */
46972
+ durability: "session",
46973
+ history: "none"
46974
+ };
46363
46975
  DeviceFeature.LightColorRgb, DeviceFeature.LightColorHsv, DeviceFeature.LightColorMired;
46364
46976
  DeviceType.Camera;
46365
46977
  `${COMPOSED_DEVICE_STABLE_ID_PREFIX}`;
@@ -46924,6 +47536,57 @@ id: union([string(), number()]) }), {
46924
47536
  kind: "mutation",
46925
47537
  auth: "admin"
46926
47538
  });
47539
+ 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({
47540
+ id: string().min(1),
47541
+ error: string().min(1)
47542
+ }), MigrationLedgerEntrySchema, {
47543
+ kind: "mutation",
47544
+ auth: "admin"
47545
+ }), method(object({ id: string().min(1) }), object({ entries: array(MigrationLedgerEntrySchema) }), {
47546
+ kind: "mutation",
47547
+ auth: "admin"
47548
+ }), method(object({
47549
+ database: DataStoreDatabaseSchema,
47550
+ snapshotId: string().min(1)
47551
+ }), object({
47552
+ scheduled: boolean(),
47553
+ held: array(string()),
47554
+ restartRequired: boolean()
47555
+ }), {
47556
+ kind: "mutation",
47557
+ auth: "admin"
47558
+ }), method(object({
47559
+ owner: string().min(1),
47560
+ metas: array(MigrationMetaSchema)
47561
+ }), object({
47562
+ bootId: string(),
47563
+ snapshot: _enum([
47564
+ "none",
47565
+ "written",
47566
+ "refused"
47567
+ ]),
47568
+ steps: array(MigrationOwnerStepSchema)
47569
+ }), {
47570
+ kind: "mutation",
47571
+ auth: "admin"
47572
+ }), method(object({ id: string().min(1) }), object({
47573
+ proceed: boolean(),
47574
+ state: union([literal("pending"), MigrationStatusSchema]),
47575
+ reason: string().nullable()
47576
+ }), {
47577
+ kind: "mutation",
47578
+ auth: "admin"
47579
+ }), method(object({
47580
+ id: string().min(1),
47581
+ result: MigrationOwnerResultSchema
47582
+ }), object({
47583
+ status: MigrationStatusSchema,
47584
+ error: string().nullable(),
47585
+ rowsChanged: number().int().nonnegative()
47586
+ }), {
47587
+ kind: "mutation",
47588
+ auth: "admin"
47589
+ });
46927
47590
  /**
46928
47591
  * records - an addon's own facts and documents (D881): ledgers, audit rows,
46929
47592
  * config documents, mirrors. The third storage API next to `ctx.settings`
@@ -47734,6 +48397,101 @@ method(object({ deviceId: number().int() }), array(SeriesInfoSchema).readonly())
47734
48397
  kind: "mutation",
47735
48398
  auth: "admin"
47736
48399
  });
48400
+ /**
48401
+ * device-runs - the durable record of ONE run of a device: a washing cycle, a
48402
+ * dishwasher cycle, later a mow or a clean (D992).
48403
+ *
48404
+ * A run is a DOCUMENT (opened at one instant, closed at another, carrying what
48405
+ * was measured over it), never a sample - device-history keeps the numbers over
48406
+ * time and its `device-runs` provider keeps these documents beside them, because
48407
+ * the energy of a run is a question about device-history's own series. The
48408
+ * record is written from the `running-state` flip on `DeviceStateChanged`; it
48409
+ * survives a restart (the open row is on disk) and a missed flip (boot compares
48410
+ * the open rows against the live snapshots).
48411
+ *
48412
+ * Every measurement is `number | null` and `null` means "could not be
48413
+ * measured", never `0` (D393). A run closed by the boot reconcile because the
48414
+ * device stopped while nobody was watching has `outcome: 'unknown'`.
48415
+ */
48416
+ var DeviceRunKindSchema = _enum([
48417
+ "appliance",
48418
+ "mower",
48419
+ "vacuum"
48420
+ ]);
48421
+ var DeviceRunStateSchema = _enum(["open", "closed"]);
48422
+ var DeviceRunOutcomeSchema = _enum([
48423
+ "completed",
48424
+ "aborted",
48425
+ "error",
48426
+ "unknown"
48427
+ ]);
48428
+ /** What opened or closed the run. `reconcile` is the boot comparison against live state. */
48429
+ var DeviceRunSourceSchema = _enum([
48430
+ "running-state",
48431
+ "activity",
48432
+ "reconcile"
48433
+ ]);
48434
+ /** How the energy of a run was obtained; `null` when it could not be. */
48435
+ var DeviceRunEnergyMethodSchema = _enum(["counter-delta", "history-integral"]);
48436
+ var DeviceRunPhaseSchema = object({
48437
+ phase: string(),
48438
+ at: number()
48439
+ });
48440
+ /** The kind-specific block; a later phase adds a branch, nothing else changes. */
48441
+ var DeviceRunDetailSchema = discriminatedUnion("kind", [object({
48442
+ kind: literal("appliance"),
48443
+ /** kWh used over the run. */
48444
+ energyKwh: number().nullable().meta(readingMeta("energy")),
48445
+ energyMethod: DeviceRunEnergyMethodSchema.nullable(),
48446
+ /** Highest power seen while the run was open. */
48447
+ peakW: number().nullable().meta(readingMeta("power")),
48448
+ /** Time-weighted mean power while the run was open. */
48449
+ avgW: number().nullable().meta(readingMeta("power")),
48450
+ /** The program the appliance said it ran when the run opened. */
48451
+ program: string().nullable(),
48452
+ remainingAtStartSec: number().nullable().meta(readingMeta("duration")),
48453
+ /** The last phase the device reported before the run closed. */
48454
+ finalPhase: string().nullable(),
48455
+ /**
48456
+ * The meter's energy counter when the run opened; `null` when the meter had
48457
+ * not said (or the run was opened late, by the boot reconcile). It lives in
48458
+ * the row so a run that outlives a restart still ends with `end - start`.
48459
+ */
48460
+ energyStartKwh: number().nullable().meta(readingMeta("energy")),
48461
+ /** Milliseconds of power readings behind `avgW` - the weight a restart resumes the mean with. */
48462
+ powerObservedMs: number()
48463
+ })]);
48464
+ var DeviceRunSchema = object({
48465
+ /** `${deviceId}:${startedAt}`. */
48466
+ runId: string(),
48467
+ deviceId: number().int(),
48468
+ kind: DeviceRunKindSchema,
48469
+ state: DeviceRunStateSchema,
48470
+ startedAt: number(),
48471
+ endedAt: number().nullable(),
48472
+ durationMs: number().nullable(),
48473
+ outcome: DeviceRunOutcomeSchema.nullable(),
48474
+ startSource: DeviceRunSourceSchema,
48475
+ endSource: DeviceRunSourceSchema.nullable(),
48476
+ /** Where the device's running bit came from (`running-state.source`); `null` when it did not say. */
48477
+ runningSource: _enum(["native", "assigned"]).nullable(),
48478
+ phases: array(DeviceRunPhaseSchema),
48479
+ detail: DeviceRunDetailSchema
48480
+ });
48481
+ DeviceType.Washer, DeviceType.Dryer, DeviceType.Dishwasher, method(object({
48482
+ deviceId: number().int(),
48483
+ outcome: DeviceRunOutcomeSchema.optional(),
48484
+ atFrom: number().optional(),
48485
+ atTo: number().optional(),
48486
+ limit: number().int().positive().max(200).default(50),
48487
+ cursor: string().optional()
48488
+ }), object({
48489
+ runs: array(DeviceRunSchema).readonly(),
48490
+ nextCursor: string().nullable()
48491
+ })), method(object({
48492
+ deviceId: number().int(),
48493
+ runId: string()
48494
+ }), DeviceRunSchema.nullable()), method(object({ deviceId: number().int() }), DeviceRunSchema.nullable());
47737
48495
  var DeviceCaStatusSchema = discriminatedUnion("state", [
47738
48496
  object({ state: literal("absent") }),
47739
48497
  object({
@@ -47869,8 +48627,8 @@ method(object({}), object({
47869
48627
  });
47870
48628
  var KeychainKeySourceSchema = object({
47871
48629
  kind: _enum([
47872
- "config-file",
47873
- "previous-config-file",
48630
+ "mounted-file",
48631
+ "previous-mounted-file",
47874
48632
  "platform",
47875
48633
  "default-file"
47876
48634
  ]),
@@ -52429,7 +53187,8 @@ var PALETTE_ENTRY_IDS = [
52429
53187
  "switch",
52430
53188
  "siren",
52431
53189
  "button",
52432
- "doorbell"
53190
+ "doorbell",
53191
+ "appliance-cycle"
52433
53192
  ];
52434
53193
  var AUDIO_ENTRY_ID = "audio";
52435
53194
  /** Six rungs, quiet → loud. The opaque end is the entry's hex. */
@@ -52500,7 +53259,8 @@ var CAMSTACK_HEX = {
52500
53259
  switch: "#7a812a",
52501
53260
  siren: "#ac5d7f",
52502
53261
  button: "#b276cd",
52503
- doorbell: "#0087ab"
53262
+ doorbell: "#0087ab",
53263
+ "appliance-cycle": "#2a8c8c"
52504
53264
  };
52505
53265
  var CAMSTACK_FALLBACK = [
52506
53266
  "#b34dc7",
@@ -52565,7 +53325,8 @@ var HIGH_CONTRAST_HEX = {
52565
53325
  switch: "#d05259",
52566
53326
  siren: "#cd67b3",
52567
53327
  button: "#c9746a",
52568
- doorbell: "#338eeb"
53328
+ doorbell: "#338eeb",
53329
+ "appliance-cycle": "#00848a"
52569
53330
  };
52570
53331
  var HIGH_CONTRAST_FALLBACK = [
52571
53332
  "#817f3b",
@@ -52630,7 +53391,8 @@ var OKABE_ITO_HEX = {
52630
53391
  switch: "#ad2c4e",
52631
53392
  siren: "#348e08",
52632
53393
  button: "#d27303",
52633
- doorbell: "#55770f"
53394
+ doorbell: "#55770f",
53395
+ "appliance-cycle": "#00737d"
52634
53396
  };
52635
53397
  var OKABE_ITO_FALLBACK = [
52636
53398
  "#9a3784",
@@ -52839,6 +53601,7 @@ sub("switch", "control", "control", "switch", "Switch");
52839
53601
  sub("siren", "control", "control", "siren", "Siren");
52840
53602
  sub("button", "control", "control", "button", "Button");
52841
53603
  sub("doorbell", "control", "control", "doorbell", "Doorbell");
53604
+ sub("appliance-cycle", "control", "control", "switch", "Appliance cycle");
52842
53605
  for (const l of AUDIO_MACRO_LABELS) {
52843
53606
  const kind = `audio-${l.id}`;
52844
53607
  if (entries.has(kind)) continue;
@@ -61486,7 +62249,8 @@ var CAP_TO_KIND = {
61486
62249
  cover: "cover",
61487
62250
  switch: "switch",
61488
62251
  button: "button",
61489
- doorbell: "doorbell"
62252
+ doorbell: "doorbell",
62253
+ "running-state": "appliance-cycle"
61490
62254
  };
61491
62255
  function buildDescriptor(capName, kind) {
61492
62256
  const t = EVENT_TAXONOMY[kind];
@@ -61867,7 +62631,18 @@ var ServerUpdateActionResultSchema = object({
61867
62631
  * store (D877), present when the pre-flight refused the update
61868
62632
  * (`reason: 'addon-would-refuse'`). Optional for version skew.
61869
62633
  */
61870
- blockers: array(UpdateBlockerSchema).optional()
62634
+ blockers: array(UpdateBlockerSchema).optional(),
62635
+ /**
62636
+ * The minimums the target server declares and this node does not meet
62637
+ * (D989), present when the update was refused with
62638
+ * `reason: 'requirements-unmet'`. Each names the versions on both sides.
62639
+ */
62640
+ unmet: array(UnmetRequirementSchema).optional(),
62641
+ /**
62642
+ * The addons this node has below the minimum the staged server declares —
62643
+ * the ones to update WITH the server. Present on an accepted update.
62644
+ */
62645
+ requiredAddonUpdates: array(RequiredAddonUpdateSchema).optional()
61871
62646
  });
61872
62647
  var LaunchOverridesSchema = object({
61873
62648
  /** The persisted heap ceiling in MB, or `null` when none is set. */
@@ -64483,7 +65258,9 @@ var PackageUpdateSchema = object({
64483
65258
  latestVersion: string(),
64484
65259
  category: _enum(["addon", "core"]),
64485
65260
  requiresRestart: boolean(),
64486
- isSystem: boolean()
65261
+ isSystem: boolean(),
65262
+ /** Held: what the newer version needs and this node does not meet (D989). */
65263
+ blockedBy: array(UnmetRequirementSchema).readonly().optional()
64487
65264
  });
64488
65265
  var PackageVersionInfoSchema = object({
64489
65266
  version: string(),
@@ -64675,10 +65452,7 @@ method(object({ nodeId: string().optional() }).optional(), array(AddonListItemSc
64675
65452
  }), InstallPackageResultSchema, {
64676
65453
  kind: "mutation",
64677
65454
  auth: "admin"
64678
- }), method(object({ packageName: string() }), InstallPackageResultSchema, {
64679
- kind: "mutation",
64680
- auth: "admin"
64681
- }), method(_void(), boolean()), method(_void(), array(unknown()).readonly()), method(object({
65455
+ }), method(object({
64682
65456
  packageName: string().min(1),
64683
65457
  nodeId: string().optional(),
64684
65458
  everywhere: boolean().optional()
@@ -65939,19 +66713,19 @@ var RemoteSetConfigResultSchema = discriminatedUnion("status", [object({ status:
65939
66713
  reason: _enum(["unknown-template", "not-a-media-player"]),
65940
66714
  detail: string()
65941
66715
  })]);
65942
- var deviceInput$1 = object({ deviceId: number().int().nonnegative() });
66716
+ var deviceInput = object({ deviceId: number().int().nonnegative() });
65943
66717
  DeviceType.MediaPlayer;
65944
- method(deviceInput$1, RemoteConfigSchema, { auth: "admin" }), method(deviceInput$1.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
66718
+ method(deviceInput, RemoteConfigSchema, { auth: "admin" }), method(deviceInput.extend({ config: RemoteConfigSchema }), RemoteSetConfigResultSchema, {
65945
66719
  kind: "mutation",
65946
66720
  auth: "admin"
65947
- }), method(deviceInput$1, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput$1.extend({
66721
+ }), method(deviceInput, RemoteCommandListSchema, { auth: "protected" }), method(deviceInput.extend({
65948
66722
  role: RemoteRoleSchema,
65949
66723
  /** Sends in a row (volume steps). */
65950
66724
  repeat: number().int().min(1).max(50).default(1)
65951
66725
  }), RemoteSendResultSchema, {
65952
66726
  kind: "mutation",
65953
66727
  auth: "protected"
65954
- }), method(deviceInput$1.extend({
66728
+ }), method(deviceInput.extend({
65955
66729
  command: RemoteMediaCommandSchema,
65956
66730
  muted: boolean().optional(),
65957
66731
  source: string().optional(),
@@ -69817,7 +70591,7 @@ var lawnMowerControlCapability = {
69817
70591
  * clients — they reverse-connect to the hub. Exposing their interfaces
69818
70592
  * via the same surface would leak internal topology with no upside.
69819
70593
  *
69820
- * Implementation in `@camstack/system/builtins/local-network/`.
70594
+ * Implementation in `@camstack/server/system/builtins/local-network/`.
69821
70595
  */
69822
70596
  /** Coarse classification derived from the interface name + IP range. */
69823
70597
  var InterfaceKindEnum = _enum([
@@ -70314,109 +71088,6 @@ var lockControlCapability = {
70314
71088
  * whether persisting is worth a SQLite commit. */
70315
71089
  volatileStateFields: ["lastChangedAt"]
70316
71090
  };
70317
- var PowerSourceRefusalReasonSchema = _enum([
70318
- "no-power-source",
70319
- "switch-missing",
70320
- "switch-offline",
70321
- "switch-unknown",
70322
- "restart-in-progress",
70323
- "switch-call-failed"
70324
- ]);
70325
- var PowerSourceActionResultSchema = discriminatedUnion("accepted", [object({ accepted: literal(true) }), object({
70326
- accepted: literal(false),
70327
- reason: PowerSourceRefusalReasonSchema,
70328
- /** One line for the operator, naming the switch. */
70329
- message: string()
70330
- })]);
70331
- var PowerSourceInfoSchema = object({
70332
- /** False when the device has no power source linked; the rest is then empty. */
70333
- linked: boolean(),
70334
- switchDeviceId: number().nullable(),
70335
- switchName: string().nullable(),
70336
- /** The switch's on/off state, or null when it is unknown. Never false for unknown (D393). */
70337
- switchOn: boolean().nullable(),
70338
- switchOnline: boolean().nullable(),
70339
- restartInProgress: boolean(),
70340
- offSeconds: number().int()
70341
- });
70342
- /** The composed slice: the switch state the link mirrors, plus the stored off time. */
70343
- var PowerSourceStateSchema = object({
70344
- /** Null while the switch has not reported: unknown is never `false` (D393). */
70345
- switchOn: boolean().nullable(),
70346
- offSeconds: number().int(),
70347
- lastFetchedAt: number()
70348
- });
70349
- var POWER_SOURCE_DEVICE_TYPES = [
70350
- DeviceType.Gateway,
70351
- DeviceType.Hub,
70352
- DeviceType.Camera,
70353
- DeviceType.Sensor,
70354
- DeviceType.MediaPlayer,
70355
- DeviceType.Vacuum,
70356
- DeviceType.Generic
70357
- ];
70358
- var powerSourceCustomize = {
70359
- version: 1,
70360
- targets: [{
70361
- id: "switch",
70362
- cardinality: "scalar",
70363
- vocabulary: {
70364
- cap: "power-source",
70365
- stateField: "switchOn"
70366
- },
70367
- deviceTypes: POWER_SOURCE_DEVICE_TYPES,
70368
- apply: "add",
70369
- path: "switchOn",
70370
- sources: [{
70371
- kind: "switch",
70372
- transforms: [{ kind: "identity" }]
70373
- }],
70374
- companions: [{
70375
- kind: "constant",
70376
- path: "offSeconds",
70377
- value: 10,
70378
- when: "add"
70379
- }, {
70380
- kind: "clock",
70381
- path: "lastFetchedAt",
70382
- when: "always"
70383
- }],
70384
- sourceReport: { status: "assignment-status" }
70385
- }]
70386
- };
70387
- var deviceInput = object({ deviceId: number().int().nonnegative() });
70388
- var powerSourceCapability = {
70389
- name: "power-source",
70390
- scope: "device",
70391
- mode: "singleton",
70392
- deviceTypes: POWER_SOURCE_DEVICE_TYPES,
70393
- methods: {
70394
- getStatus: systemMethod(deviceInput, PowerSourceInfoSchema, { auth: "admin" }),
70395
- powerOn: systemMethod(deviceInput, PowerSourceActionResultSchema, {
70396
- kind: "mutation",
70397
- auth: "admin"
70398
- }),
70399
- powerOff: systemMethod(deviceInput, PowerSourceActionResultSchema, {
70400
- kind: "mutation",
70401
- auth: "admin"
70402
- }),
70403
- forceRestart: systemMethod(deviceInput, PowerSourceActionResultSchema, {
70404
- kind: "mutation",
70405
- auth: "admin"
70406
- })
70407
- },
70408
- runtimeState: PowerSourceStateSchema,
70409
- customize: powerSourceCustomize,
70410
- /**
70411
- * Runtime-state durability: **session** — mirrored from the linked switch on
70412
- * its next report; a restored value would outlive the reading it names.
70413
- *
70414
- * See `RuntimeStateDurability`. Enforced by
70415
- * `scripts/check-runtime-state-durability.ts`.
70416
- */
70417
- durability: "session",
70418
- history: "none"
70419
- };
70420
71091
  /**
70421
71092
  * A monotonic total between resets (D944). Energy uses `power-meter.energy`
70422
71093
  * so dashboards keep one series; every other instantaneous reading lands here.
@@ -71697,7 +72368,7 @@ onStatusChanged: { data: object({
71697
72368
  * jitter, and observed/peak bandwidth per device + per client.
71698
72369
  *
71699
72370
  * Implementation lives in the server's `NetworkQualityService` (thin
71700
- * wrapper over the shared `NetworkQualityTracker` from `@camstack/system`).
72371
+ * wrapper over the shared `NetworkQualityTracker` from `@camstack/server/system`).
71701
72372
  * The provider is registered from `trpc.router.ts` against the existing
71702
72373
  * service instance — no addon owns this state.
71703
72374
  *
@@ -73848,7 +74519,7 @@ var ptzAutotrackCapability = {
73848
74519
  * - **Native**: the camera's own firmware autotrack, registered per device by
73849
74520
  * the driver when the probe says it exists (Reolink: yes; Hikvision:
73850
74521
  * model-dependent).
73851
- * - **Wrapper**: the emulated implementation (`@camstack/system`'s `autotrack` builtin),
74522
+ * - **Wrapper**: the emulated implementation (`@camstack/server/system`'s `autotrack` builtin),
73852
74523
  * which follows a subject with `ptz.move` pulses driven by the detection
73853
74524
  * pipeline. `defaultActive: false` — the operator binds it per camera in
73854
74525
  * the bindings UI.
@@ -73966,6 +74637,10 @@ var RUNNING_DEVICE_TYPES = Object.values(DeviceType);
73966
74637
  /** Generic Customize target for the running boolean (D936). */
73967
74638
  var runningCustomize = {
73968
74639
  version: 1,
74640
+ overridable: [{
74641
+ path: "running",
74642
+ by: "running"
74643
+ }],
73969
74644
  targets: [{
73970
74645
  id: "running",
73971
74646
  cardinality: "scalar",
@@ -74023,7 +74698,7 @@ var runningCustomize = {
74023
74698
  kind: "constant",
74024
74699
  path: "phase",
74025
74700
  value: null,
74026
- when: "always"
74701
+ when: "add"
74027
74702
  },
74028
74703
  {
74029
74704
  kind: "clock",
@@ -77307,7 +77982,7 @@ var ExternalIdentityKeySchema = object({
77307
77982
  provider: string().min(1),
77308
77983
  subject: string().min(1)
77309
77984
  });
77310
- method(_void(), array(UserSummarySchema), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
77985
+ method(_void(), array(UserSummarySchema), { auth: "admin" }), method(_void(), object({ defaultPasswordInUse: boolean() }), { auth: "admin" }), method(CreateUserInputSchema, UserSummarySchema, {
77311
77986
  kind: "mutation",
77312
77987
  auth: "admin",
77313
77988
  access: "create"
@@ -80683,13 +81358,6 @@ Object.freeze({
80683
81358
  access: "view",
80684
81359
  role: "admin"
80685
81360
  },
80686
- "addons.installFromWorkspace": {
80687
- capName: "addons",
80688
- capScope: "system",
80689
- addonId: null,
80690
- access: "create",
80691
- role: "admin"
80692
- },
80693
81361
  "addons.installPackage": {
80694
81362
  capName: "addons",
80695
81363
  capScope: "system",
@@ -80697,13 +81365,6 @@ Object.freeze({
80697
81365
  access: "create",
80698
81366
  role: "admin"
80699
81367
  },
80700
- "addons.isWorkspaceAvailable": {
80701
- capName: "addons",
80702
- capScope: "system",
80703
- addonId: null,
80704
- access: "view",
80705
- role: "admin"
80706
- },
80707
81368
  "addons.list": {
80708
81369
  capName: "addons",
80709
81370
  capScope: "system",
@@ -80753,13 +81414,6 @@ Object.freeze({
80753
81414
  access: "view",
80754
81415
  role: "admin"
80755
81416
  },
80756
- "addons.listWorkspacePackages": {
80757
- capName: "addons",
80758
- capScope: "system",
80759
- addonId: null,
80760
- access: "view",
80761
- role: "admin"
80762
- },
80763
81417
  "addons.onAddonLogs": {
80764
81418
  capName: "addons",
80765
81419
  capScope: "system",
@@ -83427,6 +84081,27 @@ Object.freeze({
83427
84081
  access: "create",
83428
84082
  role: "admin"
83429
84083
  },
84084
+ "deviceRuns.getCurrentRun": {
84085
+ capName: "device-runs",
84086
+ capScope: "device",
84087
+ addonId: null,
84088
+ access: "view",
84089
+ role: "viewer"
84090
+ },
84091
+ "deviceRuns.getRun": {
84092
+ capName: "device-runs",
84093
+ capScope: "device",
84094
+ addonId: null,
84095
+ access: "view",
84096
+ role: "viewer"
84097
+ },
84098
+ "deviceRuns.listRuns": {
84099
+ capName: "device-runs",
84100
+ capScope: "device",
84101
+ addonId: null,
84102
+ access: "view",
84103
+ role: "viewer"
84104
+ },
83430
84105
  "deviceState.claimFields": {
83431
84106
  capName: "device-state",
83432
84107
  capScope: "system",
@@ -84967,6 +85642,69 @@ Object.freeze({
84967
85642
  access: "view",
84968
85643
  role: "admin"
84969
85644
  },
85645
+ "migrations.declareOwner": {
85646
+ capName: "migrations",
85647
+ capScope: "system",
85648
+ addonId: null,
85649
+ access: "create",
85650
+ role: "admin"
85651
+ },
85652
+ "migrations.dryRun": {
85653
+ capName: "migrations",
85654
+ capScope: "system",
85655
+ addonId: null,
85656
+ access: "view",
85657
+ role: "admin"
85658
+ },
85659
+ "migrations.finishMigration": {
85660
+ capName: "migrations",
85661
+ capScope: "system",
85662
+ addonId: null,
85663
+ access: "create",
85664
+ role: "admin"
85665
+ },
85666
+ "migrations.listLedger": {
85667
+ capName: "migrations",
85668
+ capScope: "system",
85669
+ addonId: null,
85670
+ access: "view",
85671
+ role: "admin"
85672
+ },
85673
+ "migrations.listPending": {
85674
+ capName: "migrations",
85675
+ capScope: "system",
85676
+ addonId: null,
85677
+ access: "view",
85678
+ role: "admin"
85679
+ },
85680
+ "migrations.markFailed": {
85681
+ capName: "migrations",
85682
+ capScope: "system",
85683
+ addonId: null,
85684
+ access: "create",
85685
+ role: "admin"
85686
+ },
85687
+ "migrations.restorePreMigration": {
85688
+ capName: "migrations",
85689
+ capScope: "system",
85690
+ addonId: null,
85691
+ access: "create",
85692
+ role: "admin"
85693
+ },
85694
+ "migrations.retry": {
85695
+ capName: "migrations",
85696
+ capScope: "system",
85697
+ addonId: null,
85698
+ access: "create",
85699
+ role: "admin"
85700
+ },
85701
+ "migrations.startMigration": {
85702
+ capName: "migrations",
85703
+ capScope: "system",
85704
+ addonId: null,
85705
+ access: "create",
85706
+ role: "admin"
85707
+ },
84970
85708
  "modelConvert.convert": {
84971
85709
  capName: "model-convert",
84972
85710
  capScope: "system",
@@ -90077,6 +90815,13 @@ Object.freeze({
90077
90815
  access: "view",
90078
90816
  role: "admin"
90079
90817
  },
90818
+ "userManagement.getAdminBootstrapStatus": {
90819
+ capName: "user-management",
90820
+ capScope: "system",
90821
+ addonId: null,
90822
+ access: "view",
90823
+ role: "admin"
90824
+ },
90080
90825
  "userManagement.getTotpStatus": {
90081
90826
  capName: "user-management",
90082
90827
  capScope: "system",
@@ -91573,6 +92318,21 @@ Object.freeze({
91573
92318
  form: "single",
91574
92319
  optional: false
91575
92320
  }],
92321
+ "deviceRuns.getCurrentRun": [{
92322
+ name: "deviceId",
92323
+ form: "single",
92324
+ optional: false
92325
+ }],
92326
+ "deviceRuns.getRun": [{
92327
+ name: "deviceId",
92328
+ form: "single",
92329
+ optional: false
92330
+ }],
92331
+ "deviceRuns.listRuns": [{
92332
+ name: "deviceId",
92333
+ form: "single",
92334
+ optional: false
92335
+ }],
91576
92336
  "deviceState.claimFields": [{
91577
92337
  name: "deviceId",
91578
92338
  form: "single",
@@ -94319,13 +95079,114 @@ NC_AUDIO_DEFAULTS.hitPercent, NC_AUDIO_DEFAULTS.samplingSeconds;
94319
95079
  *
94320
95080
  * Pure, RN-safe: the viewer imports the support rule from here.
94321
95081
  */
95082
+ /**
95083
+ * Control caps that still have a raise and a clear an operator names: an
95084
+ * appliance cycle raises when it STARTS and clears when it FINISHES. Kept out
95085
+ * of `SOURCE_CAP_ACTIVE_FIELD` on purpose — that table also makes a device a
95086
+ * camera-sensor / recorder-trigger source, and a dishwasher is neither.
95087
+ */
95088
+ var CYCLE_RAISED_FIELD_BY_CAP = { "running-state": "running" };
94322
95089
  /** Event kind → the boolean field of its cap slice that means "raised". */
94323
95090
  var NC_SENSOR_RAISED_FIELD_BY_KIND = Object.freeze(Object.fromEntries(Object.entries(EVENT_KIND_BY_CAP).flatMap(([capName, descriptor]) => {
95091
+ const cycleField = CYCLE_RAISED_FIELD_BY_CAP[capName];
95092
+ if (cycleField !== void 0) return [[descriptor.kind, cycleField]];
94324
95093
  const field = SOURCE_CAP_ACTIVE_FIELD[capName];
94325
95094
  if (field === void 0 || descriptor.category !== "sensor") return [];
94326
95095
  return [[descriptor.kind, field]];
94327
95096
  })));
94328
95097
  Object.freeze(Object.keys(NC_SENSOR_RAISED_FIELD_BY_KIND));
95098
+ Object.freeze({
95099
+ contact: {
95100
+ starts: {
95101
+ en: "opened",
95102
+ it: "si apre"
95103
+ },
95104
+ finishes: {
95105
+ en: "closed",
95106
+ it: "si chiude"
95107
+ }
95108
+ },
95109
+ "motion-sensor": {
95110
+ starts: {
95111
+ en: "motion detected",
95112
+ it: "movimento rilevato"
95113
+ },
95114
+ finishes: {
95115
+ en: "motion stopped",
95116
+ it: "movimento terminato"
95117
+ }
95118
+ },
95119
+ smoke: {
95120
+ starts: {
95121
+ en: "smoke detected",
95122
+ it: "fumo rilevato"
95123
+ },
95124
+ finishes: {
95125
+ en: "smoke gone",
95126
+ it: "fumo scomparso"
95127
+ }
95128
+ },
95129
+ flood: {
95130
+ starts: {
95131
+ en: "water detected",
95132
+ it: "acqua rilevata"
95133
+ },
95134
+ finishes: {
95135
+ en: "dry again",
95136
+ it: "di nuovo asciutto"
95137
+ }
95138
+ },
95139
+ gas: {
95140
+ starts: {
95141
+ en: "gas detected",
95142
+ it: "gas rilevato"
95143
+ },
95144
+ finishes: {
95145
+ en: "gas gone",
95146
+ it: "gas scomparso"
95147
+ }
95148
+ },
95149
+ "carbon-monoxide": {
95150
+ starts: {
95151
+ en: "carbon monoxide detected",
95152
+ it: "monossido rilevato"
95153
+ },
95154
+ finishes: {
95155
+ en: "carbon monoxide gone",
95156
+ it: "monossido scomparso"
95157
+ }
95158
+ },
95159
+ vibration: {
95160
+ starts: {
95161
+ en: "vibration detected",
95162
+ it: "vibrazione rilevata"
95163
+ },
95164
+ finishes: {
95165
+ en: "vibration stopped",
95166
+ it: "vibrazione terminata"
95167
+ }
95168
+ },
95169
+ tamper: {
95170
+ starts: {
95171
+ en: "tampered with",
95172
+ it: "manomesso"
95173
+ },
95174
+ finishes: {
95175
+ en: "tamper cleared",
95176
+ it: "manomissione rientrata"
95177
+ }
95178
+ },
95179
+ "appliance-cycle": {
95180
+ starts: {
95181
+ en: "the cycle started",
95182
+ it: "il ciclo parte"
95183
+ },
95184
+ finishes: {
95185
+ en: "the cycle finished",
95186
+ it: "il ciclo è terminato"
95187
+ }
95188
+ }
95189
+ });
94329
95190
  object({
94330
95191
  phase: _enum(["raised", "resolved"]),
94331
95192
  episodeId: string().min(1),
@@ -94532,6 +95393,10 @@ var STATE_SUMMARY_VARS = [
94532
95393
  [...RULE_TEXT],
94533
95394
  [...RULE_TEXT],
94534
95395
  [...RULE_TEXT],
95396
+ [...RULE_TEXT],
95397
+ [...RULE_TEXT],
95398
+ [...RULE_TEXT],
95399
+ [...RULE_TEXT],
94535
95400
  [...RULE_BODY_ONLY],
94536
95401
  [...RULE_TEXT],
94537
95402
  ([...RULE_TEXT], [...DEVICE_EVENTS]),
@@ -94549,6 +95414,7 @@ var STATE_SUMMARY_VARS = [
94549
95414
  [...RULE_TEXT],
94550
95415
  [...RULE_TEXT],
94551
95416
  [...RULE_TEXT],
95417
+ [...RULE_TEXT],
94552
95418
  ([...RULE_TEXT], [...DEVICE_ATTENTION, ...ALERT_EPISODE]),
94553
95419
  [...RULE_TEXT],
94554
95420
  [...RULE_TEXT],
@@ -94793,6 +95659,7 @@ new Set([
94793
95659
  "siren",
94794
95660
  "button",
94795
95661
  "doorbell",
95662
+ "appliance-cycle",
94796
95663
  "audio-speech",
94797
95664
  "audio-scream",
94798
95665
  "audio-crying",
@@ -95452,4 +96319,4 @@ async function listHomeAssistantBrokers(query) {
95452
96319
  return (await query({})).filter((broker) => broker.kind === HA_BROKER_KIND);
95453
96320
  }
95454
96321
  //#endregion
95455
- export { humiditySensorCapability as $, array as $t, brokerCapability as A, temperatureSensorCapability as At, consumableKindOf as B, DeviceRole as Bt, alarmPanelCapability as C, runningFromVacuumState as Ct, bestLocationMatch as D, smokeCapability as Dt, batteryCapability as E, sendToExportedTarget as Et, carbonMonoxideCapability as F, valveCapability as Ft, deviceAdoptionCapability as G, baseUnitOf as Gt, controlCapability as H, errMsg as Ht, climateControlCapability as I, vibrationCapability as It, eventEmitterCapability as J, defineRecordKinds as Jt, deviceExportCapability as K, createEvent as Kt, colorCapability as L, waterHeaterCapability as Lt, buildDeviceAdminUrl as M, tryConvertUnit as Mt, buttonCapability as N, updateCapability as Nt, binaryCapability as O, switchCapability as Ot, calendarSourceCapability as P, vacuumControlCapability as Pt, humidifierCapability as Q, _enum as Qt, connectivityCapability as R, weatherCapability as Rt, airQualitySensorCapability as S, runningFromMediaPlayer as St, automationControlCapability as T, scriptRunnerCapability as Tt, coverCapability as U, BaseAddon as Ut, contactCapability as V, DeviceType as Vt, deviceAdminLinkCapability as W, RecordSecretUnavailableError as Wt, floodCapability as X, isReadingQuantity as Xt, fanControlCapability as Y, entryBody as Yt, gasCapability as Z, nodePin as Zt, PLUG_APPLIANCE_TYPES as _, publicEndpointReaderFrom as _t, BaseDevice as a, record as an, motionCapability as at, accessoriesCapability as b, runExportedAction as bt, COCO_TO_MACRO as c, unknown as cn, notifierCapability as ct, ConsumableItemSchema as d, parseAdminBase as dt, boolean as en, imageCapability as et, ConsumableStatusSchema as f, powerMeterCapability as ft, MediaPlayerRepeatSchema as g, pruneDeletedEntities as gt, HvacModeSchema as h, pressureSensorCapability as ht, AlarmArmModeSchema as i, object as in, mediaPlayerCapability as it, buildAddonRouteProvider as j, tryConvertDelta as jt, brightnessCapability as k, tamperCapability as kt, CameraSwitchIdSchema as l, url as ln, numericSensorCapability as lt, FanDirectionSchema as m, presenceCapability as mt, HA_PROVIDER_ADDON_ID as n, literal as nn, lawnMowerControlCapability as nt, BaseDeviceProvider as o, string as on, normalizeUnit as ot, EnumSensorDateTimeFormatSchema as p, prepareNotification as pt, enumSensorCapability as q, defineEntryKind as qt, listHomeAssistantBrokers as r, number as rn, lockControlCapability as rt, CAMERA_SWITCH_ORDER as s, union as sn, notificationOutputCapability as st, HA_BROKER_KIND as t, discriminatedUnion as tn, ingestReading as tt, ConsumableFormSchema as u, EventCategory as un, oauthIntegrationCapability as ut, RUNNING_STATE_COLD as v, quantityFromUnit as vt, ambientLightSensorCapability as w, runningStateCapability as wt, addonRoutesCapability as x, runningFromLawnMower as xt, TargetSchema as y, resolveAttachmentUrls as yt, consumableFormOf as z, DeviceFeature as zt };
96322
+ export { humiditySensorCapability as $, number as $t, brokerCapability as A, temperatureSensorCapability as At, consumableKindOf as B, RecordSecretUnavailableError as Bt, alarmPanelCapability as C, runningFromVacuumState as Ct, bestLocationMatch as D, smokeCapability as Dt, batteryCapability as E, sendToExportedTarget as Et, carbonMonoxideCapability as F, valveCapability as Ft, deviceAdoptionCapability as G, entryBody as Gt, controlCapability as H, createEvent as Ht, climateControlCapability as I, vibrationCapability as It, eventEmitterCapability as J, _enum as Jt, deviceExportCapability as K, isReadingQuantity as Kt, colorCapability as L, waterHeaterCapability as Lt, buildDeviceAdminUrl as M, tryConvertUnit as Mt, buttonCapability as N, updateCapability as Nt, binaryCapability as O, switchCapability as Ot, calendarSourceCapability as P, vacuumControlCapability as Pt, humidifierCapability as Q, literal as Qt, connectivityCapability as R, weatherCapability as Rt, airQualitySensorCapability as S, runningFromMediaPlayer as St, automationControlCapability as T, scriptRunnerCapability as Tt, coverCapability as U, defineEntryKind as Ut, contactCapability as V, baseUnitOf as Vt, deviceAdminLinkCapability as W, defineRecordKinds as Wt, floodCapability as X, boolean as Xt, fanControlCapability as Y, array as Yt, gasCapability as Z, discriminatedUnion as Zt, PLUG_APPLIANCE_TYPES as _, publicEndpointReaderFrom as _t, BaseDevice as a, url as an, motionCapability as at, accessoriesCapability as b, runExportedAction as bt, COCO_TO_MACRO as c, DeviceType as cn, notifierCapability as ct, ConsumableItemSchema as d, parseAdminBase as dt, object as en, imageCapability as et, ConsumableStatusSchema as f, powerMeterCapability as ft, MediaPlayerRepeatSchema as g, pruneDeletedEntities as gt, HvacModeSchema as h, pressureSensorCapability as ht, AlarmArmModeSchema as i, unknown as in, mediaPlayerCapability as it, buildAddonRouteProvider as j, tryConvertDelta as jt, brightnessCapability as k, tamperCapability as kt, CameraSwitchIdSchema as l, errMsg as ln, numericSensorCapability as lt, FanDirectionSchema as m, presenceCapability as mt, HA_PROVIDER_ADDON_ID as n, string as nn, lawnMowerControlCapability as nt, BaseDeviceProvider as o, DeviceFeature as on, normalizeUnit as ot, EnumSensorDateTimeFormatSchema as p, prepareNotification as pt, enumSensorCapability as q, nodePin as qt, listHomeAssistantBrokers as r, union as rn, lockControlCapability as rt, CAMERA_SWITCH_ORDER as s, DeviceRole as sn, notificationOutputCapability as st, HA_BROKER_KIND as t, record as tn, ingestReading as tt, ConsumableFormSchema as u, EventCategory as un, oauthIntegrationCapability as ut, RUNNING_STATE_COLD as v, quantityFromUnit as vt, ambientLightSensorCapability as w, runningStateCapability as wt, addonRoutesCapability as x, runningFromLawnMower as xt, TargetSchema as y, resolveAttachmentUrls as yt, consumableFormOf as z, BaseAddon as zt };