@camstack/addon-static-turn 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.
@@ -7328,6 +7328,62 @@ var RecordingConfigSchema = object({
7328
7328
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7329
7329
  });
7330
7330
  /**
7331
+ * Ops-log — the durable, append-only operations audit shared by the
7332
+ * recordings and events management surfaces.
7333
+ *
7334
+ * ONE row shape is reused for both domains so a single "Activity" view can
7335
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7336
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7337
+ * management operation, WHY it ran (reason), and its measurable effect
7338
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7339
+ * never fail the operation it records.
7340
+ */
7341
+ /** Which management domain the operation belongs to. */
7342
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7343
+ /** The kind of management operation performed. */
7344
+ var OpsLogOpSchema = _enum([
7345
+ "prune",
7346
+ "manual-delete",
7347
+ "rescan",
7348
+ "retention-run"
7349
+ ]);
7350
+ /** Why the operation ran. */
7351
+ var OpsLogReasonSchema = _enum([
7352
+ "retention",
7353
+ "quota",
7354
+ "manual",
7355
+ "operator"
7356
+ ]);
7357
+ /** One audit row, shared verbatim by both domains. */
7358
+ var OpsLogEntrySchema = object({
7359
+ /** Unique row id. */
7360
+ id: string(),
7361
+ /** Epoch ms the operation completed. */
7362
+ at: number(),
7363
+ domain: OpsLogDomainSchema,
7364
+ op: OpsLogOpSchema,
7365
+ reason: OpsLogReasonSchema,
7366
+ /** The camera the op targeted; null for a cluster/global op. */
7367
+ deviceId: number().nullable(),
7368
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7369
+ nodeId: string(),
7370
+ /** Buckets / rows deleted (op-specific unit). */
7371
+ itemsAffected: number(),
7372
+ /** Bytes reclaimed by the op (0 when not measurable). */
7373
+ bytesReclaimed: number(),
7374
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7375
+ detail: string().nullable(),
7376
+ /** Who/what triggered the op. */
7377
+ actor: string()
7378
+ });
7379
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7380
+ var OpsLogQueryInputSchema = object({
7381
+ /** Restrict to a single camera; omit for every row. */
7382
+ deviceId: number().optional(),
7383
+ /** Max rows returned, newest-first. */
7384
+ limit: number().int().min(1).max(1e3).optional()
7385
+ });
7386
+ /**
7331
7387
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7332
7388
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7333
7389
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7571,6 +7627,160 @@ var EncodeProfileSchema = object({
7571
7627
  */
7572
7628
  outputArgs: array(string()).optional()
7573
7629
  });
