@camstack/addon-advanced-notifier 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
@@ -7332,6 +7332,62 @@ var RecordingConfigSchema = object({
7332
7332
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7333
7333
  });
7334
7334
  /**
7335
+ * Ops-log — the durable, append-only operations audit shared by the
7336
+ * recordings and events management surfaces.
7337
+ *
7338
+ * ONE row shape is reused for both domains so a single "Activity" view can
7339
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7340
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7341
+ * management operation, WHY it ran (reason), and its measurable effect
7342
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7343
+ * never fail the operation it records.
7344
+ */
7345
+ /** Which management domain the operation belongs to. */
7346
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7347
+ /** The kind of management operation performed. */
7348
+ var OpsLogOpSchema = _enum([
7349
+ "prune",
7350
+ "manual-delete",
7351
+ "rescan",
7352
+ "retention-run"
7353
+ ]);
7354
+ /** Why the operation ran. */
7355
+ var OpsLogReasonSchema = _enum([
7356
+ "retention",
7357
+ "quota",
7358
+ "manual",
7359
+ "operator"
7360
+ ]);
7361
+ /** One audit row, shared verbatim by both domains. */
7362
+ var OpsLogEntrySchema = object({
7363
+ /** Unique row id. */
7364
+ id: string(),
7365
+ /** Epoch ms the operation completed. */
7366
+ at: number(),
7367
+ domain: OpsLogDomainSchema,
7368
+ op: OpsLogOpSchema,
7369
+ reason: OpsLogReasonSchema,
7370
+ /** The camera the op targeted; null for a cluster/global op. */
7371
+ deviceId: number().nullable(),
7372
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7373
+ nodeId: string(),
7374
+ /** Buckets / rows deleted (op-specific unit). */
7375
+ itemsAffected: number(),
7376
+ /** Bytes reclaimed by the op (0 when not measurable). */
7377
+ bytesReclaimed: number(),
7378
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7379
+ detail: string().nullable(),
7380
+ /** Who/what triggered the op. */
7381
+ actor: string()
7382
+ });
7383
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7384
+ var OpsLogQueryInputSchema = object({
7385
+ /** Restrict to a single camera; omit for every row. */
7386
+ deviceId: number().optional(),
7387
+ /** Max rows returned, newest-first. */
7388
+ limit: number().int().min(1).max(1e3).optional()
7389
+ });
7390
+ /**
7335
7391
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7336
7392
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7337
7393
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7589,6 +7645,160 @@ function cosineSimilarity(a, b) {
7589
7645
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
7590
7646
  return denom === 0 ? 0 : dotProduct / denom;
7591
7647
  }
7648
+ var COCO_TO_MACRO = {
7649
+ mapping: {
7650
+ person: "person",
7651
+ bicycle: "vehicle",
7652
+ car: "vehicle",
7653
+ motorcycle: "vehicle",
7654
+ airplane: "vehicle",
7655
+ bus: "vehicle",
7656
+ train: "vehicle",
7657
+ truck: "vehicle",
7658
+ boat: "vehicle",
7659
+ bird: "animal",
7660
+ cat: "animal",
7661
+ dog: "animal",
7662
+ horse: "animal",
7663
+ sheep: "animal",
7664
+ cow: "animal",
7665
+ elephant: "animal",
7666
+ bear: "animal",
7667
+ zebra: "animal",
7668
+ giraffe: "animal",
7669
+ suitcase: "package",
7670
+ backpack: "package",
7671
+ handbag: "package"
7672
+ },
7673
+ preserveOriginal: false
7674
+ };
7675
+ var AUDIO_MACRO_LABELS = [
7676
+ {
7677
+ id: "speech",
7678
+ name: "Speech",
7679
+ icon: "🗣️"
7680
+ },
7681
+ {
7682
+ id: "scream",
7683
+ name: "Scream / Shout",
7684
+ icon: "😱"
7685
+ },
7686
+ {
7687
+ id: "crying",
7688
+ name: "Crying / Baby",
7689
+ icon: "😢"
7690
+ },
7691
+ {
7692
+ id: "laughter",
7693
+ name: "Laughter",
7694
+ icon: "😂"
7695
+ },
7696
+ {
7697
+ id: "music",
7698
+ name: "Music",
7699
+ icon: "🎵"
7700
+ },
7701
+ {
7702
+ id: "dog",
7703
+ name: "Dog",
7704
+ icon: "🐕"
7705
+ },
7706
+ {
7707
+ id: "cat",
7708
+ name: "Cat",
7709
+ icon: "🐈"
7710
+ },
7711
+ {
7712
+ id: "bird",
7713
+ name: "Bird",
7714
+ icon: "🐦"
7715
+ },
7716
+ {
7717
+ id: "animal",
7718
+ name: "Animal (other)",
7719
+ icon: "🐾"
7720
+ },
7721
+ {
7722
+ id: "alarm",
7723
+ name: "Alarm / Siren",
7724
+ icon: "🚨"
7725
+ },
7726
+ {
7727
+ id: "doorbell",
7728
+ name: "Doorbell / Knock",
7729
+ icon: "🔔"
7730
+ },
7731
+ {
7732
+ id: "glass_breaking",
7733
+ name: "Glass Breaking",
7734
+ icon: "💥"
7735
+ },
7736
+ {
7737
+ id: "gunshot",
7738
+ name: "Gunshot / Explosion",
7739
+ icon: "💣"
7740
+ },
7741
+ {
7742
+ id: "vehicle",
7743
+ name: "Vehicle",
7744
+ icon: "🚗"
7745
+ },
7746
+ {
7747
+ id: "siren",
7748
+ name: "Emergency Siren",
7749
+ icon: "🚑"
7750
+ },
7751
+ {
7752
+ id: "fire",
7753
+ name: "Fire / Smoke",
7754
+ icon: "🔥"
7755
+ },
7756
+ {
7757
+ id: "water",
7758
+ name: "Water",
7759
+ icon: "💧"
7760
+ },
7761
+ {
7762
+ id: "wind",
7763
+ name: "Wind / Weather",
7764
+ icon: "🌬️"
7765
+ },
7766
+ {
7767
+ id: "door",
7768
+ name: "Door",
7769
+ icon: "🚪"
7770
+ },
7771
+ {
7772
+ id: "footsteps",
7773
+ name: "Footsteps",
7774
+ icon: "👣"
7775
+ },
7776
+ {
7777
+ id: "crowd",
7778
+ name: "Crowd / Chatter",
7779
+ icon: "👥"
7780
+ },
7781
+ {
7782
+ id: "telephone",
7783
+ name: "Telephone",
7784
+ icon: "📞"
7785
+ },
7786
+ {
7787
+ id: "engine",
7788
+ name: "Engine / Motor",
7789
+ icon: "⚙️"
7790
+ },
7791
+ {
7792
+ id: "tools",
7793
+ name: "Tools / Construction",
7794
+ icon: "🔨"
7795
+ },
7796
+ {
7797
+ id: "silence",
7798
+ name: "Silence",
7799
+ icon: "🤫"
7800
+ }
7801
+ ];
7592
7802
  var YAMNET_TO_MACRO = {
7593
7803
  mapping: {
7594
7804
  Speech: "speech",
@@ -7855,6 +8065,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7855
8065
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7856
8066
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7857
8067
  /**
8068
+ * Unified event-kind taxonomy — THE single source of truth for
8069
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8070
+ * icon }`.
8071
+ *
8072
+ * This dictionary folds together what used to be scattered across four
8073
+ * copies:
8074
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8075
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8076
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8077
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8078
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8079
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8080
+ *
8081
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8082
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8083
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8084
+ * color or an icon: they read this dictionary (server descriptors carry the
8085
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8086
+ *
8087
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8088
+ */
8089
+ var TAXONOMY_COLORS = {
8090
+ motion: "#f59e0b",
8091
+ audio: "#06b6d4",
8092
+ person: "#22c55e",
8093
+ vehicle: "#3b82f6",
8094
+ animal: "#f97316",
8095
+ package: "#a855f7",
8096
+ sensor: "#8b5cf6",
8097
+ control: "#10b981",
8098
+ genericDetection: "#64748b"
8099
+ };
8100
+ var DETECTION_SUB_COLORS = {
8101
+ car: "#f59e0b",
8102
+ truck: "#d97706",
8103
+ bus: "#b45309",
8104
+ motorcycle: "#eab308",
8105
+ bicycle: "#ca8a04",
8106
+ airplane: "#60a5fa",
8107
+ boat: "#2563eb",
8108
+ train: "#1d4ed8",
8109
+ bird: "#14b8a6",
8110
+ dog: "#84cc16",
8111
+ cat: "#f97316",
8112
+ horse: "#a16207",
8113
+ sheep: "#a3a3a3",
8114
+ cow: "#78716c",
8115
+ elephant: "#6b7280",
8116
+ bear: "#7c2d12",
8117
+ zebra: "#404040",
8118
+ giraffe: "#d4a373"
8119
+ };
8120
+ function titleCase(id) {
8121
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8122
+ }
8123
+ var entries = /* @__PURE__ */ new Map();
8124
+ function macro(kind, category, color, iconId, label) {
8125
+ entries.set(kind, {
8126
+ kind,
8127
+ parentKind: null,
8128
+ level: "macro",
8129
+ category,
8130
+ color,
8131
+ iconId,
8132
+ labelKey: `eventKind.${kind}`,
8133
+ label
8134
+ });
8135
+ }
8136
+ function sub(kind, parentKind, category, color, iconId, label) {
8137
+ entries.set(kind, {
8138
+ kind,
8139
+ parentKind,
8140
+ level: "sub",
8141
+ category,
8142
+ color,
8143
+ iconId,
8144
+ labelKey: `eventKind.${kind}`,
8145
+ label
8146
+ });
8147
+ }
8148
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8149
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8150
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8151
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8152
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8153
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8154
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8155
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8156
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8157
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8158
+ if (entries.has(cocoClass)) continue;
8159
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8160
+ }
8161
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8162
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8163
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8164
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8165
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8166
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8167
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8168
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8169
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8170
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8171
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8172
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8173
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8174
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8175
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8176
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8177
+ sub("button", "control", "control", "#10b981", "button", "Button");
8178
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8179
+ for (const l of AUDIO_MACRO_LABELS) {
8180
+ const kind = `audio-${l.id}`;
8181
+ if (entries.has(kind)) continue;
8182
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8183
+ }
8184
+ /** The complete taxonomy dictionary, keyed by kind. */
8185
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8186
+ /**
7858
8187
  * Error types for the safe expression engine. Two distinct classes so callers
7859
8188
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7860
8189
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15830,17 +16159,30 @@ var EventKindCategorySchema = _enum([
15830
16159
  "audio",
15831
16160
  "detection",
15832
16161
  "sensor",
16162
+ "control",
15833
16163
  "custom",
15834
16164
  "package"
15835
16165
  ]);
16166
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16167
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15836
16168
  var EventKindDescriptorSchema = object({
15837
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16169
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15838
16170
  kind: string(),
16171
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16172
+ labelKey: string(),
16173
+ /** English fallback label (kept for clients that don't translate). */
15839
16174
  label: string(),
