@camstack/addon-notifiers 1.1.28 → 1.1.30

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
@@ -7449,6 +7449,62 @@ var RecordingConfigSchema = object({
7449
7449
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7450
7450
  });
7451
7451
  /**
7452
+ * Ops-log — the durable, append-only operations audit shared by the
7453
+ * recordings and events management surfaces.
7454
+ *
7455
+ * ONE row shape is reused for both domains so a single "Activity" view can
7456
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7457
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7458
+ * management operation, WHY it ran (reason), and its measurable effect
7459
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7460
+ * never fail the operation it records.
7461
+ */
7462
+ /** Which management domain the operation belongs to. */
7463
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7464
+ /** The kind of management operation performed. */
7465
+ var OpsLogOpSchema = _enum([
7466
+ "prune",
7467
+ "manual-delete",
7468
+ "rescan",
7469
+ "retention-run"
7470
+ ]);
7471
+ /** Why the operation ran. */
7472
+ var OpsLogReasonSchema = _enum([
7473
+ "retention",
7474
+ "quota",
7475
+ "manual",
7476
+ "operator"
7477
+ ]);
7478
+ /** One audit row, shared verbatim by both domains. */
7479
+ var OpsLogEntrySchema = object({
7480
+ /** Unique row id. */
7481
+ id: string(),
7482
+ /** Epoch ms the operation completed. */
7483
+ at: number(),
7484
+ domain: OpsLogDomainSchema,
7485
+ op: OpsLogOpSchema,
7486
+ reason: OpsLogReasonSchema,
7487
+ /** The camera the op targeted; null for a cluster/global op. */
7488
+ deviceId: number().nullable(),
7489
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7490
+ nodeId: string(),
7491
+ /** Buckets / rows deleted (op-specific unit). */
7492
+ itemsAffected: number(),
7493
+ /** Bytes reclaimed by the op (0 when not measurable). */
7494
+ bytesReclaimed: number(),
7495
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7496
+ detail: string().nullable(),
7497
+ /** Who/what triggered the op. */
7498
+ actor: string()
7499
+ });
7500
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7501
+ var OpsLogQueryInputSchema = object({
7502
+ /** Restrict to a single camera; omit for every row. */
7503
+ deviceId: number().optional(),
7504
+ /** Max rows returned, newest-first. */
7505
+ limit: number().int().min(1).max(1e3).optional()
7506
+ });
7507
+ /**
7452
7508
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7453
7509
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7454
7510
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7692,6 +7748,160 @@ var EncodeProfileSchema = object({
7692
7748
  */
7693
7749
  outputArgs: array(string()).optional()
7694
7750
  });
