@camstack/addon-provider-rademacher 0.1.12 → 0.1.14

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
@@ -8343,6 +8343,62 @@ var RecordingConfigSchema = object({
8343
8343
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
8344
8344
  });
8345
8345
  /**
8346
+ * Ops-log — the durable, append-only operations audit shared by the
8347
+ * recordings and events management surfaces.
8348
+ *
8349
+ * ONE row shape is reused for both domains so a single "Activity" view can
8350
+ * merge the recorder's DurableState ring (recordings ops-log) and the
8351
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
8352
+ * management operation, WHY it ran (reason), and its measurable effect
8353
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
8354
+ * never fail the operation it records.
8355
+ */
8356
+ /** Which management domain the operation belongs to. */
8357
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
8358
+ /** The kind of management operation performed. */
8359
+ var OpsLogOpSchema = _enum([
8360
+ "prune",
8361
+ "manual-delete",
8362
+ "rescan",
8363
+ "retention-run"
8364
+ ]);
8365
+ /** Why the operation ran. */
8366
+ var OpsLogReasonSchema = _enum([
8367
+ "retention",
8368
+ "quota",
8369
+ "manual",
8370
+ "operator"
8371
+ ]);
8372
+ /** One audit row, shared verbatim by both domains. */
8373
+ var OpsLogEntrySchema = object({
8374
+ /** Unique row id. */
8375
+ id: string(),
8376
+ /** Epoch ms the operation completed. */
8377
+ at: number(),
8378
+ domain: OpsLogDomainSchema,
8379
+ op: OpsLogOpSchema,
8380
+ reason: OpsLogReasonSchema,
8381
+ /** The camera the op targeted; null for a cluster/global op. */
8382
+ deviceId: number().nullable(),
8383
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
8384
+ nodeId: string(),
8385
+ /** Buckets / rows deleted (op-specific unit). */
8386
+ itemsAffected: number(),
8387
+ /** Bytes reclaimed by the op (0 when not measurable). */
8388
+ bytesReclaimed: number(),
8389
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
8390
+ detail: string().nullable(),
8391
+ /** Who/what triggered the op. */
8392
+ actor: string()
8393
+ });
8394
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
8395
+ var OpsLogQueryInputSchema = object({
8396
+ /** Restrict to a single camera; omit for every row. */
8397
+ deviceId: number().optional(),
8398
+ /** Max rows returned, newest-first. */
8399
+ limit: number().int().min(1).max(1e3).optional()
8400
+ });
8401
+ /**
8346
8402
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8347
8403
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8348
8404
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8586,6 +8642,160 @@ var EncodeProfileSchema = object({
8586
8642
  */
8587
8643
  outputArgs: array(string()).optional()
8588
8644
  });
