@camstack/addon-matter-broker 0.1.23 → 0.1.25

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
@@ -7372,6 +7372,62 @@ var RecordingConfigSchema = object({
7372
7372
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7373
7373
  });
7374
7374
  /**
7375
+ * Ops-log — the durable, append-only operations audit shared by the
7376
+ * recordings and events management surfaces.
7377
+ *
7378
+ * ONE row shape is reused for both domains so a single "Activity" view can
7379
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7380
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7381
+ * management operation, WHY it ran (reason), and its measurable effect
7382
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7383
+ * never fail the operation it records.
7384
+ */
7385
+ /** Which management domain the operation belongs to. */
7386
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7387
+ /** The kind of management operation performed. */
7388
+ var OpsLogOpSchema = _enum([
7389
+ "prune",
7390
+ "manual-delete",
7391
+ "rescan",
7392
+ "retention-run"
7393
+ ]);
7394
+ /** Why the operation ran. */
7395
+ var OpsLogReasonSchema = _enum([
7396
+ "retention",
7397
+ "quota",
7398
+ "manual",
7399
+ "operator"
7400
+ ]);
7401
+ /** One audit row, shared verbatim by both domains. */
7402
+ var OpsLogEntrySchema = object({
7403
+ /** Unique row id. */
7404
+ id: string$2(),
7405
+ /** Epoch ms the operation completed. */
7406
+ at: number(),
7407
+ domain: OpsLogDomainSchema,
7408
+ op: OpsLogOpSchema,
7409
+ reason: OpsLogReasonSchema,
7410
+ /** The camera the op targeted; null for a cluster/global op. */
7411
+ deviceId: number().nullable(),
7412
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7413
+ nodeId: string$2(),
7414
+ /** Buckets / rows deleted (op-specific unit). */
7415
+ itemsAffected: number(),
7416
+ /** Bytes reclaimed by the op (0 when not measurable). */
7417
+ bytesReclaimed: number(),
7418
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7419
+ detail: string$2().nullable(),
7420
+ /** Who/what triggered the op. */
7421
+ actor: string$2()
7422
+ });
7423
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7424
+ var OpsLogQueryInputSchema = object({
7425
+ /** Restrict to a single camera; omit for every row. */
7426
+ deviceId: number().optional(),
7427
+ /** Max rows returned, newest-first. */
7428
+ limit: number().int().min(1).max(1e3).optional()
7429
+ });
7430
+ /**
7375
7431
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7376
7432
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7377
7433
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7615,6 +7671,160 @@ var EncodeProfileSchema = object({
7615
7671
  */
7616
7672
  outputArgs: array(string$2()).optional()
7617
7673
  });
