@camstack/addon-export-ha-mqtt 1.1.25 → 1.1.27

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.
@@ -7379,6 +7379,62 @@ var RecordingConfigSchema = object({
7379
7379
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7380
7380
  });
7381
7381
  /**
7382
+ * Ops-log — the durable, append-only operations audit shared by the
7383
+ * recordings and events management surfaces.
7384
+ *
7385
+ * ONE row shape is reused for both domains so a single "Activity" view can
7386
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7387
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7388
+ * management operation, WHY it ran (reason), and its measurable effect
7389
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7390
+ * never fail the operation it records.
7391
+ */
7392
+ /** Which management domain the operation belongs to. */
7393
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7394
+ /** The kind of management operation performed. */
7395
+ var OpsLogOpSchema = _enum([
7396
+ "prune",
7397
+ "manual-delete",
7398
+ "rescan",
7399
+ "retention-run"
7400
+ ]);
7401
+ /** Why the operation ran. */
7402
+ var OpsLogReasonSchema = _enum([
7403
+ "retention",
7404
+ "quota",
7405
+ "manual",
7406
+ "operator"
7407
+ ]);
7408
+ /** One audit row, shared verbatim by both domains. */
7409
+ var OpsLogEntrySchema = object({
7410
+ /** Unique row id. */
7411
+ id: string(),
7412
+ /** Epoch ms the operation completed. */
7413
+ at: number$1(),
7414
+ domain: OpsLogDomainSchema,
7415
+ op: OpsLogOpSchema,
7416
+ reason: OpsLogReasonSchema,
7417
+ /** The camera the op targeted; null for a cluster/global op. */
7418
+ deviceId: number$1().nullable(),
7419
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7420
+ nodeId: string(),
7421
+ /** Buckets / rows deleted (op-specific unit). */
7422
+ itemsAffected: number$1(),
7423
+ /** Bytes reclaimed by the op (0 when not measurable). */
7424
+ bytesReclaimed: number$1(),
7425
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7426
+ detail: string().nullable(),
7427
+ /** Who/what triggered the op. */
7428
+ actor: string()
7429
+ });
7430
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7431
+ var OpsLogQueryInputSchema = object({
7432
+ /** Restrict to a single camera; omit for every row. */
7433
+ deviceId: number$1().optional(),
7434
+ /** Max rows returned, newest-first. */
7435
+ limit: number$1().int().min(1).max(1e3).optional()
7436
+ });
7437
+ /**
7382
7438
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7383
7439
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7384
7440
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7622,6 +7678,160 @@ var EncodeProfileSchema = object({
7622
7678
  */
7623
7679
  outputArgs: array(string()).optional()
7624
7680
  });
