@camstack/addon-export-alexa 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.
@@ -7495,6 +7495,62 @@ var RecordingConfigSchema = object({
7495
7495
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7496
7496
  });
7497
7497
  /**
7498
+ * Ops-log — the durable, append-only operations audit shared by the
7499
+ * recordings and events management surfaces.
7500
+ *
7501
+ * ONE row shape is reused for both domains so a single "Activity" view can
7502
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7503
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7504
+ * management operation, WHY it ran (reason), and its measurable effect
7505
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7506
+ * never fail the operation it records.
7507
+ */
7508
+ /** Which management domain the operation belongs to. */
7509
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7510
+ /** The kind of management operation performed. */
7511
+ var OpsLogOpSchema = _enum([
7512
+ "prune",
7513
+ "manual-delete",
7514
+ "rescan",
7515
+ "retention-run"
7516
+ ]);
7517
+ /** Why the operation ran. */
7518
+ var OpsLogReasonSchema = _enum([
7519
+ "retention",
7520
+ "quota",
7521
+ "manual",
7522
+ "operator"
7523
+ ]);
7524
+ /** One audit row, shared verbatim by both domains. */
7525
+ var OpsLogEntrySchema = object({
7526
+ /** Unique row id. */
7527
+ id: string(),
7528
+ /** Epoch ms the operation completed. */
7529
+ at: number(),
7530
+ domain: OpsLogDomainSchema,
7531
+ op: OpsLogOpSchema,
7532
+ reason: OpsLogReasonSchema,
7533
+ /** The camera the op targeted; null for a cluster/global op. */
7534
+ deviceId: number().nullable(),
7535
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7536
+ nodeId: string(),
7537
+ /** Buckets / rows deleted (op-specific unit). */
7538
+ itemsAffected: number(),
7539
+ /** Bytes reclaimed by the op (0 when not measurable). */
7540
+ bytesReclaimed: number(),
7541
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7542
+ detail: string().nullable(),
7543
+ /** Who/what triggered the op. */
7544
+ actor: string()
7545
+ });
7546
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7547
+ var OpsLogQueryInputSchema = object({
7548
+ /** Restrict to a single camera; omit for every row. */
7549
+ deviceId: number().optional(),
7550
+ /** Max rows returned, newest-first. */
7551
+ limit: number().int().min(1).max(1e3).optional()
7552
+ });
7553
+ /**
7498
7554
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7499
7555
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7500
7556
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7792,6 +7848,160 @@ function pickClosestResolution(entries, target) {
7792
7848
  }
7793
7849
  return bestAbove?.entry ?? bestBelow?.entry;
7794
7850
  }
7851
+ var COCO_TO_MACRO = {
7852
+ mapping: {
7853
+ person: "person",
7854
+ bicycle: "vehicle",
7855
+ car: "vehicle",
7856
+ motorcycle: "vehicle",
7857
+ airplane: "vehicle",
7858
+ bus: "vehicle",
7859
+ train: "vehicle",
7860
+ truck: "vehicle",
7861
+ boat: "vehicle",
7862
+ bird: "animal",
7863
+ cat: "animal",
7864
+ dog: "animal",
7865
+ horse: "animal",
7866
+ sheep: "animal",
7867
+ cow: "animal",
7868
+ elephant: "animal",
7869
+ bear: "animal",
7870
+ zebra: "animal",
7871
+ giraffe: "animal",
7872
+ suitcase: "package",
7873
+ backpack: "package",
7874
+ handbag: "package"
7875
+ },
7876
+ preserveOriginal: false
7877
+ };
7878
+ var AUDIO_MACRO_LABELS = [
7879
+ {
7880
+ id: "speech",
7881
+ name: "Speech",
7882
+ icon: "🗣️"
7883
+ },
7884
+ {
7885
+ id: "scream",
7886
+ name: "Scream / Shout",
7887
+ icon: "😱"
7888
+ },
7889
+ {
7890
+ id: "crying",
7891
+ name: "Crying / Baby",
7892
+ icon: "😢"
7893
+ },
7894
+ {
7895
+ id: "laughter",
7896
+ name: "Laughter",
7897
+ icon: "😂"
7898
+ },
7899
+ {
7900
+ id: "music",
7901
+ name: "Music",
7902
+ icon: "🎵"
7903
+ },
7904
+ {
7905
+ id: "dog",
7906
+ name: "Dog",
7907
+ icon: "🐕"
7908
+ },
7909
+ {
7910
+ id: "cat",
7911
+ name: "Cat",
7912
+ icon: "🐈"
7913
+ },
7914
+ {
7915
+ id: "bird",
7916
+ name: "Bird",
7917
+ icon: "🐦"
7918
+ },
7919
+ {
7920
+ id: "animal",
7921
+ name: "Animal (other)",
7922
+ icon: "🐾"
7923
+ },
7924
+ {
7925
+ id: "alarm",
7926
+ name: "Alarm / Siren",
7927
+ icon: "🚨"
7928
+ },
7929
+ {
7930
+ id: "doorbell",
7931
+ name: "Doorbell / Knock",
7932
+ icon: "🔔"
7933
+ },
7934
+ {
7935
+ id: "glass_breaking",
7936
+ name: "Glass Breaking",
7937
+ icon: "💥"
7938
+ },
7939
+ {
7940
+ id: "gunshot",
7941
+ name: "Gunshot / Explosion",
7942
+ icon: "💣"
7943
+ },
7944
+ {
7945
+ id: "vehicle",
7946
+ name: "Vehicle",
7947
+ icon: "🚗"
7948
+ },
7949
+ {
7950
+ id: "siren",
7951
+ name: "Emergency Siren",
7952
+ icon: "🚑"
7953
+ },
7954
+ {
7955
+ id: "fire",
7956
+ name: "Fire / Smoke",
7957
+ icon: "🔥"
7958
+ },
7959
+ {
7960
+ id: "water",
7961
+ name: "Water",
7962
+ icon: "💧"
7963
+ },
7964
+ {
7965
+ id: "wind",
7966
+ name: "Wind / Weather",
7967
+ icon: "🌬️"
7968
+ },
7969
+ {
7970
+ id: "door",
7971
+ name: "Door",
7972
+ icon: "🚪"
7973
+ },
7974
+ {
7975
+ id: "footsteps",
7976
+ name: "Footsteps",
7977
+ icon: "👣"
7978
+ },
7979
+ {
7980
+ id: "crowd",
7981
+ name: "Crowd / Chatter",
7982
+ icon: "👥"
7983
+ },
7984
+ {
7985
+ id: "telephone",
7986
+ name: "Telephone",
7987
+ icon: "📞"
7988
+ },
7989
+ {
7990
+ id: "engine",
7991
+ name: "Engine / Motor",
7992
+ icon: "⚙️"
7993
+ },
7994
+ {
7995
+ id: "tools",
7996
+ name: "Tools / Construction",
7997
+ icon: "🔨"
7998
+ },
7999
+ {
8000
+ id: "silence",
8001
+ name: "Silence",
8002
+ icon: "🤫"
8003
+ }
8004
+ ];
7795
8005
  var YAMNET_TO_MACRO = {
7796
8006
  mapping: {
7797
8007
  Speech: "speech",
@@ -8058,6 +8268,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8058
8268
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8059
8269
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8060
8270
  /**
8271
+ * Unified event-kind taxonomy — THE single source of truth for
8272
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8273
+ * icon }`.
8274
+ *
8275
+ * This dictionary folds together what used to be scattered across four
8276
+ * copies:
8277
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8278
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8279
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8280
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8281
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8282
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8283
+ *
8284
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8285
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8286
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8287
+ * color or an icon: they read this dictionary (server descriptors carry the
8288
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8289
+ *
8290
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8291
+ */
8292
+ var TAXONOMY_COLORS = {
8293
+ motion: "#f59e0b",
8294
+ audio: "#06b6d4",
8295
+ person: "#22c55e",
8296
+ vehicle: "#3b82f6",
8297
+ animal: "#f97316",
8298
+ package: "#a855f7",
8299
+ sensor: "#8b5cf6",
8300
+ control: "#10b981",
8301
+ genericDetection: "#64748b"
8302
+ };
8303
+ var DETECTION_SUB_COLORS = {
8304
+ car: "#f59e0b",
8305
+ truck: "#d97706",
8306
+ bus: "#b45309",
8307
+ motorcycle: "#eab308",
8308
+ bicycle: "#ca8a04",
8309
+ airplane: "#60a5fa",
8310
+ boat: "#2563eb",
8311
+ train: "#1d4ed8",
8312
+ bird: "#14b8a6",
8313
+ dog: "#84cc16",
8314
+ cat: "#f97316",
8315
+ horse: "#a16207",
8316
+ sheep: "#a3a3a3",
8317
+ cow: "#78716c",
8318
+ elephant: "#6b7280",
8319
+ bear: "#7c2d12",
8320
+ zebra: "#404040",
8321
+ giraffe: "#d4a373"
8322
+ };
8323
+ function titleCase(id) {
8324
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8325
+ }
8326
+ var entries = /* @__PURE__ */ new Map();
8327
+ function macro(kind, category, color, iconId, label) {
8328
+ entries.set(kind, {
8329
+ kind,
8330
+ parentKind: null,
8331
+ level: "macro",
8332
+ category,
8333
+ color,
8334
+ iconId,
8335
+ labelKey: `eventKind.${kind}`,
8336
+ label
8337
+ });
8338
+ }
8339
+ function sub(kind, parentKind, category, color, iconId, label) {
8340
+ entries.set(kind, {
8341
+ kind,
8342
+ parentKind,
8343
+ level: "sub",
8344
+ category,
8345
+ color,
8346
+ iconId,
8347
+ labelKey: `eventKind.${kind}`,
8348
+ label
8349
+ });
8350
+ }
8351
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8352
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8353
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8354
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8355
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8356
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8357
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8358
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8359
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8360
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8361
+ if (entries.has(cocoClass)) continue;
8362
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8363
+ }
8364
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8365
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8366
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8367
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8368
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8369
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8370
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8371
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8372
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8373
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8374
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8375
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8376
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8377
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8378
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8379
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8380
+ sub("button", "control", "control", "#10b981", "button", "Button");
8381
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8382
+ for (const l of AUDIO_MACRO_LABELS) {
8383
+ const kind = `audio-${l.id}`;
8384
+ if (entries.has(kind)) continue;
8385
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8386
+ }
8387
+ /** The complete taxonomy dictionary, keyed by kind. */
8388
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8389
+ /**
8061
8390
  * Error types for the safe expression engine. Two distinct classes so callers
8062
8391
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8063
8392
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -16078,17 +16407,30 @@ var EventKindCategorySchema = _enum([
16078
16407
  "audio",
16079
16408
  "detection",
16080
16409
  "sensor",
16410
+ "control",
16081
16411
  "custom",
16082
16412
  "package"
16083
16413
  ]);
16414
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16415
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
16084
16416
  var EventKindDescriptorSchema = object({
16085
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16417
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
16086
16418
  kind: string(),
16419
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16420
+ labelKey: string(),
16421
+ /** English fallback label (kept for clients that don't translate). */
16087
16422
  label: string(),
