@camstack/addon-provider-homeassistant 1.1.30 → 1.1.32

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -7463,6 +7463,62 @@ var RecordingConfigSchema = object({
7463
7463
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7464
7464
  });
7465
7465
  /**
7466
+ * Ops-log — the durable, append-only operations audit shared by the
7467
+ * recordings and events management surfaces.
7468
+ *
7469
+ * ONE row shape is reused for both domains so a single "Activity" view can
7470
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7471
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7472
+ * management operation, WHY it ran (reason), and its measurable effect
7473
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7474
+ * never fail the operation it records.
7475
+ */
7476
+ /** Which management domain the operation belongs to. */
7477
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7478
+ /** The kind of management operation performed. */
7479
+ var OpsLogOpSchema = _enum([
7480
+ "prune",
7481
+ "manual-delete",
7482
+ "rescan",
7483
+ "retention-run"
7484
+ ]);
7485
+ /** Why the operation ran. */
7486
+ var OpsLogReasonSchema = _enum([
7487
+ "retention",
7488
+ "quota",
7489
+ "manual",
7490
+ "operator"
7491
+ ]);
7492
+ /** One audit row, shared verbatim by both domains. */
7493
+ var OpsLogEntrySchema = object({
7494
+ /** Unique row id. */
7495
+ id: string(),
7496
+ /** Epoch ms the operation completed. */
7497
+ at: number(),
7498
+ domain: OpsLogDomainSchema,
7499
+ op: OpsLogOpSchema,
7500
+ reason: OpsLogReasonSchema,
7501
+ /** The camera the op targeted; null for a cluster/global op. */
7502
+ deviceId: number().nullable(),
7503
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7504
+ nodeId: string(),
7505
+ /** Buckets / rows deleted (op-specific unit). */
7506
+ itemsAffected: number(),
7507
+ /** Bytes reclaimed by the op (0 when not measurable). */
7508
+ bytesReclaimed: number(),
7509
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7510
+ detail: string().nullable(),
7511
+ /** Who/what triggered the op. */
7512
+ actor: string()
7513
+ });
7514
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7515
+ var OpsLogQueryInputSchema = object({
7516
+ /** Restrict to a single camera; omit for every row. */
7517
+ deviceId: number().optional(),
7518
+ /** Max rows returned, newest-first. */
7519
+ limit: number().int().min(1).max(1e3).optional()
7520
+ });
7521
+ /**
7466
7522
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7467
7523
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7468
7524
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7706,6 +7762,160 @@ var EncodeProfileSchema = object({
7706
7762
  */
7707
7763
  outputArgs: array(string()).optional()
7708
7764
  });