7751
+ var COCO_TO_MACRO = {
7752
+ mapping: {
7753
+ person: "person",
7754
+ bicycle: "vehicle",
7755
+ car: "vehicle",
7756
+ motorcycle: "vehicle",
7757
+ airplane: "vehicle",
7758
+ bus: "vehicle",
7759
+ train: "vehicle",
7760
+ truck: "vehicle",
7761
+ boat: "vehicle",
7762
+ bird: "animal",
7763
+ cat: "animal",
7764
+ dog: "animal",
7765
+ horse: "animal",
7766
+ sheep: "animal",
7767
+ cow: "animal",
7768
+ elephant: "animal",
7769
+ bear: "animal",
7770
+ zebra: "animal",
7771
+ giraffe: "animal",
7772
+ suitcase: "package",
7773
+ backpack: "package",
7774
+ handbag: "package"
7775
+ },
7776
+ preserveOriginal: false
7777
+ };
7778
+ var AUDIO_MACRO_LABELS = [
7779
+ {
7780
+ id: "speech",
7781
+ name: "Speech",
7782
+ icon: "🗣️"
7783
+ },
7784
+ {
7785
+ id: "scream",
7786
+ name: "Scream / Shout",
7787
+ icon: "😱"
7788
+ },
7789
+ {
7790
+ id: "crying",
7791
+ name: "Crying / Baby",
7792
+ icon: "😢"
7793
+ },
7794
+ {
7795
+ id: "laughter",
7796
+ name: "Laughter",
7797
+ icon: "😂"
7798
+ },
7799
+ {
7800
+ id: "music",
7801
+ name: "Music",
7802
+ icon: "🎵"
7803
+ },
7804
+ {
7805
+ id: "dog",
7806
+ name: "Dog",
7807
+ icon: "🐕"
7808
+ },
7809
+ {
7810
+ id: "cat",
7811
+ name: "Cat",
7812
+ icon: "🐈"
7813
+ },
7814
+ {
7815
+ id: "bird",
7816
+ name: "Bird",
7817
+ icon: "🐦"
7818
+ },
7819
+ {
7820
+ id: "animal",
7821
+ name: "Animal (other)",
7822
+ icon: "🐾"
7823
+ },
7824
+ {
7825
+ id: "alarm",
7826
+ name: "Alarm / Siren",
7827
+ icon: "🚨"
7828
+ },
7829
+ {
7830
+ id: "doorbell",
7831
+ name: "Doorbell / Knock",
7832
+ icon: "🔔"
7833
+ },
7834
+ {
7835
+ id: "glass_breaking",
7836
+ name: "Glass Breaking",
7837
+ icon: "💥"
7838
+ },
7839
+ {
7840
+ id: "gunshot",
7841
+ name: "Gunshot / Explosion",
7842
+ icon: "💣"
7843
+ },
7844
+ {
7845
+ id: "vehicle",
7846
+ name: "Vehicle",
7847
+ icon: "🚗"
7848
+ },
7849
+ {
7850
+ id: "siren",
7851
+ name: "Emergency Siren",
7852
+ icon: "🚑"
7853
+ },
7854
+ {
7855
+ id: "fire",
7856
+ name: "Fire / Smoke",
7857
+ icon: "🔥"
7858
+ },
7859
+ {
7860
+ id: "water",
7861
+ name: "Water",
7862
+ icon: "💧"
7863
+ },
7864
+ {
7865
+ id: "wind",
7866
+ name: "Wind / Weather",
7867
+ icon: "🌬️"
7868
+ },
7869
+ {
7870
+ id: "door",
7871
+ name: "Door",
7872
+ icon: "🚪"
7873
+ },
7874
+ {
7875
+ id: "footsteps",
7876
+ name: "Footsteps",
7877
+ icon: "👣"
7878
+ },
7879
+ {
7880
+ id: "crowd",
7881
+ name: "Crowd / Chatter",
7882
+ icon: "👥"
7883
+ },
7884
+ {
7885
+ id: "telephone",
7886
+ name: "Telephone",
7887
+ icon: "📞"
7888
+ },
7889
+ {
7890
+ id: "engine",
7891
+ name: "Engine / Motor",
7892
+ icon: "⚙️"
7893
+ },
7894
+ {
7895
+ id: "tools",
7896
+ name: "Tools / Construction",
7897
+ icon: "🔨"
7898
+ },
7899
+ {
7900
+ id: "silence",
7901
+ name: "Silence",
7902
+ icon: "🤫"
7903
+ }
7904
+ ];
7695
7905
  var YAMNET_TO_MACRO = {
7696
7906
  mapping: {
7697
7907
  Speech: "speech",
@@ -7958,6 +8168,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7958
8168
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7959
8169
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7960
8170
  /**
8171
+ * Unified event-kind taxonomy — THE single source of truth for
8172
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8173
+ * icon }`.
8174
+ *
8175
+ * This dictionary folds together what used to be scattered across four
8176
+ * copies:
8177
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8178
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8179
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8180
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8181
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8182
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8183
+ *
8184
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8185
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8186
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8187
+ * color or an icon: they read this dictionary (server descriptors carry the
8188
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8189
+ *
8190
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8191
+ */
8192
+ var TAXONOMY_COLORS = {
8193
+ motion: "#f59e0b",
8194
+ audio: "#06b6d4",
8195
+ person: "#22c55e",
8196
+ vehicle: "#3b82f6",
8197
+ animal: "#f97316",
8198
+ package: "#a855f7",
8199
+ sensor: "#8b5cf6",
8200
+ control: "#10b981",
8201
+ genericDetection: "#64748b"
8202
+ };
8203
+ var DETECTION_SUB_COLORS = {
8204
+ car: "#f59e0b",
8205
+ truck: "#d97706",
8206
+ bus: "#b45309",
8207
+ motorcycle: "#eab308",
8208
+ bicycle: "#ca8a04",
8209
+ airplane: "#60a5fa",
8210
+ boat: "#2563eb",
8211
+ train: "#1d4ed8",
8212
+ bird: "#14b8a6",
8213
+ dog: "#84cc16",
8214
+ cat: "#f97316",
8215
+ horse: "#a16207",
8216
+ sheep: "#a3a3a3",
8217
+ cow: "#78716c",
8218
+ elephant: "#6b7280",
8219
+ bear: "#7c2d12",
8220
+ zebra: "#404040",
8221
+ giraffe: "#d4a373"
8222
+ };
8223
+ function titleCase(id) {
8224
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8225
+ }
8226
+ var entries = /* @__PURE__ */ new Map();
8227
+ function macro(kind, category, color, iconId, label) {
8228
+ entries.set(kind, {
8229
+ kind,
8230
+ parentKind: null,
8231
+ level: "macro",
8232
+ category,
8233
+ color,
8234
+ iconId,
8235
+ labelKey: `eventKind.${kind}`,
8236
+ label
8237
+ });
8238
+ }
8239
+ function sub(kind, parentKind, category, color, iconId, label) {
8240
+ entries.set(kind, {
8241
+ kind,
8242
+ parentKind,
8243
+ level: "sub",
8244
+ category,
8245
+ color,
8246
+ iconId,
8247
+ labelKey: `eventKind.${kind}`,
8248
+ label
8249
+ });
8250
+ }
8251
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8252
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8253
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8254
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8255
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8256
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8257
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8258
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8259
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8260
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8261
+ if (entries.has(cocoClass)) continue;
8262
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8263
+ }
8264
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8265
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8266
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8267
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8268
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8269
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8270
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8271
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8272
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8273
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8274
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8275
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8276
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8277
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8278
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8279
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8280
+ sub("button", "control", "control", "#10b981", "button", "Button");
8281
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8282
+ for (const l of AUDIO_MACRO_LABELS) {
8283
+ const kind = `audio-${l.id}`;
8284
+ if (entries.has(kind)) continue;
8285
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8286
+ }
8287
+ /** The complete taxonomy dictionary, keyed by kind. */
8288
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8289
+ /**
7961
8290
  * Error types for the safe expression engine. Two distinct classes so callers
7962
8291
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7963
8292
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -16133,17 +16462,30 @@ var EventKindCategorySchema = _enum([
16133
16462
  "audio",
16134
16463
  "detection",
16135
16464
  "sensor",
16465
+ "control",
16136
16466
  "custom",
16137
16467
  "package"
16138
16468
  ]);
16469
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16470
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
16139
16471
  var EventKindDescriptorSchema = object({
16140
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16472
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
16141
16473
  kind: string(),
16474
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16475
+ labelKey: string(),
16476
+ /** English fallback label (kept for clients that don't translate). */
16142
16477
  label: string(),
16143
16478
  /** Hex color for timeline/legend rendering. */
16144
16479
  color: string(),
16480
+ /** Dictionary id → lucide component on the UI side. */
16481
+ iconId: string(),
16482
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
16145
16483
  icon: EventKindIconSchema,
16146
16484
  category: EventKindCategorySchema,
16485
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16486
+ parentKind: string().nullable(),
16487
+ /** Derived from `parentKind`, explicit for the client tree. */
16488
+ level: EventKindLevelSchema,
16147
16489
  /** Which cap + device contributes this kind. For built-ins the camera
16148
16490
  * itself; for sensor kinds the LINKED source device. */
16149
16491
  source: object({
@@ -16216,11 +16558,21 @@ var TrackAudioLabelSchema = object({
16216
16558
  firstAt: number(),
16217
16559
  lastAt: number()
16218
16560
  });
16561
+ /**
16562
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16563
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16564
+ * linked sensor/control state change (no positions; carries a snapshot). The
16565
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16566
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16567
+ */
16568
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16219
16569
  var TrackSchema = object({
16220
16570
  trackId: string(),
16221
16571
  deviceId: number(),
16222
16572
  className: string(),
16223
16573
  label: string().optional(),
16574
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16575
+ source: TrackSourceSchema.optional(),
16224
16576
  firstSeen: number(),
16225
16577
  lastSeen: number(),
16226
16578
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16475,6 +16827,26 @@ var TrackCascadeCountsSchema = object({
16475
16827
  /** Per-track CLIP search vectors removed (best-effort). */
16476
16828
  embeddings: number().int()
16477
16829
  });
16830
+ /** Event-store footprint for one camera. */
16831
+ var EventStoreDeviceFootprintSchema = object({
16832
+ deviceId: number(),
16833
+ /** Persisted event rows (motion + object + audio) for the camera. */
16834
+ rows: number().int(),
16835
+ /** Event-owned media bytes on disk for the camera. */
16836
+ bytes: number().int()
16837
+ });
16838
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16839
+ var EventStoreFootprintSchema = object({
16840
+ totalRows: number().int(),
16841
+ totalBytes: number().int(),
16842
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16843
+ });
16844
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16845
+ var EventPruneCountsSchema = object({
16846
+ motion: number().int(),
16847
+ object: number().int(),
16848
+ audio: number().int()
16849
+ });
16478
16850
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16479
16851
  deviceId: number(),
16480
16852
  trackId: string()
@@ -16538,6 +16910,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16538
16910
  }), {
16539
16911
  kind: "mutation",
16540
16912
  auth: "admin"
16913
+ }), method(object({}), EventStoreFootprintSchema, {
16914
+ kind: "query",
16915
+ auth: "admin"
16916
+ }), method(object({
16917
+ olderThanMs: number(),
16918
+ reason: OpsLogReasonSchema.optional()
16919
+ }), EventPruneCountsSchema, {
16920
+ kind: "mutation",
16921
+ auth: "admin"
16922
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16923
+ kind: "mutation",
16924
+ auth: "admin"
16925
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16926
+ kind: "query",
16927
+ auth: "admin"
16541
16928
  }), method(object({
16542
16929
  eventId: string(),
16543
16930
  kind: MediaFileKindEnum.optional()
@@ -16562,6 +16949,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16562
16949
  eventId: string(),
16563
16950
  timestamp: number()
16564
16951
  });
16952
+ /**
16953
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16954
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16955
+ * caps into per-camera event-kind descriptors.
16956
+ *
16957
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16958
+ * is NOT duplicated here — every entry is derived from the single
16959
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16960
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16961
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16962
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16963
+ * eventful cap is missing.
16964
+ */
16965
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16966
+ var LEGACY_ICON = {
16967
+ motion: "motion",
16968
+ audio: "audio",
16969
+ person: "person",
16970
+ vehicle: "vehicle",
16971
+ animal: "animal",
16972
+ package: "package",
16973
+ door: "door",
16974
+ pir: "pir",
16975
+ smoke: "smoke",
16976
+ water: "water",
16977
+ button: "button",
16978
+ generic: "generic",
16979
+ gas: "smoke",
16980
+ vibration: "generic",
16981
+ tamper: "generic",
16982
+ presence: "person",
16983
+ lock: "generic",
16984
+ siren: "generic",
16985
+ switch: "generic",
16986
+ doorbell: "button"
16987
+ };
16988
+ function legacyIcon(iconId) {
16989
+ return LEGACY_ICON[iconId] ?? "generic";
16990
+ }
16991
+ /**
16992
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16993
+ * The anti-drift guard cross-checks this against the eventful caps declared
16994
+ * in `packages/types/src/capabilities/*.cap.ts`.
16995
+ */
16996
+ var CAP_TO_KIND = {
16997
+ contact: "contact",
16998
+ motion: "motion-sensor",
16999
+ smoke: "smoke",
17000
+ flood: "flood",
17001
+ gas: "gas",
17002
+ "carbon-monoxide": "carbon-monoxide",
17003
+ vibration: "vibration",
17004
+ tamper: "tamper",
17005
+ presence: "presence",
17006
+ "enum-sensor": "enum-sensor",
17007
+ "event-emitter": "device-event",
17008
+ "lock-control": "lock",
17009
+ switch: "switch",
17010
+ button: "button",
17011
+ doorbell: "doorbell"
17012
+ };
17013
+ function buildDescriptor(capName, kind) {
17014
+ const t = EVENT_TAXONOMY[kind];
17015
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
17016
+ return {
17017
+ ...t,
17018
+ icon: legacyIcon(t.iconId)
17019
+ };
17020
+ }
17021
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16565
17022
  var CameraPipelineConfigSchema = object({
16566
17023
  engine: PipelineEngineChoiceSchema.optional(),
16567
17024
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19771,13 +20228,29 @@ method(object({
19771
20228
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19772
20229
  kind: "mutation",
19773
20230
  auth: "admin"
19774
- }), method(object({ deviceId: number() }), object({
20231
+ }), method(object({
20232
+ deviceId: number(),
20233
+ reason: OpsLogReasonSchema.optional()
20234
+ }), object({
19775
20235
  floorMs: number().nullable(),
19776
20236
  deletedBuckets: number().int(),
19777
20237
  reclaimedBytes: number().int()
19778
20238
  }), {
19779
20239
  kind: "mutation",
19780
20240
  auth: "admin"
20241
+ }), method(object({
20242
+ deviceId: number(),
20243
+ fromMs: number().optional(),
20244
+ toMs: number().optional()
20245
+ }), object({
20246
+ deletedBuckets: number().int(),
20247
+ reclaimedBytes: number().int()
20248
+ }), {
20249
+ kind: "mutation",
20250
+ auth: "admin"
20251
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20252
+ kind: "query",
20253
+ auth: "admin"
19781
20254
  });
19782
20255
  /**
19783
20256
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22899,6 +23372,12 @@ Object.freeze({
22899
23372
  addonId: null,
22900
23373
  access: "delete"
22901
23374
  },
23375
+ "pipelineAnalytics.deleteDeviceEvents": {
23376
+ capName: "pipeline-analytics",
23377
+ capScope: "device",
23378
+ addonId: null,
23379
+ access: "delete"
23380
+ },
22902
23381
  "pipelineAnalytics.deleteTracks": {
22903
23382
  capName: "pipeline-analytics",
22904
23383
  capScope: "device",
@@ -22929,6 +23408,12 @@ Object.freeze({
22929
23408
  addonId: null,
22930
23409
  access: "view"
22931
23410
  },
23411
+ "pipelineAnalytics.getEventStoreFootprint": {
23412
+ capName: "pipeline-analytics",
23413
+ capScope: "device",
23414
+ addonId: null,
23415
+ access: "view"
23416
+ },
22932
23417
  "pipelineAnalytics.getKeyEvents": {
22933
23418
  capName: "pipeline-analytics",
22934
23419
  capScope: "device",
@@ -22971,6 +23456,12 @@ Object.freeze({
22971
23456
  addonId: null,
22972
23457
  access: "view"
22973
23458
  },
23459
+ "pipelineAnalytics.listOpsLog": {
23460
+ capName: "pipeline-analytics",
23461
+ capScope: "device",
23462
+ addonId: null,
23463
+ access: "view"
23464
+ },
22974
23465
  "pipelineAnalytics.listRecentTracks": {
22975
23466
  capName: "pipeline-analytics",
22976
23467
  capScope: "device",
@@ -22983,6 +23474,12 @@ Object.freeze({
22983
23474
  addonId: null,
22984
23475
  access: "view"
22985
23476
  },
23477
+ "pipelineAnalytics.pruneEvents": {
23478
+ capName: "pipeline-analytics",
23479
+ capScope: "device",
23480
+ addonId: null,
23481
+ access: "create"
23482
+ },
22986
23483
  "pipelineAnalytics.pruneEventsBefore": {
22987
23484
  capName: "pipeline-analytics",
22988
23485
  capScope: "device",
@@ -23745,6 +24242,12 @@ Object.freeze({
23745
24242
  addonId: null,
23746
24243
  access: "create"
23747
24244
  },
24245
+ "recording.deleteFootprint": {
24246
+ capName: "recording",
24247
+ capScope: "system",
24248
+ addonId: null,
24249
+ access: "delete"
24250
+ },
23748
24251
  "recording.getAvailability": {
23749
24252
  capName: "recording",
23750
24253
  capScope: "system",
@@ -23775,6 +24278,12 @@ Object.freeze({
23775
24278
  addonId: null,
23776
24279
  access: "view"
23777
24280
  },
24281
+ "recording.listOpsLog": {
24282
+ capName: "recording",
24283
+ capScope: "system",
24284
+ addonId: null,
24285
+ access: "view"
24286
+ },
23778
24287
  "recording.locateSegment": {
23779
24288
  capName: "recording",
23780
24289
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7445,6 +7445,62 @@ var RecordingConfigSchema = object({
7445
7445
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7446
7446
  });
7447
7447
  /**
7448
+ * Ops-log — the durable, append-only operations audit shared by the
7449
+ * recordings and events management surfaces.
7450
+ *
7451
+ * ONE row shape is reused for both domains so a single "Activity" view can
7452
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7453
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7454
+ * management operation, WHY it ran (reason), and its measurable effect
7455
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7456
+ * never fail the operation it records.
7457
+ */
7458
+ /** Which management domain the operation belongs to. */
7459
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7460
+ /** The kind of management operation performed. */
7461
+ var OpsLogOpSchema = _enum([
7462
+ "prune",
7463
+ "manual-delete",
7464
+ "rescan",
7465
+ "retention-run"
7466
+ ]);
7467
+ /** Why the operation ran. */
7468
+ var OpsLogReasonSchema = _enum([
7469
+ "retention",
7470
+ "quota",
7471
+ "manual",
7472
+ "operator"
7473
+ ]);
7474
+ /** One audit row, shared verbatim by both domains. */
7475
+ var OpsLogEntrySchema = object({
7476
+ /** Unique row id. */
7477
+ id: string(),
7478
+ /** Epoch ms the operation completed. */
7479
+ at: number(),
7480
+ domain: OpsLogDomainSchema,
7481
+ op: OpsLogOpSchema,
7482
+ reason: OpsLogReasonSchema,
7483
+ /** The camera the op targeted; null for a cluster/global op. */
7484
+ deviceId: number().nullable(),
7485
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7486
+ nodeId: string(),
7487
+ /** Buckets / rows deleted (op-specific unit). */
7488
+ itemsAffected: number(),
7489
+ /** Bytes reclaimed by the op (0 when not measurable). */
7490
+ bytesReclaimed: number(),
7491
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7492
+ detail: string().nullable(),
7493
+ /** Who/what triggered the op. */
7494
+ actor: string()
7495
+ });
7496
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7497
+ var OpsLogQueryInputSchema = object({
7498
+ /** Restrict to a single camera; omit for every row. */
7499
+ deviceId: number().optional(),
7500
+ /** Max rows returned, newest-first. */
7501
+ limit: number().int().min(1).max(1e3).optional()
7502
+ });
7503
+ /**
7448
7504
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7449
7505
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7450
7506
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7688,6 +7744,160 @@ var EncodeProfileSchema = object({
7688
7744
  */
7689
7745
  outputArgs: array(string()).optional()
7690
7746
  });
7747
+ var COCO_TO_MACRO = {
7748
+ mapping: {
7749
+ person: "person",
7750
+ bicycle: "vehicle",
7751
+ car: "vehicle",
7752
+ motorcycle: "vehicle",
7753
+ airplane: "vehicle",
7754
+ bus: "vehicle",
7755
+ train: "vehicle",
7756
+ truck: "vehicle",
7757
+ boat: "vehicle",
7758
+ bird: "animal",
7759
+ cat: "animal",
7760
+ dog: "animal",
7761
+ horse: "animal",
7762
+ sheep: "animal",
7763
+ cow: "animal",
7764
+ elephant: "animal",
7765
+ bear: "animal",
7766
+ zebra: "animal",
7767
+ giraffe: "animal",
7768
+ suitcase: "package",
7769
+ backpack: "package",
7770
+ handbag: "package"
7771
+ },
7772
+ preserveOriginal: false
7773
+ };
7774
+ var AUDIO_MACRO_LABELS = [
7775
+ {
7776
+ id: "speech",
7777
+ name: "Speech",
7778
+ icon: "🗣️"
7779
+ },
7780
+ {
7781
+ id: "scream",
7782
+ name: "Scream / Shout",
7783
+ icon: "😱"
7784
+ },
7785
+ {
7786
+ id: "crying",
7787
+ name: "Crying / Baby",
7788
+ icon: "😢"
7789
+ },
7790
+ {
7791
+ id: "laughter",
7792
+ name: "Laughter",
7793
+ icon: "😂"
7794
+ },
7795
+ {
7796
+ id: "music",
7797
+ name: "Music",
7798
+ icon: "🎵"
7799
+ },
7800
+ {
7801
+ id: "dog",
7802
+ name: "Dog",
7803
+ icon: "🐕"
7804
+ },
7805
+ {
7806
+ id: "cat",
7807
+ name: "Cat",
7808
+ icon: "🐈"
7809
+ },
7810
+ {
7811
+ id: "bird",
7812
+ name: "Bird",
7813
+ icon: "🐦"
7814
+ },
7815
+ {
7816
+ id: "animal",
7817
+ name: "Animal (other)",
7818
+ icon: "🐾"
7819
+ },
7820
+ {
7821
+ id: "alarm",
7822
+ name: "Alarm / Siren",
7823
+ icon: "🚨"
7824
+ },
7825
+ {
7826
+ id: "doorbell",
7827
+ name: "Doorbell / Knock",
7828
+ icon: "🔔"
7829
+ },
7830
+ {
7831
+ id: "glass_breaking",
7832
+ name: "Glass Breaking",
7833
+ icon: "💥"
7834
+ },
7835
+ {
7836
+ id: "gunshot",
7837
+ name: "Gunshot / Explosion",
7838
+ icon: "💣"
7839
+ },
7840
+ {
7841
+ id: "vehicle",
7842
+ name: "Vehicle",
7843
+ icon: "🚗"
7844
+ },
7845
+ {
7846
+ id: "siren",
7847
+ name: "Emergency Siren",
7848
+ icon: "🚑"
7849
+ },
7850
+ {
7851
+ id: "fire",
7852
+ name: "Fire / Smoke",
7853
+ icon: "🔥"
7854
+ },
7855
+ {
7856
+ id: "water",
7857
+ name: "Water",
7858
+ icon: "💧"
7859
+ },
7860
+ {
7861
+ id: "wind",
7862
+ name: "Wind / Weather",
7863
+ icon: "🌬️"
7864
+ },
7865
+ {
7866
+ id: "door",
7867
+ name: "Door",
7868
+ icon: "🚪"
7869
+ },
7870
+ {
7871
+ id: "footsteps",
7872
+ name: "Footsteps",
7873
+ icon: "👣"
7874
+ },
7875
+ {
7876
+ id: "crowd",
7877
+ name: "Crowd / Chatter",
7878
+ icon: "👥"
7879
+ },
7880
+ {
7881
+ id: "telephone",
7882
+ name: "Telephone",
7883
+ icon: "📞"
7884
+ },
7885
+ {
7886
+ id: "engine",
7887
+ name: "Engine / Motor",
7888
+ icon: "⚙️"
7889
+ },
7890
+ {
7891
+ id: "tools",
7892
+ name: "Tools / Construction",
7893
+ icon: "🔨"
7894
+ },
7895
+ {
7896
+ id: "silence",
7897
+ name: "Silence",
7898
+ icon: "🤫"
7899
+ }
7900
+ ];
7691
7901
  var YAMNET_TO_MACRO = {
7692
7902
  mapping: {
7693
7903
  Speech: "speech",
@@ -7954,6 +8164,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7954
8164
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7955
8165
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7956
8166
  /**
8167
+ * Unified event-kind taxonomy — THE single source of truth for
8168
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8169
+ * icon }`.
8170
+ *
8171
+ * This dictionary folds together what used to be scattered across four
8172
+ * copies:
8173
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8174
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8175
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8176
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8177
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8178
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8179
+ *
8180
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8181
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8182
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8183
+ * color or an icon: they read this dictionary (server descriptors carry the
8184
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8185
+ *
8186
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8187
+ */
8188
+ var TAXONOMY_COLORS = {
8189
+ motion: "#f59e0b",
8190
+ audio: "#06b6d4",
8191
+ person: "#22c55e",
8192
+ vehicle: "#3b82f6",
8193
+ animal: "#f97316",
8194
+ package: "#a855f7",
8195
+ sensor: "#8b5cf6",
8196
+ control: "#10b981",
8197
+ genericDetection: "#64748b"
8198
+ };
8199
+ var DETECTION_SUB_COLORS = {
8200
+ car: "#f59e0b",
8201
+ truck: "#d97706",
8202
+ bus: "#b45309",
8203
+ motorcycle: "#eab308",
8204
+ bicycle: "#ca8a04",
8205
+ airplane: "#60a5fa",
8206
+ boat: "#2563eb",
8207
+ train: "#1d4ed8",
8208
+ bird: "#14b8a6",
8209
+ dog: "#84cc16",
8210
+ cat: "#f97316",
8211
+ horse: "#a16207",
8212
+ sheep: "#a3a3a3",
8213
+ cow: "#78716c",
8214
+ elephant: "#6b7280",
8215
+ bear: "#7c2d12",
8216
+ zebra: "#404040",
8217
+ giraffe: "#d4a373"
8218
+ };
8219
+ function titleCase(id) {
8220
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8221
+ }
8222
+ var entries = /* @__PURE__ */ new Map();
8223
+ function macro(kind, category, color, iconId, label) {
8224
+ entries.set(kind, {
8225
+ kind,
8226
+ parentKind: null,
8227
+ level: "macro",
8228
+ category,
8229
+ color,
8230
+ iconId,
8231
+ labelKey: `eventKind.${kind}`,
8232
+ label
8233
+ });
8234
+ }
8235
+ function sub(kind, parentKind, category, color, iconId, label) {
8236
+ entries.set(kind, {
8237
+ kind,
8238
+ parentKind,
8239
+ level: "sub",
8240
+ category,
8241
+ color,
8242
+ iconId,
8243
+ labelKey: `eventKind.${kind}`,
8244
+ label
8245
+ });
8246
+ }
8247
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8248
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8249
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8250
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8251
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8252
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8253
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8254
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8255
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8256
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8257
+ if (entries.has(cocoClass)) continue;
8258
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8259
+ }
8260
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8261
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8262
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8263
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8264
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8265
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8266
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8267
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8268
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8269
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8270
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8271
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8272
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8273
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8274
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8275
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8276
+ sub("button", "control", "control", "#10b981", "button", "Button");
8277
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8278
+ for (const l of AUDIO_MACRO_LABELS) {
8279
+ const kind = `audio-${l.id}`;
8280
+ if (entries.has(kind)) continue;
8281
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8282
+ }
8283
+ /** The complete taxonomy dictionary, keyed by kind. */
8284
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8285
+ /**
7957
8286
  * Error types for the safe expression engine. Two distinct classes so callers
7958
8287
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7959
8288
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -16129,17 +16458,30 @@ var EventKindCategorySchema = _enum([
16129
16458
  "audio",
16130
16459
  "detection",
16131
16460
  "sensor",
16461
+ "control",
16132
16462
  "custom",
16133
16463
  "package"
16134
16464
  ]);
16465
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16466
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
16135
16467
  var EventKindDescriptorSchema = object({
16136
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16468
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
16137
16469
  kind: string(),
16470
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16471
+ labelKey: string(),
16472
+ /** English fallback label (kept for clients that don't translate). */
16138
16473
  label: string(),
16139
16474
  /** Hex color for timeline/legend rendering. */
16140
16475
  color: string(),
16476
+ /** Dictionary id → lucide component on the UI side. */
16477
+ iconId: string(),
16478
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
16141
16479
  icon: EventKindIconSchema,
16142
16480
  category: EventKindCategorySchema,
16481
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16482
+ parentKind: string().nullable(),
16483
+ /** Derived from `parentKind`, explicit for the client tree. */
16484
+ level: EventKindLevelSchema,
16143
16485
  /** Which cap + device contributes this kind. For built-ins the camera
16144
16486
  * itself; for sensor kinds the LINKED source device. */
16145
16487
  source: object({
@@ -16212,11 +16554,21 @@ var TrackAudioLabelSchema = object({
16212
16554
  firstAt: number(),
16213
16555
  lastAt: number()
16214
16556
  });
16557
+ /**
16558
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16559
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16560
+ * linked sensor/control state change (no positions; carries a snapshot). The
16561
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16562
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16563
+ */
16564
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16215
16565
  var TrackSchema = object({
16216
16566
  trackId: string(),
16217
16567
  deviceId: number(),
16218
16568
  className: string(),
16219
16569
  label: string().optional(),
16570
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16571
+ source: TrackSourceSchema.optional(),
16220
16572
  firstSeen: number(),
16221
16573
  lastSeen: number(),
16222
16574
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16471,6 +16823,26 @@ var TrackCascadeCountsSchema = object({
16471
16823
  /** Per-track CLIP search vectors removed (best-effort). */
16472
16824
  embeddings: number().int()
16473
16825
  });
16826
+ /** Event-store footprint for one camera. */
16827
+ var EventStoreDeviceFootprintSchema = object({
16828
+ deviceId: number(),
16829
+ /** Persisted event rows (motion + object + audio) for the camera. */
16830
+ rows: number().int(),
16831
+ /** Event-owned media bytes on disk for the camera. */
16832
+ bytes: number().int()
16833
+ });
16834
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16835
+ var EventStoreFootprintSchema = object({
16836
+ totalRows: number().int(),
16837
+ totalBytes: number().int(),
16838
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16839
+ });
16840
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16841
+ var EventPruneCountsSchema = object({
16842
+ motion: number().int(),
16843
+ object: number().int(),
16844
+ audio: number().int()
16845
+ });
16474
16846
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16475
16847
  deviceId: number(),
16476
16848
  trackId: string()
@@ -16534,6 +16906,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16534
16906
  }), {
16535
16907
  kind: "mutation",
16536
16908
  auth: "admin"
16909
+ }), method(object({}), EventStoreFootprintSchema, {
16910
+ kind: "query",
16911
+ auth: "admin"
16912
+ }), method(object({
16913
+ olderThanMs: number(),
16914
+ reason: OpsLogReasonSchema.optional()
16915
+ }), EventPruneCountsSchema, {
16916
+ kind: "mutation",
16917
+ auth: "admin"
16918
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16919
+ kind: "mutation",
16920
+ auth: "admin"
16921
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16922
+ kind: "query",
16923
+ auth: "admin"
16537
16924
  }), method(object({
16538
16925
  eventId: string(),
16539
16926
  kind: MediaFileKindEnum.optional()
@@ -16558,6 +16945,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16558
16945
  eventId: string(),
16559
16946
  timestamp: number()
16560
16947
  });
16948
+ /**
16949
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16950
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16951
+ * caps into per-camera event-kind descriptors.
16952
+ *
16953
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16954
+ * is NOT duplicated here — every entry is derived from the single
16955
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16956
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16957
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16958
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16959
+ * eventful cap is missing.
16960
+ */
16961
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16962
+ var LEGACY_ICON = {
16963
+ motion: "motion",
16964
+ audio: "audio",
16965
+ person: "person",
16966
+ vehicle: "vehicle",
16967
+ animal: "animal",
16968
+ package: "package",
16969
+ door: "door",
16970
+ pir: "pir",
16971
+ smoke: "smoke",
16972
+ water: "water",
16973
+ button: "button",
16974
+ generic: "generic",
16975
+ gas: "smoke",
16976
+ vibration: "generic",
16977
+ tamper: "generic",
16978
+ presence: "person",
16979
+ lock: "generic",
16980
+ siren: "generic",
16981
+ switch: "generic",
16982
+ doorbell: "button"
16983
+ };
16984
+ function legacyIcon(iconId) {
16985
+ return LEGACY_ICON[iconId] ?? "generic";
16986
+ }
16987
+ /**
16988
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16989
+ * The anti-drift guard cross-checks this against the eventful caps declared
16990
+ * in `packages/types/src/capabilities/*.cap.ts`.
16991
+ */
16992
+ var CAP_TO_KIND = {
16993
+ contact: "contact",
16994
+ motion: "motion-sensor",
16995
+ smoke: "smoke",
16996
+ flood: "flood",
16997
+ gas: "gas",
16998
+ "carbon-monoxide": "carbon-monoxide",
16999
+ vibration: "vibration",
17000
+ tamper: "tamper",
17001
+ presence: "presence",
17002
+ "enum-sensor": "enum-sensor",
17003
+ "event-emitter": "device-event",
17004
+ "lock-control": "lock",
17005
+ switch: "switch",
17006
+ button: "button",
17007
+ doorbell: "doorbell"
17008
+ };
17009
+ function buildDescriptor(capName, kind) {
17010
+ const t = EVENT_TAXONOMY[kind];
17011
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
17012
+ return {
17013
+ ...t,
17014
+ icon: legacyIcon(t.iconId)
17015
+ };
17016
+ }
17017
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16561
17018
  var CameraPipelineConfigSchema = object({
16562
17019
  engine: PipelineEngineChoiceSchema.optional(),
16563
17020
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19767,13 +20224,29 @@ method(object({
19767
20224
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19768
20225
  kind: "mutation",
19769
20226
  auth: "admin"
19770
- }), method(object({ deviceId: number() }), object({
20227
+ }), method(object({
20228
+ deviceId: number(),
20229
+ reason: OpsLogReasonSchema.optional()
20230
+ }), object({
19771
20231
  floorMs: number().nullable(),
19772
20232
  deletedBuckets: number().int(),
19773
20233
  reclaimedBytes: number().int()
19774
20234
  }), {
19775
20235
  kind: "mutation",
19776
20236
  auth: "admin"
20237
+ }), method(object({
20238
+ deviceId: number(),
20239
+ fromMs: number().optional(),
20240
+ toMs: number().optional()
20241
+ }), object({
20242
+ deletedBuckets: number().int(),
20243
+ reclaimedBytes: number().int()
20244
+ }), {
20245
+ kind: "mutation",
20246
+ auth: "admin"
20247
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20248
+ kind: "query",
20249
+ auth: "admin"
19777
20250
  });
19778
20251
  /**
19779
20252
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22895,6 +23368,12 @@ Object.freeze({
22895
23368
  addonId: null,
22896
23369
  access: "delete"
22897
23370
  },
23371
+ "pipelineAnalytics.deleteDeviceEvents": {
23372
+ capName: "pipeline-analytics",
23373
+ capScope: "device",
23374
+ addonId: null,
23375
+ access: "delete"
23376
+ },
22898
23377
  "pipelineAnalytics.deleteTracks": {
22899
23378
  capName: "pipeline-analytics",
22900
23379
  capScope: "device",
@@ -22925,6 +23404,12 @@ Object.freeze({
22925
23404
  addonId: null,
22926
23405
  access: "view"
22927
23406
  },
23407
+ "pipelineAnalytics.getEventStoreFootprint": {
23408
+ capName: "pipeline-analytics",
23409
+ capScope: "device",
23410
+ addonId: null,
23411
+ access: "view"
23412
+ },
22928
23413
  "pipelineAnalytics.getKeyEvents": {
22929
23414
  capName: "pipeline-analytics",
22930
23415
  capScope: "device",
@@ -22967,6 +23452,12 @@ Object.freeze({
22967
23452
  addonId: null,
22968
23453
  access: "view"
22969
23454
  },
23455
+ "pipelineAnalytics.listOpsLog": {
23456
+ capName: "pipeline-analytics",
23457
+ capScope: "device",
23458
+ addonId: null,
23459
+ access: "view"
23460
+ },
22970
23461
  "pipelineAnalytics.listRecentTracks": {
22971
23462
  capName: "pipeline-analytics",
22972
23463
  capScope: "device",
@@ -22979,6 +23470,12 @@ Object.freeze({
22979
23470
  addonId: null,
22980
23471
  access: "view"
22981
23472
  },
23473
+ "pipelineAnalytics.pruneEvents": {
23474
+ capName: "pipeline-analytics",
23475
+ capScope: "device",
23476
+ addonId: null,
23477
+ access: "create"
23478
+ },
22982
23479
  "pipelineAnalytics.pruneEventsBefore": {
22983
23480
  capName: "pipeline-analytics",
22984
23481
  capScope: "device",
@@ -23741,6 +24238,12 @@ Object.freeze({
23741
24238
  addonId: null,
23742
24239
  access: "create"
23743
24240
  },
24241
+ "recording.deleteFootprint": {
24242
+ capName: "recording",
24243
+ capScope: "system",
24244
+ addonId: null,
24245
+ access: "delete"
24246
+ },
23744
24247
  "recording.getAvailability": {
23745
24248
  capName: "recording",
23746
24249
  capScope: "system",
@@ -23771,6 +24274,12 @@ Object.freeze({
23771
24274
  addonId: null,
23772
24275
  access: "view"
23773
24276
  },
24277
+ "recording.listOpsLog": {
24278
+ capName: "recording",
24279
+ capScope: "system",
24280
+ addonId: null,
24281
+ access: "view"
24282
+ },
23774
24283
  "recording.locateSegment": {
23775
24284
  capName: "recording",
23776
24285
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-notifiers",
3
- "version": "1.1.28",
3
+ "version": "1.1.30",
4
4
  "description": "System notifiers addon for CamStack — a `notification-output` collection provider hosting per-kind notifier adapters (ntfy, pushover, gotify, telegram, discord, webhook, zentik).",
5
5
  "keywords": [
6
6
  "camstack",