@camstack/addon-smtp-nodemailer 1.1.25 → 1.1.27

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7365,6 +7365,62 @@ var RecordingConfigSchema = object({
7365
7365
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7366
7366
  });
7367
7367
  /**
7368
+ * Ops-log — the durable, append-only operations audit shared by the
7369
+ * recordings and events management surfaces.
7370
+ *
7371
+ * ONE row shape is reused for both domains so a single "Activity" view can
7372
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7373
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7374
+ * management operation, WHY it ran (reason), and its measurable effect
7375
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7376
+ * never fail the operation it records.
7377
+ */
7378
+ /** Which management domain the operation belongs to. */
7379
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7380
+ /** The kind of management operation performed. */
7381
+ var OpsLogOpSchema = _enum([
7382
+ "prune",
7383
+ "manual-delete",
7384
+ "rescan",
7385
+ "retention-run"
7386
+ ]);
7387
+ /** Why the operation ran. */
7388
+ var OpsLogReasonSchema = _enum([
7389
+ "retention",
7390
+ "quota",
7391
+ "manual",
7392
+ "operator"
7393
+ ]);
7394
+ /** One audit row, shared verbatim by both domains. */
7395
+ var OpsLogEntrySchema = object({
7396
+ /** Unique row id. */
7397
+ id: string(),
7398
+ /** Epoch ms the operation completed. */
7399
+ at: number(),
7400
+ domain: OpsLogDomainSchema,
7401
+ op: OpsLogOpSchema,
7402
+ reason: OpsLogReasonSchema,
7403
+ /** The camera the op targeted; null for a cluster/global op. */
7404
+ deviceId: number().nullable(),
7405
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7406
+ nodeId: string(),
7407
+ /** Buckets / rows deleted (op-specific unit). */
7408
+ itemsAffected: number(),
7409
+ /** Bytes reclaimed by the op (0 when not measurable). */
7410
+ bytesReclaimed: number(),
7411
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7412
+ detail: string().nullable(),
7413
+ /** Who/what triggered the op. */
7414
+ actor: string()
7415
+ });
7416
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7417
+ var OpsLogQueryInputSchema = object({
7418
+ /** Restrict to a single camera; omit for every row. */
7419
+ deviceId: number().optional(),
7420
+ /** Max rows returned, newest-first. */
7421
+ limit: number().int().min(1).max(1e3).optional()
7422
+ });
7423
+ /**
7368
7424
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7369
7425
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7370
7426
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7608,6 +7664,160 @@ var EncodeProfileSchema = object({
7608
7664
  */
7609
7665
  outputArgs: array(string()).optional()
7610
7666
  });
