@camstack/addon-provider-hikvision 1.1.27 → 1.1.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -7514,6 +7514,62 @@ var RecordingConfigSchema = object({
7514
7514
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7515
7515
  });
7516
7516
  /**
7517
+ * Ops-log — the durable, append-only operations audit shared by the
7518
+ * recordings and events management surfaces.
7519
+ *
7520
+ * ONE row shape is reused for both domains so a single "Activity" view can
7521
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7522
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7523
+ * management operation, WHY it ran (reason), and its measurable effect
7524
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7525
+ * never fail the operation it records.
7526
+ */
7527
+ /** Which management domain the operation belongs to. */
7528
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7529
+ /** The kind of management operation performed. */
7530
+ var OpsLogOpSchema = _enum([
7531
+ "prune",
7532
+ "manual-delete",
7533
+ "rescan",
7534
+ "retention-run"
7535
+ ]);
7536
+ /** Why the operation ran. */
7537
+ var OpsLogReasonSchema = _enum([
7538
+ "retention",
7539
+ "quota",
7540
+ "manual",
7541
+ "operator"
7542
+ ]);
7543
+ /** One audit row, shared verbatim by both domains. */
7544
+ var OpsLogEntrySchema = object({
7545
+ /** Unique row id. */
7546
+ id: string(),
7547
+ /** Epoch ms the operation completed. */
7548
+ at: number(),
7549
+ domain: OpsLogDomainSchema,
7550
+ op: OpsLogOpSchema,
7551
+ reason: OpsLogReasonSchema,
7552
+ /** The camera the op targeted; null for a cluster/global op. */
7553
+ deviceId: number().nullable(),
7554
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7555
+ nodeId: string(),
7556
+ /** Buckets / rows deleted (op-specific unit). */
7557
+ itemsAffected: number(),
7558
+ /** Bytes reclaimed by the op (0 when not measurable). */
7559
+ bytesReclaimed: number(),
7560
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7561
+ detail: string().nullable(),
7562
+ /** Who/what triggered the op. */
7563
+ actor: string()
7564
+ });
7565
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7566
+ var OpsLogQueryInputSchema = object({
7567
+ /** Restrict to a single camera; omit for every row. */
7568
+ deviceId: number().optional(),
7569
+ /** Max rows returned, newest-first. */
7570
+ limit: number().int().min(1).max(1e3).optional()
7571
+ });
7572
+ /**
7517
7573
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7518
7574
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7519
7575
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7757,6 +7813,160 @@ var EncodeProfileSchema = object({
7757
7813
  */
7758
7814
  outputArgs: array(string()).optional()
7759
7815
  });
