@camstack/addon-decoder-ffmpeg 1.1.17 → 1.1.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -7345,6 +7345,62 @@ var RecordingConfigSchema = object({
7345
7345
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7346
7346
  });
7347
7347
  /**
7348
+ * Ops-log — the durable, append-only operations audit shared by the
7349
+ * recordings and events management surfaces.
7350
+ *
7351
+ * ONE row shape is reused for both domains so a single "Activity" view can
7352
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7353
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7354
+ * management operation, WHY it ran (reason), and its measurable effect
7355
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7356
+ * never fail the operation it records.
7357
+ */
7358
+ /** Which management domain the operation belongs to. */
7359
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7360
+ /** The kind of management operation performed. */
7361
+ var OpsLogOpSchema = _enum([
7362
+ "prune",
7363
+ "manual-delete",
7364
+ "rescan",
7365
+ "retention-run"
7366
+ ]);
7367
+ /** Why the operation ran. */
7368
+ var OpsLogReasonSchema = _enum([
7369
+ "retention",
7370
+ "quota",
7371
+ "manual",
7372
+ "operator"
7373
+ ]);
7374
+ /** One audit row, shared verbatim by both domains. */
7375
+ var OpsLogEntrySchema = object({
7376
+ /** Unique row id. */
7377
+ id: string(),
7378
+ /** Epoch ms the operation completed. */
7379
+ at: number(),
7380
+ domain: OpsLogDomainSchema,
7381
+ op: OpsLogOpSchema,
7382
+ reason: OpsLogReasonSchema,
7383
+ /** The camera the op targeted; null for a cluster/global op. */
7384
+ deviceId: number().nullable(),
7385
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7386
+ nodeId: string(),
7387
+ /** Buckets / rows deleted (op-specific unit). */
7388
+ itemsAffected: number(),
7389
+ /** Bytes reclaimed by the op (0 when not measurable). */
7390
+ bytesReclaimed: number(),
7391
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7392
+ detail: string().nullable(),
7393
+ /** Who/what triggered the op. */
7394
+ actor: string()
7395
+ });
7396
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7397
+ var OpsLogQueryInputSchema = object({
7398
+ /** Restrict to a single camera; omit for every row. */
7399
+ deviceId: number().optional(),
7400
+ /** Max rows returned, newest-first. */
7401
+ limit: number().int().min(1).max(1e3).optional()
7402
+ });
7403
+ /**
7348
7404
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7349
7405
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7350
7406
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7588,6 +7644,160 @@ var EncodeProfileSchema = object({
7588
7644
  */
7589
7645
  outputArgs: array(string()).optional()
7590
7646
  });
