@camstack/addon-mqtt-broker 1.1.26 → 1.1.28

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.
@@ -7373,6 +7373,62 @@ var RecordingConfigSchema = object({
7373
7373
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7374
7374
  });
7375
7375
  /**
7376
+ * Ops-log — the durable, append-only operations audit shared by the
7377
+ * recordings and events management surfaces.
7378
+ *
7379
+ * ONE row shape is reused for both domains so a single "Activity" view can
7380
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7381
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7382
+ * management operation, WHY it ran (reason), and its measurable effect
7383
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7384
+ * never fail the operation it records.
7385
+ */
7386
+ /** Which management domain the operation belongs to. */
7387
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7388
+ /** The kind of management operation performed. */
7389
+ var OpsLogOpSchema = _enum([
7390
+ "prune",
7391
+ "manual-delete",
7392
+ "rescan",
7393
+ "retention-run"
7394
+ ]);
7395
+ /** Why the operation ran. */
7396
+ var OpsLogReasonSchema = _enum([
7397
+ "retention",
7398
+ "quota",
7399
+ "manual",
7400
+ "operator"
7401
+ ]);
7402
+ /** One audit row, shared verbatim by both domains. */
7403
+ var OpsLogEntrySchema = object({
7404
+ /** Unique row id. */
7405
+ id: string(),
7406
+ /** Epoch ms the operation completed. */
7407
+ at: number(),
7408
+ domain: OpsLogDomainSchema,
7409
+ op: OpsLogOpSchema,
7410
+ reason: OpsLogReasonSchema,
7411
+ /** The camera the op targeted; null for a cluster/global op. */
7412
+ deviceId: number().nullable(),
7413
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7414
+ nodeId: string(),
7415
+ /** Buckets / rows deleted (op-specific unit). */
7416
+ itemsAffected: number(),
7417
+ /** Bytes reclaimed by the op (0 when not measurable). */
7418
+ bytesReclaimed: number(),
7419
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7420
+ detail: string().nullable(),
7421
+ /** Who/what triggered the op. */
7422
+ actor: string()
7423
+ });
7424
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7425
+ var OpsLogQueryInputSchema = object({
7426
+ /** Restrict to a single camera; omit for every row. */
7427
+ deviceId: number().optional(),
7428
+ /** Max rows returned, newest-first. */
7429
+ limit: number().int().min(1).max(1e3).optional()
7430
+ });
7431
+ /**
7376
7432
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7377
7433
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7378
7434
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7616,6 +7672,160 @@ var EncodeProfileSchema = object({
7616
7672
  */
7617
7673
  outputArgs: array(string()).optional()
7618
7674
  });
