@camstack/addon-provider-reolink 1.1.28 → 1.1.30

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
@@ -7558,6 +7558,62 @@ var RecordingConfigSchema = object({
7558
7558
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7559
7559
  });
7560
7560
  /**
7561
+ * Ops-log — the durable, append-only operations audit shared by the
7562
+ * recordings and events management surfaces.
7563
+ *
7564
+ * ONE row shape is reused for both domains so a single "Activity" view can
7565
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7566
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7567
+ * management operation, WHY it ran (reason), and its measurable effect
7568
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7569
+ * never fail the operation it records.
7570
+ */
7571
+ /** Which management domain the operation belongs to. */
7572
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7573
+ /** The kind of management operation performed. */
7574
+ var OpsLogOpSchema = _enum([
7575
+ "prune",
7576
+ "manual-delete",
7577
+ "rescan",
7578
+ "retention-run"
7579
+ ]);
7580
+ /** Why the operation ran. */
7581
+ var OpsLogReasonSchema = _enum([
7582
+ "retention",
7583
+ "quota",
7584
+ "manual",
7585
+ "operator"
7586
+ ]);
7587
+ /** One audit row, shared verbatim by both domains. */
7588
+ var OpsLogEntrySchema = object({
7589
+ /** Unique row id. */
7590
+ id: string(),
7591
+ /** Epoch ms the operation completed. */
7592
+ at: number(),
7593
+ domain: OpsLogDomainSchema,
7594
+ op: OpsLogOpSchema,
7595
+ reason: OpsLogReasonSchema,
7596
+ /** The camera the op targeted; null for a cluster/global op. */
7597
+ deviceId: number().nullable(),
7598
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7599
+ nodeId: string(),
7600
+ /** Buckets / rows deleted (op-specific unit). */
7601
+ itemsAffected: number(),
7602
+ /** Bytes reclaimed by the op (0 when not measurable). */
7603
+ bytesReclaimed: number(),
7604
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7605
+ detail: string().nullable(),
7606
+ /** Who/what triggered the op. */
7607
+ actor: string()
7608
+ });
7609
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7610
+ var OpsLogQueryInputSchema = object({
7611
+ /** Restrict to a single camera; omit for every row. */
7612
+ deviceId: number().optional(),
7613
+ /** Max rows returned, newest-first. */
7614
+ limit: number().int().min(1).max(1e3).optional()
7615
+ });
7616
+ /**
7561
7617
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7562
7618
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7563
7619
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7801,6 +7857,160 @@ var EncodeProfileSchema = object({
7801
7857
  */
7802
7858
  outputArgs: array(string()).optional()
7803
7859
  });