7647
+ var COCO_TO_MACRO = {
7648
+ mapping: {
7649
+ person: "person",
7650
+ bicycle: "vehicle",
7651
+ car: "vehicle",
7652
+ motorcycle: "vehicle",
7653
+ airplane: "vehicle",
7654
+ bus: "vehicle",
7655
+ train: "vehicle",
7656
+ truck: "vehicle",
7657
+ boat: "vehicle",
7658
+ bird: "animal",
7659
+ cat: "animal",
7660
+ dog: "animal",
7661
+ horse: "animal",
7662
+ sheep: "animal",
7663
+ cow: "animal",
7664
+ elephant: "animal",
7665
+ bear: "animal",
7666
+ zebra: "animal",
7667
+ giraffe: "animal",
7668
+ suitcase: "package",
7669
+ backpack: "package",
7670
+ handbag: "package"
7671
+ },
7672
+ preserveOriginal: false
7673
+ };
7674
+ var AUDIO_MACRO_LABELS = [
7675
+ {
7676
+ id: "speech",
7677
+ name: "Speech",
7678
+ icon: "🗣️"
7679
+ },
7680
+ {
7681
+ id: "scream",
7682
+ name: "Scream / Shout",
7683
+ icon: "😱"
7684
+ },
7685
+ {
7686
+ id: "crying",
7687
+ name: "Crying / Baby",
7688
+ icon: "😢"
7689
+ },
7690
+ {
7691
+ id: "laughter",
7692
+ name: "Laughter",
7693
+ icon: "😂"
7694
+ },
7695
+ {
7696
+ id: "music",
7697
+ name: "Music",
7698
+ icon: "🎵"
7699
+ },
7700
+ {
7701
+ id: "dog",
7702
+ name: "Dog",
7703
+ icon: "🐕"
7704
+ },
7705
+ {
7706
+ id: "cat",
7707
+ name: "Cat",
7708
+ icon: "🐈"
7709
+ },
7710
+ {
7711
+ id: "bird",
7712
+ name: "Bird",
7713
+ icon: "🐦"
7714
+ },
7715
+ {
7716
+ id: "animal",
7717
+ name: "Animal (other)",
7718
+ icon: "🐾"
7719
+ },
7720
+ {
7721
+ id: "alarm",
7722
+ name: "Alarm / Siren",
7723
+ icon: "🚨"
7724
+ },
7725
+ {
7726
+ id: "doorbell",
7727
+ name: "Doorbell / Knock",
7728
+ icon: "🔔"
7729
+ },
7730
+ {
7731
+ id: "glass_breaking",
7732
+ name: "Glass Breaking",
7733
+ icon: "💥"
7734
+ },
7735
+ {
7736
+ id: "gunshot",
7737
+ name: "Gunshot / Explosion",
7738
+ icon: "💣"
7739
+ },
7740
+ {
7741
+ id: "vehicle",
7742
+ name: "Vehicle",
7743
+ icon: "🚗"
7744
+ },
7745
+ {
7746
+ id: "siren",
7747
+ name: "Emergency Siren",
7748
+ icon: "🚑"
7749
+ },
7750
+ {
7751
+ id: "fire",
7752
+ name: "Fire / Smoke",
7753
+ icon: "🔥"
7754
+ },
7755
+ {
7756
+ id: "water",
7757
+ name: "Water",
7758
+ icon: "💧"
7759
+ },
7760
+ {
7761
+ id: "wind",
7762
+ name: "Wind / Weather",
7763
+ icon: "🌬️"
7764
+ },
7765
+ {
7766
+ id: "door",
7767
+ name: "Door",
7768
+ icon: "🚪"
7769
+ },
7770
+ {
7771
+ id: "footsteps",
7772
+ name: "Footsteps",
7773
+ icon: "👣"
7774
+ },
7775
+ {
7776
+ id: "crowd",
7777
+ name: "Crowd / Chatter",
7778
+ icon: "👥"
7779
+ },
7780
+ {
7781
+ id: "telephone",
7782
+ name: "Telephone",
7783
+ icon: "📞"
7784
+ },
7785
+ {
7786
+ id: "engine",
7787
+ name: "Engine / Motor",
7788
+ icon: "⚙️"
7789
+ },
7790
+ {
7791
+ id: "tools",
7792
+ name: "Tools / Construction",
7793
+ icon: "🔨"
7794
+ },
7795
+ {
7796
+ id: "silence",
7797
+ name: "Silence",
7798
+ icon: "🤫"
7799
+ }
7800
+ ];
7591
7801
  var YAMNET_TO_MACRO = {
7592
7802
  mapping: {
7593
7803
  Speech: "speech",
@@ -7854,6 +8064,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7854
8064
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7855
8065
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7856
8066
  /**
8067
+ * Unified event-kind taxonomy — THE single source of truth for
8068
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8069
+ * icon }`.
8070
+ *
8071
+ * This dictionary folds together what used to be scattered across four
8072
+ * copies:
8073
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8074
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8075
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8076
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8077
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8078
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8079
+ *
8080
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8081
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8082
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8083
+ * color or an icon: they read this dictionary (server descriptors carry the
8084
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8085
+ *
8086
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8087
+ */
8088
+ var TAXONOMY_COLORS = {
8089
+ motion: "#f59e0b",
8090
+ audio: "#06b6d4",
8091
+ person: "#22c55e",
8092
+ vehicle: "#3b82f6",
8093
+ animal: "#f97316",
8094
+ package: "#a855f7",
8095
+ sensor: "#8b5cf6",
8096
+ control: "#10b981",
8097
+ genericDetection: "#64748b"
8098
+ };
8099
+ var DETECTION_SUB_COLORS = {
8100
+ car: "#f59e0b",
8101
+ truck: "#d97706",
8102
+ bus: "#b45309",
8103
+ motorcycle: "#eab308",
8104
+ bicycle: "#ca8a04",
8105
+ airplane: "#60a5fa",
8106
+ boat: "#2563eb",
8107
+ train: "#1d4ed8",
8108
+ bird: "#14b8a6",
8109
+ dog: "#84cc16",
8110
+ cat: "#f97316",
8111
+ horse: "#a16207",
8112
+ sheep: "#a3a3a3",
8113
+ cow: "#78716c",
8114
+ elephant: "#6b7280",
8115
+ bear: "#7c2d12",
8116
+ zebra: "#404040",
8117
+ giraffe: "#d4a373"
8118
+ };
8119
+ function titleCase(id) {
8120
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8121
+ }
8122
+ var entries = /* @__PURE__ */ new Map();
8123
+ function macro(kind, category, color, iconId, label) {
8124
+ entries.set(kind, {
8125
+ kind,
8126
+ parentKind: null,
8127
+ level: "macro",
8128
+ category,
8129
+ color,
8130
+ iconId,
8131
+ labelKey: `eventKind.${kind}`,
8132
+ label
8133
+ });
8134
+ }
8135
+ function sub(kind, parentKind, category, color, iconId, label) {
8136
+ entries.set(kind, {
8137
+ kind,
8138
+ parentKind,
8139
+ level: "sub",
8140
+ category,
8141
+ color,
8142
+ iconId,
8143
+ labelKey: `eventKind.${kind}`,
8144
+ label
8145
+ });
8146
+ }
8147
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8148
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8149
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8150
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8151
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8152
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8153
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8154
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8155
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8156
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8157
+ if (entries.has(cocoClass)) continue;
8158
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8159
+ }
8160
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8161
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8162
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8163
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8164
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8165
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8166
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8167
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8168
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8169
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8170
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8171
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8172
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8173
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8174
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8175
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8176
+ sub("button", "control", "control", "#10b981", "button", "Button");
8177
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8178
+ for (const l of AUDIO_MACRO_LABELS) {
8179
+ const kind = `audio-${l.id}`;
8180
+ if (entries.has(kind)) continue;
8181
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8182
+ }
8183
+ /** The complete taxonomy dictionary, keyed by kind. */
8184
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8185
+ /**
7857
8186
  * Error types for the safe expression engine. Two distinct classes so callers
7858
8187
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7859
8188
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15933,17 +16262,30 @@ var EventKindCategorySchema = _enum([
15933
16262
  "audio",
15934
16263
  "detection",
15935
16264
  "sensor",
16265
+ "control",
15936
16266
  "custom",
15937
16267
  "package"
15938
16268
  ]);
16269
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16270
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15939
16271
  var EventKindDescriptorSchema = object({
15940
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16272
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15941
16273
  kind: string(),
16274
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16275
+ labelKey: string(),
16276
+ /** English fallback label (kept for clients that don't translate). */
15942
16277
  label: string(),
15943
16278
  /** Hex color for timeline/legend rendering. */
15944
16279
  color: string(),
16280
+ /** Dictionary id → lucide component on the UI side. */
16281
+ iconId: string(),
16282
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15945
16283
  icon: EventKindIconSchema,
15946
16284
  category: EventKindCategorySchema,
16285
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16286
+ parentKind: string().nullable(),
16287
+ /** Derived from `parentKind`, explicit for the client tree. */
16288
+ level: EventKindLevelSchema,
15947
16289
  /** Which cap + device contributes this kind. For built-ins the camera
15948
16290
  * itself; for sensor kinds the LINKED source device. */
15949
16291
  source: object({
@@ -16016,11 +16358,21 @@ var TrackAudioLabelSchema = object({
16016
16358
  firstAt: number(),
16017
16359
  lastAt: number()
16018
16360
  });
16361
+ /**
16362
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16363
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16364
+ * linked sensor/control state change (no positions; carries a snapshot). The
16365
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16366
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16367
+ */
16368
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16019
16369
  var TrackSchema = object({
16020
16370
  trackId: string(),
16021
16371
  deviceId: number(),
16022
16372
  className: string(),
16023
16373
  label: string().optional(),
16374
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16375
+ source: TrackSourceSchema.optional(),
16024
16376
  firstSeen: number(),
16025
16377
  lastSeen: number(),
16026
16378
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16275,6 +16627,26 @@ var TrackCascadeCountsSchema = object({
16275
16627
  /** Per-track CLIP search vectors removed (best-effort). */
16276
16628
  embeddings: number().int()
16277
16629
  });
16630
+ /** Event-store footprint for one camera. */
16631
+ var EventStoreDeviceFootprintSchema = object({
16632
+ deviceId: number(),
16633
+ /** Persisted event rows (motion + object + audio) for the camera. */
16634
+ rows: number().int(),
16635
+ /** Event-owned media bytes on disk for the camera. */
16636
+ bytes: number().int()
16637
+ });
16638
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16639
+ var EventStoreFootprintSchema = object({
16640
+ totalRows: number().int(),
16641
+ totalBytes: number().int(),
16642
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16643
+ });
16644
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16645
+ var EventPruneCountsSchema = object({
16646
+ motion: number().int(),
16647
+ object: number().int(),
16648
+ audio: number().int()
16649
+ });
16278
16650
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16279
16651
  deviceId: number(),
16280
16652
  trackId: string()
@@ -16338,6 +16710,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16338
16710
  }), {
16339
16711
  kind: "mutation",
16340
16712
  auth: "admin"
16713
+ }), method(object({}), EventStoreFootprintSchema, {
16714
+ kind: "query",
16715
+ auth: "admin"
16716
+ }), method(object({
16717
+ olderThanMs: number(),
16718
+ reason: OpsLogReasonSchema.optional()
16719
+ }), EventPruneCountsSchema, {
16720
+ kind: "mutation",
16721
+ auth: "admin"
16722
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16723
+ kind: "mutation",
16724
+ auth: "admin"
16725
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16726
+ kind: "query",
16727
+ auth: "admin"
16341
16728
  }), method(object({
16342
16729
  eventId: string(),
16343
16730
  kind: MediaFileKindEnum.optional()
@@ -16362,6 +16749,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16362
16749
  eventId: string(),
16363
16750
  timestamp: number()
16364
16751
  });
16752
+ /**
16753
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16754
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16755
+ * caps into per-camera event-kind descriptors.
16756
+ *
16757
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16758
+ * is NOT duplicated here — every entry is derived from the single
16759
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16760
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16761
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16762
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16763
+ * eventful cap is missing.
16764
+ */
16765
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16766
+ var LEGACY_ICON = {
16767
+ motion: "motion",
16768
+ audio: "audio",
16769
+ person: "person",
16770
+ vehicle: "vehicle",
16771
+ animal: "animal",
16772
+ package: "package",
16773
+ door: "door",
16774
+ pir: "pir",
16775
+ smoke: "smoke",
16776
+ water: "water",
16777
+ button: "button",
16778
+ generic: "generic",
16779
+ gas: "smoke",
16780
+ vibration: "generic",
16781
+ tamper: "generic",
16782
+ presence: "person",
16783
+ lock: "generic",
16784
+ siren: "generic",
16785
+ switch: "generic",
16786
+ doorbell: "button"
16787
+ };
16788
+ function legacyIcon(iconId) {
16789
+ return LEGACY_ICON[iconId] ?? "generic";
16790
+ }
16791
+ /**
16792
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16793
+ * The anti-drift guard cross-checks this against the eventful caps declared
16794
+ * in `packages/types/src/capabilities/*.cap.ts`.
16795
+ */
16796
+ var CAP_TO_KIND = {
16797
+ contact: "contact",
16798
+ motion: "motion-sensor",
16799
+ smoke: "smoke",
16800
+ flood: "flood",
16801
+ gas: "gas",
16802
+ "carbon-monoxide": "carbon-monoxide",
16803
+ vibration: "vibration",
16804
+ tamper: "tamper",
16805
+ presence: "presence",
16806
+ "enum-sensor": "enum-sensor",
16807
+ "event-emitter": "device-event",
16808
+ "lock-control": "lock",
16809
+ switch: "switch",
16810
+ button: "button",
16811
+ doorbell: "doorbell"
16812
+ };
16813
+ function buildDescriptor(capName, kind) {
16814
+ const t = EVENT_TAXONOMY[kind];
16815
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16816
+ return {
16817
+ ...t,
16818
+ icon: legacyIcon(t.iconId)
16819
+ };
16820
+ }
16821
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16365
16822
  var CameraPipelineConfigSchema = object({
16366
16823
  engine: PipelineEngineChoiceSchema.optional(),
16367
16824
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19571,13 +20028,29 @@ method(object({
19571
20028
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19572
20029
  kind: "mutation",
19573
20030
  auth: "admin"
19574
- }), method(object({ deviceId: number() }), object({
20031
+ }), method(object({
20032
+ deviceId: number(),
20033
+ reason: OpsLogReasonSchema.optional()
20034
+ }), object({
19575
20035
  floorMs: number().nullable(),
19576
20036
  deletedBuckets: number().int(),
19577
20037
  reclaimedBytes: number().int()
19578
20038
  }), {
19579
20039
  kind: "mutation",
19580
20040
  auth: "admin"
20041
+ }), method(object({
20042
+ deviceId: number(),
20043
+ fromMs: number().optional(),
20044
+ toMs: number().optional()
20045
+ }), object({
20046
+ deletedBuckets: number().int(),
20047
+ reclaimedBytes: number().int()
20048
+ }), {
20049
+ kind: "mutation",
20050
+ auth: "admin"
20051
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20052
+ kind: "query",
20053
+ auth: "admin"
19581
20054
  });
19582
20055
  /**
19583
20056
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22699,6 +23172,12 @@ Object.freeze({
22699
23172
  addonId: null,
22700
23173
  access: "delete"
22701
23174
  },
23175
+ "pipelineAnalytics.deleteDeviceEvents": {
23176
+ capName: "pipeline-analytics",
23177
+ capScope: "device",
23178
+ addonId: null,
23179
+ access: "delete"
23180
+ },
22702
23181
  "pipelineAnalytics.deleteTracks": {
22703
23182
  capName: "pipeline-analytics",
22704
23183
  capScope: "device",
@@ -22729,6 +23208,12 @@ Object.freeze({
22729
23208
  addonId: null,
22730
23209
  access: "view"
22731
23210
  },
23211
+ "pipelineAnalytics.getEventStoreFootprint": {
23212
+ capName: "pipeline-analytics",
23213
+ capScope: "device",
23214
+ addonId: null,
23215
+ access: "view"
23216
+ },
22732
23217
  "pipelineAnalytics.getKeyEvents": {
22733
23218
  capName: "pipeline-analytics",
22734
23219
  capScope: "device",
@@ -22771,6 +23256,12 @@ Object.freeze({
22771
23256
  addonId: null,
22772
23257
  access: "view"
22773
23258
  },
23259
+ "pipelineAnalytics.listOpsLog": {
23260
+ capName: "pipeline-analytics",
23261
+ capScope: "device",
23262
+ addonId: null,
23263
+ access: "view"
23264
+ },
22774
23265
  "pipelineAnalytics.listRecentTracks": {
22775
23266
  capName: "pipeline-analytics",
22776
23267
  capScope: "device",
@@ -22783,6 +23274,12 @@ Object.freeze({
22783
23274
  addonId: null,
22784
23275
  access: "view"
22785
23276
  },
23277
+ "pipelineAnalytics.pruneEvents": {
23278
+ capName: "pipeline-analytics",
23279
+ capScope: "device",
23280
+ addonId: null,
23281
+ access: "create"
23282
+ },
22786
23283
  "pipelineAnalytics.pruneEventsBefore": {
22787
23284
  capName: "pipeline-analytics",
22788
23285
  capScope: "device",
@@ -23545,6 +24042,12 @@ Object.freeze({
23545
24042
  addonId: null,
23546
24043
  access: "create"
23547
24044
  },
24045
+ "recording.deleteFootprint": {
24046
+ capName: "recording",
24047
+ capScope: "system",
24048
+ addonId: null,
24049
+ access: "delete"
24050
+ },
23548
24051
  "recording.getAvailability": {
23549
24052
  capName: "recording",
23550
24053
  capScope: "system",
@@ -23575,6 +24078,12 @@ Object.freeze({
23575
24078
  addonId: null,
23576
24079
  access: "view"
23577
24080
  },
24081
+ "recording.listOpsLog": {
24082
+ capName: "recording",
24083
+ capScope: "system",
24084
+ addonId: null,
24085
+ access: "view"
24086
+ },
23578
24087
  "recording.locateSegment": {
23579
24088
  capName: "recording",
23580
24089
  capScope: "system",
package/dist/index.mjs CHANGED
@@ -7341,6 +7341,62 @@ var RecordingConfigSchema = object({
7341
7341
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7342
7342
  });
7343
7343
  /**
7344
+ * Ops-log — the durable, append-only operations audit shared by the
7345
+ * recordings and events management surfaces.
7346
+ *
7347
+ * ONE row shape is reused for both domains so a single "Activity" view can
7348
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7349
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7350
+ * management operation, WHY it ran (reason), and its measurable effect
7351
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7352
+ * never fail the operation it records.
7353
+ */
7354
+ /** Which management domain the operation belongs to. */
7355
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7356
+ /** The kind of management operation performed. */
7357
+ var OpsLogOpSchema = _enum([
7358
+ "prune",
7359
+ "manual-delete",
7360
+ "rescan",
7361
+ "retention-run"
7362
+ ]);
7363
+ /** Why the operation ran. */
7364
+ var OpsLogReasonSchema = _enum([
7365
+ "retention",
7366
+ "quota",
7367
+ "manual",
7368
+ "operator"
7369
+ ]);
7370
+ /** One audit row, shared verbatim by both domains. */
7371
+ var OpsLogEntrySchema = object({
7372
+ /** Unique row id. */
7373
+ id: string(),
7374
+ /** Epoch ms the operation completed. */
7375
+ at: number(),
7376
+ domain: OpsLogDomainSchema,
7377
+ op: OpsLogOpSchema,
7378
+ reason: OpsLogReasonSchema,
7379
+ /** The camera the op targeted; null for a cluster/global op. */
7380
+ deviceId: number().nullable(),
7381
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7382
+ nodeId: string(),
7383
+ /** Buckets / rows deleted (op-specific unit). */
7384
+ itemsAffected: number(),
7385
+ /** Bytes reclaimed by the op (0 when not measurable). */
7386
+ bytesReclaimed: number(),
7387
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7388
+ detail: string().nullable(),
7389
+ /** Who/what triggered the op. */
7390
+ actor: string()
7391
+ });
7392
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7393
+ var OpsLogQueryInputSchema = object({
7394
+ /** Restrict to a single camera; omit for every row. */
7395
+ deviceId: number().optional(),
7396
+ /** Max rows returned, newest-first. */
7397
+ limit: number().int().min(1).max(1e3).optional()
7398
+ });
7399
+ /**
7344
7400
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7345
7401
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7346
7402
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7584,6 +7640,160 @@ var EncodeProfileSchema = object({
7584
7640
  */
7585
7641
  outputArgs: array(string()).optional()
7586
7642
  });
7643
+ var COCO_TO_MACRO = {
7644
+ mapping: {
7645
+ person: "person",
7646
+ bicycle: "vehicle",
7647
+ car: "vehicle",
7648
+ motorcycle: "vehicle",
7649
+ airplane: "vehicle",
7650
+ bus: "vehicle",
7651
+ train: "vehicle",
7652
+ truck: "vehicle",
7653
+ boat: "vehicle",
7654
+ bird: "animal",
7655
+ cat: "animal",
7656
+ dog: "animal",
7657
+ horse: "animal",
7658
+ sheep: "animal",
7659
+ cow: "animal",
7660
+ elephant: "animal",
7661
+ bear: "animal",
7662
+ zebra: "animal",
7663
+ giraffe: "animal",
7664
+ suitcase: "package",
7665
+ backpack: "package",
7666
+ handbag: "package"
7667
+ },
7668
+ preserveOriginal: false
7669
+ };
7670
+ var AUDIO_MACRO_LABELS = [
7671
+ {
7672
+ id: "speech",
7673
+ name: "Speech",
7674
+ icon: "🗣️"
7675
+ },
7676
+ {
7677
+ id: "scream",
7678
+ name: "Scream / Shout",
7679
+ icon: "😱"
7680
+ },
7681
+ {
7682
+ id: "crying",
7683
+ name: "Crying / Baby",
7684
+ icon: "😢"
7685
+ },
7686
+ {
7687
+ id: "laughter",
7688
+ name: "Laughter",
7689
+ icon: "😂"
7690
+ },
7691
+ {
7692
+ id: "music",
7693
+ name: "Music",
7694
+ icon: "🎵"
7695
+ },
7696
+ {
7697
+ id: "dog",
7698
+ name: "Dog",
7699
+ icon: "🐕"
7700
+ },
7701
+ {
7702
+ id: "cat",
7703
+ name: "Cat",
7704
+ icon: "🐈"
7705
+ },
7706
+ {
7707
+ id: "bird",
7708
+ name: "Bird",
7709
+ icon: "🐦"
7710
+ },
7711
+ {
7712
+ id: "animal",
7713
+ name: "Animal (other)",
7714
+ icon: "🐾"
7715
+ },
7716
+ {
7717
+ id: "alarm",
7718
+ name: "Alarm / Siren",
7719
+ icon: "🚨"
7720
+ },
7721
+ {
7722
+ id: "doorbell",
7723
+ name: "Doorbell / Knock",
7724
+ icon: "🔔"
7725
+ },
7726
+ {
7727
+ id: "glass_breaking",
7728
+ name: "Glass Breaking",
7729
+ icon: "💥"
7730
+ },
7731
+ {
7732
+ id: "gunshot",
7733
+ name: "Gunshot / Explosion",
7734
+ icon: "💣"
7735
+ },
7736
+ {
7737
+ id: "vehicle",
7738
+ name: "Vehicle",
7739
+ icon: "🚗"
7740
+ },
7741
+ {
7742
+ id: "siren",
7743
+ name: "Emergency Siren",
7744
+ icon: "🚑"
7745
+ },
7746
+ {
7747
+ id: "fire",
7748
+ name: "Fire / Smoke",
7749
+ icon: "🔥"
7750
+ },
7751
+ {
7752
+ id: "water",
7753
+ name: "Water",
7754
+ icon: "💧"
7755
+ },
7756
+ {
7757
+ id: "wind",
7758
+ name: "Wind / Weather",
7759
+ icon: "🌬️"
7760
+ },
7761
+ {
7762
+ id: "door",
7763
+ name: "Door",
7764
+ icon: "🚪"
7765
+ },
7766
+ {
7767
+ id: "footsteps",
7768
+ name: "Footsteps",
7769
+ icon: "👣"
7770
+ },
7771
+ {
7772
+ id: "crowd",
7773
+ name: "Crowd / Chatter",
7774
+ icon: "👥"
7775
+ },
7776
+ {
7777
+ id: "telephone",
7778
+ name: "Telephone",
7779
+ icon: "📞"
7780
+ },
7781
+ {
7782
+ id: "engine",
7783
+ name: "Engine / Motor",
7784
+ icon: "⚙️"
7785
+ },
7786
+ {
7787
+ id: "tools",
7788
+ name: "Tools / Construction",
7789
+ icon: "🔨"
7790
+ },
7791
+ {
7792
+ id: "silence",
7793
+ name: "Silence",
7794
+ icon: "🤫"
7795
+ }
7796
+ ];
7587
7797
  var YAMNET_TO_MACRO = {
7588
7798
  mapping: {
7589
7799
  Speech: "speech",
@@ -7850,6 +8060,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7850
8060
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7851
8061
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7852
8062
  /**
8063
+ * Unified event-kind taxonomy — THE single source of truth for
8064
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8065
+ * icon }`.
8066
+ *
8067
+ * This dictionary folds together what used to be scattered across four
8068
+ * copies:
8069
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8070
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8071
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8072
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8073
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8074
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8075
+ *
8076
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8077
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8078
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8079
+ * color or an icon: they read this dictionary (server descriptors carry the
8080
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8081
+ *
8082
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8083
+ */
8084
+ var TAXONOMY_COLORS = {
8085
+ motion: "#f59e0b",
8086
+ audio: "#06b6d4",
8087
+ person: "#22c55e",
8088
+ vehicle: "#3b82f6",
8089
+ animal: "#f97316",
8090
+ package: "#a855f7",
8091
+ sensor: "#8b5cf6",
8092
+ control: "#10b981",
8093
+ genericDetection: "#64748b"
8094
+ };
8095
+ var DETECTION_SUB_COLORS = {
8096
+ car: "#f59e0b",
8097
+ truck: "#d97706",
8098
+ bus: "#b45309",
8099
+ motorcycle: "#eab308",
8100
+ bicycle: "#ca8a04",
8101
+ airplane: "#60a5fa",
8102
+ boat: "#2563eb",
8103
+ train: "#1d4ed8",
8104
+ bird: "#14b8a6",
8105
+ dog: "#84cc16",
8106
+ cat: "#f97316",
8107
+ horse: "#a16207",
8108
+ sheep: "#a3a3a3",
8109
+ cow: "#78716c",
8110
+ elephant: "#6b7280",
8111
+ bear: "#7c2d12",
8112
+ zebra: "#404040",
8113
+ giraffe: "#d4a373"
8114
+ };
8115
+ function titleCase(id) {
8116
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8117
+ }
8118
+ var entries = /* @__PURE__ */ new Map();
8119
+ function macro(kind, category, color, iconId, label) {
8120
+ entries.set(kind, {
8121
+ kind,
8122
+ parentKind: null,
8123
+ level: "macro",
8124
+ category,
8125
+ color,
8126
+ iconId,
8127
+ labelKey: `eventKind.${kind}`,
8128
+ label
8129
+ });
8130
+ }
8131
+ function sub(kind, parentKind, category, color, iconId, label) {
8132
+ entries.set(kind, {
8133
+ kind,
8134
+ parentKind,
8135
+ level: "sub",
8136
+ category,
8137
+ color,
8138
+ iconId,
8139
+ labelKey: `eventKind.${kind}`,
8140
+ label
8141
+ });
8142
+ }
8143
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8144
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8145
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8146
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8147
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8148
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8149
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8150
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8151
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8152
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8153
+ if (entries.has(cocoClass)) continue;
8154
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8155
+ }
8156
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8157
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8158
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8159
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8160
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8161
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8162
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8163
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8164
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8165
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8166
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8167
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8168
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8169
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8170
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8171
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8172
+ sub("button", "control", "control", "#10b981", "button", "Button");
8173
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8174
+ for (const l of AUDIO_MACRO_LABELS) {
8175
+ const kind = `audio-${l.id}`;
8176
+ if (entries.has(kind)) continue;
8177
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8178
+ }
8179
+ /** The complete taxonomy dictionary, keyed by kind. */
8180
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8181
+ /**
7853
8182
  * Error types for the safe expression engine. Two distinct classes so callers
7854
8183
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7855
8184
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15929,17 +16258,30 @@ var EventKindCategorySchema = _enum([
15929
16258
  "audio",
15930
16259
  "detection",
15931
16260
  "sensor",
16261
+ "control",
15932
16262
  "custom",
15933
16263
  "package"
15934
16264
  ]);
16265
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
16266
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
15935
16267
  var EventKindDescriptorSchema = object({
15936
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
16268
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
15937
16269
  kind: string(),
16270
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
16271
+ labelKey: string(),
16272
+ /** English fallback label (kept for clients that don't translate). */
15938
16273
  label: string(),
15939
16274
  /** Hex color for timeline/legend rendering. */
15940
16275
  color: string(),
16276
+ /** Dictionary id → lucide component on the UI side. */
16277
+ iconId: string(),
16278
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
15941
16279
  icon: EventKindIconSchema,
15942
16280
  category: EventKindCategorySchema,
16281
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
16282
+ parentKind: string().nullable(),
16283
+ /** Derived from `parentKind`, explicit for the client tree. */
16284
+ level: EventKindLevelSchema,
15943
16285
  /** Which cap + device contributes this kind. For built-ins the camera
15944
16286
  * itself; for sensor kinds the LINKED source device. */
15945
16287
  source: object({
@@ -16012,11 +16354,21 @@ var TrackAudioLabelSchema = object({
16012
16354
  firstAt: number(),
16013
16355
  lastAt: number()
16014
16356
  });
16357
+ /**
16358
+ * How a track was produced. `pipeline` (default / absent) = the spatial
16359
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
16360
+ * linked sensor/control state change (no positions; carries a snapshot). The
16361
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
16362
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
16363
+ */
16364
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
16015
16365
  var TrackSchema = object({
16016
16366
  trackId: string(),
16017
16367
  deviceId: number(),
16018
16368
  className: string(),
16019
16369
  label: string().optional(),
16370
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
16371
+ source: TrackSourceSchema.optional(),
16020
16372
  firstSeen: number(),
16021
16373
  lastSeen: number(),
16022
16374
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -16271,6 +16623,26 @@ var TrackCascadeCountsSchema = object({
16271
16623
  /** Per-track CLIP search vectors removed (best-effort). */
16272
16624
  embeddings: number().int()
16273
16625
  });
16626
+ /** Event-store footprint for one camera. */
16627
+ var EventStoreDeviceFootprintSchema = object({
16628
+ deviceId: number(),
16629
+ /** Persisted event rows (motion + object + audio) for the camera. */
16630
+ rows: number().int(),
16631
+ /** Event-owned media bytes on disk for the camera. */
16632
+ bytes: number().int()
16633
+ });
16634
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
16635
+ var EventStoreFootprintSchema = object({
16636
+ totalRows: number().int(),
16637
+ totalBytes: number().int(),
16638
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
16639
+ });
16640
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
16641
+ var EventPruneCountsSchema = object({
16642
+ motion: number().int(),
16643
+ object: number().int(),
16644
+ audio: number().int()
16645
+ });
16274
16646
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
16275
16647
  deviceId: number(),
16276
16648
  trackId: string()
@@ -16334,6 +16706,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16334
16706
  }), {
16335
16707
  kind: "mutation",
16336
16708
  auth: "admin"
16709
+ }), method(object({}), EventStoreFootprintSchema, {
16710
+ kind: "query",
16711
+ auth: "admin"
16712
+ }), method(object({
16713
+ olderThanMs: number(),
16714
+ reason: OpsLogReasonSchema.optional()
16715
+ }), EventPruneCountsSchema, {
16716
+ kind: "mutation",
16717
+ auth: "admin"
16718
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
16719
+ kind: "mutation",
16720
+ auth: "admin"
16721
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
16722
+ kind: "query",
16723
+ auth: "admin"
16337
16724
  }), method(object({
16338
16725
  eventId: string(),
16339
16726
  kind: MediaFileKindEnum.optional()
@@ -16358,6 +16745,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
16358
16745
  eventId: string(),
16359
16746
  timestamp: number()
16360
16747
  });
16748
+ /**
16749
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
16750
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
16751
+ * caps into per-camera event-kind descriptors.
16752
+ *
16753
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
16754
+ * is NOT duplicated here — every entry is derived from the single
16755
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
16756
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
16757
+ * control cap means adding one line here (and a taxonomy entry); the anti-
16758
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
16759
+ * eventful cap is missing.
16760
+ */
16761
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
16762
+ var LEGACY_ICON = {
16763
+ motion: "motion",
16764
+ audio: "audio",
16765
+ person: "person",
16766
+ vehicle: "vehicle",
16767
+ animal: "animal",
16768
+ package: "package",
16769
+ door: "door",
16770
+ pir: "pir",
16771
+ smoke: "smoke",
16772
+ water: "water",
16773
+ button: "button",
16774
+ generic: "generic",
16775
+ gas: "smoke",
16776
+ vibration: "generic",
16777
+ tamper: "generic",
16778
+ presence: "person",
16779
+ lock: "generic",
16780
+ siren: "generic",
16781
+ switch: "generic",
16782
+ doorbell: "button"
16783
+ };
16784
+ function legacyIcon(iconId) {
16785
+ return LEGACY_ICON[iconId] ?? "generic";
16786
+ }
16787
+ /**
16788
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
16789
+ * The anti-drift guard cross-checks this against the eventful caps declared
16790
+ * in `packages/types/src/capabilities/*.cap.ts`.
16791
+ */
16792
+ var CAP_TO_KIND = {
16793
+ contact: "contact",
16794
+ motion: "motion-sensor",
16795
+ smoke: "smoke",
16796
+ flood: "flood",
16797
+ gas: "gas",
16798
+ "carbon-monoxide": "carbon-monoxide",
16799
+ vibration: "vibration",
16800
+ tamper: "tamper",
16801
+ presence: "presence",
16802
+ "enum-sensor": "enum-sensor",
16803
+ "event-emitter": "device-event",
16804
+ "lock-control": "lock",
16805
+ switch: "switch",
16806
+ button: "button",
16807
+ doorbell: "doorbell"
16808
+ };
16809
+ function buildDescriptor(capName, kind) {
16810
+ const t = EVENT_TAXONOMY[kind];
16811
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
16812
+ return {
16813
+ ...t,
16814
+ icon: legacyIcon(t.iconId)
16815
+ };
16816
+ }
16817
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
16361
16818
  var CameraPipelineConfigSchema = object({
16362
16819
  engine: PipelineEngineChoiceSchema.optional(),
16363
16820
  steps: array(PipelineStepInputSchema).readonly(),
@@ -19567,13 +20024,29 @@ method(object({
19567
20024
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
19568
20025
  kind: "mutation",
19569
20026
  auth: "admin"
19570
- }), method(object({ deviceId: number() }), object({
20027
+ }), method(object({
20028
+ deviceId: number(),
20029
+ reason: OpsLogReasonSchema.optional()
20030
+ }), object({
19571
20031
  floorMs: number().nullable(),
19572
20032
  deletedBuckets: number().int(),
19573
20033
  reclaimedBytes: number().int()
19574
20034
  }), {
19575
20035
  kind: "mutation",
19576
20036
  auth: "admin"
20037
+ }), method(object({
20038
+ deviceId: number(),
20039
+ fromMs: number().optional(),
20040
+ toMs: number().optional()
20041
+ }), object({
20042
+ deletedBuckets: number().int(),
20043
+ reclaimedBytes: number().int()
20044
+ }), {
20045
+ kind: "mutation",
20046
+ auth: "admin"
20047
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20048
+ kind: "query",
20049
+ auth: "admin"
19577
20050
  });
19578
20051
  /**
19579
20052
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -22695,6 +23168,12 @@ Object.freeze({
22695
23168
  addonId: null,
22696
23169
  access: "delete"
22697
23170
  },
23171
+ "pipelineAnalytics.deleteDeviceEvents": {
23172
+ capName: "pipeline-analytics",
23173
+ capScope: "device",
23174
+ addonId: null,
23175
+ access: "delete"
23176
+ },
22698
23177
  "pipelineAnalytics.deleteTracks": {
22699
23178
  capName: "pipeline-analytics",
22700
23179
  capScope: "device",
@@ -22725,6 +23204,12 @@ Object.freeze({
22725
23204
  addonId: null,
22726
23205
  access: "view"
22727
23206
  },
23207
+ "pipelineAnalytics.getEventStoreFootprint": {
23208
+ capName: "pipeline-analytics",
23209
+ capScope: "device",
23210
+ addonId: null,
23211
+ access: "view"
23212
+ },
22728
23213
  "pipelineAnalytics.getKeyEvents": {
22729
23214
  capName: "pipeline-analytics",
22730
23215
  capScope: "device",
@@ -22767,6 +23252,12 @@ Object.freeze({
22767
23252
  addonId: null,
22768
23253
  access: "view"
22769
23254
  },
23255
+ "pipelineAnalytics.listOpsLog": {
23256
+ capName: "pipeline-analytics",
23257
+ capScope: "device",
23258
+ addonId: null,
23259
+ access: "view"
23260
+ },
22770
23261
  "pipelineAnalytics.listRecentTracks": {
22771
23262
  capName: "pipeline-analytics",
22772
23263
  capScope: "device",
@@ -22779,6 +23270,12 @@ Object.freeze({
22779
23270
  addonId: null,
22780
23271
  access: "view"
22781
23272
  },
23273
+ "pipelineAnalytics.pruneEvents": {
23274
+ capName: "pipeline-analytics",
23275
+ capScope: "device",
23276
+ addonId: null,
23277
+ access: "create"
23278
+ },
22782
23279
  "pipelineAnalytics.pruneEventsBefore": {
22783
23280
  capName: "pipeline-analytics",
22784
23281
  capScope: "device",
@@ -23541,6 +24038,12 @@ Object.freeze({
23541
24038
  addonId: null,
23542
24039
  access: "create"
23543
24040
  },
24041
+ "recording.deleteFootprint": {
24042
+ capName: "recording",
24043
+ capScope: "system",
24044
+ addonId: null,
24045
+ access: "delete"
24046
+ },
23544
24047
  "recording.getAvailability": {
23545
24048
  capName: "recording",
23546
24049
  capScope: "system",
@@ -23571,6 +24074,12 @@ Object.freeze({
23571
24074
  addonId: null,
23572
24075
  access: "view"
23573
24076
  },
24077
+ "recording.listOpsLog": {
24078
+ capName: "recording",
24079
+ capScope: "system",
24080
+ addonId: null,
24081
+ access: "view"
24082
+ },
23574
24083
  "recording.locateSegment": {
23575
24084
  capName: "recording",
23576
24085
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-decoder-ffmpeg",
3
- "version": "1.1.17",
3
+ "version": "1.1.19",
4
4
  "description": "Standalone ffmpeg-subprocess decoder fallback addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",