7667
+ var COCO_TO_MACRO = {
7668
+ mapping: {
7669
+ person: "person",
7670
+ bicycle: "vehicle",
7671
+ car: "vehicle",
7672
+ motorcycle: "vehicle",
7673
+ airplane: "vehicle",
7674
+ bus: "vehicle",
7675
+ train: "vehicle",
7676
+ truck: "vehicle",
7677
+ boat: "vehicle",
7678
+ bird: "animal",
7679
+ cat: "animal",
7680
+ dog: "animal",
7681
+ horse: "animal",
7682
+ sheep: "animal",
7683
+ cow: "animal",
7684
+ elephant: "animal",
7685
+ bear: "animal",
7686
+ zebra: "animal",
7687
+ giraffe: "animal",
7688
+ suitcase: "package",
7689
+ backpack: "package",
7690
+ handbag: "package"
7691
+ },
7692
+ preserveOriginal: false
7693
+ };
7694
+ var AUDIO_MACRO_LABELS = [
7695
+ {
7696
+ id: "speech",
7697
+ name: "Speech",
7698
+ icon: "🗣️"
7699
+ },
7700
+ {
7701
+ id: "scream",
7702
+ name: "Scream / Shout",
7703
+ icon: "😱"
7704
+ },
7705
+ {
7706
+ id: "crying",
7707
+ name: "Crying / Baby",
7708
+ icon: "😢"
7709
+ },
7710
+ {
7711
+ id: "laughter",
7712
+ name: "Laughter",
7713
+ icon: "😂"
7714
+ },
7715
+ {
7716
+ id: "music",
7717
+ name: "Music",
7718
+ icon: "🎵"
7719
+ },
7720
+ {
7721
+ id: "dog",
7722
+ name: "Dog",
7723
+ icon: "🐕"
7724
+ },
7725
+ {
7726
+ id: "cat",
7727
+ name: "Cat",
7728
+ icon: "🐈"
7729
+ },
7730
+ {
7731
+ id: "bird",
7732
+ name: "Bird",
7733
+ icon: "🐦"
7734
+ },
7735
+ {
7736
+ id: "animal",
7737
+ name: "Animal (other)",
7738
+ icon: "🐾"
7739
+ },
7740
+ {
7741
+ id: "alarm",
7742
+ name: "Alarm / Siren",
7743
+ icon: "🚨"
7744
+ },
7745
+ {
7746
+ id: "doorbell",
7747
+ name: "Doorbell / Knock",
7748
+ icon: "🔔"
7749
+ },
7750
+ {
7751
+ id: "glass_breaking",
7752
+ name: "Glass Breaking",
7753
+ icon: "💥"
7754
+ },
7755
+ {
7756
+ id: "gunshot",
7757
+ name: "Gunshot / Explosion",
7758
+ icon: "💣"
7759
+ },
7760
+ {
7761
+ id: "vehicle",
7762
+ name: "Vehicle",
7763
+ icon: "🚗"
7764
+ },
7765
+ {
7766
+ id: "siren",
7767
+ name: "Emergency Siren",
7768
+ icon: "🚑"
7769
+ },
7770
+ {
7771
+ id: "fire",
7772
+ name: "Fire / Smoke",
7773
+ icon: "🔥"
7774
+ },
7775
+ {
7776
+ id: "water",
7777
+ name: "Water",
7778
+ icon: "💧"
7779
+ },
7780
+ {
7781
+ id: "wind",
7782
+ name: "Wind / Weather",
7783
+ icon: "🌬️"
7784
+ },
7785
+ {
7786
+ id: "door",
7787
+ name: "Door",
7788
+ icon: "🚪"
7789
+ },
7790
+ {
7791
+ id: "footsteps",
7792
+ name: "Footsteps",
7793
+ icon: "👣"
7794
+ },
7795
+ {
7796
+ id: "crowd",
7797
+ name: "Crowd / Chatter",
7798
+ icon: "👥"
7799
+ },
7800
+ {
7801
+ id: "telephone",
7802
+ name: "Telephone",
7803
+ icon: "📞"
7804
+ },
7805
+ {
7806
+ id: "engine",
7807
+ name: "Engine / Motor",
7808
+ icon: "⚙️"
7809
+ },
7810
+ {
7811
+ id: "tools",
7812
+ name: "Tools / Construction",
7813
+ icon: "🔨"
7814
+ },
7815
+ {
7816
+ id: "silence",
7817
+ name: "Silence",
7818
+ icon: "🤫"
7819
+ }
7820
+ ];
7611
7821
  var YAMNET_TO_MACRO = {
7612
7822
  mapping: {
7613
7823
  Speech: "speech",
@@ -7874,6 +8084,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7874
8084
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7875
8085
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7876
8086
  /**
8087
+ * Unified event-kind taxonomy — THE single source of truth for
8088
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8089
+ * icon }`.
8090
+ *
8091
+ * This dictionary folds together what used to be scattered across four
8092
+ * copies:
8093
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8094
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8095
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8096
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8097
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8098
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8099
+ *
8100
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8101
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8102
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8103
+ * color or an icon: they read this dictionary (server descriptors carry the
8104
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8105
+ *
8106
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8107
+ */
8108
+ var TAXONOMY_COLORS = {
8109
+ motion: "#f59e0b",
8110
+ audio: "#06b6d4",
8111
+ person: "#22c55e",
8112
+ vehicle: "#3b82f6",
8113
+ animal: "#f97316",
8114
+ package: "#a855f7",
8115
+ sensor: "#8b5cf6",
8116
+ control: "#10b981",
8117
+ genericDetection: "#64748b"
8118
+ };
8119
+ var DETECTION_SUB_COLORS = {
8120
+ car: "#f59e0b",
8121
+ truck: "#d97706",
8122
+ bus: "#b45309",
8123
+ motorcycle: "#eab308",
8124
+ bicycle: "#ca8a04",
8125
+ airplane: "#60a5fa",
8126
+ boat: "#2563eb",
8127
+ train: "#1d4ed8",
8128
+ bird: "#14b8a6",
8129
+ dog: "#84cc16",
8130
+ cat: "#f97316",
8131
+ horse: "#a16207",
8132
+ sheep: "#a3a3a3",
8133
+ cow: "#78716c",
8134
+ elephant: "#6b7280",
8135
+ bear: "#7c2d12",
8136
+ zebra: "#404040",
8137
+ giraffe: "#d4a373"
8138
+ };
8139
+ function titleCase(id) {
8140
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8141
+ }
8142
+ var entries = /* @__PURE__ */ new Map();
8143
+ function macro(kind, category, color, iconId, label) {
8144
+ entries.set(kind, {
8145
+ kind,
8146
+ parentKind: null,
8147
+ level: "macro",
8148
+ category,
8149
+ color,
8150
+ iconId,
8151
+ labelKey: `eventKind.${kind}`,
8152
+ label
8153
+ });
8154
+ }
8155
+ function sub(kind, parentKind, category, color, iconId, label) {
8156
+ entries.set(kind, {
8157
+ kind,
8158
+ parentKind,
8159
+ level: "sub",
8160
+ category,
8161
+ color,
8162
+ iconId,
8163
+ labelKey: `eventKind.${kind}`,
8164
+ label
8165
+ });
8166
+ }
8167
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8168
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8169
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8170
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8171
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8172
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8173
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8174
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8175
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8176
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8177
+ if (entries.has(cocoClass)) continue;
8178
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8179
+ }
8180
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8181
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8182
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8183
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8184
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8185
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8186
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8187
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8188
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8189
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8190
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8191
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8192
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8193
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8194
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8195
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8196
+ sub("button", "control", "control", "#10b981", "button", "Button");
8197
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8198
+ for (const l of AUDIO_MACRO_LABELS) {
8199
+ const kind = `audio-${l.id}`;
8200
+ if (entries.has(kind)) continue;
8201
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8202
+ }
8203
+ /** The complete taxonomy dictionary, keyed by kind. */
8204
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8205
+ /**
7877
8206
  * Error types for the safe expression engine. Two distinct classes so callers
7878
8207
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7879
8208
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15823,17 +16152,30 @@ var EventKindCategorySchema = _enum([
15823
16152
  "audio",
15824
16153
  "detection",
15825
16154
  "sensor",
16155
+ "control",
15826
16156
  "custom",
15827
16157
  "package"
15828
16158
  ]);
16159
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16160
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15829
16161
  var EventKindDescriptorSchema = object({
15830
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16162
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15831
16163
  kind: string(),
16164
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16165
+ labelKey: string(),
16166
+ /** English fallback label (kept for clients that don't translate). */
15832
16167
  label: string(),
15833
16168
  /** Hex color for timeline/legend rendering. */
15834
16169
  color: string(),
16170
+ /** Dictionary id → lucide component on the UI side. */
16171
+ iconId: string(),
16172
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15835
16173
  icon: EventKindIconSchema,
15836
16174
  category: EventKindCategorySchema,
16175
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16176
+ parentKind: string().nullable(),
16177
+ /** Derived from `parentKind`, explicit for the client tree. */
16178
+ level: EventKindLevelSchema,
15837
16179
  /** Which cap + device contributes this kind. For built-ins the camera
15838
16180
  * itself; for sensor kinds the LINKED source device. */
15839
16181
  source: object({
@@ -15906,11 +16248,21 @@ var TrackAudioLabelSchema = object({
15906
16248
  firstAt: number(),
15907
16249
  lastAt: number()
15908
16250
  });
16251
+ /**
16252
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16253
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16254
+ * linked sensor/control state change (no positions; carries a snapshot). The
16255
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16256
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16257
+ */
16258
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15909
16259
  var TrackSchema = object({
15910
16260
  trackId: string(),
15911
16261
  deviceId: number(),
15912
16262
  className: string(),
15913
16263
  label: string().optional(),
16264
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16265
+ source: TrackSourceSchema.optional(),
15914
16266
  firstSeen: number(),
15915
16267
  lastSeen: number(),
15916
16268
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16165,6 +16517,26 @@ var TrackCascadeCountsSchema = object({
16165
16517
  /** Per-track CLIP search vectors removed (best-effort). */
16166
16518
  embeddings: number().int()
16167
16519
  });
16520
+ /** Event-store footprint for one camera. */
16521
+ var EventStoreDeviceFootprintSchema = object({
16522
+ deviceId: number(),
16523
+ /** Persisted event rows (motion + object + audio) for the camera. */
16524
+ rows: number().int(),
16525
+ /** Event-owned media bytes on disk for the camera. */
16526
+ bytes: number().int()
16527
+ });
16528
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16529
+ var EventStoreFootprintSchema = object({
16530
+ totalRows: number().int(),
16531
+ totalBytes: number().int(),
16532
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16533
+ });
16534
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16535
+ var EventPruneCountsSchema = object({
16536
+ motion: number().int(),
16537
+ object: number().int(),
16538
+ audio: number().int()
16539
+ });
16168
16540
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16169
16541
  deviceId: number(),
16170
16542
  trackId: string()
@@ -16228,6 +16600,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16228
16600
  }), {
16229
16601
  kind: "mutation",
16230
16602
  auth: "admin"
16603
+ }), method(object({}), EventStoreFootprintSchema, {
16604
+ kind: "query",
16605
+ auth: "admin"
16606
+ }), method(object({
16607
+ olderThanMs: number(),
16608
+ reason: OpsLogReasonSchema.optional()
16609
+ }), EventPruneCountsSchema, {
16610
+ kind: "mutation",
16611
+ auth: "admin"
16612
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16613
+ kind: "mutation",
16614
+ auth: "admin"
16615
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16616
+ kind: "query",
16617
+ auth: "admin"
16231
16618
  }), method(object({
16232
16619
  eventId: string(),
16233
16620
  kind: MediaFileKindEnum.optional()
@@ -16252,6 +16639,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16252
16639
  eventId: string(),
16253
16640
  timestamp: number()
16254
16641
  });
16642
+ /**
16643
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16644
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16645
+ * caps into per-camera event-kind descriptors.
16646
+ *
16647
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16648
+ * is NOT duplicated here — every entry is derived from the single
16649
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16650
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16651
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16652
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16653
+ * eventful cap is missing.
16654
+ */
16655
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16656
+ var LEGACY_ICON = {
16657
+ motion: "motion",
16658
+ audio: "audio",
16659
+ person: "person",
16660
+ vehicle: "vehicle",
16661
+ animal: "animal",
16662
+ package: "package",
16663
+ door: "door",
16664
+ pir: "pir",
16665
+ smoke: "smoke",
16666
+ water: "water",
16667
+ button: "button",
16668
+ generic: "generic",
16669
+ gas: "smoke",
16670
+ vibration: "generic",
16671
+ tamper: "generic",
16672
+ presence: "person",
16673
+ lock: "generic",
16674
+ siren: "generic",
16675
+ switch: "generic",
16676
+ doorbell: "button"
16677
+ };
16678
+ function legacyIcon(iconId) {
16679
+ return LEGACY_ICON[iconId] ?? "generic";
16680
+ }
16681
+ /**
16682
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16683
+ * The anti-drift guard cross-checks this against the eventful caps declared
16684
+ * in `packages/types/src/capabilities/*.cap.ts`.
16685
+ */
16686
+ var CAP_TO_KIND = {
16687
+ contact: "contact",
16688
+ motion: "motion-sensor",
16689
+ smoke: "smoke",
16690
+ flood: "flood",
16691
+ gas: "gas",
16692
+ "carbon-monoxide": "carbon-monoxide",
16693
+ vibration: "vibration",
16694
+ tamper: "tamper",
16695
+ presence: "presence",
16696
+ "enum-sensor": "enum-sensor",
16697
+ "event-emitter": "device-event",
16698
+ "lock-control": "lock",
16699
+ switch: "switch",
16700
+ button: "button",
16701
+ doorbell: "doorbell"
16702
+ };
16703
+ function buildDescriptor(capName, kind) {
16704
+ const t = EVENT_TAXONOMY[kind];
16705
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16706
+ return {
16707
+ ...t,
16708
+ icon: legacyIcon(t.iconId)
16709
+ };
16710
+ }
16711
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16255
16712
  var CameraPipelineConfigSchema = object({
16256
16713
  engine: PipelineEngineChoiceSchema.optional(),
16257
16714
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19474,13 +19931,29 @@ method(object({
19474
19931
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19475
19932
  kind: "mutation",
19476
19933
  auth: "admin"
19477
- }), method(object({ deviceId: number() }), object({
19934
+ }), method(object({
19935
+ deviceId: number(),
19936
+ reason: OpsLogReasonSchema.optional()
19937
+ }), object({
19478
19938
  floorMs: number().nullable(),
19479
19939
  deletedBuckets: number().int(),
19480
19940
  reclaimedBytes: number().int()
19481
19941
  }), {
19482
19942
  kind: "mutation",
19483
19943
  auth: "admin"
19944
+ }), method(object({
19945
+ deviceId: number(),
19946
+ fromMs: number().optional(),
19947
+ toMs: number().optional()
19948
+ }), object({
19949
+ deletedBuckets: number().int(),
19950
+ reclaimedBytes: number().int()
19951
+ }), {
19952
+ kind: "mutation",
19953
+ auth: "admin"
19954
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19955
+ kind: "query",
19956
+ auth: "admin"
19484
19957
  });
19485
19958
  /**
19486
19959
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22602,6 +23075,12 @@ Object.freeze({
22602
23075
  addonId: null,
22603
23076
  access: "delete"
22604
23077
  },
23078
+ "pipelineAnalytics.deleteDeviceEvents": {
23079
+ capName: "pipeline-analytics",
23080
+ capScope: "device",
23081
+ addonId: null,
23082
+ access: "delete"
23083
+ },
22605
23084
  "pipelineAnalytics.deleteTracks": {
22606
23085
  capName: "pipeline-analytics",
22607
23086
  capScope: "device",
@@ -22632,6 +23111,12 @@ Object.freeze({
22632
23111
  addonId: null,
22633
23112
  access: "view"
22634
23113
  },
23114
+ "pipelineAnalytics.getEventStoreFootprint": {
23115
+ capName: "pipeline-analytics",
23116
+ capScope: "device",
23117
+ addonId: null,
23118
+ access: "view"
23119
+ },
22635
23120
  "pipelineAnalytics.getKeyEvents": {
22636
23121
  capName: "pipeline-analytics",
22637
23122
  capScope: "device",
@@ -22674,6 +23159,12 @@ Object.freeze({
22674
23159
  addonId: null,
22675
23160
  access: "view"
22676
23161
  },
23162
+ "pipelineAnalytics.listOpsLog": {
23163
+ capName: "pipeline-analytics",
23164
+ capScope: "device",
23165
+ addonId: null,
23166
+ access: "view"
23167
+ },
22677
23168
  "pipelineAnalytics.listRecentTracks": {
22678
23169
  capName: "pipeline-analytics",
22679
23170
  capScope: "device",
@@ -22686,6 +23177,12 @@ Object.freeze({
22686
23177
  addonId: null,
22687
23178
  access: "view"
22688
23179
  },
23180
+ "pipelineAnalytics.pruneEvents": {
23181
+ capName: "pipeline-analytics",
23182
+ capScope: "device",
23183
+ addonId: null,
23184
+ access: "create"
23185
+ },
22689
23186
  "pipelineAnalytics.pruneEventsBefore": {
22690
23187
  capName: "pipeline-analytics",
22691
23188
  capScope: "device",
@@ -23448,6 +23945,12 @@ Object.freeze({
23448
23945
  addonId: null,
23449
23946
  access: "create"
23450
23947
  },
23948
+ "recording.deleteFootprint": {
23949
+ capName: "recording",
23950
+ capScope: "system",
23951
+ addonId: null,
23952
+ access: "delete"
23953
+ },
23451
23954
  "recording.getAvailability": {
23452
23955
  capName: "recording",
23453
23956
  capScope: "system",
@@ -23478,6 +23981,12 @@ Object.freeze({
23478
23981
  addonId: null,
23479
23982
  access: "view"
23480
23983
  },
23984
+ "recording.listOpsLog": {
23985
+ capName: "recording",
23986
+ capScope: "system",
23987
+ addonId: null,
23988
+ access: "view"
23989
+ },
23481
23990
  "recording.locateSegment": {
23482
23991
  capName: "recording",
23483
23992
  capScope: "system",
@@ -7363,6 +7363,62 @@ var RecordingConfigSchema = object({
7363
7363
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7364
7364
  });
7365
7365
  /**
7366
+ * Ops-log — the durable, append-only operations audit shared by the
7367
+ * recordings and events management surfaces.
7368
+ *
7369
+ * ONE row shape is reused for both domains so a single "Activity" view can
7370
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7371
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7372
+ * management operation, WHY it ran (reason), and its measurable effect
7373
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7374
+ * never fail the operation it records.
7375
+ */
7376
+ /** Which management domain the operation belongs to. */
7377
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7378
+ /** The kind of management operation performed. */
7379
+ var OpsLogOpSchema = _enum([
7380
+ "prune",
7381
+ "manual-delete",
7382
+ "rescan",
7383
+ "retention-run"
7384
+ ]);
7385
+ /** Why the operation ran. */
7386
+ var OpsLogReasonSchema = _enum([
7387
+ "retention",
7388
+ "quota",
7389
+ "manual",
7390
+ "operator"
7391
+ ]);
7392
+ /** One audit row, shared verbatim by both domains. */
7393
+ var OpsLogEntrySchema = object({
7394
+ /** Unique row id. */
7395
+ id: string(),
7396
+ /** Epoch ms the operation completed. */
7397
+ at: number(),
7398
+ domain: OpsLogDomainSchema,
7399
+ op: OpsLogOpSchema,
7400
+ reason: OpsLogReasonSchema,
7401
+ /** The camera the op targeted; null for a cluster/global op. */
7402
+ deviceId: number().nullable(),
7403
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7404
+ nodeId: string(),
7405
+ /** Buckets / rows deleted (op-specific unit). */
7406
+ itemsAffected: number(),
7407
+ /** Bytes reclaimed by the op (0 when not measurable). */
7408
+ bytesReclaimed: number(),
7409
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7410
+ detail: string().nullable(),
7411
+ /** Who/what triggered the op. */
7412
+ actor: string()
7413
+ });
7414
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7415
+ var OpsLogQueryInputSchema = object({
7416
+ /** Restrict to a single camera; omit for every row. */
7417
+ deviceId: number().optional(),
7418
+ /** Max rows returned, newest-first. */
7419
+ limit: number().int().min(1).max(1e3).optional()
7420
+ });
7421
+ /**
7366
7422
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7367
7423
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7368
7424
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7606,6 +7662,160 @@ var EncodeProfileSchema = object({
7606
7662
  */
7607
7663
  outputArgs: array(string()).optional()
7608
7664
  });
7665
+ var COCO_TO_MACRO = {
7666
+ mapping: {
7667
+ person: "person",
7668
+ bicycle: "vehicle",
7669
+ car: "vehicle",
7670
+ motorcycle: "vehicle",
7671
+ airplane: "vehicle",
7672
+ bus: "vehicle",
7673
+ train: "vehicle",
7674
+ truck: "vehicle",
7675
+ boat: "vehicle",
7676
+ bird: "animal",
7677
+ cat: "animal",
7678
+ dog: "animal",
7679
+ horse: "animal",
7680
+ sheep: "animal",
7681
+ cow: "animal",
7682
+ elephant: "animal",
7683
+ bear: "animal",
7684
+ zebra: "animal",
7685
+ giraffe: "animal",
7686
+ suitcase: "package",
7687
+ backpack: "package",
7688
+ handbag: "package"
7689
+ },
7690
+ preserveOriginal: false
7691
+ };
7692
+ var AUDIO_MACRO_LABELS = [
7693
+ {
7694
+ id: "speech",
7695
+ name: "Speech",
7696
+ icon: "🗣️"
7697
+ },
7698
+ {
7699
+ id: "scream",
7700
+ name: "Scream / Shout",
7701
+ icon: "😱"
7702
+ },
7703
+ {
7704
+ id: "crying",
7705
+ name: "Crying / Baby",
7706
+ icon: "😢"
7707
+ },
7708
+ {
7709
+ id: "laughter",
7710
+ name: "Laughter",
7711
+ icon: "😂"
7712
+ },
7713
+ {
7714
+ id: "music",
7715
+ name: "Music",
7716
+ icon: "🎵"
7717
+ },
7718
+ {
7719
+ id: "dog",
7720
+ name: "Dog",
7721
+ icon: "🐕"
7722
+ },
7723
+ {
7724
+ id: "cat",
7725
+ name: "Cat",
7726
+ icon: "🐈"
7727
+ },
7728
+ {
7729
+ id: "bird",
7730
+ name: "Bird",
7731
+ icon: "🐦"
7732
+ },
7733
+ {
7734
+ id: "animal",
7735
+ name: "Animal (other)",
7736
+ icon: "🐾"
7737
+ },
7738
+ {
7739
+ id: "alarm",
7740
+ name: "Alarm / Siren",
7741
+ icon: "🚨"
7742
+ },
7743
+ {
7744
+ id: "doorbell",
7745
+ name: "Doorbell / Knock",
7746
+ icon: "🔔"
7747
+ },
7748
+ {
7749
+ id: "glass_breaking",
7750
+ name: "Glass Breaking",
7751
+ icon: "💥"
7752
+ },
7753
+ {
7754
+ id: "gunshot",
7755
+ name: "Gunshot / Explosion",
7756
+ icon: "💣"
7757
+ },
7758
+ {
7759
+ id: "vehicle",
7760
+ name: "Vehicle",
7761
+ icon: "🚗"
7762
+ },
7763
+ {
7764
+ id: "siren",
7765
+ name: "Emergency Siren",
7766
+ icon: "🚑"
7767
+ },
7768
+ {
7769
+ id: "fire",
7770
+ name: "Fire / Smoke",
7771
+ icon: "🔥"
7772
+ },
7773
+ {
7774
+ id: "water",
7775
+ name: "Water",
7776
+ icon: "💧"
7777
+ },
7778
+ {
7779
+ id: "wind",
7780
+ name: "Wind / Weather",
7781
+ icon: "🌬️"
7782
+ },
7783
+ {
7784
+ id: "door",
7785
+ name: "Door",
7786
+ icon: "🚪"
7787
+ },
7788
+ {
7789
+ id: "footsteps",
7790
+ name: "Footsteps",
7791
+ icon: "👣"
7792
+ },
7793
+ {
7794
+ id: "crowd",
7795
+ name: "Crowd / Chatter",
7796
+ icon: "👥"
7797
+ },
7798
+ {
7799
+ id: "telephone",
7800
+ name: "Telephone",
7801
+ icon: "📞"
7802
+ },
7803
+ {
7804
+ id: "engine",
7805
+ name: "Engine / Motor",
7806
+ icon: "⚙️"
7807
+ },
7808
+ {
7809
+ id: "tools",
7810
+ name: "Tools / Construction",
7811
+ icon: "🔨"
7812
+ },
7813
+ {
7814
+ id: "silence",
7815
+ name: "Silence",
7816
+ icon: "🤫"
7817
+ }
7818
+ ];
7609
7819
  var YAMNET_TO_MACRO = {
7610
7820
  mapping: {
7611
7821
  Speech: "speech",
@@ -7872,6 +8082,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7872
8082
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7873
8083
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7874
8084
  /**
8085
+ * Unified event-kind taxonomy — THE single source of truth for
8086
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8087
+ * icon }`.
8088
+ *
8089
+ * This dictionary folds together what used to be scattered across four
8090
+ * copies:
8091
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8092
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8093
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8094
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8095
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8096
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8097
+ *
8098
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8099
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8100
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8101
+ * color or an icon: they read this dictionary (server descriptors carry the
8102
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8103
+ *
8104
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8105
+ */
8106
+ var TAXONOMY_COLORS = {
8107
+ motion: "#f59e0b",
8108
+ audio: "#06b6d4",
8109
+ person: "#22c55e",
8110
+ vehicle: "#3b82f6",
8111
+ animal: "#f97316",
8112
+ package: "#a855f7",
8113
+ sensor: "#8b5cf6",
8114
+ control: "#10b981",
8115
+ genericDetection: "#64748b"
8116
+ };
8117
+ var DETECTION_SUB_COLORS = {
8118
+ car: "#f59e0b",
8119
+ truck: "#d97706",
8120
+ bus: "#b45309",
8121
+ motorcycle: "#eab308",
8122
+ bicycle: "#ca8a04",
8123
+ airplane: "#60a5fa",
8124
+ boat: "#2563eb",
8125
+ train: "#1d4ed8",
8126
+ bird: "#14b8a6",
8127
+ dog: "#84cc16",
8128
+ cat: "#f97316",
8129
+ horse: "#a16207",
8130
+ sheep: "#a3a3a3",
8131
+ cow: "#78716c",
8132
+ elephant: "#6b7280",
8133
+ bear: "#7c2d12",
8134
+ zebra: "#404040",
8135
+ giraffe: "#d4a373"
8136
+ };
8137
+ function titleCase(id) {
8138
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8139
+ }
8140
+ var entries = /* @__PURE__ */ new Map();
8141
+ function macro(kind, category, color, iconId, label) {
8142
+ entries.set(kind, {
8143
+ kind,
8144
+ parentKind: null,
8145
+ level: "macro",
8146
+ category,
8147
+ color,
8148
+ iconId,
8149
+ labelKey: `eventKind.${kind}`,
8150
+ label
8151
+ });
8152
+ }
8153
+ function sub(kind, parentKind, category, color, iconId, label) {
8154
+ entries.set(kind, {
8155
+ kind,
8156
+ parentKind,
8157
+ level: "sub",
8158
+ category,
8159
+ color,
8160
+ iconId,
8161
+ labelKey: `eventKind.${kind}`,
8162
+ label
8163
+ });
8164
+ }
8165
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8166
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8167
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8168
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8169
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8170
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8171
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8172
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8173
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8174
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8175
+ if (entries.has(cocoClass)) continue;
8176
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8177
+ }
8178
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8179
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8180
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8181
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8182
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8183
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8184
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8185
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8186
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8187
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8188
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8189
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8190
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8191
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8192
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8193
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8194
+ sub("button", "control", "control", "#10b981", "button", "Button");
8195
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8196
+ for (const l of AUDIO_MACRO_LABELS) {
8197
+ const kind = `audio-${l.id}`;
8198
+ if (entries.has(kind)) continue;
8199
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8200
+ }
8201
+ /** The complete taxonomy dictionary, keyed by kind. */
8202
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8203
+ /**
7875
8204
  * Error types for the safe expression engine. Two distinct classes so callers
7876
8205
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7877
8206
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15821,17 +16150,30 @@ var EventKindCategorySchema = _enum([
15821
16150
  "audio",
15822
16151
  "detection",
15823
16152
  "sensor",
16153
+ "control",
15824
16154
  "custom",
15825
16155
  "package"
15826
16156
  ]);
16157
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16158
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15827
16159
  var EventKindDescriptorSchema = object({
15828
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16160
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15829
16161
  kind: string(),
16162
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16163
+ labelKey: string(),
16164
+ /** English fallback label (kept for clients that don't translate). */
15830
16165
  label: string(),
15831
16166
  /** Hex color for timeline/legend rendering. */
15832
16167
  color: string(),
16168
+ /** Dictionary id → lucide component on the UI side. */
16169
+ iconId: string(),
16170
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15833
16171
  icon: EventKindIconSchema,
15834
16172
  category: EventKindCategorySchema,
16173
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16174
+ parentKind: string().nullable(),
16175
+ /** Derived from `parentKind`, explicit for the client tree. */
16176
+ level: EventKindLevelSchema,
15835
16177
  /** Which cap + device contributes this kind. For built-ins the camera
15836
16178
  * itself; for sensor kinds the LINKED source device. */
15837
16179
  source: object({
@@ -15904,11 +16246,21 @@ var TrackAudioLabelSchema = object({
15904
16246
  firstAt: number(),
15905
16247
  lastAt: number()
15906
16248
  });
16249
+ /**
16250
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16251
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16252
+ * linked sensor/control state change (no positions; carries a snapshot). The
16253
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16254
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16255
+ */
16256
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15907
16257
  var TrackSchema = object({
15908
16258
  trackId: string(),
15909
16259
  deviceId: number(),
15910
16260
  className: string(),
15911
16261
  label: string().optional(),
16262
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16263
+ source: TrackSourceSchema.optional(),
15912
16264
  firstSeen: number(),
15913
16265
  lastSeen: number(),
15914
16266
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16163,6 +16515,26 @@ var TrackCascadeCountsSchema = object({
16163
16515
  /** Per-track CLIP search vectors removed (best-effort). */
16164
16516
  embeddings: number().int()
16165
16517
  });
16518
+ /** Event-store footprint for one camera. */
16519
+ var EventStoreDeviceFootprintSchema = object({
16520
+ deviceId: number(),
16521
+ /** Persisted event rows (motion + object + audio) for the camera. */
16522
+ rows: number().int(),
16523
+ /** Event-owned media bytes on disk for the camera. */
16524
+ bytes: number().int()
16525
+ });
16526
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16527
+ var EventStoreFootprintSchema = object({
16528
+ totalRows: number().int(),
16529
+ totalBytes: number().int(),
16530
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16531
+ });
16532
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16533
+ var EventPruneCountsSchema = object({
16534
+ motion: number().int(),
16535
+ object: number().int(),
16536
+ audio: number().int()
16537
+ });
16166
16538
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16167
16539
  deviceId: number(),
16168
16540
  trackId: string()
@@ -16226,6 +16598,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16226
16598
  }), {
16227
16599
  kind: "mutation",
16228
16600
  auth: "admin"
16601
+ }), method(object({}), EventStoreFootprintSchema, {
16602
+ kind: "query",
16603
+ auth: "admin"
16604
+ }), method(object({
16605
+ olderThanMs: number(),
16606
+ reason: OpsLogReasonSchema.optional()
16607
+ }), EventPruneCountsSchema, {
16608
+ kind: "mutation",
16609
+ auth: "admin"
16610
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16611
+ kind: "mutation",
16612
+ auth: "admin"
16613
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16614
+ kind: "query",
16615
+ auth: "admin"
16229
16616
  }), method(object({
16230
16617
  eventId: string(),
16231
16618
  kind: MediaFileKindEnum.optional()
@@ -16250,6 +16637,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16250
16637
  eventId: string(),
16251
16638
  timestamp: number()
16252
16639
  });
16640
+ /**
16641
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16642
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16643
+ * caps into per-camera event-kind descriptors.
16644
+ *
16645
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16646
+ * is NOT duplicated here — every entry is derived from the single
16647
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16648
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16649
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16650
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16651
+ * eventful cap is missing.
16652
+ */
16653
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16654
+ var LEGACY_ICON = {
16655
+ motion: "motion",
16656
+ audio: "audio",
16657
+ person: "person",
16658
+ vehicle: "vehicle",
16659
+ animal: "animal",
16660
+ package: "package",
16661
+ door: "door",
16662
+ pir: "pir",
16663
+ smoke: "smoke",
16664
+ water: "water",
16665
+ button: "button",
16666
+ generic: "generic",
16667
+ gas: "smoke",
16668
+ vibration: "generic",
16669
+ tamper: "generic",
16670
+ presence: "person",
16671
+ lock: "generic",
16672
+ siren: "generic",
16673
+ switch: "generic",
16674
+ doorbell: "button"
16675
+ };
16676
+ function legacyIcon(iconId) {
16677
+ return LEGACY_ICON[iconId] ?? "generic";
16678
+ }
16679
+ /**
16680
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16681
+ * The anti-drift guard cross-checks this against the eventful caps declared
16682
+ * in `packages/types/src/capabilities/*.cap.ts`.
16683
+ */
16684
+ var CAP_TO_KIND = {
16685
+ contact: "contact",
16686
+ motion: "motion-sensor",
16687
+ smoke: "smoke",
16688
+ flood: "flood",
16689
+ gas: "gas",
16690
+ "carbon-monoxide": "carbon-monoxide",
16691
+ vibration: "vibration",
16692
+ tamper: "tamper",
16693
+ presence: "presence",
16694
+ "enum-sensor": "enum-sensor",
16695
+ "event-emitter": "device-event",
16696
+ "lock-control": "lock",
16697
+ switch: "switch",
16698
+ button: "button",
16699
+ doorbell: "doorbell"
16700
+ };
16701
+ function buildDescriptor(capName, kind) {
16702
+ const t = EVENT_TAXONOMY[kind];
16703
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16704
+ return {
16705
+ ...t,
16706
+ icon: legacyIcon(t.iconId)
16707
+ };
16708
+ }
16709
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16253
16710
  var CameraPipelineConfigSchema = object({
16254
16711
  engine: PipelineEngineChoiceSchema.optional(),
16255
16712
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19472,13 +19929,29 @@ method(object({
19472
19929
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19473
19930
  kind: "mutation",
19474
19931
  auth: "admin"
19475
- }), method(object({ deviceId: number() }), object({
19932
+ }), method(object({
19933
+ deviceId: number(),
19934
+ reason: OpsLogReasonSchema.optional()
19935
+ }), object({
19476
19936
  floorMs: number().nullable(),
19477
19937
  deletedBuckets: number().int(),
19478
19938
  reclaimedBytes: number().int()
19479
19939
  }), {
19480
19940
  kind: "mutation",
19481
19941
  auth: "admin"
19942
+ }), method(object({
19943
+ deviceId: number(),
19944
+ fromMs: number().optional(),
19945
+ toMs: number().optional()
19946
+ }), object({
19947
+ deletedBuckets: number().int(),
19948
+ reclaimedBytes: number().int()
19949
+ }), {
19950
+ kind: "mutation",
19951
+ auth: "admin"
19952
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19953
+ kind: "query",
19954
+ auth: "admin"
19482
19955
  });
19483
19956
  /**
19484
19957
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22600,6 +23073,12 @@ Object.freeze({
22600
23073
  addonId: null,
22601
23074
  access: "delete"
22602
23075
  },
23076
+ "pipelineAnalytics.deleteDeviceEvents": {
23077
+ capName: "pipeline-analytics",
23078
+ capScope: "device",
23079
+ addonId: null,
23080
+ access: "delete"
23081
+ },
22603
23082
  "pipelineAnalytics.deleteTracks": {
22604
23083
  capName: "pipeline-analytics",
22605
23084
  capScope: "device",
@@ -22630,6 +23109,12 @@ Object.freeze({
22630
23109
  addonId: null,
22631
23110
  access: "view"
22632
23111
  },
23112
+ "pipelineAnalytics.getEventStoreFootprint": {
23113
+ capName: "pipeline-analytics",
23114
+ capScope: "device",
23115
+ addonId: null,
23116
+ access: "view"
23117
+ },
22633
23118
  "pipelineAnalytics.getKeyEvents": {
22634
23119
  capName: "pipeline-analytics",
22635
23120
  capScope: "device",
@@ -22672,6 +23157,12 @@ Object.freeze({
22672
23157
  addonId: null,
22673
23158
  access: "view"
22674
23159
  },
23160
+ "pipelineAnalytics.listOpsLog": {
23161
+ capName: "pipeline-analytics",
23162
+ capScope: "device",
23163
+ addonId: null,
23164
+ access: "view"
23165
+ },
22675
23166
  "pipelineAnalytics.listRecentTracks": {
22676
23167
  capName: "pipeline-analytics",
22677
23168
  capScope: "device",
@@ -22684,6 +23175,12 @@ Object.freeze({
22684
23175
  addonId: null,
22685
23176
  access: "view"
22686
23177
  },
23178
+ "pipelineAnalytics.pruneEvents": {
23179
+ capName: "pipeline-analytics",
23180
+ capScope: "device",
23181
+ addonId: null,
23182
+ access: "create"
23183
+ },
22687
23184
  "pipelineAnalytics.pruneEventsBefore": {
22688
23185
  capName: "pipeline-analytics",
22689
23186
  capScope: "device",
@@ -23446,6 +23943,12 @@ Object.freeze({
23446
23943
  addonId: null,
23447
23944
  access: "create"
23448
23945
  },
23946
+ "recording.deleteFootprint": {
23947
+ capName: "recording",
23948
+ capScope: "system",
23949
+ addonId: null,
23950
+ access: "delete"
23951
+ },
23449
23952
  "recording.getAvailability": {
23450
23953
  capName: "recording",
23451
23954
  capScope: "system",
@@ -23476,6 +23979,12 @@ Object.freeze({
23476
23979
  addonId: null,
23477
23980
  access: "view"
23478
23981
  },
23982
+ "recording.listOpsLog": {
23983
+ capName: "recording",
23984
+ capScope: "system",
23985
+ addonId: null,
23986
+ access: "view"
23987
+ },
23479
23988
  "recording.locateSegment": {
23480
23989
  capName: "recording",
23481
23990
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-smtp-nodemailer",
3
- "version": "1.1.25",
3
+ "version": "1.1.27",
4
4
  "description": "SMTP email provider addon for CamStack — wraps `nodemailer` and registers a `smtp-provider` cap collection entry. Used by magic-link login + notifier addons.",
5
5
  "keywords": [
6
6
  "camstack",