@camstack/addon-provider-ecowitt 0.1.25 → 0.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
@@ -7361,6 +7361,62 @@ var RecordingConfigSchema = object({
7361
7361
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7362
7362
  });
7363
7363
  /**
7364
+ * Ops-log — the durable, append-only operations audit shared by the
7365
+ * recordings and events management surfaces.
7366
+ *
7367
+ * ONE row shape is reused for both domains so a single "Activity" view can
7368
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7369
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7370
+ * management operation, WHY it ran (reason), and its measurable effect
7371
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7372
+ * never fail the operation it records.
7373
+ */
7374
+ /** Which management domain the operation belongs to. */
7375
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7376
+ /** The kind of management operation performed. */
7377
+ var OpsLogOpSchema = _enum([
7378
+ "prune",
7379
+ "manual-delete",
7380
+ "rescan",
7381
+ "retention-run"
7382
+ ]);
7383
+ /** Why the operation ran. */
7384
+ var OpsLogReasonSchema = _enum([
7385
+ "retention",
7386
+ "quota",
7387
+ "manual",
7388
+ "operator"
7389
+ ]);
7390
+ /** One audit row, shared verbatim by both domains. */
7391
+ var OpsLogEntrySchema = object({
7392
+ /** Unique row id. */
7393
+ id: string(),
7394
+ /** Epoch ms the operation completed. */
7395
+ at: number(),
7396
+ domain: OpsLogDomainSchema,
7397
+ op: OpsLogOpSchema,
7398
+ reason: OpsLogReasonSchema,
7399
+ /** The camera the op targeted; null for a cluster/global op. */
7400
+ deviceId: number().nullable(),
7401
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7402
+ nodeId: string(),
7403
+ /** Buckets / rows deleted (op-specific unit). */
7404
+ itemsAffected: number(),
7405
+ /** Bytes reclaimed by the op (0 when not measurable). */
7406
+ bytesReclaimed: number(),
7407
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7408
+ detail: string().nullable(),
7409
+ /** Who/what triggered the op. */
7410
+ actor: string()
7411
+ });
7412
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7413
+ var OpsLogQueryInputSchema = object({
7414
+ /** Restrict to a single camera; omit for every row. */
7415
+ deviceId: number().optional(),
7416
+ /** Max rows returned, newest-first. */
7417
+ limit: number().int().min(1).max(1e3).optional()
7418
+ });
7419
+ /**
7364
7420
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7365
7421
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7366
7422
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7604,6 +7660,160 @@ var EncodeProfileSchema = object({
7604
7660
  */
7605
7661
  outputArgs: array(string()).optional()
7606
7662
  });