7675
+ var COCO_TO_MACRO = {
7676
+ mapping: {
7677
+ person: "person",
7678
+ bicycle: "vehicle",
7679
+ car: "vehicle",
7680
+ motorcycle: "vehicle",
7681
+ airplane: "vehicle",
7682
+ bus: "vehicle",
7683
+ train: "vehicle",
7684
+ truck: "vehicle",
7685
+ boat: "vehicle",
7686
+ bird: "animal",
7687
+ cat: "animal",
7688
+ dog: "animal",
7689
+ horse: "animal",
7690
+ sheep: "animal",
7691
+ cow: "animal",
7692
+ elephant: "animal",
7693
+ bear: "animal",
7694
+ zebra: "animal",
7695
+ giraffe: "animal",
7696
+ suitcase: "package",
7697
+ backpack: "package",
7698
+ handbag: "package"
7699
+ },
7700
+ preserveOriginal: false
7701
+ };
7702
+ var AUDIO_MACRO_LABELS = [
7703
+ {
7704
+ id: "speech",
7705
+ name: "Speech",
7706
+ icon: "🗣️"
7707
+ },
7708
+ {
7709
+ id: "scream",
7710
+ name: "Scream / Shout",
7711
+ icon: "😱"
7712
+ },
7713
+ {
7714
+ id: "crying",
7715
+ name: "Crying / Baby",
7716
+ icon: "😢"
7717
+ },
7718
+ {
7719
+ id: "laughter",
7720
+ name: "Laughter",
7721
+ icon: "😂"
7722
+ },
7723
+ {
7724
+ id: "music",
7725
+ name: "Music",
7726
+ icon: "🎵"
7727
+ },
7728
+ {
7729
+ id: "dog",
7730
+ name: "Dog",
7731
+ icon: "🐕"
7732
+ },
7733
+ {
7734
+ id: "cat",
7735
+ name: "Cat",
7736
+ icon: "🐈"
7737
+ },
7738
+ {
7739
+ id: "bird",
7740
+ name: "Bird",
7741
+ icon: "🐦"
7742
+ },
7743
+ {
7744
+ id: "animal",
7745
+ name: "Animal (other)",
7746
+ icon: "🐾"
7747
+ },
7748
+ {
7749
+ id: "alarm",
7750
+ name: "Alarm / Siren",
7751
+ icon: "🚨"
7752
+ },
7753
+ {
7754
+ id: "doorbell",
7755
+ name: "Doorbell / Knock",
7756
+ icon: "🔔"
7757
+ },
7758
+ {
7759
+ id: "glass_breaking",
7760
+ name: "Glass Breaking",
7761
+ icon: "💥"
7762
+ },
7763
+ {
7764
+ id: "gunshot",
7765
+ name: "Gunshot / Explosion",
7766
+ icon: "💣"
7767
+ },
7768
+ {
7769
+ id: "vehicle",
7770
+ name: "Vehicle",
7771
+ icon: "🚗"
7772
+ },
7773
+ {
7774
+ id: "siren",
7775
+ name: "Emergency Siren",
7776
+ icon: "🚑"
7777
+ },
7778
+ {
7779
+ id: "fire",
7780
+ name: "Fire / Smoke",
7781
+ icon: "🔥"
7782
+ },
7783
+ {
7784
+ id: "water",
7785
+ name: "Water",
7786
+ icon: "💧"
7787
+ },
7788
+ {
7789
+ id: "wind",
7790
+ name: "Wind / Weather",
7791
+ icon: "🌬️"
7792
+ },
7793
+ {
7794
+ id: "door",
7795
+ name: "Door",
7796
+ icon: "🚪"
7797
+ },
7798
+ {
7799
+ id: "footsteps",
7800
+ name: "Footsteps",
7801
+ icon: "👣"
7802
+ },
7803
+ {
7804
+ id: "crowd",
7805
+ name: "Crowd / Chatter",
7806
+ icon: "👥"
7807
+ },
7808
+ {
7809
+ id: "telephone",
7810
+ name: "Telephone",
7811
+ icon: "📞"
7812
+ },
7813
+ {
7814
+ id: "engine",
7815
+ name: "Engine / Motor",
7816
+ icon: "⚙️"
7817
+ },
7818
+ {
7819
+ id: "tools",
7820
+ name: "Tools / Construction",
7821
+ icon: "🔨"
7822
+ },
7823
+ {
7824
+ id: "silence",
7825
+ name: "Silence",
7826
+ icon: "🤫"
7827
+ }
7828
+ ];
7619
7829
  var YAMNET_TO_MACRO = {
7620
7830
  mapping: {
7621
7831
  Speech: "speech",
@@ -7882,6 +8092,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7882
8092
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7883
8093
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7884
8094
  /**
8095
+ * Unified event-kind taxonomy — THE single source of truth for
8096
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8097
+ * icon }`.
8098
+ *
8099
+ * This dictionary folds together what used to be scattered across four
8100
+ * copies:
8101
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8102
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8103
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8104
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8105
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8106
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8107
+ *
8108
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8109
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8110
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8111
+ * color or an icon: they read this dictionary (server descriptors carry the
8112
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8113
+ *
8114
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8115
+ */
8116
+ var TAXONOMY_COLORS = {
8117
+ motion: "#f59e0b",
8118
+ audio: "#06b6d4",
8119
+ person: "#22c55e",
8120
+ vehicle: "#3b82f6",
8121
+ animal: "#f97316",
8122
+ package: "#a855f7",
8123
+ sensor: "#8b5cf6",
8124
+ control: "#10b981",
8125
+ genericDetection: "#64748b"
8126
+ };
8127
+ var DETECTION_SUB_COLORS = {
8128
+ car: "#f59e0b",
8129
+ truck: "#d97706",
8130
+ bus: "#b45309",
8131
+ motorcycle: "#eab308",
8132
+ bicycle: "#ca8a04",
8133
+ airplane: "#60a5fa",
8134
+ boat: "#2563eb",
8135
+ train: "#1d4ed8",
8136
+ bird: "#14b8a6",
8137
+ dog: "#84cc16",
8138
+ cat: "#f97316",
8139
+ horse: "#a16207",
8140
+ sheep: "#a3a3a3",
8141
+ cow: "#78716c",
8142
+ elephant: "#6b7280",
8143
+ bear: "#7c2d12",
8144
+ zebra: "#404040",
8145
+ giraffe: "#d4a373"
8146
+ };
8147
+ function titleCase(id) {
8148
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8149
+ }
8150
+ var entries = /* @__PURE__ */ new Map();
8151
+ function macro(kind, category, color, iconId, label) {
8152
+ entries.set(kind, {
8153
+ kind,
8154
+ parentKind: null,
8155
+ level: "macro",
8156
+ category,
8157
+ color,
8158
+ iconId,
8159
+ labelKey: `eventKind.${kind}`,
8160
+ label
8161
+ });
8162
+ }
8163
+ function sub(kind, parentKind, category, color, iconId, label) {
8164
+ entries.set(kind, {
8165
+ kind,
8166
+ parentKind,
8167
+ level: "sub",
8168
+ category,
8169
+ color,
8170
+ iconId,
8171
+ labelKey: `eventKind.${kind}`,
8172
+ label
8173
+ });
8174
+ }
8175
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8176
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8177
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8178
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8179
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8180
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8181
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8182
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8183
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8184
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8185
+ if (entries.has(cocoClass)) continue;
8186
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8187
+ }
8188
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8189
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8190
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8191
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8192
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8193
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8194
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8195
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8196
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8197
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8198
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8199
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8200
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8201
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8202
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8203
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8204
+ sub("button", "control", "control", "#10b981", "button", "Button");
8205
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8206
+ for (const l of AUDIO_MACRO_LABELS) {
8207
+ const kind = `audio-${l.id}`;
8208
+ if (entries.has(kind)) continue;
8209
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8210
+ }
8211
+ /** The complete taxonomy dictionary, keyed by kind. */
8212
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8213
+ /**
7885
8214
  * Error types for the safe expression engine. Two distinct classes so callers
7886
8215
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7887
8216
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15899,17 +16228,30 @@ var EventKindCategorySchema = _enum([
15899
16228
  "audio",
15900
16229
  "detection",
15901
16230
  "sensor",
16231
+ "control",
15902
16232
  "custom",
15903
16233
  "package"
15904
16234
  ]);
16235
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16236
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15905
16237
  var EventKindDescriptorSchema = object({
15906
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16238
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15907
16239
  kind: string(),
16240
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16241
+ labelKey: string(),
16242
+ /** English fallback label (kept for clients that don't translate). */
15908
16243
  label: string(),
15909
16244
  /** Hex color for timeline/legend rendering. */
15910
16245
  color: string(),
16246
+ /** Dictionary id → lucide component on the UI side. */
16247
+ iconId: string(),
16248
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15911
16249
  icon: EventKindIconSchema,
15912
16250
  category: EventKindCategorySchema,
16251
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16252
+ parentKind: string().nullable(),
16253
+ /** Derived from `parentKind`, explicit for the client tree. */
16254
+ level: EventKindLevelSchema,
15913
16255
  /** Which cap + device contributes this kind. For built-ins the camera
15914
16256
  * itself; for sensor kinds the LINKED source device. */
15915
16257
  source: object({
@@ -15982,11 +16324,21 @@ var TrackAudioLabelSchema = object({
15982
16324
  firstAt: number(),
15983
16325
  lastAt: number()
15984
16326
  });
16327
+ /**
16328
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16329
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16330
+ * linked sensor/control state change (no positions; carries a snapshot). The
16331
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16332
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16333
+ */
16334
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15985
16335
  var TrackSchema = object({
15986
16336
  trackId: string(),
15987
16337
  deviceId: number(),
15988
16338
  className: string(),
15989
16339
  label: string().optional(),
16340
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16341
+ source: TrackSourceSchema.optional(),
15990
16342
  firstSeen: number(),
15991
16343
  lastSeen: number(),
15992
16344
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16241,6 +16593,26 @@ var TrackCascadeCountsSchema = object({
16241
16593
  /** Per-track CLIP search vectors removed (best-effort). */
16242
16594
  embeddings: number().int()
16243
16595
  });
16596
+ /** Event-store footprint for one camera. */
16597
+ var EventStoreDeviceFootprintSchema = object({
16598
+ deviceId: number(),
16599
+ /** Persisted event rows (motion + object + audio) for the camera. */
16600
+ rows: number().int(),
16601
+ /** Event-owned media bytes on disk for the camera. */
16602
+ bytes: number().int()
16603
+ });
16604
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16605
+ var EventStoreFootprintSchema = object({
16606
+ totalRows: number().int(),
16607
+ totalBytes: number().int(),
16608
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16609
+ });
16610
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16611
+ var EventPruneCountsSchema = object({
16612
+ motion: number().int(),
16613
+ object: number().int(),
16614
+ audio: number().int()
16615
+ });
16244
16616
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16245
16617
  deviceId: number(),
16246
16618
  trackId: string()
@@ -16304,6 +16676,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16304
16676
  }), {
16305
16677
  kind: "mutation",
16306
16678
  auth: "admin"
16679
+ }), method(object({}), EventStoreFootprintSchema, {
16680
+ kind: "query",
16681
+ auth: "admin"
16682
+ }), method(object({
16683
+ olderThanMs: number(),
16684
+ reason: OpsLogReasonSchema.optional()
16685
+ }), EventPruneCountsSchema, {
16686
+ kind: "mutation",
16687
+ auth: "admin"
16688
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16689
+ kind: "mutation",
16690
+ auth: "admin"
16691
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16692
+ kind: "query",
16693
+ auth: "admin"
16307
16694
  }), method(object({
16308
16695
  eventId: string(),
16309
16696
  kind: MediaFileKindEnum.optional()
@@ -16328,6 +16715,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16328
16715
  eventId: string(),
16329
16716
  timestamp: number()
16330
16717
  });
16718
+ /**
16719
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16720
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16721
+ * caps into per-camera event-kind descriptors.
16722
+ *
16723
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16724
+ * is NOT duplicated here — every entry is derived from the single
16725
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16726
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16727
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16728
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16729
+ * eventful cap is missing.
16730
+ */
16731
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16732
+ var LEGACY_ICON = {
16733
+ motion: "motion",
16734
+ audio: "audio",
16735
+ person: "person",
16736
+ vehicle: "vehicle",
16737
+ animal: "animal",
16738
+ package: "package",
16739
+ door: "door",
16740
+ pir: "pir",
16741
+ smoke: "smoke",
16742
+ water: "water",
16743
+ button: "button",
16744
+ generic: "generic",
16745
+ gas: "smoke",
16746
+ vibration: "generic",
16747
+ tamper: "generic",
16748
+ presence: "person",
16749
+ lock: "generic",
16750
+ siren: "generic",
16751
+ switch: "generic",
16752
+ doorbell: "button"
16753
+ };
16754
+ function legacyIcon(iconId) {
16755
+ return LEGACY_ICON[iconId] ?? "generic";
16756
+ }
16757
+ /**
16758
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16759
+ * The anti-drift guard cross-checks this against the eventful caps declared
16760
+ * in `packages/types/src/capabilities/*.cap.ts`.
16761
+ */
16762
+ var CAP_TO_KIND = {
16763
+ contact: "contact",
16764
+ motion: "motion-sensor",
16765
+ smoke: "smoke",
16766
+ flood: "flood",
16767
+ gas: "gas",
16768
+ "carbon-monoxide": "carbon-monoxide",
16769
+ vibration: "vibration",
16770
+ tamper: "tamper",
16771
+ presence: "presence",
16772
+ "enum-sensor": "enum-sensor",
16773
+ "event-emitter": "device-event",
16774
+ "lock-control": "lock",
16775
+ switch: "switch",
16776
+ button: "button",
16777
+ doorbell: "doorbell"
16778
+ };
16779
+ function buildDescriptor(capName, kind) {
16780
+ const t = EVENT_TAXONOMY[kind];
16781
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16782
+ return {
16783
+ ...t,
16784
+ icon: legacyIcon(t.iconId)
16785
+ };
16786
+ }
16787
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16331
16788
  var CameraPipelineConfigSchema = object({
16332
16789
  engine: PipelineEngineChoiceSchema.optional(),
16333
16790
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19537,13 +19994,29 @@ method(object({
19537
19994
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19538
19995
  kind: "mutation",
19539
19996
  auth: "admin"
19540
- }), method(object({ deviceId: number() }), object({
19997
+ }), method(object({
19998
+ deviceId: number(),
19999
+ reason: OpsLogReasonSchema.optional()
20000
+ }), object({
19541
20001
  floorMs: number().nullable(),
19542
20002
  deletedBuckets: number().int(),
19543
20003
  reclaimedBytes: number().int()
19544
20004
  }), {
19545
20005
  kind: "mutation",
19546
20006
  auth: "admin"
20007
+ }), method(object({
20008
+ deviceId: number(),
20009
+ fromMs: number().optional(),
20010
+ toMs: number().optional()
20011
+ }), object({
20012
+ deletedBuckets: number().int(),
20013
+ reclaimedBytes: number().int()
20014
+ }), {
20015
+ kind: "mutation",
20016
+ auth: "admin"
20017
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20018
+ kind: "query",
20019
+ auth: "admin"
19547
20020
  });
19548
20021
  /**
19549
20022
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22665,6 +23138,12 @@ Object.freeze({
22665
23138
  addonId: null,
22666
23139
  access: "delete"
22667
23140
  },
23141
+ "pipelineAnalytics.deleteDeviceEvents": {
23142
+ capName: "pipeline-analytics",
23143
+ capScope: "device",
23144
+ addonId: null,
23145
+ access: "delete"
23146
+ },
22668
23147
  "pipelineAnalytics.deleteTracks": {
22669
23148
  capName: "pipeline-analytics",
22670
23149
  capScope: "device",
@@ -22695,6 +23174,12 @@ Object.freeze({
22695
23174
  addonId: null,
22696
23175
  access: "view"
22697
23176
  },
23177
+ "pipelineAnalytics.getEventStoreFootprint": {
23178
+ capName: "pipeline-analytics",
23179
+ capScope: "device",
23180
+ addonId: null,
23181
+ access: "view"
23182
+ },
22698
23183
  "pipelineAnalytics.getKeyEvents": {
22699
23184
  capName: "pipeline-analytics",
22700
23185
  capScope: "device",
@@ -22737,6 +23222,12 @@ Object.freeze({
22737
23222
  addonId: null,
22738
23223
  access: "view"
22739
23224
  },
23225
+ "pipelineAnalytics.listOpsLog": {
23226
+ capName: "pipeline-analytics",
23227
+ capScope: "device",
23228
+ addonId: null,
23229
+ access: "view"
23230
+ },
22740
23231
  "pipelineAnalytics.listRecentTracks": {
22741
23232
  capName: "pipeline-analytics",
22742
23233
  capScope: "device",
@@ -22749,6 +23240,12 @@ Object.freeze({
22749
23240
  addonId: null,
22750
23241
  access: "view"
22751
23242
  },
23243
+ "pipelineAnalytics.pruneEvents": {
23244
+ capName: "pipeline-analytics",
23245
+ capScope: "device",
23246
+ addonId: null,
23247
+ access: "create"
23248
+ },
22752
23249
  "pipelineAnalytics.pruneEventsBefore": {
22753
23250
  capName: "pipeline-analytics",
22754
23251
  capScope: "device",
@@ -23511,6 +24008,12 @@ Object.freeze({
23511
24008
  addonId: null,
23512
24009
  access: "create"
23513
24010
  },
24011
+ "recording.deleteFootprint": {
24012
+ capName: "recording",
24013
+ capScope: "system",
24014
+ addonId: null,
24015
+ access: "delete"
24016
+ },
23514
24017
  "recording.getAvailability": {
23515
24018
  capName: "recording",
23516
24019
  capScope: "system",
@@ -23541,6 +24044,12 @@ Object.freeze({
23541
24044
  addonId: null,
23542
24045
  access: "view"
23543
24046
  },
24047
+ "recording.listOpsLog": {
24048
+ capName: "recording",
24049
+ capScope: "system",
24050
+ addonId: null,
24051
+ access: "view"
24052
+ },
23544
24053
  "recording.locateSegment": {
23545
24054
  capName: "recording",
23546
24055
  capScope: "system",
@@ -7368,6 +7368,62 @@ var RecordingConfigSchema = object({
7368
7368
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7369
7369
  });
7370
7370
  /**
7371
+ * Ops-log — the durable, append-only operations audit shared by the
7372
+ * recordings and events management surfaces.
7373
+ *
7374
+ * ONE row shape is reused for both domains so a single "Activity" view can
7375
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7376
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7377
+ * management operation, WHY it ran (reason), and its measurable effect
7378
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7379
+ * never fail the operation it records.
7380
+ */
7381
+ /** Which management domain the operation belongs to. */
7382
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7383
+ /** The kind of management operation performed. */
7384
+ var OpsLogOpSchema = _enum([
7385
+ "prune",
7386
+ "manual-delete",
7387
+ "rescan",
7388
+ "retention-run"
7389
+ ]);
7390
+ /** Why the operation ran. */
7391
+ var OpsLogReasonSchema = _enum([
7392
+ "retention",
7393
+ "quota",
7394
+ "manual",
7395
+ "operator"
7396
+ ]);
7397
+ /** One audit row, shared verbatim by both domains. */
7398
+ var OpsLogEntrySchema = object({
7399
+ /** Unique row id. */
7400
+ id: string(),
7401
+ /** Epoch ms the operation completed. */
7402
+ at: number(),
7403
+ domain: OpsLogDomainSchema,
7404
+ op: OpsLogOpSchema,
7405
+ reason: OpsLogReasonSchema,
7406
+ /** The camera the op targeted; null for a cluster/global op. */
7407
+ deviceId: number().nullable(),
7408
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7409
+ nodeId: string(),
7410
+ /** Buckets / rows deleted (op-specific unit). */
7411
+ itemsAffected: number(),
7412
+ /** Bytes reclaimed by the op (0 when not measurable). */
7413
+ bytesReclaimed: number(),
7414
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7415
+ detail: string().nullable(),
7416
+ /** Who/what triggered the op. */
7417
+ actor: string()
7418
+ });
7419
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7420
+ var OpsLogQueryInputSchema = object({
7421
+ /** Restrict to a single camera; omit for every row. */
7422
+ deviceId: number().optional(),
7423
+ /** Max rows returned, newest-first. */
7424
+ limit: number().int().min(1).max(1e3).optional()
7425
+ });
7426
+ /**
7371
7427
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7372
7428
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7373
7429
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7611,6 +7667,160 @@ var EncodeProfileSchema = object({
7611
7667
  */
7612
7668
  outputArgs: array(string()).optional()
7613
7669
  });
7670
+ var COCO_TO_MACRO = {
7671
+ mapping: {
7672
+ person: "person",
7673
+ bicycle: "vehicle",
7674
+ car: "vehicle",
7675
+ motorcycle: "vehicle",
7676
+ airplane: "vehicle",
7677
+ bus: "vehicle",
7678
+ train: "vehicle",
7679
+ truck: "vehicle",
7680
+ boat: "vehicle",
7681
+ bird: "animal",
7682
+ cat: "animal",
7683
+ dog: "animal",
7684
+ horse: "animal",
7685
+ sheep: "animal",
7686
+ cow: "animal",
7687
+ elephant: "animal",
7688
+ bear: "animal",
7689
+ zebra: "animal",
7690
+ giraffe: "animal",
7691
+ suitcase: "package",
7692
+ backpack: "package",
7693
+ handbag: "package"
7694
+ },
7695
+ preserveOriginal: false
7696
+ };
7697
+ var AUDIO_MACRO_LABELS = [
7698
+ {
7699
+ id: "speech",
7700
+ name: "Speech",
7701
+ icon: "🗣️"
7702
+ },
7703
+ {
7704
+ id: "scream",
7705
+ name: "Scream / Shout",
7706
+ icon: "😱"
7707
+ },
7708
+ {
7709
+ id: "crying",
7710
+ name: "Crying / Baby",
7711
+ icon: "😢"
7712
+ },
7713
+ {
7714
+ id: "laughter",
7715
+ name: "Laughter",
7716
+ icon: "😂"
7717
+ },
7718
+ {
7719
+ id: "music",
7720
+ name: "Music",
7721
+ icon: "🎵"
7722
+ },
7723
+ {
7724
+ id: "dog",
7725
+ name: "Dog",
7726
+ icon: "🐕"
7727
+ },
7728
+ {
7729
+ id: "cat",
7730
+ name: "Cat",
7731
+ icon: "🐈"
7732
+ },
7733
+ {
7734
+ id: "bird",
7735
+ name: "Bird",
7736
+ icon: "🐦"
7737
+ },
7738
+ {
7739
+ id: "animal",
7740
+ name: "Animal (other)",
7741
+ icon: "🐾"
7742
+ },
7743
+ {
7744
+ id: "alarm",
7745
+ name: "Alarm / Siren",
7746
+ icon: "🚨"
7747
+ },
7748
+ {
7749
+ id: "doorbell",
7750
+ name: "Doorbell / Knock",
7751
+ icon: "🔔"
7752
+ },
7753
+ {
7754
+ id: "glass_breaking",
7755
+ name: "Glass Breaking",
7756
+ icon: "💥"
7757
+ },
7758
+ {
7759
+ id: "gunshot",
7760
+ name: "Gunshot / Explosion",
7761
+ icon: "💣"
7762
+ },
7763
+ {
7764
+ id: "vehicle",
7765
+ name: "Vehicle",
7766
+ icon: "🚗"
7767
+ },
7768
+ {
7769
+ id: "siren",
7770
+ name: "Emergency Siren",
7771
+ icon: "🚑"
7772
+ },
7773
+ {
7774
+ id: "fire",
7775
+ name: "Fire / Smoke",
7776
+ icon: "🔥"
7777
+ },
7778
+ {
7779
+ id: "water",
7780
+ name: "Water",
7781
+ icon: "💧"
7782
+ },
7783
+ {
7784
+ id: "wind",
7785
+ name: "Wind / Weather",
7786
+ icon: "🌬️"
7787
+ },
7788
+ {
7789
+ id: "door",
7790
+ name: "Door",
7791
+ icon: "🚪"
7792
+ },
7793
+ {
7794
+ id: "footsteps",
7795
+ name: "Footsteps",
7796
+ icon: "👣"
7797
+ },
7798
+ {
7799
+ id: "crowd",
7800
+ name: "Crowd / Chatter",
7801
+ icon: "👥"
7802
+ },
7803
+ {
7804
+ id: "telephone",
7805
+ name: "Telephone",
7806
+ icon: "📞"
7807
+ },
7808
+ {
7809
+ id: "engine",
7810
+ name: "Engine / Motor",
7811
+ icon: "⚙️"
7812
+ },
7813
+ {
7814
+ id: "tools",
7815
+ name: "Tools / Construction",
7816
+ icon: "🔨"
7817
+ },
7818
+ {
7819
+ id: "silence",
7820
+ name: "Silence",
7821
+ icon: "🤫"
7822
+ }
7823
+ ];
7614
7824
  var YAMNET_TO_MACRO = {
7615
7825
  mapping: {
7616
7826
  Speech: "speech",
@@ -7877,6 +8087,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7877
8087
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7878
8088
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7879
8089
  /**
8090
+ * Unified event-kind taxonomy — THE single source of truth for
8091
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8092
+ * icon }`.
8093
+ *
8094
+ * This dictionary folds together what used to be scattered across four
8095
+ * copies:
8096
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8097
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8098
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8099
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8100
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8101
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8102
+ *
8103
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8104
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8105
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8106
+ * color or an icon: they read this dictionary (server descriptors carry the
8107
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8108
+ *
8109
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8110
+ */
8111
+ var TAXONOMY_COLORS = {
8112
+ motion: "#f59e0b",
8113
+ audio: "#06b6d4",
8114
+ person: "#22c55e",
8115
+ vehicle: "#3b82f6",
8116
+ animal: "#f97316",
8117
+ package: "#a855f7",
8118
+ sensor: "#8b5cf6",
8119
+ control: "#10b981",
8120
+ genericDetection: "#64748b"
8121
+ };
8122
+ var DETECTION_SUB_COLORS = {
8123
+ car: "#f59e0b",
8124
+ truck: "#d97706",
8125
+ bus: "#b45309",
8126
+ motorcycle: "#eab308",
8127
+ bicycle: "#ca8a04",
8128
+ airplane: "#60a5fa",
8129
+ boat: "#2563eb",
8130
+ train: "#1d4ed8",
8131
+ bird: "#14b8a6",
8132
+ dog: "#84cc16",
8133
+ cat: "#f97316",
8134
+ horse: "#a16207",
8135
+ sheep: "#a3a3a3",
8136
+ cow: "#78716c",
8137
+ elephant: "#6b7280",
8138
+ bear: "#7c2d12",
8139
+ zebra: "#404040",
8140
+ giraffe: "#d4a373"
8141
+ };
8142
+ function titleCase(id) {
8143
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8144
+ }
8145
+ var entries = /* @__PURE__ */ new Map();
8146
+ function macro(kind, category, color, iconId, label) {
8147
+ entries.set(kind, {
8148
+ kind,
8149
+ parentKind: null,
8150
+ level: "macro",
8151
+ category,
8152
+ color,
8153
+ iconId,
8154
+ labelKey: `eventKind.${kind}`,
8155
+ label
8156
+ });
8157
+ }
8158
+ function sub(kind, parentKind, category, color, iconId, label) {
8159
+ entries.set(kind, {
8160
+ kind,
8161
+ parentKind,
8162
+ level: "sub",
8163
+ category,
8164
+ color,
8165
+ iconId,
8166
+ labelKey: `eventKind.${kind}`,
8167
+ label
8168
+ });
8169
+ }
8170
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8171
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8172
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8173
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8174
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8175
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8176
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8177
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8178
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8179
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8180
+ if (entries.has(cocoClass)) continue;
8181
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8182
+ }
8183
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8184
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8185
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8186
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8187
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8188
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8189
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8190
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8191
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8192
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8193
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8194
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8195
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8196
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8197
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8198
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8199
+ sub("button", "control", "control", "#10b981", "button", "Button");
8200
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8201
+ for (const l of AUDIO_MACRO_LABELS) {
8202
+ const kind = `audio-${l.id}`;
8203
+ if (entries.has(kind)) continue;
8204
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8205
+ }
8206
+ /** The complete taxonomy dictionary, keyed by kind. */
8207
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8208
+ /**
7880
8209
  * Error types for the safe expression engine. Two distinct classes so callers
7881
8210
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7882
8211
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15894,17 +16223,30 @@ var EventKindCategorySchema = _enum([
15894
16223
  "audio",
15895
16224
  "detection",
15896
16225
  "sensor",
16226
+ "control",
15897
16227
  "custom",
15898
16228
  "package"
15899
16229
  ]);
16230
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16231
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15900
16232
  var EventKindDescriptorSchema = object({
15901
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16233
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15902
16234
  kind: string(),
16235
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16236
+ labelKey: string(),
16237
+ /** English fallback label (kept for clients that don't translate). */
15903
16238
  label: string(),
15904
16239
  /** Hex color for timeline/legend rendering. */
15905
16240
  color: string(),
16241
+ /** Dictionary id → lucide component on the UI side. */
16242
+ iconId: string(),
16243
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15906
16244
  icon: EventKindIconSchema,
15907
16245
  category: EventKindCategorySchema,
16246
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16247
+ parentKind: string().nullable(),
16248
+ /** Derived from `parentKind`, explicit for the client tree. */
16249
+ level: EventKindLevelSchema,
15908
16250
  /** Which cap + device contributes this kind. For built-ins the camera
15909
16251
  * itself; for sensor kinds the LINKED source device. */
15910
16252
  source: object({
@@ -15977,11 +16319,21 @@ var TrackAudioLabelSchema = object({
15977
16319
  firstAt: number(),
15978
16320
  lastAt: number()
15979
16321
  });
16322
+ /**
16323
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16324
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16325
+ * linked sensor/control state change (no positions; carries a snapshot). The
16326
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16327
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16328
+ */
16329
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15980
16330
  var TrackSchema = object({
15981
16331
  trackId: string(),
15982
16332
  deviceId: number(),
15983
16333
  className: string(),
15984
16334
  label: string().optional(),
16335
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16336
+ source: TrackSourceSchema.optional(),
15985
16337
  firstSeen: number(),
15986
16338
  lastSeen: number(),
15987
16339
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16236,6 +16588,26 @@ var TrackCascadeCountsSchema = object({
16236
16588
  /** Per-track CLIP search vectors removed (best-effort). */
16237
16589
  embeddings: number().int()
16238
16590
  });
16591
+ /** Event-store footprint for one camera. */
16592
+ var EventStoreDeviceFootprintSchema = object({
16593
+ deviceId: number(),
16594
+ /** Persisted event rows (motion + object + audio) for the camera. */
16595
+ rows: number().int(),
16596
+ /** Event-owned media bytes on disk for the camera. */
16597
+ bytes: number().int()
16598
+ });
16599
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16600
+ var EventStoreFootprintSchema = object({
16601
+ totalRows: number().int(),
16602
+ totalBytes: number().int(),
16603
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16604
+ });
16605
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16606
+ var EventPruneCountsSchema = object({
16607
+ motion: number().int(),
16608
+ object: number().int(),
16609
+ audio: number().int()
16610
+ });
16239
16611
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16240
16612
  deviceId: number(),
16241
16613
  trackId: string()
@@ -16299,6 +16671,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16299
16671
  }), {
16300
16672
  kind: "mutation",
16301
16673
  auth: "admin"
16674
+ }), method(object({}), EventStoreFootprintSchema, {
16675
+ kind: "query",
16676
+ auth: "admin"
16677
+ }), method(object({
16678
+ olderThanMs: number(),
16679
+ reason: OpsLogReasonSchema.optional()
16680
+ }), EventPruneCountsSchema, {
16681
+ kind: "mutation",
16682
+ auth: "admin"
16683
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16684
+ kind: "mutation",
16685
+ auth: "admin"
16686
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16687
+ kind: "query",
16688
+ auth: "admin"
16302
16689
  }), method(object({
16303
16690
  eventId: string(),
16304
16691
  kind: MediaFileKindEnum.optional()
@@ -16323,6 +16710,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16323
16710
  eventId: string(),
16324
16711
  timestamp: number()
16325
16712
  });
16713
+ /**
16714
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16715
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16716
+ * caps into per-camera event-kind descriptors.
16717
+ *
16718
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16719
+ * is NOT duplicated here — every entry is derived from the single
16720
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16721
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16722
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16723
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16724
+ * eventful cap is missing.
16725
+ */
16726
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16727
+ var LEGACY_ICON = {
16728
+ motion: "motion",
16729
+ audio: "audio",
16730
+ person: "person",
16731
+ vehicle: "vehicle",
16732
+ animal: "animal",
16733
+ package: "package",
16734
+ door: "door",
16735
+ pir: "pir",
16736
+ smoke: "smoke",
16737
+ water: "water",
16738
+ button: "button",
16739
+ generic: "generic",
16740
+ gas: "smoke",
16741
+ vibration: "generic",
16742
+ tamper: "generic",
16743
+ presence: "person",
16744
+ lock: "generic",
16745
+ siren: "generic",
16746
+ switch: "generic",
16747
+ doorbell: "button"
16748
+ };
16749
+ function legacyIcon(iconId) {
16750
+ return LEGACY_ICON[iconId] ?? "generic";
16751
+ }
16752
+ /**
16753
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16754
+ * The anti-drift guard cross-checks this against the eventful caps declared
16755
+ * in `packages/types/src/capabilities/*.cap.ts`.
16756
+ */
16757
+ var CAP_TO_KIND = {
16758
+ contact: "contact",
16759
+ motion: "motion-sensor",
16760
+ smoke: "smoke",
16761
+ flood: "flood",
16762
+ gas: "gas",
16763
+ "carbon-monoxide": "carbon-monoxide",
16764
+ vibration: "vibration",
16765
+ tamper: "tamper",
16766
+ presence: "presence",
16767
+ "enum-sensor": "enum-sensor",
16768
+ "event-emitter": "device-event",
16769
+ "lock-control": "lock",
16770
+ switch: "switch",
16771
+ button: "button",
16772
+ doorbell: "doorbell"
16773
+ };
16774
+ function buildDescriptor(capName, kind) {
16775
+ const t = EVENT_TAXONOMY[kind];
16776
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16777
+ return {
16778
+ ...t,
16779
+ icon: legacyIcon(t.iconId)
16780
+ };
16781
+ }
16782
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16326
16783
  var CameraPipelineConfigSchema = object({
16327
16784
  engine: PipelineEngineChoiceSchema.optional(),
16328
16785
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19532,13 +19989,29 @@ method(object({
19532
19989
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19533
19990
  kind: "mutation",
19534
19991
  auth: "admin"
19535
- }), method(object({ deviceId: number() }), object({
19992
+ }), method(object({
19993
+ deviceId: number(),
19994
+ reason: OpsLogReasonSchema.optional()
19995
+ }), object({
19536
19996
  floorMs: number().nullable(),
19537
19997
  deletedBuckets: number().int(),
19538
19998
  reclaimedBytes: number().int()
19539
19999
  }), {
19540
20000
  kind: "mutation",
19541
20001
  auth: "admin"
20002
+ }), method(object({
20003
+ deviceId: number(),
20004
+ fromMs: number().optional(),
20005
+ toMs: number().optional()
20006
+ }), object({
20007
+ deletedBuckets: number().int(),
20008
+ reclaimedBytes: number().int()
20009
+ }), {
20010
+ kind: "mutation",
20011
+ auth: "admin"
20012
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20013
+ kind: "query",
20014
+ auth: "admin"
19542
20015
  });
19543
20016
  /**
19544
20017
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22660,6 +23133,12 @@ Object.freeze({
22660
23133
  addonId: null,
22661
23134
  access: "delete"
22662
23135
  },
23136
+ "pipelineAnalytics.deleteDeviceEvents": {
23137
+ capName: "pipeline-analytics",
23138
+ capScope: "device",
23139
+ addonId: null,
23140
+ access: "delete"
23141
+ },
22663
23142
  "pipelineAnalytics.deleteTracks": {
22664
23143
  capName: "pipeline-analytics",
22665
23144
  capScope: "device",
@@ -22690,6 +23169,12 @@ Object.freeze({
22690
23169
  addonId: null,
22691
23170
  access: "view"
22692
23171
  },
23172
+ "pipelineAnalytics.getEventStoreFootprint": {
23173
+ capName: "pipeline-analytics",
23174
+ capScope: "device",
23175
+ addonId: null,
23176
+ access: "view"
23177
+ },
22693
23178
  "pipelineAnalytics.getKeyEvents": {
22694
23179
  capName: "pipeline-analytics",
22695
23180
  capScope: "device",
@@ -22732,6 +23217,12 @@ Object.freeze({
22732
23217
  addonId: null,
22733
23218
  access: "view"
22734
23219
  },
23220
+ "pipelineAnalytics.listOpsLog": {
23221
+ capName: "pipeline-analytics",
23222
+ capScope: "device",
23223
+ addonId: null,
23224
+ access: "view"
23225
+ },
22735
23226
  "pipelineAnalytics.listRecentTracks": {
22736
23227
  capName: "pipeline-analytics",
22737
23228
  capScope: "device",
@@ -22744,6 +23235,12 @@ Object.freeze({
22744
23235
  addonId: null,
22745
23236
  access: "view"
22746
23237
  },
23238
+ "pipelineAnalytics.pruneEvents": {
23239
+ capName: "pipeline-analytics",
23240
+ capScope: "device",
23241
+ addonId: null,
23242
+ access: "create"
23243
+ },
22747
23244
  "pipelineAnalytics.pruneEventsBefore": {
22748
23245
  capName: "pipeline-analytics",
22749
23246
  capScope: "device",
@@ -23506,6 +24003,12 @@ Object.freeze({
23506
24003
  addonId: null,
23507
24004
  access: "create"
23508
24005
  },
24006
+ "recording.deleteFootprint": {
24007
+ capName: "recording",
24008
+ capScope: "system",
24009
+ addonId: null,
24010
+ access: "delete"
24011
+ },
23509
24012
  "recording.getAvailability": {
23510
24013
  capName: "recording",
23511
24014
  capScope: "system",
@@ -23536,6 +24039,12 @@ Object.freeze({
23536
24039
  addonId: null,
23537
24040
  access: "view"
23538
24041
  },
24042
+ "recording.listOpsLog": {
24043
+ capName: "recording",
24044
+ capScope: "system",
24045
+ addonId: null,
24046
+ access: "view"
24047
+ },
23539
24048
  "recording.locateSegment": {
23540
24049
  capName: "recording",
23541
24050
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-mqtt-broker",
3
- "version": "1.1.26",
3
+ "version": "1.1.28",
4
4
  "description": "MQTT broker registry addon for CamStack — manages external broker entries + an optional embedded aedes broker. Consumers spin up their own `mqtt.js` clients via the `mqtt-broker` cap.",
5
5
  "keywords": [
6
6
  "camstack",