7630
+ var COCO_TO_MACRO = {
7631
+ mapping: {
7632
+ person: "person",
7633
+ bicycle: "vehicle",
7634
+ car: "vehicle",
7635
+ motorcycle: "vehicle",
7636
+ airplane: "vehicle",
7637
+ bus: "vehicle",
7638
+ train: "vehicle",
7639
+ truck: "vehicle",
7640
+ boat: "vehicle",
7641
+ bird: "animal",
7642
+ cat: "animal",
7643
+ dog: "animal",
7644
+ horse: "animal",
7645
+ sheep: "animal",
7646
+ cow: "animal",
7647
+ elephant: "animal",
7648
+ bear: "animal",
7649
+ zebra: "animal",
7650
+ giraffe: "animal",
7651
+ suitcase: "package",
7652
+ backpack: "package",
7653
+ handbag: "package"
7654
+ },
7655
+ preserveOriginal: false
7656
+ };
7657
+ var AUDIO_MACRO_LABELS = [
7658
+ {
7659
+ id: "speech",
7660
+ name: "Speech",
7661
+ icon: "🗣️"
7662
+ },
7663
+ {
7664
+ id: "scream",
7665
+ name: "Scream / Shout",
7666
+ icon: "😱"
7667
+ },
7668
+ {
7669
+ id: "crying",
7670
+ name: "Crying / Baby",
7671
+ icon: "😢"
7672
+ },
7673
+ {
7674
+ id: "laughter",
7675
+ name: "Laughter",
7676
+ icon: "😂"
7677
+ },
7678
+ {
7679
+ id: "music",
7680
+ name: "Music",
7681
+ icon: "🎵"
7682
+ },
7683
+ {
7684
+ id: "dog",
7685
+ name: "Dog",
7686
+ icon: "🐕"
7687
+ },
7688
+ {
7689
+ id: "cat",
7690
+ name: "Cat",
7691
+ icon: "🐈"
7692
+ },
7693
+ {
7694
+ id: "bird",
7695
+ name: "Bird",
7696
+ icon: "🐦"
7697
+ },
7698
+ {
7699
+ id: "animal",
7700
+ name: "Animal (other)",
7701
+ icon: "🐾"
7702
+ },
7703
+ {
7704
+ id: "alarm",
7705
+ name: "Alarm / Siren",
7706
+ icon: "🚨"
7707
+ },
7708
+ {
7709
+ id: "doorbell",
7710
+ name: "Doorbell / Knock",
7711
+ icon: "🔔"
7712
+ },
7713
+ {
7714
+ id: "glass_breaking",
7715
+ name: "Glass Breaking",
7716
+ icon: "💥"
7717
+ },
7718
+ {
7719
+ id: "gunshot",
7720
+ name: "Gunshot / Explosion",
7721
+ icon: "💣"
7722
+ },
7723
+ {
7724
+ id: "vehicle",
7725
+ name: "Vehicle",
7726
+ icon: "🚗"
7727
+ },
7728
+ {
7729
+ id: "siren",
7730
+ name: "Emergency Siren",
7731
+ icon: "🚑"
7732
+ },
7733
+ {
7734
+ id: "fire",
7735
+ name: "Fire / Smoke",
7736
+ icon: "🔥"
7737
+ },
7738
+ {
7739
+ id: "water",
7740
+ name: "Water",
7741
+ icon: "💧"
7742
+ },
7743
+ {
7744
+ id: "wind",
7745
+ name: "Wind / Weather",
7746
+ icon: "🌬️"
7747
+ },
7748
+ {
7749
+ id: "door",
7750
+ name: "Door",
7751
+ icon: "🚪"
7752
+ },
7753
+ {
7754
+ id: "footsteps",
7755
+ name: "Footsteps",
7756
+ icon: "👣"
7757
+ },
7758
+ {
7759
+ id: "crowd",
7760
+ name: "Crowd / Chatter",
7761
+ icon: "👥"
7762
+ },
7763
+ {
7764
+ id: "telephone",
7765
+ name: "Telephone",
7766
+ icon: "📞"
7767
+ },
7768
+ {
7769
+ id: "engine",
7770
+ name: "Engine / Motor",
7771
+ icon: "⚙️"
7772
+ },
7773
+ {
7774
+ id: "tools",
7775
+ name: "Tools / Construction",
7776
+ icon: "🔨"
7777
+ },
7778
+ {
7779
+ id: "silence",
7780
+ name: "Silence",
7781
+ icon: "🤫"
7782
+ }
7783
+ ];
7574
7784
  var YAMNET_TO_MACRO = {
7575
7785
  mapping: {
7576
7786
  Speech: "speech",
@@ -7837,6 +8047,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7837
8047
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7838
8048
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7839
8049
  /**
8050
+ * Unified event-kind taxonomy — THE single source of truth for
8051
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8052
+ * icon }`.
8053
+ *
8054
+ * This dictionary folds together what used to be scattered across four
8055
+ * copies:
8056
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8057
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8058
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8059
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8060
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8061
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8062
+ *
8063
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8064
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8065
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8066
+ * color or an icon: they read this dictionary (server descriptors carry the
8067
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8068
+ *
8069
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8070
+ */
8071
+ var TAXONOMY_COLORS = {
8072
+ motion: "#f59e0b",
8073
+ audio: "#06b6d4",
8074
+ person: "#22c55e",
8075
+ vehicle: "#3b82f6",
8076
+ animal: "#f97316",
8077
+ package: "#a855f7",
8078
+ sensor: "#8b5cf6",
8079
+ control: "#10b981",
8080
+ genericDetection: "#64748b"
8081
+ };
8082
+ var DETECTION_SUB_COLORS = {
8083
+ car: "#f59e0b",
8084
+ truck: "#d97706",
8085
+ bus: "#b45309",
8086
+ motorcycle: "#eab308",
8087
+ bicycle: "#ca8a04",
8088
+ airplane: "#60a5fa",
8089
+ boat: "#2563eb",
8090
+ train: "#1d4ed8",
8091
+ bird: "#14b8a6",
8092
+ dog: "#84cc16",
8093
+ cat: "#f97316",
8094
+ horse: "#a16207",
8095
+ sheep: "#a3a3a3",
8096
+ cow: "#78716c",
8097
+ elephant: "#6b7280",
8098
+ bear: "#7c2d12",
8099
+ zebra: "#404040",
8100
+ giraffe: "#d4a373"
8101
+ };
8102
+ function titleCase(id) {
8103
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8104
+ }
8105
+ var entries = /* @__PURE__ */ new Map();
8106
+ function macro(kind, category, color, iconId, label) {
8107
+ entries.set(kind, {
8108
+ kind,
8109
+ parentKind: null,
8110
+ level: "macro",
8111
+ category,
8112
+ color,
8113
+ iconId,
8114
+ labelKey: `eventKind.${kind}`,
8115
+ label
8116
+ });
8117
+ }
8118
+ function sub(kind, parentKind, category, color, iconId, label) {
8119
+ entries.set(kind, {
8120
+ kind,
8121
+ parentKind,
8122
+ level: "sub",
8123
+ category,
8124
+ color,
8125
+ iconId,
8126
+ labelKey: `eventKind.${kind}`,
8127
+ label
8128
+ });
8129
+ }
8130
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8131
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8132
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8133
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8134
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8135
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8136
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8137
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8138
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8139
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8140
+ if (entries.has(cocoClass)) continue;
8141
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8142
+ }
8143
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8144
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8145
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8146
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8147
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8148
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8149
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8150
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8151
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8152
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8153
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8154
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8155
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8156
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8157
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8158
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8159
+ sub("button", "control", "control", "#10b981", "button", "Button");
8160
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8161
+ for (const l of AUDIO_MACRO_LABELS) {
8162
+ const kind = `audio-${l.id}`;
8163
+ if (entries.has(kind)) continue;
8164
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8165
+ }
8166
+ /** The complete taxonomy dictionary, keyed by kind. */
8167
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8168
+ /**
7840
8169
  * Error types for the safe expression engine. Two distinct classes so callers
7841
8170
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7842
8171
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15786,17 +16115,30 @@ var EventKindCategorySchema = _enum([
15786
16115
  "audio",
15787
16116
  "detection",
15788
16117
  "sensor",
16118
+ "control",
15789
16119
  "custom",
15790
16120
  "package"
15791
16121
  ]);
16122
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16123
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15792
16124
  var EventKindDescriptorSchema = object({
15793
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16125
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15794
16126
  kind: string(),
16127
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16128
+ labelKey: string(),
16129
+ /** English fallback label (kept for clients that don't translate). */
15795
16130
  label: string(),
15796
16131
  /** Hex color for timeline/legend rendering. */
15797
16132
  color: string(),
16133
+ /** Dictionary id → lucide component on the UI side. */
16134
+ iconId: string(),
16135
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15798
16136
  icon: EventKindIconSchema,
15799
16137
  category: EventKindCategorySchema,
16138
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16139
+ parentKind: string().nullable(),
16140
+ /** Derived from `parentKind`, explicit for the client tree. */
16141
+ level: EventKindLevelSchema,
15800
16142
  /** Which cap + device contributes this kind. For built-ins the camera
15801
16143
  * itself; for sensor kinds the LINKED source device. */
15802
16144
  source: object({
@@ -15869,11 +16211,21 @@ var TrackAudioLabelSchema = object({
15869
16211
  firstAt: number(),
15870
16212
  lastAt: number()
15871
16213
  });
16214
+ /**
16215
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16216
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16217
+ * linked sensor/control state change (no positions; carries a snapshot). The
16218
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16219
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16220
+ */
16221
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15872
16222
  var TrackSchema = object({
15873
16223
  trackId: string(),
15874
16224
  deviceId: number(),
15875
16225
  className: string(),
15876
16226
  label: string().optional(),
16227
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16228
+ source: TrackSourceSchema.optional(),
15877
16229
  firstSeen: number(),
15878
16230
  lastSeen: number(),
15879
16231
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16128,6 +16480,26 @@ var TrackCascadeCountsSchema = object({
16128
16480
  /** Per-track CLIP search vectors removed (best-effort). */
16129
16481
  embeddings: number().int()
16130
16482
  });
16483
+ /** Event-store footprint for one camera. */
16484
+ var EventStoreDeviceFootprintSchema = object({
16485
+ deviceId: number(),
16486
+ /** Persisted event rows (motion + object + audio) for the camera. */
16487
+ rows: number().int(),
16488
+ /** Event-owned media bytes on disk for the camera. */
16489
+ bytes: number().int()
16490
+ });
16491
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16492
+ var EventStoreFootprintSchema = object({
16493
+ totalRows: number().int(),
16494
+ totalBytes: number().int(),
16495
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16496
+ });
16497
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16498
+ var EventPruneCountsSchema = object({
16499
+ motion: number().int(),
16500
+ object: number().int(),
16501
+ audio: number().int()
16502
+ });
16131
16503
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16132
16504
  deviceId: number(),
16133
16505
  trackId: string()
@@ -16191,6 +16563,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16191
16563
  }), {
16192
16564
  kind: "mutation",
16193
16565
  auth: "admin"
16566
+ }), method(object({}), EventStoreFootprintSchema, {
16567
+ kind: "query",
16568
+ auth: "admin"
16569
+ }), method(object({
16570
+ olderThanMs: number(),
16571
+ reason: OpsLogReasonSchema.optional()
16572
+ }), EventPruneCountsSchema, {
16573
+ kind: "mutation",
16574
+ auth: "admin"
16575
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16576
+ kind: "mutation",
16577
+ auth: "admin"
16578
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16579
+ kind: "query",
16580
+ auth: "admin"
16194
16581
  }), method(object({
16195
16582
  eventId: string(),
16196
16583
  kind: MediaFileKindEnum.optional()
@@ -16215,6 +16602,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16215
16602
  eventId: string(),
16216
16603
  timestamp: number()
16217
16604
  });
16605
+ /**
16606
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16607
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16608
+ * caps into per-camera event-kind descriptors.
16609
+ *
16610
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16611
+ * is NOT duplicated here — every entry is derived from the single
16612
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16613
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16614
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16615
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16616
+ * eventful cap is missing.
16617
+ */
16618
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16619
+ var LEGACY_ICON = {
16620
+ motion: "motion",
16621
+ audio: "audio",
16622
+ person: "person",
16623
+ vehicle: "vehicle",
16624
+ animal: "animal",
16625
+ package: "package",
16626
+ door: "door",
16627
+ pir: "pir",
16628
+ smoke: "smoke",
16629
+ water: "water",
16630
+ button: "button",
16631
+ generic: "generic",
16632
+ gas: "smoke",
16633
+ vibration: "generic",
16634
+ tamper: "generic",
16635
+ presence: "person",
16636
+ lock: "generic",
16637
+ siren: "generic",
16638
+ switch: "generic",
16639
+ doorbell: "button"
16640
+ };
16641
+ function legacyIcon(iconId) {
16642
+ return LEGACY_ICON[iconId] ?? "generic";
16643
+ }
16644
+ /**
16645
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16646
+ * The anti-drift guard cross-checks this against the eventful caps declared
16647
+ * in `packages/types/src/capabilities/*.cap.ts`.
16648
+ */
16649
+ var CAP_TO_KIND = {
16650
+ contact: "contact",
16651
+ motion: "motion-sensor",
16652
+ smoke: "smoke",
16653
+ flood: "flood",
16654
+ gas: "gas",
16655
+ "carbon-monoxide": "carbon-monoxide",
16656
+ vibration: "vibration",
16657
+ tamper: "tamper",
16658
+ presence: "presence",
16659
+ "enum-sensor": "enum-sensor",
16660
+ "event-emitter": "device-event",
16661
+ "lock-control": "lock",
16662
+ switch: "switch",
16663
+ button: "button",
16664
+ doorbell: "doorbell"
16665
+ };
16666
+ function buildDescriptor(capName, kind) {
16667
+ const t = EVENT_TAXONOMY[kind];
16668
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16669
+ return {
16670
+ ...t,
16671
+ icon: legacyIcon(t.iconId)
16672
+ };
16673
+ }
16674
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16218
16675
  var CameraPipelineConfigSchema = object({
16219
16676
  engine: PipelineEngineChoiceSchema.optional(),
16220
16677
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19447,13 +19904,29 @@ method(object({
19447
19904
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19448
19905
  kind: "mutation",
19449
19906
  auth: "admin"
19450
- }), method(object({ deviceId: number() }), object({
19907
+ }), method(object({
19908
+ deviceId: number(),
19909
+ reason: OpsLogReasonSchema.optional()
19910
+ }), object({
19451
19911
  floorMs: number().nullable(),
19452
19912
  deletedBuckets: number().int(),
19453
19913
  reclaimedBytes: number().int()
19454
19914
  }), {
19455
19915
  kind: "mutation",
19456
19916
  auth: "admin"
19917
+ }), method(object({
19918
+ deviceId: number(),
19919
+ fromMs: number().optional(),
19920
+ toMs: number().optional()
19921
+ }), object({
19922
+ deletedBuckets: number().int(),
19923
+ reclaimedBytes: number().int()
19924
+ }), {
19925
+ kind: "mutation",
19926
+ auth: "admin"
19927
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19928
+ kind: "query",
19929
+ auth: "admin"
19457
19930
  });
19458
19931
  /**
19459
19932
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22575,6 +23048,12 @@ Object.freeze({
22575
23048
  addonId: null,
22576
23049
  access: "delete"
22577
23050
  },
23051
+ "pipelineAnalytics.deleteDeviceEvents": {
23052
+ capName: "pipeline-analytics",
23053
+ capScope: "device",
23054
+ addonId: null,
23055
+ access: "delete"
23056
+ },
22578
23057
  "pipelineAnalytics.deleteTracks": {
22579
23058
  capName: "pipeline-analytics",
22580
23059
  capScope: "device",
@@ -22605,6 +23084,12 @@ Object.freeze({
22605
23084
  addonId: null,
22606
23085
  access: "view"
22607
23086
  },
23087
+ "pipelineAnalytics.getEventStoreFootprint": {
23088
+ capName: "pipeline-analytics",
23089
+ capScope: "device",
23090
+ addonId: null,
23091
+ access: "view"
23092
+ },
22608
23093
  "pipelineAnalytics.getKeyEvents": {
22609
23094
  capName: "pipeline-analytics",
22610
23095
  capScope: "device",
@@ -22647,6 +23132,12 @@ Object.freeze({
22647
23132
  addonId: null,
22648
23133
  access: "view"
22649
23134
  },
23135
+ "pipelineAnalytics.listOpsLog": {
23136
+ capName: "pipeline-analytics",
23137
+ capScope: "device",
23138
+ addonId: null,
23139
+ access: "view"
23140
+ },
22650
23141
  "pipelineAnalytics.listRecentTracks": {
22651
23142
  capName: "pipeline-analytics",
22652
23143
  capScope: "device",
@@ -22659,6 +23150,12 @@ Object.freeze({
22659
23150
  addonId: null,
22660
23151
  access: "view"
22661
23152
  },
23153
+ "pipelineAnalytics.pruneEvents": {
23154
+ capName: "pipeline-analytics",
23155
+ capScope: "device",
23156
+ addonId: null,
23157
+ access: "create"
23158
+ },
22662
23159
  "pipelineAnalytics.pruneEventsBefore": {
22663
23160
  capName: "pipeline-analytics",
22664
23161
  capScope: "device",
@@ -23421,6 +23918,12 @@ Object.freeze({
23421
23918
  addonId: null,
23422
23919
  access: "create"
23423
23920
  },
23921
+ "recording.deleteFootprint": {
23922
+ capName: "recording",
23923
+ capScope: "system",
23924
+ addonId: null,
23925
+ access: "delete"
23926
+ },
23424
23927
  "recording.getAvailability": {
23425
23928
  capName: "recording",
23426
23929
  capScope: "system",
@@ -23451,6 +23954,12 @@ Object.freeze({
23451
23954
  addonId: null,
23452
23955
  access: "view"
23453
23956
  },
23957
+ "recording.listOpsLog": {
23958
+ capName: "recording",
23959
+ capScope: "system",
23960
+ addonId: null,
23961
+ access: "view"
23962
+ },
23454
23963
  "recording.locateSegment": {
23455
23964
  capName: "recording",
23456
23965
  capScope: "system",
@@ -7327,6 +7327,62 @@ var RecordingConfigSchema = object({
7327
7327
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7328
7328
  });
7329
7329
  /**
7330
+ * Ops-log — the durable, append-only operations audit shared by the
7331
+ * recordings and events management surfaces.
7332
+ *
7333
+ * ONE row shape is reused for both domains so a single "Activity" view can
7334
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7335
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7336
+ * management operation, WHY it ran (reason), and its measurable effect
7337
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7338
+ * never fail the operation it records.
7339
+ */
7340
+ /** Which management domain the operation belongs to. */
7341
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7342
+ /** The kind of management operation performed. */
7343
+ var OpsLogOpSchema = _enum([
7344
+ "prune",
7345
+ "manual-delete",
7346
+ "rescan",
7347
+ "retention-run"
7348
+ ]);
7349
+ /** Why the operation ran. */
7350
+ var OpsLogReasonSchema = _enum([
7351
+ "retention",
7352
+ "quota",
7353
+ "manual",
7354
+ "operator"
7355
+ ]);
7356
+ /** One audit row, shared verbatim by both domains. */
7357
+ var OpsLogEntrySchema = object({
7358
+ /** Unique row id. */
7359
+ id: string(),
7360
+ /** Epoch ms the operation completed. */
7361
+ at: number(),
7362
+ domain: OpsLogDomainSchema,
7363
+ op: OpsLogOpSchema,
7364
+ reason: OpsLogReasonSchema,
7365
+ /** The camera the op targeted; null for a cluster/global op. */
7366
+ deviceId: number().nullable(),
7367
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7368
+ nodeId: string(),
7369
+ /** Buckets / rows deleted (op-specific unit). */
7370
+ itemsAffected: number(),
7371
+ /** Bytes reclaimed by the op (0 when not measurable). */
7372
+ bytesReclaimed: number(),
7373
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7374
+ detail: string().nullable(),
7375
+ /** Who/what triggered the op. */
7376
+ actor: string()
7377
+ });
7378
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7379
+ var OpsLogQueryInputSchema = object({
7380
+ /** Restrict to a single camera; omit for every row. */
7381
+ deviceId: number().optional(),
7382
+ /** Max rows returned, newest-first. */
7383
+ limit: number().int().min(1).max(1e3).optional()
7384
+ });
7385
+ /**
7330
7386
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7331
7387
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7332
7388
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7570,6 +7626,160 @@ var EncodeProfileSchema = object({
7570
7626
  */
7571
7627
  outputArgs: array(string()).optional()
7572
7628
  });
7629
+ var COCO_TO_MACRO = {
7630
+ mapping: {
7631
+ person: "person",
7632
+ bicycle: "vehicle",
7633
+ car: "vehicle",
7634
+ motorcycle: "vehicle",
7635
+ airplane: "vehicle",
7636
+ bus: "vehicle",
7637
+ train: "vehicle",
7638
+ truck: "vehicle",
7639
+ boat: "vehicle",
7640
+ bird: "animal",
7641
+ cat: "animal",
7642
+ dog: "animal",
7643
+ horse: "animal",
7644
+ sheep: "animal",
7645
+ cow: "animal",
7646
+ elephant: "animal",
7647
+ bear: "animal",
7648
+ zebra: "animal",
7649
+ giraffe: "animal",
7650
+ suitcase: "package",
7651
+ backpack: "package",
7652
+ handbag: "package"
7653
+ },
7654
+ preserveOriginal: false
7655
+ };
7656
+ var AUDIO_MACRO_LABELS = [
7657
+ {
7658
+ id: "speech",
7659
+ name: "Speech",
7660
+ icon: "🗣️"
7661
+ },
7662
+ {
7663
+ id: "scream",
7664
+ name: "Scream / Shout",
7665
+ icon: "😱"
7666
+ },
7667
+ {
7668
+ id: "crying",
7669
+ name: "Crying / Baby",
7670
+ icon: "😢"
7671
+ },
7672
+ {
7673
+ id: "laughter",
7674
+ name: "Laughter",
7675
+ icon: "😂"
7676
+ },
7677
+ {
7678
+ id: "music",
7679
+ name: "Music",
7680
+ icon: "🎵"
7681
+ },
7682
+ {
7683
+ id: "dog",
7684
+ name: "Dog",
7685
+ icon: "🐕"
7686
+ },
7687
+ {
7688
+ id: "cat",
7689
+ name: "Cat",
7690
+ icon: "🐈"
7691
+ },
7692
+ {
7693
+ id: "bird",
7694
+ name: "Bird",
7695
+ icon: "🐦"
7696
+ },
7697
+ {
7698
+ id: "animal",
7699
+ name: "Animal (other)",
7700
+ icon: "🐾"
7701
+ },
7702
+ {
7703
+ id: "alarm",
7704
+ name: "Alarm / Siren",
7705
+ icon: "🚨"
7706
+ },
7707
+ {
7708
+ id: "doorbell",
7709
+ name: "Doorbell / Knock",
7710
+ icon: "🔔"
7711
+ },
7712
+ {
7713
+ id: "glass_breaking",
7714
+ name: "Glass Breaking",
7715
+ icon: "💥"
7716
+ },
7717
+ {
7718
+ id: "gunshot",
7719
+ name: "Gunshot / Explosion",
7720
+ icon: "💣"
7721
+ },
7722
+ {
7723
+ id: "vehicle",
7724
+ name: "Vehicle",
7725
+ icon: "🚗"
7726
+ },
7727
+ {
7728
+ id: "siren",
7729
+ name: "Emergency Siren",
7730
+ icon: "🚑"
7731
+ },
7732
+ {
7733
+ id: "fire",
7734
+ name: "Fire / Smoke",
7735
+ icon: "🔥"
7736
+ },
7737
+ {
7738
+ id: "water",
7739
+ name: "Water",
7740
+ icon: "💧"
7741
+ },
7742
+ {
7743
+ id: "wind",
7744
+ name: "Wind / Weather",
7745
+ icon: "🌬️"
7746
+ },
7747
+ {
7748
+ id: "door",
7749
+ name: "Door",
7750
+ icon: "🚪"
7751
+ },
7752
+ {
7753
+ id: "footsteps",
7754
+ name: "Footsteps",
7755
+ icon: "👣"
7756
+ },
7757
+ {
7758
+ id: "crowd",
7759
+ name: "Crowd / Chatter",
7760
+ icon: "👥"
7761
+ },
7762
+ {
7763
+ id: "telephone",
7764
+ name: "Telephone",
7765
+ icon: "📞"
7766
+ },
7767
+ {
7768
+ id: "engine",
7769
+ name: "Engine / Motor",
7770
+ icon: "⚙️"
7771
+ },
7772
+ {
7773
+ id: "tools",
7774
+ name: "Tools / Construction",
7775
+ icon: "🔨"
7776
+ },
7777
+ {
7778
+ id: "silence",
7779
+ name: "Silence",
7780
+ icon: "🤫"
7781
+ }
7782
+ ];
7573
7783
  var YAMNET_TO_MACRO = {
7574
7784
  mapping: {
7575
7785
  Speech: "speech",
@@ -7836,6 +8046,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7836
8046
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7837
8047
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7838
8048
  /**
8049
+ * Unified event-kind taxonomy — THE single source of truth for
8050
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8051
+ * icon }`.
8052
+ *
8053
+ * This dictionary folds together what used to be scattered across four
8054
+ * copies:
8055
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8056
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8057
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8058
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8059
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8060
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8061
+ *
8062
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8063
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8064
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8065
+ * color or an icon: they read this dictionary (server descriptors carry the
8066
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8067
+ *
8068
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8069
+ */
8070
+ var TAXONOMY_COLORS = {
8071
+ motion: "#f59e0b",
8072
+ audio: "#06b6d4",
8073
+ person: "#22c55e",
8074
+ vehicle: "#3b82f6",
8075
+ animal: "#f97316",
8076
+ package: "#a855f7",
8077
+ sensor: "#8b5cf6",
8078
+ control: "#10b981",
8079
+ genericDetection: "#64748b"
8080
+ };
8081
+ var DETECTION_SUB_COLORS = {
8082
+ car: "#f59e0b",
8083
+ truck: "#d97706",
8084
+ bus: "#b45309",
8085
+ motorcycle: "#eab308",
8086
+ bicycle: "#ca8a04",
8087
+ airplane: "#60a5fa",
8088
+ boat: "#2563eb",
8089
+ train: "#1d4ed8",
8090
+ bird: "#14b8a6",
8091
+ dog: "#84cc16",
8092
+ cat: "#f97316",
8093
+ horse: "#a16207",
8094
+ sheep: "#a3a3a3",
8095
+ cow: "#78716c",
8096
+ elephant: "#6b7280",
8097
+ bear: "#7c2d12",
8098
+ zebra: "#404040",
8099
+ giraffe: "#d4a373"
8100
+ };
8101
+ function titleCase(id) {
8102
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8103
+ }
8104
+ var entries = /* @__PURE__ */ new Map();
8105
+ function macro(kind, category, color, iconId, label) {
8106
+ entries.set(kind, {
8107
+ kind,
8108
+ parentKind: null,
8109
+ level: "macro",
8110
+ category,
8111
+ color,
8112
+ iconId,
8113
+ labelKey: `eventKind.${kind}`,
8114
+ label
8115
+ });
8116
+ }
8117
+ function sub(kind, parentKind, category, color, iconId, label) {
8118
+ entries.set(kind, {
8119
+ kind,
8120
+ parentKind,
8121
+ level: "sub",
8122
+ category,
8123
+ color,
8124
+ iconId,
8125
+ labelKey: `eventKind.${kind}`,
8126
+ label
8127
+ });
8128
+ }
8129
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8130
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8131
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8132
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8133
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8134
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8135
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8136
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8137
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8138
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8139
+ if (entries.has(cocoClass)) continue;
8140
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8141
+ }
8142
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8143
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8144
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8145
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8146
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8147
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8148
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8149
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8150
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8151
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8152
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8153
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8154
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8155
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8156
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8157
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8158
+ sub("button", "control", "control", "#10b981", "button", "Button");
8159
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8160
+ for (const l of AUDIO_MACRO_LABELS) {
8161
+ const kind = `audio-${l.id}`;
8162
+ if (entries.has(kind)) continue;
8163
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8164
+ }
8165
+ /** The complete taxonomy dictionary, keyed by kind. */
8166
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8167
+ /**
7839
8168
  * Error types for the safe expression engine. Two distinct classes so callers
7840
8169
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7841
8170
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15785,17 +16114,30 @@ var EventKindCategorySchema = _enum([
15785
16114
  "audio",
15786
16115
  "detection",
15787
16116
  "sensor",
16117
+ "control",
15788
16118
  "custom",
15789
16119
  "package"
15790
16120
  ]);
16121
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16122
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15791
16123
  var EventKindDescriptorSchema = object({
15792
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16124
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15793
16125
  kind: string(),
16126
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16127
+ labelKey: string(),
16128
+ /** English fallback label (kept for clients that don't translate). */
15794
16129
  label: string(),
15795
16130
  /** Hex color for timeline/legend rendering. */
15796
16131
  color: string(),
16132
+ /** Dictionary id → lucide component on the UI side. */
16133
+ iconId: string(),
16134
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15797
16135
  icon: EventKindIconSchema,
15798
16136
  category: EventKindCategorySchema,
16137
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16138
+ parentKind: string().nullable(),
16139
+ /** Derived from `parentKind`, explicit for the client tree. */
16140
+ level: EventKindLevelSchema,
15799
16141
  /** Which cap + device contributes this kind. For built-ins the camera
15800
16142
  * itself; for sensor kinds the LINKED source device. */
15801
16143
  source: object({
@@ -15868,11 +16210,21 @@ var TrackAudioLabelSchema = object({
15868
16210
  firstAt: number(),
15869
16211
  lastAt: number()
15870
16212
  });
16213
+ /**
16214
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16215
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16216
+ * linked sensor/control state change (no positions; carries a snapshot). The
16217
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16218
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16219
+ */
16220
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
15871
16221
  var TrackSchema = object({
15872
16222
  trackId: string(),
15873
16223
  deviceId: number(),
15874
16224
  className: string(),
15875
16225
  label: string().optional(),
16226
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16227
+ source: TrackSourceSchema.optional(),
15876
16228
  firstSeen: number(),
15877
16229
  lastSeen: number(),
15878
16230
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16127,6 +16479,26 @@ var TrackCascadeCountsSchema = object({
16127
16479
  /** Per-track CLIP search vectors removed (best-effort). */
16128
16480
  embeddings: number().int()
16129
16481
  });
16482
+ /** Event-store footprint for one camera. */
16483
+ var EventStoreDeviceFootprintSchema = object({
16484
+ deviceId: number(),
16485
+ /** Persisted event rows (motion + object + audio) for the camera. */
16486
+ rows: number().int(),
16487
+ /** Event-owned media bytes on disk for the camera. */
16488
+ bytes: number().int()
16489
+ });
16490
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16491
+ var EventStoreFootprintSchema = object({
16492
+ totalRows: number().int(),
16493
+ totalBytes: number().int(),
16494
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16495
+ });
16496
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16497
+ var EventPruneCountsSchema = object({
16498
+ motion: number().int(),
16499
+ object: number().int(),
16500
+ audio: number().int()
16501
+ });
16130
16502
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16131
16503
  deviceId: number(),
16132
16504
  trackId: string()
@@ -16190,6 +16562,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16190
16562
  }), {
16191
16563
  kind: "mutation",
16192
16564
  auth: "admin"
16565
+ }), method(object({}), EventStoreFootprintSchema, {
16566
+ kind: "query",
16567
+ auth: "admin"
16568
+ }), method(object({
16569
+ olderThanMs: number(),
16570
+ reason: OpsLogReasonSchema.optional()
16571
+ }), EventPruneCountsSchema, {
16572
+ kind: "mutation",
16573
+ auth: "admin"
16574
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16575
+ kind: "mutation",
16576
+ auth: "admin"
16577
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16578
+ kind: "query",
16579
+ auth: "admin"
16193
16580
  }), method(object({
16194
16581
  eventId: string(),
16195
16582
  kind: MediaFileKindEnum.optional()
@@ -16214,6 +16601,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16214
16601
  eventId: string(),
16215
16602
  timestamp: number()
16216
16603
  });
16604
+ /**
16605
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16606
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16607
+ * caps into per-camera event-kind descriptors.
16608
+ *
16609
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16610
+ * is NOT duplicated here — every entry is derived from the single
16611
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16612
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16613
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16614
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16615
+ * eventful cap is missing.
16616
+ */
16617
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16618
+ var LEGACY_ICON = {
16619
+ motion: "motion",
16620
+ audio: "audio",
16621
+ person: "person",
16622
+ vehicle: "vehicle",
16623
+ animal: "animal",
16624
+ package: "package",
16625
+ door: "door",
16626
+ pir: "pir",
16627
+ smoke: "smoke",
16628
+ water: "water",
16629
+ button: "button",
16630
+ generic: "generic",
16631
+ gas: "smoke",
16632
+ vibration: "generic",
16633
+ tamper: "generic",
16634
+ presence: "person",
16635
+ lock: "generic",
16636
+ siren: "generic",
16637
+ switch: "generic",
16638
+ doorbell: "button"
16639
+ };
16640
+ function legacyIcon(iconId) {
16641
+ return LEGACY_ICON[iconId] ?? "generic";
16642
+ }
16643
+ /**
16644
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16645
+ * The anti-drift guard cross-checks this against the eventful caps declared
16646
+ * in `packages/types/src/capabilities/*.cap.ts`.
16647
+ */
16648
+ var CAP_TO_KIND = {
16649
+ contact: "contact",
16650
+ motion: "motion-sensor",
16651
+ smoke: "smoke",
16652
+ flood: "flood",
16653
+ gas: "gas",
16654
+ "carbon-monoxide": "carbon-monoxide",
16655
+ vibration: "vibration",
16656
+ tamper: "tamper",
16657
+ presence: "presence",
16658
+ "enum-sensor": "enum-sensor",
16659
+ "event-emitter": "device-event",
16660
+ "lock-control": "lock",
16661
+ switch: "switch",
16662
+ button: "button",
16663
+ doorbell: "doorbell"
16664
+ };
16665
+ function buildDescriptor(capName, kind) {
16666
+ const t = EVENT_TAXONOMY[kind];
16667
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16668
+ return {
16669
+ ...t,
16670
+ icon: legacyIcon(t.iconId)
16671
+ };
16672
+ }
16673
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16217
16674
  var CameraPipelineConfigSchema = object({
16218
16675
  engine: PipelineEngineChoiceSchema.optional(),
16219
16676
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19446,13 +19903,29 @@ method(object({
19446
19903
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19447
19904
  kind: "mutation",
19448
19905
  auth: "admin"
19449
- }), method(object({ deviceId: number() }), object({
19906
+ }), method(object({
19907
+ deviceId: number(),
19908
+ reason: OpsLogReasonSchema.optional()
19909
+ }), object({
19450
19910
  floorMs: number().nullable(),
19451
19911
  deletedBuckets: number().int(),
19452
19912
  reclaimedBytes: number().int()
19453
19913
  }), {
19454
19914
  kind: "mutation",
19455
19915
  auth: "admin"
19916
+ }), method(object({
19917
+ deviceId: number(),
19918
+ fromMs: number().optional(),
19919
+ toMs: number().optional()
19920
+ }), object({
19921
+ deletedBuckets: number().int(),
19922
+ reclaimedBytes: number().int()
19923
+ }), {
19924
+ kind: "mutation",
19925
+ auth: "admin"
19926
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19927
+ kind: "query",
19928
+ auth: "admin"
19456
19929
  });
19457
19930
  /**
19458
19931
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22574,6 +23047,12 @@ Object.freeze({
22574
23047
  addonId: null,
22575
23048
  access: "delete"
22576
23049
  },
23050
+ "pipelineAnalytics.deleteDeviceEvents": {
23051
+ capName: "pipeline-analytics",
23052
+ capScope: "device",
23053
+ addonId: null,
23054
+ access: "delete"
23055
+ },
22577
23056
  "pipelineAnalytics.deleteTracks": {
22578
23057
  capName: "pipeline-analytics",
22579
23058
  capScope: "device",
@@ -22604,6 +23083,12 @@ Object.freeze({
22604
23083
  addonId: null,
22605
23084
  access: "view"
22606
23085
  },
23086
+ "pipelineAnalytics.getEventStoreFootprint": {
23087
+ capName: "pipeline-analytics",
23088
+ capScope: "device",
23089
+ addonId: null,
23090
+ access: "view"
23091
+ },
22607
23092
  "pipelineAnalytics.getKeyEvents": {
22608
23093
  capName: "pipeline-analytics",
22609
23094
  capScope: "device",
@@ -22646,6 +23131,12 @@ Object.freeze({
22646
23131
  addonId: null,
22647
23132
  access: "view"
22648
23133
  },
23134
+ "pipelineAnalytics.listOpsLog": {
23135
+ capName: "pipeline-analytics",
23136
+ capScope: "device",
23137
+ addonId: null,
23138
+ access: "view"
23139
+ },
22649
23140
  "pipelineAnalytics.listRecentTracks": {
22650
23141
  capName: "pipeline-analytics",
22651
23142
  capScope: "device",
@@ -22658,6 +23149,12 @@ Object.freeze({
22658
23149
  addonId: null,
22659
23150
  access: "view"
22660
23151
  },
23152
+ "pipelineAnalytics.pruneEvents": {
23153
+ capName: "pipeline-analytics",
23154
+ capScope: "device",
23155
+ addonId: null,
23156
+ access: "create"
23157
+ },
22661
23158
  "pipelineAnalytics.pruneEventsBefore": {
22662
23159
  capName: "pipeline-analytics",
22663
23160
  capScope: "device",
@@ -23420,6 +23917,12 @@ Object.freeze({
23420
23917
  addonId: null,
23421
23918
  access: "create"
23422
23919
  },
23920
+ "recording.deleteFootprint": {
23921
+ capName: "recording",
23922
+ capScope: "system",
23923
+ addonId: null,
23924
+ access: "delete"
23925
+ },
23423
23926
  "recording.getAvailability": {
23424
23927
  capName: "recording",
23425
23928
  capScope: "system",
@@ -23450,6 +23953,12 @@ Object.freeze({
23450
23953
  addonId: null,
23451
23954
  access: "view"
23452
23955
  },
23956
+ "recording.listOpsLog": {
23957
+ capName: "recording",
23958
+ capScope: "system",
23959
+ addonId: null,
23960
+ access: "view"
23961
+ },
23453
23962
  "recording.locateSegment": {
23454
23963
  capName: "recording",
23455
23964
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-static-turn",
3
- "version": "1.1.26",
3
+ "version": "1.1.28",
4
4
  "description": "Static / self-hosted (coturn) TURN provider for CamStack",
5
5
  "keywords": [
6
6
  "camstack",