@camstack/addon-provider-homematic 1.1.25 → 1.1.27

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
@@ -7367,6 +7367,62 @@ var RecordingConfigSchema = object({
7367
7367
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7368
7368
  });
7369
7369
  /**
7370
+ * Ops-log — the durable, append-only operations audit shared by the
7371
+ * recordings and events management surfaces.
7372
+ *
7373
+ * ONE row shape is reused for both domains so a single "Activity" view can
7374
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7375
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7376
+ * management operation, WHY it ran (reason), and its measurable effect
7377
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7378
+ * never fail the operation it records.
7379
+ */
7380
+ /** Which management domain the operation belongs to. */
7381
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7382
+ /** The kind of management operation performed. */
7383
+ var OpsLogOpSchema = _enum([
7384
+ "prune",
7385
+ "manual-delete",
7386
+ "rescan",
7387
+ "retention-run"
7388
+ ]);
7389
+ /** Why the operation ran. */
7390
+ var OpsLogReasonSchema = _enum([
7391
+ "retention",
7392
+ "quota",
7393
+ "manual",
7394
+ "operator"
7395
+ ]);
7396
+ /** One audit row, shared verbatim by both domains. */
7397
+ var OpsLogEntrySchema = object({
7398
+ /** Unique row id. */
7399
+ id: string(),
7400
+ /** Epoch ms the operation completed. */
7401
+ at: number(),
7402
+ domain: OpsLogDomainSchema,
7403
+ op: OpsLogOpSchema,
7404
+ reason: OpsLogReasonSchema,
7405
+ /** The camera the op targeted; null for a cluster/global op. */
7406
+ deviceId: number().nullable(),
7407
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7408
+ nodeId: string(),
7409
+ /** Buckets / rows deleted (op-specific unit). */
7410
+ itemsAffected: number(),
7411
+ /** Bytes reclaimed by the op (0 when not measurable). */
7412
+ bytesReclaimed: number(),
7413
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7414
+ detail: string().nullable(),
7415
+ /** Who/what triggered the op. */
7416
+ actor: string()
7417
+ });
7418
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7419
+ var OpsLogQueryInputSchema = object({
7420
+ /** Restrict to a single camera; omit for every row. */
7421
+ deviceId: number().optional(),
7422
+ /** Max rows returned, newest-first. */
7423
+ limit: number().int().min(1).max(1e3).optional()
7424
+ });
7425
+ /**
7370
7426
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7371
7427
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7372
7428
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7610,6 +7666,160 @@ var EncodeProfileSchema = object({
7610
7666
  */
7611
7667
  outputArgs: array(string()).optional()
7612
7668
  });