7663
+ var COCO_TO_MACRO = {
7664
+ mapping: {
7665
+ person: "person",
7666
+ bicycle: "vehicle",
7667
+ car: "vehicle",
7668
+ motorcycle: "vehicle",
7669
+ airplane: "vehicle",
7670
+ bus: "vehicle",
7671
+ train: "vehicle",
7672
+ truck: "vehicle",
7673
+ boat: "vehicle",
7674
+ bird: "animal",
7675
+ cat: "animal",
7676
+ dog: "animal",
7677
+ horse: "animal",
7678
+ sheep: "animal",
7679
+ cow: "animal",
7680
+ elephant: "animal",
7681
+ bear: "animal",
7682
+ zebra: "animal",
7683
+ giraffe: "animal",
7684
+ suitcase: "package",
7685
+ backpack: "package",
7686
+ handbag: "package"
7687
+ },
7688
+ preserveOriginal: false
7689
+ };
7690
+ var AUDIO_MACRO_LABELS = [
7691
+ {
7692
+ id: "speech",
7693
+ name: "Speech",
7694
+ icon: "🗣️"
7695
+ },
7696
+ {
7697
+ id: "scream",
7698
+ name: "Scream / Shout",
7699
+ icon: "😱"
7700
+ },
7701
+ {
7702
+ id: "crying",
7703
+ name: "Crying / Baby",
7704
+ icon: "😢"
7705
+ },
7706
+ {
7707
+ id: "laughter",
7708
+ name: "Laughter",
7709
+ icon: "😂"
7710
+ },
7711
+ {
7712
+ id: "music",
7713
+ name: "Music",
7714
+ icon: "🎵"
7715
+ },
7716
+ {
7717
+ id: "dog",
7718
+ name: "Dog",
7719
+ icon: "🐕"
7720
+ },
7721
+ {
7722
+ id: "cat",
7723
+ name: "Cat",
7724
+ icon: "🐈"
7725
+ },
7726
+ {
7727
+ id: "bird",
7728
+ name: "Bird",
7729
+ icon: "🐦"
7730
+ },
7731
+ {
7732
+ id: "animal",
7733
+ name: "Animal (other)",
7734
+ icon: "🐾"
7735
+ },
7736
+ {
7737
+ id: "alarm",
7738
+ name: "Alarm / Siren",
7739
+ icon: "🚨"
7740
+ },
7741
+ {
7742
+ id: "doorbell",
7743
+ name: "Doorbell / Knock",
7744
+ icon: "🔔"
7745
+ },
7746
+ {
7747
+ id: "glass_breaking",
7748
+ name: "Glass Breaking",
7749
+ icon: "💥"
7750
+ },
7751
+ {
7752
+ id: "gunshot",
7753
+ name: "Gunshot / Explosion",
7754
+ icon: "💣"
7755
+ },
7756
+ {
7757
+ id: "vehicle",
7758
+ name: "Vehicle",
7759
+ icon: "🚗"
7760
+ },
7761
+ {
7762
+ id: "siren",
7763
+ name: "Emergency Siren",
7764
+ icon: "🚑"
7765
+ },
7766
+ {
7767
+ id: "fire",
7768
+ name: "Fire / Smoke",
7769
+ icon: "🔥"
7770
+ },
7771
+ {
7772
+ id: "water",
7773
+ name: "Water",
7774
+ icon: "💧"
7775
+ },
7776
+ {
7777
+ id: "wind",
7778
+ name: "Wind / Weather",
7779
+ icon: "🌬️"
7780
+ },
7781
+ {
7782
+ id: "door",
7783
+ name: "Door",
7784
+ icon: "🚪"
7785
+ },
7786
+ {
7787
+ id: "footsteps",
7788
+ name: "Footsteps",
7789
+ icon: "👣"
7790
+ },
7791
+ {
7792
+ id: "crowd",
7793
+ name: "Crowd / Chatter",
7794
+ icon: "👥"
7795
+ },
7796
+ {
7797
+ id: "telephone",
7798
+ name: "Telephone",
7799
+ icon: "📞"
7800
+ },
7801
+ {
7802
+ id: "engine",
7803
+ name: "Engine / Motor",
7804
+ icon: "⚙️"
7805
+ },
7806
+ {
7807
+ id: "tools",
7808
+ name: "Tools / Construction",
7809
+ icon: "🔨"
7810
+ },
7811
+ {
7812
+ id: "silence",
7813
+ name: "Silence",
7814
+ icon: "🤫"
7815
+ }
7816
+ ];
7607
7817
  var YAMNET_TO_MACRO = {
7608
7818
  mapping: {
7609
7819
  Speech: "speech",
@@ -7870,6 +8080,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7870
8080
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7871
8081
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7872
8082
  /**
8083
+ * Unified event-kind taxonomy — THE single source of truth for
8084
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8085
+ * icon }`.
8086
+ *
8087
+ * This dictionary folds together what used to be scattered across four
8088
+ * copies:
8089
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8090
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8091
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8092
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8093
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8094
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8095
+ *
8096
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8097
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8098
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8099
+ * color or an icon: they read this dictionary (server descriptors carry the
8100
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8101
+ *
8102
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8103
+ */
8104
+ var TAXONOMY_COLORS = {
8105
+ motion: "#f59e0b",
8106
+ audio: "#06b6d4",
8107
+ person: "#22c55e",
8108
+ vehicle: "#3b82f6",
8109
+ animal: "#f97316",
8110
+ package: "#a855f7",
8111
+ sensor: "#8b5cf6",
8112
+ control: "#10b981",
8113
+ genericDetection: "#64748b"
8114
+ };
8115
+ var DETECTION_SUB_COLORS = {
8116
+ car: "#f59e0b",
8117
+ truck: "#d97706",
8118
+ bus: "#b45309",
8119
+ motorcycle: "#eab308",
8120
+ bicycle: "#ca8a04",
8121
+ airplane: "#60a5fa",
8122
+ boat: "#2563eb",
8123
+ train: "#1d4ed8",
8124
+ bird: "#14b8a6",
8125
+ dog: "#84cc16",
8126
+ cat: "#f97316",
8127
+ horse: "#a16207",
8128
+ sheep: "#a3a3a3",
8129
+ cow: "#78716c",
8130
+ elephant: "#6b7280",
8131
+ bear: "#7c2d12",
8132
+ zebra: "#404040",
8133
+ giraffe: "#d4a373"
8134
+ };
8135
+ function titleCase(id) {
8136
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8137
+ }
8138
+ var entries = /* @__PURE__ */ new Map();
8139
+ function macro(kind, category, color, iconId, label) {
8140
+ entries.set(kind, {
8141
+ kind,
8142
+ parentKind: null,
8143
+ level: "macro",
8144
+ category,
8145
+ color,
8146
+ iconId,
8147
+ labelKey: `eventKind.${kind}`,
8148
+ label
8149
+ });
8150
+ }
8151
+ function sub(kind, parentKind, category, color, iconId, label) {
8152
+ entries.set(kind, {
8153
+ kind,
8154
+ parentKind,
8155
+ level: "sub",
8156
+ category,
8157
+ color,
8158
+ iconId,
8159
+ labelKey: `eventKind.${kind}`,
8160
+ label
8161
+ });
8162
+ }
8163
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8164
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8165
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8166
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8167
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8168
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8169
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8170
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8171
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8172
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8173
+ if (entries.has(cocoClass)) continue;
8174
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8175
+ }
8176
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8177
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8178
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8179
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8180
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8181
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8182
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8183
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8184
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8185
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8186
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8187
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8188
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8189
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8190
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8191
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8192
+ sub("button", "control", "control", "#10b981", "button", "Button");
8193
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8194
+ for (const l of AUDIO_MACRO_LABELS) {
8195
+ const kind = `audio-${l.id}`;
8196
+ if (entries.has(kind)) continue;
8197
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8198
+ }
8199
+ /** The complete taxonomy dictionary, keyed by kind. */
8200
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8201
+ /**
7873
8202
  * Error types for the safe expression engine. Two distinct classes so callers
7874
8203
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7875
8204
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14824,9 +15153,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14824
15153
  * `package` backs the package-drop detector — a package zone is a
14825
15154
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14826
15155
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14827
- * The orchestrator provider that writes this stage lands in a later
14828
- * slice; until then the mirror carries only `{motion, detection}` and
14829
- * consumers read `package` as absent (treat as `[]`).
15156
+ * The orchestrator provider writes this stage as a first-class slice
15157
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15158
+ * package}` shape, so consumers read the current package rules directly
15159
+ * off `device.state.zoneRules.value.package`.
14830
15160
  */
14831
15161
  runtimeState: object({
14832
15162
  motion: array(ZoneRuleSchema).readonly(),
@@ -18801,17 +19131,30 @@ var EventKindCategorySchema = _enum([
18801
19131
  "audio",
18802
19132
  "detection",
18803
19133
  "sensor",
19134
+ "control",
18804
19135
  "custom",
18805
19136
  "package"
18806
19137
  ]);
19138
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19139
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18807
19140
  var EventKindDescriptorSchema = object({
18808
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19141
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18809
19142
  kind: string(),
19143
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19144
+ labelKey: string(),
19145
+ /** English fallback label (kept for clients that don't translate). */
18810
19146
  label: string(),
18811
19147
  /** Hex color for timeline/legend rendering. */
18812
19148
  color: string(),
19149
+ /** Dictionary id → lucide component on the UI side. */
19150
+ iconId: string(),
19151
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18813
19152
  icon: EventKindIconSchema,
18814
19153
  category: EventKindCategorySchema,
19154
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19155
+ parentKind: string().nullable(),
19156
+ /** Derived from `parentKind`, explicit for the client tree. */
19157
+ level: EventKindLevelSchema,
18815
19158
  /** Which cap + device contributes this kind. For built-ins the camera
18816
19159
  * itself; for sensor kinds the LINKED source device. */
18817
19160
  source: object({
@@ -18884,11 +19227,21 @@ var TrackAudioLabelSchema = object({
18884
19227
  firstAt: number(),
18885
19228
  lastAt: number()
18886
19229
  });
19230
+ /**
19231
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19232
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19233
+ * linked sensor/control state change (no positions; carries a snapshot). The
19234
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19235
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19236
+ */
19237
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
18887
19238
  var TrackSchema = object({
18888
19239
  trackId: string(),
18889
19240
  deviceId: number(),
18890
19241
  className: string(),
18891
19242
  label: string().optional(),
19243
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19244
+ source: TrackSourceSchema.optional(),
18892
19245
  firstSeen: number(),
18893
19246
  lastSeen: number(),
18894
19247
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19143,6 +19496,26 @@ var TrackCascadeCountsSchema = object({
19143
19496
  /** Per-track CLIP search vectors removed (best-effort). */
19144
19497
  embeddings: number().int()
19145
19498
  });
19499
+ /** Event-store footprint for one camera. */
19500
+ var EventStoreDeviceFootprintSchema = object({
19501
+ deviceId: number(),
19502
+ /** Persisted event rows (motion + object + audio) for the camera. */
19503
+ rows: number().int(),
19504
+ /** Event-owned media bytes on disk for the camera. */
19505
+ bytes: number().int()
19506
+ });
19507
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19508
+ var EventStoreFootprintSchema = object({
19509
+ totalRows: number().int(),
19510
+ totalBytes: number().int(),
19511
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19512
+ });
19513
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19514
+ var EventPruneCountsSchema = object({
19515
+ motion: number().int(),
19516
+ object: number().int(),
19517
+ audio: number().int()
19518
+ });
19146
19519
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19147
19520
  deviceId: number(),
19148
19521
  trackId: string()
@@ -19206,6 +19579,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19206
19579
  }), {
19207
19580
  kind: "mutation",
19208
19581
  auth: "admin"
19582
+ }), method(object({}), EventStoreFootprintSchema, {
19583
+ kind: "query",
19584
+ auth: "admin"
19585
+ }), method(object({
19586
+ olderThanMs: number(),
19587
+ reason: OpsLogReasonSchema.optional()
19588
+ }), EventPruneCountsSchema, {
19589
+ kind: "mutation",
19590
+ auth: "admin"
19591
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19592
+ kind: "mutation",
19593
+ auth: "admin"
19594
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19595
+ kind: "query",
19596
+ auth: "admin"
19209
19597
  }), method(object({
19210
19598
  eventId: string(),
19211
19599
  kind: MediaFileKindEnum.optional()
@@ -19230,6 +19618,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19230
19618
  eventId: string(),
19231
19619
  timestamp: number()
19232
19620
  });
19621
+ /**
19622
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19623
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19624
+ * caps into per-camera event-kind descriptors.
19625
+ *
19626
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19627
+ * is NOT duplicated here — every entry is derived from the single
19628
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19629
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19630
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19631
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19632
+ * eventful cap is missing.
19633
+ */
19634
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19635
+ var LEGACY_ICON = {
19636
+ motion: "motion",
19637
+ audio: "audio",
19638
+ person: "person",
19639
+ vehicle: "vehicle",
19640
+ animal: "animal",
19641
+ package: "package",
19642
+ door: "door",
19643
+ pir: "pir",
19644
+ smoke: "smoke",
19645
+ water: "water",
19646
+ button: "button",
19647
+ generic: "generic",
19648
+ gas: "smoke",
19649
+ vibration: "generic",
19650
+ tamper: "generic",
19651
+ presence: "person",
19652
+ lock: "generic",
19653
+ siren: "generic",
19654
+ switch: "generic",
19655
+ doorbell: "button"
19656
+ };
19657
+ function legacyIcon(iconId) {
19658
+ return LEGACY_ICON[iconId] ?? "generic";
19659
+ }
19660
+ /**
19661
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19662
+ * The anti-drift guard cross-checks this against the eventful caps declared
19663
+ * in `packages/types/src/capabilities/*.cap.ts`.
19664
+ */
19665
+ var CAP_TO_KIND = {
19666
+ contact: "contact",
19667
+ motion: "motion-sensor",
19668
+ smoke: "smoke",
19669
+ flood: "flood",
19670
+ gas: "gas",
19671
+ "carbon-monoxide": "carbon-monoxide",
19672
+ vibration: "vibration",
19673
+ tamper: "tamper",
19674
+ presence: "presence",
19675
+ "enum-sensor": "enum-sensor",
19676
+ "event-emitter": "device-event",
19677
+ "lock-control": "lock",
19678
+ switch: "switch",
19679
+ button: "button",
19680
+ doorbell: "doorbell"
19681
+ };
19682
+ function buildDescriptor(capName, kind) {
19683
+ const t = EVENT_TAXONOMY[kind];
19684
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19685
+ return {
19686
+ ...t,
19687
+ icon: legacyIcon(t.iconId)
19688
+ };
19689
+ }
19690
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19233
19691
  var CameraPipelineConfigSchema = object({
19234
19692
  engine: PipelineEngineChoiceSchema.optional(),
19235
19693
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22439,13 +22897,29 @@ method(object({
22439
22897
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22440
22898
  kind: "mutation",
22441
22899
  auth: "admin"
22442
- }), method(object({ deviceId: number() }), object({
22900
+ }), method(object({
22901
+ deviceId: number(),
22902
+ reason: OpsLogReasonSchema.optional()
22903
+ }), object({
22443
22904
  floorMs: number().nullable(),
22444
22905
  deletedBuckets: number().int(),
22445
22906
  reclaimedBytes: number().int()
22446
22907
  }), {
22447
22908
  kind: "mutation",
22448
22909
  auth: "admin"
22910
+ }), method(object({
22911
+ deviceId: number(),
22912
+ fromMs: number().optional(),
22913
+ toMs: number().optional()
22914
+ }), object({
22915
+ deletedBuckets: number().int(),
22916
+ reclaimedBytes: number().int()
22917
+ }), {
22918
+ kind: "mutation",
22919
+ auth: "admin"
22920
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22921
+ kind: "query",
22922
+ auth: "admin"
22449
22923
  });
22450
22924
  /**
22451
22925
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25567,6 +26041,12 @@ Object.freeze({
25567
26041
  addonId: null,
25568
26042
  access: "delete"
25569
26043
  },
26044
+ "pipelineAnalytics.deleteDeviceEvents": {
26045
+ capName: "pipeline-analytics",
26046
+ capScope: "device",
26047
+ addonId: null,
26048
+ access: "delete"
26049
+ },
25570
26050
  "pipelineAnalytics.deleteTracks": {
25571
26051
  capName: "pipeline-analytics",
25572
26052
  capScope: "device",
@@ -25597,6 +26077,12 @@ Object.freeze({
25597
26077
  addonId: null,
25598
26078
  access: "view"
25599
26079
  },
26080
+ "pipelineAnalytics.getEventStoreFootprint": {
26081
+ capName: "pipeline-analytics",
26082
+ capScope: "device",
26083
+ addonId: null,
26084
+ access: "view"
26085
+ },
25600
26086
  "pipelineAnalytics.getKeyEvents": {
25601
26087
  capName: "pipeline-analytics",
25602
26088
  capScope: "device",
@@ -25639,6 +26125,12 @@ Object.freeze({
25639
26125
  addonId: null,
25640
26126
  access: "view"
25641
26127
  },
26128
+ "pipelineAnalytics.listOpsLog": {
26129
+ capName: "pipeline-analytics",
26130
+ capScope: "device",
26131
+ addonId: null,
26132
+ access: "view"
26133
+ },
25642
26134
  "pipelineAnalytics.listRecentTracks": {
25643
26135
  capName: "pipeline-analytics",
25644
26136
  capScope: "device",
@@ -25651,6 +26143,12 @@ Object.freeze({
25651
26143
  addonId: null,
25652
26144
  access: "view"
25653
26145
  },
26146
+ "pipelineAnalytics.pruneEvents": {
26147
+ capName: "pipeline-analytics",
26148
+ capScope: "device",
26149
+ addonId: null,
26150
+ access: "create"
26151
+ },
25654
26152
  "pipelineAnalytics.pruneEventsBefore": {
25655
26153
  capName: "pipeline-analytics",
25656
26154
  capScope: "device",
@@ -26413,6 +26911,12 @@ Object.freeze({
26413
26911
  addonId: null,
26414
26912
  access: "create"
26415
26913
  },
26914
+ "recording.deleteFootprint": {
26915
+ capName: "recording",
26916
+ capScope: "system",
26917
+ addonId: null,
26918
+ access: "delete"
26919
+ },
26416
26920
  "recording.getAvailability": {
26417
26921
  capName: "recording",
26418
26922
  capScope: "system",
@@ -26443,6 +26947,12 @@ Object.freeze({
26443
26947
  addonId: null,
26444
26948
  access: "view"
26445
26949
  },
26950
+ "recording.listOpsLog": {
26951
+ capName: "recording",
26952
+ capScope: "system",
26953
+ addonId: null,
26954
+ access: "view"
26955
+ },
26446
26956
  "recording.locateSegment": {
26447
26957
  capName: "recording",
26448
26958
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7360,6 +7360,62 @@ var RecordingConfigSchema = object({
7360
7360
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7361
7361
  });
7362
7362
  /**
7363
+ * Ops-log — the durable, append-only operations audit shared by the
7364
+ * recordings and events management surfaces.
7365
+ *
7366
+ * ONE row shape is reused for both domains so a single "Activity" view can
7367
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7368
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7369
+ * management operation, WHY it ran (reason), and its measurable effect
7370
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7371
+ * never fail the operation it records.
7372
+ */
7373
+ /** Which management domain the operation belongs to. */
7374
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7375
+ /** The kind of management operation performed. */
7376
+ var OpsLogOpSchema = _enum([
7377
+ "prune",
7378
+ "manual-delete",
7379
+ "rescan",
7380
+ "retention-run"
7381
+ ]);
7382
+ /** Why the operation ran. */
7383
+ var OpsLogReasonSchema = _enum([
7384
+ "retention",
7385
+ "quota",
7386
+ "manual",
7387
+ "operator"
7388
+ ]);
7389
+ /** One audit row, shared verbatim by both domains. */
7390
+ var OpsLogEntrySchema = object({
7391
+ /** Unique row id. */
7392
+ id: string(),
7393
+ /** Epoch ms the operation completed. */
7394
+ at: number(),
7395
+ domain: OpsLogDomainSchema,
7396
+ op: OpsLogOpSchema,
7397
+ reason: OpsLogReasonSchema,
7398
+ /** The camera the op targeted; null for a cluster/global op. */
7399
+ deviceId: number().nullable(),
7400
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7401
+ nodeId: string(),
7402
+ /** Buckets / rows deleted (op-specific unit). */
7403
+ itemsAffected: number(),
7404
+ /** Bytes reclaimed by the op (0 when not measurable). */
7405
+ bytesReclaimed: number(),
7406
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7407
+ detail: string().nullable(),
7408
+ /** Who/what triggered the op. */
7409
+ actor: string()
7410
+ });
7411
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7412
+ var OpsLogQueryInputSchema = object({
7413
+ /** Restrict to a single camera; omit for every row. */
7414
+ deviceId: number().optional(),
7415
+ /** Max rows returned, newest-first. */
7416
+ limit: number().int().min(1).max(1e3).optional()
7417
+ });
7418
+ /**
7363
7419
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7364
7420
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7365
7421
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7603,6 +7659,160 @@ var EncodeProfileSchema = object({
7603
7659
  */
7604
7660
  outputArgs: array(string()).optional()
7605
7661
  });
7662
+ var COCO_TO_MACRO = {
7663
+ mapping: {
7664
+ person: "person",
7665
+ bicycle: "vehicle",
7666
+ car: "vehicle",
7667
+ motorcycle: "vehicle",
7668
+ airplane: "vehicle",
7669
+ bus: "vehicle",
7670
+ train: "vehicle",
7671
+ truck: "vehicle",
7672
+ boat: "vehicle",
7673
+ bird: "animal",
7674
+ cat: "animal",
7675
+ dog: "animal",
7676
+ horse: "animal",
7677
+ sheep: "animal",
7678
+ cow: "animal",
7679
+ elephant: "animal",
7680
+ bear: "animal",
7681
+ zebra: "animal",
7682
+ giraffe: "animal",
7683
+ suitcase: "package",
7684
+ backpack: "package",
7685
+ handbag: "package"
7686
+ },
7687
+ preserveOriginal: false
7688
+ };
7689
+ var AUDIO_MACRO_LABELS = [
7690
+ {
7691
+ id: "speech",
7692
+ name: "Speech",
7693
+ icon: "🗣️"
7694
+ },
7695
+ {
7696
+ id: "scream",
7697
+ name: "Scream / Shout",
7698
+ icon: "😱"
7699
+ },
7700
+ {
7701
+ id: "crying",
7702
+ name: "Crying / Baby",
7703
+ icon: "😢"
7704
+ },
7705
+ {
7706
+ id: "laughter",
7707
+ name: "Laughter",
7708
+ icon: "😂"
7709
+ },
7710
+ {
7711
+ id: "music",
7712
+ name: "Music",
7713
+ icon: "🎵"
7714
+ },
7715
+ {
7716
+ id: "dog",
7717
+ name: "Dog",
7718
+ icon: "🐕"
7719
+ },
7720
+ {
7721
+ id: "cat",
7722
+ name: "Cat",
7723
+ icon: "🐈"
7724
+ },
7725
+ {
7726
+ id: "bird",
7727
+ name: "Bird",
7728
+ icon: "🐦"
7729
+ },
7730
+ {
7731
+ id: "animal",
7732
+ name: "Animal (other)",
7733
+ icon: "🐾"
7734
+ },
7735
+ {
7736
+ id: "alarm",
7737
+ name: "Alarm / Siren",
7738
+ icon: "🚨"
7739
+ },
7740
+ {
7741
+ id: "doorbell",
7742
+ name: "Doorbell / Knock",
7743
+ icon: "🔔"
7744
+ },
7745
+ {
7746
+ id: "glass_breaking",
7747
+ name: "Glass Breaking",
7748
+ icon: "💥"
7749
+ },
7750
+ {
7751
+ id: "gunshot",
7752
+ name: "Gunshot / Explosion",
7753
+ icon: "💣"
7754
+ },
7755
+ {
7756
+ id: "vehicle",
7757
+ name: "Vehicle",
7758
+ icon: "🚗"
7759
+ },
7760
+ {
7761
+ id: "siren",
7762
+ name: "Emergency Siren",
7763
+ icon: "🚑"
7764
+ },
7765
+ {
7766
+ id: "fire",
7767
+ name: "Fire / Smoke",
7768
+ icon: "🔥"
7769
+ },
7770
+ {
7771
+ id: "water",
7772
+ name: "Water",
7773
+ icon: "💧"
7774
+ },
7775
+ {
7776
+ id: "wind",
7777
+ name: "Wind / Weather",
7778
+ icon: "🌬️"
7779
+ },
7780
+ {
7781
+ id: "door",
7782
+ name: "Door",
7783
+ icon: "🚪"
7784
+ },
7785
+ {
7786
+ id: "footsteps",
7787
+ name: "Footsteps",
7788
+ icon: "👣"
7789
+ },
7790
+ {
7791
+ id: "crowd",
7792
+ name: "Crowd / Chatter",
7793
+ icon: "👥"
7794
+ },
7795
+ {
7796
+ id: "telephone",
7797
+ name: "Telephone",
7798
+ icon: "📞"
7799
+ },
7800
+ {
7801
+ id: "engine",
7802
+ name: "Engine / Motor",
7803
+ icon: "⚙️"
7804
+ },
7805
+ {
7806
+ id: "tools",
7807
+ name: "Tools / Construction",
7808
+ icon: "🔨"
7809
+ },
7810
+ {
7811
+ id: "silence",
7812
+ name: "Silence",
7813
+ icon: "🤫"
7814
+ }
7815
+ ];
7606
7816
  var YAMNET_TO_MACRO = {
7607
7817
  mapping: {
7608
7818
  Speech: "speech",
@@ -7869,6 +8079,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7869
8079
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7870
8080
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7871
8081
  /**
8082
+ * Unified event-kind taxonomy — THE single source of truth for
8083
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8084
+ * icon }`.
8085
+ *
8086
+ * This dictionary folds together what used to be scattered across four
8087
+ * copies:
8088
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8089
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8090
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8091
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8092
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8093
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8094
+ *
8095
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8096
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8097
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8098
+ * color or an icon: they read this dictionary (server descriptors carry the
8099
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8100
+ *
8101
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8102
+ */
8103
+ var TAXONOMY_COLORS = {
8104
+ motion: "#f59e0b",
8105
+ audio: "#06b6d4",
8106
+ person: "#22c55e",
8107
+ vehicle: "#3b82f6",
8108
+ animal: "#f97316",
8109
+ package: "#a855f7",
8110
+ sensor: "#8b5cf6",
8111
+ control: "#10b981",
8112
+ genericDetection: "#64748b"
8113
+ };
8114
+ var DETECTION_SUB_COLORS = {
8115
+ car: "#f59e0b",
8116
+ truck: "#d97706",
8117
+ bus: "#b45309",
8118
+ motorcycle: "#eab308",
8119
+ bicycle: "#ca8a04",
8120
+ airplane: "#60a5fa",
8121
+ boat: "#2563eb",
8122
+ train: "#1d4ed8",
8123
+ bird: "#14b8a6",
8124
+ dog: "#84cc16",
8125
+ cat: "#f97316",
8126
+ horse: "#a16207",
8127
+ sheep: "#a3a3a3",
8128
+ cow: "#78716c",
8129
+ elephant: "#6b7280",
8130
+ bear: "#7c2d12",
8131
+ zebra: "#404040",
8132
+ giraffe: "#d4a373"
8133
+ };
8134
+ function titleCase(id) {
8135
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8136
+ }
8137
+ var entries = /* @__PURE__ */ new Map();
8138
+ function macro(kind, category, color, iconId, label) {
8139
+ entries.set(kind, {
8140
+ kind,
8141
+ parentKind: null,
8142
+ level: "macro",
8143
+ category,
8144
+ color,
8145
+ iconId,
8146
+ labelKey: `eventKind.${kind}`,
8147
+ label
8148
+ });
8149
+ }
8150
+ function sub(kind, parentKind, category, color, iconId, label) {
8151
+ entries.set(kind, {
8152
+ kind,
8153
+ parentKind,
8154
+ level: "sub",
8155
+ category,
8156
+ color,
8157
+ iconId,
8158
+ labelKey: `eventKind.${kind}`,
8159
+ label
8160
+ });
8161
+ }
8162
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8163
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8164
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8165
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8166
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8167
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8168
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8169
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8170
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8171
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8172
+ if (entries.has(cocoClass)) continue;
8173
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8174
+ }
8175
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8176
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8177
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8178
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8179
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8180
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8181
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8182
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8183
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8184
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8185
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8186
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8187
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8188
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8189
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8190
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8191
+ sub("button", "control", "control", "#10b981", "button", "Button");
8192
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8193
+ for (const l of AUDIO_MACRO_LABELS) {
8194
+ const kind = `audio-${l.id}`;
8195
+ if (entries.has(kind)) continue;
8196
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8197
+ }
8198
+ /** The complete taxonomy dictionary, keyed by kind. */
8199
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8200
+ /**
7872
8201
  * Error types for the safe expression engine. Two distinct classes so callers
7873
8202
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7874
8203
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14823,9 +15152,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14823
15152
  * `package` backs the package-drop detector — a package zone is a
14824
15153
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14825
15154
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14826
- * The orchestrator provider that writes this stage lands in a later
14827
- * slice; until then the mirror carries only `{motion, detection}` and
14828
- * consumers read `package` as absent (treat as `[]`).
15155
+ * The orchestrator provider writes this stage as a first-class slice
15156
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15157
+ * package}` shape, so consumers read the current package rules directly
15158
+ * off `device.state.zoneRules.value.package`.
14829
15159
  */
14830
15160
  runtimeState: object({
14831
15161
  motion: array(ZoneRuleSchema).readonly(),
@@ -18800,17 +19130,30 @@ var EventKindCategorySchema = _enum([
18800
19130
  "audio",
18801
19131
  "detection",
18802
19132
  "sensor",
19133
+ "control",
18803
19134
  "custom",
18804
19135
  "package"
18805
19136
  ]);
19137
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19138
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18806
19139
  var EventKindDescriptorSchema = object({
18807
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19140
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18808
19141
  kind: string(),
19142
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19143
+ labelKey: string(),
19144
+ /** English fallback label (kept for clients that don't translate). */
18809
19145
  label: string(),
18810
19146
  /** Hex color for timeline/legend rendering. */
18811
19147
  color: string(),
19148
+ /** Dictionary id → lucide component on the UI side. */
19149
+ iconId: string(),
19150
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18812
19151
  icon: EventKindIconSchema,
18813
19152
  category: EventKindCategorySchema,
19153
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19154
+ parentKind: string().nullable(),
19155
+ /** Derived from `parentKind`, explicit for the client tree. */
19156
+ level: EventKindLevelSchema,
18814
19157
  /** Which cap + device contributes this kind. For built-ins the camera
18815
19158
  * itself; for sensor kinds the LINKED source device. */
18816
19159
  source: object({
@@ -18883,11 +19226,21 @@ var TrackAudioLabelSchema = object({
18883
19226
  firstAt: number(),
18884
19227
  lastAt: number()
18885
19228
  });
19229
+ /**
19230
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19231
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19232
+ * linked sensor/control state change (no positions; carries a snapshot). The
19233
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19234
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19235
+ */
19236
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
18886
19237
  var TrackSchema = object({
18887
19238
  trackId: string(),
18888
19239
  deviceId: number(),
18889
19240
  className: string(),
18890
19241
  label: string().optional(),
19242
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19243
+ source: TrackSourceSchema.optional(),
18891
19244
  firstSeen: number(),
18892
19245
  lastSeen: number(),
18893
19246
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19142,6 +19495,26 @@ var TrackCascadeCountsSchema = object({
19142
19495
  /** Per-track CLIP search vectors removed (best-effort). */
19143
19496
  embeddings: number().int()
19144
19497
  });
19498
+ /** Event-store footprint for one camera. */
19499
+ var EventStoreDeviceFootprintSchema = object({
19500
+ deviceId: number(),
19501
+ /** Persisted event rows (motion + object + audio) for the camera. */
19502
+ rows: number().int(),
19503
+ /** Event-owned media bytes on disk for the camera. */
19504
+ bytes: number().int()
19505
+ });
19506
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19507
+ var EventStoreFootprintSchema = object({
19508
+ totalRows: number().int(),
19509
+ totalBytes: number().int(),
19510
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19511
+ });
19512
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19513
+ var EventPruneCountsSchema = object({
19514
+ motion: number().int(),
19515
+ object: number().int(),
19516
+ audio: number().int()
19517
+ });
19145
19518
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19146
19519
  deviceId: number(),
19147
19520
  trackId: string()
@@ -19205,6 +19578,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19205
19578
  }), {
19206
19579
  kind: "mutation",
19207
19580
  auth: "admin"
19581
+ }), method(object({}), EventStoreFootprintSchema, {
19582
+ kind: "query",
19583
+ auth: "admin"
19584
+ }), method(object({
19585
+ olderThanMs: number(),
19586
+ reason: OpsLogReasonSchema.optional()
19587
+ }), EventPruneCountsSchema, {
19588
+ kind: "mutation",
19589
+ auth: "admin"
19590
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19591
+ kind: "mutation",
19592
+ auth: "admin"
19593
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19594
+ kind: "query",
19595
+ auth: "admin"
19208
19596
  }), method(object({
19209
19597
  eventId: string(),
19210
19598
  kind: MediaFileKindEnum.optional()
@@ -19229,6 +19617,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19229
19617
  eventId: string(),
19230
19618
  timestamp: number()
19231
19619
  });
19620
+ /**
19621
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19622
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19623
+ * caps into per-camera event-kind descriptors.
19624
+ *
19625
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19626
+ * is NOT duplicated here — every entry is derived from the single
19627
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19628
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19629
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19630
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19631
+ * eventful cap is missing.
19632
+ */
19633
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19634
+ var LEGACY_ICON = {
19635
+ motion: "motion",
19636
+ audio: "audio",
19637
+ person: "person",
19638
+ vehicle: "vehicle",
19639
+ animal: "animal",
19640
+ package: "package",
19641
+ door: "door",
19642
+ pir: "pir",
19643
+ smoke: "smoke",
19644
+ water: "water",
19645
+ button: "button",
19646
+ generic: "generic",
19647
+ gas: "smoke",
19648
+ vibration: "generic",
19649
+ tamper: "generic",
19650
+ presence: "person",
19651
+ lock: "generic",
19652
+ siren: "generic",
19653
+ switch: "generic",
19654
+ doorbell: "button"
19655
+ };
19656
+ function legacyIcon(iconId) {
19657
+ return LEGACY_ICON[iconId] ?? "generic";
19658
+ }
19659
+ /**
19660
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19661
+ * The anti-drift guard cross-checks this against the eventful caps declared
19662
+ * in `packages/types/src/capabilities/*.cap.ts`.
19663
+ */
19664
+ var CAP_TO_KIND = {
19665
+ contact: "contact",
19666
+ motion: "motion-sensor",
19667
+ smoke: "smoke",
19668
+ flood: "flood",
19669
+ gas: "gas",
19670
+ "carbon-monoxide": "carbon-monoxide",
19671
+ vibration: "vibration",
19672
+ tamper: "tamper",
19673
+ presence: "presence",
19674
+ "enum-sensor": "enum-sensor",
19675
+ "event-emitter": "device-event",
19676
+ "lock-control": "lock",
19677
+ switch: "switch",
19678
+ button: "button",
19679
+ doorbell: "doorbell"
19680
+ };
19681
+ function buildDescriptor(capName, kind) {
19682
+ const t = EVENT_TAXONOMY[kind];
19683
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19684
+ return {
19685
+ ...t,
19686
+ icon: legacyIcon(t.iconId)
19687
+ };
19688
+ }
19689
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19232
19690
  var CameraPipelineConfigSchema = object({
19233
19691
  engine: PipelineEngineChoiceSchema.optional(),
19234
19692
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22438,13 +22896,29 @@ method(object({
22438
22896
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22439
22897
  kind: "mutation",
22440
22898
  auth: "admin"
22441
- }), method(object({ deviceId: number() }), object({
22899
+ }), method(object({
22900
+ deviceId: number(),
22901
+ reason: OpsLogReasonSchema.optional()
22902
+ }), object({
22442
22903
  floorMs: number().nullable(),
22443
22904
  deletedBuckets: number().int(),
22444
22905
  reclaimedBytes: number().int()
22445
22906
  }), {
22446
22907
  kind: "mutation",
22447
22908
  auth: "admin"
22909
+ }), method(object({
22910
+ deviceId: number(),
22911
+ fromMs: number().optional(),
22912
+ toMs: number().optional()
22913
+ }), object({
22914
+ deletedBuckets: number().int(),
22915
+ reclaimedBytes: number().int()
22916
+ }), {
22917
+ kind: "mutation",
22918
+ auth: "admin"
22919
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
22920
+ kind: "query",
22921
+ auth: "admin"
22448
22922
  });
22449
22923
  /**
22450
22924
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25566,6 +26040,12 @@ Object.freeze({
25566
26040
  addonId: null,
25567
26041
  access: "delete"
25568
26042
  },
26043
+ "pipelineAnalytics.deleteDeviceEvents": {
26044
+ capName: "pipeline-analytics",
26045
+ capScope: "device",
26046
+ addonId: null,
26047
+ access: "delete"
26048
+ },
25569
26049
  "pipelineAnalytics.deleteTracks": {
25570
26050
  capName: "pipeline-analytics",
25571
26051
  capScope: "device",
@@ -25596,6 +26076,12 @@ Object.freeze({
25596
26076
  addonId: null,
25597
26077
  access: "view"
25598
26078
  },
26079
+ "pipelineAnalytics.getEventStoreFootprint": {
26080
+ capName: "pipeline-analytics",
26081
+ capScope: "device",
26082
+ addonId: null,
26083
+ access: "view"
26084
+ },
25599
26085
  "pipelineAnalytics.getKeyEvents": {
25600
26086
  capName: "pipeline-analytics",
25601
26087
  capScope: "device",
@@ -25638,6 +26124,12 @@ Object.freeze({
25638
26124
  addonId: null,
25639
26125
  access: "view"
25640
26126
  },
26127
+ "pipelineAnalytics.listOpsLog": {
26128
+ capName: "pipeline-analytics",
26129
+ capScope: "device",
26130
+ addonId: null,
26131
+ access: "view"
26132
+ },
25641
26133
  "pipelineAnalytics.listRecentTracks": {
25642
26134
  capName: "pipeline-analytics",
25643
26135
  capScope: "device",
@@ -25650,6 +26142,12 @@ Object.freeze({
25650
26142
  addonId: null,
25651
26143
  access: "view"
25652
26144
  },
26145
+ "pipelineAnalytics.pruneEvents": {
26146
+ capName: "pipeline-analytics",
26147
+ capScope: "device",
26148
+ addonId: null,
26149
+ access: "create"
26150
+ },
25653
26151
  "pipelineAnalytics.pruneEventsBefore": {
25654
26152
  capName: "pipeline-analytics",
25655
26153
  capScope: "device",
@@ -26412,6 +26910,12 @@ Object.freeze({
26412
26910
  addonId: null,
26413
26911
  access: "create"
26414
26912
  },
26913
+ "recording.deleteFootprint": {
26914
+ capName: "recording",
26915
+ capScope: "system",
26916
+ addonId: null,
26917
+ access: "delete"
26918
+ },
26415
26919
  "recording.getAvailability": {
26416
26920
  capName: "recording",
26417
26921
  capScope: "system",
@@ -26442,6 +26946,12 @@ Object.freeze({
26442
26946
  addonId: null,
26443
26947
  access: "view"
26444
26948
  },
26949
+ "recording.listOpsLog": {
26950
+ capName: "recording",
26951
+ capScope: "system",
26952
+ addonId: null,
26953
+ access: "view"
26954
+ },
26445
26955
  "recording.locateSegment": {
26446
26956
  capName: "recording",
26447
26957
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-ecowitt",
3
- "version": "0.1.25",
3
+ "version": "0.1.27",
4
4
  "description": "Ecowitt weather-station device-provider addon for CamStack — wraps the @apocaliss92/nodewitt local-poll / push client",
5
5
  "keywords": [
6
6
  "camstack",