8645
+ var COCO_TO_MACRO = {
8646
+ mapping: {
8647
+ person: "person",
8648
+ bicycle: "vehicle",
8649
+ car: "vehicle",
8650
+ motorcycle: "vehicle",
8651
+ airplane: "vehicle",
8652
+ bus: "vehicle",
8653
+ train: "vehicle",
8654
+ truck: "vehicle",
8655
+ boat: "vehicle",
8656
+ bird: "animal",
8657
+ cat: "animal",
8658
+ dog: "animal",
8659
+ horse: "animal",
8660
+ sheep: "animal",
8661
+ cow: "animal",
8662
+ elephant: "animal",
8663
+ bear: "animal",
8664
+ zebra: "animal",
8665
+ giraffe: "animal",
8666
+ suitcase: "package",
8667
+ backpack: "package",
8668
+ handbag: "package"
8669
+ },
8670
+ preserveOriginal: false
8671
+ };
8672
+ var AUDIO_MACRO_LABELS = [
8673
+ {
8674
+ id: "speech",
8675
+ name: "Speech",
8676
+ icon: "🗣️"
8677
+ },
8678
+ {
8679
+ id: "scream",
8680
+ name: "Scream / Shout",
8681
+ icon: "😱"
8682
+ },
8683
+ {
8684
+ id: "crying",
8685
+ name: "Crying / Baby",
8686
+ icon: "😢"
8687
+ },
8688
+ {
8689
+ id: "laughter",
8690
+ name: "Laughter",
8691
+ icon: "😂"
8692
+ },
8693
+ {
8694
+ id: "music",
8695
+ name: "Music",
8696
+ icon: "🎵"
8697
+ },
8698
+ {
8699
+ id: "dog",
8700
+ name: "Dog",
8701
+ icon: "🐕"
8702
+ },
8703
+ {
8704
+ id: "cat",
8705
+ name: "Cat",
8706
+ icon: "🐈"
8707
+ },
8708
+ {
8709
+ id: "bird",
8710
+ name: "Bird",
8711
+ icon: "🐦"
8712
+ },
8713
+ {
8714
+ id: "animal",
8715
+ name: "Animal (other)",
8716
+ icon: "🐾"
8717
+ },
8718
+ {
8719
+ id: "alarm",
8720
+ name: "Alarm / Siren",
8721
+ icon: "🚨"
8722
+ },
8723
+ {
8724
+ id: "doorbell",
8725
+ name: "Doorbell / Knock",
8726
+ icon: "🔔"
8727
+ },
8728
+ {
8729
+ id: "glass_breaking",
8730
+ name: "Glass Breaking",
8731
+ icon: "💥"
8732
+ },
8733
+ {
8734
+ id: "gunshot",
8735
+ name: "Gunshot / Explosion",
8736
+ icon: "💣"
8737
+ },
8738
+ {
8739
+ id: "vehicle",
8740
+ name: "Vehicle",
8741
+ icon: "🚗"
8742
+ },
8743
+ {
8744
+ id: "siren",
8745
+ name: "Emergency Siren",
8746
+ icon: "🚑"
8747
+ },
8748
+ {
8749
+ id: "fire",
8750
+ name: "Fire / Smoke",
8751
+ icon: "🔥"
8752
+ },
8753
+ {
8754
+ id: "water",
8755
+ name: "Water",
8756
+ icon: "💧"
8757
+ },
8758
+ {
8759
+ id: "wind",
8760
+ name: "Wind / Weather",
8761
+ icon: "🌬️"
8762
+ },
8763
+ {
8764
+ id: "door",
8765
+ name: "Door",
8766
+ icon: "🚪"
8767
+ },
8768
+ {
8769
+ id: "footsteps",
8770
+ name: "Footsteps",
8771
+ icon: "👣"
8772
+ },
8773
+ {
8774
+ id: "crowd",
8775
+ name: "Crowd / Chatter",
8776
+ icon: "👥"
8777
+ },
8778
+ {
8779
+ id: "telephone",
8780
+ name: "Telephone",
8781
+ icon: "📞"
8782
+ },
8783
+ {
8784
+ id: "engine",
8785
+ name: "Engine / Motor",
8786
+ icon: "⚙️"
8787
+ },
8788
+ {
8789
+ id: "tools",
8790
+ name: "Tools / Construction",
8791
+ icon: "🔨"
8792
+ },
8793
+ {
8794
+ id: "silence",
8795
+ name: "Silence",
8796
+ icon: "🤫"
8797
+ }
8798
+ ];
8589
8799
  var YAMNET_TO_MACRO = {
8590
8800
  mapping: {
8591
8801
  Speech: "speech",
@@ -8852,6 +9062,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8852
9062
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8853
9063
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8854
9064
  /**
9065
+ * Unified event-kind taxonomy — THE single source of truth for
9066
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
9067
+ * icon }`.
9068
+ *
9069
+ * This dictionary folds together what used to be scattered across four
9070
+ * copies:
9071
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
9072
+ * - `addon-post-analysis/.../services/event-kinds.ts`
9073
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
9074
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
9075
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
9076
+ * - the COCO / audio class maps (macro ↔ sub relationships)
9077
+ *
9078
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
9079
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
9080
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
9081
+ * color or an icon: they read this dictionary (server descriptors carry the
9082
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
9083
+ *
9084
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
9085
+ */
9086
+ var TAXONOMY_COLORS = {
9087
+ motion: "#f59e0b",
9088
+ audio: "#06b6d4",
9089
+ person: "#22c55e",
9090
+ vehicle: "#3b82f6",
9091
+ animal: "#f97316",
9092
+ package: "#a855f7",
9093
+ sensor: "#8b5cf6",
9094
+ control: "#10b981",
9095
+ genericDetection: "#64748b"
9096
+ };
9097
+ var DETECTION_SUB_COLORS = {
9098
+ car: "#f59e0b",
9099
+ truck: "#d97706",
9100
+ bus: "#b45309",
9101
+ motorcycle: "#eab308",
9102
+ bicycle: "#ca8a04",
9103
+ airplane: "#60a5fa",
9104
+ boat: "#2563eb",
9105
+ train: "#1d4ed8",
9106
+ bird: "#14b8a6",
9107
+ dog: "#84cc16",
9108
+ cat: "#f97316",
9109
+ horse: "#a16207",
9110
+ sheep: "#a3a3a3",
9111
+ cow: "#78716c",
9112
+ elephant: "#6b7280",
9113
+ bear: "#7c2d12",
9114
+ zebra: "#404040",
9115
+ giraffe: "#d4a373"
9116
+ };
9117
+ function titleCase(id) {
9118
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
9119
+ }
9120
+ var entries = /* @__PURE__ */ new Map();
9121
+ function macro(kind, category, color, iconId, label) {
9122
+ entries.set(kind, {
9123
+ kind,
9124
+ parentKind: null,
9125
+ level: "macro",
9126
+ category,
9127
+ color,
9128
+ iconId,
9129
+ labelKey: `eventKind.${kind}`,
9130
+ label
9131
+ });
9132
+ }
9133
+ function sub(kind, parentKind, category, color, iconId, label) {
9134
+ entries.set(kind, {
9135
+ kind,
9136
+ parentKind,
9137
+ level: "sub",
9138
+ category,
9139
+ color,
9140
+ iconId,
9141
+ labelKey: `eventKind.${kind}`,
9142
+ label
9143
+ });
9144
+ }
9145
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
9146
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
9147
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
9148
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
9149
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
9150
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
9151
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
9152
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
9153
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
9154
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
9155
+ if (entries.has(cocoClass)) continue;
9156
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
9157
+ }
9158
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
9159
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
9160
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
9161
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
9162
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
9163
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
9164
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
9165
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
9166
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
9167
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
9168
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
9169
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
9170
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
9171
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
9172
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
9173
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
9174
+ sub("button", "control", "control", "#10b981", "button", "Button");
9175
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
9176
+ for (const l of AUDIO_MACRO_LABELS) {
9177
+ const kind = `audio-${l.id}`;
9178
+ if (entries.has(kind)) continue;
9179
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
9180
+ }
9181
+ /** The complete taxonomy dictionary, keyed by kind. */
9182
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
9183
+ /**
8855
9184
  * Error types for the safe expression engine. Two distinct classes so callers
8856
9185
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8857
9186
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15806,9 +16135,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
15806
16135
  * `package` backs the package-drop detector — a package zone is a
15807
16136
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
15808
16137
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15809
- * The orchestrator provider that writes this stage lands in a later
15810
- * slice; until then the mirror carries only `{motion, detection}` and
15811
- * consumers read `package` as absent (treat as `[]`).
16138
+ * The orchestrator provider writes this stage as a first-class slice
16139
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
16140
+ * package}` shape, so consumers read the current package rules directly
16141
+ * off `device.state.zoneRules.value.package`.
15812
16142
  */
15813
16143
  runtimeState: object({
15814
16144
  motion: array(ZoneRuleSchema).readonly(),
@@ -19783,17 +20113,30 @@ var EventKindCategorySchema = _enum([
19783
20113
  "audio",
19784
20114
  "detection",
19785
20115
  "sensor",
20116
+ "control",
19786
20117
  "custom",
19787
20118
  "package"
19788
20119
  ]);
20120
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
20121
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19789
20122
  var EventKindDescriptorSchema = object({
19790
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
20123
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19791
20124
  kind: string(),
20125
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
20126
+ labelKey: string(),
20127
+ /** English fallback label (kept for clients that don't translate). */
19792
20128
  label: string(),
19793
20129
  /** Hex color for timeline/legend rendering. */
19794
20130
  color: string(),
20131
+ /** Dictionary id → lucide component on the UI side. */
20132
+ iconId: string(),
20133
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19795
20134
  icon: EventKindIconSchema,
19796
20135
  category: EventKindCategorySchema,
20136
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
20137
+ parentKind: string().nullable(),
20138
+ /** Derived from `parentKind`, explicit for the client tree. */
20139
+ level: EventKindLevelSchema,
19797
20140
  /** Which cap + device contributes this kind. For built-ins the camera
19798
20141
  * itself; for sensor kinds the LINKED source device. */
19799
20142
  source: object({
@@ -19866,11 +20209,21 @@ var TrackAudioLabelSchema = object({
19866
20209
  firstAt: number(),
19867
20210
  lastAt: number()
19868
20211
  });
20212
+ /**
20213
+ * How a track was produced. `pipeline` (default / absent) = the spatial
20214
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
20215
+ * linked sensor/control state change (no positions; carries a snapshot). The
20216
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
20217
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
20218
+ */
20219
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19869
20220
  var TrackSchema = object({
19870
20221
  trackId: string(),
19871
20222
  deviceId: number(),
19872
20223
  className: string(),
19873
20224
  label: string().optional(),
20225
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
20226
+ source: TrackSourceSchema.optional(),
19874
20227
  firstSeen: number(),
19875
20228
  lastSeen: number(),
19876
20229
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -20125,6 +20478,26 @@ var TrackCascadeCountsSchema = object({
20125
20478
  /** Per-track CLIP search vectors removed (best-effort). */
20126
20479
  embeddings: number().int()
20127
20480
  });
20481
+ /** Event-store footprint for one camera. */
20482
+ var EventStoreDeviceFootprintSchema = object({
20483
+ deviceId: number(),
20484
+ /** Persisted event rows (motion + object + audio) for the camera. */
20485
+ rows: number().int(),
20486
+ /** Event-owned media bytes on disk for the camera. */
20487
+ bytes: number().int()
20488
+ });
20489
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
20490
+ var EventStoreFootprintSchema = object({
20491
+ totalRows: number().int(),
20492
+ totalBytes: number().int(),
20493
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
20494
+ });
20495
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
20496
+ var EventPruneCountsSchema = object({
20497
+ motion: number().int(),
20498
+ object: number().int(),
20499
+ audio: number().int()
20500
+ });
20128
20501
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
20129
20502
  deviceId: number(),
20130
20503
  trackId: string()
@@ -20188,6 +20561,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20188
20561
  }), {
20189
20562
  kind: "mutation",
20190
20563
  auth: "admin"
20564
+ }), method(object({}), EventStoreFootprintSchema, {
20565
+ kind: "query",
20566
+ auth: "admin"
20567
+ }), method(object({
20568
+ olderThanMs: number(),
20569
+ reason: OpsLogReasonSchema.optional()
20570
+ }), EventPruneCountsSchema, {
20571
+ kind: "mutation",
20572
+ auth: "admin"
20573
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
20574
+ kind: "mutation",
20575
+ auth: "admin"
20576
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20577
+ kind: "query",
20578
+ auth: "admin"
20191
20579
  }), method(object({
20192
20580
  eventId: string(),
20193
20581
  kind: MediaFileKindEnum.optional()
@@ -20212,6 +20600,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20212
20600
  eventId: string(),
20213
20601
  timestamp: number()
20214
20602
  });
20603
+ /**
20604
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
20605
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
20606
+ * caps into per-camera event-kind descriptors.
20607
+ *
20608
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
20609
+ * is NOT duplicated here — every entry is derived from the single
20610
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
20611
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
20612
+ * control cap means adding one line here (and a taxonomy entry); the anti-
20613
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
20614
+ * eventful cap is missing.
20615
+ */
20616
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
20617
+ var LEGACY_ICON = {
20618
+ motion: "motion",
20619
+ audio: "audio",
20620
+ person: "person",
20621
+ vehicle: "vehicle",
20622
+ animal: "animal",
20623
+ package: "package",
20624
+ door: "door",
20625
+ pir: "pir",
20626
+ smoke: "smoke",
20627
+ water: "water",
20628
+ button: "button",
20629
+ generic: "generic",
20630
+ gas: "smoke",
20631
+ vibration: "generic",
20632
+ tamper: "generic",
20633
+ presence: "person",
20634
+ lock: "generic",
20635
+ siren: "generic",
20636
+ switch: "generic",
20637
+ doorbell: "button"
20638
+ };
20639
+ function legacyIcon(iconId) {
20640
+ return LEGACY_ICON[iconId] ?? "generic";
20641
+ }
20642
+ /**
20643
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
20644
+ * The anti-drift guard cross-checks this against the eventful caps declared
20645
+ * in `packages/types/src/capabilities/*.cap.ts`.
20646
+ */
20647
+ var CAP_TO_KIND = {
20648
+ contact: "contact",
20649
+ motion: "motion-sensor",
20650
+ smoke: "smoke",
20651
+ flood: "flood",
20652
+ gas: "gas",
20653
+ "carbon-monoxide": "carbon-monoxide",
20654
+ vibration: "vibration",
20655
+ tamper: "tamper",
20656
+ presence: "presence",
20657
+ "enum-sensor": "enum-sensor",
20658
+ "event-emitter": "device-event",
20659
+ "lock-control": "lock",
20660
+ switch: "switch",
20661
+ button: "button",
20662
+ doorbell: "doorbell"
20663
+ };
20664
+ function buildDescriptor(capName, kind) {
20665
+ const t = EVENT_TAXONOMY[kind];
20666
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
20667
+ return {
20668
+ ...t,
20669
+ icon: legacyIcon(t.iconId)
20670
+ };
20671
+ }
20672
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
20215
20673
  var CameraPipelineConfigSchema = object({
20216
20674
  engine: PipelineEngineChoiceSchema.optional(),
20217
20675
  steps: array(PipelineStepInputSchema).readonly(),
@@ -23421,13 +23879,29 @@ method(object({
23421
23879
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
23422
23880
  kind: "mutation",
23423
23881
  auth: "admin"
23424
- }), method(object({ deviceId: number() }), object({
23882
+ }), method(object({
23883
+ deviceId: number(),
23884
+ reason: OpsLogReasonSchema.optional()
23885
+ }), object({
23425
23886
  floorMs: number().nullable(),
23426
23887
  deletedBuckets: number().int(),
23427
23888
  reclaimedBytes: number().int()
23428
23889
  }), {
23429
23890
  kind: "mutation",
23430
23891
  auth: "admin"
23892
+ }), method(object({
23893
+ deviceId: number(),
23894
+ fromMs: number().optional(),
23895
+ toMs: number().optional()
23896
+ }), object({
23897
+ deletedBuckets: number().int(),
23898
+ reclaimedBytes: number().int()
23899
+ }), {
23900
+ kind: "mutation",
23901
+ auth: "admin"
23902
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23903
+ kind: "query",
23904
+ auth: "admin"
23431
23905
  });
23432
23906
  /**
23433
23907
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -26549,6 +27023,12 @@ Object.freeze({
26549
27023
  addonId: null,
26550
27024
  access: "delete"
26551
27025
  },
27026
+ "pipelineAnalytics.deleteDeviceEvents": {
27027
+ capName: "pipeline-analytics",
27028
+ capScope: "device",
27029
+ addonId: null,
27030
+ access: "delete"
27031
+ },
26552
27032
  "pipelineAnalytics.deleteTracks": {
26553
27033
  capName: "pipeline-analytics",
26554
27034
  capScope: "device",
@@ -26579,6 +27059,12 @@ Object.freeze({
26579
27059
  addonId: null,
26580
27060
  access: "view"
26581
27061
  },
27062
+ "pipelineAnalytics.getEventStoreFootprint": {
27063
+ capName: "pipeline-analytics",
27064
+ capScope: "device",
27065
+ addonId: null,
27066
+ access: "view"
27067
+ },
26582
27068
  "pipelineAnalytics.getKeyEvents": {
26583
27069
  capName: "pipeline-analytics",
26584
27070
  capScope: "device",
@@ -26621,6 +27107,12 @@ Object.freeze({
26621
27107
  addonId: null,
26622
27108
  access: "view"
26623
27109
  },
27110
+ "pipelineAnalytics.listOpsLog": {
27111
+ capName: "pipeline-analytics",
27112
+ capScope: "device",
27113
+ addonId: null,
27114
+ access: "view"
27115
+ },
26624
27116
  "pipelineAnalytics.listRecentTracks": {
26625
27117
  capName: "pipeline-analytics",
26626
27118
  capScope: "device",
@@ -26633,6 +27125,12 @@ Object.freeze({
26633
27125
  addonId: null,
26634
27126
  access: "view"
26635
27127
  },
27128
+ "pipelineAnalytics.pruneEvents": {
27129
+ capName: "pipeline-analytics",
27130
+ capScope: "device",
27131
+ addonId: null,
27132
+ access: "create"
27133
+ },
26636
27134
  "pipelineAnalytics.pruneEventsBefore": {
26637
27135
  capName: "pipeline-analytics",
26638
27136
  capScope: "device",
@@ -27395,6 +27893,12 @@ Object.freeze({
27395
27893
  addonId: null,
27396
27894
  access: "create"
27397
27895
  },
27896
+ "recording.deleteFootprint": {
27897
+ capName: "recording",
27898
+ capScope: "system",
27899
+ addonId: null,
27900
+ access: "delete"
27901
+ },
27398
27902
  "recording.getAvailability": {
27399
27903
  capName: "recording",
27400
27904
  capScope: "system",
@@ -27425,6 +27929,12 @@ Object.freeze({
27425
27929
  addonId: null,
27426
27930
  access: "view"
27427
27931
  },
27932
+ "recording.listOpsLog": {
27933
+ capName: "recording",
27934
+ capScope: "system",
27935
+ addonId: null,
27936
+ access: "view"
27937
+ },
27428
27938
  "recording.locateSegment": {
27429
27939
  capName: "recording",
27430
27940
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -8342,6 +8342,62 @@ var RecordingConfigSchema = object({
8342
8342
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
8343
8343
  });
8344
8344
  /**
8345
+ * Ops-log — the durable, append-only operations audit shared by the
8346
+ * recordings and events management surfaces.
8347
+ *
8348
+ * ONE row shape is reused for both domains so a single "Activity" view can
8349
+ * merge the recorder's DurableState ring (recordings ops-log) and the
8350
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
8351
+ * management operation, WHY it ran (reason), and its measurable effect
8352
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
8353
+ * never fail the operation it records.
8354
+ */
8355
+ /** Which management domain the operation belongs to. */
8356
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
8357
+ /** The kind of management operation performed. */
8358
+ var OpsLogOpSchema = _enum([
8359
+ "prune",
8360
+ "manual-delete",
8361
+ "rescan",
8362
+ "retention-run"
8363
+ ]);
8364
+ /** Why the operation ran. */
8365
+ var OpsLogReasonSchema = _enum([
8366
+ "retention",
8367
+ "quota",
8368
+ "manual",
8369
+ "operator"
8370
+ ]);
8371
+ /** One audit row, shared verbatim by both domains. */
8372
+ var OpsLogEntrySchema = object({
8373
+ /** Unique row id. */
8374
+ id: string(),
8375
+ /** Epoch ms the operation completed. */
8376
+ at: number(),
8377
+ domain: OpsLogDomainSchema,
8378
+ op: OpsLogOpSchema,
8379
+ reason: OpsLogReasonSchema,
8380
+ /** The camera the op targeted; null for a cluster/global op. */
8381
+ deviceId: number().nullable(),
8382
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
8383
+ nodeId: string(),
8384
+ /** Buckets / rows deleted (op-specific unit). */
8385
+ itemsAffected: number(),
8386
+ /** Bytes reclaimed by the op (0 when not measurable). */
8387
+ bytesReclaimed: number(),
8388
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
8389
+ detail: string().nullable(),
8390
+ /** Who/what triggered the op. */
8391
+ actor: string()
8392
+ });
8393
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
8394
+ var OpsLogQueryInputSchema = object({
8395
+ /** Restrict to a single camera; omit for every row. */
8396
+ deviceId: number().optional(),
8397
+ /** Max rows returned, newest-first. */
8398
+ limit: number().int().min(1).max(1e3).optional()
8399
+ });
8400
+ /**
8345
8401
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8346
8402
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8347
8403
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8585,6 +8641,160 @@ var EncodeProfileSchema = object({
8585
8641
  */
8586
8642
  outputArgs: array(string()).optional()
8587
8643
  });
8644
+ var COCO_TO_MACRO = {
8645
+ mapping: {
8646
+ person: "person",
8647
+ bicycle: "vehicle",
8648
+ car: "vehicle",
8649
+ motorcycle: "vehicle",
8650
+ airplane: "vehicle",
8651
+ bus: "vehicle",
8652
+ train: "vehicle",
8653
+ truck: "vehicle",
8654
+ boat: "vehicle",
8655
+ bird: "animal",
8656
+ cat: "animal",
8657
+ dog: "animal",
8658
+ horse: "animal",
8659
+ sheep: "animal",
8660
+ cow: "animal",
8661
+ elephant: "animal",
8662
+ bear: "animal",
8663
+ zebra: "animal",
8664
+ giraffe: "animal",
8665
+ suitcase: "package",
8666
+ backpack: "package",
8667
+ handbag: "package"
8668
+ },
8669
+ preserveOriginal: false
8670
+ };
8671
+ var AUDIO_MACRO_LABELS = [
8672
+ {
8673
+ id: "speech",
8674
+ name: "Speech",
8675
+ icon: "🗣️"
8676
+ },
8677
+ {
8678
+ id: "scream",
8679
+ name: "Scream / Shout",
8680
+ icon: "😱"
8681
+ },
8682
+ {
8683
+ id: "crying",
8684
+ name: "Crying / Baby",
8685
+ icon: "😢"
8686
+ },
8687
+ {
8688
+ id: "laughter",
8689
+ name: "Laughter",
8690
+ icon: "😂"
8691
+ },
8692
+ {
8693
+ id: "music",
8694
+ name: "Music",
8695
+ icon: "🎵"
8696
+ },
8697
+ {
8698
+ id: "dog",
8699
+ name: "Dog",
8700
+ icon: "🐕"
8701
+ },
8702
+ {
8703
+ id: "cat",
8704
+ name: "Cat",
8705
+ icon: "🐈"
8706
+ },
8707
+ {
8708
+ id: "bird",
8709
+ name: "Bird",
8710
+ icon: "🐦"
8711
+ },
8712
+ {
8713
+ id: "animal",
8714
+ name: "Animal (other)",
8715
+ icon: "🐾"
8716
+ },
8717
+ {
8718
+ id: "alarm",
8719
+ name: "Alarm / Siren",
8720
+ icon: "🚨"
8721
+ },
8722
+ {
8723
+ id: "doorbell",
8724
+ name: "Doorbell / Knock",
8725
+ icon: "🔔"
8726
+ },
8727
+ {
8728
+ id: "glass_breaking",
8729
+ name: "Glass Breaking",
8730
+ icon: "💥"
8731
+ },
8732
+ {
8733
+ id: "gunshot",
8734
+ name: "Gunshot / Explosion",
8735
+ icon: "💣"
8736
+ },
8737
+ {
8738
+ id: "vehicle",
8739
+ name: "Vehicle",
8740
+ icon: "🚗"
8741
+ },
8742
+ {
8743
+ id: "siren",
8744
+ name: "Emergency Siren",
8745
+ icon: "🚑"
8746
+ },
8747
+ {
8748
+ id: "fire",
8749
+ name: "Fire / Smoke",
8750
+ icon: "🔥"
8751
+ },
8752
+ {
8753
+ id: "water",
8754
+ name: "Water",
8755
+ icon: "💧"
8756
+ },
8757
+ {
8758
+ id: "wind",
8759
+ name: "Wind / Weather",
8760
+ icon: "🌬️"
8761
+ },
8762
+ {
8763
+ id: "door",
8764
+ name: "Door",
8765
+ icon: "🚪"
8766
+ },
8767
+ {
8768
+ id: "footsteps",
8769
+ name: "Footsteps",
8770
+ icon: "👣"
8771
+ },
8772
+ {
8773
+ id: "crowd",
8774
+ name: "Crowd / Chatter",
8775
+ icon: "👥"
8776
+ },
8777
+ {
8778
+ id: "telephone",
8779
+ name: "Telephone",
8780
+ icon: "📞"
8781
+ },
8782
+ {
8783
+ id: "engine",
8784
+ name: "Engine / Motor",
8785
+ icon: "⚙️"
8786
+ },
8787
+ {
8788
+ id: "tools",
8789
+ name: "Tools / Construction",
8790
+ icon: "🔨"
8791
+ },
8792
+ {
8793
+ id: "silence",
8794
+ name: "Silence",
8795
+ icon: "🤫"
8796
+ }
8797
+ ];
8588
8798
  var YAMNET_TO_MACRO = {
8589
8799
  mapping: {
8590
8800
  Speech: "speech",
@@ -8851,6 +9061,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
8851
9061
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8852
9062
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
8853
9063
  /**
9064
+ * Unified event-kind taxonomy — THE single source of truth for
9065
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
9066
+ * icon }`.
9067
+ *
9068
+ * This dictionary folds together what used to be scattered across four
9069
+ * copies:
9070
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
9071
+ * - `addon-post-analysis/.../services/event-kinds.ts`
9072
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
9073
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
9074
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
9075
+ * - the COCO / audio class maps (macro ↔ sub relationships)
9076
+ *
9077
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
9078
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
9079
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
9080
+ * color or an icon: they read this dictionary (server descriptors carry the
9081
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
9082
+ *
9083
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
9084
+ */
9085
+ var TAXONOMY_COLORS = {
9086
+ motion: "#f59e0b",
9087
+ audio: "#06b6d4",
9088
+ person: "#22c55e",
9089
+ vehicle: "#3b82f6",
9090
+ animal: "#f97316",
9091
+ package: "#a855f7",
9092
+ sensor: "#8b5cf6",
9093
+ control: "#10b981",
9094
+ genericDetection: "#64748b"
9095
+ };
9096
+ var DETECTION_SUB_COLORS = {
9097
+ car: "#f59e0b",
9098
+ truck: "#d97706",
9099
+ bus: "#b45309",
9100
+ motorcycle: "#eab308",
9101
+ bicycle: "#ca8a04",
9102
+ airplane: "#60a5fa",
9103
+ boat: "#2563eb",
9104
+ train: "#1d4ed8",
9105
+ bird: "#14b8a6",
9106
+ dog: "#84cc16",
9107
+ cat: "#f97316",
9108
+ horse: "#a16207",
9109
+ sheep: "#a3a3a3",
9110
+ cow: "#78716c",
9111
+ elephant: "#6b7280",
9112
+ bear: "#7c2d12",
9113
+ zebra: "#404040",
9114
+ giraffe: "#d4a373"
9115
+ };
9116
+ function titleCase(id) {
9117
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
9118
+ }
9119
+ var entries = /* @__PURE__ */ new Map();
9120
+ function macro(kind, category, color, iconId, label) {
9121
+ entries.set(kind, {
9122
+ kind,
9123
+ parentKind: null,
9124
+ level: "macro",
9125
+ category,
9126
+ color,
9127
+ iconId,
9128
+ labelKey: `eventKind.${kind}`,
9129
+ label
9130
+ });
9131
+ }
9132
+ function sub(kind, parentKind, category, color, iconId, label) {
9133
+ entries.set(kind, {
9134
+ kind,
9135
+ parentKind,
9136
+ level: "sub",
9137
+ category,
9138
+ color,
9139
+ iconId,
9140
+ labelKey: `eventKind.${kind}`,
9141
+ label
9142
+ });
9143
+ }
9144
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
9145
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
9146
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
9147
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
9148
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
9149
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
9150
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
9151
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
9152
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
9153
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
9154
+ if (entries.has(cocoClass)) continue;
9155
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
9156
+ }
9157
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
9158
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
9159
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
9160
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
9161
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
9162
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
9163
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
9164
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
9165
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
9166
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
9167
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
9168
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
9169
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
9170
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
9171
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
9172
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
9173
+ sub("button", "control", "control", "#10b981", "button", "Button");
9174
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
9175
+ for (const l of AUDIO_MACRO_LABELS) {
9176
+ const kind = `audio-${l.id}`;
9177
+ if (entries.has(kind)) continue;
9178
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
9179
+ }
9180
+ /** The complete taxonomy dictionary, keyed by kind. */
9181
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
9182
+ /**
8854
9183
  * Error types for the safe expression engine. Two distinct classes so callers
8855
9184
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
8856
9185
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -15805,9 +16134,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
15805
16134
  * `package` backs the package-drop detector — a package zone is a
15806
16135
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
15807
16136
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
15808
- * The orchestrator provider that writes this stage lands in a later
15809
- * slice; until then the mirror carries only `{motion, detection}` and
15810
- * consumers read `package` as absent (treat as `[]`).
16137
+ * The orchestrator provider writes this stage as a first-class slice
16138
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
16139
+ * package}` shape, so consumers read the current package rules directly
16140
+ * off `device.state.zoneRules.value.package`.
15811
16141
  */
15812
16142
  runtimeState: object({
15813
16143
  motion: array(ZoneRuleSchema).readonly(),
@@ -19782,17 +20112,30 @@ var EventKindCategorySchema = _enum([
19782
20112
  "audio",
19783
20113
  "detection",
19784
20114
  "sensor",
20115
+ "control",
19785
20116
  "custom",
19786
20117
  "package"
19787
20118
  ]);
20119
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
20120
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
19788
20121
  var EventKindDescriptorSchema = object({
19789
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
20122
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
19790
20123
  kind: string(),
20124
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
20125
+ labelKey: string(),
20126
+ /** English fallback label (kept for clients that don't translate). */
19791
20127
  label: string(),
19792
20128
  /** Hex color for timeline/legend rendering. */
19793
20129
  color: string(),
20130
+ /** Dictionary id → lucide component on the UI side. */
20131
+ iconId: string(),
20132
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
19794
20133
  icon: EventKindIconSchema,
19795
20134
  category: EventKindCategorySchema,
20135
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
20136
+ parentKind: string().nullable(),
20137
+ /** Derived from `parentKind`, explicit for the client tree. */
20138
+ level: EventKindLevelSchema,
19796
20139
  /** Which cap + device contributes this kind. For built-ins the camera
19797
20140
  * itself; for sensor kinds the LINKED source device. */
19798
20141
  source: object({
@@ -19865,11 +20208,21 @@ var TrackAudioLabelSchema = object({
19865
20208
  firstAt: number(),
19866
20209
  lastAt: number()
19867
20210
  });
20211
+ /**
20212
+ * How a track was produced. `pipeline` (default / absent) = the spatial
20213
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
20214
+ * linked sensor/control state change (no positions; carries a snapshot). The
20215
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
20216
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
20217
+ */
20218
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
19868
20219
  var TrackSchema = object({
19869
20220
  trackId: string(),
19870
20221
  deviceId: number(),
19871
20222
  className: string(),
19872
20223
  label: string().optional(),
20224
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
20225
+ source: TrackSourceSchema.optional(),
19873
20226
  firstSeen: number(),
19874
20227
  lastSeen: number(),
19875
20228
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -20124,6 +20477,26 @@ var TrackCascadeCountsSchema = object({
20124
20477
  /** Per-track CLIP search vectors removed (best-effort). */
20125
20478
  embeddings: number().int()
20126
20479
  });
20480
+ /** Event-store footprint for one camera. */
20481
+ var EventStoreDeviceFootprintSchema = object({
20482
+ deviceId: number(),
20483
+ /** Persisted event rows (motion + object + audio) for the camera. */
20484
+ rows: number().int(),
20485
+ /** Event-owned media bytes on disk for the camera. */
20486
+ bytes: number().int()
20487
+ });
20488
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
20489
+ var EventStoreFootprintSchema = object({
20490
+ totalRows: number().int(),
20491
+ totalBytes: number().int(),
20492
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
20493
+ });
20494
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
20495
+ var EventPruneCountsSchema = object({
20496
+ motion: number().int(),
20497
+ object: number().int(),
20498
+ audio: number().int()
20499
+ });
20127
20500
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
20128
20501
  deviceId: number(),
20129
20502
  trackId: string()
@@ -20187,6 +20560,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20187
20560
  }), {
20188
20561
  kind: "mutation",
20189
20562
  auth: "admin"
20563
+ }), method(object({}), EventStoreFootprintSchema, {
20564
+ kind: "query",
20565
+ auth: "admin"
20566
+ }), method(object({
20567
+ olderThanMs: number(),
20568
+ reason: OpsLogReasonSchema.optional()
20569
+ }), EventPruneCountsSchema, {
20570
+ kind: "mutation",
20571
+ auth: "admin"
20572
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
20573
+ kind: "mutation",
20574
+ auth: "admin"
20575
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20576
+ kind: "query",
20577
+ auth: "admin"
20190
20578
  }), method(object({
20191
20579
  eventId: string(),
20192
20580
  kind: MediaFileKindEnum.optional()
@@ -20211,6 +20599,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20211
20599
  eventId: string(),
20212
20600
  timestamp: number()
20213
20601
  });
20602
+ /**
20603
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
20604
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
20605
+ * caps into per-camera event-kind descriptors.
20606
+ *
20607
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
20608
+ * is NOT duplicated here — every entry is derived from the single
20609
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
20610
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
20611
+ * control cap means adding one line here (and a taxonomy entry); the anti-
20612
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
20613
+ * eventful cap is missing.
20614
+ */
20615
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
20616
+ var LEGACY_ICON = {
20617
+ motion: "motion",
20618
+ audio: "audio",
20619
+ person: "person",
20620
+ vehicle: "vehicle",
20621
+ animal: "animal",
20622
+ package: "package",
20623
+ door: "door",
20624
+ pir: "pir",
20625
+ smoke: "smoke",
20626
+ water: "water",
20627
+ button: "button",
20628
+ generic: "generic",
20629
+ gas: "smoke",
20630
+ vibration: "generic",
20631
+ tamper: "generic",
20632
+ presence: "person",
20633
+ lock: "generic",
20634
+ siren: "generic",
20635
+ switch: "generic",
20636
+ doorbell: "button"
20637
+ };
20638
+ function legacyIcon(iconId) {
20639
+ return LEGACY_ICON[iconId] ?? "generic";
20640
+ }
20641
+ /**
20642
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
20643
+ * The anti-drift guard cross-checks this against the eventful caps declared
20644
+ * in `packages/types/src/capabilities/*.cap.ts`.
20645
+ */
20646
+ var CAP_TO_KIND = {
20647
+ contact: "contact",
20648
+ motion: "motion-sensor",
20649
+ smoke: "smoke",
20650
+ flood: "flood",
20651
+ gas: "gas",
20652
+ "carbon-monoxide": "carbon-monoxide",
20653
+ vibration: "vibration",
20654
+ tamper: "tamper",
20655
+ presence: "presence",
20656
+ "enum-sensor": "enum-sensor",
20657
+ "event-emitter": "device-event",
20658
+ "lock-control": "lock",
20659
+ switch: "switch",
20660
+ button: "button",
20661
+ doorbell: "doorbell"
20662
+ };
20663
+ function buildDescriptor(capName, kind) {
20664
+ const t = EVENT_TAXONOMY[kind];
20665
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
20666
+ return {
20667
+ ...t,
20668
+ icon: legacyIcon(t.iconId)
20669
+ };
20670
+ }
20671
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
20214
20672
  var CameraPipelineConfigSchema = object({
20215
20673
  engine: PipelineEngineChoiceSchema.optional(),
20216
20674
  steps: array(PipelineStepInputSchema).readonly(),
@@ -23420,13 +23878,29 @@ method(object({
23420
23878
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
23421
23879
  kind: "mutation",
23422
23880
  auth: "admin"
23423
- }), method(object({ deviceId: number() }), object({
23881
+ }), method(object({
23882
+ deviceId: number(),
23883
+ reason: OpsLogReasonSchema.optional()
23884
+ }), object({
23424
23885
  floorMs: number().nullable(),
23425
23886
  deletedBuckets: number().int(),
23426
23887
  reclaimedBytes: number().int()
23427
23888
  }), {
23428
23889
  kind: "mutation",
23429
23890
  auth: "admin"
23891
+ }), method(object({
23892
+ deviceId: number(),
23893
+ fromMs: number().optional(),
23894
+ toMs: number().optional()
23895
+ }), object({
23896
+ deletedBuckets: number().int(),
23897
+ reclaimedBytes: number().int()
23898
+ }), {
23899
+ kind: "mutation",
23900
+ auth: "admin"
23901
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23902
+ kind: "query",
23903
+ auth: "admin"
23430
23904
  });
23431
23905
  /**
23432
23906
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -26548,6 +27022,12 @@ Object.freeze({
26548
27022
  addonId: null,
26549
27023
  access: "delete"
26550
27024
  },
27025
+ "pipelineAnalytics.deleteDeviceEvents": {
27026
+ capName: "pipeline-analytics",
27027
+ capScope: "device",
27028
+ addonId: null,
27029
+ access: "delete"
27030
+ },
26551
27031
  "pipelineAnalytics.deleteTracks": {
26552
27032
  capName: "pipeline-analytics",
26553
27033
  capScope: "device",
@@ -26578,6 +27058,12 @@ Object.freeze({
26578
27058
  addonId: null,
26579
27059
  access: "view"
26580
27060
  },
27061
+ "pipelineAnalytics.getEventStoreFootprint": {
27062
+ capName: "pipeline-analytics",
27063
+ capScope: "device",
27064
+ addonId: null,
27065
+ access: "view"
27066
+ },
26581
27067
  "pipelineAnalytics.getKeyEvents": {
26582
27068
  capName: "pipeline-analytics",
26583
27069
  capScope: "device",
@@ -26620,6 +27106,12 @@ Object.freeze({
26620
27106
  addonId: null,
26621
27107
  access: "view"
26622
27108
  },
27109
+ "pipelineAnalytics.listOpsLog": {
27110
+ capName: "pipeline-analytics",
27111
+ capScope: "device",
27112
+ addonId: null,
27113
+ access: "view"
27114
+ },
26623
27115
  "pipelineAnalytics.listRecentTracks": {
26624
27116
  capName: "pipeline-analytics",
26625
27117
  capScope: "device",
@@ -26632,6 +27124,12 @@ Object.freeze({
26632
27124
  addonId: null,
26633
27125
  access: "view"
26634
27126
  },
27127
+ "pipelineAnalytics.pruneEvents": {
27128
+ capName: "pipeline-analytics",
27129
+ capScope: "device",
27130
+ addonId: null,
27131
+ access: "create"
27132
+ },
26635
27133
  "pipelineAnalytics.pruneEventsBefore": {
26636
27134
  capName: "pipeline-analytics",
26637
27135
  capScope: "device",
@@ -27394,6 +27892,12 @@ Object.freeze({
27394
27892
  addonId: null,
27395
27893
  access: "create"
27396
27894
  },
27895
+ "recording.deleteFootprint": {
27896
+ capName: "recording",
27897
+ capScope: "system",
27898
+ addonId: null,
27899
+ access: "delete"
27900
+ },
27397
27901
  "recording.getAvailability": {
27398
27902
  capName: "recording",
27399
27903
  capScope: "system",
@@ -27424,6 +27928,12 @@ Object.freeze({
27424
27928
  addonId: null,
27425
27929
  access: "view"
27426
27930
  },
27931
+ "recording.listOpsLog": {
27932
+ capName: "recording",
27933
+ capScope: "system",
27934
+ addonId: null,
27935
+ access: "view"
27936
+ },
27427
27937
  "recording.locateSegment": {
27428
27938
  capName: "recording",
27429
27939
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rademacher",
3
- "version": "0.1.12",
3
+ "version": "0.1.14",
4
4
  "description": "Rademacher HomePilot device-provider addon for CamStack — wraps the @apocaliss92/noderademacher local-hub client (roller shutters over the cover cap)",
5
5
  "keywords": [
6
6
  "camstack",