16088
16423
  /** Hex color for timeline/legend rendering. */
16089
16424
  color: string(),
16425
+ /** Dictionary id → lucide component on the UI side. */
16426
+ iconId: string(),
16427
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
16090
16428
  icon: EventKindIconSchema,
16091
16429
  category: EventKindCategorySchema,
16430
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16431
+ parentKind: string().nullable(),
16432
+ /** Derived from `parentKind`, explicit for the client tree. */
16433
+ level: EventKindLevelSchema,
16092
16434
  /** Which cap + device contributes this kind. For built-ins the camera
16093
16435
  * itself; for sensor kinds the LINKED source device. */
16094
16436
  source: object({
@@ -16161,11 +16503,21 @@ var TrackAudioLabelSchema = object({
16161
16503
  firstAt: number(),
16162
16504
  lastAt: number()
16163
16505
  });
16506
+ /**
16507
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16508
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16509
+ * linked sensor/control state change (no positions; carries a snapshot). The
16510
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16511
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16512
+ */
16513
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16164
16514
  var TrackSchema = object({
16165
16515
  trackId: string(),
16166
16516
  deviceId: number(),
16167
16517
  className: string(),
16168
16518
  label: string().optional(),
16519
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16520
+ source: TrackSourceSchema.optional(),
16169
16521
  firstSeen: number(),
16170
16522
  lastSeen: number(),
16171
16523
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16420,6 +16772,26 @@ var TrackCascadeCountsSchema = object({
16420
16772
  /** Per-track CLIP search vectors removed (best-effort). */
16421
16773
  embeddings: number().int()
16422
16774
  });
16775
+ /** Event-store footprint for one camera. */
16776
+ var EventStoreDeviceFootprintSchema = object({
16777
+ deviceId: number(),
16778
+ /** Persisted event rows (motion + object + audio) for the camera. */
16779
+ rows: number().int(),
16780
+ /** Event-owned media bytes on disk for the camera. */
16781
+ bytes: number().int()
16782
+ });
16783
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16784
+ var EventStoreFootprintSchema = object({
16785
+ totalRows: number().int(),
16786
+ totalBytes: number().int(),
16787
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16788
+ });
16789
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16790
+ var EventPruneCountsSchema = object({
16791
+ motion: number().int(),
16792
+ object: number().int(),
16793
+ audio: number().int()
16794
+ });
16423
16795
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16424
16796
  deviceId: number(),
16425
16797
  trackId: string()
@@ -16483,6 +16855,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16483
16855
  }), {
16484
16856
  kind: "mutation",
16485
16857
  auth: "admin"
16858
+ }), method(object({}), EventStoreFootprintSchema, {
16859
+ kind: "query",
16860
+ auth: "admin"
16861
+ }), method(object({
16862
+ olderThanMs: number(),
16863
+ reason: OpsLogReasonSchema.optional()
16864
+ }), EventPruneCountsSchema, {
16865
+ kind: "mutation",
16866
+ auth: "admin"
16867
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16868
+ kind: "mutation",
16869
+ auth: "admin"
16870
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16871
+ kind: "query",
16872
+ auth: "admin"
16486
16873
  }), method(object({
16487
16874
  eventId: string(),
16488
16875
  kind: MediaFileKindEnum.optional()
@@ -16507,6 +16894,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16507
16894
  eventId: string(),
16508
16895
  timestamp: number()
16509
16896
  });
16897
+ /**
16898
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16899
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16900
+ * caps into per-camera event-kind descriptors.
16901
+ *
16902
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16903
+ * is NOT duplicated here — every entry is derived from the single
16904
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16905
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16906
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16907
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16908
+ * eventful cap is missing.
16909
+ */
16910
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16911
+ var LEGACY_ICON = {
16912
+ motion: "motion",
16913
+ audio: "audio",
16914
+ person: "person",
16915
+ vehicle: "vehicle",
16916
+ animal: "animal",
16917
+ package: "package",
16918
+ door: "door",
16919
+ pir: "pir",
16920
+ smoke: "smoke",
16921
+ water: "water",
16922
+ button: "button",
16923
+ generic: "generic",
16924
+ gas: "smoke",
16925
+ vibration: "generic",
16926
+ tamper: "generic",
16927
+ presence: "person",
16928
+ lock: "generic",
16929
+ siren: "generic",
16930
+ switch: "generic",
16931
+ doorbell: "button"
16932
+ };
16933
+ function legacyIcon(iconId) {
16934
+ return LEGACY_ICON[iconId] ?? "generic";
16935
+ }
16936
+ /**
16937
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16938
+ * The anti-drift guard cross-checks this against the eventful caps declared
16939
+ * in `packages/types/src/capabilities/*.cap.ts`.
16940
+ */
16941
+ var CAP_TO_KIND = {
16942
+ contact: "contact",
16943
+ motion: "motion-sensor",
16944
+ smoke: "smoke",
16945
+ flood: "flood",
16946
+ gas: "gas",
16947
+ "carbon-monoxide": "carbon-monoxide",
16948
+ vibration: "vibration",
16949
+ tamper: "tamper",
16950
+ presence: "presence",
16951
+ "enum-sensor": "enum-sensor",
16952
+ "event-emitter": "device-event",
16953
+ "lock-control": "lock",
16954
+ switch: "switch",
16955
+ button: "button",
16956
+ doorbell: "doorbell"
16957
+ };
16958
+ function buildDescriptor(capName, kind) {
16959
+ const t = EVENT_TAXONOMY[kind];
16960
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16961
+ return {
16962
+ ...t,
16963
+ icon: legacyIcon(t.iconId)
16964
+ };
16965
+ }
16966
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16510
16967
  var CameraPipelineConfigSchema = object({
16511
16968
  engine: PipelineEngineChoiceSchema.optional(),
16512
16969
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19716,13 +20173,29 @@ method(object({
19716
20173
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19717
20174
  kind: "mutation",
19718
20175
  auth: "admin"
19719
- }), method(object({ deviceId: number() }), object({
20176
+ }), method(object({
20177
+ deviceId: number(),
20178
+ reason: OpsLogReasonSchema.optional()
20179
+ }), object({
19720
20180
  floorMs: number().nullable(),
19721
20181
  deletedBuckets: number().int(),
19722
20182
  reclaimedBytes: number().int()
19723
20183
  }), {
19724
20184
  kind: "mutation",
19725
20185
  auth: "admin"
20186
+ }), method(object({
20187
+ deviceId: number(),
20188
+ fromMs: number().optional(),
20189
+ toMs: number().optional()
20190
+ }), object({
20191
+ deletedBuckets: number().int(),
20192
+ reclaimedBytes: number().int()
20193
+ }), {
20194
+ kind: "mutation",
20195
+ auth: "admin"
20196
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20197
+ kind: "query",
20198
+ auth: "admin"
19726
20199
  });
19727
20200
  /**
19728
20201
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22844,6 +23317,12 @@ Object.freeze({
22844
23317
  addonId: null,
22845
23318
  access: "delete"
22846
23319
  },
23320
+ "pipelineAnalytics.deleteDeviceEvents": {
23321
+ capName: "pipeline-analytics",
23322
+ capScope: "device",
23323
+ addonId: null,
23324
+ access: "delete"
23325
+ },
22847
23326
  "pipelineAnalytics.deleteTracks": {
22848
23327
  capName: "pipeline-analytics",
22849
23328
  capScope: "device",
@@ -22874,6 +23353,12 @@ Object.freeze({
22874
23353
  addonId: null,
22875
23354
  access: "view"
22876
23355
  },
23356
+ "pipelineAnalytics.getEventStoreFootprint": {
23357
+ capName: "pipeline-analytics",
23358
+ capScope: "device",
23359
+ addonId: null,
23360
+ access: "view"
23361
+ },
22877
23362
  "pipelineAnalytics.getKeyEvents": {
22878
23363
  capName: "pipeline-analytics",
22879
23364
  capScope: "device",
@@ -22916,6 +23401,12 @@ Object.freeze({
22916
23401
  addonId: null,
22917
23402
  access: "view"
22918
23403
  },
23404
+ "pipelineAnalytics.listOpsLog": {
23405
+ capName: "pipeline-analytics",
23406
+ capScope: "device",
23407
+ addonId: null,
23408
+ access: "view"
23409
+ },
22919
23410
  "pipelineAnalytics.listRecentTracks": {
22920
23411
  capName: "pipeline-analytics",
22921
23412
  capScope: "device",
@@ -22928,6 +23419,12 @@ Object.freeze({
22928
23419
  addonId: null,
22929
23420
  access: "view"
22930
23421
  },
23422
+ "pipelineAnalytics.pruneEvents": {
23423
+ capName: "pipeline-analytics",
23424
+ capScope: "device",
23425
+ addonId: null,
23426
+ access: "create"
23427
+ },
22931
23428
  "pipelineAnalytics.pruneEventsBefore": {
22932
23429
  capName: "pipeline-analytics",
22933
23430
  capScope: "device",
@@ -23690,6 +24187,12 @@ Object.freeze({
23690
24187
  addonId: null,
23691
24188
  access: "create"
23692
24189
  },
24190
+ "recording.deleteFootprint": {
24191
+ capName: "recording",
24192
+ capScope: "system",
24193
+ addonId: null,
24194
+ access: "delete"
24195
+ },
23693
24196
  "recording.getAvailability": {
23694
24197
  capName: "recording",
23695
24198
  capScope: "system",
@@ -23720,6 +24223,12 @@ Object.freeze({
23720
24223
  addonId: null,
23721
24224
  access: "view"
23722
24225
  },
24226
+ "recording.listOpsLog": {
24227
+ capName: "recording",
24228
+ capScope: "system",
24229
+ addonId: null,
24230
+ access: "view"
24231
+ },
23723
24232
  "recording.locateSegment": {
23724
24233
  capName: "recording",
23725
24234
  capScope: "system",
@@ -7469,6 +7469,62 @@ var RecordingConfigSchema = object({
7469
7469
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7470
7470
  });
7471
7471
  /**
7472
+ * Ops-log — the durable, append-only operations audit shared by the
7473
+ * recordings and events management surfaces.
7474
+ *
7475
+ * ONE row shape is reused for both domains so a single "Activity" view can
7476
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7477
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7478
+ * management operation, WHY it ran (reason), and its measurable effect
7479
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7480
+ * never fail the operation it records.
7481
+ */
7482
+ /** Which management domain the operation belongs to. */
7483
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7484
+ /** The kind of management operation performed. */
7485
+ var OpsLogOpSchema = _enum([
7486
+ "prune",
7487
+ "manual-delete",
7488
+ "rescan",
7489
+ "retention-run"
7490
+ ]);
7491
+ /** Why the operation ran. */
7492
+ var OpsLogReasonSchema = _enum([
7493
+ "retention",
7494
+ "quota",
7495
+ "manual",
7496
+ "operator"
7497
+ ]);
7498
+ /** One audit row, shared verbatim by both domains. */
7499
+ var OpsLogEntrySchema = object({
7500
+ /** Unique row id. */
7501
+ id: string(),
7502
+ /** Epoch ms the operation completed. */
7503
+ at: number(),
7504
+ domain: OpsLogDomainSchema,
7505
+ op: OpsLogOpSchema,
7506
+ reason: OpsLogReasonSchema,
7507
+ /** The camera the op targeted; null for a cluster/global op. */
7508
+ deviceId: number().nullable(),
7509
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7510
+ nodeId: string(),
7511
+ /** Buckets / rows deleted (op-specific unit). */
7512
+ itemsAffected: number(),
7513
+ /** Bytes reclaimed by the op (0 when not measurable). */
7514
+ bytesReclaimed: number(),
7515
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7516
+ detail: string().nullable(),
7517
+ /** Who/what triggered the op. */
7518
+ actor: string()
7519
+ });
7520
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7521
+ var OpsLogQueryInputSchema = object({
7522
+ /** Restrict to a single camera; omit for every row. */
7523
+ deviceId: number().optional(),
7524
+ /** Max rows returned, newest-first. */
7525
+ limit: number().int().min(1).max(1e3).optional()
7526
+ });
7527
+ /**
7472
7528
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7473
7529
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7474
7530
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7766,6 +7822,160 @@ function pickClosestResolution(entries, target) {
7766
7822
  }
7767
7823
  return bestAbove?.entry ?? bestBelow?.entry;
7768
7824
  }
7825
+ var COCO_TO_MACRO = {
7826
+ mapping: {
7827
+ person: "person",
7828
+ bicycle: "vehicle",
7829
+ car: "vehicle",
7830
+ motorcycle: "vehicle",
7831
+ airplane: "vehicle",
7832
+ bus: "vehicle",
7833
+ train: "vehicle",
7834
+ truck: "vehicle",
7835
+ boat: "vehicle",
7836
+ bird: "animal",
7837
+ cat: "animal",
7838
+ dog: "animal",
7839
+ horse: "animal",
7840
+ sheep: "animal",
7841
+ cow: "animal",
7842
+ elephant: "animal",
7843
+ bear: "animal",
7844
+ zebra: "animal",
7845
+ giraffe: "animal",
7846
+ suitcase: "package",
7847
+ backpack: "package",
7848
+ handbag: "package"
7849
+ },
7850
+ preserveOriginal: false
7851
+ };
7852
+ var AUDIO_MACRO_LABELS = [
7853
+ {
7854
+ id: "speech",
7855
+ name: "Speech",
7856
+ icon: "🗣️"
7857
+ },
7858
+ {
7859
+ id: "scream",
7860
+ name: "Scream / Shout",
7861
+ icon: "😱"
7862
+ },
7863
+ {
7864
+ id: "crying",
7865
+ name: "Crying / Baby",
7866
+ icon: "😢"
7867
+ },
7868
+ {
7869
+ id: "laughter",
7870
+ name: "Laughter",
7871
+ icon: "😂"
7872
+ },
7873
+ {
7874
+ id: "music",
7875
+ name: "Music",
7876
+ icon: "🎵"
7877
+ },
7878
+ {
7879
+ id: "dog",
7880
+ name: "Dog",
7881
+ icon: "🐕"
7882
+ },
7883
+ {
7884
+ id: "cat",
7885
+ name: "Cat",
7886
+ icon: "🐈"
7887
+ },
7888
+ {
7889
+ id: "bird",
7890
+ name: "Bird",
7891
+ icon: "🐦"
7892
+ },
7893
+ {
7894
+ id: "animal",
7895
+ name: "Animal (other)",
7896
+ icon: "🐾"
7897
+ },
7898
+ {
7899
+ id: "alarm",
7900
+ name: "Alarm / Siren",
7901
+ icon: "🚨"
7902
+ },
7903
+ {
7904
+ id: "doorbell",
7905
+ name: "Doorbell / Knock",
7906
+ icon: "🔔"
7907
+ },
7908
+ {
7909
+ id: "glass_breaking",
7910
+ name: "Glass Breaking",
7911
+ icon: "💥"
7912
+ },
7913
+ {
7914
+ id: "gunshot",
7915
+ name: "Gunshot / Explosion",
7916
+ icon: "💣"
7917
+ },
7918
+ {
7919
+ id: "vehicle",
7920
+ name: "Vehicle",
7921
+ icon: "🚗"
7922
+ },
7923
+ {
7924
+ id: "siren",
7925
+ name: "Emergency Siren",
7926
+ icon: "🚑"
7927
+ },
7928
+ {
7929
+ id: "fire",
7930
+ name: "Fire / Smoke",
7931
+ icon: "🔥"
7932
+ },
7933
+ {
7934
+ id: "water",
7935
+ name: "Water",
7936
+ icon: "💧"
7937
+ },
7938
+ {
7939
+ id: "wind",
7940
+ name: "Wind / Weather",
7941
+ icon: "🌬️"
7942
+ },
7943
+ {
7944
+ id: "door",
7945
+ name: "Door",
7946
+ icon: "🚪"
7947
+ },
7948
+ {
7949
+ id: "footsteps",
7950
+ name: "Footsteps",
7951
+ icon: "👣"
7952
+ },
7953
+ {
7954
+ id: "crowd",
7955
+ name: "Crowd / Chatter",
7956
+ icon: "👥"
7957
+ },
7958
+ {
7959
+ id: "telephone",
7960
+ name: "Telephone",
7961
+ icon: "📞"
7962
+ },
7963
+ {
7964
+ id: "engine",
7965
+ name: "Engine / Motor",
7966
+ icon: "⚙️"
7967
+ },
7968
+ {
7969
+ id: "tools",
7970
+ name: "Tools / Construction",
7971
+ icon: "🔨"
7972
+ },
7973
+ {
7974
+ id: "silence",
7975
+ name: "Silence",
7976
+ icon: "🤫"
7977
+ }
7978
+ ];
7769
7979
  var YAMNET_TO_MACRO = {
7770
7980
  mapping: {
7771
7981
  Speech: "speech",
@@ -8032,6 +8242,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8032
8242
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8033
8243
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8034
8244
  /**
8245
+ * Unified event-kind taxonomy — THE single source of truth for
8246
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8247
+ * icon }`.
8248
+ *
8249
+ * This dictionary folds together what used to be scattered across four
8250
+ * copies:
8251
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8252
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8253
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8254
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8255
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8256
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8257
+ *
8258
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8259
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8260
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8261
+ * color or an icon: they read this dictionary (server descriptors carry the
8262
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8263
+ *
8264
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8265
+ */
8266
+ var TAXONOMY_COLORS = {
8267
+ motion: "#f59e0b",
8268
+ audio: "#06b6d4",
8269
+ person: "#22c55e",
8270
+ vehicle: "#3b82f6",
8271
+ animal: "#f97316",
8272
+ package: "#a855f7",
8273
+ sensor: "#8b5cf6",
8274
+ control: "#10b981",
8275
+ genericDetection: "#64748b"
8276
+ };
8277
+ var DETECTION_SUB_COLORS = {
8278
+ car: "#f59e0b",
8279
+ truck: "#d97706",
8280
+ bus: "#b45309",
8281
+ motorcycle: "#eab308",
8282
+ bicycle: "#ca8a04",
8283
+ airplane: "#60a5fa",
8284
+ boat: "#2563eb",
8285
+ train: "#1d4ed8",
8286
+ bird: "#14b8a6",
8287
+ dog: "#84cc16",
8288
+ cat: "#f97316",
8289
+ horse: "#a16207",
8290
+ sheep: "#a3a3a3",
8291
+ cow: "#78716c",
8292
+ elephant: "#6b7280",
8293
+ bear: "#7c2d12",
8294
+ zebra: "#404040",
8295
+ giraffe: "#d4a373"
8296
+ };
8297
+ function titleCase(id) {
8298
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8299
+ }
8300
+ var entries = /* @__PURE__ */ new Map();
8301
+ function macro(kind, category, color, iconId, label) {
8302
+ entries.set(kind, {
8303
+ kind,
8304
+ parentKind: null,
8305
+ level: "macro",
8306
+ category,
8307
+ color,
8308
+ iconId,
8309
+ labelKey: `eventKind.${kind}`,
8310
+ label
8311
+ });
8312
+ }
8313
+ function sub(kind, parentKind, category, color, iconId, label) {
8314
+ entries.set(kind, {
8315
+ kind,
8316
+ parentKind,
8317
+ level: "sub",
8318
+ category,
8319
+ color,
8320
+ iconId,
8321
+ labelKey: `eventKind.${kind}`,
8322
+ label
8323
+ });
8324
+ }
8325
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8326
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8327
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8328
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8329
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8330
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8331
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8332
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8333
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8334
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8335
+ if (entries.has(cocoClass)) continue;
8336
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8337
+ }
8338
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8339
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8340
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8341
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8342
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8343
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8344
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8345
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8346
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8347
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8348
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8349
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8350
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8351
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8352
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8353
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8354
+ sub("button", "control", "control", "#10b981", "button", "Button");
8355
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8356
+ for (const l of AUDIO_MACRO_LABELS) {
8357
+ const kind = `audio-${l.id}`;
8358
+ if (entries.has(kind)) continue;
8359
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8360
+ }
8361
+ /** The complete taxonomy dictionary, keyed by kind. */
8362
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8363
+ /**
8035
8364
  * Error types for the safe expression engine. Two distinct classes so callers
8036
8365
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8037
8366
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -16052,17 +16381,30 @@ var EventKindCategorySchema = _enum([
16052
16381
  "audio",
16053
16382
  "detection",
16054
16383
  "sensor",
16384
+ "control",
16055
16385
  "custom",
16056
16386
  "package"
16057
16387
  ]);
16388
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16389
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
16058
16390
  var EventKindDescriptorSchema = object({
16059
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16391
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
16060
16392
  kind: string(),
16393
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16394
+ labelKey: string(),
16395
+ /** English fallback label (kept for clients that don't translate). */
16061
16396
  label: string(),
16062
16397
  /** Hex color for timeline/legend rendering. */
16063
16398
  color: string(),
16399
+ /** Dictionary id → lucide component on the UI side. */
16400
+ iconId: string(),
16401
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
16064
16402
  icon: EventKindIconSchema,
16065
16403
  category: EventKindCategorySchema,
16404
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16405
+ parentKind: string().nullable(),
16406
+ /** Derived from `parentKind`, explicit for the client tree. */
16407
+ level: EventKindLevelSchema,
16066
16408
  /** Which cap + device contributes this kind. For built-ins the camera
16067
16409
  * itself; for sensor kinds the LINKED source device. */
16068
16410
  source: object({
@@ -16135,11 +16477,21 @@ var TrackAudioLabelSchema = object({
16135
16477
  firstAt: number(),
16136
16478
  lastAt: number()
16137
16479
  });
16480
+ /**
16481
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16482
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16483
+ * linked sensor/control state change (no positions; carries a snapshot). The
16484
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16485
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16486
+ */
16487
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16138
16488
  var TrackSchema = object({
16139
16489
  trackId: string(),
16140
16490
  deviceId: number(),
16141
16491
  className: string(),
16142
16492
  label: string().optional(),
16493
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16494
+ source: TrackSourceSchema.optional(),
16143
16495
  firstSeen: number(),
16144
16496
  lastSeen: number(),
16145
16497
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16394,6 +16746,26 @@ var TrackCascadeCountsSchema = object({
16394
16746
  /** Per-track CLIP search vectors removed (best-effort). */
16395
16747
  embeddings: number().int()
16396
16748
  });
16749
+ /** Event-store footprint for one camera. */
16750
+ var EventStoreDeviceFootprintSchema = object({
16751
+ deviceId: number(),
16752
+ /** Persisted event rows (motion + object + audio) for the camera. */
16753
+ rows: number().int(),
16754
+ /** Event-owned media bytes on disk for the camera. */
16755
+ bytes: number().int()
16756
+ });
16757
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16758
+ var EventStoreFootprintSchema = object({
16759
+ totalRows: number().int(),
16760
+ totalBytes: number().int(),
16761
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16762
+ });
16763
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16764
+ var EventPruneCountsSchema = object({
16765
+ motion: number().int(),
16766
+ object: number().int(),
16767
+ audio: number().int()
16768
+ });
16397
16769
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16398
16770
  deviceId: number(),
16399
16771
  trackId: string()
@@ -16457,6 +16829,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16457
16829
  }), {
16458
16830
  kind: "mutation",
16459
16831
  auth: "admin"
16832
+ }), method(object({}), EventStoreFootprintSchema, {
16833
+ kind: "query",
16834
+ auth: "admin"
16835
+ }), method(object({
16836
+ olderThanMs: number(),
16837
+ reason: OpsLogReasonSchema.optional()
16838
+ }), EventPruneCountsSchema, {
16839
+ kind: "mutation",
16840
+ auth: "admin"
16841
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16842
+ kind: "mutation",
16843
+ auth: "admin"
16844
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16845
+ kind: "query",
16846
+ auth: "admin"
16460
16847
  }), method(object({
16461
16848
  eventId: string(),
16462
16849
  kind: MediaFileKindEnum.optional()
@@ -16481,6 +16868,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16481
16868
  eventId: string(),
16482
16869
  timestamp: number()
16483
16870
  });
16871
+ /**
16872
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16873
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16874
+ * caps into per-camera event-kind descriptors.
16875
+ *
16876
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16877
+ * is NOT duplicated here — every entry is derived from the single
16878
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16879
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16880
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16881
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16882
+ * eventful cap is missing.
16883
+ */
16884
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16885
+ var LEGACY_ICON = {
16886
+ motion: "motion",
16887
+ audio: "audio",
16888
+ person: "person",
16889
+ vehicle: "vehicle",
16890
+ animal: "animal",
16891
+ package: "package",
16892
+ door: "door",
16893
+ pir: "pir",
16894
+ smoke: "smoke",
16895
+ water: "water",
16896
+ button: "button",
16897
+ generic: "generic",
16898
+ gas: "smoke",
16899
+ vibration: "generic",
16900
+ tamper: "generic",
16901
+ presence: "person",
16902
+ lock: "generic",
16903
+ siren: "generic",
16904
+ switch: "generic",
16905
+ doorbell: "button"
16906
+ };
16907
+ function legacyIcon(iconId) {
16908
+ return LEGACY_ICON[iconId] ?? "generic";
16909
+ }
16910
+ /**
16911
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16912
+ * The anti-drift guard cross-checks this against the eventful caps declared
16913
+ * in `packages/types/src/capabilities/*.cap.ts`.
16914
+ */
16915
+ var CAP_TO_KIND = {
16916
+ contact: "contact",
16917
+ motion: "motion-sensor",
16918
+ smoke: "smoke",
16919
+ flood: "flood",
16920
+ gas: "gas",
16921
+ "carbon-monoxide": "carbon-monoxide",
16922
+ vibration: "vibration",
16923
+ tamper: "tamper",
16924
+ presence: "presence",
16925
+ "enum-sensor": "enum-sensor",
16926
+ "event-emitter": "device-event",
16927
+ "lock-control": "lock",
16928
+ switch: "switch",
16929
+ button: "button",
16930
+ doorbell: "doorbell"
16931
+ };
16932
+ function buildDescriptor(capName, kind) {
16933
+ const t = EVENT_TAXONOMY[kind];
16934
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16935
+ return {
16936
+ ...t,
16937
+ icon: legacyIcon(t.iconId)
16938
+ };
16939
+ }
16940
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16484
16941
  var CameraPipelineConfigSchema = object({
16485
16942
  engine: PipelineEngineChoiceSchema.optional(),
16486
16943
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19690,13 +20147,29 @@ method(object({
19690
20147
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19691
20148
  kind: "mutation",
19692
20149
  auth: "admin"
19693
- }), method(object({ deviceId: number() }), object({
20150
+ }), method(object({
20151
+ deviceId: number(),
20152
+ reason: OpsLogReasonSchema.optional()
20153
+ }), object({
19694
20154
  floorMs: number().nullable(),
19695
20155
  deletedBuckets: number().int(),
19696
20156
  reclaimedBytes: number().int()
19697
20157
  }), {
19698
20158
  kind: "mutation",
19699
20159
  auth: "admin"
20160
+ }), method(object({
20161
+ deviceId: number(),
20162
+ fromMs: number().optional(),
20163
+ toMs: number().optional()
20164
+ }), object({
20165
+ deletedBuckets: number().int(),
20166
+ reclaimedBytes: number().int()
20167
+ }), {
20168
+ kind: "mutation",
20169
+ auth: "admin"
20170
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20171
+ kind: "query",
20172
+ auth: "admin"
19700
20173
  });
19701
20174
  /**
19702
20175
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22818,6 +23291,12 @@ Object.freeze({
22818
23291
  addonId: null,
22819
23292
  access: "delete"
22820
23293
  },
23294
+ "pipelineAnalytics.deleteDeviceEvents": {
23295
+ capName: "pipeline-analytics",
23296
+ capScope: "device",
23297
+ addonId: null,
23298
+ access: "delete"
23299
+ },
22821
23300
  "pipelineAnalytics.deleteTracks": {
22822
23301
  capName: "pipeline-analytics",
22823
23302
  capScope: "device",
@@ -22848,6 +23327,12 @@ Object.freeze({
22848
23327
  addonId: null,
22849
23328
  access: "view"
22850
23329
  },
23330
+ "pipelineAnalytics.getEventStoreFootprint": {
23331
+ capName: "pipeline-analytics",
23332
+ capScope: "device",
23333
+ addonId: null,
23334
+ access: "view"
23335
+ },
22851
23336
  "pipelineAnalytics.getKeyEvents": {
22852
23337
  capName: "pipeline-analytics",
22853
23338
  capScope: "device",
@@ -22890,6 +23375,12 @@ Object.freeze({
22890
23375
  addonId: null,
22891
23376
  access: "view"
22892
23377
  },
23378
+ "pipelineAnalytics.listOpsLog": {
23379
+ capName: "pipeline-analytics",
23380
+ capScope: "device",
23381
+ addonId: null,
23382
+ access: "view"
23383
+ },
22893
23384
  "pipelineAnalytics.listRecentTracks": {
22894
23385
  capName: "pipeline-analytics",
22895
23386
  capScope: "device",
@@ -22902,6 +23393,12 @@ Object.freeze({
22902
23393
  addonId: null,
22903
23394
  access: "view"
22904
23395
  },
23396
+ "pipelineAnalytics.pruneEvents": {
23397
+ capName: "pipeline-analytics",
23398
+ capScope: "device",
23399
+ addonId: null,
23400
+ access: "create"
23401
+ },
22905
23402
  "pipelineAnalytics.pruneEventsBefore": {
22906
23403
  capName: "pipeline-analytics",
22907
23404
  capScope: "device",
@@ -23664,6 +24161,12 @@ Object.freeze({
23664
24161
  addonId: null,
23665
24162
  access: "create"
23666
24163
  },
24164
+ "recording.deleteFootprint": {
24165
+ capName: "recording",
24166
+ capScope: "system",
24167
+ addonId: null,
24168
+ access: "delete"
24169
+ },
23667
24170
  "recording.getAvailability": {
23668
24171
  capName: "recording",
23669
24172
  capScope: "system",
@@ -23694,6 +24197,12 @@ Object.freeze({
23694
24197
  addonId: null,
23695
24198
  access: "view"
23696
24199
  },
24200
+ "recording.listOpsLog": {
24201
+ capName: "recording",
24202
+ capScope: "system",
24203
+ addonId: null,
24204
+ access: "view"
24205
+ },
23697
24206
  "recording.locateSegment": {
23698
24207
  capName: "recording",
23699
24208
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-alexa",
3
- "version": "1.1.25",
3
+ "version": "1.1.27",
4
4
  "description": "Alexa Smart Home Skill export — hub-side OAuth provider + directive handler. The companion AWS Lambda forwards Alexa directives to this addon's /addon/export-alexa/directive endpoint.",
5
5
  "keywords": [
6
6
  "camstack",