7860
+ var COCO_TO_MACRO = {
7861
+ mapping: {
7862
+ person: "person",
7863
+ bicycle: "vehicle",
7864
+ car: "vehicle",
7865
+ motorcycle: "vehicle",
7866
+ airplane: "vehicle",
7867
+ bus: "vehicle",
7868
+ train: "vehicle",
7869
+ truck: "vehicle",
7870
+ boat: "vehicle",
7871
+ bird: "animal",
7872
+ cat: "animal",
7873
+ dog: "animal",
7874
+ horse: "animal",
7875
+ sheep: "animal",
7876
+ cow: "animal",
7877
+ elephant: "animal",
7878
+ bear: "animal",
7879
+ zebra: "animal",
7880
+ giraffe: "animal",
7881
+ suitcase: "package",
7882
+ backpack: "package",
7883
+ handbag: "package"
7884
+ },
7885
+ preserveOriginal: false
7886
+ };
7887
+ var AUDIO_MACRO_LABELS = [
7888
+ {
7889
+ id: "speech",
7890
+ name: "Speech",
7891
+ icon: "🗣️"
7892
+ },
7893
+ {
7894
+ id: "scream",
7895
+ name: "Scream / Shout",
7896
+ icon: "😱"
7897
+ },
7898
+ {
7899
+ id: "crying",
7900
+ name: "Crying / Baby",
7901
+ icon: "😢"
7902
+ },
7903
+ {
7904
+ id: "laughter",
7905
+ name: "Laughter",
7906
+ icon: "😂"
7907
+ },
7908
+ {
7909
+ id: "music",
7910
+ name: "Music",
7911
+ icon: "🎵"
7912
+ },
7913
+ {
7914
+ id: "dog",
7915
+ name: "Dog",
7916
+ icon: "🐕"
7917
+ },
7918
+ {
7919
+ id: "cat",
7920
+ name: "Cat",
7921
+ icon: "🐈"
7922
+ },
7923
+ {
7924
+ id: "bird",
7925
+ name: "Bird",
7926
+ icon: "🐦"
7927
+ },
7928
+ {
7929
+ id: "animal",
7930
+ name: "Animal (other)",
7931
+ icon: "🐾"
7932
+ },
7933
+ {
7934
+ id: "alarm",
7935
+ name: "Alarm / Siren",
7936
+ icon: "🚨"
7937
+ },
7938
+ {
7939
+ id: "doorbell",
7940
+ name: "Doorbell / Knock",
7941
+ icon: "🔔"
7942
+ },
7943
+ {
7944
+ id: "glass_breaking",
7945
+ name: "Glass Breaking",
7946
+ icon: "💥"
7947
+ },
7948
+ {
7949
+ id: "gunshot",
7950
+ name: "Gunshot / Explosion",
7951
+ icon: "💣"
7952
+ },
7953
+ {
7954
+ id: "vehicle",
7955
+ name: "Vehicle",
7956
+ icon: "🚗"
7957
+ },
7958
+ {
7959
+ id: "siren",
7960
+ name: "Emergency Siren",
7961
+ icon: "🚑"
7962
+ },
7963
+ {
7964
+ id: "fire",
7965
+ name: "Fire / Smoke",
7966
+ icon: "🔥"
7967
+ },
7968
+ {
7969
+ id: "water",
7970
+ name: "Water",
7971
+ icon: "💧"
7972
+ },
7973
+ {
7974
+ id: "wind",
7975
+ name: "Wind / Weather",
7976
+ icon: "🌬️"
7977
+ },
7978
+ {
7979
+ id: "door",
7980
+ name: "Door",
7981
+ icon: "🚪"
7982
+ },
7983
+ {
7984
+ id: "footsteps",
7985
+ name: "Footsteps",
7986
+ icon: "👣"
7987
+ },
7988
+ {
7989
+ id: "crowd",
7990
+ name: "Crowd / Chatter",
7991
+ icon: "👥"
7992
+ },
7993
+ {
7994
+ id: "telephone",
7995
+ name: "Telephone",
7996
+ icon: "📞"
7997
+ },
7998
+ {
7999
+ id: "engine",
8000
+ name: "Engine / Motor",
8001
+ icon: "⚙️"
8002
+ },
8003
+ {
8004
+ id: "tools",
8005
+ name: "Tools / Construction",
8006
+ icon: "🔨"
8007
+ },
8008
+ {
8009
+ id: "silence",
8010
+ name: "Silence",
8011
+ icon: "🤫"
8012
+ }
8013
+ ];
7804
8014
  var YAMNET_TO_MACRO = {
7805
8015
  mapping: {
7806
8016
  Speech: "speech",
@@ -8067,6 +8277,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8067
8277
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8068
8278
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8069
8279
  /**
8280
+ * Unified event-kind taxonomy — THE single source of truth for
8281
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8282
+ * icon }`.
8283
+ *
8284
+ * This dictionary folds together what used to be scattered across four
8285
+ * copies:
8286
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8287
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8288
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8289
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8290
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8291
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8292
+ *
8293
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8294
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8295
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8296
+ * color or an icon: they read this dictionary (server descriptors carry the
8297
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8298
+ *
8299
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8300
+ */
8301
+ var TAXONOMY_COLORS = {
8302
+ motion: "#f59e0b",
8303
+ audio: "#06b6d4",
8304
+ person: "#22c55e",
8305
+ vehicle: "#3b82f6",
8306
+ animal: "#f97316",
8307
+ package: "#a855f7",
8308
+ sensor: "#8b5cf6",
8309
+ control: "#10b981",
8310
+ genericDetection: "#64748b"
8311
+ };
8312
+ var DETECTION_SUB_COLORS = {
8313
+ car: "#f59e0b",
8314
+ truck: "#d97706",
8315
+ bus: "#b45309",
8316
+ motorcycle: "#eab308",
8317
+ bicycle: "#ca8a04",
8318
+ airplane: "#60a5fa",
8319
+ boat: "#2563eb",
8320
+ train: "#1d4ed8",
8321
+ bird: "#14b8a6",
8322
+ dog: "#84cc16",
8323
+ cat: "#f97316",
8324
+ horse: "#a16207",
8325
+ sheep: "#a3a3a3",
8326
+ cow: "#78716c",
8327
+ elephant: "#6b7280",
8328
+ bear: "#7c2d12",
8329
+ zebra: "#404040",
8330
+ giraffe: "#d4a373"
8331
+ };
8332
+ function titleCase(id) {
8333
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8334
+ }
8335
+ var entries = /* @__PURE__ */ new Map();
8336
+ function macro(kind, category, color, iconId, label) {
8337
+ entries.set(kind, {
8338
+ kind,
8339
+ parentKind: null,
8340
+ level: "macro",
8341
+ category,
8342
+ color,
8343
+ iconId,
8344
+ labelKey: `eventKind.${kind}`,
8345
+ label
8346
+ });
8347
+ }
8348
+ function sub(kind, parentKind, category, color, iconId, label) {
8349
+ entries.set(kind, {
8350
+ kind,
8351
+ parentKind,
8352
+ level: "sub",
8353
+ category,
8354
+ color,
8355
+ iconId,
8356
+ labelKey: `eventKind.${kind}`,
8357
+ label
8358
+ });
8359
+ }
8360
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8361
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8362
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8363
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8364
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8365
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8366
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8367
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8368
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8369
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8370
+ if (entries.has(cocoClass)) continue;
8371
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8372
+ }
8373
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8374
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8375
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8376
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8377
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8378
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8379
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8380
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8381
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8382
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8383
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8384
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8385
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8386
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8387
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8388
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8389
+ sub("button", "control", "control", "#10b981", "button", "Button");
8390
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8391
+ for (const l of AUDIO_MACRO_LABELS) {
8392
+ const kind = `audio-${l.id}`;
8393
+ if (entries.has(kind)) continue;
8394
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8395
+ }
8396
+ /** The complete taxonomy dictionary, keyed by kind. */
8397
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8398
+ /**
8070
8399
  * Error types for the safe expression engine. Two distinct classes so callers
8071
8400
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8072
8401
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15060,9 +15389,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
15060
15389
  * `package` backs the package-drop detector — a package zone is a
15061
15390
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
15062
15391
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15063
- * The orchestrator provider that writes this stage lands in a later
15064
- * slice; until then the mirror carries only `{motion, detection}` and
15065
- * consumers read `package` as absent (treat as `[]`).
15392
+ * The orchestrator provider writes this stage as a first-class slice
15393
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15394
+ * package}` shape, so consumers read the current package rules directly
15395
+ * off `device.state.zoneRules.value.package`.
15066
15396
  */
15067
15397
  runtimeState: object({
15068
15398
  motion: array(ZoneRuleSchema).readonly(),
@@ -19049,17 +19379,30 @@ var EventKindCategorySchema = _enum([
19049
19379
  "audio",
19050
19380
  "detection",
19051
19381
  "sensor",
19382
+ "control",
19052
19383
  "custom",
19053
19384
  "package"
19054
19385
  ]);
19386
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19387
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19055
19388
  var EventKindDescriptorSchema = object({
19056
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19389
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19057
19390
  kind: string(),
19391
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19392
+ labelKey: string(),
19393
+ /** English fallback label (kept for clients that don't translate). */
19058
19394
  label: string(),
19059
19395
  /** Hex color for timeline/legend rendering. */
19060
19396
  color: string(),
19397
+ /** Dictionary id → lucide component on the UI side. */
19398
+ iconId: string(),
19399
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19061
19400
  icon: EventKindIconSchema,
19062
19401
  category: EventKindCategorySchema,
19402
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19403
+ parentKind: string().nullable(),
19404
+ /** Derived from `parentKind`, explicit for the client tree. */
19405
+ level: EventKindLevelSchema,
19063
19406
  /** Which cap + device contributes this kind. For built-ins the camera
19064
19407
  * itself; for sensor kinds the LINKED source device. */
19065
19408
  source: object({
@@ -19132,11 +19475,21 @@ var TrackAudioLabelSchema = object({
19132
19475
  firstAt: number(),
19133
19476
  lastAt: number()
19134
19477
  });
19478
+ /**
19479
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19480
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19481
+ * linked sensor/control state change (no positions; carries a snapshot). The
19482
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19483
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19484
+ */
19485
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19135
19486
  var TrackSchema = object({
19136
19487
  trackId: string(),
19137
19488
  deviceId: number(),
19138
19489
  className: string(),
19139
19490
  label: string().optional(),
19491
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19492
+ source: TrackSourceSchema.optional(),
19140
19493
  firstSeen: number(),
19141
19494
  lastSeen: number(),
19142
19495
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19391,6 +19744,26 @@ var TrackCascadeCountsSchema = object({
19391
19744
  /** Per-track CLIP search vectors removed (best-effort). */
19392
19745
  embeddings: number().int()
19393
19746
  });
19747
+ /** Event-store footprint for one camera. */
19748
+ var EventStoreDeviceFootprintSchema = object({
19749
+ deviceId: number(),
19750
+ /** Persisted event rows (motion + object + audio) for the camera. */
19751
+ rows: number().int(),
19752
+ /** Event-owned media bytes on disk for the camera. */
19753
+ bytes: number().int()
19754
+ });
19755
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19756
+ var EventStoreFootprintSchema = object({
19757
+ totalRows: number().int(),
19758
+ totalBytes: number().int(),
19759
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19760
+ });
19761
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19762
+ var EventPruneCountsSchema = object({
19763
+ motion: number().int(),
19764
+ object: number().int(),
19765
+ audio: number().int()
19766
+ });
19394
19767
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19395
19768
  deviceId: number(),
19396
19769
  trackId: string()
@@ -19454,6 +19827,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19454
19827
  }), {
19455
19828
  kind: "mutation",
19456
19829
  auth: "admin"
19830
+ }), method(object({}), EventStoreFootprintSchema, {
19831
+ kind: "query",
19832
+ auth: "admin"
19833
+ }), method(object({
19834
+ olderThanMs: number(),
19835
+ reason: OpsLogReasonSchema.optional()
19836
+ }), EventPruneCountsSchema, {
19837
+ kind: "mutation",
19838
+ auth: "admin"
19839
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19840
+ kind: "mutation",
19841
+ auth: "admin"
19842
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19843
+ kind: "query",
19844
+ auth: "admin"
19457
19845
  }), method(object({
19458
19846
  eventId: string(),
19459
19847
  kind: MediaFileKindEnum.optional()
@@ -19478,6 +19866,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19478
19866
  eventId: string(),
19479
19867
  timestamp: number()
19480
19868
  });
19869
+ /**
19870
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19871
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19872
+ * caps into per-camera event-kind descriptors.
19873
+ *
19874
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19875
+ * is NOT duplicated here — every entry is derived from the single
19876
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19877
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19878
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19879
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19880
+ * eventful cap is missing.
19881
+ */
19882
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19883
+ var LEGACY_ICON = {
19884
+ motion: "motion",
19885
+ audio: "audio",
19886
+ person: "person",
19887
+ vehicle: "vehicle",
19888
+ animal: "animal",
19889
+ package: "package",
19890
+ door: "door",
19891
+ pir: "pir",
19892
+ smoke: "smoke",
19893
+ water: "water",
19894
+ button: "button",
19895
+ generic: "generic",
19896
+ gas: "smoke",
19897
+ vibration: "generic",
19898
+ tamper: "generic",
19899
+ presence: "person",
19900
+ lock: "generic",
19901
+ siren: "generic",
19902
+ switch: "generic",
19903
+ doorbell: "button"
19904
+ };
19905
+ function legacyIcon(iconId) {
19906
+ return LEGACY_ICON[iconId] ?? "generic";
19907
+ }
19908
+ /**
19909
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19910
+ * The anti-drift guard cross-checks this against the eventful caps declared
19911
+ * in `packages/types/src/capabilities/*.cap.ts`.
19912
+ */
19913
+ var CAP_TO_KIND = {
19914
+ contact: "contact",
19915
+ motion: "motion-sensor",
19916
+ smoke: "smoke",
19917
+ flood: "flood",
19918
+ gas: "gas",
19919
+ "carbon-monoxide": "carbon-monoxide",
19920
+ vibration: "vibration",
19921
+ tamper: "tamper",
19922
+ presence: "presence",
19923
+ "enum-sensor": "enum-sensor",
19924
+ "event-emitter": "device-event",
19925
+ "lock-control": "lock",
19926
+ switch: "switch",
19927
+ button: "button",
19928
+ doorbell: "doorbell"
19929
+ };
19930
+ function buildDescriptor(capName, kind) {
19931
+ const t = EVENT_TAXONOMY[kind];
19932
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19933
+ return {
19934
+ ...t,
19935
+ icon: legacyIcon(t.iconId)
19936
+ };
19937
+ }
19938
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19481
19939
  var CameraPipelineConfigSchema = object({
19482
19940
  engine: PipelineEngineChoiceSchema.optional(),
19483
19941
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22856,13 +23314,29 @@ method(object({
22856
23314
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22857
23315
  kind: "mutation",
22858
23316
  auth: "admin"
22859
- }), method(object({ deviceId: number() }), object({
23317
+ }), method(object({
23318
+ deviceId: number(),
23319
+ reason: OpsLogReasonSchema.optional()
23320
+ }), object({
22860
23321
  floorMs: number().nullable(),
22861
23322
  deletedBuckets: number().int(),
22862
23323
  reclaimedBytes: number().int()
22863
23324
  }), {
22864
23325
  kind: "mutation",
22865
23326
  auth: "admin"
23327
+ }), method(object({
23328
+ deviceId: number(),
23329
+ fromMs: number().optional(),
23330
+ toMs: number().optional()
23331
+ }), object({
23332
+ deletedBuckets: number().int(),
23333
+ reclaimedBytes: number().int()
23334
+ }), {
23335
+ kind: "mutation",
23336
+ auth: "admin"
23337
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23338
+ kind: "query",
23339
+ auth: "admin"
22866
23340
  });
22867
23341
  /**
22868
23342
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -26195,6 +26669,12 @@ Object.freeze({
26195
26669
  addonId: null,
26196
26670
  access: "delete"
26197
26671
  },
26672
+ "pipelineAnalytics.deleteDeviceEvents": {
26673
+ capName: "pipeline-analytics",
26674
+ capScope: "device",
26675
+ addonId: null,
26676
+ access: "delete"
26677
+ },
26198
26678
  "pipelineAnalytics.deleteTracks": {
26199
26679
  capName: "pipeline-analytics",
26200
26680
  capScope: "device",
@@ -26225,6 +26705,12 @@ Object.freeze({
26225
26705
  addonId: null,
26226
26706
  access: "view"
26227
26707
  },
26708
+ "pipelineAnalytics.getEventStoreFootprint": {
26709
+ capName: "pipeline-analytics",
26710
+ capScope: "device",
26711
+ addonId: null,
26712
+ access: "view"
26713
+ },
26228
26714
  "pipelineAnalytics.getKeyEvents": {
26229
26715
  capName: "pipeline-analytics",
26230
26716
  capScope: "device",
@@ -26267,6 +26753,12 @@ Object.freeze({
26267
26753
  addonId: null,
26268
26754
  access: "view"
26269
26755
  },
26756
+ "pipelineAnalytics.listOpsLog": {
26757
+ capName: "pipeline-analytics",
26758
+ capScope: "device",
26759
+ addonId: null,
26760
+ access: "view"
26761
+ },
26270
26762
  "pipelineAnalytics.listRecentTracks": {
26271
26763
  capName: "pipeline-analytics",
26272
26764
  capScope: "device",
@@ -26279,6 +26771,12 @@ Object.freeze({
26279
26771
  addonId: null,
26280
26772
  access: "view"
26281
26773
  },
26774
+ "pipelineAnalytics.pruneEvents": {
26775
+ capName: "pipeline-analytics",
26776
+ capScope: "device",
26777
+ addonId: null,
26778
+ access: "create"
26779
+ },
26282
26780
  "pipelineAnalytics.pruneEventsBefore": {
26283
26781
  capName: "pipeline-analytics",
26284
26782
  capScope: "device",
@@ -27041,6 +27539,12 @@ Object.freeze({
27041
27539
  addonId: null,
27042
27540
  access: "create"
27043
27541
  },
27542
+ "recording.deleteFootprint": {
27543
+ capName: "recording",
27544
+ capScope: "system",
27545
+ addonId: null,
27546
+ access: "delete"
27547
+ },
27044
27548
  "recording.getAvailability": {
27045
27549
  capName: "recording",
27046
27550
  capScope: "system",
@@ -27071,6 +27575,12 @@ Object.freeze({
27071
27575
  addonId: null,
27072
27576
  access: "view"
27073
27577
  },
27578
+ "recording.listOpsLog": {
27579
+ capName: "recording",
27580
+ capScope: "system",
27581
+ addonId: null,
27582
+ access: "view"
27583
+ },
27074
27584
  "recording.locateSegment": {
27075
27585
  capName: "recording",
27076
27586
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7553,6 +7553,62 @@ var RecordingConfigSchema = object({
7553
7553
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7554
7554
  });
7555
7555
  /**
7556
+ * Ops-log — the durable, append-only operations audit shared by the
7557
+ * recordings and events management surfaces.
7558
+ *
7559
+ * ONE row shape is reused for both domains so a single "Activity" view can
7560
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7561
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7562
+ * management operation, WHY it ran (reason), and its measurable effect
7563
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7564
+ * never fail the operation it records.
7565
+ */
7566
+ /** Which management domain the operation belongs to. */
7567
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7568
+ /** The kind of management operation performed. */
7569
+ var OpsLogOpSchema = _enum([
7570
+ "prune",
7571
+ "manual-delete",
7572
+ "rescan",
7573
+ "retention-run"
7574
+ ]);
7575
+ /** Why the operation ran. */
7576
+ var OpsLogReasonSchema = _enum([
7577
+ "retention",
7578
+ "quota",
7579
+ "manual",
7580
+ "operator"
7581
+ ]);
7582
+ /** One audit row, shared verbatim by both domains. */
7583
+ var OpsLogEntrySchema = object({
7584
+ /** Unique row id. */
7585
+ id: string(),
7586
+ /** Epoch ms the operation completed. */
7587
+ at: number(),
7588
+ domain: OpsLogDomainSchema,
7589
+ op: OpsLogOpSchema,
7590
+ reason: OpsLogReasonSchema,
7591
+ /** The camera the op targeted; null for a cluster/global op. */
7592
+ deviceId: number().nullable(),
7593
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7594
+ nodeId: string(),
7595
+ /** Buckets / rows deleted (op-specific unit). */
7596
+ itemsAffected: number(),
7597
+ /** Bytes reclaimed by the op (0 when not measurable). */
7598
+ bytesReclaimed: number(),
7599
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7600
+ detail: string().nullable(),
7601
+ /** Who/what triggered the op. */
7602
+ actor: string()
7603
+ });
7604
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7605
+ var OpsLogQueryInputSchema = object({
7606
+ /** Restrict to a single camera; omit for every row. */
7607
+ deviceId: number().optional(),
7608
+ /** Max rows returned, newest-first. */
7609
+ limit: number().int().min(1).max(1e3).optional()
7610
+ });
7611
+ /**
7556
7612
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7557
7613
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7558
7614
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7796,6 +7852,160 @@ var EncodeProfileSchema = object({
7796
7852
  */
7797
7853
  outputArgs: array(string()).optional()
7798
7854
  });
7855
+ var COCO_TO_MACRO = {
7856
+ mapping: {
7857
+ person: "person",
7858
+ bicycle: "vehicle",
7859
+ car: "vehicle",
7860
+ motorcycle: "vehicle",
7861
+ airplane: "vehicle",
7862
+ bus: "vehicle",
7863
+ train: "vehicle",
7864
+ truck: "vehicle",
7865
+ boat: "vehicle",
7866
+ bird: "animal",
7867
+ cat: "animal",
7868
+ dog: "animal",
7869
+ horse: "animal",
7870
+ sheep: "animal",
7871
+ cow: "animal",
7872
+ elephant: "animal",
7873
+ bear: "animal",
7874
+ zebra: "animal",
7875
+ giraffe: "animal",
7876
+ suitcase: "package",
7877
+ backpack: "package",
7878
+ handbag: "package"
7879
+ },
7880
+ preserveOriginal: false
7881
+ };
7882
+ var AUDIO_MACRO_LABELS = [
7883
+ {
7884
+ id: "speech",
7885
+ name: "Speech",
7886
+ icon: "🗣️"
7887
+ },
7888
+ {
7889
+ id: "scream",
7890
+ name: "Scream / Shout",
7891
+ icon: "😱"
7892
+ },
7893
+ {
7894
+ id: "crying",
7895
+ name: "Crying / Baby",
7896
+ icon: "😢"
7897
+ },
7898
+ {
7899
+ id: "laughter",
7900
+ name: "Laughter",
7901
+ icon: "😂"
7902
+ },
7903
+ {
7904
+ id: "music",
7905
+ name: "Music",
7906
+ icon: "🎵"
7907
+ },
7908
+ {
7909
+ id: "dog",
7910
+ name: "Dog",
7911
+ icon: "🐕"
7912
+ },
7913
+ {
7914
+ id: "cat",
7915
+ name: "Cat",
7916
+ icon: "🐈"
7917
+ },
7918
+ {
7919
+ id: "bird",
7920
+ name: "Bird",
7921
+ icon: "🐦"
7922
+ },
7923
+ {
7924
+ id: "animal",
7925
+ name: "Animal (other)",
7926
+ icon: "🐾"
7927
+ },
7928
+ {
7929
+ id: "alarm",
7930
+ name: "Alarm / Siren",
7931
+ icon: "🚨"
7932
+ },
7933
+ {
7934
+ id: "doorbell",
7935
+ name: "Doorbell / Knock",
7936
+ icon: "🔔"
7937
+ },
7938
+ {
7939
+ id: "glass_breaking",
7940
+ name: "Glass Breaking",
7941
+ icon: "💥"
7942
+ },
7943
+ {
7944
+ id: "gunshot",
7945
+ name: "Gunshot / Explosion",
7946
+ icon: "💣"
7947
+ },
7948
+ {
7949
+ id: "vehicle",
7950
+ name: "Vehicle",
7951
+ icon: "🚗"
7952
+ },
7953
+ {
7954
+ id: "siren",
7955
+ name: "Emergency Siren",
7956
+ icon: "🚑"
7957
+ },
7958
+ {
7959
+ id: "fire",
7960
+ name: "Fire / Smoke",
7961
+ icon: "🔥"
7962
+ },
7963
+ {
7964
+ id: "water",
7965
+ name: "Water",
7966
+ icon: "💧"
7967
+ },
7968
+ {
7969
+ id: "wind",
7970
+ name: "Wind / Weather",
7971
+ icon: "🌬️"
7972
+ },
7973
+ {
7974
+ id: "door",
7975
+ name: "Door",
7976
+ icon: "🚪"
7977
+ },
7978
+ {
7979
+ id: "footsteps",
7980
+ name: "Footsteps",
7981
+ icon: "👣"
7982
+ },
7983
+ {
7984
+ id: "crowd",
7985
+ name: "Crowd / Chatter",
7986
+ icon: "👥"
7987
+ },
7988
+ {
7989
+ id: "telephone",
7990
+ name: "Telephone",
7991
+ icon: "📞"
7992
+ },
7993
+ {
7994
+ id: "engine",
7995
+ name: "Engine / Motor",
7996
+ icon: "⚙️"
7997
+ },
7998
+ {
7999
+ id: "tools",
8000
+ name: "Tools / Construction",
8001
+ icon: "🔨"
8002
+ },
8003
+ {
8004
+ id: "silence",
8005
+ name: "Silence",
8006
+ icon: "🤫"
8007
+ }
8008
+ ];
7799
8009
  var YAMNET_TO_MACRO = {
7800
8010
  mapping: {
7801
8011
  Speech: "speech",
@@ -8062,6 +8272,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8062
8272
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8063
8273
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8064
8274
  /**
8275
+ * Unified event-kind taxonomy — THE single source of truth for
8276
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8277
+ * icon }`.
8278
+ *
8279
+ * This dictionary folds together what used to be scattered across four
8280
+ * copies:
8281
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8282
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8283
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8284
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8285
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8286
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8287
+ *
8288
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8289
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8290
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8291
+ * color or an icon: they read this dictionary (server descriptors carry the
8292
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8293
+ *
8294
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8295
+ */
8296
+ var TAXONOMY_COLORS = {
8297
+ motion: "#f59e0b",
8298
+ audio: "#06b6d4",
8299
+ person: "#22c55e",
8300
+ vehicle: "#3b82f6",
8301
+ animal: "#f97316",
8302
+ package: "#a855f7",
8303
+ sensor: "#8b5cf6",
8304
+ control: "#10b981",
8305
+ genericDetection: "#64748b"
8306
+ };
8307
+ var DETECTION_SUB_COLORS = {
8308
+ car: "#f59e0b",
8309
+ truck: "#d97706",
8310
+ bus: "#b45309",
8311
+ motorcycle: "#eab308",
8312
+ bicycle: "#ca8a04",
8313
+ airplane: "#60a5fa",
8314
+ boat: "#2563eb",
8315
+ train: "#1d4ed8",
8316
+ bird: "#14b8a6",
8317
+ dog: "#84cc16",
8318
+ cat: "#f97316",
8319
+ horse: "#a16207",
8320
+ sheep: "#a3a3a3",
8321
+ cow: "#78716c",
8322
+ elephant: "#6b7280",
8323
+ bear: "#7c2d12",
8324
+ zebra: "#404040",
8325
+ giraffe: "#d4a373"
8326
+ };
8327
+ function titleCase(id) {
8328
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8329
+ }
8330
+ var entries = /* @__PURE__ */ new Map();
8331
+ function macro(kind, category, color, iconId, label) {
8332
+ entries.set(kind, {
8333
+ kind,
8334
+ parentKind: null,
8335
+ level: "macro",
8336
+ category,
8337
+ color,
8338
+ iconId,
8339
+ labelKey: `eventKind.${kind}`,
8340
+ label
8341
+ });
8342
+ }
8343
+ function sub(kind, parentKind, category, color, iconId, label) {
8344
+ entries.set(kind, {
8345
+ kind,
8346
+ parentKind,
8347
+ level: "sub",
8348
+ category,
8349
+ color,
8350
+ iconId,
8351
+ labelKey: `eventKind.${kind}`,
8352
+ label
8353
+ });
8354
+ }
8355
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8356
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8357
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8358
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8359
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8360
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8361
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8362
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8363
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8364
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8365
+ if (entries.has(cocoClass)) continue;
8366
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8367
+ }
8368
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8369
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8370
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8371
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8372
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8373
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8374
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8375
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8376
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8377
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8378
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8379
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8380
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8381
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8382
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8383
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8384
+ sub("button", "control", "control", "#10b981", "button", "Button");
8385
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8386
+ for (const l of AUDIO_MACRO_LABELS) {
8387
+ const kind = `audio-${l.id}`;
8388
+ if (entries.has(kind)) continue;
8389
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8390
+ }
8391
+ /** The complete taxonomy dictionary, keyed by kind. */
8392
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8393
+ /**
8065
8394
  * Error types for the safe expression engine. Two distinct classes so callers
8066
8395
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8067
8396
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15055,9 +15384,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
15055
15384
  * `package` backs the package-drop detector — a package zone is a
15056
15385
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
15057
15386
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15058
- * The orchestrator provider that writes this stage lands in a later
15059
- * slice; until then the mirror carries only `{motion, detection}` and
15060
- * consumers read `package` as absent (treat as `[]`).
15387
+ * The orchestrator provider writes this stage as a first-class slice
15388
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15389
+ * package}` shape, so consumers read the current package rules directly
15390
+ * off `device.state.zoneRules.value.package`.
15061
15391
  */
15062
15392
  runtimeState: object({
15063
15393
  motion: array(ZoneRuleSchema).readonly(),
@@ -19044,17 +19374,30 @@ var EventKindCategorySchema = _enum([
19044
19374
  "audio",
19045
19375
  "detection",
19046
19376
  "sensor",
19377
+ "control",
19047
19378
  "custom",
19048
19379
  "package"
19049
19380
  ]);
19381
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19382
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19050
19383
  var EventKindDescriptorSchema = object({
19051
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19384
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19052
19385
  kind: string(),
19386
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19387
+ labelKey: string(),
19388
+ /** English fallback label (kept for clients that don't translate). */
19053
19389
  label: string(),
19054
19390
  /** Hex color for timeline/legend rendering. */
19055
19391
  color: string(),
19392
+ /** Dictionary id → lucide component on the UI side. */
19393
+ iconId: string(),
19394
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19056
19395
  icon: EventKindIconSchema,
19057
19396
  category: EventKindCategorySchema,
19397
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19398
+ parentKind: string().nullable(),
19399
+ /** Derived from `parentKind`, explicit for the client tree. */
19400
+ level: EventKindLevelSchema,
19058
19401
  /** Which cap + device contributes this kind. For built-ins the camera
19059
19402
  * itself; for sensor kinds the LINKED source device. */
19060
19403
  source: object({
@@ -19127,11 +19470,21 @@ var TrackAudioLabelSchema = object({
19127
19470
  firstAt: number(),
19128
19471
  lastAt: number()
19129
19472
  });
19473
+ /**
19474
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19475
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19476
+ * linked sensor/control state change (no positions; carries a snapshot). The
19477
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19478
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19479
+ */
19480
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19130
19481
  var TrackSchema = object({
19131
19482
  trackId: string(),
19132
19483
  deviceId: number(),
19133
19484
  className: string(),
19134
19485
  label: string().optional(),
19486
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19487
+ source: TrackSourceSchema.optional(),
19135
19488
  firstSeen: number(),
19136
19489
  lastSeen: number(),
19137
19490
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19386,6 +19739,26 @@ var TrackCascadeCountsSchema = object({
19386
19739
  /** Per-track CLIP search vectors removed (best-effort). */
19387
19740
  embeddings: number().int()
19388
19741
  });
19742
+ /** Event-store footprint for one camera. */
19743
+ var EventStoreDeviceFootprintSchema = object({
19744
+ deviceId: number(),
19745
+ /** Persisted event rows (motion + object + audio) for the camera. */
19746
+ rows: number().int(),
19747
+ /** Event-owned media bytes on disk for the camera. */
19748
+ bytes: number().int()
19749
+ });
19750
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19751
+ var EventStoreFootprintSchema = object({
19752
+ totalRows: number().int(),
19753
+ totalBytes: number().int(),
19754
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19755
+ });
19756
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19757
+ var EventPruneCountsSchema = object({
19758
+ motion: number().int(),
19759
+ object: number().int(),
19760
+ audio: number().int()
19761
+ });
19389
19762
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19390
19763
  deviceId: number(),
19391
19764
  trackId: string()
@@ -19449,6 +19822,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19449
19822
  }), {
19450
19823
  kind: "mutation",
19451
19824
  auth: "admin"
19825
+ }), method(object({}), EventStoreFootprintSchema, {
19826
+ kind: "query",
19827
+ auth: "admin"
19828
+ }), method(object({
19829
+ olderThanMs: number(),
19830
+ reason: OpsLogReasonSchema.optional()
19831
+ }), EventPruneCountsSchema, {
19832
+ kind: "mutation",
19833
+ auth: "admin"
19834
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19835
+ kind: "mutation",
19836
+ auth: "admin"
19837
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19838
+ kind: "query",
19839
+ auth: "admin"
19452
19840
  }), method(object({
19453
19841
  eventId: string(),
19454
19842
  kind: MediaFileKindEnum.optional()
@@ -19473,6 +19861,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19473
19861
  eventId: string(),
19474
19862
  timestamp: number()
19475
19863
  });
19864
+ /**
19865
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19866
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19867
+ * caps into per-camera event-kind descriptors.
19868
+ *
19869
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19870
+ * is NOT duplicated here — every entry is derived from the single
19871
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19872
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19873
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19874
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19875
+ * eventful cap is missing.
19876
+ */
19877
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19878
+ var LEGACY_ICON = {
19879
+ motion: "motion",
19880
+ audio: "audio",
19881
+ person: "person",
19882
+ vehicle: "vehicle",
19883
+ animal: "animal",
19884
+ package: "package",
19885
+ door: "door",
19886
+ pir: "pir",
19887
+ smoke: "smoke",
19888
+ water: "water",
19889
+ button: "button",
19890
+ generic: "generic",
19891
+ gas: "smoke",
19892
+ vibration: "generic",
19893
+ tamper: "generic",
19894
+ presence: "person",
19895
+ lock: "generic",
19896
+ siren: "generic",
19897
+ switch: "generic",
19898
+ doorbell: "button"
19899
+ };
19900
+ function legacyIcon(iconId) {
19901
+ return LEGACY_ICON[iconId] ?? "generic";
19902
+ }
19903
+ /**
19904
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19905
+ * The anti-drift guard cross-checks this against the eventful caps declared
19906
+ * in `packages/types/src/capabilities/*.cap.ts`.
19907
+ */
19908
+ var CAP_TO_KIND = {
19909
+ contact: "contact",
19910
+ motion: "motion-sensor",
19911
+ smoke: "smoke",
19912
+ flood: "flood",
19913
+ gas: "gas",
19914
+ "carbon-monoxide": "carbon-monoxide",
19915
+ vibration: "vibration",
19916
+ tamper: "tamper",
19917
+ presence: "presence",
19918
+ "enum-sensor": "enum-sensor",
19919
+ "event-emitter": "device-event",
19920
+ "lock-control": "lock",
19921
+ switch: "switch",
19922
+ button: "button",
19923
+ doorbell: "doorbell"
19924
+ };
19925
+ function buildDescriptor(capName, kind) {
19926
+ const t = EVENT_TAXONOMY[kind];
19927
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19928
+ return {
19929
+ ...t,
19930
+ icon: legacyIcon(t.iconId)
19931
+ };
19932
+ }
19933
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19476
19934
  var CameraPipelineConfigSchema = object({
19477
19935
  engine: PipelineEngineChoiceSchema.optional(),
19478
19936
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22851,13 +23309,29 @@ method(object({
22851
23309
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22852
23310
  kind: "mutation",
22853
23311
  auth: "admin"
22854
- }), method(object({ deviceId: number() }), object({
23312
+ }), method(object({
23313
+ deviceId: number(),
23314
+ reason: OpsLogReasonSchema.optional()
23315
+ }), object({
22855
23316
  floorMs: number().nullable(),
22856
23317
  deletedBuckets: number().int(),
22857
23318
  reclaimedBytes: number().int()
22858
23319
  }), {
22859
23320
  kind: "mutation",
22860
23321
  auth: "admin"
23322
+ }), method(object({
23323
+ deviceId: number(),
23324
+ fromMs: number().optional(),
23325
+ toMs: number().optional()
23326
+ }), object({
23327
+ deletedBuckets: number().int(),
23328
+ reclaimedBytes: number().int()
23329
+ }), {
23330
+ kind: "mutation",
23331
+ auth: "admin"
23332
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23333
+ kind: "query",
23334
+ auth: "admin"
22861
23335
  });
22862
23336
  /**
22863
23337
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -26190,6 +26664,12 @@ Object.freeze({
26190
26664
  addonId: null,
26191
26665
  access: "delete"
26192
26666
  },
26667
+ "pipelineAnalytics.deleteDeviceEvents": {
26668
+ capName: "pipeline-analytics",
26669
+ capScope: "device",
26670
+ addonId: null,
26671
+ access: "delete"
26672
+ },
26193
26673
  "pipelineAnalytics.deleteTracks": {
26194
26674
  capName: "pipeline-analytics",
26195
26675
  capScope: "device",
@@ -26220,6 +26700,12 @@ Object.freeze({
26220
26700
  addonId: null,
26221
26701
  access: "view"
26222
26702
  },
26703
+ "pipelineAnalytics.getEventStoreFootprint": {
26704
+ capName: "pipeline-analytics",
26705
+ capScope: "device",
26706
+ addonId: null,
26707
+ access: "view"
26708
+ },
26223
26709
  "pipelineAnalytics.getKeyEvents": {
26224
26710
  capName: "pipeline-analytics",
26225
26711
  capScope: "device",
@@ -26262,6 +26748,12 @@ Object.freeze({
26262
26748
  addonId: null,
26263
26749
  access: "view"
26264
26750
  },
26751
+ "pipelineAnalytics.listOpsLog": {
26752
+ capName: "pipeline-analytics",
26753
+ capScope: "device",
26754
+ addonId: null,
26755
+ access: "view"
26756
+ },
26265
26757
  "pipelineAnalytics.listRecentTracks": {
26266
26758
  capName: "pipeline-analytics",
26267
26759
  capScope: "device",
@@ -26274,6 +26766,12 @@ Object.freeze({
26274
26766
  addonId: null,
26275
26767
  access: "view"
26276
26768
  },
26769
+ "pipelineAnalytics.pruneEvents": {
26770
+ capName: "pipeline-analytics",
26771
+ capScope: "device",
26772
+ addonId: null,
26773
+ access: "create"
26774
+ },
26277
26775
  "pipelineAnalytics.pruneEventsBefore": {
26278
26776
  capName: "pipeline-analytics",
26279
26777
  capScope: "device",
@@ -27036,6 +27534,12 @@ Object.freeze({
27036
27534
  addonId: null,
27037
27535
  access: "create"
27038
27536
  },
27537
+ "recording.deleteFootprint": {
27538
+ capName: "recording",
27539
+ capScope: "system",
27540
+ addonId: null,
27541
+ access: "delete"
27542
+ },
27039
27543
  "recording.getAvailability": {
27040
27544
  capName: "recording",
27041
27545
  capScope: "system",
@@ -27066,6 +27570,12 @@ Object.freeze({
27066
27570
  addonId: null,
27067
27571
  access: "view"
27068
27572
  },
27573
+ "recording.listOpsLog": {
27574
+ capName: "recording",
27575
+ capScope: "system",
27576
+ addonId: null,
27577
+ access: "view"
27578
+ },
27069
27579
  "recording.locateSegment": {
27070
27580
  capName: "recording",
27071
27581
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.1.28",
3
+ "version": "1.1.30",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",