7674
+ var COCO_TO_MACRO = {
7675
+ mapping: {
7676
+ person: "person",
7677
+ bicycle: "vehicle",
7678
+ car: "vehicle",
7679
+ motorcycle: "vehicle",
7680
+ airplane: "vehicle",
7681
+ bus: "vehicle",
7682
+ train: "vehicle",
7683
+ truck: "vehicle",
7684
+ boat: "vehicle",
7685
+ bird: "animal",
7686
+ cat: "animal",
7687
+ dog: "animal",
7688
+ horse: "animal",
7689
+ sheep: "animal",
7690
+ cow: "animal",
7691
+ elephant: "animal",
7692
+ bear: "animal",
7693
+ zebra: "animal",
7694
+ giraffe: "animal",
7695
+ suitcase: "package",
7696
+ backpack: "package",
7697
+ handbag: "package"
7698
+ },
7699
+ preserveOriginal: false
7700
+ };
7701
+ var AUDIO_MACRO_LABELS = [
7702
+ {
7703
+ id: "speech",
7704
+ name: "Speech",
7705
+ icon: "🗣️"
7706
+ },
7707
+ {
7708
+ id: "scream",
7709
+ name: "Scream / Shout",
7710
+ icon: "😱"
7711
+ },
7712
+ {
7713
+ id: "crying",
7714
+ name: "Crying / Baby",
7715
+ icon: "😢"
7716
+ },
7717
+ {
7718
+ id: "laughter",
7719
+ name: "Laughter",
7720
+ icon: "😂"
7721
+ },
7722
+ {
7723
+ id: "music",
7724
+ name: "Music",
7725
+ icon: "🎵"
7726
+ },
7727
+ {
7728
+ id: "dog",
7729
+ name: "Dog",
7730
+ icon: "🐕"
7731
+ },
7732
+ {
7733
+ id: "cat",
7734
+ name: "Cat",
7735
+ icon: "🐈"
7736
+ },
7737
+ {
7738
+ id: "bird",
7739
+ name: "Bird",
7740
+ icon: "🐦"
7741
+ },
7742
+ {
7743
+ id: "animal",
7744
+ name: "Animal (other)",
7745
+ icon: "🐾"
7746
+ },
7747
+ {
7748
+ id: "alarm",
7749
+ name: "Alarm / Siren",
7750
+ icon: "🚨"
7751
+ },
7752
+ {
7753
+ id: "doorbell",
7754
+ name: "Doorbell / Knock",
7755
+ icon: "🔔"
7756
+ },
7757
+ {
7758
+ id: "glass_breaking",
7759
+ name: "Glass Breaking",
7760
+ icon: "💥"
7761
+ },
7762
+ {
7763
+ id: "gunshot",
7764
+ name: "Gunshot / Explosion",
7765
+ icon: "💣"
7766
+ },
7767
+ {
7768
+ id: "vehicle",
7769
+ name: "Vehicle",
7770
+ icon: "🚗"
7771
+ },
7772
+ {
7773
+ id: "siren",
7774
+ name: "Emergency Siren",
7775
+ icon: "🚑"
7776
+ },
7777
+ {
7778
+ id: "fire",
7779
+ name: "Fire / Smoke",
7780
+ icon: "🔥"
7781
+ },
7782
+ {
7783
+ id: "water",
7784
+ name: "Water",
7785
+ icon: "💧"
7786
+ },
7787
+ {
7788
+ id: "wind",
7789
+ name: "Wind / Weather",
7790
+ icon: "🌬️"
7791
+ },
7792
+ {
7793
+ id: "door",
7794
+ name: "Door",
7795
+ icon: "🚪"
7796
+ },
7797
+ {
7798
+ id: "footsteps",
7799
+ name: "Footsteps",
7800
+ icon: "👣"
7801
+ },
7802
+ {
7803
+ id: "crowd",
7804
+ name: "Crowd / Chatter",
7805
+ icon: "👥"
7806
+ },
7807
+ {
7808
+ id: "telephone",
7809
+ name: "Telephone",
7810
+ icon: "📞"
7811
+ },
7812
+ {
7813
+ id: "engine",
7814
+ name: "Engine / Motor",
7815
+ icon: "⚙️"
7816
+ },
7817
+ {
7818
+ id: "tools",
7819
+ name: "Tools / Construction",
7820
+ icon: "🔨"
7821
+ },
7822
+ {
7823
+ id: "silence",
7824
+ name: "Silence",
7825
+ icon: "🤫"
7826
+ }
7827
+ ];
7618
7828
  var YAMNET_TO_MACRO = {
7619
7829
  mapping: {
7620
7830
  Speech: "speech",
@@ -7881,6 +8091,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7881
8091
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7882
8092
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7883
8093
  /**
8094
+ * Unified event-kind taxonomy — THE single source of truth for
8095
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8096
+ * icon }`.
8097
+ *
8098
+ * This dictionary folds together what used to be scattered across four
8099
+ * copies:
8100
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8101
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8102
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8103
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8104
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8105
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8106
+ *
8107
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8108
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8109
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8110
+ * color or an icon: they read this dictionary (server descriptors carry the
8111
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8112
+ *
8113
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8114
+ */
8115
+ var TAXONOMY_COLORS = {
8116
+ motion: "#f59e0b",
8117
+ audio: "#06b6d4",
8118
+ person: "#22c55e",
8119
+ vehicle: "#3b82f6",
8120
+ animal: "#f97316",
8121
+ package: "#a855f7",
8122
+ sensor: "#8b5cf6",
8123
+ control: "#10b981",
8124
+ genericDetection: "#64748b"
8125
+ };
8126
+ var DETECTION_SUB_COLORS = {
8127
+ car: "#f59e0b",
8128
+ truck: "#d97706",
8129
+ bus: "#b45309",
8130
+ motorcycle: "#eab308",
8131
+ bicycle: "#ca8a04",
8132
+ airplane: "#60a5fa",
8133
+ boat: "#2563eb",
8134
+ train: "#1d4ed8",
8135
+ bird: "#14b8a6",
8136
+ dog: "#84cc16",
8137
+ cat: "#f97316",
8138
+ horse: "#a16207",
8139
+ sheep: "#a3a3a3",
8140
+ cow: "#78716c",
8141
+ elephant: "#6b7280",
8142
+ bear: "#7c2d12",
8143
+ zebra: "#404040",
8144
+ giraffe: "#d4a373"
8145
+ };
8146
+ function titleCase(id) {
8147
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8148
+ }
8149
+ var entries = /* @__PURE__ */ new Map();
8150
+ function macro(kind, category, color, iconId, label) {
8151
+ entries.set(kind, {
8152
+ kind,
8153
+ parentKind: null,
8154
+ level: "macro",
8155
+ category,
8156
+ color,
8157
+ iconId,
8158
+ labelKey: `eventKind.${kind}`,
8159
+ label
8160
+ });
8161
+ }
8162
+ function sub(kind, parentKind, category, color, iconId, label) {
8163
+ entries.set(kind, {
8164
+ kind,
8165
+ parentKind,
8166
+ level: "sub",
8167
+ category,
8168
+ color,
8169
+ iconId,
8170
+ labelKey: `eventKind.${kind}`,
8171
+ label
8172
+ });
8173
+ }
8174
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8175
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8176
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8177
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8178
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8179
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8180
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8181
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8182
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8183
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8184
+ if (entries.has(cocoClass)) continue;
8185
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8186
+ }
8187
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8188
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8189
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8190
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8191
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8192
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8193
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8194
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8195
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8196
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8197
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8198
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8199
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8200
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8201
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8202
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8203
+ sub("button", "control", "control", "#10b981", "button", "Button");
8204
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8205
+ for (const l of AUDIO_MACRO_LABELS) {
8206
+ const kind = `audio-${l.id}`;
8207
+ if (entries.has(kind)) continue;
8208
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8209
+ }
8210
+ /** The complete taxonomy dictionary, keyed by kind. */
8211
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8212
+ /**
7884
8213
  * Error types for the safe expression engine. Two distinct classes so callers
7885
8214
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7886
8215
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14835,9 +15164,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14835
15164
  * `package` backs the package-drop detector — a package zone is a
14836
15165
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14837
15166
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14838
- * The orchestrator provider that writes this stage lands in a later
14839
- * slice; until then the mirror carries only `{motion, detection}` and
14840
- * consumers read `package` as absent (treat as `[]`).
15167
+ * The orchestrator provider writes this stage as a first-class slice
15168
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15169
+ * package}` shape, so consumers read the current package rules directly
15170
+ * off `device.state.zoneRules.value.package`.
14841
15171
  */
14842
15172
  runtimeState: object({
14843
15173
  motion: array(ZoneRuleSchema).readonly(),
@@ -18878,17 +19208,30 @@ var EventKindCategorySchema = _enum([
18878
19208
  "audio",
18879
19209
  "detection",
18880
19210
  "sensor",
19211
+ "control",
18881
19212
  "custom",
18882
19213
  "package"
18883
19214
  ]);
19215
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19216
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18884
19217
  var EventKindDescriptorSchema = object({
18885
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19218
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18886
19219
  kind: string$2(),
19220
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19221
+ labelKey: string$2(),
19222
+ /** English fallback label (kept for clients that don't translate). */
18887
19223
  label: string$2(),
18888
19224
  /** Hex color for timeline/legend rendering. */
18889
19225
  color: string$2(),
19226
+ /** Dictionary id → lucide component on the UI side. */
19227
+ iconId: string$2(),
19228
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18890
19229
  icon: EventKindIconSchema,
18891
19230
  category: EventKindCategorySchema,
19231
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19232
+ parentKind: string$2().nullable(),
19233
+ /** Derived from `parentKind`, explicit for the client tree. */
19234
+ level: EventKindLevelSchema,
18892
19235
  /** Which cap + device contributes this kind. For built-ins the camera
18893
19236
  * itself; for sensor kinds the LINKED source device. */
18894
19237
  source: object({
@@ -18961,11 +19304,21 @@ var TrackAudioLabelSchema = object({
18961
19304
  firstAt: number(),
18962
19305
  lastAt: number()
18963
19306
  });
19307
+ /**
19308
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19309
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19310
+ * linked sensor/control state change (no positions; carries a snapshot). The
19311
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19312
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19313
+ */
19314
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
18964
19315
  var TrackSchema = object({
18965
19316
  trackId: string$2(),
18966
19317
  deviceId: number(),
18967
19318
  className: string$2(),
18968
19319
  label: string$2().optional(),
19320
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19321
+ source: TrackSourceSchema.optional(),
18969
19322
  firstSeen: number(),
18970
19323
  lastSeen: number(),
18971
19324
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19220,6 +19573,26 @@ var TrackCascadeCountsSchema = object({
19220
19573
  /** Per-track CLIP search vectors removed (best-effort). */
19221
19574
  embeddings: number().int()
19222
19575
  });
19576
+ /** Event-store footprint for one camera. */
19577
+ var EventStoreDeviceFootprintSchema = object({
19578
+ deviceId: number(),
19579
+ /** Persisted event rows (motion + object + audio) for the camera. */
19580
+ rows: number().int(),
19581
+ /** Event-owned media bytes on disk for the camera. */
19582
+ bytes: number().int()
19583
+ });
19584
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19585
+ var EventStoreFootprintSchema = object({
19586
+ totalRows: number().int(),
19587
+ totalBytes: number().int(),
19588
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19589
+ });
19590
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19591
+ var EventPruneCountsSchema = object({
19592
+ motion: number().int(),
19593
+ object: number().int(),
19594
+ audio: number().int()
19595
+ });
19223
19596
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19224
19597
  deviceId: number(),
19225
19598
  trackId: string$2()
@@ -19283,6 +19656,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19283
19656
  }), {
19284
19657
  kind: "mutation",
19285
19658
  auth: "admin"
19659
+ }), method(object({}), EventStoreFootprintSchema, {
19660
+ kind: "query",
19661
+ auth: "admin"
19662
+ }), method(object({
19663
+ olderThanMs: number(),
19664
+ reason: OpsLogReasonSchema.optional()
19665
+ }), EventPruneCountsSchema, {
19666
+ kind: "mutation",
19667
+ auth: "admin"
19668
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19669
+ kind: "mutation",
19670
+ auth: "admin"
19671
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19672
+ kind: "query",
19673
+ auth: "admin"
19286
19674
  }), method(object({
19287
19675
  eventId: string$2(),
19288
19676
  kind: MediaFileKindEnum.optional()
@@ -19307,6 +19695,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19307
19695
  eventId: string$2(),
19308
19696
  timestamp: number()
19309
19697
  });
19698
+ /**
19699
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19700
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19701
+ * caps into per-camera event-kind descriptors.
19702
+ *
19703
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19704
+ * is NOT duplicated here — every entry is derived from the single
19705
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19706
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19707
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19708
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19709
+ * eventful cap is missing.
19710
+ */
19711
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19712
+ var LEGACY_ICON = {
19713
+ motion: "motion",
19714
+ audio: "audio",
19715
+ person: "person",
19716
+ vehicle: "vehicle",
19717
+ animal: "animal",
19718
+ package: "package",
19719
+ door: "door",
19720
+ pir: "pir",
19721
+ smoke: "smoke",
19722
+ water: "water",
19723
+ button: "button",
19724
+ generic: "generic",
19725
+ gas: "smoke",
19726
+ vibration: "generic",
19727
+ tamper: "generic",
19728
+ presence: "person",
19729
+ lock: "generic",
19730
+ siren: "generic",
19731
+ switch: "generic",
19732
+ doorbell: "button"
19733
+ };
19734
+ function legacyIcon(iconId) {
19735
+ return LEGACY_ICON[iconId] ?? "generic";
19736
+ }
19737
+ /**
19738
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19739
+ * The anti-drift guard cross-checks this against the eventful caps declared
19740
+ * in `packages/types/src/capabilities/*.cap.ts`.
19741
+ */
19742
+ var CAP_TO_KIND = {
19743
+ contact: "contact",
19744
+ motion: "motion-sensor",
19745
+ smoke: "smoke",
19746
+ flood: "flood",
19747
+ gas: "gas",
19748
+ "carbon-monoxide": "carbon-monoxide",
19749
+ vibration: "vibration",
19750
+ tamper: "tamper",
19751
+ presence: "presence",
19752
+ "enum-sensor": "enum-sensor",
19753
+ "event-emitter": "device-event",
19754
+ "lock-control": "lock",
19755
+ switch: "switch",
19756
+ button: "button",
19757
+ doorbell: "doorbell"
19758
+ };
19759
+ function buildDescriptor(capName, kind) {
19760
+ const t = EVENT_TAXONOMY[kind];
19761
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19762
+ return {
19763
+ ...t,
19764
+ icon: legacyIcon(t.iconId)
19765
+ };
19766
+ }
19767
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19310
19768
  var CameraPipelineConfigSchema = object({
19311
19769
  engine: PipelineEngineChoiceSchema.optional(),
19312
19770
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22533,13 +22991,29 @@ method(object({
22533
22991
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22534
22992
  kind: "mutation",
22535
22993
  auth: "admin"
22536
- }), method(object({ deviceId: number() }), object({
22994
+ }), method(object({
22995
+ deviceId: number(),
22996
+ reason: OpsLogReasonSchema.optional()
22997
+ }), object({
22537
22998
  floorMs: number().nullable(),
22538
22999
  deletedBuckets: number().int(),
22539
23000
  reclaimedBytes: number().int()
22540
23001
  }), {
22541
23002
  kind: "mutation",
22542
23003
  auth: "admin"
23004
+ }), method(object({
23005
+ deviceId: number(),
23006
+ fromMs: number().optional(),
23007
+ toMs: number().optional()
23008
+ }), object({
23009
+ deletedBuckets: number().int(),
23010
+ reclaimedBytes: number().int()
23011
+ }), {
23012
+ kind: "mutation",
23013
+ auth: "admin"
23014
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23015
+ kind: "query",
23016
+ auth: "admin"
22543
23017
  });
22544
23018
  /**
22545
23019
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25661,6 +26135,12 @@ Object.freeze({
25661
26135
  addonId: null,
25662
26136
  access: "delete"
25663
26137
  },
26138
+ "pipelineAnalytics.deleteDeviceEvents": {
26139
+ capName: "pipeline-analytics",
26140
+ capScope: "device",
26141
+ addonId: null,
26142
+ access: "delete"
26143
+ },
25664
26144
  "pipelineAnalytics.deleteTracks": {
25665
26145
  capName: "pipeline-analytics",
25666
26146
  capScope: "device",
@@ -25691,6 +26171,12 @@ Object.freeze({
25691
26171
  addonId: null,
25692
26172
  access: "view"
25693
26173
  },
26174
+ "pipelineAnalytics.getEventStoreFootprint": {
26175
+ capName: "pipeline-analytics",
26176
+ capScope: "device",
26177
+ addonId: null,
26178
+ access: "view"
26179
+ },
25694
26180
  "pipelineAnalytics.getKeyEvents": {
25695
26181
  capName: "pipeline-analytics",
25696
26182
  capScope: "device",
@@ -25733,6 +26219,12 @@ Object.freeze({
25733
26219
  addonId: null,
25734
26220
  access: "view"
25735
26221
  },
26222
+ "pipelineAnalytics.listOpsLog": {
26223
+ capName: "pipeline-analytics",
26224
+ capScope: "device",
26225
+ addonId: null,
26226
+ access: "view"
26227
+ },
25736
26228
  "pipelineAnalytics.listRecentTracks": {
25737
26229
  capName: "pipeline-analytics",
25738
26230
  capScope: "device",
@@ -25745,6 +26237,12 @@ Object.freeze({
25745
26237
  addonId: null,
25746
26238
  access: "view"
25747
26239
  },
26240
+ "pipelineAnalytics.pruneEvents": {
26241
+ capName: "pipeline-analytics",
26242
+ capScope: "device",
26243
+ addonId: null,
26244
+ access: "create"
26245
+ },
25748
26246
  "pipelineAnalytics.pruneEventsBefore": {
25749
26247
  capName: "pipeline-analytics",
25750
26248
  capScope: "device",
@@ -26507,6 +27005,12 @@ Object.freeze({
26507
27005
  addonId: null,
26508
27006
  access: "create"
26509
27007
  },
27008
+ "recording.deleteFootprint": {
27009
+ capName: "recording",
27010
+ capScope: "system",
27011
+ addonId: null,
27012
+ access: "delete"
27013
+ },
26510
27014
  "recording.getAvailability": {
26511
27015
  capName: "recording",
26512
27016
  capScope: "system",
@@ -26537,6 +27041,12 @@ Object.freeze({
26537
27041
  addonId: null,
26538
27042
  access: "view"
26539
27043
  },
27044
+ "recording.listOpsLog": {
27045
+ capName: "recording",
27046
+ capScope: "system",
27047
+ addonId: null,
27048
+ access: "view"
27049
+ },
26540
27050
  "recording.locateSegment": {
26541
27051
  capName: "recording",
26542
27052
  capScope: "system",
package/dist/addon.mjs CHANGED
@@ -7370,6 +7370,62 @@ var RecordingConfigSchema = object({
7370
7370
  scrubThumbnails: ScrubThumbnailPresetSchema.optional()
7371
7371
  });
7372
7372
  /**
7373
+ * Ops-log — the durable, append-only operations audit shared by the
7374
+ * recordings and events management surfaces.
7375
+ *
7376
+ * ONE row shape is reused for both domains so a single "Activity" view can
7377
+ * merge the recorder's DurableState ring (recordings ops-log) and the
7378
+ * pipeline-analytics SQLite collection (events ops-log). Each row records a
7379
+ * management operation, WHY it ran (reason), and its measurable effect
7380
+ * (itemsAffected + bytesReclaimed). Writes are best-effort — a failed log must
7381
+ * never fail the operation it records.
7382
+ */
7383
+ /** Which management domain the operation belongs to. */
7384
+ var OpsLogDomainSchema = _enum(["recording", "events"]);
7385
+ /** The kind of management operation performed. */
7386
+ var OpsLogOpSchema = _enum([
7387
+ "prune",
7388
+ "manual-delete",
7389
+ "rescan",
7390
+ "retention-run"
7391
+ ]);
7392
+ /** Why the operation ran. */
7393
+ var OpsLogReasonSchema = _enum([
7394
+ "retention",
7395
+ "quota",
7396
+ "manual",
7397
+ "operator"
7398
+ ]);
7399
+ /** One audit row, shared verbatim by both domains. */
7400
+ var OpsLogEntrySchema = object({
7401
+ /** Unique row id. */
7402
+ id: string$2(),
7403
+ /** Epoch ms the operation completed. */
7404
+ at: number(),
7405
+ domain: OpsLogDomainSchema,
7406
+ op: OpsLogOpSchema,
7407
+ reason: OpsLogReasonSchema,
7408
+ /** The camera the op targeted; null for a cluster/global op. */
7409
+ deviceId: number().nullable(),
7410
+ /** Node that performed the op (the log carries nodeId — no cross-node aggregation). */
7411
+ nodeId: string$2(),
7412
+ /** Buckets / rows deleted (op-specific unit). */
7413
+ itemsAffected: number(),
7414
+ /** Bytes reclaimed by the op (0 when not measurable). */
7415
+ bytesReclaimed: number(),
7416
+ /** Free-text detail (e.g. "floor moved to <ts>"); null when none. */
7417
+ detail: string$2().nullable(),
7418
+ /** Who/what triggered the op. */
7419
+ actor: string$2()
7420
+ });
7421
+ /** Shared query input for the per-domain `listOpsLog` cap methods. */
7422
+ var OpsLogQueryInputSchema = object({
7423
+ /** Restrict to a single camera; omit for every row. */
7424
+ deviceId: number().optional(),
7425
+ /** Max rows returned, newest-first. */
7426
+ limit: number().int().min(1).max(1e3).optional()
7427
+ });
7428
+ /**
7373
7429
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
7374
7430
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
7375
7431
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -7613,6 +7669,160 @@ var EncodeProfileSchema = object({
7613
7669
  */
7614
7670
  outputArgs: array(string$2()).optional()
7615
7671
  });
7672
+ var COCO_TO_MACRO = {
7673
+ mapping: {
7674
+ person: "person",
7675
+ bicycle: "vehicle",
7676
+ car: "vehicle",
7677
+ motorcycle: "vehicle",
7678
+ airplane: "vehicle",
7679
+ bus: "vehicle",
7680
+ train: "vehicle",
7681
+ truck: "vehicle",
7682
+ boat: "vehicle",
7683
+ bird: "animal",
7684
+ cat: "animal",
7685
+ dog: "animal",
7686
+ horse: "animal",
7687
+ sheep: "animal",
7688
+ cow: "animal",
7689
+ elephant: "animal",
7690
+ bear: "animal",
7691
+ zebra: "animal",
7692
+ giraffe: "animal",
7693
+ suitcase: "package",
7694
+ backpack: "package",
7695
+ handbag: "package"
7696
+ },
7697
+ preserveOriginal: false
7698
+ };
7699
+ var AUDIO_MACRO_LABELS = [
7700
+ {
7701
+ id: "speech",
7702
+ name: "Speech",
7703
+ icon: "🗣️"
7704
+ },
7705
+ {
7706
+ id: "scream",
7707
+ name: "Scream / Shout",
7708
+ icon: "😱"
7709
+ },
7710
+ {
7711
+ id: "crying",
7712
+ name: "Crying / Baby",
7713
+ icon: "😢"
7714
+ },
7715
+ {
7716
+ id: "laughter",
7717
+ name: "Laughter",
7718
+ icon: "😂"
7719
+ },
7720
+ {
7721
+ id: "music",
7722
+ name: "Music",
7723
+ icon: "🎵"
7724
+ },
7725
+ {
7726
+ id: "dog",
7727
+ name: "Dog",
7728
+ icon: "🐕"
7729
+ },
7730
+ {
7731
+ id: "cat",
7732
+ name: "Cat",
7733
+ icon: "🐈"
7734
+ },
7735
+ {
7736
+ id: "bird",
7737
+ name: "Bird",
7738
+ icon: "🐦"
7739
+ },
7740
+ {
7741
+ id: "animal",
7742
+ name: "Animal (other)",
7743
+ icon: "🐾"
7744
+ },
7745
+ {
7746
+ id: "alarm",
7747
+ name: "Alarm / Siren",
7748
+ icon: "🚨"
7749
+ },
7750
+ {
7751
+ id: "doorbell",
7752
+ name: "Doorbell / Knock",
7753
+ icon: "🔔"
7754
+ },
7755
+ {
7756
+ id: "glass_breaking",
7757
+ name: "Glass Breaking",
7758
+ icon: "💥"
7759
+ },
7760
+ {
7761
+ id: "gunshot",
7762
+ name: "Gunshot / Explosion",
7763
+ icon: "💣"
7764
+ },
7765
+ {
7766
+ id: "vehicle",
7767
+ name: "Vehicle",
7768
+ icon: "🚗"
7769
+ },
7770
+ {
7771
+ id: "siren",
7772
+ name: "Emergency Siren",
7773
+ icon: "🚑"
7774
+ },
7775
+ {
7776
+ id: "fire",
7777
+ name: "Fire / Smoke",
7778
+ icon: "🔥"
7779
+ },
7780
+ {
7781
+ id: "water",
7782
+ name: "Water",
7783
+ icon: "💧"
7784
+ },
7785
+ {
7786
+ id: "wind",
7787
+ name: "Wind / Weather",
7788
+ icon: "🌬️"
7789
+ },
7790
+ {
7791
+ id: "door",
7792
+ name: "Door",
7793
+ icon: "🚪"
7794
+ },
7795
+ {
7796
+ id: "footsteps",
7797
+ name: "Footsteps",
7798
+ icon: "👣"
7799
+ },
7800
+ {
7801
+ id: "crowd",
7802
+ name: "Crowd / Chatter",
7803
+ icon: "👥"
7804
+ },
7805
+ {
7806
+ id: "telephone",
7807
+ name: "Telephone",
7808
+ icon: "📞"
7809
+ },
7810
+ {
7811
+ id: "engine",
7812
+ name: "Engine / Motor",
7813
+ icon: "⚙️"
7814
+ },
7815
+ {
7816
+ id: "tools",
7817
+ name: "Tools / Construction",
7818
+ icon: "🔨"
7819
+ },
7820
+ {
7821
+ id: "silence",
7822
+ name: "Silence",
7823
+ icon: "🤫"
7824
+ }
7825
+ ];
7616
7826
  var YAMNET_TO_MACRO = {
7617
7827
  mapping: {
7618
7828
  Speech: "speech",
@@ -7879,6 +8089,125 @@ var _macroLookup = /* @__PURE__ */ new Map();
7879
8089
  for (const [k, v] of Object.entries(YAMNET_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7880
8090
  for (const [k, v] of Object.entries(APPLE_SA_TO_MACRO.mapping)) _macroLookup.set(k.toLowerCase(), v);
7881
8091
  /**
8092
+ * Unified event-kind taxonomy — THE single source of truth for
8093
+ * `kind → { parentKind, category, level, color, iconId, labelKey, label,
8094
+ * icon }`.
8095
+ *
8096
+ * This dictionary folds together what used to be scattered across four
8097
+ * copies:
8098
+ * - `capabilities/sensor-event-kinds.ts` (sensor cap colors)
8099
+ * - `addon-post-analysis/.../services/event-kinds.ts`
8100
+ * (MOTION/PERSON/VEHICLE… _COLOR constants)
8101
+ * - `ui-library/composites/detection-colors.ts` (CLASS_COLORS)
8102
+ * - `addon-post-analysis/shared/frame/box-drawer.ts` (DEFAULT_COLOR)
8103
+ * - the COCO / audio class maps (macro ↔ sub relationships)
8104
+ *
8105
+ * The DATA (serializable — color/iconId/labelKey/parentKind) lives here in
8106
+ * `@camstack/types`. The UI-side mapping `iconId → lucide component` and
8107
+ * `labelKey → t()` lives in `@camstack/ui-library`. UIs never hardcode a
8108
+ * color or an icon: they read this dictionary (server descriptors carry the
8109
+ * fields inline; the client resolves color/icon/label from `iconId`/`kind`).
8110
+ *
8111
+ * Two levels only (v1 YAGNI): macro → sub. `person` is a leaf macro.
8112
+ */
8113
+ var TAXONOMY_COLORS = {
8114
+ motion: "#f59e0b",
8115
+ audio: "#06b6d4",
8116
+ person: "#22c55e",
8117
+ vehicle: "#3b82f6",
8118
+ animal: "#f97316",
8119
+ package: "#a855f7",
8120
+ sensor: "#8b5cf6",
8121
+ control: "#10b981",
8122
+ genericDetection: "#64748b"
8123
+ };
8124
+ var DETECTION_SUB_COLORS = {
8125
+ car: "#f59e0b",
8126
+ truck: "#d97706",
8127
+ bus: "#b45309",
8128
+ motorcycle: "#eab308",
8129
+ bicycle: "#ca8a04",
8130
+ airplane: "#60a5fa",
8131
+ boat: "#2563eb",
8132
+ train: "#1d4ed8",
8133
+ bird: "#14b8a6",
8134
+ dog: "#84cc16",
8135
+ cat: "#f97316",
8136
+ horse: "#a16207",
8137
+ sheep: "#a3a3a3",
8138
+ cow: "#78716c",
8139
+ elephant: "#6b7280",
8140
+ bear: "#7c2d12",
8141
+ zebra: "#404040",
8142
+ giraffe: "#d4a373"
8143
+ };
8144
+ function titleCase(id) {
8145
+ return id.split(/[-_ ]/).filter((p) => p.length > 0).map((p) => p.charAt(0).toUpperCase() + p.slice(1)).join(" ");
8146
+ }
8147
+ var entries = /* @__PURE__ */ new Map();
8148
+ function macro(kind, category, color, iconId, label) {
8149
+ entries.set(kind, {
8150
+ kind,
8151
+ parentKind: null,
8152
+ level: "macro",
8153
+ category,
8154
+ color,
8155
+ iconId,
8156
+ labelKey: `eventKind.${kind}`,
8157
+ label
8158
+ });
8159
+ }
8160
+ function sub(kind, parentKind, category, color, iconId, label) {
8161
+ entries.set(kind, {
8162
+ kind,
8163
+ parentKind,
8164
+ level: "sub",
8165
+ category,
8166
+ color,
8167
+ iconId,
8168
+ labelKey: `eventKind.${kind}`,
8169
+ label
8170
+ });
8171
+ }
8172
+ macro("motion", "motion", TAXONOMY_COLORS.motion, "motion", "Motion");
8173
+ macro("audio", "audio", TAXONOMY_COLORS.audio, "audio", "Audio");
8174
+ macro("person", "detection", TAXONOMY_COLORS.person, "person", "Person");
8175
+ macro("vehicle", "detection", TAXONOMY_COLORS.vehicle, "vehicle", "Vehicle");
8176
+ macro("animal", "detection", TAXONOMY_COLORS.animal, "animal", "Animal");
8177
+ macro("package", "package", TAXONOMY_COLORS.package, "package", "Package");
8178
+ macro("sensor", "sensor", TAXONOMY_COLORS.sensor, "sensor", "Sensor");
8179
+ macro("control", "control", TAXONOMY_COLORS.control, "control", "Control");
8180
+ for (const [cocoClass, macroClass] of Object.entries(COCO_TO_MACRO.mapping)) {
8181
+ if (macroClass !== "vehicle" && macroClass !== "animal") continue;
8182
+ if (entries.has(cocoClass)) continue;
8183
+ sub(cocoClass, macroClass, "detection", DETECTION_SUB_COLORS[cocoClass] ?? TAXONOMY_COLORS.genericDetection, cocoClass, titleCase(cocoClass));
8184
+ }
8185
+ sub("package-delivered", "package", "package", TAXONOMY_COLORS.package, "package", "Package delivered");
8186
+ sub("package-picked-up", "package", "package", TAXONOMY_COLORS.package, "package", "Package picked up");
8187
+ sub("contact", "sensor", "sensor", "#f59e0b", "door", "Contact");
8188
+ sub("motion-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "pir", "Motion sensor");
8189
+ sub("smoke", "sensor", "sensor", "#ef4444", "smoke", "Smoke");
8190
+ sub("flood", "sensor", "sensor", "#3b82f6", "water", "Water leak");
8191
+ sub("gas", "sensor", "sensor", "#ef4444", "gas", "Gas");
8192
+ sub("carbon-monoxide", "sensor", "sensor", "#dc2626", "smoke", "Carbon monoxide");
8193
+ sub("vibration", "sensor", "sensor", "#eab308", "vibration", "Vibration");
8194
+ sub("tamper", "sensor", "sensor", "#f97316", "tamper", "Tamper");
8195
+ sub("presence", "sensor", "sensor", "#22c55e", "presence", "Presence");
8196
+ sub("enum-sensor", "sensor", "sensor", TAXONOMY_COLORS.sensor, "generic", "Sensor state");
8197
+ sub("device-event", "sensor", "sensor", "#10b981", "button", "Device event");
8198
+ sub("lock", "control", "control", "#0ea5e9", "lock", "Lock");
8199
+ sub("switch", "control", "control", TAXONOMY_COLORS.control, "switch", "Switch");
8200
+ sub("siren", "control", "control", "#dc2626", "siren", "Siren");
8201
+ sub("button", "control", "control", "#10b981", "button", "Button");
8202
+ sub("doorbell", "control", "control", "#a855f7", "doorbell", "Doorbell");
8203
+ for (const l of AUDIO_MACRO_LABELS) {
8204
+ const kind = `audio-${l.id}`;
8205
+ if (entries.has(kind)) continue;
8206
+ sub(kind, "audio", "audio", TAXONOMY_COLORS.audio, kind, l.name);
8207
+ }
8208
+ /** The complete taxonomy dictionary, keyed by kind. */
8209
+ var EVENT_TAXONOMY = Object.freeze(Object.fromEntries(entries));
8210
+ /**
7882
8211
  * Error types for the safe expression engine. Two distinct classes so callers
7883
8212
  * can tell a compile-time (grammar) failure from a runtime (evaluation)
7884
8213
  * failure — both are non-fatal to the host: read paths degrade to "skip link".
@@ -14833,9 +15162,10 @@ var DEVICE_LOCAL_STATE_CAPS = {
14833
15162
  * `package` backs the package-drop detector — a package zone is a
14834
15163
  * `ZoneRule` on the `'package'` stage referencing drawn polygons
14835
15164
  * (see docs/superpowers/specs/2026-07-17-package-zones-design.md §3.1).
14836
- * The orchestrator provider that writes this stage lands in a later
14837
- * slice; until then the mirror carries only `{motion, detection}` and
14838
- * consumers read `package` as absent (treat as `[]`).
15165
+ * The orchestrator provider writes this stage as a first-class slice
15166
+ * (Phase 4): every mutation mirrors the full `{motion, detection,
15167
+ * package}` shape, so consumers read the current package rules directly
15168
+ * off `device.state.zoneRules.value.package`.
14839
15169
  */
14840
15170
  runtimeState: object({
14841
15171
  motion: array(ZoneRuleSchema).readonly(),
@@ -18876,17 +19206,30 @@ var EventKindCategorySchema = _enum([
18876
19206
  "audio",
18877
19207
  "detection",
18878
19208
  "sensor",
19209
+ "control",
18879
19210
  "custom",
18880
19211
  "package"
18881
19212
  ]);
19213
+ /** Taxonomy level — macro (timeline lane) vs sub (events-page leaf). */
19214
+ var EventKindLevelSchema = _enum(["macro", "sub"]);
18882
19215
  var EventKindDescriptorSchema = object({
18883
- /** Stable kind id (e.g. 'motion', 'person', 'contact'). */
19216
+ /** Stable kind id (e.g. 'motion', 'vehicle', 'car', 'lock'). */
18884
19217
  kind: string$2(),
19218
+ /** i18n key resolved on the UI side; `label` is the English fallback. */
19219
+ labelKey: string$2(),
19220
+ /** English fallback label (kept for clients that don't translate). */
18885
19221
  label: string$2(),
18886
19222
  /** Hex color for timeline/legend rendering. */
18887
19223
  color: string$2(),
19224
+ /** Dictionary id → lucide component on the UI side. */
19225
+ iconId: string$2(),
19226
+ /** Legacy closed-vocab glyph — fallback for `iconId`. */
18888
19227
  icon: EventKindIconSchema,
18889
19228
  category: EventKindCategorySchema,
19229
+ /** Macro parent for this kind ('car' → 'vehicle'); null for a macro. */
19230
+ parentKind: string$2().nullable(),
19231
+ /** Derived from `parentKind`, explicit for the client tree. */
19232
+ level: EventKindLevelSchema,
18890
19233
  /** Which cap + device contributes this kind. For built-ins the camera
18891
19234
  * itself; for sensor kinds the LINKED source device. */
18892
19235
  source: object({
@@ -18959,11 +19302,21 @@ var TrackAudioLabelSchema = object({
18959
19302
  firstAt: number(),
18960
19303
  lastAt: number()
18961
19304
  });
19305
+ /**
19306
+ * How a track was produced. `pipeline` (default / absent) = the spatial
19307
+ * detection+tracking pipeline. `sensor` = a SYNTHETIC track projected from a
19308
+ * linked sensor/control state change (no positions; carries a snapshot). The
19309
+ * spatial subsystems (tracker association, occupancy count, re-id/embedding,
19310
+ * resurrection) MUST skip `sensor` tracks — they have no bbox trajectory.
19311
+ */
19312
+ var TrackSourceSchema = _enum(["pipeline", "sensor"]);
18962
19313
  var TrackSchema = object({
18963
19314
  trackId: string$2(),
18964
19315
  deviceId: number(),
18965
19316
  className: string$2(),
18966
19317
  label: string$2().optional(),
19318
+ /** Track provenance. Absent ⇒ `pipeline` (legacy rows). */
19319
+ source: TrackSourceSchema.optional(),
18967
19320
  firstSeen: number(),
18968
19321
  lastSeen: number(),
18969
19322
  /** Frame-rate position history (subject to maxPositionHistory cap). */
@@ -19218,6 +19571,26 @@ var TrackCascadeCountsSchema = object({
19218
19571
  /** Per-track CLIP search vectors removed (best-effort). */
19219
19572
  embeddings: number().int()
19220
19573
  });
19574
+ /** Event-store footprint for one camera. */
19575
+ var EventStoreDeviceFootprintSchema = object({
19576
+ deviceId: number(),
19577
+ /** Persisted event rows (motion + object + audio) for the camera. */
19578
+ rows: number().int(),
19579
+ /** Event-owned media bytes on disk for the camera. */
19580
+ bytes: number().int()
19581
+ });
19582
+ /** Aggregate event-store footprint: global totals + per-camera breakdown. */
19583
+ var EventStoreFootprintSchema = object({
19584
+ totalRows: number().int(),
19585
+ totalBytes: number().int(),
19586
+ devices: array(EventStoreDeviceFootprintSchema).readonly()
19587
+ });
19588
+ /** Per-kind counts returned by the event-prune / device-delete mutations. */
19589
+ var EventPruneCountsSchema = object({
19590
+ motion: number().int(),
19591
+ object: number().int(),
19592
+ audio: number().int()
19593
+ });
19221
19594
  DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).readonly()), method(object({
19222
19595
  deviceId: number(),
19223
19596
  trackId: string$2()
@@ -19281,6 +19654,21 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19281
19654
  }), {
19282
19655
  kind: "mutation",
19283
19656
  auth: "admin"
19657
+ }), method(object({}), EventStoreFootprintSchema, {
19658
+ kind: "query",
19659
+ auth: "admin"
19660
+ }), method(object({
19661
+ olderThanMs: number(),
19662
+ reason: OpsLogReasonSchema.optional()
19663
+ }), EventPruneCountsSchema, {
19664
+ kind: "mutation",
19665
+ auth: "admin"
19666
+ }), method(object({ deviceId: number() }), EventPruneCountsSchema, {
19667
+ kind: "mutation",
19668
+ auth: "admin"
19669
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19670
+ kind: "query",
19671
+ auth: "admin"
19284
19672
  }), method(object({
19285
19673
  eventId: string$2(),
19286
19674
  kind: MediaFileKindEnum.optional()
@@ -19305,6 +19693,76 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19305
19693
  eventId: string$2(),
19306
19694
  timestamp: number()
19307
19695
  });
19696
+ /**
19697
+ * Cap → event-kind mapping for the SENSOR / CONTROL cap families — the table
19698
+ * `pipeline-analytics.listEventKinds` uses to turn a linked device's bound
19699
+ * caps into per-camera event-kind descriptors.
19700
+ *
19701
+ * The descriptor DATA (color / iconId / labelKey / parentKind / category)
19702
+ * is NOT duplicated here — every entry is derived from the single
19703
+ * `EVENT_TAXONOMY` dictionary (`catalogs/event-taxonomy.ts`). This file owns
19704
+ * ONLY the cap-name → taxonomy-kind mapping. Adding a new eventful sensor /
19705
+ * control cap means adding one line here (and a taxonomy entry); the anti-
19706
+ * drift guard `scripts/check-event-kind-coverage.ts` fails the build if an
19707
+ * eventful cap is missing.
19708
+ */
19709
+ /** Map a taxonomy `iconId` onto the legacy closed `EventKindIcon` enum. */
19710
+ var LEGACY_ICON = {
19711
+ motion: "motion",
19712
+ audio: "audio",
19713
+ person: "person",
19714
+ vehicle: "vehicle",
19715
+ animal: "animal",
19716
+ package: "package",
19717
+ door: "door",
19718
+ pir: "pir",
19719
+ smoke: "smoke",
19720
+ water: "water",
19721
+ button: "button",
19722
+ generic: "generic",
19723
+ gas: "smoke",
19724
+ vibration: "generic",
19725
+ tamper: "generic",
19726
+ presence: "person",
19727
+ lock: "generic",
19728
+ siren: "generic",
19729
+ switch: "generic",
19730
+ doorbell: "button"
19731
+ };
19732
+ function legacyIcon(iconId) {
19733
+ return LEGACY_ICON[iconId] ?? "generic";
19734
+ }
19735
+ /**
19736
+ * Cap name → taxonomy kind id. Covers EVERY eventful sensor / control cap.
19737
+ * The anti-drift guard cross-checks this against the eventful caps declared
19738
+ * in `packages/types/src/capabilities/*.cap.ts`.
19739
+ */
19740
+ var CAP_TO_KIND = {
19741
+ contact: "contact",
19742
+ motion: "motion-sensor",
19743
+ smoke: "smoke",
19744
+ flood: "flood",
19745
+ gas: "gas",
19746
+ "carbon-monoxide": "carbon-monoxide",
19747
+ vibration: "vibration",
19748
+ tamper: "tamper",
19749
+ presence: "presence",
19750
+ "enum-sensor": "enum-sensor",
19751
+ "event-emitter": "device-event",
19752
+ "lock-control": "lock",
19753
+ switch: "switch",
19754
+ button: "button",
19755
+ doorbell: "doorbell"
19756
+ };
19757
+ function buildDescriptor(capName, kind) {
19758
+ const t = EVENT_TAXONOMY[kind];
19759
+ if (t === void 0) throw new Error(`EVENT_KIND_BY_CAP: cap '${capName}' maps to unknown taxonomy kind '${kind}'`);
19760
+ return {
19761
+ ...t,
19762
+ icon: legacyIcon(t.iconId)
19763
+ };
19764
+ }
19765
+ Object.freeze(Object.fromEntries(Object.entries(CAP_TO_KIND).map(([capName, kind]) => [capName, buildDescriptor(capName, kind)])));
19308
19766
  var CameraPipelineConfigSchema = object({
19309
19767
  engine: PipelineEngineChoiceSchema.optional(),
19310
19768
  steps: array(PipelineStepInputSchema).readonly(),
@@ -22531,13 +22989,29 @@ method(object({
22531
22989
  }), method(object({ deviceId: number() }), RecordingStatusSchema, {
22532
22990
  kind: "mutation",
22533
22991
  auth: "admin"
22534
- }), method(object({ deviceId: number() }), object({
22992
+ }), method(object({
22993
+ deviceId: number(),
22994
+ reason: OpsLogReasonSchema.optional()
22995
+ }), object({
22535
22996
  floorMs: number().nullable(),
22536
22997
  deletedBuckets: number().int(),
22537
22998
  reclaimedBytes: number().int()
22538
22999
  }), {
22539
23000
  kind: "mutation",
22540
23001
  auth: "admin"
23002
+ }), method(object({
23003
+ deviceId: number(),
23004
+ fromMs: number().optional(),
23005
+ toMs: number().optional()
23006
+ }), object({
23007
+ deletedBuckets: number().int(),
23008
+ reclaimedBytes: number().int()
23009
+ }), {
23010
+ kind: "mutation",
23011
+ auth: "admin"
23012
+ }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
23013
+ kind: "query",
23014
+ auth: "admin"
22541
23015
  });
22542
23016
  /**
22543
23017
  * `recordingExport` cap — render a footage time range into a single downloadable
@@ -25659,6 +26133,12 @@ Object.freeze({
25659
26133
  addonId: null,
25660
26134
  access: "delete"
25661
26135
  },
26136
+ "pipelineAnalytics.deleteDeviceEvents": {
26137
+ capName: "pipeline-analytics",
26138
+ capScope: "device",
26139
+ addonId: null,
26140
+ access: "delete"
26141
+ },
25662
26142
  "pipelineAnalytics.deleteTracks": {
25663
26143
  capName: "pipeline-analytics",
25664
26144
  capScope: "device",
@@ -25689,6 +26169,12 @@ Object.freeze({
25689
26169
  addonId: null,
25690
26170
  access: "view"
25691
26171
  },
26172
+ "pipelineAnalytics.getEventStoreFootprint": {
26173
+ capName: "pipeline-analytics",
26174
+ capScope: "device",
26175
+ addonId: null,
26176
+ access: "view"
26177
+ },
25692
26178
  "pipelineAnalytics.getKeyEvents": {
25693
26179
  capName: "pipeline-analytics",
25694
26180
  capScope: "device",
@@ -25731,6 +26217,12 @@ Object.freeze({
25731
26217
  addonId: null,
25732
26218
  access: "view"
25733
26219
  },
26220
+ "pipelineAnalytics.listOpsLog": {
26221
+ capName: "pipeline-analytics",
26222
+ capScope: "device",
26223
+ addonId: null,
26224
+ access: "view"
26225
+ },
25734
26226
  "pipelineAnalytics.listRecentTracks": {
25735
26227
  capName: "pipeline-analytics",
25736
26228
  capScope: "device",
@@ -25743,6 +26235,12 @@ Object.freeze({
25743
26235
  addonId: null,
25744
26236
  access: "view"
25745
26237
  },
26238
+ "pipelineAnalytics.pruneEvents": {
26239
+ capName: "pipeline-analytics",
26240
+ capScope: "device",
26241
+ addonId: null,
26242
+ access: "create"
26243
+ },
25746
26244
  "pipelineAnalytics.pruneEventsBefore": {
25747
26245
  capName: "pipeline-analytics",
25748
26246
  capScope: "device",
@@ -26505,6 +27003,12 @@ Object.freeze({
26505
27003
  addonId: null,
26506
27004
  access: "create"
26507
27005
  },
27006
+ "recording.deleteFootprint": {
27007
+ capName: "recording",
27008
+ capScope: "system",
27009
+ addonId: null,
27010
+ access: "delete"
27011
+ },
26508
27012
  "recording.getAvailability": {
26509
27013
  capName: "recording",
26510
27014
  capScope: "system",
@@ -26535,6 +27039,12 @@ Object.freeze({
26535
27039
  addonId: null,
26536
27040
  access: "view"
26537
27041
  },
27042
+ "recording.listOpsLog": {
27043
+ capName: "recording",
27044
+ capScope: "system",
27045
+ addonId: null,
27046
+ access: "view"
27047
+ },
26538
27048
  "recording.locateSegment": {
26539
27049
  capName: "recording",
26540
27050
  capScope: "system",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",