15840
16175
  /** Hex color for timeline/legend rendering. */
15841
16176
  color: string(),
16177
+ /** Dictionary id → lucide component on the UI side. */
16178
+ iconId: string(),
16179
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15842
16180
  icon: EventKindIconSchema,
15843
16181
  category: EventKindCategorySchema,
16182
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16183
+ parentKind: string().nullable(),
16184
+ /** Derived from `parentKind`, explicit for the client tree. */
16185
+ level: EventKindLevelSchema,
15844
16186
  /** Which cap + device contributes this kind. For built-ins the camera
15845
16187
  * itself; for sensor kinds the LINKED source device. */
15846
16188
  source: object({
@@ -15913,11 +16255,21 @@ var TrackAudioLabelSchema = object({
15913
16255
  firstAt: number(),
15914
16256
  lastAt: number()
15915
16257
  });
16258
+ /**
16259
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16260
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16261
+ * linked sensor/control state change (no positions; carries a snapshot). The
16262
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16263
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16264
+ */
16265
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15916
16266
  var TrackSchema = object({
15917
16267
  trackId: string(),
15918
16268
  deviceId: number(),
15919
16269
  className: string(),
15920
16270
  label: string().optional(),
16271
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16272
+ source: TrackSourceSchema.optional(),
15921
16273
  firstSeen: number(),
15922
16274
  lastSeen: number(),
15923
16275
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16172,6 +16524,26 @@ var TrackCascadeCountsSchema = object({
16172
16524
  /** Per-track CLIP search vectors removed (best-effort). */
16173
16525
  embeddings: number().int()
16174
16526
  });
16527
+ /** Event-store footprint for one camera. */
16528
+ var EventStoreDeviceFootprintSchema = object({
16529
+ deviceId: number(),
16530
+ /** Persisted event rows (motion + object + audio) for the camera. */
16531
+ rows: number().int(),
16532
+ /** Event-owned media bytes on disk for the camera. */
16533
+ bytes: number().int()
16534
+ });
16535
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16536
+ var EventStoreFootprintSchema = object({
16537
+ totalRows: number().int(),
16538
+ totalBytes: number().int(),
16539
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16540
+ });
16541
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16542
+ var EventPruneCountsSchema = object({
16543
+ motion: number().int(),
16544
+ object: number().int(),
16545
+ audio: number().int()
16546
+ });
16175
16547
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16176
16548
  deviceId: number(),
16177
16549
  trackId: string()
@@ -16235,6 +16607,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16235
16607
  }), {
16236
16608
  kind: "mutation",
16237
16609
  auth: "admin"
16610
+ }), method(object({}), EventStoreFootprintSchema, {
16611
+ kind: "query",
16612
+ auth: "admin"
16613
+ }), method(object({
16614
+ olderThanMs: number(),
16615
+ reason: OpsLogReasonSchema.optional()
16616
+ }), EventPruneCountsSchema, {
16617
+ kind: "mutation",
16618
+ auth: "admin"
16619
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16620
+ kind: "mutation",
16621
+ auth: "admin"
16622
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16623
+ kind: "query",
16624
+ auth: "admin"
16238
16625
  }), method(object({
16239
16626
  eventId: string(),
16240
16627
  kind: MediaFileKindEnum.optional()
@@ -16259,6 +16646,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16259
16646
  eventId: string(),
16260
16647
  timestamp: number()
16261
16648
  });
16649
+ /**
16650
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16651
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16652
+ * caps into per-camera event-kind descriptors.
16653
+ *
16654
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16655
+ * is NOT duplicated here — every entry is derived from the single
16656
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16657
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16658
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16659
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16660
+ * eventful cap is missing.
16661
+ */
16662
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16663
+ var LEGACY_ICON = {
16664
+ motion: "motion",
16665
+ audio: "audio",
16666
+ person: "person",
16667
+ vehicle: "vehicle",
16668
+ animal: "animal",
16669
+ package: "package",
16670
+ door: "door",
16671
+ pir: "pir",
16672
+ smoke: "smoke",
16673
+ water: "water",
16674
+ button: "button",
16675
+ generic: "generic",
16676
+ gas: "smoke",
16677
+ vibration: "generic",
16678
+ tamper: "generic",
16679
+ presence: "person",
16680
+ lock: "generic",
16681
+ siren: "generic",
16682
+ switch: "generic",
16683
+ doorbell: "button"
16684
+ };
16685
+ function legacyIcon(iconId) {
16686
+ return LEGACY_ICON[iconId] ?? "generic";
16687
+ }
16688
+ /**
16689
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16690
+ * The anti-drift guard cross-checks this against the eventful caps declared
16691
+ * in `packages/types/src/capabilities/*.cap.ts`.
16692
+ */
16693
+ var CAP_TO_KIND = {
16694
+ contact: "contact",
16695
+ motion: "motion-sensor",
16696
+ smoke: "smoke",
16697
+ flood: "flood",
16698
+ gas: "gas",
16699
+ "carbon-monoxide": "carbon-monoxide",
16700
+ vibration: "vibration",
16701
+ tamper: "tamper",
16702
+ presence: "presence",
16703
+ "enum-sensor": "enum-sensor",
16704
+ "event-emitter": "device-event",
16705
+ "lock-control": "lock",
16706
+ switch: "switch",
16707
+ button: "button",
16708
+ doorbell: "doorbell"
16709
+ };
16710
+ function buildDescriptor(capName, kind) {
16711
+ const t = EVENT_TAXONOMY[kind];
16712
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16713
+ return {
16714
+ ...t,
16715
+ icon: legacyIcon(t.iconId)
16716
+ };
16717
+ }
16718
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16262
16719
  var CameraPipelineConfigSchema = object({
16263
16720
  engine: PipelineEngineChoiceSchema.optional(),
16264
16721
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19468,13 +19925,29 @@ method(object({
19468
19925
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19469
19926
  kind: "mutation",
19470
19927
  auth: "admin"
19471
- }), method(object({ deviceId: number() }), object({
19928
+ }), method(object({
19929
+ deviceId: number(),
19930
+ reason: OpsLogReasonSchema.optional()
19931
+ }), object({
19472
19932
  floorMs: number().nullable(),
19473
19933
  deletedBuckets: number().int(),
19474
19934
  reclaimedBytes: number().int()
19475
19935
  }), {
19476
19936
  kind: "mutation",
19477
19937
  auth: "admin"
19938
+ }), method(object({
19939
+ deviceId: number(),
19940
+ fromMs: number().optional(),
19941
+ toMs: number().optional()
19942
+ }), object({
19943
+ deletedBuckets: number().int(),
19944
+ reclaimedBytes: number().int()
19945
+ }), {
19946
+ kind: "mutation",
19947
+ auth: "admin"
19948
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19949
+ kind: "query",
19950
+ auth: "admin"
19478
19951
  });
19479
19952
  /**
19480
19953
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22596,6 +23069,12 @@ Object.freeze({
22596
23069
  addonId: null,
22597
23070
  access: "delete"
22598
23071
  },
23072
+ "pipelineAnalytics.deleteDeviceEvents": {
23073
+ capName: "pipeline-analytics",
23074
+ capScope: "device",
23075
+ addonId: null,
23076
+ access: "delete"
23077
+ },
22599
23078
  "pipelineAnalytics.deleteTracks": {
22600
23079
  capName: "pipeline-analytics",
22601
23080
  capScope: "device",
@@ -22626,6 +23105,12 @@ Object.freeze({
22626
23105
  addonId: null,
22627
23106
  access: "view"
22628
23107
  },
23108
+ "pipelineAnalytics.getEventStoreFootprint": {
23109
+ capName: "pipeline-analytics",
23110
+ capScope: "device",
23111
+ addonId: null,
23112
+ access: "view"
23113
+ },
22629
23114
  "pipelineAnalytics.getKeyEvents": {
22630
23115
  capName: "pipeline-analytics",
22631
23116
  capScope: "device",
@@ -22668,6 +23153,12 @@ Object.freeze({
22668
23153
  addonId: null,
22669
23154
  access: "view"
22670
23155
  },
23156
+ "pipelineAnalytics.listOpsLog": {
23157
+ capName: "pipeline-analytics",
23158
+ capScope: "device",
23159
+ addonId: null,
23160
+ access: "view"
23161
+ },
22671
23162
  "pipelineAnalytics.listRecentTracks": {
22672
23163
  capName: "pipeline-analytics",
22673
23164
  capScope: "device",
@@ -22680,6 +23171,12 @@ Object.freeze({
22680
23171
  addonId: null,
22681
23172
  access: "view"
22682
23173
  },
23174
+ "pipelineAnalytics.pruneEvents": {
23175
+ capName: "pipeline-analytics",
23176
+ capScope: "device",
23177
+ addonId: null,
23178
+ access: "create"
23179
+ },
22683
23180
  "pipelineAnalytics.pruneEventsBefore": {
22684
23181
  capName: "pipeline-analytics",
22685
23182
  capScope: "device",
@@ -23442,6 +23939,12 @@ Object.freeze({
23442
23939
  addonId: null,
23443
23940
  access: "create"
23444
23941
  },
23942
+ "recording.deleteFootprint": {
23943
+ capName: "recording",
23944
+ capScope: "system",
23945
+ addonId: null,
23946
+ access: "delete"
23947
+ },
23445
23948
  "recording.getAvailability": {
23446
23949
  capName: "recording",
23447
23950
  capScope: "system",
@@ -23472,6 +23975,12 @@ Object.freeze({
23472
23975
  addonId: null,
23473
23976
  access: "view"
23474
23977
  },
23978
+ "recording.listOpsLog": {
23979
+ capName: "recording",
23980
+ capScope: "system",
23981
+ addonId: null,
23982
+ access: "view"
23983
+ },
23475
23984
  "recording.locateSegment": {
23476
23985
  capName: "recording",
23477
23986
  capScope: "system",
@@ -24845,7 +25354,7 @@ var RuleEngine = class {
24845
25354
  if (!className || !conditions.classNames.includes(className)) return false;
24846
25355
  }
24847
25356
  if (conditions.zoneIds?.length) {
24848
- const zones = data["zones"];
25357
+ const zones = data["zones"] ?? data["zoneIds"];
24849
25358
  const zoneId = data["zoneId"];
24850
25359
  if (!(zones ?? (zoneId ? [zoneId] : [])).some((z) => conditions.zoneIds.includes(z))) return false;
24851
25360
  }
@@ -24932,7 +25441,9 @@ var EVENT_CATEGORIES = [
24932
25441
  EventCategory.DetectionRaw,
24933
25442
  EventCategory.DetectionResult,
24934
25443
  EventCategory.PipelineAnalyticsDetectionEvent,
24935
- EventCategory.EventEmitted
25444
+ EventCategory.EventEmitted,
25445
+ EventCategory.PipelineAnalyticsPackageDelivered,
25446
+ EventCategory.PipelineAnalyticsPackagePickedUp
24936
25447
  ];
24937
25448
  var DEFAULT_NOTIFIER_CONFIG = {
24938
25449
  defaultCooldownSeconds: 60,
@@ -25187,7 +25698,7 @@ var AdvancedNotifierAddon = class extends BaseAddon {
25187
25698
  deviceName: data["deviceName"] ?? String(event.source.id),
25188
25699
  className: data["className"] ?? "",
25189
25700
  confidence: String(data["confidence"] ?? ""),
25190
- zoneId: data["zoneId"] ?? "",
25701
+ zoneId: data["zoneId"] ?? (Array.isArray(data["zoneIds"]) ? String(data["zoneIds"][0] ?? "") : ""),
25191
25702
  zoneName: data["zoneName"] ?? "",
25192
25703
  category: event.category,
25193
25704
  timestamp: event.timestamp.toISOString()
package/dist/addon.mjs CHANGED
@@ -7328,6 +7328,62 @@ var RecordingConfigSchema = object({
7328
7328
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7329
7329
  });
7330
7330
  /**
7331
+ * Ops-log — the durable, append-only operations audit shared by the
7332
+ * recordings and events management surfaces.
7333
+ *
7334
+ * ONE row shape is reused for both domains so a single "Activity" view can
7335
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7336
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7337
+ * management operation, WHY it ran (reason), and its measurable effect
7338
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7339
+ * never fail the operation it records.
7340
+ */
7341
+ /** Which management domain the operation belongs to. */
7342
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7343
+ /** The kind of management operation performed. */
7344
+ var OpsLogOpSchema = _enum([
7345
+ "prune",
7346
+ "manual-delete",
7347
+ "rescan",
7348
+ "retention-run"
7349
+ ]);
7350
+ /** Why the operation ran. */
7351
+ var OpsLogReasonSchema = _enum([
7352
+ "retention",
7353
+ "quota",
7354
+ "manual",
7355
+ "operator"
7356
+ ]);
7357
+ /** One audit row, shared verbatim by both domains. */
7358
+ var OpsLogEntrySchema = object({
7359
+ /** Unique row id. */
7360
+ id: string(),
7361
+ /** Epoch ms the operation completed. */
7362
+ at: number(),
7363
+ domain: OpsLogDomainSchema,
7364
+ op: OpsLogOpSchema,
7365
+ reason: OpsLogReasonSchema,
7366
+ /** The camera the op targeted; null for a cluster/global op. */
7367
+ deviceId: number().nullable(),
7368
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7369
+ nodeId: string(),
7370
+ /** Buckets / rows deleted (op-specific unit). */
7371
+ itemsAffected: number(),
7372
+ /** Bytes reclaimed by the op (0 when not measurable). */
7373
+ bytesReclaimed: number(),
7374
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7375
+ detail: string().nullable(),
7376
+ /** Who/what triggered the op. */
7377
+ actor: string()
7378
+ });
7379
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7380
+ var OpsLogQueryInputSchema = object({
7381
+ /** Restrict to a single camera; omit for every row. */
7382
+ deviceId: number().optional(),
7383
+ /** Max rows returned, newest-first. */
7384
+ limit: number().int().min(1).max(1e3).optional()
7385
+ });
7386
+ /**
7331
7387
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7332
7388
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7333
7389
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7585,6 +7641,160 @@ function cosineSimilarity(a, b) {
7585
7641
  const denom = Math.sqrt(normA) * Math.sqrt(normB);
7586
7642
  return denom === 0 ? 0 : dotProduct / denom;
7587
7643
  }
7644
+ var COCO_TO_MACRO = {
7645
+ mapping: {
7646
+ person: "person",
7647
+ bicycle: "vehicle",
7648
+ car: "vehicle",
7649
+ motorcycle: "vehicle",
7650
+ airplane: "vehicle",
7651
+ bus: "vehicle",
7652
+ train: "vehicle",
7653
+ truck: "vehicle",
7654
+ boat: "vehicle",
7655
+ bird: "animal",
7656
+ cat: "animal",
7657
+ dog: "animal",
7658
+ horse: "animal",
7659
+ sheep: "animal",
7660
+ cow: "animal",
7661
+ elephant: "animal",
7662
+ bear: "animal",
7663
+ zebra: "animal",
7664
+ giraffe: "animal",
7665
+ suitcase: "package",
7666
+ backpack: "package",
7667
+ handbag: "package"
7668
+ },
7669
+ preserveOriginal: false
7670
+ };
7671
+ var AUDIO_MACRO_LABELS = [
7672
+ {
7673
+ id: "speech",
7674
+ name: "Speech",
7675
+ icon: "🗣️"
7676
+ },
7677
+ {
7678
+ id: "scream",
7679
+ name: "Scream / Shout",
7680
+ icon: "😱"
7681
+ },
7682
+ {
7683
+ id: "crying",
7684
+ name: "Crying / Baby",
7685
+ icon: "😢"
7686
+ },
7687
+ {
7688
+ id: "laughter",
7689
+ name: "Laughter",
7690
+ icon: "😂"
7691
+ },
7692
+ {
7693
+ id: "music",
7694
+ name: "Music",
7695
+ icon: "🎵"
7696
+ },
7697
+ {
7698
+ id: "dog",
7699
+ name: "Dog",
7700
+ icon: "🐕"
7701
+ },
7702
+ {
7703
+ id: "cat",
7704
+ name: "Cat",
7705
+ icon: "🐈"
7706
+ },
7707
+ {
7708
+ id: "bird",
7709
+ name: "Bird",
7710
+ icon: "🐦"
7711
+ },
7712
+ {
7713
+ id: "animal",
7714
+ name: "Animal (other)",
7715
+ icon: "🐾"
7716
+ },
7717
+ {
7718
+ id: "alarm",
7719
+ name: "Alarm / Siren",
7720
+ icon: "🚨"
7721
+ },
7722
+ {
7723
+ id: "doorbell",
7724
+ name: "Doorbell / Knock",
7725
+ icon: "🔔"
7726
+ },
7727
+ {
7728
+ id: "glass_breaking",
7729
+ name: "Glass Breaking",
7730
+ icon: "💥"
7731
+ },
7732
+ {
7733
+ id: "gunshot",
7734
+ name: "Gunshot / Explosion",
7735
+ icon: "💣"
7736
+ },
7737
+ {
7738
+ id: "vehicle",
7739
+ name: "Vehicle",
7740
+ icon: "🚗"
7741
+ },
7742
+ {
7743
+ id: "siren",
7744
+ name: "Emergency Siren",
7745
+ icon: "🚑"
7746
+ },
7747
+ {
7748
+ id: "fire",
7749
+ name: "Fire / Smoke",
7750
+ icon: "🔥"
7751
+ },
7752
+ {
7753
+ id: "water",
7754
+ name: "Water",
7755
+ icon: "💧"
7756
+ },
7757
+ {
7758
+ id: "wind",
7759
+ name: "Wind / Weather",
7760
+ icon: "🌬️"
7761
+ },
7762
+ {
7763
+ id: "door",
7764
+ name: "Door",
7765
+ icon: "🚪"
7766
+ },
7767
+ {
7768
+ id: "footsteps",
7769
+ name: "Footsteps",
7770
+ icon: "👣"
7771
+ },
7772
+ {
7773
+ id: "crowd",
7774
+ name: "Crowd / Chatter",
7775
+ icon: "👥"
7776
+ },
7777
+ {
7778
+ id: "telephone",
7779
+ name: "Telephone",
7780
+ icon: "📞"
7781
+ },
7782
+ {
7783
+ id: "engine",
7784
+ name: "Engine / Motor",
7785
+ icon: "⚙️"
7786
+ },
7787
+ {
7788
+ id: "tools",
7789
+ name: "Tools / Construction",
7790
+ icon: "🔨"
7791
+ },
7792
+ {
7793
+ id: "silence",
7794
+ name: "Silence",
7795
+ icon: "🤫"
7796
+ }
7797
+ ];
7588
7798
  var YAMNET_TO_MACRO = {
7589
7799
  mapping: {
7590
7800
  Speech: "speech",
@@ -7851,6 +8061,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7851
8061
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7852
8062
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7853
8063
  /**
8064
+ * Unified event-kind taxonomy — THE single source of truth for
8065
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8066
+ * icon }`.
8067
+ *
8068
+ * This dictionary folds together what used to be scattered across four
8069
+ * copies:
8070
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8071
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8072
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8073
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8074
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8075
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8076
+ *
8077
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8078
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8079
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8080
+ * color or an icon: they read this dictionary (server descriptors carry the
8081
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8082
+ *
8083
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8084
+ */
8085
+ var TAXONOMY_COLORS = {
8086
+ motion: "#f59e0b",
8087
+ audio: "#06b6d4",
8088
+ person: "#22c55e",
8089
+ vehicle: "#3b82f6",
8090
+ animal: "#f97316",
8091
+ package: "#a855f7",
8092
+ sensor: "#8b5cf6",
8093
+ control: "#10b981",
8094
+ genericDetection: "#64748b"
8095
+ };
8096
+ var DETECTION_SUB_COLORS = {
8097
+ car: "#f59e0b",
8098
+ truck: "#d97706",
8099
+ bus: "#b45309",
8100
+ motorcycle: "#eab308",
8101
+ bicycle: "#ca8a04",
8102
+ airplane: "#60a5fa",
8103
+ boat: "#2563eb",
8104
+ train: "#1d4ed8",
8105
+ bird: "#14b8a6",
8106
+ dog: "#84cc16",
8107
+ cat: "#f97316",
8108
+ horse: "#a16207",
8109
+ sheep: "#a3a3a3",
8110
+ cow: "#78716c",
8111
+ elephant: "#6b7280",
8112
+ bear: "#7c2d12",
8113
+ zebra: "#404040",
8114
+ giraffe: "#d4a373"
8115
+ };
8116
+ function titleCase(id) {
8117
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8118
+ }
8119
+ var entries = /* @__PURE__ */ new Map();
8120
+ function macro(kind, category, color, iconId, label) {
8121
+ entries.set(kind, {
8122
+ kind,
8123
+ parentKind: null,
8124
+ level: "macro",
8125
+ category,
8126
+ color,
8127
+ iconId,
8128
+ labelKey: `eventKind.${kind}`,
8129
+ label
8130
+ });
8131
+ }
8132
+ function sub(kind, parentKind, category, color, iconId, label) {
8133
+ entries.set(kind, {
8134
+ kind,
8135
+ parentKind,
8136
+ level: "sub",
8137
+ category,
8138
+ color,
8139
+ iconId,
8140
+ labelKey: `eventKind.${kind}`,
8141
+ label
8142
+ });
8143
+ }
8144
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8145
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8146
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8147
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8148
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8149
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8150
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8151
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8152
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8153
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8154
+ if (entries.has(cocoClass)) continue;
8155
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8156
+ }
8157
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8158
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8159
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8160
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8161
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8162
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8163
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8164
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8165
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8166
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8167
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8168
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8169
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8170
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8171
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8172
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8173
+ sub("button", "control", "control", "#10b981", "button", "Button");
8174
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8175
+ for (const l of AUDIO_MACRO_LABELS) {
8176
+ const kind = `audio-${l.id}`;
8177
+ if (entries.has(kind)) continue;
8178
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8179
+ }
8180
+ /** The complete taxonomy dictionary, keyed by kind. */
8181
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8182
+ /**
7854
8183
  * Error types for the safe expression engine. Two distinct classes so callers
7855
8184
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7856
8185
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15826,17 +16155,30 @@ var EventKindCategorySchema = _enum([
15826
16155
  "audio",
15827
16156
  "detection",
15828
16157
  "sensor",
16158
+ "control",
15829
16159
  "custom",
15830
16160
  "package"
15831
16161
  ]);
16162
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16163
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15832
16164
  var EventKindDescriptorSchema = object({
15833
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16165
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15834
16166
  kind: string(),
16167
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16168
+ labelKey: string(),
16169
+ /** English fallback label (kept for clients that don't translate). */
15835
16170
  label: string(),
15836
16171
  /** Hex color for timeline/legend rendering. */
15837
16172
  color: string(),
16173
+ /** Dictionary id → lucide component on the UI side. */
16174
+ iconId: string(),
16175
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15838
16176
  icon: EventKindIconSchema,
15839
16177
  category: EventKindCategorySchema,
16178
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16179
+ parentKind: string().nullable(),
16180
+ /** Derived from `parentKind`, explicit for the client tree. */
16181
+ level: EventKindLevelSchema,
15840
16182
  /** Which cap + device contributes this kind. For built-ins the camera
15841
16183
  * itself; for sensor kinds the LINKED source device. */
15842
16184
  source: object({
@@ -15909,11 +16251,21 @@ var TrackAudioLabelSchema = object({
15909
16251
  firstAt: number(),
15910
16252
  lastAt: number()
15911
16253
  });
16254
+ /**
16255
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16256
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16257
+ * linked sensor/control state change (no positions; carries a snapshot). The
16258
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16259
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16260
+ */
16261
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15912
16262
  var TrackSchema = object({
15913
16263
  trackId: string(),
15914
16264
  deviceId: number(),
15915
16265
  className: string(),
15916
16266
  label: string().optional(),
16267
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16268
+ source: TrackSourceSchema.optional(),
15917
16269
  firstSeen: number(),
15918
16270
  lastSeen: number(),
15919
16271
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16168,6 +16520,26 @@ var TrackCascadeCountsSchema = object({
16168
16520
  /** Per-track CLIP search vectors removed (best-effort). */
16169
16521
  embeddings: number().int()
16170
16522
  });
16523
+ /** Event-store footprint for one camera. */
16524
+ var EventStoreDeviceFootprintSchema = object({
16525
+ deviceId: number(),
16526
+ /** Persisted event rows (motion + object + audio) for the camera. */
16527
+ rows: number().int(),
16528
+ /** Event-owned media bytes on disk for the camera. */
16529
+ bytes: number().int()
16530
+ });
16531
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16532
+ var EventStoreFootprintSchema = object({
16533
+ totalRows: number().int(),
16534
+ totalBytes: number().int(),
16535
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16536
+ });
16537
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16538
+ var EventPruneCountsSchema = object({
16539
+ motion: number().int(),
16540
+ object: number().int(),
16541
+ audio: number().int()
16542
+ });
16171
16543
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16172
16544
  deviceId: number(),
16173
16545
  trackId: string()
@@ -16231,6 +16603,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16231
16603
  }), {
16232
16604
  kind: "mutation",
16233
16605
  auth: "admin"
16606
+ }), method(object({}), EventStoreFootprintSchema, {
16607
+ kind: "query",
16608
+ auth: "admin"
16609
+ }), method(object({
16610
+ olderThanMs: number(),
16611
+ reason: OpsLogReasonSchema.optional()
16612
+ }), EventPruneCountsSchema, {
16613
+ kind: "mutation",
16614
+ auth: "admin"
16615
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16616
+ kind: "mutation",
16617
+ auth: "admin"
16618
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16619
+ kind: "query",
16620
+ auth: "admin"
16234
16621
  }), method(object({
16235
16622
  eventId: string(),
16236
16623
  kind: MediaFileKindEnum.optional()
@@ -16255,6 +16642,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16255
16642
  eventId: string(),
16256
16643
  timestamp: number()
16257
16644
  });
16645
+ /**
16646
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16647
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16648
+ * caps into per-camera event-kind descriptors.
16649
+ *
16650
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16651
+ * is NOT duplicated here — every entry is derived from the single
16652
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16653
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16654
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16655
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16656
+ * eventful cap is missing.
16657
+ */
16658
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16659
+ var LEGACY_ICON = {
16660
+ motion: "motion",
16661
+ audio: "audio",
16662
+ person: "person",
16663
+ vehicle: "vehicle",
16664
+ animal: "animal",
16665
+ package: "package",
16666
+ door: "door",
16667
+ pir: "pir",
16668
+ smoke: "smoke",
16669
+ water: "water",
16670
+ button: "button",
16671
+ generic: "generic",
16672
+ gas: "smoke",
16673
+ vibration: "generic",
16674
+ tamper: "generic",
16675
+ presence: "person",
16676
+ lock: "generic",
16677
+ siren: "generic",
16678
+ switch: "generic",
16679
+ doorbell: "button"
16680
+ };
16681
+ function legacyIcon(iconId) {
16682
+ return LEGACY_ICON[iconId] ?? "generic";
16683
+ }
16684
+ /**
16685
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16686
+ * The anti-drift guard cross-checks this against the eventful caps declared
16687
+ * in `packages/types/src/capabilities/*.cap.ts`.
16688
+ */
16689
+ var CAP_TO_KIND = {
16690
+ contact: "contact",
16691
+ motion: "motion-sensor",
16692
+ smoke: "smoke",
16693
+ flood: "flood",
16694
+ gas: "gas",
16695
+ "carbon-monoxide": "carbon-monoxide",
16696
+ vibration: "vibration",
16697
+ tamper: "tamper",
16698
+ presence: "presence",
16699
+ "enum-sensor": "enum-sensor",
16700
+ "event-emitter": "device-event",
16701
+ "lock-control": "lock",
16702
+ switch: "switch",
16703
+ button: "button",
16704
+ doorbell: "doorbell"
16705
+ };
16706
+ function buildDescriptor(capName, kind) {
16707
+ const t = EVENT_TAXONOMY[kind];
16708
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16709
+ return {
16710
+ ...t,
16711
+ icon: legacyIcon(t.iconId)
16712
+ };
16713
+ }
16714
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16258
16715
  var CameraPipelineConfigSchema = object({
16259
16716
  engine: PipelineEngineChoiceSchema.optional(),
16260
16717
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19464,13 +19921,29 @@ method(object({
19464
19921
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19465
19922
  kind: "mutation",
19466
19923
  auth: "admin"
19467
- }), method(object({ deviceId: number() }), object({
19924
+ }), method(object({
19925
+ deviceId: number(),
19926
+ reason: OpsLogReasonSchema.optional()
19927
+ }), object({
19468
19928
  floorMs: number().nullable(),
19469
19929
  deletedBuckets: number().int(),
19470
19930
  reclaimedBytes: number().int()
19471
19931
  }), {
19472
19932
  kind: "mutation",
19473
19933
  auth: "admin"
19934
+ }), method(object({
19935
+ deviceId: number(),
19936
+ fromMs: number().optional(),
19937
+ toMs: number().optional()
19938
+ }), object({
19939
+ deletedBuckets: number().int(),
19940
+ reclaimedBytes: number().int()
19941
+ }), {
19942
+ kind: "mutation",
19943
+ auth: "admin"
19944
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19945
+ kind: "query",
19946
+ auth: "admin"
19474
19947
  });
19475
19948
  /**
19476
19949
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22592,6 +23065,12 @@ Object.freeze({
22592
23065
  addonId: null,
22593
23066
  access: "delete"
22594
23067
  },
23068
+ "pipelineAnalytics.deleteDeviceEvents": {
23069
+ capName: "pipeline-analytics",
23070
+ capScope: "device",
23071
+ addonId: null,
23072
+ access: "delete"
23073
+ },
22595
23074
  "pipelineAnalytics.deleteTracks": {
22596
23075
  capName: "pipeline-analytics",
22597
23076
  capScope: "device",
@@ -22622,6 +23101,12 @@ Object.freeze({
22622
23101
  addonId: null,
22623
23102
  access: "view"
22624
23103
  },
23104
+ "pipelineAnalytics.getEventStoreFootprint": {
23105
+ capName: "pipeline-analytics",
23106
+ capScope: "device",
23107
+ addonId: null,
23108
+ access: "view"
23109
+ },
22625
23110
  "pipelineAnalytics.getKeyEvents": {
22626
23111
  capName: "pipeline-analytics",
22627
23112
  capScope: "device",
@@ -22664,6 +23149,12 @@ Object.freeze({
22664
23149
  addonId: null,
22665
23150
  access: "view"
22666
23151
  },
23152
+ "pipelineAnalytics.listOpsLog": {
23153
+ capName: "pipeline-analytics",
23154
+ capScope: "device",
23155
+ addonId: null,
23156
+ access: "view"
23157
+ },
22667
23158
  "pipelineAnalytics.listRecentTracks": {
22668
23159
  capName: "pipeline-analytics",
22669
23160
  capScope: "device",
@@ -22676,6 +23167,12 @@ Object.freeze({
22676
23167
  addonId: null,
22677
23168
  access: "view"
22678
23169
  },
23170
+ "pipelineAnalytics.pruneEvents": {
23171
+ capName: "pipeline-analytics",
23172
+ capScope: "device",
23173
+ addonId: null,
23174
+ access: "create"
23175
+ },
22679
23176
  "pipelineAnalytics.pruneEventsBefore": {
22680
23177
  capName: "pipeline-analytics",
22681
23178
  capScope: "device",
@@ -23438,6 +23935,12 @@ Object.freeze({
23438
23935
  addonId: null,
23439
23936
  access: "create"
23440
23937
  },
23938
+ "recording.deleteFootprint": {
23939
+ capName: "recording",
23940
+ capScope: "system",
23941
+ addonId: null,
23942
+ access: "delete"
23943
+ },
23441
23944
  "recording.getAvailability": {
23442
23945
  capName: "recording",
23443
23946
  capScope: "system",
@@ -23468,6 +23971,12 @@ Object.freeze({
23468
23971
  addonId: null,
23469
23972
  access: "view"
23470
23973
  },
23974
+ "recording.listOpsLog": {
23975
+ capName: "recording",
23976
+ capScope: "system",
23977
+ addonId: null,
23978
+ access: "view"
23979
+ },
23471
23980
  "recording.locateSegment": {
23472
23981
  capName: "recording",
23473
23982
  capScope: "system",
@@ -24841,7 +25350,7 @@ var RuleEngine = class {
24841
25350
  if (!className || !conditions.classNames.includes(className)) return false;
24842
25351
  }
24843
25352
  if (conditions.zoneIds?.length) {
24844
- const zones = data["zones"];
25353
+ const zones = data["zones"] ?? data["zoneIds"];
24845
25354
  const zoneId = data["zoneId"];
24846
25355
  if (!(zones ?? (zoneId ? [zoneId] : [])).some((z) => conditions.zoneIds.includes(z))) return false;
24847
25356
  }
@@ -24928,7 +25437,9 @@ var EVENT_CATEGORIES = [
24928
25437
  EventCategory.DetectionRaw,
24929
25438
  EventCategory.DetectionResult,
24930
25439
  EventCategory.PipelineAnalyticsDetectionEvent,
24931
- EventCategory.EventEmitted
25440
+ EventCategory.EventEmitted,
25441
+ EventCategory.PipelineAnalyticsPackageDelivered,
25442
+ EventCategory.PipelineAnalyticsPackagePickedUp
24932
25443
  ];
24933
25444
  var DEFAULT_NOTIFIER_CONFIG = {
24934
25445
  defaultCooldownSeconds: 60,
@@ -25183,7 +25694,7 @@ var AdvancedNotifierAddon = class extends BaseAddon {
25183
25694
  deviceName: data["deviceName"] ?? String(event.source.id),
25184
25695
  className: data["className"] ?? "",
25185
25696
  confidence: String(data["confidence"] ?? ""),
25186
- zoneId: data["zoneId"] ?? "",
25697
+ zoneId: data["zoneId"] ?? (Array.isArray(data["zoneIds"]) ? String(data["zoneIds"][0] ?? "") : ""),
25187
25698
  zoneName: data["zoneName"] ?? "",
25188
25699
  category: event.category,
25189
25700
  timestamp: event.timestamp.toISOString()
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-advanced-notifier",
3
- "version": "1.1.28",
3
+ "version": "1.1.30",
4
4
  "description": "Rules-based notification engine for CamStack",
5
5
  "license": "MIT",
6
6
  "main": "./dist/index.js",