7681
+ var COCO_TO_MACRO = {
7682
+ mapping: {
7683
+ person: "person",
7684
+ bicycle: "vehicle",
7685
+ car: "vehicle",
7686
+ motorcycle: "vehicle",
7687
+ airplane: "vehicle",
7688
+ bus: "vehicle",
7689
+ train: "vehicle",
7690
+ truck: "vehicle",
7691
+ boat: "vehicle",
7692
+ bird: "animal",
7693
+ cat: "animal",
7694
+ dog: "animal",
7695
+ horse: "animal",
7696
+ sheep: "animal",
7697
+ cow: "animal",
7698
+ elephant: "animal",
7699
+ bear: "animal",
7700
+ zebra: "animal",
7701
+ giraffe: "animal",
7702
+ suitcase: "package",
7703
+ backpack: "package",
7704
+ handbag: "package"
7705
+ },
7706
+ preserveOriginal: false
7707
+ };
7708
+ var AUDIO_MACRO_LABELS = [
7709
+ {
7710
+ id: "speech",
7711
+ name: "Speech",
7712
+ icon: "🗣️"
7713
+ },
7714
+ {
7715
+ id: "scream",
7716
+ name: "Scream / Shout",
7717
+ icon: "😱"
7718
+ },
7719
+ {
7720
+ id: "crying",
7721
+ name: "Crying / Baby",
7722
+ icon: "😢"
7723
+ },
7724
+ {
7725
+ id: "laughter",
7726
+ name: "Laughter",
7727
+ icon: "😂"
7728
+ },
7729
+ {
7730
+ id: "music",
7731
+ name: "Music",
7732
+ icon: "🎵"
7733
+ },
7734
+ {
7735
+ id: "dog",
7736
+ name: "Dog",
7737
+ icon: "🐕"
7738
+ },
7739
+ {
7740
+ id: "cat",
7741
+ name: "Cat",
7742
+ icon: "🐈"
7743
+ },
7744
+ {
7745
+ id: "bird",
7746
+ name: "Bird",
7747
+ icon: "🐦"
7748
+ },
7749
+ {
7750
+ id: "animal",
7751
+ name: "Animal (other)",
7752
+ icon: "🐾"
7753
+ },
7754
+ {
7755
+ id: "alarm",
7756
+ name: "Alarm / Siren",
7757
+ icon: "🚨"
7758
+ },
7759
+ {
7760
+ id: "doorbell",
7761
+ name: "Doorbell / Knock",
7762
+ icon: "🔔"
7763
+ },
7764
+ {
7765
+ id: "glass_breaking",
7766
+ name: "Glass Breaking",
7767
+ icon: "💥"
7768
+ },
7769
+ {
7770
+ id: "gunshot",
7771
+ name: "Gunshot / Explosion",
7772
+ icon: "💣"
7773
+ },
7774
+ {
7775
+ id: "vehicle",
7776
+ name: "Vehicle",
7777
+ icon: "🚗"
7778
+ },
7779
+ {
7780
+ id: "siren",
7781
+ name: "Emergency Siren",
7782
+ icon: "🚑"
7783
+ },
7784
+ {
7785
+ id: "fire",
7786
+ name: "Fire / Smoke",
7787
+ icon: "🔥"
7788
+ },
7789
+ {
7790
+ id: "water",
7791
+ name: "Water",
7792
+ icon: "💧"
7793
+ },
7794
+ {
7795
+ id: "wind",
7796
+ name: "Wind / Weather",
7797
+ icon: "🌬️"
7798
+ },
7799
+ {
7800
+ id: "door",
7801
+ name: "Door",
7802
+ icon: "🚪"
7803
+ },
7804
+ {
7805
+ id: "footsteps",
7806
+ name: "Footsteps",
7807
+ icon: "👣"
7808
+ },
7809
+ {
7810
+ id: "crowd",
7811
+ name: "Crowd / Chatter",
7812
+ icon: "👥"
7813
+ },
7814
+ {
7815
+ id: "telephone",
7816
+ name: "Telephone",
7817
+ icon: "📞"
7818
+ },
7819
+ {
7820
+ id: "engine",
7821
+ name: "Engine / Motor",
7822
+ icon: "⚙️"
7823
+ },
7824
+ {
7825
+ id: "tools",
7826
+ name: "Tools / Construction",
7827
+ icon: "🔨"
7828
+ },
7829
+ {
7830
+ id: "silence",
7831
+ name: "Silence",
7832
+ icon: "🤫"
7833
+ }
7834
+ ];
7625
7835
  var YAMNET_TO_MACRO = {
7626
7836
  mapping: {
7627
7837
  Speech: "speech",
@@ -7888,6 +8098,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7888
8098
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7889
8099
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7890
8100
  /**
8101
+ * Unified event-kind taxonomy — THE single source of truth for
8102
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8103
+ * icon }`.
8104
+ *
8105
+ * This dictionary folds together what used to be scattered across four
8106
+ * copies:
8107
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8108
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8109
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8110
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8111
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8112
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8113
+ *
8114
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8115
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8116
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8117
+ * color or an icon: they read this dictionary (server descriptors carry the
8118
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8119
+ *
8120
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8121
+ */
8122
+ var TAXONOMY_COLORS = {
8123
+ motion: "#f59e0b",
8124
+ audio: "#06b6d4",
8125
+ person: "#22c55e",
8126
+ vehicle: "#3b82f6",
8127
+ animal: "#f97316",
8128
+ package: "#a855f7",
8129
+ sensor: "#8b5cf6",
8130
+ control: "#10b981",
8131
+ genericDetection: "#64748b"
8132
+ };
8133
+ var DETECTION_SUB_COLORS = {
8134
+ car: "#f59e0b",
8135
+ truck: "#d97706",
8136
+ bus: "#b45309",
8137
+ motorcycle: "#eab308",
8138
+ bicycle: "#ca8a04",
8139
+ airplane: "#60a5fa",
8140
+ boat: "#2563eb",
8141
+ train: "#1d4ed8",
8142
+ bird: "#14b8a6",
8143
+ dog: "#84cc16",
8144
+ cat: "#f97316",
8145
+ horse: "#a16207",
8146
+ sheep: "#a3a3a3",
8147
+ cow: "#78716c",
8148
+ elephant: "#6b7280",
8149
+ bear: "#7c2d12",
8150
+ zebra: "#404040",
8151
+ giraffe: "#d4a373"
8152
+ };
8153
+ function titleCase(id) {
8154
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8155
+ }
8156
+ var entries = /* @__PURE__ */ new Map();
8157
+ function macro(kind, category, color, iconId, label) {
8158
+ entries.set(kind, {
8159
+ kind,
8160
+ parentKind: null,
8161
+ level: "macro",
8162
+ category,
8163
+ color,
8164
+ iconId,
8165
+ labelKey: `eventKind.${kind}`,
8166
+ label
8167
+ });
8168
+ }
8169
+ function sub(kind, parentKind, category, color, iconId, label) {
8170
+ entries.set(kind, {
8171
+ kind,
8172
+ parentKind,
8173
+ level: "sub",
8174
+ category,
8175
+ color,
8176
+ iconId,
8177
+ labelKey: `eventKind.${kind}`,
8178
+ label
8179
+ });
8180
+ }
8181
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8182
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8183
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8184
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8185
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8186
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8187
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8188
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8189
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8190
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8191
+ if (entries.has(cocoClass)) continue;
8192
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8193
+ }
8194
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8195
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8196
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8197
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8198
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8199
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8200
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8201
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8202
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8203
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8204
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8205
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8206
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8207
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8208
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8209
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8210
+ sub("button", "control", "control", "#10b981", "button", "Button");
8211
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8212
+ for (const l of AUDIO_MACRO_LABELS) {
8213
+ const kind = `audio-${l.id}`;
8214
+ if (entries.has(kind)) continue;
8215
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8216
+ }
8217
+ /** The complete taxonomy dictionary, keyed by kind. */
8218
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8219
+ /**
7891
8220
  * Error types for the safe expression engine. Two distinct classes so callers
7892
8221
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7893
8222
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15862,17 +16191,30 @@ var EventKindCategorySchema = _enum([
15862
16191
  "audio",
15863
16192
  "detection",
15864
16193
  "sensor",
16194
+ "control",
15865
16195
  "custom",
15866
16196
  "package"
15867
16197
  ]);
16198
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16199
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15868
16200
  var EventKindDescriptorSchema = object({
15869
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16201
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15870
16202
  kind: string(),
16203
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16204
+ labelKey: string(),
16205
+ /** English fallback label (kept for clients that don't translate). */
15871
16206
  label: string(),
15872
16207
  /** Hex color for timeline/legend rendering. */
15873
16208
  color: string(),
16209
+ /** Dictionary id → lucide component on the UI side. */
16210
+ iconId: string(),
16211
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15874
16212
  icon: EventKindIconSchema,
15875
16213
  category: EventKindCategorySchema,
16214
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16215
+ parentKind: string().nullable(),
16216
+ /** Derived from `parentKind`, explicit for the client tree. */
16217
+ level: EventKindLevelSchema,
15876
16218
  /** Which cap + device contributes this kind. For built-ins the camera
15877
16219
  * itself; for sensor kinds the LINKED source device. */
15878
16220
  source: object({
@@ -15945,11 +16287,21 @@ var TrackAudioLabelSchema = object({
15945
16287
  firstAt: number$1(),
15946
16288
  lastAt: number$1()
15947
16289
  });
16290
+ /**
16291
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16292
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16293
+ * linked sensor/control state change (no positions; carries a snapshot). The
16294
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16295
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16296
+ */
16297
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15948
16298
  var TrackSchema = object({
15949
16299
  trackId: string(),
15950
16300
  deviceId: number$1(),
15951
16301
  className: string(),
15952
16302
  label: string().optional(),
16303
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16304
+ source: TrackSourceSchema.optional(),
15953
16305
  firstSeen: number$1(),
15954
16306
  lastSeen: number$1(),
15955
16307
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16204,6 +16556,26 @@ var TrackCascadeCountsSchema = object({
16204
16556
  /** Per-track CLIP search vectors removed (best-effort). */
16205
16557
  embeddings: number$1().int()
16206
16558
  });
16559
+ /** Event-store footprint for one camera. */
16560
+ var EventStoreDeviceFootprintSchema = object({
16561
+ deviceId: number$1(),
16562
+ /** Persisted event rows (motion + object + audio) for the camera. */
16563
+ rows: number$1().int(),
16564
+ /** Event-owned media bytes on disk for the camera. */
16565
+ bytes: number$1().int()
16566
+ });
16567
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16568
+ var EventStoreFootprintSchema = object({
16569
+ totalRows: number$1().int(),
16570
+ totalBytes: number$1().int(),
16571
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16572
+ });
16573
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16574
+ var EventPruneCountsSchema = object({
16575
+ motion: number$1().int(),
16576
+ object: number$1().int(),
16577
+ audio: number$1().int()
16578
+ });
16207
16579
  DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).readonly()), method(object({
16208
16580
  deviceId: number$1(),
16209
16581
  trackId: string()
@@ -16267,6 +16639,21 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
16267
16639
  }), {
16268
16640
  kind: "mutation",
16269
16641
  auth: "admin"
16642
+ }), method(object({}), EventStoreFootprintSchema, {
16643
+ kind: "query",
16644
+ auth: "admin"
16645
+ }), method(object({
16646
+ olderThanMs: number$1(),
16647
+ reason: OpsLogReasonSchema.optional()
16648
+ }), EventPruneCountsSchema, {
16649
+ kind: "mutation",
16650
+ auth: "admin"
16651
+ }), method(object({ deviceId: number$1() }), EventPruneCountsSchema, {
16652
+ kind: "mutation",
16653
+ auth: "admin"
16654
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16655
+ kind: "query",
16656
+ auth: "admin"
16270
16657
  }), method(object({
16271
16658
  eventId: string(),
16272
16659
  kind: MediaFileKindEnum.optional()
@@ -16291,6 +16678,76 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
16291
16678
  eventId: string(),
16292
16679
  timestamp: number$1()
16293
16680
  });
16681
+ /**
16682
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16683
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16684
+ * caps into per-camera event-kind descriptors.
16685
+ *
16686
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16687
+ * is NOT duplicated here — every entry is derived from the single
16688
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16689
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16690
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16691
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16692
+ * eventful cap is missing.
16693
+ */
16694
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16695
+ var LEGACY_ICON = {
16696
+ motion: "motion",
16697
+ audio: "audio",
16698
+ person: "person",
16699
+ vehicle: "vehicle",
16700
+ animal: "animal",
16701
+ package: "package",
16702
+ door: "door",
16703
+ pir: "pir",
16704
+ smoke: "smoke",
16705
+ water: "water",
16706
+ button: "button",
16707
+ generic: "generic",
16708
+ gas: "smoke",
16709
+ vibration: "generic",
16710
+ tamper: "generic",
16711
+ presence: "person",
16712
+ lock: "generic",
16713
+ siren: "generic",
16714
+ switch: "generic",
16715
+ doorbell: "button"
16716
+ };
16717
+ function legacyIcon(iconId) {
16718
+ return LEGACY_ICON[iconId] ?? "generic";
16719
+ }
16720
+ /**
16721
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16722
+ * The anti-drift guard cross-checks this against the eventful caps declared
16723
+ * in `packages/types/src/capabilities/*.cap.ts`.
16724
+ */
16725
+ var CAP_TO_KIND = {
16726
+ contact: "contact",
16727
+ motion: "motion-sensor",
16728
+ smoke: "smoke",
16729
+ flood: "flood",
16730
+ gas: "gas",
16731
+ "carbon-monoxide": "carbon-monoxide",
16732
+ vibration: "vibration",
16733
+ tamper: "tamper",
16734
+ presence: "presence",
16735
+ "enum-sensor": "enum-sensor",
16736
+ "event-emitter": "device-event",
16737
+ "lock-control": "lock",
16738
+ switch: "switch",
16739
+ button: "button",
16740
+ doorbell: "doorbell"
16741
+ };
16742
+ function buildDescriptor(capName, kind) {
16743
+ const t = EVENT_TAXONOMY[kind];
16744
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16745
+ return {
16746
+ ...t,
16747
+ icon: legacyIcon(t.iconId)
16748
+ };
16749
+ }
16750
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16294
16751
  var CameraPipelineConfigSchema = object({
16295
16752
  engine: PipelineEngineChoiceSchema.optional(),
16296
16753
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19500,13 +19957,29 @@ method(object({
19500
19957
  }), method(object({ deviceId: number$1() }), RecordingStatusSchema, {
19501
19958
  kind: "mutation",
19502
19959
  auth: "admin"
19503
- }), method(object({ deviceId: number$1() }), object({
19960
+ }), method(object({
19961
+ deviceId: number$1(),
19962
+ reason: OpsLogReasonSchema.optional()
19963
+ }), object({
19504
19964
  floorMs: number$1().nullable(),
19505
19965
  deletedBuckets: number$1().int(),
19506
19966
  reclaimedBytes: number$1().int()
19507
19967
  }), {
19508
19968
  kind: "mutation",
19509
19969
  auth: "admin"
19970
+ }), method(object({
19971
+ deviceId: number$1(),
19972
+ fromMs: number$1().optional(),
19973
+ toMs: number$1().optional()
19974
+ }), object({
19975
+ deletedBuckets: number$1().int(),
19976
+ reclaimedBytes: number$1().int()
19977
+ }), {
19978
+ kind: "mutation",
19979
+ auth: "admin"
19980
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19981
+ kind: "query",
19982
+ auth: "admin"
19510
19983
  });
19511
19984
  /**
19512
19985
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22628,6 +23101,12 @@ Object.freeze({
22628
23101
  addonId: null,
22629
23102
  access: "delete"
22630
23103
  },
23104
+ "pipelineAnalytics.deleteDeviceEvents": {
23105
+ capName: "pipeline-analytics",
23106
+ capScope: "device",
23107
+ addonId: null,
23108
+ access: "delete"
23109
+ },
22631
23110
  "pipelineAnalytics.deleteTracks": {
22632
23111
  capName: "pipeline-analytics",
22633
23112
  capScope: "device",
@@ -22658,6 +23137,12 @@ Object.freeze({
22658
23137
  addonId: null,
22659
23138
  access: "view"
22660
23139
  },
23140
+ "pipelineAnalytics.getEventStoreFootprint": {
23141
+ capName: "pipeline-analytics",
23142
+ capScope: "device",
23143
+ addonId: null,
23144
+ access: "view"
23145
+ },
22661
23146
  "pipelineAnalytics.getKeyEvents": {
22662
23147
  capName: "pipeline-analytics",
22663
23148
  capScope: "device",
@@ -22700,6 +23185,12 @@ Object.freeze({
22700
23185
  addonId: null,
22701
23186
  access: "view"
22702
23187
  },
23188
+ "pipelineAnalytics.listOpsLog": {
23189
+ capName: "pipeline-analytics",
23190
+ capScope: "device",
23191
+ addonId: null,
23192
+ access: "view"
23193
+ },
22703
23194
  "pipelineAnalytics.listRecentTracks": {
22704
23195
  capName: "pipeline-analytics",
22705
23196
  capScope: "device",
@@ -22712,6 +23203,12 @@ Object.freeze({
22712
23203
  addonId: null,
22713
23204
  access: "view"
22714
23205
  },
23206
+ "pipelineAnalytics.pruneEvents": {
23207
+ capName: "pipeline-analytics",
23208
+ capScope: "device",
23209
+ addonId: null,
23210
+ access: "create"
23211
+ },
22715
23212
  "pipelineAnalytics.pruneEventsBefore": {
22716
23213
  capName: "pipeline-analytics",
22717
23214
  capScope: "device",
@@ -23474,6 +23971,12 @@ Object.freeze({
23474
23971
  addonId: null,
23475
23972
  access: "create"
23476
23973
  },
23974
+ "recording.deleteFootprint": {
23975
+ capName: "recording",
23976
+ capScope: "system",
23977
+ addonId: null,
23978
+ access: "delete"
23979
+ },
23477
23980
  "recording.getAvailability": {
23478
23981
  capName: "recording",
23479
23982
  capScope: "system",
@@ -23504,6 +24007,12 @@ Object.freeze({
23504
24007
  addonId: null,
23505
24008
  access: "view"
23506
24009
  },
24010
+ "recording.listOpsLog": {
24011
+ capName: "recording",
24012
+ capScope: "system",
24013
+ addonId: null,
24014
+ access: "view"
24015
+ },
23507
24016
  "recording.locateSegment": {
23508
24017
  capName: "recording",
23509
24018
  capScope: "system",
@@ -7377,6 +7377,62 @@ var RecordingConfigSchema = object({
7377
7377
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7378
7378
  });
7379
7379
  /**
7380
+ * Ops-log — the durable, append-only operations audit shared by the
7381
+ * recordings and events management surfaces.
7382
+ *
7383
+ * ONE row shape is reused for both domains so a single "Activity" view can
7384
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7385
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7386
+ * management operation, WHY it ran (reason), and its measurable effect
7387
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7388
+ * never fail the operation it records.
7389
+ */
7390
+ /** Which management domain the operation belongs to. */
7391
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7392
+ /** The kind of management operation performed. */
7393
+ var OpsLogOpSchema = _enum([
7394
+ "prune",
7395
+ "manual-delete",
7396
+ "rescan",
7397
+ "retention-run"
7398
+ ]);
7399
+ /** Why the operation ran. */
7400
+ var OpsLogReasonSchema = _enum([
7401
+ "retention",
7402
+ "quota",
7403
+ "manual",
7404
+ "operator"
7405
+ ]);
7406
+ /** One audit row, shared verbatim by both domains. */
7407
+ var OpsLogEntrySchema = object({
7408
+ /** Unique row id. */
7409
+ id: string(),
7410
+ /** Epoch ms the operation completed. */
7411
+ at: number$1(),
7412
+ domain: OpsLogDomainSchema,
7413
+ op: OpsLogOpSchema,
7414
+ reason: OpsLogReasonSchema,
7415
+ /** The camera the op targeted; null for a cluster/global op. */
7416
+ deviceId: number$1().nullable(),
7417
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7418
+ nodeId: string(),
7419
+ /** Buckets / rows deleted (op-specific unit). */
7420
+ itemsAffected: number$1(),
7421
+ /** Bytes reclaimed by the op (0 when not measurable). */
7422
+ bytesReclaimed: number$1(),
7423
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7424
+ detail: string().nullable(),
7425
+ /** Who/what triggered the op. */
7426
+ actor: string()
7427
+ });
7428
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7429
+ var OpsLogQueryInputSchema = object({
7430
+ /** Restrict to a single camera; omit for every row. */
7431
+ deviceId: number$1().optional(),
7432
+ /** Max rows returned, newest-first. */
7433
+ limit: number$1().int().min(1).max(1e3).optional()
7434
+ });
7435
+ /**
7380
7436
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7381
7437
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7382
7438
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7620,6 +7676,160 @@ var EncodeProfileSchema = object({
7620
7676
  */
7621
7677
  outputArgs: array(string()).optional()
7622
7678
  });
7679
+ var COCO_TO_MACRO = {
7680
+ mapping: {
7681
+ person: "person",
7682
+ bicycle: "vehicle",
7683
+ car: "vehicle",
7684
+ motorcycle: "vehicle",
7685
+ airplane: "vehicle",
7686
+ bus: "vehicle",
7687
+ train: "vehicle",
7688
+ truck: "vehicle",
7689
+ boat: "vehicle",
7690
+ bird: "animal",
7691
+ cat: "animal",
7692
+ dog: "animal",
7693
+ horse: "animal",
7694
+ sheep: "animal",
7695
+ cow: "animal",
7696
+ elephant: "animal",
7697
+ bear: "animal",
7698
+ zebra: "animal",
7699
+ giraffe: "animal",
7700
+ suitcase: "package",
7701
+ backpack: "package",
7702
+ handbag: "package"
7703
+ },
7704
+ preserveOriginal: false
7705
+ };
7706
+ var AUDIO_MACRO_LABELS = [
7707
+ {
7708
+ id: "speech",
7709
+ name: "Speech",
7710
+ icon: "🗣️"
7711
+ },
7712
+ {
7713
+ id: "scream",
7714
+ name: "Scream / Shout",
7715
+ icon: "😱"
7716
+ },
7717
+ {
7718
+ id: "crying",
7719
+ name: "Crying / Baby",
7720
+ icon: "😢"
7721
+ },
7722
+ {
7723
+ id: "laughter",
7724
+ name: "Laughter",
7725
+ icon: "😂"
7726
+ },
7727
+ {
7728
+ id: "music",
7729
+ name: "Music",
7730
+ icon: "🎵"
7731
+ },
7732
+ {
7733
+ id: "dog",
7734
+ name: "Dog",
7735
+ icon: "🐕"
7736
+ },
7737
+ {
7738
+ id: "cat",
7739
+ name: "Cat",
7740
+ icon: "🐈"
7741
+ },
7742
+ {
7743
+ id: "bird",
7744
+ name: "Bird",
7745
+ icon: "🐦"
7746
+ },
7747
+ {
7748
+ id: "animal",
7749
+ name: "Animal (other)",
7750
+ icon: "🐾"
7751
+ },
7752
+ {
7753
+ id: "alarm",
7754
+ name: "Alarm / Siren",
7755
+ icon: "🚨"
7756
+ },
7757
+ {
7758
+ id: "doorbell",
7759
+ name: "Doorbell / Knock",
7760
+ icon: "🔔"
7761
+ },
7762
+ {
7763
+ id: "glass_breaking",
7764
+ name: "Glass Breaking",
7765
+ icon: "💥"
7766
+ },
7767
+ {
7768
+ id: "gunshot",
7769
+ name: "Gunshot / Explosion",
7770
+ icon: "💣"
7771
+ },
7772
+ {
7773
+ id: "vehicle",
7774
+ name: "Vehicle",
7775
+ icon: "🚗"
7776
+ },
7777
+ {
7778
+ id: "siren",
7779
+ name: "Emergency Siren",
7780
+ icon: "🚑"
7781
+ },
7782
+ {
7783
+ id: "fire",
7784
+ name: "Fire / Smoke",
7785
+ icon: "🔥"
7786
+ },
7787
+ {
7788
+ id: "water",
7789
+ name: "Water",
7790
+ icon: "💧"
7791
+ },
7792
+ {
7793
+ id: "wind",
7794
+ name: "Wind / Weather",
7795
+ icon: "🌬️"
7796
+ },
7797
+ {
7798
+ id: "door",
7799
+ name: "Door",
7800
+ icon: "🚪"
7801
+ },
7802
+ {
7803
+ id: "footsteps",
7804
+ name: "Footsteps",
7805
+ icon: "👣"
7806
+ },
7807
+ {
7808
+ id: "crowd",
7809
+ name: "Crowd / Chatter",
7810
+ icon: "👥"
7811
+ },
7812
+ {
7813
+ id: "telephone",
7814
+ name: "Telephone",
7815
+ icon: "📞"
7816
+ },
7817
+ {
7818
+ id: "engine",
7819
+ name: "Engine / Motor",
7820
+ icon: "⚙️"
7821
+ },
7822
+ {
7823
+ id: "tools",
7824
+ name: "Tools / Construction",
7825
+ icon: "🔨"
7826
+ },
7827
+ {
7828
+ id: "silence",
7829
+ name: "Silence",
7830
+ icon: "🤫"
7831
+ }
7832
+ ];
7623
7833
  var YAMNET_TO_MACRO = {
7624
7834
  mapping: {
7625
7835
  Speech: "speech",
@@ -7886,6 +8096,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7886
8096
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7887
8097
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7888
8098
  /**
8099
+ * Unified event-kind taxonomy — THE single source of truth for
8100
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8101
+ * icon }`.
8102
+ *
8103
+ * This dictionary folds together what used to be scattered across four
8104
+ * copies:
8105
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8106
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8107
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8108
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8109
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8110
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8111
+ *
8112
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8113
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8114
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8115
+ * color or an icon: they read this dictionary (server descriptors carry the
8116
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8117
+ *
8118
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8119
+ */
8120
+ var TAXONOMY_COLORS = {
8121
+ motion: "#f59e0b",
8122
+ audio: "#06b6d4",
8123
+ person: "#22c55e",
8124
+ vehicle: "#3b82f6",
8125
+ animal: "#f97316",
8126
+ package: "#a855f7",
8127
+ sensor: "#8b5cf6",
8128
+ control: "#10b981",
8129
+ genericDetection: "#64748b"
8130
+ };
8131
+ var DETECTION_SUB_COLORS = {
8132
+ car: "#f59e0b",
8133
+ truck: "#d97706",
8134
+ bus: "#b45309",
8135
+ motorcycle: "#eab308",
8136
+ bicycle: "#ca8a04",
8137
+ airplane: "#60a5fa",
8138
+ boat: "#2563eb",
8139
+ train: "#1d4ed8",
8140
+ bird: "#14b8a6",
8141
+ dog: "#84cc16",
8142
+ cat: "#f97316",
8143
+ horse: "#a16207",
8144
+ sheep: "#a3a3a3",
8145
+ cow: "#78716c",
8146
+ elephant: "#6b7280",
8147
+ bear: "#7c2d12",
8148
+ zebra: "#404040",
8149
+ giraffe: "#d4a373"
8150
+ };
8151
+ function titleCase(id) {
8152
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8153
+ }
8154
+ var entries = /* @__PURE__ */ new Map();
8155
+ function macro(kind, category, color, iconId, label) {
8156
+ entries.set(kind, {
8157
+ kind,
8158
+ parentKind: null,
8159
+ level: "macro",
8160
+ category,
8161
+ color,
8162
+ iconId,
8163
+ labelKey: `eventKind.${kind}`,
8164
+ label
8165
+ });
8166
+ }
8167
+ function sub(kind, parentKind, category, color, iconId, label) {
8168
+ entries.set(kind, {
8169
+ kind,
8170
+ parentKind,
8171
+ level: "sub",
8172
+ category,
8173
+ color,
8174
+ iconId,
8175
+ labelKey: `eventKind.${kind}`,
8176
+ label
8177
+ });
8178
+ }
8179
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8180
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8181
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8182
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8183
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8184
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8185
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8186
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8187
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8188
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8189
+ if (entries.has(cocoClass)) continue;
8190
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8191
+ }
8192
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8193
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8194
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8195
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8196
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8197
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8198
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8199
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8200
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8201
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8202
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8203
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8204
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8205
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8206
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8207
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8208
+ sub("button", "control", "control", "#10b981", "button", "Button");
8209
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8210
+ for (const l of AUDIO_MACRO_LABELS) {
8211
+ const kind = `audio-${l.id}`;
8212
+ if (entries.has(kind)) continue;
8213
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8214
+ }
8215
+ /** The complete taxonomy dictionary, keyed by kind. */
8216
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8217
+ /**
7889
8218
  * Error types for the safe expression engine. Two distinct classes so callers
7890
8219
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7891
8220
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15860,17 +16189,30 @@ var EventKindCategorySchema = _enum([
15860
16189
  "audio",
15861
16190
  "detection",
15862
16191
  "sensor",
16192
+ "control",
15863
16193
  "custom",
15864
16194
  "package"
15865
16195
  ]);
16196
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16197
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15866
16198
  var EventKindDescriptorSchema = object({
15867
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16199
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15868
16200
  kind: string(),
16201
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16202
+ labelKey: string(),
16203
+ /** English fallback label (kept for clients that don't translate). */
15869
16204
  label: string(),
15870
16205
  /** Hex color for timeline/legend rendering. */
15871
16206
  color: string(),
16207
+ /** Dictionary id → lucide component on the UI side. */
16208
+ iconId: string(),
16209
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15872
16210
  icon: EventKindIconSchema,
15873
16211
  category: EventKindCategorySchema,
16212
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16213
+ parentKind: string().nullable(),
16214
+ /** Derived from `parentKind`, explicit for the client tree. */
16215
+ level: EventKindLevelSchema,
15874
16216
  /** Which cap + device contributes this kind. For built-ins the camera
15875
16217
  * itself; for sensor kinds the LINKED source device. */
15876
16218
  source: object({
@@ -15943,11 +16285,21 @@ var TrackAudioLabelSchema = object({
15943
16285
  firstAt: number$1(),
15944
16286
  lastAt: number$1()
15945
16287
  });
16288
+ /**
16289
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16290
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16291
+ * linked sensor/control state change (no positions; carries a snapshot). The
16292
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16293
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16294
+ */
16295
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15946
16296
  var TrackSchema = object({
15947
16297
  trackId: string(),
15948
16298
  deviceId: number$1(),
15949
16299
  className: string(),
15950
16300
  label: string().optional(),
16301
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16302
+ source: TrackSourceSchema.optional(),
15951
16303
  firstSeen: number$1(),
15952
16304
  lastSeen: number$1(),
15953
16305
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16202,6 +16554,26 @@ var TrackCascadeCountsSchema = object({
16202
16554
  /** Per-track CLIP search vectors removed (best-effort). */
16203
16555
  embeddings: number$1().int()
16204
16556
  });
16557
+ /** Event-store footprint for one camera. */
16558
+ var EventStoreDeviceFootprintSchema = object({
16559
+ deviceId: number$1(),
16560
+ /** Persisted event rows (motion + object + audio) for the camera. */
16561
+ rows: number$1().int(),
16562
+ /** Event-owned media bytes on disk for the camera. */
16563
+ bytes: number$1().int()
16564
+ });
16565
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16566
+ var EventStoreFootprintSchema = object({
16567
+ totalRows: number$1().int(),
16568
+ totalBytes: number$1().int(),
16569
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16570
+ });
16571
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16572
+ var EventPruneCountsSchema = object({
16573
+ motion: number$1().int(),
16574
+ object: number$1().int(),
16575
+ audio: number$1().int()
16576
+ });
16205
16577
  DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).readonly()), method(object({
16206
16578
  deviceId: number$1(),
16207
16579
  trackId: string()
@@ -16265,6 +16637,21 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
16265
16637
  }), {
16266
16638
  kind: "mutation",
16267
16639
  auth: "admin"
16640
+ }), method(object({}), EventStoreFootprintSchema, {
16641
+ kind: "query",
16642
+ auth: "admin"
16643
+ }), method(object({
16644
+ olderThanMs: number$1(),
16645
+ reason: OpsLogReasonSchema.optional()
16646
+ }), EventPruneCountsSchema, {
16647
+ kind: "mutation",
16648
+ auth: "admin"
16649
+ }), method(object({ deviceId: number$1() }), EventPruneCountsSchema, {
16650
+ kind: "mutation",
16651
+ auth: "admin"
16652
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16653
+ kind: "query",
16654
+ auth: "admin"
16268
16655
  }), method(object({
16269
16656
  eventId: string(),
16270
16657
  kind: MediaFileKindEnum.optional()
@@ -16289,6 +16676,76 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
16289
16676
  eventId: string(),
16290
16677
  timestamp: number$1()
16291
16678
  });
16679
+ /**
16680
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16681
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16682
+ * caps into per-camera event-kind descriptors.
16683
+ *
16684
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16685
+ * is NOT duplicated here — every entry is derived from the single
16686
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16687
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16688
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16689
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16690
+ * eventful cap is missing.
16691
+ */
16692
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16693
+ var LEGACY_ICON = {
16694
+ motion: "motion",
16695
+ audio: "audio",
16696
+ person: "person",
16697
+ vehicle: "vehicle",
16698
+ animal: "animal",
16699
+ package: "package",
16700
+ door: "door",
16701
+ pir: "pir",
16702
+ smoke: "smoke",
16703
+ water: "water",
16704
+ button: "button",
16705
+ generic: "generic",
16706
+ gas: "smoke",
16707
+ vibration: "generic",
16708
+ tamper: "generic",
16709
+ presence: "person",
16710
+ lock: "generic",
16711
+ siren: "generic",
16712
+ switch: "generic",
16713
+ doorbell: "button"
16714
+ };
16715
+ function legacyIcon(iconId) {
16716
+ return LEGACY_ICON[iconId] ?? "generic";
16717
+ }
16718
+ /**
16719
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16720
+ * The anti-drift guard cross-checks this against the eventful caps declared
16721
+ * in `packages/types/src/capabilities/*.cap.ts`.
16722
+ */
16723
+ var CAP_TO_KIND = {
16724
+ contact: "contact",
16725
+ motion: "motion-sensor",
16726
+ smoke: "smoke",
16727
+ flood: "flood",
16728
+ gas: "gas",
16729
+ "carbon-monoxide": "carbon-monoxide",
16730
+ vibration: "vibration",
16731
+ tamper: "tamper",
16732
+ presence: "presence",
16733
+ "enum-sensor": "enum-sensor",
16734
+ "event-emitter": "device-event",
16735
+ "lock-control": "lock",
16736
+ switch: "switch",
16737
+ button: "button",
16738
+ doorbell: "doorbell"
16739
+ };
16740
+ function buildDescriptor(capName, kind) {
16741
+ const t = EVENT_TAXONOMY[kind];
16742
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16743
+ return {
16744
+ ...t,
16745
+ icon: legacyIcon(t.iconId)
16746
+ };
16747
+ }
16748
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16292
16749
  var CameraPipelineConfigSchema = object({
16293
16750
  engine: PipelineEngineChoiceSchema.optional(),
16294
16751
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19498,13 +19955,29 @@ method(object({
19498
19955
  }), method(object({ deviceId: number$1() }), RecordingStatusSchema, {
19499
19956
  kind: "mutation",
19500
19957
  auth: "admin"
19501
- }), method(object({ deviceId: number$1() }), object({
19958
+ }), method(object({
19959
+ deviceId: number$1(),
19960
+ reason: OpsLogReasonSchema.optional()
19961
+ }), object({
19502
19962
  floorMs: number$1().nullable(),
19503
19963
  deletedBuckets: number$1().int(),
19504
19964
  reclaimedBytes: number$1().int()
19505
19965
  }), {
19506
19966
  kind: "mutation",
19507
19967
  auth: "admin"
19968
+ }), method(object({
19969
+ deviceId: number$1(),
19970
+ fromMs: number$1().optional(),
19971
+ toMs: number$1().optional()
19972
+ }), object({
19973
+ deletedBuckets: number$1().int(),
19974
+ reclaimedBytes: number$1().int()
19975
+ }), {
19976
+ kind: "mutation",
19977
+ auth: "admin"
19978
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19979
+ kind: "query",
19980
+ auth: "admin"
19508
19981
  });
19509
19982
  /**
19510
19983
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22626,6 +23099,12 @@ Object.freeze({
22626
23099
  addonId: null,
22627
23100
  access: "delete"
22628
23101
  },
23102
+ "pipelineAnalytics.deleteDeviceEvents": {
23103
+ capName: "pipeline-analytics",
23104
+ capScope: "device",
23105
+ addonId: null,
23106
+ access: "delete"
23107
+ },
22629
23108
  "pipelineAnalytics.deleteTracks": {
22630
23109
  capName: "pipeline-analytics",
22631
23110
  capScope: "device",
@@ -22656,6 +23135,12 @@ Object.freeze({
22656
23135
  addonId: null,
22657
23136
  access: "view"
22658
23137
  },
23138
+ "pipelineAnalytics.getEventStoreFootprint": {
23139
+ capName: "pipeline-analytics",
23140
+ capScope: "device",
23141
+ addonId: null,
23142
+ access: "view"
23143
+ },
22659
23144
  "pipelineAnalytics.getKeyEvents": {
22660
23145
  capName: "pipeline-analytics",
22661
23146
  capScope: "device",
@@ -22698,6 +23183,12 @@ Object.freeze({
22698
23183
  addonId: null,
22699
23184
  access: "view"
22700
23185
  },
23186
+ "pipelineAnalytics.listOpsLog": {
23187
+ capName: "pipeline-analytics",
23188
+ capScope: "device",
23189
+ addonId: null,
23190
+ access: "view"
23191
+ },
22701
23192
  "pipelineAnalytics.listRecentTracks": {
22702
23193
  capName: "pipeline-analytics",
22703
23194
  capScope: "device",
@@ -22710,6 +23201,12 @@ Object.freeze({
22710
23201
  addonId: null,
22711
23202
  access: "view"
22712
23203
  },
23204
+ "pipelineAnalytics.pruneEvents": {
23205
+ capName: "pipeline-analytics",
23206
+ capScope: "device",
23207
+ addonId: null,
23208
+ access: "create"
23209
+ },
22713
23210
  "pipelineAnalytics.pruneEventsBefore": {
22714
23211
  capName: "pipeline-analytics",
22715
23212
  capScope: "device",
@@ -23472,6 +23969,12 @@ Object.freeze({
23472
23969
  addonId: null,
23473
23970
  access: "create"
23474
23971
  },
23972
+ "recording.deleteFootprint": {
23973
+ capName: "recording",
23974
+ capScope: "system",
23975
+ addonId: null,
23976
+ access: "delete"
23977
+ },
23475
23978
  "recording.getAvailability": {
23476
23979
  capName: "recording",
23477
23980
  capScope: "system",
@@ -23502,6 +24005,12 @@ Object.freeze({
23502
24005
  addonId: null,
23503
24006
  access: "view"
23504
24007
  },
24008
+ "recording.listOpsLog": {
24009
+ capName: "recording",
24010
+ capScope: "system",
24011
+ addonId: null,
24012
+ access: "view"
24013
+ },
23505
24014
  "recording.locateSegment": {
23506
24015
  capName: "recording",
23507
24016
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-ha-mqtt",
3
- "version": "1.1.25",
3
+ "version": "1.1.27",
4
4
  "description": "HomeAssistant MQTT discovery exporter for CamStack devices. Publishes discovery topics so HA auto-creates entities for exposed cameras/switches/intercoms/sensors.",
5
5
  "keywords": [
6
6
  "camstack",