7765
+ var COCO_TO_MACRO = {
7766
+ mapping: {
7767
+ person: "person",
7768
+ bicycle: "vehicle",
7769
+ car: "vehicle",
7770
+ motorcycle: "vehicle",
7771
+ airplane: "vehicle",
7772
+ bus: "vehicle",
7773
+ train: "vehicle",
7774
+ truck: "vehicle",
7775
+ boat: "vehicle",
7776
+ bird: "animal",
7777
+ cat: "animal",
7778
+ dog: "animal",
7779
+ horse: "animal",
7780
+ sheep: "animal",
7781
+ cow: "animal",
7782
+ elephant: "animal",
7783
+ bear: "animal",
7784
+ zebra: "animal",
7785
+ giraffe: "animal",
7786
+ suitcase: "package",
7787
+ backpack: "package",
7788
+ handbag: "package"
7789
+ },
7790
+ preserveOriginal: false
7791
+ };
7792
+ var AUDIO_MACRO_LABELS = [
7793
+ {
7794
+ id: "speech",
7795
+ name: "Speech",
7796
+ icon: "🗣️"
7797
+ },
7798
+ {
7799
+ id: "scream",
7800
+ name: "Scream / Shout",
7801
+ icon: "😱"
7802
+ },
7803
+ {
7804
+ id: "crying",
7805
+ name: "Crying / Baby",
7806
+ icon: "😢"
7807
+ },
7808
+ {
7809
+ id: "laughter",
7810
+ name: "Laughter",
7811
+ icon: "😂"
7812
+ },
7813
+ {
7814
+ id: "music",
7815
+ name: "Music",
7816
+ icon: "🎵"
7817
+ },
7818
+ {
7819
+ id: "dog",
7820
+ name: "Dog",
7821
+ icon: "🐕"
7822
+ },
7823
+ {
7824
+ id: "cat",
7825
+ name: "Cat",
7826
+ icon: "🐈"
7827
+ },
7828
+ {
7829
+ id: "bird",
7830
+ name: "Bird",
7831
+ icon: "🐦"
7832
+ },
7833
+ {
7834
+ id: "animal",
7835
+ name: "Animal (other)",
7836
+ icon: "🐾"
7837
+ },
7838
+ {
7839
+ id: "alarm",
7840
+ name: "Alarm / Siren",
7841
+ icon: "🚨"
7842
+ },
7843
+ {
7844
+ id: "doorbell",
7845
+ name: "Doorbell / Knock",
7846
+ icon: "🔔"
7847
+ },
7848
+ {
7849
+ id: "glass_breaking",
7850
+ name: "Glass Breaking",
7851
+ icon: "💥"
7852
+ },
7853
+ {
7854
+ id: "gunshot",
7855
+ name: "Gunshot / Explosion",
7856
+ icon: "💣"
7857
+ },
7858
+ {
7859
+ id: "vehicle",
7860
+ name: "Vehicle",
7861
+ icon: "🚗"
7862
+ },
7863
+ {
7864
+ id: "siren",
7865
+ name: "Emergency Siren",
7866
+ icon: "🚑"
7867
+ },
7868
+ {
7869
+ id: "fire",
7870
+ name: "Fire / Smoke",
7871
+ icon: "🔥"
7872
+ },
7873
+ {
7874
+ id: "water",
7875
+ name: "Water",
7876
+ icon: "💧"
7877
+ },
7878
+ {
7879
+ id: "wind",
7880
+ name: "Wind / Weather",
7881
+ icon: "🌬️"
7882
+ },
7883
+ {
7884
+ id: "door",
7885
+ name: "Door",
7886
+ icon: "🚪"
7887
+ },
7888
+ {
7889
+ id: "footsteps",
7890
+ name: "Footsteps",
7891
+ icon: "👣"
7892
+ },
7893
+ {
7894
+ id: "crowd",
7895
+ name: "Crowd / Chatter",
7896
+ icon: "👥"
7897
+ },
7898
+ {
7899
+ id: "telephone",
7900
+ name: "Telephone",
7901
+ icon: "📞"
7902
+ },
7903
+ {
7904
+ id: "engine",
7905
+ name: "Engine / Motor",
7906
+ icon: "⚙️"
7907
+ },
7908
+ {
7909
+ id: "tools",
7910
+ name: "Tools / Construction",
7911
+ icon: "🔨"
7912
+ },
7913
+ {
7914
+ id: "silence",
7915
+ name: "Silence",
7916
+ icon: "🤫"
7917
+ }
7918
+ ];
7709
7919
  var YAMNET_TO_MACRO = {
7710
7920
  mapping: {
7711
7921
  Speech: "speech",
@@ -7972,6 +8182,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7972
8182
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7973
8183
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7974
8184
  /**
8185
+ * Unified event-kind taxonomy — THE single source of truth for
8186
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8187
+ * icon }`.
8188
+ *
8189
+ * This dictionary folds together what used to be scattered across four
8190
+ * copies:
8191
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8192
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8193
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8194
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8195
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8196
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8197
+ *
8198
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8199
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8200
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8201
+ * color or an icon: they read this dictionary (server descriptors carry the
8202
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8203
+ *
8204
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8205
+ */
8206
+ var TAXONOMY_COLORS = {
8207
+ motion: "#f59e0b",
8208
+ audio: "#06b6d4",
8209
+ person: "#22c55e",
8210
+ vehicle: "#3b82f6",
8211
+ animal: "#f97316",
8212
+ package: "#a855f7",
8213
+ sensor: "#8b5cf6",
8214
+ control: "#10b981",
8215
+ genericDetection: "#64748b"
8216
+ };
8217
+ var DETECTION_SUB_COLORS = {
8218
+ car: "#f59e0b",
8219
+ truck: "#d97706",
8220
+ bus: "#b45309",
8221
+ motorcycle: "#eab308",
8222
+ bicycle: "#ca8a04",
8223
+ airplane: "#60a5fa",
8224
+ boat: "#2563eb",
8225
+ train: "#1d4ed8",
8226
+ bird: "#14b8a6",
8227
+ dog: "#84cc16",
8228
+ cat: "#f97316",
8229
+ horse: "#a16207",
8230
+ sheep: "#a3a3a3",
8231
+ cow: "#78716c",
8232
+ elephant: "#6b7280",
8233
+ bear: "#7c2d12",
8234
+ zebra: "#404040",
8235
+ giraffe: "#d4a373"
8236
+ };
8237
+ function titleCase(id) {
8238
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8239
+ }
8240
+ var entries = /* @__PURE__ */ new Map();
8241
+ function macro(kind, category, color, iconId, label) {
8242
+ entries.set(kind, {
8243
+ kind,
8244
+ parentKind: null,
8245
+ level: "macro",
8246
+ category,
8247
+ color,
8248
+ iconId,
8249
+ labelKey: `eventKind.${kind}`,
8250
+ label
8251
+ });
8252
+ }
8253
+ function sub(kind, parentKind, category, color, iconId, label) {
8254
+ entries.set(kind, {
8255
+ kind,
8256
+ parentKind,
8257
+ level: "sub",
8258
+ category,
8259
+ color,
8260
+ iconId,
8261
+ labelKey: `eventKind.${kind}`,
8262
+ label
8263
+ });
8264
+ }
8265
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8266
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8267
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8268
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8269
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8270
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8271
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8272
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8273
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8274
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8275
+ if (entries.has(cocoClass)) continue;
8276
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8277
+ }
8278
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8279
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8280
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8281
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8282
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8283
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8284
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8285
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8286
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8287
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8288
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8289
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8290
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8291
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8292
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8293
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8294
+ sub("button", "control", "control", "#10b981", "button", "Button");
8295
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8296
+ for (const l of AUDIO_MACRO_LABELS) {
8297
+ const kind = `audio-${l.id}`;
8298
+ if (entries.has(kind)) continue;
8299
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8300
+ }
8301
+ /** The complete taxonomy dictionary, keyed by kind. */
8302
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8303
+ /**
7975
8304
  * Error types for the safe expression engine. Two distinct classes so callers
7976
8305
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7977
8306
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15164,9 +15493,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
15164
15493
  * `package` backs the package-drop detector — a package zone is a
15165
15494
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
15166
15495
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15167
- * The orchestrator provider that writes this stage lands in a later
15168
- * slice; until then the mirror carries only `{motion, detection}` and
15169
- * consumers read `package` as absent (treat as `[]`).
15496
+ * The orchestrator provider writes this stage as a first-class slice
15497
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15498
+ * package}` shape, so consumers read the current package rules directly
15499
+ * off `device.state.zoneRules.value.package`.
15170
15500
  */
15171
15501
  runtimeState: object({
15172
15502
  motion: array(ZoneRuleSchema).readonly(),
@@ -19240,17 +19570,30 @@ var EventKindCategorySchema = _enum([
19240
19570
  "audio",
19241
19571
  "detection",
19242
19572
  "sensor",
19573
+ "control",
19243
19574
  "custom",
19244
19575
  "package"
19245
19576
  ]);
19577
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19578
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19246
19579
  var EventKindDescriptorSchema = object({
19247
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19580
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19248
19581
  kind: string(),
19582
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19583
+ labelKey: string(),
19584
+ /** English fallback label (kept for clients that don't translate). */
19249
19585
  label: string(),
19250
19586
  /** Hex color for timeline/legend rendering. */
19251
19587
  color: string(),
19588
+ /** Dictionary id → lucide component on the UI side. */
19589
+ iconId: string(),
19590
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19252
19591
  icon: EventKindIconSchema,
19253
19592
  category: EventKindCategorySchema,
19593
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19594
+ parentKind: string().nullable(),
19595
+ /** Derived from `parentKind`, explicit for the client tree. */
19596
+ level: EventKindLevelSchema,
19254
19597
  /** Which cap + device contributes this kind. For built-ins the camera
19255
19598
  * itself; for sensor kinds the LINKED source device. */
19256
19599
  source: object({
@@ -19323,11 +19666,21 @@ var TrackAudioLabelSchema = object({
19323
19666
  firstAt: number(),
19324
19667
  lastAt: number()
19325
19668
  });
19669
+ /**
19670
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19671
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19672
+ * linked sensor/control state change (no positions; carries a snapshot). The
19673
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19674
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19675
+ */
19676
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19326
19677
  var TrackSchema = object({
19327
19678
  trackId: string(),
19328
19679
  deviceId: number(),
19329
19680
  className: string(),
19330
19681
  label: string().optional(),
19682
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19683
+ source: TrackSourceSchema.optional(),
19331
19684
  firstSeen: number(),
19332
19685
  lastSeen: number(),
19333
19686
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19582,6 +19935,26 @@ var TrackCascadeCountsSchema = object({
19582
19935
  /** Per-track CLIP search vectors removed (best-effort). */
19583
19936
  embeddings: number().int()
19584
19937
  });
19938
+ /** Event-store footprint for one camera. */
19939
+ var EventStoreDeviceFootprintSchema = object({
19940
+ deviceId: number(),
19941
+ /** Persisted event rows (motion + object + audio) for the camera. */
19942
+ rows: number().int(),
19943
+ /** Event-owned media bytes on disk for the camera. */
19944
+ bytes: number().int()
19945
+ });
19946
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19947
+ var EventStoreFootprintSchema = object({
19948
+ totalRows: number().int(),
19949
+ totalBytes: number().int(),
19950
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19951
+ });
19952
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19953
+ var EventPruneCountsSchema = object({
19954
+ motion: number().int(),
19955
+ object: number().int(),
19956
+ audio: number().int()
19957
+ });
19585
19958
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19586
19959
  deviceId: number(),
19587
19960
  trackId: string()
@@ -19645,6 +20018,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19645
20018
  }), {
19646
20019
  kind: "mutation",
19647
20020
  auth: "admin"
20021
+ }), method(object({}), EventStoreFootprintSchema, {
20022
+ kind: "query",
20023
+ auth: "admin"
20024
+ }), method(object({
20025
+ olderThanMs: number(),
20026
+ reason: OpsLogReasonSchema.optional()
20027
+ }), EventPruneCountsSchema, {
20028
+ kind: "mutation",
20029
+ auth: "admin"
20030
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
20031
+ kind: "mutation",
20032
+ auth: "admin"
20033
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20034
+ kind: "query",
20035
+ auth: "admin"
19648
20036
  }), method(object({
19649
20037
  eventId: string(),
19650
20038
  kind: MediaFileKindEnum.optional()
@@ -19669,6 +20057,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19669
20057
  eventId: string(),
19670
20058
  timestamp: number()
19671
20059
  });
20060
+ /**
20061
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
20062
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
20063
+ * caps into per-camera event-kind descriptors.
20064
+ *
20065
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
20066
+ * is NOT duplicated here — every entry is derived from the single
20067
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
20068
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
20069
+ * control cap means adding one line here (and a taxonomy entry); the anti-
20070
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
20071
+ * eventful cap is missing.
20072
+ */
20073
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
20074
+ var LEGACY_ICON = {
20075
+ motion: "motion",
20076
+ audio: "audio",
20077
+ person: "person",
20078
+ vehicle: "vehicle",
20079
+ animal: "animal",
20080
+ package: "package",
20081
+ door: "door",
20082
+ pir: "pir",
20083
+ smoke: "smoke",
20084
+ water: "water",
20085
+ button: "button",
20086
+ generic: "generic",
20087
+ gas: "smoke",
20088
+ vibration: "generic",
20089
+ tamper: "generic",
20090
+ presence: "person",
20091
+ lock: "generic",
20092
+ siren: "generic",
20093
+ switch: "generic",
20094
+ doorbell: "button"
20095
+ };
20096
+ function legacyIcon(iconId) {
20097
+ return LEGACY_ICON[iconId] ?? "generic";
20098
+ }
20099
+ /**
20100
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
20101
+ * The anti-drift guard cross-checks this against the eventful caps declared
20102
+ * in `packages/types/src/capabilities/*.cap.ts`.
20103
+ */
20104
+ var CAP_TO_KIND = {
20105
+ contact: "contact",
20106
+ motion: "motion-sensor",
20107
+ smoke: "smoke",
20108
+ flood: "flood",
20109
+ gas: "gas",
20110
+ "carbon-monoxide": "carbon-monoxide",
20111
+ vibration: "vibration",
20112
+ tamper: "tamper",
20113
+ presence: "presence",
20114
+ "enum-sensor": "enum-sensor",
20115
+ "event-emitter": "device-event",
20116
+ "lock-control": "lock",
20117
+ switch: "switch",
20118
+ button: "button",
20119
+ doorbell: "doorbell"
20120
+ };
20121
+ function buildDescriptor(capName, kind) {
20122
+ const t = EVENT_TAXONOMY[kind];
20123
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
20124
+ return {
20125
+ ...t,
20126
+ icon: legacyIcon(t.iconId)
20127
+ };
20128
+ }
20129
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19672
20130
  var CameraPipelineConfigSchema = object({
19673
20131
  engine: PipelineEngineChoiceSchema.optional(),
19674
20132
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22967,13 +23425,29 @@ method(object({
22967
23425
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22968
23426
  kind: "mutation",
22969
23427
  auth: "admin"
22970
- }), method(object({ deviceId: number() }), object({
23428
+ }), method(object({
23429
+ deviceId: number(),
23430
+ reason: OpsLogReasonSchema.optional()
23431
+ }), object({
22971
23432
  floorMs: number().nullable(),
22972
23433
  deletedBuckets: number().int(),
22973
23434
  reclaimedBytes: number().int()
22974
23435
  }), {
22975
23436
  kind: "mutation",
22976
23437
  auth: "admin"
23438
+ }), method(object({
23439
+ deviceId: number(),
23440
+ fromMs: number().optional(),
23441
+ toMs: number().optional()
23442
+ }), object({
23443
+ deletedBuckets: number().int(),
23444
+ reclaimedBytes: number().int()
23445
+ }), {
23446
+ kind: "mutation",
23447
+ auth: "admin"
23448
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23449
+ kind: "query",
23450
+ auth: "admin"
22977
23451
  });
22978
23452
  /**
22979
23453
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -26095,6 +26569,12 @@ Object.freeze({
26095
26569
  addonId: null,
26096
26570
  access: "delete"
26097
26571
  },
26572
+ "pipelineAnalytics.deleteDeviceEvents": {
26573
+ capName: "pipeline-analytics",
26574
+ capScope: "device",
26575
+ addonId: null,
26576
+ access: "delete"
26577
+ },
26098
26578
  "pipelineAnalytics.deleteTracks": {
26099
26579
  capName: "pipeline-analytics",
26100
26580
  capScope: "device",
@@ -26125,6 +26605,12 @@ Object.freeze({
26125
26605
  addonId: null,
26126
26606
  access: "view"
26127
26607
  },
26608
+ "pipelineAnalytics.getEventStoreFootprint": {
26609
+ capName: "pipeline-analytics",
26610
+ capScope: "device",
26611
+ addonId: null,
26612
+ access: "view"
26613
+ },
26128
26614
  "pipelineAnalytics.getKeyEvents": {
26129
26615
  capName: "pipeline-analytics",
26130
26616
  capScope: "device",
@@ -26167,6 +26653,12 @@ Object.freeze({
26167
26653
  addonId: null,
26168
26654
  access: "view"
26169
26655
  },
26656
+ "pipelineAnalytics.listOpsLog": {
26657
+ capName: "pipeline-analytics",
26658
+ capScope: "device",
26659
+ addonId: null,
26660
+ access: "view"
26661
+ },
26170
26662
  "pipelineAnalytics.listRecentTracks": {
26171
26663
  capName: "pipeline-analytics",
26172
26664
  capScope: "device",
@@ -26179,6 +26671,12 @@ Object.freeze({
26179
26671
  addonId: null,
26180
26672
  access: "view"
26181
26673
  },
26674
+ "pipelineAnalytics.pruneEvents": {
26675
+ capName: "pipeline-analytics",
26676
+ capScope: "device",
26677
+ addonId: null,
26678
+ access: "create"
26679
+ },
26182
26680
  "pipelineAnalytics.pruneEventsBefore": {
26183
26681
  capName: "pipeline-analytics",
26184
26682
  capScope: "device",
@@ -26941,6 +27439,12 @@ Object.freeze({
26941
27439
  addonId: null,
26942
27440
  access: "create"
26943
27441
  },
27442
+ "recording.deleteFootprint": {
27443
+ capName: "recording",
27444
+ capScope: "system",
27445
+ addonId: null,
27446
+ access: "delete"
27447
+ },
26944
27448
  "recording.getAvailability": {
26945
27449
  capName: "recording",
26946
27450
  capScope: "system",
@@ -26971,6 +27475,12 @@ Object.freeze({
26971
27475
  addonId: null,
26972
27476
  access: "view"
26973
27477
  },
27478
+ "recording.listOpsLog": {
27479
+ capName: "recording",
27480
+ capScope: "system",
27481
+ addonId: null,
27482
+ access: "view"
27483
+ },
26974
27484
  "recording.locateSegment": {
26975
27485
  capName: "recording",
26976
27486
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7462,6 +7462,62 @@ var RecordingConfigSchema = object({
7462
7462
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7463
7463
  });
7464
7464
  /**
7465
+ * Ops-log — the durable, append-only operations audit shared by the
7466
+ * recordings and events management surfaces.
7467
+ *
7468
+ * ONE row shape is reused for both domains so a single "Activity" view can
7469
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7470
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7471
+ * management operation, WHY it ran (reason), and its measurable effect
7472
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7473
+ * never fail the operation it records.
7474
+ */
7475
+ /** Which management domain the operation belongs to. */
7476
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7477
+ /** The kind of management operation performed. */
7478
+ var OpsLogOpSchema = _enum([
7479
+ "prune",
7480
+ "manual-delete",
7481
+ "rescan",
7482
+ "retention-run"
7483
+ ]);
7484
+ /** Why the operation ran. */
7485
+ var OpsLogReasonSchema = _enum([
7486
+ "retention",
7487
+ "quota",
7488
+ "manual",
7489
+ "operator"
7490
+ ]);
7491
+ /** One audit row, shared verbatim by both domains. */
7492
+ var OpsLogEntrySchema = object({
7493
+ /** Unique row id. */
7494
+ id: string(),
7495
+ /** Epoch ms the operation completed. */
7496
+ at: number(),
7497
+ domain: OpsLogDomainSchema,
7498
+ op: OpsLogOpSchema,
7499
+ reason: OpsLogReasonSchema,
7500
+ /** The camera the op targeted; null for a cluster/global op. */
7501
+ deviceId: number().nullable(),
7502
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7503
+ nodeId: string(),
7504
+ /** Buckets / rows deleted (op-specific unit). */
7505
+ itemsAffected: number(),
7506
+ /** Bytes reclaimed by the op (0 when not measurable). */
7507
+ bytesReclaimed: number(),
7508
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7509
+ detail: string().nullable(),
7510
+ /** Who/what triggered the op. */
7511
+ actor: string()
7512
+ });
7513
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7514
+ var OpsLogQueryInputSchema = object({
7515
+ /** Restrict to a single camera; omit for every row. */
7516
+ deviceId: number().optional(),
7517
+ /** Max rows returned, newest-first. */
7518
+ limit: number().int().min(1).max(1e3).optional()
7519
+ });
7520
+ /**
7465
7521
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7466
7522
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7467
7523
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7705,6 +7761,160 @@ var EncodeProfileSchema = object({
7705
7761
  */
7706
7762
  outputArgs: array(string()).optional()
7707
7763
  });
7764
+ var COCO_TO_MACRO = {
7765
+ mapping: {
7766
+ person: "person",
7767
+ bicycle: "vehicle",
7768
+ car: "vehicle",
7769
+ motorcycle: "vehicle",
7770
+ airplane: "vehicle",
7771
+ bus: "vehicle",
7772
+ train: "vehicle",
7773
+ truck: "vehicle",
7774
+ boat: "vehicle",
7775
+ bird: "animal",
7776
+ cat: "animal",
7777
+ dog: "animal",
7778
+ horse: "animal",
7779
+ sheep: "animal",
7780
+ cow: "animal",
7781
+ elephant: "animal",
7782
+ bear: "animal",
7783
+ zebra: "animal",
7784
+ giraffe: "animal",
7785
+ suitcase: "package",
7786
+ backpack: "package",
7787
+ handbag: "package"
7788
+ },
7789
+ preserveOriginal: false
7790
+ };
7791
+ var AUDIO_MACRO_LABELS = [
7792
+ {
7793
+ id: "speech",
7794
+ name: "Speech",
7795
+ icon: "🗣️"
7796
+ },
7797
+ {
7798
+ id: "scream",
7799
+ name: "Scream / Shout",
7800
+ icon: "😱"
7801
+ },
7802
+ {
7803
+ id: "crying",
7804
+ name: "Crying / Baby",
7805
+ icon: "😢"
7806
+ },
7807
+ {
7808
+ id: "laughter",
7809
+ name: "Laughter",
7810
+ icon: "😂"
7811
+ },
7812
+ {
7813
+ id: "music",
7814
+ name: "Music",
7815
+ icon: "🎵"
7816
+ },
7817
+ {
7818
+ id: "dog",
7819
+ name: "Dog",
7820
+ icon: "🐕"
7821
+ },
7822
+ {
7823
+ id: "cat",
7824
+ name: "Cat",
7825
+ icon: "🐈"
7826
+ },
7827
+ {
7828
+ id: "bird",
7829
+ name: "Bird",
7830
+ icon: "🐦"
7831
+ },
7832
+ {
7833
+ id: "animal",
7834
+ name: "Animal (other)",
7835
+ icon: "🐾"
7836
+ },
7837
+ {
7838
+ id: "alarm",
7839
+ name: "Alarm / Siren",
7840
+ icon: "🚨"
7841
+ },
7842
+ {
7843
+ id: "doorbell",
7844
+ name: "Doorbell / Knock",
7845
+ icon: "🔔"
7846
+ },
7847
+ {
7848
+ id: "glass_breaking",
7849
+ name: "Glass Breaking",
7850
+ icon: "💥"
7851
+ },
7852
+ {
7853
+ id: "gunshot",
7854
+ name: "Gunshot / Explosion",
7855
+ icon: "💣"
7856
+ },
7857
+ {
7858
+ id: "vehicle",
7859
+ name: "Vehicle",
7860
+ icon: "🚗"
7861
+ },
7862
+ {
7863
+ id: "siren",
7864
+ name: "Emergency Siren",
7865
+ icon: "🚑"
7866
+ },
7867
+ {
7868
+ id: "fire",
7869
+ name: "Fire / Smoke",
7870
+ icon: "🔥"
7871
+ },
7872
+ {
7873
+ id: "water",
7874
+ name: "Water",
7875
+ icon: "💧"
7876
+ },
7877
+ {
7878
+ id: "wind",
7879
+ name: "Wind / Weather",
7880
+ icon: "🌬️"
7881
+ },
7882
+ {
7883
+ id: "door",
7884
+ name: "Door",
7885
+ icon: "🚪"
7886
+ },
7887
+ {
7888
+ id: "footsteps",
7889
+ name: "Footsteps",
7890
+ icon: "👣"
7891
+ },
7892
+ {
7893
+ id: "crowd",
7894
+ name: "Crowd / Chatter",
7895
+ icon: "👥"
7896
+ },
7897
+ {
7898
+ id: "telephone",
7899
+ name: "Telephone",
7900
+ icon: "📞"
7901
+ },
7902
+ {
7903
+ id: "engine",
7904
+ name: "Engine / Motor",
7905
+ icon: "⚙️"
7906
+ },
7907
+ {
7908
+ id: "tools",
7909
+ name: "Tools / Construction",
7910
+ icon: "🔨"
7911
+ },
7912
+ {
7913
+ id: "silence",
7914
+ name: "Silence",
7915
+ icon: "🤫"
7916
+ }
7917
+ ];
7708
7918
  var YAMNET_TO_MACRO = {
7709
7919
  mapping: {
7710
7920
  Speech: "speech",
@@ -7971,6 +8181,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7971
8181
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7972
8182
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7973
8183
  /**
8184
+ * Unified event-kind taxonomy — THE single source of truth for
8185
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8186
+ * icon }`.
8187
+ *
8188
+ * This dictionary folds together what used to be scattered across four
8189
+ * copies:
8190
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8191
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8192
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8193
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8194
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8195
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8196
+ *
8197
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8198
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8199
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8200
+ * color or an icon: they read this dictionary (server descriptors carry the
8201
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8202
+ *
8203
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8204
+ */
8205
+ var TAXONOMY_COLORS = {
8206
+ motion: "#f59e0b",
8207
+ audio: "#06b6d4",
8208
+ person: "#22c55e",
8209
+ vehicle: "#3b82f6",
8210
+ animal: "#f97316",
8211
+ package: "#a855f7",
8212
+ sensor: "#8b5cf6",
8213
+ control: "#10b981",
8214
+ genericDetection: "#64748b"
8215
+ };
8216
+ var DETECTION_SUB_COLORS = {
8217
+ car: "#f59e0b",
8218
+ truck: "#d97706",
8219
+ bus: "#b45309",
8220
+ motorcycle: "#eab308",
8221
+ bicycle: "#ca8a04",
8222
+ airplane: "#60a5fa",
8223
+ boat: "#2563eb",
8224
+ train: "#1d4ed8",
8225
+ bird: "#14b8a6",
8226
+ dog: "#84cc16",
8227
+ cat: "#f97316",
8228
+ horse: "#a16207",
8229
+ sheep: "#a3a3a3",
8230
+ cow: "#78716c",
8231
+ elephant: "#6b7280",
8232
+ bear: "#7c2d12",
8233
+ zebra: "#404040",
8234
+ giraffe: "#d4a373"
8235
+ };
8236
+ function titleCase(id) {
8237
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8238
+ }
8239
+ var entries = /* @__PURE__ */ new Map();
8240
+ function macro(kind, category, color, iconId, label) {
8241
+ entries.set(kind, {
8242
+ kind,
8243
+ parentKind: null,
8244
+ level: "macro",
8245
+ category,
8246
+ color,
8247
+ iconId,
8248
+ labelKey: `eventKind.${kind}`,
8249
+ label
8250
+ });
8251
+ }
8252
+ function sub(kind, parentKind, category, color, iconId, label) {
8253
+ entries.set(kind, {
8254
+ kind,
8255
+ parentKind,
8256
+ level: "sub",
8257
+ category,
8258
+ color,
8259
+ iconId,
8260
+ labelKey: `eventKind.${kind}`,
8261
+ label
8262
+ });
8263
+ }
8264
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8265
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8266
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8267
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8268
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8269
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8270
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8271
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8272
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8273
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8274
+ if (entries.has(cocoClass)) continue;
8275
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8276
+ }
8277
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8278
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8279
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8280
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8281
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8282
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8283
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8284
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8285
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8286
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8287
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8288
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8289
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8290
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8291
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8292
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8293
+ sub("button", "control", "control", "#10b981", "button", "Button");
8294
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8295
+ for (const l of AUDIO_MACRO_LABELS) {
8296
+ const kind = `audio-${l.id}`;
8297
+ if (entries.has(kind)) continue;
8298
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8299
+ }
8300
+ /** The complete taxonomy dictionary, keyed by kind. */
8301
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8302
+ /**
7974
8303
  * Error types for the safe expression engine. Two distinct classes so callers
7975
8304
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7976
8305
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15163,9 +15492,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
15163
15492
  * `package` backs the package-drop detector — a package zone is a
15164
15493
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
15165
15494
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15166
- * The orchestrator provider that writes this stage lands in a later
15167
- * slice; until then the mirror carries only `{motion, detection}` and
15168
- * consumers read `package` as absent (treat as `[]`).
15495
+ * The orchestrator provider writes this stage as a first-class slice
15496
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15497
+ * package}` shape, so consumers read the current package rules directly
15498
+ * off `device.state.zoneRules.value.package`.
15169
15499
  */
15170
15500
  runtimeState: object({
15171
15501
  motion: array(ZoneRuleSchema).readonly(),
@@ -19239,17 +19569,30 @@ var EventKindCategorySchema = _enum([
19239
19569
  "audio",
19240
19570
  "detection",
19241
19571
  "sensor",
19572
+ "control",
19242
19573
  "custom",
19243
19574
  "package"
19244
19575
  ]);
19576
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19577
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19245
19578
  var EventKindDescriptorSchema = object({
19246
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19579
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19247
19580
  kind: string(),
19581
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19582
+ labelKey: string(),
19583
+ /** English fallback label (kept for clients that don't translate). */
19248
19584
  label: string(),
19249
19585
  /** Hex color for timeline/legend rendering. */
19250
19586
  color: string(),
19587
+ /** Dictionary id → lucide component on the UI side. */
19588
+ iconId: string(),
19589
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19251
19590
  icon: EventKindIconSchema,
19252
19591
  category: EventKindCategorySchema,
19592
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19593
+ parentKind: string().nullable(),
19594
+ /** Derived from `parentKind`, explicit for the client tree. */
19595
+ level: EventKindLevelSchema,
19253
19596
  /** Which cap + device contributes this kind. For built-ins the camera
19254
19597
  * itself; for sensor kinds the LINKED source device. */
19255
19598
  source: object({
@@ -19322,11 +19665,21 @@ var TrackAudioLabelSchema = object({
19322
19665
  firstAt: number(),
19323
19666
  lastAt: number()
19324
19667
  });
19668
+ /**
19669
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19670
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19671
+ * linked sensor/control state change (no positions; carries a snapshot). The
19672
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19673
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19674
+ */
19675
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19325
19676
  var TrackSchema = object({
19326
19677
  trackId: string(),
19327
19678
  deviceId: number(),
19328
19679
  className: string(),
19329
19680
  label: string().optional(),
19681
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19682
+ source: TrackSourceSchema.optional(),
19330
19683
  firstSeen: number(),
19331
19684
  lastSeen: number(),
19332
19685
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19581,6 +19934,26 @@ var TrackCascadeCountsSchema = object({
19581
19934
  /** Per-track CLIP search vectors removed (best-effort). */
19582
19935
  embeddings: number().int()
19583
19936
  });
19937
+ /** Event-store footprint for one camera. */
19938
+ var EventStoreDeviceFootprintSchema = object({
19939
+ deviceId: number(),
19940
+ /** Persisted event rows (motion + object + audio) for the camera. */
19941
+ rows: number().int(),
19942
+ /** Event-owned media bytes on disk for the camera. */
19943
+ bytes: number().int()
19944
+ });
19945
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19946
+ var EventStoreFootprintSchema = object({
19947
+ totalRows: number().int(),
19948
+ totalBytes: number().int(),
19949
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19950
+ });
19951
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19952
+ var EventPruneCountsSchema = object({
19953
+ motion: number().int(),
19954
+ object: number().int(),
19955
+ audio: number().int()
19956
+ });
19584
19957
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19585
19958
  deviceId: number(),
19586
19959
  trackId: string()
@@ -19644,6 +20017,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19644
20017
  }), {
19645
20018
  kind: "mutation",
19646
20019
  auth: "admin"
20020
+ }), method(object({}), EventStoreFootprintSchema, {
20021
+ kind: "query",
20022
+ auth: "admin"
20023
+ }), method(object({
20024
+ olderThanMs: number(),
20025
+ reason: OpsLogReasonSchema.optional()
20026
+ }), EventPruneCountsSchema, {
20027
+ kind: "mutation",
20028
+ auth: "admin"
20029
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
20030
+ kind: "mutation",
20031
+ auth: "admin"
20032
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20033
+ kind: "query",
20034
+ auth: "admin"
19647
20035
  }), method(object({
19648
20036
  eventId: string(),
19649
20037
  kind: MediaFileKindEnum.optional()
@@ -19668,6 +20056,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19668
20056
  eventId: string(),
19669
20057
  timestamp: number()
19670
20058
  });
20059
+ /**
20060
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
20061
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
20062
+ * caps into per-camera event-kind descriptors.
20063
+ *
20064
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
20065
+ * is NOT duplicated here — every entry is derived from the single
20066
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
20067
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
20068
+ * control cap means adding one line here (and a taxonomy entry); the anti-
20069
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
20070
+ * eventful cap is missing.
20071
+ */
20072
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
20073
+ var LEGACY_ICON = {
20074
+ motion: "motion",
20075
+ audio: "audio",
20076
+ person: "person",
20077
+ vehicle: "vehicle",
20078
+ animal: "animal",
20079
+ package: "package",
20080
+ door: "door",
20081
+ pir: "pir",
20082
+ smoke: "smoke",
20083
+ water: "water",
20084
+ button: "button",
20085
+ generic: "generic",
20086
+ gas: "smoke",
20087
+ vibration: "generic",
20088
+ tamper: "generic",
20089
+ presence: "person",
20090
+ lock: "generic",
20091
+ siren: "generic",
20092
+ switch: "generic",
20093
+ doorbell: "button"
20094
+ };
20095
+ function legacyIcon(iconId) {
20096
+ return LEGACY_ICON[iconId] ?? "generic";
20097
+ }
20098
+ /**
20099
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
20100
+ * The anti-drift guard cross-checks this against the eventful caps declared
20101
+ * in `packages/types/src/capabilities/*.cap.ts`.
20102
+ */
20103
+ var CAP_TO_KIND = {
20104
+ contact: "contact",
20105
+ motion: "motion-sensor",
20106
+ smoke: "smoke",
20107
+ flood: "flood",
20108
+ gas: "gas",
20109
+ "carbon-monoxide": "carbon-monoxide",
20110
+ vibration: "vibration",
20111
+ tamper: "tamper",
20112
+ presence: "presence",
20113
+ "enum-sensor": "enum-sensor",
20114
+ "event-emitter": "device-event",
20115
+ "lock-control": "lock",
20116
+ switch: "switch",
20117
+ button: "button",
20118
+ doorbell: "doorbell"
20119
+ };
20120
+ function buildDescriptor(capName, kind) {
20121
+ const t = EVENT_TAXONOMY[kind];
20122
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
20123
+ return {
20124
+ ...t,
20125
+ icon: legacyIcon(t.iconId)
20126
+ };
20127
+ }
20128
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19671
20129
  var CameraPipelineConfigSchema = object({
19672
20130
  engine: PipelineEngineChoiceSchema.optional(),
19673
20131
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22966,13 +23424,29 @@ method(object({
22966
23424
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22967
23425
  kind: "mutation",
22968
23426
  auth: "admin"
22969
- }), method(object({ deviceId: number() }), object({
23427
+ }), method(object({
23428
+ deviceId: number(),
23429
+ reason: OpsLogReasonSchema.optional()
23430
+ }), object({
22970
23431
  floorMs: number().nullable(),
22971
23432
  deletedBuckets: number().int(),
22972
23433
  reclaimedBytes: number().int()
22973
23434
  }), {
22974
23435
  kind: "mutation",
22975
23436
  auth: "admin"
23437
+ }), method(object({
23438
+ deviceId: number(),
23439
+ fromMs: number().optional(),
23440
+ toMs: number().optional()
23441
+ }), object({
23442
+ deletedBuckets: number().int(),
23443
+ reclaimedBytes: number().int()
23444
+ }), {
23445
+ kind: "mutation",
23446
+ auth: "admin"
23447
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23448
+ kind: "query",
23449
+ auth: "admin"
22976
23450
  });
22977
23451
  /**
22978
23452
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -26094,6 +26568,12 @@ Object.freeze({
26094
26568
  addonId: null,
26095
26569
  access: "delete"
26096
26570
  },
26571
+ "pipelineAnalytics.deleteDeviceEvents": {
26572
+ capName: "pipeline-analytics",
26573
+ capScope: "device",
26574
+ addonId: null,
26575
+ access: "delete"
26576
+ },
26097
26577
  "pipelineAnalytics.deleteTracks": {
26098
26578
  capName: "pipeline-analytics",
26099
26579
  capScope: "device",
@@ -26124,6 +26604,12 @@ Object.freeze({
26124
26604
  addonId: null,
26125
26605
  access: "view"
26126
26606
  },
26607
+ "pipelineAnalytics.getEventStoreFootprint": {
26608
+ capName: "pipeline-analytics",
26609
+ capScope: "device",
26610
+ addonId: null,
26611
+ access: "view"
26612
+ },
26127
26613
  "pipelineAnalytics.getKeyEvents": {
26128
26614
  capName: "pipeline-analytics",
26129
26615
  capScope: "device",
@@ -26166,6 +26652,12 @@ Object.freeze({
26166
26652
  addonId: null,
26167
26653
  access: "view"
26168
26654
  },
26655
+ "pipelineAnalytics.listOpsLog": {
26656
+ capName: "pipeline-analytics",
26657
+ capScope: "device",
26658
+ addonId: null,
26659
+ access: "view"
26660
+ },
26169
26661
  "pipelineAnalytics.listRecentTracks": {
26170
26662
  capName: "pipeline-analytics",
26171
26663
  capScope: "device",
@@ -26178,6 +26670,12 @@ Object.freeze({
26178
26670
  addonId: null,
26179
26671
  access: "view"
26180
26672
  },
26673
+ "pipelineAnalytics.pruneEvents": {
26674
+ capName: "pipeline-analytics",
26675
+ capScope: "device",
26676
+ addonId: null,
26677
+ access: "create"
26678
+ },
26181
26679
  "pipelineAnalytics.pruneEventsBefore": {
26182
26680
  capName: "pipeline-analytics",
26183
26681
  capScope: "device",
@@ -26940,6 +27438,12 @@ Object.freeze({
26940
27438
  addonId: null,
26941
27439
  access: "create"
26942
27440
  },
27441
+ "recording.deleteFootprint": {
27442
+ capName: "recording",
27443
+ capScope: "system",
27444
+ addonId: null,
27445
+ access: "delete"
27446
+ },
26943
27447
  "recording.getAvailability": {
26944
27448
  capName: "recording",
26945
27449
  capScope: "system",
@@ -26970,6 +27474,12 @@ Object.freeze({
26970
27474
  addonId: null,
26971
27475
  access: "view"
26972
27476
  },
27477
+ "recording.listOpsLog": {
27478
+ capName: "recording",
27479
+ capScope: "system",
27480
+ addonId: null,
27481
+ access: "view"
27482
+ },
26973
27483
  "recording.locateSegment": {
26974
27484
  capName: "recording",
26975
27485
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homeassistant",
3
- "version": "1.1.30",
3
+ "version": "1.1.32",
4
4
  "description": "Home Assistant device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",