7669
+ var COCO_TO_MACRO = {
7670
+ mapping: {
7671
+ person: "person",
7672
+ bicycle: "vehicle",
7673
+ car: "vehicle",
7674
+ motorcycle: "vehicle",
7675
+ airplane: "vehicle",
7676
+ bus: "vehicle",
7677
+ train: "vehicle",
7678
+ truck: "vehicle",
7679
+ boat: "vehicle",
7680
+ bird: "animal",
7681
+ cat: "animal",
7682
+ dog: "animal",
7683
+ horse: "animal",
7684
+ sheep: "animal",
7685
+ cow: "animal",
7686
+ elephant: "animal",
7687
+ bear: "animal",
7688
+ zebra: "animal",
7689
+ giraffe: "animal",
7690
+ suitcase: "package",
7691
+ backpack: "package",
7692
+ handbag: "package"
7693
+ },
7694
+ preserveOriginal: false
7695
+ };
7696
+ var AUDIO_MACRO_LABELS = [
7697
+ {
7698
+ id: "speech",
7699
+ name: "Speech",
7700
+ icon: "🗣️"
7701
+ },
7702
+ {
7703
+ id: "scream",
7704
+ name: "Scream / Shout",
7705
+ icon: "😱"
7706
+ },
7707
+ {
7708
+ id: "crying",
7709
+ name: "Crying / Baby",
7710
+ icon: "😢"
7711
+ },
7712
+ {
7713
+ id: "laughter",
7714
+ name: "Laughter",
7715
+ icon: "😂"
7716
+ },
7717
+ {
7718
+ id: "music",
7719
+ name: "Music",
7720
+ icon: "🎵"
7721
+ },
7722
+ {
7723
+ id: "dog",
7724
+ name: "Dog",
7725
+ icon: "🐕"
7726
+ },
7727
+ {
7728
+ id: "cat",
7729
+ name: "Cat",
7730
+ icon: "🐈"
7731
+ },
7732
+ {
7733
+ id: "bird",
7734
+ name: "Bird",
7735
+ icon: "🐦"
7736
+ },
7737
+ {
7738
+ id: "animal",
7739
+ name: "Animal (other)",
7740
+ icon: "🐾"
7741
+ },
7742
+ {
7743
+ id: "alarm",
7744
+ name: "Alarm / Siren",
7745
+ icon: "🚨"
7746
+ },
7747
+ {
7748
+ id: "doorbell",
7749
+ name: "Doorbell / Knock",
7750
+ icon: "🔔"
7751
+ },
7752
+ {
7753
+ id: "glass_breaking",
7754
+ name: "Glass Breaking",
7755
+ icon: "💥"
7756
+ },
7757
+ {
7758
+ id: "gunshot",
7759
+ name: "Gunshot / Explosion",
7760
+ icon: "💣"
7761
+ },
7762
+ {
7763
+ id: "vehicle",
7764
+ name: "Vehicle",
7765
+ icon: "🚗"
7766
+ },
7767
+ {
7768
+ id: "siren",
7769
+ name: "Emergency Siren",
7770
+ icon: "🚑"
7771
+ },
7772
+ {
7773
+ id: "fire",
7774
+ name: "Fire / Smoke",
7775
+ icon: "🔥"
7776
+ },
7777
+ {
7778
+ id: "water",
7779
+ name: "Water",
7780
+ icon: "💧"
7781
+ },
7782
+ {
7783
+ id: "wind",
7784
+ name: "Wind / Weather",
7785
+ icon: "🌬️"
7786
+ },
7787
+ {
7788
+ id: "door",
7789
+ name: "Door",
7790
+ icon: "🚪"
7791
+ },
7792
+ {
7793
+ id: "footsteps",
7794
+ name: "Footsteps",
7795
+ icon: "👣"
7796
+ },
7797
+ {
7798
+ id: "crowd",
7799
+ name: "Crowd / Chatter",
7800
+ icon: "👥"
7801
+ },
7802
+ {
7803
+ id: "telephone",
7804
+ name: "Telephone",
7805
+ icon: "📞"
7806
+ },
7807
+ {
7808
+ id: "engine",
7809
+ name: "Engine / Motor",
7810
+ icon: "⚙️"
7811
+ },
7812
+ {
7813
+ id: "tools",
7814
+ name: "Tools / Construction",
7815
+ icon: "🔨"
7816
+ },
7817
+ {
7818
+ id: "silence",
7819
+ name: "Silence",
7820
+ icon: "🤫"
7821
+ }
7822
+ ];
7613
7823
  var YAMNET_TO_MACRO = {
7614
7824
  mapping: {
7615
7825
  Speech: "speech",
@@ -7876,6 +8086,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7876
8086
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7877
8087
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7878
8088
  /**
8089
+ * Unified event-kind taxonomy — THE single source of truth for
8090
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8091
+ * icon }`.
8092
+ *
8093
+ * This dictionary folds together what used to be scattered across four
8094
+ * copies:
8095
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8096
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8097
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8098
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8099
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8100
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8101
+ *
8102
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8103
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8104
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8105
+ * color or an icon: they read this dictionary (server descriptors carry the
8106
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8107
+ *
8108
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8109
+ */
8110
+ var TAXONOMY_COLORS = {
8111
+ motion: "#f59e0b",
8112
+ audio: "#06b6d4",
8113
+ person: "#22c55e",
8114
+ vehicle: "#3b82f6",
8115
+ animal: "#f97316",
8116
+ package: "#a855f7",
8117
+ sensor: "#8b5cf6",
8118
+ control: "#10b981",
8119
+ genericDetection: "#64748b"
8120
+ };
8121
+ var DETECTION_SUB_COLORS = {
8122
+ car: "#f59e0b",
8123
+ truck: "#d97706",
8124
+ bus: "#b45309",
8125
+ motorcycle: "#eab308",
8126
+ bicycle: "#ca8a04",
8127
+ airplane: "#60a5fa",
8128
+ boat: "#2563eb",
8129
+ train: "#1d4ed8",
8130
+ bird: "#14b8a6",
8131
+ dog: "#84cc16",
8132
+ cat: "#f97316",
8133
+ horse: "#a16207",
8134
+ sheep: "#a3a3a3",
8135
+ cow: "#78716c",
8136
+ elephant: "#6b7280",
8137
+ bear: "#7c2d12",
8138
+ zebra: "#404040",
8139
+ giraffe: "#d4a373"
8140
+ };
8141
+ function titleCase(id) {
8142
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8143
+ }
8144
+ var entries = /* @__PURE__ */ new Map();
8145
+ function macro(kind, category, color, iconId, label) {
8146
+ entries.set(kind, {
8147
+ kind,
8148
+ parentKind: null,
8149
+ level: "macro",
8150
+ category,
8151
+ color,
8152
+ iconId,
8153
+ labelKey: `eventKind.${kind}`,
8154
+ label
8155
+ });
8156
+ }
8157
+ function sub(kind, parentKind, category, color, iconId, label) {
8158
+ entries.set(kind, {
8159
+ kind,
8160
+ parentKind,
8161
+ level: "sub",
8162
+ category,
8163
+ color,
8164
+ iconId,
8165
+ labelKey: `eventKind.${kind}`,
8166
+ label
8167
+ });
8168
+ }
8169
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8170
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8171
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8172
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8173
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8174
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8175
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8176
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8177
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8178
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8179
+ if (entries.has(cocoClass)) continue;
8180
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8181
+ }
8182
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8183
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8184
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8185
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8186
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8187
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8188
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8189
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8190
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8191
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8192
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8193
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8194
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8195
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8196
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8197
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8198
+ sub("button", "control", "control", "#10b981", "button", "Button");
8199
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8200
+ for (const l of AUDIO_MACRO_LABELS) {
8201
+ const kind = `audio-${l.id}`;
8202
+ if (entries.has(kind)) continue;
8203
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8204
+ }
8205
+ /** The complete taxonomy dictionary, keyed by kind. */
8206
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8207
+ /**
7879
8208
  * Error types for the safe expression engine. Two distinct classes so callers
7880
8209
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7881
8210
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14830,9 +15159,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14830
15159
  * `package` backs the package-drop detector — a package zone is a
14831
15160
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14832
15161
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14833
- * The orchestrator provider that writes this stage lands in a later
14834
- * slice; until then the mirror carries only `{motion, detection}` and
14835
- * consumers read `package` as absent (treat as `[]`).
15162
+ * The orchestrator provider writes this stage as a first-class slice
15163
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15164
+ * package}` shape, so consumers read the current package rules directly
15165
+ * off `device.state.zoneRules.value.package`.
14836
15166
  */
14837
15167
  runtimeState: object({
14838
15168
  motion: array(ZoneRuleSchema).readonly(),
@@ -18873,17 +19203,30 @@ var EventKindCategorySchema = _enum([
18873
19203
  "audio",
18874
19204
  "detection",
18875
19205
  "sensor",
19206
+ "control",
18876
19207
  "custom",
18877
19208
  "package"
18878
19209
  ]);
19210
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19211
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18879
19212
  var EventKindDescriptorSchema = object({
18880
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19213
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18881
19214
  kind: string(),
19215
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19216
+ labelKey: string(),
19217
+ /** English fallback label (kept for clients that don't translate). */
18882
19218
  label: string(),
18883
19219
  /** Hex color for timeline/legend rendering. */
18884
19220
  color: string(),
19221
+ /** Dictionary id → lucide component on the UI side. */
19222
+ iconId: string(),
19223
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18885
19224
  icon: EventKindIconSchema,
18886
19225
  category: EventKindCategorySchema,
19226
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19227
+ parentKind: string().nullable(),
19228
+ /** Derived from `parentKind`, explicit for the client tree. */
19229
+ level: EventKindLevelSchema,
18887
19230
  /** Which cap + device contributes this kind. For built-ins the camera
18888
19231
  * itself; for sensor kinds the LINKED source device. */
18889
19232
  source: object({
@@ -18956,11 +19299,21 @@ var TrackAudioLabelSchema = object({
18956
19299
  firstAt: number(),
18957
19300
  lastAt: number()
18958
19301
  });
19302
+ /**
19303
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19304
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19305
+ * linked sensor/control state change (no positions; carries a snapshot). The
19306
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19307
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19308
+ */
19309
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
18959
19310
  var TrackSchema = object({
18960
19311
  trackId: string(),
18961
19312
  deviceId: number(),
18962
19313
  className: string(),
18963
19314
  label: string().optional(),
19315
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19316
+ source: TrackSourceSchema.optional(),
18964
19317
  firstSeen: number(),
18965
19318
  lastSeen: number(),
18966
19319
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19215,6 +19568,26 @@ var TrackCascadeCountsSchema = object({
19215
19568
  /** Per-track CLIP search vectors removed (best-effort). */
19216
19569
  embeddings: number().int()
19217
19570
  });
19571
+ /** Event-store footprint for one camera. */
19572
+ var EventStoreDeviceFootprintSchema = object({
19573
+ deviceId: number(),
19574
+ /** Persisted event rows (motion + object + audio) for the camera. */
19575
+ rows: number().int(),
19576
+ /** Event-owned media bytes on disk for the camera. */
19577
+ bytes: number().int()
19578
+ });
19579
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19580
+ var EventStoreFootprintSchema = object({
19581
+ totalRows: number().int(),
19582
+ totalBytes: number().int(),
19583
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19584
+ });
19585
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19586
+ var EventPruneCountsSchema = object({
19587
+ motion: number().int(),
19588
+ object: number().int(),
19589
+ audio: number().int()
19590
+ });
19218
19591
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19219
19592
  deviceId: number(),
19220
19593
  trackId: string()
@@ -19278,6 +19651,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19278
19651
  }), {
19279
19652
  kind: "mutation",
19280
19653
  auth: "admin"
19654
+ }), method(object({}), EventStoreFootprintSchema, {
19655
+ kind: "query",
19656
+ auth: "admin"
19657
+ }), method(object({
19658
+ olderThanMs: number(),
19659
+ reason: OpsLogReasonSchema.optional()
19660
+ }), EventPruneCountsSchema, {
19661
+ kind: "mutation",
19662
+ auth: "admin"
19663
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19664
+ kind: "mutation",
19665
+ auth: "admin"
19666
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19667
+ kind: "query",
19668
+ auth: "admin"
19281
19669
  }), method(object({
19282
19670
  eventId: string(),
19283
19671
  kind: MediaFileKindEnum.optional()
@@ -19302,6 +19690,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19302
19690
  eventId: string(),
19303
19691
  timestamp: number()
19304
19692
  });
19693
+ /**
19694
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19695
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19696
+ * caps into per-camera event-kind descriptors.
19697
+ *
19698
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19699
+ * is NOT duplicated here — every entry is derived from the single
19700
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19701
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19702
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19703
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19704
+ * eventful cap is missing.
19705
+ */
19706
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19707
+ var LEGACY_ICON = {
19708
+ motion: "motion",
19709
+ audio: "audio",
19710
+ person: "person",
19711
+ vehicle: "vehicle",
19712
+ animal: "animal",
19713
+ package: "package",
19714
+ door: "door",
19715
+ pir: "pir",
19716
+ smoke: "smoke",
19717
+ water: "water",
19718
+ button: "button",
19719
+ generic: "generic",
19720
+ gas: "smoke",
19721
+ vibration: "generic",
19722
+ tamper: "generic",
19723
+ presence: "person",
19724
+ lock: "generic",
19725
+ siren: "generic",
19726
+ switch: "generic",
19727
+ doorbell: "button"
19728
+ };
19729
+ function legacyIcon(iconId) {
19730
+ return LEGACY_ICON[iconId] ?? "generic";
19731
+ }
19732
+ /**
19733
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19734
+ * The anti-drift guard cross-checks this against the eventful caps declared
19735
+ * in `packages/types/src/capabilities/*.cap.ts`.
19736
+ */
19737
+ var CAP_TO_KIND = {
19738
+ contact: "contact",
19739
+ motion: "motion-sensor",
19740
+ smoke: "smoke",
19741
+ flood: "flood",
19742
+ gas: "gas",
19743
+ "carbon-monoxide": "carbon-monoxide",
19744
+ vibration: "vibration",
19745
+ tamper: "tamper",
19746
+ presence: "presence",
19747
+ "enum-sensor": "enum-sensor",
19748
+ "event-emitter": "device-event",
19749
+ "lock-control": "lock",
19750
+ switch: "switch",
19751
+ button: "button",
19752
+ doorbell: "doorbell"
19753
+ };
19754
+ function buildDescriptor(capName, kind) {
19755
+ const t = EVENT_TAXONOMY[kind];
19756
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19757
+ return {
19758
+ ...t,
19759
+ icon: legacyIcon(t.iconId)
19760
+ };
19761
+ }
19762
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19305
19763
  var CameraPipelineConfigSchema = object({
19306
19764
  engine: PipelineEngineChoiceSchema.optional(),
19307
19765
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22511,13 +22969,29 @@ method(object({
22511
22969
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22512
22970
  kind: "mutation",
22513
22971
  auth: "admin"
22514
- }), method(object({ deviceId: number() }), object({
22972
+ }), method(object({
22973
+ deviceId: number(),
22974
+ reason: OpsLogReasonSchema.optional()
22975
+ }), object({
22515
22976
  floorMs: number().nullable(),
22516
22977
  deletedBuckets: number().int(),
22517
22978
  reclaimedBytes: number().int()
22518
22979
  }), {
22519
22980
  kind: "mutation",
22520
22981
  auth: "admin"
22982
+ }), method(object({
22983
+ deviceId: number(),
22984
+ fromMs: number().optional(),
22985
+ toMs: number().optional()
22986
+ }), object({
22987
+ deletedBuckets: number().int(),
22988
+ reclaimedBytes: number().int()
22989
+ }), {
22990
+ kind: "mutation",
22991
+ auth: "admin"
22992
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22993
+ kind: "query",
22994
+ auth: "admin"
22521
22995
  });
22522
22996
  /**
22523
22997
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25639,6 +26113,12 @@ Object.freeze({
25639
26113
  addonId: null,
25640
26114
  access: "delete"
25641
26115
  },
26116
+ "pipelineAnalytics.deleteDeviceEvents": {
26117
+ capName: "pipeline-analytics",
26118
+ capScope: "device",
26119
+ addonId: null,
26120
+ access: "delete"
26121
+ },
25642
26122
  "pipelineAnalytics.deleteTracks": {
25643
26123
  capName: "pipeline-analytics",
25644
26124
  capScope: "device",
@@ -25669,6 +26149,12 @@ Object.freeze({
25669
26149
  addonId: null,
25670
26150
  access: "view"
25671
26151
  },
26152
+ "pipelineAnalytics.getEventStoreFootprint": {
26153
+ capName: "pipeline-analytics",
26154
+ capScope: "device",
26155
+ addonId: null,
26156
+ access: "view"
26157
+ },
25672
26158
  "pipelineAnalytics.getKeyEvents": {
25673
26159
  capName: "pipeline-analytics",
25674
26160
  capScope: "device",
@@ -25711,6 +26197,12 @@ Object.freeze({
25711
26197
  addonId: null,
25712
26198
  access: "view"
25713
26199
  },
26200
+ "pipelineAnalytics.listOpsLog": {
26201
+ capName: "pipeline-analytics",
26202
+ capScope: "device",
26203
+ addonId: null,
26204
+ access: "view"
26205
+ },
25714
26206
  "pipelineAnalytics.listRecentTracks": {
25715
26207
  capName: "pipeline-analytics",
25716
26208
  capScope: "device",
@@ -25723,6 +26215,12 @@ Object.freeze({
25723
26215
  addonId: null,
25724
26216
  access: "view"
25725
26217
  },
26218
+ "pipelineAnalytics.pruneEvents": {
26219
+ capName: "pipeline-analytics",
26220
+ capScope: "device",
26221
+ addonId: null,
26222
+ access: "create"
26223
+ },
25726
26224
  "pipelineAnalytics.pruneEventsBefore": {
25727
26225
  capName: "pipeline-analytics",
25728
26226
  capScope: "device",
@@ -26485,6 +26983,12 @@ Object.freeze({
26485
26983
  addonId: null,
26486
26984
  access: "create"
26487
26985
  },
26986
+ "recording.deleteFootprint": {
26987
+ capName: "recording",
26988
+ capScope: "system",
26989
+ addonId: null,
26990
+ access: "delete"
26991
+ },
26488
26992
  "recording.getAvailability": {
26489
26993
  capName: "recording",
26490
26994
  capScope: "system",
@@ -26515,6 +27019,12 @@ Object.freeze({
26515
27019
  addonId: null,
26516
27020
  access: "view"
26517
27021
  },
27022
+ "recording.listOpsLog": {
27023
+ capName: "recording",
27024
+ capScope: "system",
27025
+ addonId: null,
27026
+ access: "view"
27027
+ },
26518
27028
  "recording.locateSegment": {
26519
27029
  capName: "recording",
26520
27030
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7368,6 +7368,62 @@ var RecordingConfigSchema = object({
7368
7368
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7369
7369
  });
7370
7370
  /**
7371
+ * Ops-log — the durable, append-only operations audit shared by the
7372
+ * recordings and events management surfaces.
7373
+ *
7374
+ * ONE row shape is reused for both domains so a single "Activity" view can
7375
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7376
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7377
+ * management operation, WHY it ran (reason), and its measurable effect
7378
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7379
+ * never fail the operation it records.
7380
+ */
7381
+ /** Which management domain the operation belongs to. */
7382
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7383
+ /** The kind of management operation performed. */
7384
+ var OpsLogOpSchema = _enum([
7385
+ "prune",
7386
+ "manual-delete",
7387
+ "rescan",
7388
+ "retention-run"
7389
+ ]);
7390
+ /** Why the operation ran. */
7391
+ var OpsLogReasonSchema = _enum([
7392
+ "retention",
7393
+ "quota",
7394
+ "manual",
7395
+ "operator"
7396
+ ]);
7397
+ /** One audit row, shared verbatim by both domains. */
7398
+ var OpsLogEntrySchema = object({
7399
+ /** Unique row id. */
7400
+ id: string(),
7401
+ /** Epoch ms the operation completed. */
7402
+ at: number(),
7403
+ domain: OpsLogDomainSchema,
7404
+ op: OpsLogOpSchema,
7405
+ reason: OpsLogReasonSchema,
7406
+ /** The camera the op targeted; null for a cluster/global op. */
7407
+ deviceId: number().nullable(),
7408
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7409
+ nodeId: string(),
7410
+ /** Buckets / rows deleted (op-specific unit). */
7411
+ itemsAffected: number(),
7412
+ /** Bytes reclaimed by the op (0 when not measurable). */
7413
+ bytesReclaimed: number(),
7414
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7415
+ detail: string().nullable(),
7416
+ /** Who/what triggered the op. */
7417
+ actor: string()
7418
+ });
7419
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7420
+ var OpsLogQueryInputSchema = object({
7421
+ /** Restrict to a single camera; omit for every row. */
7422
+ deviceId: number().optional(),
7423
+ /** Max rows returned, newest-first. */
7424
+ limit: number().int().min(1).max(1e3).optional()
7425
+ });
7426
+ /**
7371
7427
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7372
7428
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7373
7429
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7611,6 +7667,160 @@ var EncodeProfileSchema = object({
7611
7667
  */
7612
7668
  outputArgs: array(string()).optional()
7613
7669
  });
7670
+ var COCO_TO_MACRO = {
7671
+ mapping: {
7672
+ person: "person",
7673
+ bicycle: "vehicle",
7674
+ car: "vehicle",
7675
+ motorcycle: "vehicle",
7676
+ airplane: "vehicle",
7677
+ bus: "vehicle",
7678
+ train: "vehicle",
7679
+ truck: "vehicle",
7680
+ boat: "vehicle",
7681
+ bird: "animal",
7682
+ cat: "animal",
7683
+ dog: "animal",
7684
+ horse: "animal",
7685
+ sheep: "animal",
7686
+ cow: "animal",
7687
+ elephant: "animal",
7688
+ bear: "animal",
7689
+ zebra: "animal",
7690
+ giraffe: "animal",
7691
+ suitcase: "package",
7692
+ backpack: "package",
7693
+ handbag: "package"
7694
+ },
7695
+ preserveOriginal: false
7696
+ };
7697
+ var AUDIO_MACRO_LABELS = [
7698
+ {
7699
+ id: "speech",
7700
+ name: "Speech",
7701
+ icon: "🗣️"
7702
+ },
7703
+ {
7704
+ id: "scream",
7705
+ name: "Scream / Shout",
7706
+ icon: "😱"
7707
+ },
7708
+ {
7709
+ id: "crying",
7710
+ name: "Crying / Baby",
7711
+ icon: "😢"
7712
+ },
7713
+ {
7714
+ id: "laughter",
7715
+ name: "Laughter",
7716
+ icon: "😂"
7717
+ },
7718
+ {
7719
+ id: "music",
7720
+ name: "Music",
7721
+ icon: "🎵"
7722
+ },
7723
+ {
7724
+ id: "dog",
7725
+ name: "Dog",
7726
+ icon: "🐕"
7727
+ },
7728
+ {
7729
+ id: "cat",
7730
+ name: "Cat",
7731
+ icon: "🐈"
7732
+ },
7733
+ {
7734
+ id: "bird",
7735
+ name: "Bird",
7736
+ icon: "🐦"
7737
+ },
7738
+ {
7739
+ id: "animal",
7740
+ name: "Animal (other)",
7741
+ icon: "🐾"
7742
+ },
7743
+ {
7744
+ id: "alarm",
7745
+ name: "Alarm / Siren",
7746
+ icon: "🚨"
7747
+ },
7748
+ {
7749
+ id: "doorbell",
7750
+ name: "Doorbell / Knock",
7751
+ icon: "🔔"
7752
+ },
7753
+ {
7754
+ id: "glass_breaking",
7755
+ name: "Glass Breaking",
7756
+ icon: "💥"
7757
+ },
7758
+ {
7759
+ id: "gunshot",
7760
+ name: "Gunshot / Explosion",
7761
+ icon: "💣"
7762
+ },
7763
+ {
7764
+ id: "vehicle",
7765
+ name: "Vehicle",
7766
+ icon: "🚗"
7767
+ },
7768
+ {
7769
+ id: "siren",
7770
+ name: "Emergency Siren",
7771
+ icon: "🚑"
7772
+ },
7773
+ {
7774
+ id: "fire",
7775
+ name: "Fire / Smoke",
7776
+ icon: "🔥"
7777
+ },
7778
+ {
7779
+ id: "water",
7780
+ name: "Water",
7781
+ icon: "💧"
7782
+ },
7783
+ {
7784
+ id: "wind",
7785
+ name: "Wind / Weather",
7786
+ icon: "🌬️"
7787
+ },
7788
+ {
7789
+ id: "door",
7790
+ name: "Door",
7791
+ icon: "🚪"
7792
+ },
7793
+ {
7794
+ id: "footsteps",
7795
+ name: "Footsteps",
7796
+ icon: "👣"
7797
+ },
7798
+ {
7799
+ id: "crowd",
7800
+ name: "Crowd / Chatter",
7801
+ icon: "👥"
7802
+ },
7803
+ {
7804
+ id: "telephone",
7805
+ name: "Telephone",
7806
+ icon: "📞"
7807
+ },
7808
+ {
7809
+ id: "engine",
7810
+ name: "Engine / Motor",
7811
+ icon: "⚙️"
7812
+ },
7813
+ {
7814
+ id: "tools",
7815
+ name: "Tools / Construction",
7816
+ icon: "🔨"
7817
+ },
7818
+ {
7819
+ id: "silence",
7820
+ name: "Silence",
7821
+ icon: "🤫"
7822
+ }
7823
+ ];
7614
7824
  var YAMNET_TO_MACRO = {
7615
7825
  mapping: {
7616
7826
  Speech: "speech",
@@ -7877,6 +8087,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7877
8087
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7878
8088
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7879
8089
  /**
8090
+ * Unified event-kind taxonomy — THE single source of truth for
8091
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8092
+ * icon }`.
8093
+ *
8094
+ * This dictionary folds together what used to be scattered across four
8095
+ * copies:
8096
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8097
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8098
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8099
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8100
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8101
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8102
+ *
8103
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8104
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8105
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8106
+ * color or an icon: they read this dictionary (server descriptors carry the
8107
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8108
+ *
8109
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8110
+ */
8111
+ var TAXONOMY_COLORS = {
8112
+ motion: "#f59e0b",
8113
+ audio: "#06b6d4",
8114
+ person: "#22c55e",
8115
+ vehicle: "#3b82f6",
8116
+ animal: "#f97316",
8117
+ package: "#a855f7",
8118
+ sensor: "#8b5cf6",
8119
+ control: "#10b981",
8120
+ genericDetection: "#64748b"
8121
+ };
8122
+ var DETECTION_SUB_COLORS = {
8123
+ car: "#f59e0b",
8124
+ truck: "#d97706",
8125
+ bus: "#b45309",
8126
+ motorcycle: "#eab308",
8127
+ bicycle: "#ca8a04",
8128
+ airplane: "#60a5fa",
8129
+ boat: "#2563eb",
8130
+ train: "#1d4ed8",
8131
+ bird: "#14b8a6",
8132
+ dog: "#84cc16",
8133
+ cat: "#f97316",
8134
+ horse: "#a16207",
8135
+ sheep: "#a3a3a3",
8136
+ cow: "#78716c",
8137
+ elephant: "#6b7280",
8138
+ bear: "#7c2d12",
8139
+ zebra: "#404040",
8140
+ giraffe: "#d4a373"
8141
+ };
8142
+ function titleCase(id) {
8143
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8144
+ }
8145
+ var entries = /* @__PURE__ */ new Map();
8146
+ function macro(kind, category, color, iconId, label) {
8147
+ entries.set(kind, {
8148
+ kind,
8149
+ parentKind: null,
8150
+ level: "macro",
8151
+ category,
8152
+ color,
8153
+ iconId,
8154
+ labelKey: `eventKind.${kind}`,
8155
+ label
8156
+ });
8157
+ }
8158
+ function sub(kind, parentKind, category, color, iconId, label) {
8159
+ entries.set(kind, {
8160
+ kind,
8161
+ parentKind,
8162
+ level: "sub",
8163
+ category,
8164
+ color,
8165
+ iconId,
8166
+ labelKey: `eventKind.${kind}`,
8167
+ label
8168
+ });
8169
+ }
8170
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8171
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8172
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8173
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8174
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8175
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8176
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8177
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8178
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8179
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8180
+ if (entries.has(cocoClass)) continue;
8181
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8182
+ }
8183
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8184
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8185
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8186
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8187
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8188
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8189
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8190
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8191
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8192
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8193
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8194
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8195
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8196
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8197
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8198
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8199
+ sub("button", "control", "control", "#10b981", "button", "Button");
8200
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8201
+ for (const l of AUDIO_MACRO_LABELS) {
8202
+ const kind = `audio-${l.id}`;
8203
+ if (entries.has(kind)) continue;
8204
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8205
+ }
8206
+ /** The complete taxonomy dictionary, keyed by kind. */
8207
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8208
+ /**
7880
8209
  * Error types for the safe expression engine. Two distinct classes so callers
7881
8210
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7882
8211
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14831,9 +15160,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14831
15160
  * `package` backs the package-drop detector — a package zone is a
14832
15161
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14833
15162
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14834
- * The orchestrator provider that writes this stage lands in a later
14835
- * slice; until then the mirror carries only `{motion, detection}` and
14836
- * consumers read `package` as absent (treat as `[]`).
15163
+ * The orchestrator provider writes this stage as a first-class slice
15164
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15165
+ * package}` shape, so consumers read the current package rules directly
15166
+ * off `device.state.zoneRules.value.package`.
14837
15167
  */
14838
15168
  runtimeState: object({
14839
15169
  motion: array(ZoneRuleSchema).readonly(),
@@ -18874,17 +19204,30 @@ var EventKindCategorySchema = _enum([
18874
19204
  "audio",
18875
19205
  "detection",
18876
19206
  "sensor",
19207
+ "control",
18877
19208
  "custom",
18878
19209
  "package"
18879
19210
  ]);
19211
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19212
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18880
19213
  var EventKindDescriptorSchema = object({
18881
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19214
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18882
19215
  kind: string(),
19216
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19217
+ labelKey: string(),
19218
+ /** English fallback label (kept for clients that don't translate). */
18883
19219
  label: string(),
18884
19220
  /** Hex color for timeline/legend rendering. */
18885
19221
  color: string(),
19222
+ /** Dictionary id → lucide component on the UI side. */
19223
+ iconId: string(),
19224
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18886
19225
  icon: EventKindIconSchema,
18887
19226
  category: EventKindCategorySchema,
19227
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19228
+ parentKind: string().nullable(),
19229
+ /** Derived from `parentKind`, explicit for the client tree. */
19230
+ level: EventKindLevelSchema,
18888
19231
  /** Which cap + device contributes this kind. For built-ins the camera
18889
19232
  * itself; for sensor kinds the LINKED source device. */
18890
19233
  source: object({
@@ -18957,11 +19300,21 @@ var TrackAudioLabelSchema = object({
18957
19300
  firstAt: number(),
18958
19301
  lastAt: number()
18959
19302
  });
19303
+ /**
19304
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19305
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19306
+ * linked sensor/control state change (no positions; carries a snapshot). The
19307
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19308
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19309
+ */
19310
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
18960
19311
  var TrackSchema = object({
18961
19312
  trackId: string(),
18962
19313
  deviceId: number(),
18963
19314
  className: string(),
18964
19315
  label: string().optional(),
19316
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19317
+ source: TrackSourceSchema.optional(),
18965
19318
  firstSeen: number(),
18966
19319
  lastSeen: number(),
18967
19320
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19216,6 +19569,26 @@ var TrackCascadeCountsSchema = object({
19216
19569
  /** Per-track CLIP search vectors removed (best-effort). */
19217
19570
  embeddings: number().int()
19218
19571
  });
19572
+ /** Event-store footprint for one camera. */
19573
+ var EventStoreDeviceFootprintSchema = object({
19574
+ deviceId: number(),
19575
+ /** Persisted event rows (motion + object + audio) for the camera. */
19576
+ rows: number().int(),
19577
+ /** Event-owned media bytes on disk for the camera. */
19578
+ bytes: number().int()
19579
+ });
19580
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19581
+ var EventStoreFootprintSchema = object({
19582
+ totalRows: number().int(),
19583
+ totalBytes: number().int(),
19584
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19585
+ });
19586
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19587
+ var EventPruneCountsSchema = object({
19588
+ motion: number().int(),
19589
+ object: number().int(),
19590
+ audio: number().int()
19591
+ });
19219
19592
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19220
19593
  deviceId: number(),
19221
19594
  trackId: string()
@@ -19279,6 +19652,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19279
19652
  }), {
19280
19653
  kind: "mutation",
19281
19654
  auth: "admin"
19655
+ }), method(object({}), EventStoreFootprintSchema, {
19656
+ kind: "query",
19657
+ auth: "admin"
19658
+ }), method(object({
19659
+ olderThanMs: number(),
19660
+ reason: OpsLogReasonSchema.optional()
19661
+ }), EventPruneCountsSchema, {
19662
+ kind: "mutation",
19663
+ auth: "admin"
19664
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19665
+ kind: "mutation",
19666
+ auth: "admin"
19667
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19668
+ kind: "query",
19669
+ auth: "admin"
19282
19670
  }), method(object({
19283
19671
  eventId: string(),
19284
19672
  kind: MediaFileKindEnum.optional()
@@ -19303,6 +19691,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19303
19691
  eventId: string(),
19304
19692
  timestamp: number()
19305
19693
  });
19694
+ /**
19695
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19696
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19697
+ * caps into per-camera event-kind descriptors.
19698
+ *
19699
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19700
+ * is NOT duplicated here — every entry is derived from the single
19701
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19702
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19703
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19704
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19705
+ * eventful cap is missing.
19706
+ */
19707
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19708
+ var LEGACY_ICON = {
19709
+ motion: "motion",
19710
+ audio: "audio",
19711
+ person: "person",
19712
+ vehicle: "vehicle",
19713
+ animal: "animal",
19714
+ package: "package",
19715
+ door: "door",
19716
+ pir: "pir",
19717
+ smoke: "smoke",
19718
+ water: "water",
19719
+ button: "button",
19720
+ generic: "generic",
19721
+ gas: "smoke",
19722
+ vibration: "generic",
19723
+ tamper: "generic",
19724
+ presence: "person",
19725
+ lock: "generic",
19726
+ siren: "generic",
19727
+ switch: "generic",
19728
+ doorbell: "button"
19729
+ };
19730
+ function legacyIcon(iconId) {
19731
+ return LEGACY_ICON[iconId] ?? "generic";
19732
+ }
19733
+ /**
19734
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19735
+ * The anti-drift guard cross-checks this against the eventful caps declared
19736
+ * in `packages/types/src/capabilities/*.cap.ts`.
19737
+ */
19738
+ var CAP_TO_KIND = {
19739
+ contact: "contact",
19740
+ motion: "motion-sensor",
19741
+ smoke: "smoke",
19742
+ flood: "flood",
19743
+ gas: "gas",
19744
+ "carbon-monoxide": "carbon-monoxide",
19745
+ vibration: "vibration",
19746
+ tamper: "tamper",
19747
+ presence: "presence",
19748
+ "enum-sensor": "enum-sensor",
19749
+ "event-emitter": "device-event",
19750
+ "lock-control": "lock",
19751
+ switch: "switch",
19752
+ button: "button",
19753
+ doorbell: "doorbell"
19754
+ };
19755
+ function buildDescriptor(capName, kind) {
19756
+ const t = EVENT_TAXONOMY[kind];
19757
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19758
+ return {
19759
+ ...t,
19760
+ icon: legacyIcon(t.iconId)
19761
+ };
19762
+ }
19763
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19306
19764
  var CameraPipelineConfigSchema = object({
19307
19765
  engine: PipelineEngineChoiceSchema.optional(),
19308
19766
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22512,13 +22970,29 @@ method(object({
22512
22970
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22513
22971
  kind: "mutation",
22514
22972
  auth: "admin"
22515
- }), method(object({ deviceId: number() }), object({
22973
+ }), method(object({
22974
+ deviceId: number(),
22975
+ reason: OpsLogReasonSchema.optional()
22976
+ }), object({
22516
22977
  floorMs: number().nullable(),
22517
22978
  deletedBuckets: number().int(),
22518
22979
  reclaimedBytes: number().int()
22519
22980
  }), {
22520
22981
  kind: "mutation",
22521
22982
  auth: "admin"
22983
+ }), method(object({
22984
+ deviceId: number(),
22985
+ fromMs: number().optional(),
22986
+ toMs: number().optional()
22987
+ }), object({
22988
+ deletedBuckets: number().int(),
22989
+ reclaimedBytes: number().int()
22990
+ }), {
22991
+ kind: "mutation",
22992
+ auth: "admin"
22993
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22994
+ kind: "query",
22995
+ auth: "admin"
22522
22996
  });
22523
22997
  /**
22524
22998
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25640,6 +26114,12 @@ Object.freeze({
25640
26114
  addonId: null,
25641
26115
  access: "delete"
25642
26116
  },
26117
+ "pipelineAnalytics.deleteDeviceEvents": {
26118
+ capName: "pipeline-analytics",
26119
+ capScope: "device",
26120
+ addonId: null,
26121
+ access: "delete"
26122
+ },
25643
26123
  "pipelineAnalytics.deleteTracks": {
25644
26124
  capName: "pipeline-analytics",
25645
26125
  capScope: "device",
@@ -25670,6 +26150,12 @@ Object.freeze({
25670
26150
  addonId: null,
25671
26151
  access: "view"
25672
26152
  },
26153
+ "pipelineAnalytics.getEventStoreFootprint": {
26154
+ capName: "pipeline-analytics",
26155
+ capScope: "device",
26156
+ addonId: null,
26157
+ access: "view"
26158
+ },
25673
26159
  "pipelineAnalytics.getKeyEvents": {
25674
26160
  capName: "pipeline-analytics",
25675
26161
  capScope: "device",
@@ -25712,6 +26198,12 @@ Object.freeze({
25712
26198
  addonId: null,
25713
26199
  access: "view"
25714
26200
  },
26201
+ "pipelineAnalytics.listOpsLog": {
26202
+ capName: "pipeline-analytics",
26203
+ capScope: "device",
26204
+ addonId: null,
26205
+ access: "view"
26206
+ },
25715
26207
  "pipelineAnalytics.listRecentTracks": {
25716
26208
  capName: "pipeline-analytics",
25717
26209
  capScope: "device",
@@ -25724,6 +26216,12 @@ Object.freeze({
25724
26216
  addonId: null,
25725
26217
  access: "view"
25726
26218
  },
26219
+ "pipelineAnalytics.pruneEvents": {
26220
+ capName: "pipeline-analytics",
26221
+ capScope: "device",
26222
+ addonId: null,
26223
+ access: "create"
26224
+ },
25727
26225
  "pipelineAnalytics.pruneEventsBefore": {
25728
26226
  capName: "pipeline-analytics",
25729
26227
  capScope: "device",
@@ -26486,6 +26984,12 @@ Object.freeze({
26486
26984
  addonId: null,
26487
26985
  access: "create"
26488
26986
  },
26987
+ "recording.deleteFootprint": {
26988
+ capName: "recording",
26989
+ capScope: "system",
26990
+ addonId: null,
26991
+ access: "delete"
26992
+ },
26489
26993
  "recording.getAvailability": {
26490
26994
  capName: "recording",
26491
26995
  capScope: "system",
@@ -26516,6 +27020,12 @@ Object.freeze({
26516
27020
  addonId: null,
26517
27021
  access: "view"
26518
27022
  },
27023
+ "recording.listOpsLog": {
27024
+ capName: "recording",
27025
+ capScope: "system",
27026
+ addonId: null,
27027
+ access: "view"
27028
+ },
26519
27029
  "recording.locateSegment": {
26520
27030
  capName: "recording",
26521
27031
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-homematic",
3
- "version": "1.1.25",
3
+ "version": "1.1.27",
4
4
  "description": "Homematic / HomematicIP (CCU3 / RaspberryMatic) device-provider addon for CamStack — wraps the nodehomematic library",
5
5
  "keywords": [
6
6
  "camstack",