7816
+ var COCO_TO_MACRO = {
7817
+ mapping: {
7818
+ person: "person",
7819
+ bicycle: "vehicle",
7820
+ car: "vehicle",
7821
+ motorcycle: "vehicle",
7822
+ airplane: "vehicle",
7823
+ bus: "vehicle",
7824
+ train: "vehicle",
7825
+ truck: "vehicle",
7826
+ boat: "vehicle",
7827
+ bird: "animal",
7828
+ cat: "animal",
7829
+ dog: "animal",
7830
+ horse: "animal",
7831
+ sheep: "animal",
7832
+ cow: "animal",
7833
+ elephant: "animal",
7834
+ bear: "animal",
7835
+ zebra: "animal",
7836
+ giraffe: "animal",
7837
+ suitcase: "package",
7838
+ backpack: "package",
7839
+ handbag: "package"
7840
+ },
7841
+ preserveOriginal: false
7842
+ };
7843
+ var AUDIO_MACRO_LABELS = [
7844
+ {
7845
+ id: "speech",
7846
+ name: "Speech",
7847
+ icon: "🗣️"
7848
+ },
7849
+ {
7850
+ id: "scream",
7851
+ name: "Scream / Shout",
7852
+ icon: "😱"
7853
+ },
7854
+ {
7855
+ id: "crying",
7856
+ name: "Crying / Baby",
7857
+ icon: "😢"
7858
+ },
7859
+ {
7860
+ id: "laughter",
7861
+ name: "Laughter",
7862
+ icon: "😂"
7863
+ },
7864
+ {
7865
+ id: "music",
7866
+ name: "Music",
7867
+ icon: "🎵"
7868
+ },
7869
+ {
7870
+ id: "dog",
7871
+ name: "Dog",
7872
+ icon: "🐕"
7873
+ },
7874
+ {
7875
+ id: "cat",
7876
+ name: "Cat",
7877
+ icon: "🐈"
7878
+ },
7879
+ {
7880
+ id: "bird",
7881
+ name: "Bird",
7882
+ icon: "🐦"
7883
+ },
7884
+ {
7885
+ id: "animal",
7886
+ name: "Animal (other)",
7887
+ icon: "🐾"
7888
+ },
7889
+ {
7890
+ id: "alarm",
7891
+ name: "Alarm / Siren",
7892
+ icon: "🚨"
7893
+ },
7894
+ {
7895
+ id: "doorbell",
7896
+ name: "Doorbell / Knock",
7897
+ icon: "🔔"
7898
+ },
7899
+ {
7900
+ id: "glass_breaking",
7901
+ name: "Glass Breaking",
7902
+ icon: "💥"
7903
+ },
7904
+ {
7905
+ id: "gunshot",
7906
+ name: "Gunshot / Explosion",
7907
+ icon: "💣"
7908
+ },
7909
+ {
7910
+ id: "vehicle",
7911
+ name: "Vehicle",
7912
+ icon: "🚗"
7913
+ },
7914
+ {
7915
+ id: "siren",
7916
+ name: "Emergency Siren",
7917
+ icon: "🚑"
7918
+ },
7919
+ {
7920
+ id: "fire",
7921
+ name: "Fire / Smoke",
7922
+ icon: "🔥"
7923
+ },
7924
+ {
7925
+ id: "water",
7926
+ name: "Water",
7927
+ icon: "💧"
7928
+ },
7929
+ {
7930
+ id: "wind",
7931
+ name: "Wind / Weather",
7932
+ icon: "🌬️"
7933
+ },
7934
+ {
7935
+ id: "door",
7936
+ name: "Door",
7937
+ icon: "🚪"
7938
+ },
7939
+ {
7940
+ id: "footsteps",
7941
+ name: "Footsteps",
7942
+ icon: "👣"
7943
+ },
7944
+ {
7945
+ id: "crowd",
7946
+ name: "Crowd / Chatter",
7947
+ icon: "👥"
7948
+ },
7949
+ {
7950
+ id: "telephone",
7951
+ name: "Telephone",
7952
+ icon: "📞"
7953
+ },
7954
+ {
7955
+ id: "engine",
7956
+ name: "Engine / Motor",
7957
+ icon: "⚙️"
7958
+ },
7959
+ {
7960
+ id: "tools",
7961
+ name: "Tools / Construction",
7962
+ icon: "🔨"
7963
+ },
7964
+ {
7965
+ id: "silence",
7966
+ name: "Silence",
7967
+ icon: "🤫"
7968
+ }
7969
+ ];
7760
7970
  var YAMNET_TO_MACRO = {
7761
7971
  mapping: {
7762
7972
  Speech: "speech",
@@ -8023,6 +8233,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8023
8233
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8024
8234
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8025
8235
  /**
8236
+ * Unified event-kind taxonomy — THE single source of truth for
8237
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8238
+ * icon }`.
8239
+ *
8240
+ * This dictionary folds together what used to be scattered across four
8241
+ * copies:
8242
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8243
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8244
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8245
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8246
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8247
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8248
+ *
8249
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8250
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8251
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8252
+ * color or an icon: they read this dictionary (server descriptors carry the
8253
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8254
+ *
8255
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8256
+ */
8257
+ var TAXONOMY_COLORS = {
8258
+ motion: "#f59e0b",
8259
+ audio: "#06b6d4",
8260
+ person: "#22c55e",
8261
+ vehicle: "#3b82f6",
8262
+ animal: "#f97316",
8263
+ package: "#a855f7",
8264
+ sensor: "#8b5cf6",
8265
+ control: "#10b981",
8266
+ genericDetection: "#64748b"
8267
+ };
8268
+ var DETECTION_SUB_COLORS = {
8269
+ car: "#f59e0b",
8270
+ truck: "#d97706",
8271
+ bus: "#b45309",
8272
+ motorcycle: "#eab308",
8273
+ bicycle: "#ca8a04",
8274
+ airplane: "#60a5fa",
8275
+ boat: "#2563eb",
8276
+ train: "#1d4ed8",
8277
+ bird: "#14b8a6",
8278
+ dog: "#84cc16",
8279
+ cat: "#f97316",
8280
+ horse: "#a16207",
8281
+ sheep: "#a3a3a3",
8282
+ cow: "#78716c",
8283
+ elephant: "#6b7280",
8284
+ bear: "#7c2d12",
8285
+ zebra: "#404040",
8286
+ giraffe: "#d4a373"
8287
+ };
8288
+ function titleCase(id) {
8289
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8290
+ }
8291
+ var entries = /* @__PURE__ */ new Map();
8292
+ function macro(kind, category, color, iconId, label) {
8293
+ entries.set(kind, {
8294
+ kind,
8295
+ parentKind: null,
8296
+ level: "macro",
8297
+ category,
8298
+ color,
8299
+ iconId,
8300
+ labelKey: `eventKind.${kind}`,
8301
+ label
8302
+ });
8303
+ }
8304
+ function sub(kind, parentKind, category, color, iconId, label) {
8305
+ entries.set(kind, {
8306
+ kind,
8307
+ parentKind,
8308
+ level: "sub",
8309
+ category,
8310
+ color,
8311
+ iconId,
8312
+ labelKey: `eventKind.${kind}`,
8313
+ label
8314
+ });
8315
+ }
8316
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8317
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8318
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8319
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8320
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8321
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8322
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8323
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8324
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8325
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8326
+ if (entries.has(cocoClass)) continue;
8327
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8328
+ }
8329
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8330
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8331
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8332
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8333
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8334
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8335
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8336
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8337
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8338
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8339
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8340
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8341
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8342
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8343
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8344
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8345
+ sub("button", "control", "control", "#10b981", "button", "Button");
8346
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8347
+ for (const l of AUDIO_MACRO_LABELS) {
8348
+ const kind = `audio-${l.id}`;
8349
+ if (entries.has(kind)) continue;
8350
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8351
+ }
8352
+ /** The complete taxonomy dictionary, keyed by kind. */
8353
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8354
+ /**
8026
8355
  * Error types for the safe expression engine. Two distinct classes so callers
8027
8356
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8028
8357
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15082,9 +15411,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
15082
15411
  * `package` backs the package-drop detector — a package zone is a
15083
15412
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
15084
15413
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15085
- * The orchestrator provider that writes this stage lands in a later
15086
- * slice; until then the mirror carries only `{motion, detection}` and
15087
- * consumers read `package` as absent (treat as `[]`).
15414
+ * The orchestrator provider writes this stage as a first-class slice
15415
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15416
+ * package}` shape, so consumers read the current package rules directly
15417
+ * off `device.state.zoneRules.value.package`.
15088
15418
  */
15089
15419
  runtimeState: object({
15090
15420
  motion: array(ZoneRuleSchema).readonly(),
@@ -19071,17 +19401,30 @@ var EventKindCategorySchema = _enum([
19071
19401
  "audio",
19072
19402
  "detection",
19073
19403
  "sensor",
19404
+ "control",
19074
19405
  "custom",
19075
19406
  "package"
19076
19407
  ]);
19408
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19409
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19077
19410
  var EventKindDescriptorSchema = object({
19078
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19411
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19079
19412
  kind: string(),
19413
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19414
+ labelKey: string(),
19415
+ /** English fallback label (kept for clients that don't translate). */
19080
19416
  label: string(),
19081
19417
  /** Hex color for timeline/legend rendering. */
19082
19418
  color: string(),
19419
+ /** Dictionary id → lucide component on the UI side. */
19420
+ iconId: string(),
19421
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19083
19422
  icon: EventKindIconSchema,
19084
19423
  category: EventKindCategorySchema,
19424
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19425
+ parentKind: string().nullable(),
19426
+ /** Derived from `parentKind`, explicit for the client tree. */
19427
+ level: EventKindLevelSchema,
19085
19428
  /** Which cap + device contributes this kind. For built-ins the camera
19086
19429
  * itself; for sensor kinds the LINKED source device. */
19087
19430
  source: object({
@@ -19154,11 +19497,21 @@ var TrackAudioLabelSchema = object({
19154
19497
  firstAt: number(),
19155
19498
  lastAt: number()
19156
19499
  });
19500
+ /**
19501
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19502
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19503
+ * linked sensor/control state change (no positions; carries a snapshot). The
19504
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19505
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19506
+ */
19507
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19157
19508
  var TrackSchema = object({
19158
19509
  trackId: string(),
19159
19510
  deviceId: number(),
19160
19511
  className: string(),
19161
19512
  label: string().optional(),
19513
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19514
+ source: TrackSourceSchema.optional(),
19162
19515
  firstSeen: number(),
19163
19516
  lastSeen: number(),
19164
19517
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19413,6 +19766,26 @@ var TrackCascadeCountsSchema = object({
19413
19766
  /** Per-track CLIP search vectors removed (best-effort). */
19414
19767
  embeddings: number().int()
19415
19768
  });
19769
+ /** Event-store footprint for one camera. */
19770
+ var EventStoreDeviceFootprintSchema = object({
19771
+ deviceId: number(),
19772
+ /** Persisted event rows (motion + object + audio) for the camera. */
19773
+ rows: number().int(),
19774
+ /** Event-owned media bytes on disk for the camera. */
19775
+ bytes: number().int()
19776
+ });
19777
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19778
+ var EventStoreFootprintSchema = object({
19779
+ totalRows: number().int(),
19780
+ totalBytes: number().int(),
19781
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19782
+ });
19783
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19784
+ var EventPruneCountsSchema = object({
19785
+ motion: number().int(),
19786
+ object: number().int(),
19787
+ audio: number().int()
19788
+ });
19416
19789
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19417
19790
  deviceId: number(),
19418
19791
  trackId: string()
@@ -19476,6 +19849,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19476
19849
  }), {
19477
19850
  kind: "mutation",
19478
19851
  auth: "admin"
19852
+ }), method(object({}), EventStoreFootprintSchema, {
19853
+ kind: "query",
19854
+ auth: "admin"
19855
+ }), method(object({
19856
+ olderThanMs: number(),
19857
+ reason: OpsLogReasonSchema.optional()
19858
+ }), EventPruneCountsSchema, {
19859
+ kind: "mutation",
19860
+ auth: "admin"
19861
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19862
+ kind: "mutation",
19863
+ auth: "admin"
19864
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19865
+ kind: "query",
19866
+ auth: "admin"
19479
19867
  }), method(object({
19480
19868
  eventId: string(),
19481
19869
  kind: MediaFileKindEnum.optional()
@@ -19500,6 +19888,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19500
19888
  eventId: string(),
19501
19889
  timestamp: number()
19502
19890
  });
19891
+ /**
19892
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19893
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19894
+ * caps into per-camera event-kind descriptors.
19895
+ *
19896
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19897
+ * is NOT duplicated here — every entry is derived from the single
19898
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19899
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19900
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19901
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19902
+ * eventful cap is missing.
19903
+ */
19904
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19905
+ var LEGACY_ICON = {
19906
+ motion: "motion",
19907
+ audio: "audio",
19908
+ person: "person",
19909
+ vehicle: "vehicle",
19910
+ animal: "animal",
19911
+ package: "package",
19912
+ door: "door",
19913
+ pir: "pir",
19914
+ smoke: "smoke",
19915
+ water: "water",
19916
+ button: "button",
19917
+ generic: "generic",
19918
+ gas: "smoke",
19919
+ vibration: "generic",
19920
+ tamper: "generic",
19921
+ presence: "person",
19922
+ lock: "generic",
19923
+ siren: "generic",
19924
+ switch: "generic",
19925
+ doorbell: "button"
19926
+ };
19927
+ function legacyIcon(iconId) {
19928
+ return LEGACY_ICON[iconId] ?? "generic";
19929
+ }
19930
+ /**
19931
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19932
+ * The anti-drift guard cross-checks this against the eventful caps declared
19933
+ * in `packages/types/src/capabilities/*.cap.ts`.
19934
+ */
19935
+ var CAP_TO_KIND = {
19936
+ contact: "contact",
19937
+ motion: "motion-sensor",
19938
+ smoke: "smoke",
19939
+ flood: "flood",
19940
+ gas: "gas",
19941
+ "carbon-monoxide": "carbon-monoxide",
19942
+ vibration: "vibration",
19943
+ tamper: "tamper",
19944
+ presence: "presence",
19945
+ "enum-sensor": "enum-sensor",
19946
+ "event-emitter": "device-event",
19947
+ "lock-control": "lock",
19948
+ switch: "switch",
19949
+ button: "button",
19950
+ doorbell: "doorbell"
19951
+ };
19952
+ function buildDescriptor(capName, kind) {
19953
+ const t = EVENT_TAXONOMY[kind];
19954
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19955
+ return {
19956
+ ...t,
19957
+ icon: legacyIcon(t.iconId)
19958
+ };
19959
+ }
19960
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19503
19961
  var CameraPipelineConfigSchema = object({
19504
19962
  engine: PipelineEngineChoiceSchema.optional(),
19505
19963
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22924,13 +23382,29 @@ method(object({
22924
23382
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22925
23383
  kind: "mutation",
22926
23384
  auth: "admin"
22927
- }), method(object({ deviceId: number() }), object({
23385
+ }), method(object({
23386
+ deviceId: number(),
23387
+ reason: OpsLogReasonSchema.optional()
23388
+ }), object({
22928
23389
  floorMs: number().nullable(),
22929
23390
  deletedBuckets: number().int(),
22930
23391
  reclaimedBytes: number().int()
22931
23392
  }), {
22932
23393
  kind: "mutation",
22933
23394
  auth: "admin"
23395
+ }), method(object({
23396
+ deviceId: number(),
23397
+ fromMs: number().optional(),
23398
+ toMs: number().optional()
23399
+ }), object({
23400
+ deletedBuckets: number().int(),
23401
+ reclaimedBytes: number().int()
23402
+ }), {
23403
+ kind: "mutation",
23404
+ auth: "admin"
23405
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23406
+ kind: "query",
23407
+ auth: "admin"
22934
23408
  });
22935
23409
  /**
22936
23410
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -26263,6 +26737,12 @@ Object.freeze({
26263
26737
  addonId: null,
26264
26738
  access: "delete"
26265
26739
  },
26740
+ "pipelineAnalytics.deleteDeviceEvents": {
26741
+ capName: "pipeline-analytics",
26742
+ capScope: "device",
26743
+ addonId: null,
26744
+ access: "delete"
26745
+ },
26266
26746
  "pipelineAnalytics.deleteTracks": {
26267
26747
  capName: "pipeline-analytics",
26268
26748
  capScope: "device",
@@ -26293,6 +26773,12 @@ Object.freeze({
26293
26773
  addonId: null,
26294
26774
  access: "view"
26295
26775
  },
26776
+ "pipelineAnalytics.getEventStoreFootprint": {
26777
+ capName: "pipeline-analytics",
26778
+ capScope: "device",
26779
+ addonId: null,
26780
+ access: "view"
26781
+ },
26296
26782
  "pipelineAnalytics.getKeyEvents": {
26297
26783
  capName: "pipeline-analytics",
26298
26784
  capScope: "device",
@@ -26335,6 +26821,12 @@ Object.freeze({
26335
26821
  addonId: null,
26336
26822
  access: "view"
26337
26823
  },
26824
+ "pipelineAnalytics.listOpsLog": {
26825
+ capName: "pipeline-analytics",
26826
+ capScope: "device",
26827
+ addonId: null,
26828
+ access: "view"
26829
+ },
26338
26830
  "pipelineAnalytics.listRecentTracks": {
26339
26831
  capName: "pipeline-analytics",
26340
26832
  capScope: "device",
@@ -26347,6 +26839,12 @@ Object.freeze({
26347
26839
  addonId: null,
26348
26840
  access: "view"
26349
26841
  },
26842
+ "pipelineAnalytics.pruneEvents": {
26843
+ capName: "pipeline-analytics",
26844
+ capScope: "device",
26845
+ addonId: null,
26846
+ access: "create"
26847
+ },
26350
26848
  "pipelineAnalytics.pruneEventsBefore": {
26351
26849
  capName: "pipeline-analytics",
26352
26850
  capScope: "device",
@@ -27109,6 +27607,12 @@ Object.freeze({
27109
27607
  addonId: null,
27110
27608
  access: "create"
27111
27609
  },
27610
+ "recording.deleteFootprint": {
27611
+ capName: "recording",
27612
+ capScope: "system",
27613
+ addonId: null,
27614
+ access: "delete"
27615
+ },
27112
27616
  "recording.getAvailability": {
27113
27617
  capName: "recording",
27114
27618
  capScope: "system",
@@ -27139,6 +27643,12 @@ Object.freeze({
27139
27643
  addonId: null,
27140
27644
  access: "view"
27141
27645
  },
27646
+ "recording.listOpsLog": {
27647
+ capName: "recording",
27648
+ capScope: "system",
27649
+ addonId: null,
27650
+ access: "view"
27651
+ },
27142
27652
  "recording.locateSegment": {
27143
27653
  capName: "recording",
27144
27654
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7515,6 +7515,62 @@ var RecordingConfigSchema = object({
7515
7515
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7516
7516
  });
7517
7517
  /**
7518
+ * Ops-log — the durable, append-only operations audit shared by the
7519
+ * recordings and events management surfaces.
7520
+ *
7521
+ * ONE row shape is reused for both domains so a single "Activity" view can
7522
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7523
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7524
+ * management operation, WHY it ran (reason), and its measurable effect
7525
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7526
+ * never fail the operation it records.
7527
+ */
7528
+ /** Which management domain the operation belongs to. */
7529
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7530
+ /** The kind of management operation performed. */
7531
+ var OpsLogOpSchema = _enum([
7532
+ "prune",
7533
+ "manual-delete",
7534
+ "rescan",
7535
+ "retention-run"
7536
+ ]);
7537
+ /** Why the operation ran. */
7538
+ var OpsLogReasonSchema = _enum([
7539
+ "retention",
7540
+ "quota",
7541
+ "manual",
7542
+ "operator"
7543
+ ]);
7544
+ /** One audit row, shared verbatim by both domains. */
7545
+ var OpsLogEntrySchema = object({
7546
+ /** Unique row id. */
7547
+ id: string(),
7548
+ /** Epoch ms the operation completed. */
7549
+ at: number(),
7550
+ domain: OpsLogDomainSchema,
7551
+ op: OpsLogOpSchema,
7552
+ reason: OpsLogReasonSchema,
7553
+ /** The camera the op targeted; null for a cluster/global op. */
7554
+ deviceId: number().nullable(),
7555
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7556
+ nodeId: string(),
7557
+ /** Buckets / rows deleted (op-specific unit). */
7558
+ itemsAffected: number(),
7559
+ /** Bytes reclaimed by the op (0 when not measurable). */
7560
+ bytesReclaimed: number(),
7561
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7562
+ detail: string().nullable(),
7563
+ /** Who/what triggered the op. */
7564
+ actor: string()
7565
+ });
7566
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7567
+ var OpsLogQueryInputSchema = object({
7568
+ /** Restrict to a single camera; omit for every row. */
7569
+ deviceId: number().optional(),
7570
+ /** Max rows returned, newest-first. */
7571
+ limit: number().int().min(1).max(1e3).optional()
7572
+ });
7573
+ /**
7518
7574
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7519
7575
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7520
7576
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7758,6 +7814,160 @@ var EncodeProfileSchema = object({
7758
7814
  */
7759
7815
  outputArgs: array(string()).optional()
7760
7816
  });
7817
+ var COCO_TO_MACRO = {
7818
+ mapping: {
7819
+ person: "person",
7820
+ bicycle: "vehicle",
7821
+ car: "vehicle",
7822
+ motorcycle: "vehicle",
7823
+ airplane: "vehicle",
7824
+ bus: "vehicle",
7825
+ train: "vehicle",
7826
+ truck: "vehicle",
7827
+ boat: "vehicle",
7828
+ bird: "animal",
7829
+ cat: "animal",
7830
+ dog: "animal",
7831
+ horse: "animal",
7832
+ sheep: "animal",
7833
+ cow: "animal",
7834
+ elephant: "animal",
7835
+ bear: "animal",
7836
+ zebra: "animal",
7837
+ giraffe: "animal",
7838
+ suitcase: "package",
7839
+ backpack: "package",
7840
+ handbag: "package"
7841
+ },
7842
+ preserveOriginal: false
7843
+ };
7844
+ var AUDIO_MACRO_LABELS = [
7845
+ {
7846
+ id: "speech",
7847
+ name: "Speech",
7848
+ icon: "🗣️"
7849
+ },
7850
+ {
7851
+ id: "scream",
7852
+ name: "Scream / Shout",
7853
+ icon: "😱"
7854
+ },
7855
+ {
7856
+ id: "crying",
7857
+ name: "Crying / Baby",
7858
+ icon: "😢"
7859
+ },
7860
+ {
7861
+ id: "laughter",
7862
+ name: "Laughter",
7863
+ icon: "😂"
7864
+ },
7865
+ {
7866
+ id: "music",
7867
+ name: "Music",
7868
+ icon: "🎵"
7869
+ },
7870
+ {
7871
+ id: "dog",
7872
+ name: "Dog",
7873
+ icon: "🐕"
7874
+ },
7875
+ {
7876
+ id: "cat",
7877
+ name: "Cat",
7878
+ icon: "🐈"
7879
+ },
7880
+ {
7881
+ id: "bird",
7882
+ name: "Bird",
7883
+ icon: "🐦"
7884
+ },
7885
+ {
7886
+ id: "animal",
7887
+ name: "Animal (other)",
7888
+ icon: "🐾"
7889
+ },
7890
+ {
7891
+ id: "alarm",
7892
+ name: "Alarm / Siren",
7893
+ icon: "🚨"
7894
+ },
7895
+ {
7896
+ id: "doorbell",
7897
+ name: "Doorbell / Knock",
7898
+ icon: "🔔"
7899
+ },
7900
+ {
7901
+ id: "glass_breaking",
7902
+ name: "Glass Breaking",
7903
+ icon: "💥"
7904
+ },
7905
+ {
7906
+ id: "gunshot",
7907
+ name: "Gunshot / Explosion",
7908
+ icon: "💣"
7909
+ },
7910
+ {
7911
+ id: "vehicle",
7912
+ name: "Vehicle",
7913
+ icon: "🚗"
7914
+ },
7915
+ {
7916
+ id: "siren",
7917
+ name: "Emergency Siren",
7918
+ icon: "🚑"
7919
+ },
7920
+ {
7921
+ id: "fire",
7922
+ name: "Fire / Smoke",
7923
+ icon: "🔥"
7924
+ },
7925
+ {
7926
+ id: "water",
7927
+ name: "Water",
7928
+ icon: "💧"
7929
+ },
7930
+ {
7931
+ id: "wind",
7932
+ name: "Wind / Weather",
7933
+ icon: "🌬️"
7934
+ },
7935
+ {
7936
+ id: "door",
7937
+ name: "Door",
7938
+ icon: "🚪"
7939
+ },
7940
+ {
7941
+ id: "footsteps",
7942
+ name: "Footsteps",
7943
+ icon: "👣"
7944
+ },
7945
+ {
7946
+ id: "crowd",
7947
+ name: "Crowd / Chatter",
7948
+ icon: "👥"
7949
+ },
7950
+ {
7951
+ id: "telephone",
7952
+ name: "Telephone",
7953
+ icon: "📞"
7954
+ },
7955
+ {
7956
+ id: "engine",
7957
+ name: "Engine / Motor",
7958
+ icon: "⚙️"
7959
+ },
7960
+ {
7961
+ id: "tools",
7962
+ name: "Tools / Construction",
7963
+ icon: "🔨"
7964
+ },
7965
+ {
7966
+ id: "silence",
7967
+ name: "Silence",
7968
+ icon: "🤫"
7969
+ }
7970
+ ];
7761
7971
  var YAMNET_TO_MACRO = {
7762
7972
  mapping: {
7763
7973
  Speech: "speech",
@@ -8024,6 +8234,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8024
8234
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8025
8235
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8026
8236
  /**
8237
+ * Unified event-kind taxonomy — THE single source of truth for
8238
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8239
+ * icon }`.
8240
+ *
8241
+ * This dictionary folds together what used to be scattered across four
8242
+ * copies:
8243
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8244
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8245
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8246
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8247
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8248
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8249
+ *
8250
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8251
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8252
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8253
+ * color or an icon: they read this dictionary (server descriptors carry the
8254
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8255
+ *
8256
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8257
+ */
8258
+ var TAXONOMY_COLORS = {
8259
+ motion: "#f59e0b",
8260
+ audio: "#06b6d4",
8261
+ person: "#22c55e",
8262
+ vehicle: "#3b82f6",
8263
+ animal: "#f97316",
8264
+ package: "#a855f7",
8265
+ sensor: "#8b5cf6",
8266
+ control: "#10b981",
8267
+ genericDetection: "#64748b"
8268
+ };
8269
+ var DETECTION_SUB_COLORS = {
8270
+ car: "#f59e0b",
8271
+ truck: "#d97706",
8272
+ bus: "#b45309",
8273
+ motorcycle: "#eab308",
8274
+ bicycle: "#ca8a04",
8275
+ airplane: "#60a5fa",
8276
+ boat: "#2563eb",
8277
+ train: "#1d4ed8",
8278
+ bird: "#14b8a6",
8279
+ dog: "#84cc16",
8280
+ cat: "#f97316",
8281
+ horse: "#a16207",
8282
+ sheep: "#a3a3a3",
8283
+ cow: "#78716c",
8284
+ elephant: "#6b7280",
8285
+ bear: "#7c2d12",
8286
+ zebra: "#404040",
8287
+ giraffe: "#d4a373"
8288
+ };
8289
+ function titleCase(id) {
8290
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8291
+ }
8292
+ var entries = /* @__PURE__ */ new Map();
8293
+ function macro(kind, category, color, iconId, label) {
8294
+ entries.set(kind, {
8295
+ kind,
8296
+ parentKind: null,
8297
+ level: "macro",
8298
+ category,
8299
+ color,
8300
+ iconId,
8301
+ labelKey: `eventKind.${kind}`,
8302
+ label
8303
+ });
8304
+ }
8305
+ function sub(kind, parentKind, category, color, iconId, label) {
8306
+ entries.set(kind, {
8307
+ kind,
8308
+ parentKind,
8309
+ level: "sub",
8310
+ category,
8311
+ color,
8312
+ iconId,
8313
+ labelKey: `eventKind.${kind}`,
8314
+ label
8315
+ });
8316
+ }
8317
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8318
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8319
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8320
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8321
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8322
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8323
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8324
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8325
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8326
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8327
+ if (entries.has(cocoClass)) continue;
8328
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8329
+ }
8330
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8331
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8332
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8333
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8334
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8335
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8336
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8337
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8338
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8339
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8340
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8341
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8342
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8343
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8344
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8345
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8346
+ sub("button", "control", "control", "#10b981", "button", "Button");
8347
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8348
+ for (const l of AUDIO_MACRO_LABELS) {
8349
+ const kind = `audio-${l.id}`;
8350
+ if (entries.has(kind)) continue;
8351
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8352
+ }
8353
+ /** The complete taxonomy dictionary, keyed by kind. */
8354
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8355
+ /**
8027
8356
  * Error types for the safe expression engine. Two distinct classes so callers
8028
8357
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8029
8358
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15083,9 +15412,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
15083
15412
  * `package` backs the package-drop detector — a package zone is a
15084
15413
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
15085
15414
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15086
- * The orchestrator provider that writes this stage lands in a later
15087
- * slice; until then the mirror carries only `{motion, detection}` and
15088
- * consumers read `package` as absent (treat as `[]`).
15415
+ * The orchestrator provider writes this stage as a first-class slice
15416
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15417
+ * package}` shape, so consumers read the current package rules directly
15418
+ * off `device.state.zoneRules.value.package`.
15089
15419
  */
15090
15420
  runtimeState: object({
15091
15421
  motion: array(ZoneRuleSchema).readonly(),
@@ -19072,17 +19402,30 @@ var EventKindCategorySchema = _enum([
19072
19402
  "audio",
19073
19403
  "detection",
19074
19404
  "sensor",
19405
+ "control",
19075
19406
  "custom",
19076
19407
  "package"
19077
19408
  ]);
19409
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19410
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19078
19411
  var EventKindDescriptorSchema = object({
19079
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19412
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19080
19413
  kind: string(),
19414
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19415
+ labelKey: string(),
19416
+ /** English fallback label (kept for clients that don't translate). */
19081
19417
  label: string(),
19082
19418
  /** Hex color for timeline/legend rendering. */
19083
19419
  color: string(),
19420
+ /** Dictionary id → lucide component on the UI side. */
19421
+ iconId: string(),
19422
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19084
19423
  icon: EventKindIconSchema,
19085
19424
  category: EventKindCategorySchema,
19425
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19426
+ parentKind: string().nullable(),
19427
+ /** Derived from `parentKind`, explicit for the client tree. */
19428
+ level: EventKindLevelSchema,
19086
19429
  /** Which cap + device contributes this kind. For built-ins the camera
19087
19430
  * itself; for sensor kinds the LINKED source device. */
19088
19431
  source: object({
@@ -19155,11 +19498,21 @@ var TrackAudioLabelSchema = object({
19155
19498
  firstAt: number(),
19156
19499
  lastAt: number()
19157
19500
  });
19501
+ /**
19502
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19503
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19504
+ * linked sensor/control state change (no positions; carries a snapshot). The
19505
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19506
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19507
+ */
19508
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19158
19509
  var TrackSchema = object({
19159
19510
  trackId: string(),
19160
19511
  deviceId: number(),
19161
19512
  className: string(),
19162
19513
  label: string().optional(),
19514
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19515
+ source: TrackSourceSchema.optional(),
19163
19516
  firstSeen: number(),
19164
19517
  lastSeen: number(),
19165
19518
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19414,6 +19767,26 @@ var TrackCascadeCountsSchema = object({
19414
19767
  /** Per-track CLIP search vectors removed (best-effort). */
19415
19768
  embeddings: number().int()
19416
19769
  });
19770
+ /** Event-store footprint for one camera. */
19771
+ var EventStoreDeviceFootprintSchema = object({
19772
+ deviceId: number(),
19773
+ /** Persisted event rows (motion + object + audio) for the camera. */
19774
+ rows: number().int(),
19775
+ /** Event-owned media bytes on disk for the camera. */
19776
+ bytes: number().int()
19777
+ });
19778
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19779
+ var EventStoreFootprintSchema = object({
19780
+ totalRows: number().int(),
19781
+ totalBytes: number().int(),
19782
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19783
+ });
19784
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19785
+ var EventPruneCountsSchema = object({
19786
+ motion: number().int(),
19787
+ object: number().int(),
19788
+ audio: number().int()
19789
+ });
19417
19790
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19418
19791
  deviceId: number(),
19419
19792
  trackId: string()
@@ -19477,6 +19850,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19477
19850
  }), {
19478
19851
  kind: "mutation",
19479
19852
  auth: "admin"
19853
+ }), method(object({}), EventStoreFootprintSchema, {
19854
+ kind: "query",
19855
+ auth: "admin"
19856
+ }), method(object({
19857
+ olderThanMs: number(),
19858
+ reason: OpsLogReasonSchema.optional()
19859
+ }), EventPruneCountsSchema, {
19860
+ kind: "mutation",
19861
+ auth: "admin"
19862
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19863
+ kind: "mutation",
19864
+ auth: "admin"
19865
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19866
+ kind: "query",
19867
+ auth: "admin"
19480
19868
  }), method(object({
19481
19869
  eventId: string(),
19482
19870
  kind: MediaFileKindEnum.optional()
@@ -19501,6 +19889,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19501
19889
  eventId: string(),
19502
19890
  timestamp: number()
19503
19891
  });
19892
+ /**
19893
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19894
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19895
+ * caps into per-camera event-kind descriptors.
19896
+ *
19897
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19898
+ * is NOT duplicated here — every entry is derived from the single
19899
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19900
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19901
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19902
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19903
+ * eventful cap is missing.
19904
+ */
19905
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19906
+ var LEGACY_ICON = {
19907
+ motion: "motion",
19908
+ audio: "audio",
19909
+ person: "person",
19910
+ vehicle: "vehicle",
19911
+ animal: "animal",
19912
+ package: "package",
19913
+ door: "door",
19914
+ pir: "pir",
19915
+ smoke: "smoke",
19916
+ water: "water",
19917
+ button: "button",
19918
+ generic: "generic",
19919
+ gas: "smoke",
19920
+ vibration: "generic",
19921
+ tamper: "generic",
19922
+ presence: "person",
19923
+ lock: "generic",
19924
+ siren: "generic",
19925
+ switch: "generic",
19926
+ doorbell: "button"
19927
+ };
19928
+ function legacyIcon(iconId) {
19929
+ return LEGACY_ICON[iconId] ?? "generic";
19930
+ }
19931
+ /**
19932
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19933
+ * The anti-drift guard cross-checks this against the eventful caps declared
19934
+ * in `packages/types/src/capabilities/*.cap.ts`.
19935
+ */
19936
+ var CAP_TO_KIND = {
19937
+ contact: "contact",
19938
+ motion: "motion-sensor",
19939
+ smoke: "smoke",
19940
+ flood: "flood",
19941
+ gas: "gas",
19942
+ "carbon-monoxide": "carbon-monoxide",
19943
+ vibration: "vibration",
19944
+ tamper: "tamper",
19945
+ presence: "presence",
19946
+ "enum-sensor": "enum-sensor",
19947
+ "event-emitter": "device-event",
19948
+ "lock-control": "lock",
19949
+ switch: "switch",
19950
+ button: "button",
19951
+ doorbell: "doorbell"
19952
+ };
19953
+ function buildDescriptor(capName, kind) {
19954
+ const t = EVENT_TAXONOMY[kind];
19955
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19956
+ return {
19957
+ ...t,
19958
+ icon: legacyIcon(t.iconId)
19959
+ };
19960
+ }
19961
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19504
19962
  var CameraPipelineConfigSchema = object({
19505
19963
  engine: PipelineEngineChoiceSchema.optional(),
19506
19964
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22925,13 +23383,29 @@ method(object({
22925
23383
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22926
23384
  kind: "mutation",
22927
23385
  auth: "admin"
22928
- }), method(object({ deviceId: number() }), object({
23386
+ }), method(object({
23387
+ deviceId: number(),
23388
+ reason: OpsLogReasonSchema.optional()
23389
+ }), object({
22929
23390
  floorMs: number().nullable(),
22930
23391
  deletedBuckets: number().int(),
22931
23392
  reclaimedBytes: number().int()
22932
23393
  }), {
22933
23394
  kind: "mutation",
22934
23395
  auth: "admin"
23396
+ }), method(object({
23397
+ deviceId: number(),
23398
+ fromMs: number().optional(),
23399
+ toMs: number().optional()
23400
+ }), object({
23401
+ deletedBuckets: number().int(),
23402
+ reclaimedBytes: number().int()
23403
+ }), {
23404
+ kind: "mutation",
23405
+ auth: "admin"
23406
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23407
+ kind: "query",
23408
+ auth: "admin"
22935
23409
  });
22936
23410
  /**
22937
23411
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -26264,6 +26738,12 @@ Object.freeze({
26264
26738
  addonId: null,
26265
26739
  access: "delete"
26266
26740
  },
26741
+ "pipelineAnalytics.deleteDeviceEvents": {
26742
+ capName: "pipeline-analytics",
26743
+ capScope: "device",
26744
+ addonId: null,
26745
+ access: "delete"
26746
+ },
26267
26747
  "pipelineAnalytics.deleteTracks": {
26268
26748
  capName: "pipeline-analytics",
26269
26749
  capScope: "device",
@@ -26294,6 +26774,12 @@ Object.freeze({
26294
26774
  addonId: null,
26295
26775
  access: "view"
26296
26776
  },
26777
+ "pipelineAnalytics.getEventStoreFootprint": {
26778
+ capName: "pipeline-analytics",
26779
+ capScope: "device",
26780
+ addonId: null,
26781
+ access: "view"
26782
+ },
26297
26783
  "pipelineAnalytics.getKeyEvents": {
26298
26784
  capName: "pipeline-analytics",
26299
26785
  capScope: "device",
@@ -26336,6 +26822,12 @@ Object.freeze({
26336
26822
  addonId: null,
26337
26823
  access: "view"
26338
26824
  },
26825
+ "pipelineAnalytics.listOpsLog": {
26826
+ capName: "pipeline-analytics",
26827
+ capScope: "device",
26828
+ addonId: null,
26829
+ access: "view"
26830
+ },
26339
26831
  "pipelineAnalytics.listRecentTracks": {
26340
26832
  capName: "pipeline-analytics",
26341
26833
  capScope: "device",
@@ -26348,6 +26840,12 @@ Object.freeze({
26348
26840
  addonId: null,
26349
26841
  access: "view"
26350
26842
  },
26843
+ "pipelineAnalytics.pruneEvents": {
26844
+ capName: "pipeline-analytics",
26845
+ capScope: "device",
26846
+ addonId: null,
26847
+ access: "create"
26848
+ },
26351
26849
  "pipelineAnalytics.pruneEventsBefore": {
26352
26850
  capName: "pipeline-analytics",
26353
26851
  capScope: "device",
@@ -27110,6 +27608,12 @@ Object.freeze({
27110
27608
  addonId: null,
27111
27609
  access: "create"
27112
27610
  },
27611
+ "recording.deleteFootprint": {
27612
+ capName: "recording",
27613
+ capScope: "system",
27614
+ addonId: null,
27615
+ access: "delete"
27616
+ },
27113
27617
  "recording.getAvailability": {
27114
27618
  capName: "recording",
27115
27619
  capScope: "system",
@@ -27140,6 +27644,12 @@ Object.freeze({
27140
27644
  addonId: null,
27141
27645
  access: "view"
27142
27646
  },
27647
+ "recording.listOpsLog": {
27648
+ capName: "recording",
27649
+ capScope: "system",
27650
+ addonId: null,
27651
+ access: "view"
27652
+ },
27143
27653
  "recording.locateSegment": {
27144
27654
  capName: "recording",
27145
27655
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.1.27",
3
+ "version": "1.1.29",
4
4
  "description": "Hikvision camera device provider addon for CamStack — ISAPI over HTTP(S) with digest auth (snapshot, alarm stream, RTSP discovery)",
5
5
  "keywords": [
6
6
  "camstack",