@camstack/addon-ai 0.4.104 → 0.4.106

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.
Files changed (3) hide show
  1. package/dist/addon.js +292 -58
  2. package/dist/addon.mjs +292 -58
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -12,7 +12,7 @@ let _camstack_system_addon_utils = require("@camstack/system/addon-utils");
12
12
  let node_fs_promises = require("node:fs/promises");
13
13
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
14
14
  let node_child_process = require("node:child_process");
15
- //#region ../types/dist/event-category-DAXzJeTX.mjs
15
+ //#region ../types/dist/event-category-ZyX6jcse.mjs
16
16
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
17
17
  EventCategory["SystemBoot"] = "system.boot";
18
18
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -573,7 +573,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
573
573
  * a package zone — a newly-appeared stationary object of a package class
574
574
  * that cleared the class / zone / dwell / size gates. Payload:
575
575
  * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
576
- * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
576
+ * entryId, className, zoneIds, keyFrameMediaId?, bbox, timestamp }`.
577
577
  * Telemetry (D8): the durable record is the `package-events` store row;
578
578
  * this bus topic drives notifier rules + live UI. See
579
579
  * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
@@ -5506,7 +5506,7 @@ function number(params) {
5506
5506
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
5507
5507
  }
5508
5508
  //#endregion
5509
- //#region ../types/dist/sleep-BnujYGPe.mjs
5509
+ //#region ../types/dist/sleep-DBKu2-U5.mjs
5510
5510
  /**
5511
5511
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5512
5512
  * window to float samples (D455).
@@ -7516,13 +7516,30 @@ new Set([
7516
7516
  "scene",
7517
7517
  "motion",
7518
7518
  "object",
7519
- "audio"
7519
+ "audio",
7520
+ "mosaic"
7520
7521
  ]);
7521
- new Set([
7522
+ /**
7523
+ * The owner types that are event tables, in the order the event-media resolver
7524
+ * should consider them. Exported so a consumer asking "is this key an event?"
7525
+ * does not re-spell the list and drift from it.
7526
+ */
7527
+ var EVENT_OWNER_TYPES = [
7522
7528
  "motion",
7523
7529
  "object",
7524
7530
  "audio"
7525
- ]);
7531
+ ];
7532
+ /**
7533
+ * The same list as a Zod enum, for the cap inputs that must NAME the table
7534
+ * (D482: `getEventMedia` / `listEventMedia` take an owner, and an owner is
7535
+ * `(table, id)`).
7536
+ *
7537
+ * Built FROM {@link EVENT_OWNER_TYPES} rather than re-spelled: a fourth event
7538
+ * table would otherwise be accepted by the codec and refused at the door, with
7539
+ * nothing failing until a caller asked.
7540
+ */
7541
+ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7542
+ new Set(EVENT_OWNER_TYPES);
7526
7543
  var EncodeProfileSchema = object({
7527
7544
  video: object({
7528
7545
  codec: _enum([
@@ -7561,7 +7578,23 @@ var EncodeProfileSchema = object({
7561
7578
  "zerolatency",
7562
7579
  "film",
7563
7580
  "animation"
7564
- ]).optional()
7581
+ ]).optional(),
7582
+ /**
7583
+ * ONE slice per access unit.
7584
+ *
7585
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7586
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7587
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7588
+ * frame-end per frame: the first pictures render and the video then freezes
7589
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7590
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7591
+ *
7592
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7593
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7594
+ * consumers meaning the same thing spelled differently would stop sharing one
7595
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7596
+ */
7597
+ singleSlicePerFrame: boolean().optional()
7565
7598
  }),
7566
7599
  audio: union([literal("passthrough"), object({
7567
7600
  codec: _enum([
@@ -7611,7 +7644,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7611
7644
  audio: "passthrough"
7612
7645
  };
7613
7646
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7614
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7647
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7615
7648
  /**
7616
7649
  * Deep wiring healthcheck — snapshot of active reachability probes across
7617
7650
  * every declared capability + widget of every installed plugin, on every
@@ -12619,6 +12652,16 @@ method(object({
12619
12652
  filter: QueryFilterSchema.optional(),
12620
12653
  columns: array(string()).readonly().optional()
12621
12654
  }), array(SettingsRecordSchema).readonly()), method(object({
12655
+ namespace: string().optional(),
12656
+ collection: string(),
12657
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
12658
+ * index on the collection. Never empty. */
12659
+ conflict: array(string()).readonly(),
12660
+ record: BulkRecordSchema
12661
+ }), object({
12662
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
12663
+ * row's own id on an update. */
12664
+ id: union([string(), number$1()]) }), { kind: "mutation" }), method(object({
12622
12665
  namespace: string().optional(),
12623
12666
  collection: string(),
12624
12667
  record: object({
@@ -12753,6 +12796,14 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
12753
12796
  id: union([string(), number$1()]) }), {
12754
12797
  kind: "mutation",
12755
12798
  auth: "admin"
12799
+ }), method(object({
12800
+ namespace: string().optional(),
12801
+ collection: string(),
12802
+ conflict: array(string()).readonly(),
12803
+ record: BulkRecordSchema
12804
+ }), object({ id: union([string(), number$1()]) }), {
12805
+ kind: "mutation",
12806
+ auth: "admin"
12756
12807
  }), method(object({
12757
12808
  namespace: string().optional(),
12758
12809
  collection: string(),
@@ -19063,8 +19114,8 @@ var TrackPositionSchema = object({
19063
19114
  var TrackSnapshotSchema = object({
19064
19115
  timestamp: number$1(),
19065
19116
  position: TrackPositionSchema,
19066
- /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
19067
- mediaKey: string()
19117
+ /** The snapshot's media row id (D482); resolve via `listTrackMedia({ trackId })`. */
19118
+ mediaId: number$1().int()
19068
19119
  });
19069
19120
  /**
19070
19121
  * Normalized 0..1 trajectory envelope (min/max over every position bbox,
@@ -19668,14 +19719,14 @@ var ObjectEventSchema = object({
19668
19719
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
19669
19720
  frameWidth: number$1().optional(),
19670
19721
  frameHeight: number$1().optional(),
19671
- /** MediaStore key for the crop attached to this event (if any). */
19672
- mediaKey: string().optional(),
19673
- /** Design B: MediaStore key of the track's native-resolution key frame (the
19722
+ /** Media row id of the crop attached to this event (if any) — D482. */
19723
+ mediaId: number$1().int().optional(),
19724
+ /** Design B: media row id of the track's native-resolution key frame (the
19674
19725
  * best-detection full frame). Resolve via the event-media data-plane
19675
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`) for a detail view that
19726
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`) for a detail view that
19676
19727
  * draws `bbox` over the native frame. Absent on legacy rows / non-decoded
19677
- * sources — consumers fall back to `mediaKey` (the tight crop). */
19678
- keyFrameMediaKey: string().optional(),
19728
+ * sources — consumers fall back to `mediaId` (the tight crop). */
19729
+ keyFrameMediaId: number$1().int().optional(),
19679
19730
  /** Populated by B5 (recording playback URL for this event). */
19680
19731
  mediaUrl: string().optional(),
19681
19732
  /** The parent track's key-event importance [0,1], propagated to every object
@@ -19725,7 +19776,7 @@ var MediaFileKindEnum = _enum([
19725
19776
  * hub-main's heap on the way past — for an `<img>` that would have cached it.
19726
19777
  *
19727
19778
  * `url` points at the `event-media` data plane
19728
- * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
19779
+ * (`/addon/<addonId>/event-media/m/<mediaId>` — D482), which serves the same blob
19729
19780
  * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
19730
19781
  * render a `?variant=thumb`, and streams. The hub gate in front of it requires
19731
19782
  * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
@@ -19733,10 +19784,13 @@ var MediaFileKindEnum = _enum([
19733
19784
  * `data-plane-access.ts` for the rule and the one gap it does not close
19734
19785
  * (per-device scoping).
19735
19786
  *
19736
- * The URL is built from the row's **stored** key, which is not always its
19737
- * published `kind`: a track's face/plate crop is stored as `crop` under
19787
+ * The URL is built from the row's own id, which since D482 says nothing about
19788
+ * the row's owner or kind — and that is the point. The published `kind` is not
19789
+ * the stored one (a track's face/plate crop is stored as `crop` under
19738
19790
  * `('face'|'plate', '<prefix>-<trackId>')` and published as
19739
- * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
19791
+ * `faceCrop`/`plateCrop`), and under the old composite key the URL had to be
19792
+ * taken from `row.key` verbatim or it 404'd exactly those two rows. An id
19793
+ * cannot be mis-reconstructed, because there is nothing in it to reconstruct.
19740
19794
  *
19741
19795
  * ## `base64` is TRANSITIONAL and is going away
19742
19796
  *
@@ -19749,7 +19803,19 @@ var MediaFileKindEnum = _enum([
19749
19803
  * `analytics-query-facade.ts`; nothing else reads it.
19750
19804
  */
19751
19805
  var MediaFileSchema = object({
19752
- key: string(),
19806
+ /**
19807
+ * The media row's OWN id — the rowid SQLite assigned it (D482).
19808
+ *
19809
+ * It used to be the row's composite key, `<ownerType>:<ownerId>:<fileKind>
19810
+ * [:<timestampMs>]`, which embedded a FOREIGN id. D474 made event ids
19811
+ * per-table rowid aliases, so `object:101314` and `motion:101314` are two
19812
+ * different rows that spell the same number, and a track whose
19813
+ * `bestEventId` was an object event was served a motion event's snapshot —
19814
+ * a night-time thumbnail on an afternoon track, with no error anywhere. A
19815
+ * surrogate id cannot name the wrong row's owner, because it names no owner
19816
+ * at all.
19817
+ */
19818
+ mediaId: number$1().int(),
19753
19819
  kind: MediaFileKindEnum,
19754
19820
  sizeBytes: number$1(),
19755
19821
  timestamp: number$1()
@@ -19857,7 +19923,7 @@ var RetrainTrackSchema = object({
19857
19923
  });
19858
19924
  /** A frame the picker may offer — an index row, no blob was read to produce it. */
19859
19925
  var RetrainFrameCandidateSchema = object({
19860
- mediaKey: string(),
19926
+ mediaId: number$1().int(),
19861
19927
  kind: MediaFileKindEnum,
19862
19928
  timestamp: number$1(),
19863
19929
  sizeBytes: number$1().int(),
@@ -19871,7 +19937,7 @@ var RetrainFrameSchema = object({
19871
19937
  deviceId: number$1(),
19872
19938
  trackId: string(),
19873
19939
  /** Provenance only. It may already point at nothing — that is expected. */
19874
- sourceMediaKey: string(),
19940
+ sourceMediaId: number$1().int(),
19875
19941
  sourceKind: MediaFileKindEnum,
19876
19942
  sizeBytes: number$1().int(),
19877
19943
  width: number$1().int(),
@@ -19887,7 +19953,7 @@ var RetrainCopyRefusalSchema = _enum([
19887
19953
  var RetrainFrameSelectionSchema = object({
19888
19954
  copied: array(RetrainFrameSchema).readonly(),
19889
19955
  refused: array(object({
19890
- sourceMediaKey: string(),
19956
+ sourceMediaId: number$1().int(),
19891
19957
  reason: RetrainCopyRefusalSchema
19892
19958
  })).readonly()
19893
19959
  });
@@ -19895,7 +19961,7 @@ var RetrainFrameListSchema = object({
19895
19961
  candidates: array(RetrainFrameCandidateSchema).readonly(),
19896
19962
  copies: array(RetrainFrameSchema).readonly(),
19897
19963
  /** What the page pre-selects — the native key frame when one survives. */
19898
- autoPickMediaKey: string().optional()
19964
+ autoPickMediaId: number$1().int().optional()
19899
19965
  });
19900
19966
  /** What the operator asked the assist to look for. */
19901
19967
  var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
@@ -19990,9 +20056,11 @@ var RecentTracksQueryInput = object({
19990
20056
  });
19991
20057
  /**
19992
20058
  * A Summary (D359): the per-camera session envelope that references tracks.
19993
- * `groupShotKey` is the media key of its group shot (`ownerKind: 'summary'`,
19994
- * served by the event-media plane by key), null until the first frame with a
19995
- * member visible has been materialised.
20059
+ * `groupShotMediaId` is the media ROW id of its group shot (`ownerKind:
20060
+ * 'summary'`, served by the event-media plane at `/m/<id>`), null until the
20061
+ * first frame with a member visible has been materialised (D482 — it was the
20062
+ * row's composite key, which embedded the owner and could name another table's
20063
+ * row once event ids became per-table rowids).
19996
20064
  */
19997
20065
  var SummarySchema = object({
19998
20066
  id: string(),
@@ -20001,19 +20069,115 @@ var SummarySchema = object({
20001
20069
  lastActiveAt: number$1(),
20002
20070
  sealedAt: number$1().nullable(),
20003
20071
  memberCount: number$1().int().nonnegative(),
20004
- groupShotKey: string().nullable()
20072
+ groupShotMediaId: number$1().int().nullable()
20005
20073
  });
20006
20074
  var SummariesQueryInput = object({
20007
20075
  deviceIds: array(number$1()).min(1),
20008
20076
  since: number$1().optional(),
20009
20077
  until: number$1().optional(),
20010
- limit: number$1().int().min(1).max(200).default(50)
20078
+ limit: number$1().int().min(1).max(200).default(50),
20079
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20080
+ * meaning of "dog" on every summary surface. */
20081
+ classes: array(string().min(1).max(60)).max(40).optional()
20011
20082
  });
20012
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20013
20083
  var SummaryDetailSchema = object({
20014
20084
  summary: SummarySchema,
20015
- /** Member track ids, in absorption order. */
20016
- trackIds: array(string()).readonly()
20085
+ /** Members in absorption order, oldest join first. */
20086
+ members: array(object({
20087
+ trackId: string(),
20088
+ /**
20089
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20090
+ * and the track no longer exists to fill it.
20091
+ *
20092
+ * The join, not the latest opinion: class voting can re-decide a track's
20093
+ * class afterwards, and a group's composition is the history of who came
20094
+ * together. A consumer counts these to badge a thumbnail — "two people and a
20095
+ * dog" — rather than being handed a second, denormalised tally that would
20096
+ * drift from the members it claims to describe.
20097
+ */
20098
+ className: string().nullable(),
20099
+ /** `primary` is the one member that carries the group's identity. */
20100
+ role: _enum([
20101
+ "primary",
20102
+ "sibling",
20103
+ "child"
20104
+ ]).nullable(),
20105
+ joinedAt: number$1(),
20106
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
20107
+ * at read time. Absent when nothing resolved, never an empty string. */
20108
+ label: string().optional(),
20109
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
20110
+ * recognised nobody, and absent for a member whose track has been pruned —
20111
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
20112
+ subLabel: string().optional()
20113
+ })).readonly()
20114
+ });
20115
+ /**
20116
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
20117
+ *
20118
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
20119
+ * we extend when we notice we need more". Enough to draw the card ("two people
20120
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
20121
+ * are on {@link getSummary}: shipping every member of every group so a client
20122
+ * can count them turns a page of two hundred groups into a thousand records to
20123
+ * draw two hundred badges.
20124
+ */
20125
+ var SummaryListItemSchema = object({
20126
+ summary: SummarySchema,
20127
+ /** Class counts, most numerous first; `className: null` is a member whose
20128
+ * class never resolved, counted so the counts add up to `memberCount`. */
20129
+ classes: array(object({
20130
+ className: string().nullable(),
20131
+ count: number$1().int().positive()
20132
+ })).readonly()
20133
+ });
20134
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
20135
+ /**
20136
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
20137
+ *
20138
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
20139
+ * summaries feed that could only answer "the last N for these cameras"
20140
+ * (`listSummaries`) would make groups available in the timeline and nowhere
20141
+ * else. Two feeds with different ordering contracts would be two ideas of what
20142
+ * "newest" means, on one screen.
20143
+ */
20144
+ var RecentSummariesQueryInput = object({
20145
+ /** Devices to merge. An empty array yields an empty page. */
20146
+ deviceIds: array(number$1()),
20147
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20148
+ since: number$1().optional(),
20149
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20150
+ until: number$1().optional(),
20151
+ limit: number$1().int().min(1).max(500).default(100),
20152
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20153
+ cursor: string().optional(),
20154
+ /**
20155
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20156
+ * deliberately the same name: the events filter tree emits one list of ticked
20157
+ * taxonomy nodes, and a summaries feed that called it something else would
20158
+ * make the operator's one filter component produce two shapes.
20159
+ *
20160
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20161
+ * me the arrivals with a dog" has to return the group of four people who had
20162
+ * one with them; a filter on the group's own fields could only ever answer
20163
+ * about its primary.
20164
+ *
20165
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20166
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20167
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20168
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20169
+ * that read only the class would answer "animal" and never "dog".
20170
+ *
20171
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20172
+ */
20173
+ classes: array(string().min(1).max(60)).max(40).optional()
20174
+ });
20175
+ var RecentSummariesPageSchema = object({
20176
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20177
+ * {@link listSummaries} — one list shape, so a card renders the same
20178
+ * whichever feed drew it. */
20179
+ summaries: array(SummaryListItemSchema).readonly(),
20180
+ nextCursor: string().nullable()
20017
20181
  });
20018
20182
  var RecentTracksPageSchema = object({
20019
20183
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20427,7 +20591,7 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
20427
20591
  classes: array(string()).optional(),
20428
20592
  /** See {@link ExcludeSourcesDoc}. */
20429
20593
  excludeSources: array(TrackSourceSchema).optional()
20430
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number$1() }), _void(), {
20594
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number$1() }), _void(), {
20431
20595
  kind: "mutation",
20432
20596
  auth: "admin"
20433
20597
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number$1() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number$1()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -20555,7 +20719,7 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
20555
20719
  }), method(object({
20556
20720
  deviceId: number$1(),
20557
20721
  trackId: string(),
20558
- mediaKeys: array(string()).min(1)
20722
+ mediaIds: array(number$1().int()).min(1)
20559
20723
  }), RetrainFrameSelectionSchema, {
20560
20724
  kind: "mutation",
20561
20725
  auth: "admin"
@@ -20623,7 +20787,10 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
20623
20787
  kind: "query",
20624
20788
  auth: "admin"
20625
20789
  }), method(object({
20626
- eventId: string(),
20790
+ /** Which of the three event tables owns the row. */
20791
+ ownerType: EventOwnerTypeSchema,
20792
+ /** The event's rowid in that table (D474: an event id is a number). */
20793
+ eventId: number$1().int(),
20627
20794
  kind: MediaFileKindEnum.optional(),
20628
20795
  deviceId: number$1()
20629
20796
  }), array(MediaFileSchema).readonly()), method(object({
@@ -20634,7 +20801,8 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
20634
20801
  trackId: string(),
20635
20802
  deviceId: number$1()
20636
20803
  }), array(MediaFileInfoSchema).readonly()), method(object({
20637
- eventId: string(),
20804
+ ownerType: EventOwnerTypeSchema,
20805
+ eventId: number$1().int(),
20638
20806
  deviceId: number$1()
20639
20807
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
20640
20808
  kind: "mutation",
@@ -25097,8 +25265,8 @@ var IdentitySchema = object({
25097
25265
  id: string(),
25098
25266
  name: string(),
25099
25267
  sampleCount: number$1().int().nonnegative(),
25100
- coverMediaKey: string().optional(),
25101
- /** Inline base64 of the cover sample's crop, resolved from `coverMediaKey`.
25268
+ coverMediaId: number$1().int().optional(),
25269
+ /** Inline base64 of the cover sample's crop, resolved from `coverMediaId`.
25102
25270
  * Lets the UI render the person's face avatar instead of a placeholder icon. */
25103
25271
  coverBase64: string().optional()
25104
25272
  });
@@ -25124,8 +25292,8 @@ var IdentitySampleAuditRowSchema = object({
25124
25292
  enrolledAt: number$1().int(),
25125
25293
  deviceId: number$1().int().optional(),
25126
25294
  source: _enum(["detected", "photo"]),
25127
- /** The enrolled crop's media key, so the panel can show the evidence. */
25128
- mediaKey: string().optional(),
25295
+ /** The enrolled crop's media row id, so the panel can show the evidence. */
25296
+ mediaId: number$1().int().optional(),
25129
25297
  /**
25130
25298
  * False = the sample is stamped with a DIFFERENT face model, and a cosine
25131
25299
  * across feature spaces is a well-formed float with no meaning. Every number
@@ -25194,15 +25362,15 @@ var FaceInfoSchema = object({
25194
25362
  */
25195
25363
  cropUrl: string().optional(),
25196
25364
  /** Design B: the face bbox (pixel space) on the key frame — lets a detail
25197
- * view draw the box over the native `keyFrameMediaKey` frame. Absent on
25365
+ * view draw the box over the native `keyFrameMediaId` frame. Absent on
25198
25366
  * legacy rows written before design B. */
25199
25367
  faceBbox: BoundingBoxSchema.optional(),
25200
25368
  /** Design B: MediaStore key of the track's native-resolution key frame.
25201
25369
  * Fetch the native JPEG via the event-media data-plane
25202
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`). Absent when the
25370
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`). Absent when the
25203
25371
  * track produced no key frame (e.g. native/onboard source) — the UI falls
25204
25372
  * back to the inline `base64` face crop. */
25205
- keyFrameMediaKey: string().optional(),
25373
+ keyFrameMediaId: number$1().int().optional(),
25206
25374
  /** Winning identity-match cosine (0..1) for this face's track, when an
25207
25375
  * identity was auto-confirmed. Lets the UI surface WHY a face was assigned
25208
25376
  * (confidence badge / low-confidence audit). Absent on legacy rows and on
@@ -25271,11 +25439,14 @@ var FaceClusterSchema = object({
25271
25439
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25272
25440
  * dialog already rendering its key FRAME from the `event-media` plane.
25273
25441
  *
25274
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25275
- * media key directly, so this needed no new plane and no new access decision.
25442
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
25443
+ * resolves a media row by its own id, so this needed no new plane and no new
25444
+ * access decision.
25276
25445
  */
25277
25446
  var MediaFileLiteSchema$1 = object({
25278
- key: string(),
25447
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
25448
+ * (D482). It was the row's composite key, which embedded the owner. */
25449
+ mediaId: number$1().int(),
25279
25450
  kind: string(),
25280
25451
  url: string(),
25281
25452
  sizeBytes: number$1(),
@@ -25292,7 +25463,7 @@ method(object({
25292
25463
  * covers inline, ~10 KB of base64 per row, on a query this UI mounts
25293
25464
  * four times and the viewer holds at `staleTime: 30_000`.
25294
25465
  *
25295
- * Nothing loses its avatar: `coverMediaKey` is already on every row and
25466
+ * Nothing loses its avatar: `coverMediaId` is already on every row and
25296
25467
  * the `event-media` plane serves that key `immutable` with an ETag.
25297
25468
  *
25298
25469
  * **This is an INPUT field, so it does not reach the addon until the
@@ -27760,8 +27931,8 @@ var VehicleSchema = object({
27760
27931
  id: string(),
27761
27932
  name: string(),
27762
27933
  sampleCount: number$1().int().nonnegative(),
27763
- coverMediaKey: string().optional(),
27764
- /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
27934
+ coverMediaId: number$1().int().optional(),
27935
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaId`. */
27765
27936
  coverBase64: string().optional()
27766
27937
  });
27767
27938
  var VehicleSampleInfoSchema = object({
@@ -27794,7 +27965,7 @@ var PlateInfoSchema = object({
27794
27965
  /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
27795
27966
  plateBbox: BoundingBoxSchema.optional(),
27796
27967
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
27797
- keyFrameMediaKey: string().optional(),
27968
+ keyFrameMediaId: number$1().int().optional(),
27798
27969
  base64: string().optional(),
27799
27970
  /**
27800
27971
  * Same crop as a data-plane URL, always present when the plate has a stored
@@ -27815,11 +27986,14 @@ var PlateInfoSchema = object({
27815
27986
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
27816
27987
  * dialog already rendering its key FRAME from the `event-media` plane.
27817
27988
  *
27818
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
27819
- * media key directly, so this needed no new plane and no new access decision.
27989
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
27990
+ * resolves a media row by its own id, so this needed no new plane and no new
27991
+ * access decision.
27820
27992
  */
27821
27993
  var MediaFileLiteSchema = object({
27822
- key: string(),
27994
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
27995
+ * (D482). It was the row's composite key, which embedded the owner. */
27996
+ mediaId: number$1().int(),
27823
27997
  kind: string(),
27824
27998
  url: string(),
27825
27999
  sizeBytes: number$1(),
@@ -27878,7 +28052,7 @@ method(object({
27878
28052
  }), method(object({
27879
28053
  /** Inline {@link VehicleSchema.coverBase64} on every row. Default
27880
28054
  * `false` — the vehicle twin of `faceGallery.listIdentities`'s
27881
- * option; `coverMediaKey` + the `event-media` plane carry the picture.
28055
+ * option; `coverMediaId` + the `event-media` plane carry the picture.
27882
28056
  * INPUT field: stripped by the hub router until the train ships, which
27883
28057
  * resolves to `false` and is exactly the intended default. */
27884
28058
  includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
@@ -29202,7 +29376,14 @@ var SceneReferenceSchema = object({
29202
29376
  modelId: string(),
29203
29377
  condition: SceneConditionSchema,
29204
29378
  capturedAt: number$1(),
29205
- thumbnailMediaId: string().optional(),
29379
+ /**
29380
+ * The reference thumbnail's media ROW id (D482).
29381
+ *
29382
+ * The name already said `mediaId` while the value was the row's composite
29383
+ * key — a leftover that read as done and was not. It is the row's own id
29384
+ * now, addressed at `/addon/<addonId>/event-media/m/<id>`.
29385
+ */
29386
+ thumbnailMediaId: number$1().int().optional(),
29206
29387
  /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
29207
29388
  * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
29208
29389
  * normalized rect frame a different piece of world, and the scene would
@@ -30016,7 +30197,29 @@ var LoggingSettingsPatchSchema = object({
30016
30197
  * disarmed the channels it did not mention would make the Levels page and
30017
30198
  * the Diagnostics page fight over the same value.
30018
30199
  */
30019
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
30200
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
30201
+ /**
30202
+ * How many days the analytics OPS LOG is kept — the audit trail of what
30203
+ * retention actually deleted.
30204
+ *
30205
+ * It lives HERE, on the logging document, and not on the analytics addon's
30206
+ * settings form, on the operator's instruction: every other log-ish window in
30207
+ * this system is configured through this one document, and a retention audit
30208
+ * configured somewhere else is a second place to look when the question is
30209
+ * "what happened to my data".
30210
+ *
30211
+ * That placement has a cost worth stating: this document ships in the
30212
+ * framework closure, so this knob needs a published `@camstack/server` while
30213
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
30214
+ *
30215
+ * `retention-model.ts` argued this one must NOT follow a device window,
30216
+ * because the audit trail is the history of the SWEEP and not of the camera.
30217
+ * That argument stands and is untouched — what changes is that 30 days stops
30218
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
30219
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
30220
+ * state in which the next 9.5 GiB leak cannot be explained.
30221
+ */
30222
+ opsLogRetentionDays: number$1().int().min(0).max(3650).optional()
30020
30223
  });
30021
30224
  /**
30022
30225
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -30089,6 +30292,14 @@ var LoggingSettingsStateSchema = object({
30089
30292
  channels: array(LogChannelDescriptorSchema).readonly(),
30090
30293
  /** The channels ARMED right now, each with its deadline. */
30091
30294
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
30295
+ /**
30296
+ * The ops-log window in days, as it is in force. `0` = kept forever.
30297
+ *
30298
+ * Reported rather than left to the caller's memory of what it wrote: this is
30299
+ * the value the analytics sweep will actually use, and an operator asking how
30300
+ * long their audit trail lives should not have to infer it from a default.
30301
+ */
30302
+ opsLogRetentionDays: number$1().int().min(0),
30092
30303
  persisted: boolean()
30093
30304
  });
30094
30305
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
@@ -30650,7 +30861,7 @@ var StationaryObjectSchema = object({
30650
30861
  /** Enrichment label carried from the source track (identity / plate). */
30651
30862
  label: string().optional(),
30652
30863
  /** Native-resolution key-frame media key for the parked object's best image. */
30653
- keyFrameMediaKey: string().optional()
30864
+ keyFrameMediaId: number$1().int().optional()
30654
30865
  });
30655
30866
  var CameraOccupancySnapshotSchema = object({
30656
30867
  /** Frame timestamp of the inference result that produced this snapshot. */
@@ -31806,6 +32017,12 @@ Object.freeze({
31806
32017
  addonId: null,
31807
32018
  access: "create"
31808
32019
  },
32020
+ "dataStoreProvider.upsert": {
32021
+ capName: "data-store-provider",
32022
+ capScope: "system",
32023
+ addonId: null,
32024
+ access: "create"
32025
+ },
31809
32026
  "dayNight.getOptions": {
31810
32027
  capName: "day-night",
31811
32028
  capScope: "device",
@@ -34242,6 +34459,12 @@ Object.freeze({
34242
34459
  addonId: null,
34243
34460
  access: "view"
34244
34461
  },
34462
+ "pipelineAnalytics.listRecentSummaries": {
34463
+ capName: "pipeline-analytics",
34464
+ capScope: "device",
34465
+ addonId: null,
34466
+ access: "view"
34467
+ },
34245
34468
  "pipelineAnalytics.listRecentTracks": {
34246
34469
  capName: "pipeline-analytics",
34247
34470
  capScope: "device",
@@ -35670,6 +35893,12 @@ Object.freeze({
35670
35893
  addonId: null,
35671
35894
  access: "create"
35672
35895
  },
35896
+ "settingsStore.upsert": {
35897
+ capName: "settings-store",
35898
+ capScope: "system",
35899
+ addonId: null,
35900
+ access: "create"
35901
+ },
35673
35902
  "smtpProvider.getStatus": {
35674
35903
  capName: "smtp-provider",
35675
35904
  capScope: "system",
@@ -38063,6 +38292,11 @@ Object.freeze({
38063
38292
  form: "single",
38064
38293
  optional: true
38065
38294
  }],
38295
+ "pipelineAnalytics.listRecentSummaries": [{
38296
+ name: "deviceIds",
38297
+ form: "array",
38298
+ optional: false
38299
+ }],
38066
38300
  "pipelineAnalytics.listRecentTracks": [{
38067
38301
  name: "deviceIds",
38068
38302
  form: "array",
package/dist/addon.mjs CHANGED
@@ -39,7 +39,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
39
39
  }) : target, mod));
40
40
  var __require = /* @__PURE__ */ createRequire(import.meta.url);
41
41
  //#endregion
42
- //#region ../types/dist/event-category-DAXzJeTX.mjs
42
+ //#region ../types/dist/event-category-ZyX6jcse.mjs
43
43
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
44
44
  EventCategory["SystemBoot"] = "system.boot";
45
45
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -600,7 +600,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
600
600
  * a package zone — a newly-appeared stationary object of a package class
601
601
  * that cleared the class / zone / dwell / size gates. Payload:
602
602
  * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
603
- * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
603
+ * entryId, className, zoneIds, keyFrameMediaId?, bbox, timestamp }`.
604
604
  * Telemetry (D8): the durable record is the `package-events` store row;
605
605
  * this bus topic drives notifier rules + live UI. See
606
606
  * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
@@ -5533,7 +5533,7 @@ function number(params) {
5533
5533
  return /* @__PURE__ */ _coercedNumber(ZodNumber, params);
5534
5534
  }
5535
5535
  //#endregion
5536
- //#region ../types/dist/sleep-BnujYGPe.mjs
5536
+ //#region ../types/dist/sleep-DBKu2-U5.mjs
5537
5537
  /**
5538
5538
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5539
5539
  * window to float samples (D455).
@@ -7543,13 +7543,30 @@ new Set([
7543
7543
  "scene",
7544
7544
  "motion",
7545
7545
  "object",
7546
- "audio"
7546
+ "audio",
7547
+ "mosaic"
7547
7548
  ]);
7548
- new Set([
7549
+ /**
7550
+ * The owner types that are event tables, in the order the event-media resolver
7551
+ * should consider them. Exported so a consumer asking "is this key an event?"
7552
+ * does not re-spell the list and drift from it.
7553
+ */
7554
+ var EVENT_OWNER_TYPES = [
7549
7555
  "motion",
7550
7556
  "object",
7551
7557
  "audio"
7552
- ]);
7558
+ ];
7559
+ /**
7560
+ * The same list as a Zod enum, for the cap inputs that must NAME the table
7561
+ * (D482: `getEventMedia` / `listEventMedia` take an owner, and an owner is
7562
+ * `(table, id)`).
7563
+ *
7564
+ * Built FROM {@link EVENT_OWNER_TYPES} rather than re-spelled: a fourth event
7565
+ * table would otherwise be accepted by the codec and refused at the door, with
7566
+ * nothing failing until a caller asked.
7567
+ */
7568
+ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
7569
+ new Set(EVENT_OWNER_TYPES);
7553
7570
  var EncodeProfileSchema = object({
7554
7571
  video: object({
7555
7572
  codec: _enum([
@@ -7588,7 +7605,23 @@ var EncodeProfileSchema = object({
7588
7605
  "zerolatency",
7589
7606
  "film",
7590
7607
  "animation"
7591
- ]).optional()
7608
+ ]).optional(),
7609
+ /**
7610
+ * ONE slice per access unit.
7611
+ *
7612
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7613
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7614
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7615
+ * frame-end per frame: the first pictures render and the video then freezes
7616
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7617
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7618
+ *
7619
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7620
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7621
+ * consumers meaning the same thing spelled differently would stop sharing one
7622
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7623
+ */
7624
+ singleSlicePerFrame: boolean().optional()
7592
7625
  }),
7593
7626
  audio: union([literal("passthrough"), object({
7594
7627
  codec: _enum([
@@ -7638,7 +7671,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7638
7671
  audio: "passthrough"
7639
7672
  };
7640
7673
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7641
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7674
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7642
7675
  /**
7643
7676
  * Deep wiring healthcheck — snapshot of active reachability probes across
7644
7677
  * every declared capability + widget of every installed plugin, on every
@@ -12646,6 +12679,16 @@ method(object({
12646
12679
  filter: QueryFilterSchema.optional(),
12647
12680
  columns: array(string()).readonly().optional()
12648
12681
  }), array(SettingsRecordSchema).readonly()), method(object({
12682
+ namespace: string().optional(),
12683
+ collection: string(),
12684
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
12685
+ * index on the collection. Never empty. */
12686
+ conflict: array(string()).readonly(),
12687
+ record: BulkRecordSchema
12688
+ }), object({
12689
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
12690
+ * row's own id on an update. */
12691
+ id: union([string(), number$1()]) }), { kind: "mutation" }), method(object({
12649
12692
  namespace: string().optional(),
12650
12693
  collection: string(),
12651
12694
  record: object({
@@ -12780,6 +12823,14 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
12780
12823
  id: union([string(), number$1()]) }), {
12781
12824
  kind: "mutation",
12782
12825
  auth: "admin"
12826
+ }), method(object({
12827
+ namespace: string().optional(),
12828
+ collection: string(),
12829
+ conflict: array(string()).readonly(),
12830
+ record: BulkRecordSchema
12831
+ }), object({ id: union([string(), number$1()]) }), {
12832
+ kind: "mutation",
12833
+ auth: "admin"
12783
12834
  }), method(object({
12784
12835
  namespace: string().optional(),
12785
12836
  collection: string(),
@@ -19090,8 +19141,8 @@ var TrackPositionSchema = object({
19090
19141
  var TrackSnapshotSchema = object({
19091
19142
  timestamp: number$1(),
19092
19143
  position: TrackPositionSchema,
19093
- /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
19094
- mediaKey: string()
19144
+ /** The snapshot's media row id (D482); resolve via `listTrackMedia({ trackId })`. */
19145
+ mediaId: number$1().int()
19095
19146
  });
19096
19147
  /**
19097
19148
  * Normalized 0..1 trajectory envelope (min/max over every position bbox,
@@ -19695,14 +19746,14 @@ var ObjectEventSchema = object({
19695
19746
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
19696
19747
  frameWidth: number$1().optional(),
19697
19748
  frameHeight: number$1().optional(),
19698
- /** MediaStore key for the crop attached to this event (if any). */
19699
- mediaKey: string().optional(),
19700
- /** Design B: MediaStore key of the track's native-resolution key frame (the
19749
+ /** Media row id of the crop attached to this event (if any) — D482. */
19750
+ mediaId: number$1().int().optional(),
19751
+ /** Design B: media row id of the track's native-resolution key frame (the
19701
19752
  * best-detection full frame). Resolve via the event-media data-plane
19702
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`) for a detail view that
19753
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`) for a detail view that
19703
19754
  * draws `bbox` over the native frame. Absent on legacy rows / non-decoded
19704
- * sources — consumers fall back to `mediaKey` (the tight crop). */
19705
- keyFrameMediaKey: string().optional(),
19755
+ * sources — consumers fall back to `mediaId` (the tight crop). */
19756
+ keyFrameMediaId: number$1().int().optional(),
19706
19757
  /** Populated by B5 (recording playback URL for this event). */
19707
19758
  mediaUrl: string().optional(),
19708
19759
  /** The parent track's key-event importance [0,1], propagated to every object
@@ -19752,7 +19803,7 @@ var MediaFileKindEnum = _enum([
19752
19803
  * hub-main's heap on the way past — for an `<img>` that would have cached it.
19753
19804
  *
19754
19805
  * `url` points at the `event-media` data plane
19755
- * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
19806
+ * (`/addon/<addonId>/event-media/m/<mediaId>` — D482), which serves the same blob
19756
19807
  * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
19757
19808
  * render a `?variant=thumb`, and streams. The hub gate in front of it requires
19758
19809
  * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
@@ -19760,10 +19811,13 @@ var MediaFileKindEnum = _enum([
19760
19811
  * `data-plane-access.ts` for the rule and the one gap it does not close
19761
19812
  * (per-device scoping).
19762
19813
  *
19763
- * The URL is built from the row's **stored** key, which is not always its
19764
- * published `kind`: a track's face/plate crop is stored as `crop` under
19814
+ * The URL is built from the row's own id, which since D482 says nothing about
19815
+ * the row's owner or kind — and that is the point. The published `kind` is not
19816
+ * the stored one (a track's face/plate crop is stored as `crop` under
19765
19817
  * `('face'|'plate', '<prefix>-<trackId>')` and published as
19766
- * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
19818
+ * `faceCrop`/`plateCrop`), and under the old composite key the URL had to be
19819
+ * taken from `row.key` verbatim or it 404'd exactly those two rows. An id
19820
+ * cannot be mis-reconstructed, because there is nothing in it to reconstruct.
19767
19821
  *
19768
19822
  * ## `base64` is TRANSITIONAL and is going away
19769
19823
  *
@@ -19776,7 +19830,19 @@ var MediaFileKindEnum = _enum([
19776
19830
  * `analytics-query-facade.ts`; nothing else reads it.
19777
19831
  */
19778
19832
  var MediaFileSchema = object({
19779
- key: string(),
19833
+ /**
19834
+ * The media row's OWN id — the rowid SQLite assigned it (D482).
19835
+ *
19836
+ * It used to be the row's composite key, `<ownerType>:<ownerId>:<fileKind>
19837
+ * [:<timestampMs>]`, which embedded a FOREIGN id. D474 made event ids
19838
+ * per-table rowid aliases, so `object:101314` and `motion:101314` are two
19839
+ * different rows that spell the same number, and a track whose
19840
+ * `bestEventId` was an object event was served a motion event's snapshot —
19841
+ * a night-time thumbnail on an afternoon track, with no error anywhere. A
19842
+ * surrogate id cannot name the wrong row's owner, because it names no owner
19843
+ * at all.
19844
+ */
19845
+ mediaId: number$1().int(),
19780
19846
  kind: MediaFileKindEnum,
19781
19847
  sizeBytes: number$1(),
19782
19848
  timestamp: number$1()
@@ -19884,7 +19950,7 @@ var RetrainTrackSchema = object({
19884
19950
  });
19885
19951
  /** A frame the picker may offer — an index row, no blob was read to produce it. */
19886
19952
  var RetrainFrameCandidateSchema = object({
19887
- mediaKey: string(),
19953
+ mediaId: number$1().int(),
19888
19954
  kind: MediaFileKindEnum,
19889
19955
  timestamp: number$1(),
19890
19956
  sizeBytes: number$1().int(),
@@ -19898,7 +19964,7 @@ var RetrainFrameSchema = object({
19898
19964
  deviceId: number$1(),
19899
19965
  trackId: string(),
19900
19966
  /** Provenance only. It may already point at nothing — that is expected. */
19901
- sourceMediaKey: string(),
19967
+ sourceMediaId: number$1().int(),
19902
19968
  sourceKind: MediaFileKindEnum,
19903
19969
  sizeBytes: number$1().int(),
19904
19970
  width: number$1().int(),
@@ -19914,7 +19980,7 @@ var RetrainCopyRefusalSchema = _enum([
19914
19980
  var RetrainFrameSelectionSchema = object({
19915
19981
  copied: array(RetrainFrameSchema).readonly(),
19916
19982
  refused: array(object({
19917
- sourceMediaKey: string(),
19983
+ sourceMediaId: number$1().int(),
19918
19984
  reason: RetrainCopyRefusalSchema
19919
19985
  })).readonly()
19920
19986
  });
@@ -19922,7 +19988,7 @@ var RetrainFrameListSchema = object({
19922
19988
  candidates: array(RetrainFrameCandidateSchema).readonly(),
19923
19989
  copies: array(RetrainFrameSchema).readonly(),
19924
19990
  /** What the page pre-selects — the native key frame when one survives. */
19925
- autoPickMediaKey: string().optional()
19991
+ autoPickMediaId: number$1().int().optional()
19926
19992
  });
19927
19993
  /** What the operator asked the assist to look for. */
19928
19994
  var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
@@ -20017,9 +20083,11 @@ var RecentTracksQueryInput = object({
20017
20083
  });
20018
20084
  /**
20019
20085
  * A Summary (D359): the per-camera session envelope that references tracks.
20020
- * `groupShotKey` is the media key of its group shot (`ownerKind: 'summary'`,
20021
- * served by the event-media plane by key), null until the first frame with a
20022
- * member visible has been materialised.
20086
+ * `groupShotMediaId` is the media ROW id of its group shot (`ownerKind:
20087
+ * 'summary'`, served by the event-media plane at `/m/<id>`), null until the
20088
+ * first frame with a member visible has been materialised (D482 — it was the
20089
+ * row's composite key, which embedded the owner and could name another table's
20090
+ * row once event ids became per-table rowids).
20023
20091
  */
20024
20092
  var SummarySchema = object({
20025
20093
  id: string(),
@@ -20028,19 +20096,115 @@ var SummarySchema = object({
20028
20096
  lastActiveAt: number$1(),
20029
20097
  sealedAt: number$1().nullable(),
20030
20098
  memberCount: number$1().int().nonnegative(),
20031
- groupShotKey: string().nullable()
20099
+ groupShotMediaId: number$1().int().nullable()
20032
20100
  });
20033
20101
  var SummariesQueryInput = object({
20034
20102
  deviceIds: array(number$1()).min(1),
20035
20103
  since: number$1().optional(),
20036
20104
  until: number$1().optional(),
20037
- limit: number$1().int().min(1).max(200).default(50)
20105
+ limit: number$1().int().min(1).max(200).default(50),
20106
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20107
+ * meaning of "dog" on every summary surface. */
20108
+ classes: array(string().min(1).max(60)).max(40).optional()
20038
20109
  });
20039
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20040
20110
  var SummaryDetailSchema = object({
20041
20111
  summary: SummarySchema,
20042
- /** Member track ids, in absorption order. */
20043
- trackIds: array(string()).readonly()
20112
+ /** Members in absorption order, oldest join first. */
20113
+ members: array(object({
20114
+ trackId: string(),
20115
+ /**
20116
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20117
+ * and the track no longer exists to fill it.
20118
+ *
20119
+ * The join, not the latest opinion: class voting can re-decide a track's
20120
+ * class afterwards, and a group's composition is the history of who came
20121
+ * together. A consumer counts these to badge a thumbnail — "two people and a
20122
+ * dog" — rather than being handed a second, denormalised tally that would
20123
+ * drift from the members it claims to describe.
20124
+ */
20125
+ className: string().nullable(),
20126
+ /** `primary` is the one member that carries the group's identity. */
20127
+ role: _enum([
20128
+ "primary",
20129
+ "sibling",
20130
+ "child"
20131
+ ]).nullable(),
20132
+ joinedAt: number$1(),
20133
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
20134
+ * at read time. Absent when nothing resolved, never an empty string. */
20135
+ label: string().optional(),
20136
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
20137
+ * recognised nobody, and absent for a member whose track has been pruned —
20138
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
20139
+ subLabel: string().optional()
20140
+ })).readonly()
20141
+ });
20142
+ /**
20143
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
20144
+ *
20145
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
20146
+ * we extend when we notice we need more". Enough to draw the card ("two people
20147
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
20148
+ * are on {@link getSummary}: shipping every member of every group so a client
20149
+ * can count them turns a page of two hundred groups into a thousand records to
20150
+ * draw two hundred badges.
20151
+ */
20152
+ var SummaryListItemSchema = object({
20153
+ summary: SummarySchema,
20154
+ /** Class counts, most numerous first; `className: null` is a member whose
20155
+ * class never resolved, counted so the counts add up to `memberCount`. */
20156
+ classes: array(object({
20157
+ className: string().nullable(),
20158
+ count: number$1().int().positive()
20159
+ })).readonly()
20160
+ });
20161
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
20162
+ /**
20163
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
20164
+ *
20165
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
20166
+ * summaries feed that could only answer "the last N for these cameras"
20167
+ * (`listSummaries`) would make groups available in the timeline and nowhere
20168
+ * else. Two feeds with different ordering contracts would be two ideas of what
20169
+ * "newest" means, on one screen.
20170
+ */
20171
+ var RecentSummariesQueryInput = object({
20172
+ /** Devices to merge. An empty array yields an empty page. */
20173
+ deviceIds: array(number$1()),
20174
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20175
+ since: number$1().optional(),
20176
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20177
+ until: number$1().optional(),
20178
+ limit: number$1().int().min(1).max(500).default(100),
20179
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20180
+ cursor: string().optional(),
20181
+ /**
20182
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20183
+ * deliberately the same name: the events filter tree emits one list of ticked
20184
+ * taxonomy nodes, and a summaries feed that called it something else would
20185
+ * make the operator's one filter component produce two shapes.
20186
+ *
20187
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20188
+ * me the arrivals with a dog" has to return the group of four people who had
20189
+ * one with them; a filter on the group's own fields could only ever answer
20190
+ * about its primary.
20191
+ *
20192
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20193
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20194
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20195
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20196
+ * that read only the class would answer "animal" and never "dog".
20197
+ *
20198
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20199
+ */
20200
+ classes: array(string().min(1).max(60)).max(40).optional()
20201
+ });
20202
+ var RecentSummariesPageSchema = object({
20203
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20204
+ * {@link listSummaries} — one list shape, so a card renders the same
20205
+ * whichever feed drew it. */
20206
+ summaries: array(SummaryListItemSchema).readonly(),
20207
+ nextCursor: string().nullable()
20044
20208
  });
20045
20209
  var RecentTracksPageSchema = object({
20046
20210
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20454,7 +20618,7 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
20454
20618
  classes: array(string()).optional(),
20455
20619
  /** See {@link ExcludeSourcesDoc}. */
20456
20620
  excludeSources: array(TrackSourceSchema).optional()
20457
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number$1() }), _void(), {
20621
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number$1() }), _void(), {
20458
20622
  kind: "mutation",
20459
20623
  auth: "admin"
20460
20624
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number$1() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number$1()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -20582,7 +20746,7 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
20582
20746
  }), method(object({
20583
20747
  deviceId: number$1(),
20584
20748
  trackId: string(),
20585
- mediaKeys: array(string()).min(1)
20749
+ mediaIds: array(number$1().int()).min(1)
20586
20750
  }), RetrainFrameSelectionSchema, {
20587
20751
  kind: "mutation",
20588
20752
  auth: "admin"
@@ -20650,7 +20814,10 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
20650
20814
  kind: "query",
20651
20815
  auth: "admin"
20652
20816
  }), method(object({
20653
- eventId: string(),
20817
+ /** Which of the three event tables owns the row. */
20818
+ ownerType: EventOwnerTypeSchema,
20819
+ /** The event's rowid in that table (D474: an event id is a number). */
20820
+ eventId: number$1().int(),
20654
20821
  kind: MediaFileKindEnum.optional(),
20655
20822
  deviceId: number$1()
20656
20823
  }), array(MediaFileSchema).readonly()), method(object({
@@ -20661,7 +20828,8 @@ DeviceType.Camera, method(object({ deviceId: number$1() }), array(TrackSchema).r
20661
20828
  trackId: string(),
20662
20829
  deviceId: number$1()
20663
20830
  }), array(MediaFileInfoSchema).readonly()), method(object({
20664
- eventId: string(),
20831
+ ownerType: EventOwnerTypeSchema,
20832
+ eventId: number$1().int(),
20665
20833
  deviceId: number$1()
20666
20834
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
20667
20835
  kind: "mutation",
@@ -25124,8 +25292,8 @@ var IdentitySchema = object({
25124
25292
  id: string(),
25125
25293
  name: string(),
25126
25294
  sampleCount: number$1().int().nonnegative(),
25127
- coverMediaKey: string().optional(),
25128
- /** Inline base64 of the cover sample's crop, resolved from `coverMediaKey`.
25295
+ coverMediaId: number$1().int().optional(),
25296
+ /** Inline base64 of the cover sample's crop, resolved from `coverMediaId`.
25129
25297
  * Lets the UI render the person's face avatar instead of a placeholder icon. */
25130
25298
  coverBase64: string().optional()
25131
25299
  });
@@ -25151,8 +25319,8 @@ var IdentitySampleAuditRowSchema = object({
25151
25319
  enrolledAt: number$1().int(),
25152
25320
  deviceId: number$1().int().optional(),
25153
25321
  source: _enum(["detected", "photo"]),
25154
- /** The enrolled crop's media key, so the panel can show the evidence. */
25155
- mediaKey: string().optional(),
25322
+ /** The enrolled crop's media row id, so the panel can show the evidence. */
25323
+ mediaId: number$1().int().optional(),
25156
25324
  /**
25157
25325
  * False = the sample is stamped with a DIFFERENT face model, and a cosine
25158
25326
  * across feature spaces is a well-formed float with no meaning. Every number
@@ -25221,15 +25389,15 @@ var FaceInfoSchema = object({
25221
25389
  */
25222
25390
  cropUrl: string().optional(),
25223
25391
  /** Design B: the face bbox (pixel space) on the key frame — lets a detail
25224
- * view draw the box over the native `keyFrameMediaKey` frame. Absent on
25392
+ * view draw the box over the native `keyFrameMediaId` frame. Absent on
25225
25393
  * legacy rows written before design B. */
25226
25394
  faceBbox: BoundingBoxSchema.optional(),
25227
25395
  /** Design B: MediaStore key of the track's native-resolution key frame.
25228
25396
  * Fetch the native JPEG via the event-media data-plane
25229
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`). Absent when the
25397
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`). Absent when the
25230
25398
  * track produced no key frame (e.g. native/onboard source) — the UI falls
25231
25399
  * back to the inline `base64` face crop. */
25232
- keyFrameMediaKey: string().optional(),
25400
+ keyFrameMediaId: number$1().int().optional(),
25233
25401
  /** Winning identity-match cosine (0..1) for this face's track, when an
25234
25402
  * identity was auto-confirmed. Lets the UI surface WHY a face was assigned
25235
25403
  * (confidence badge / low-confidence audit). Absent on legacy rows and on
@@ -25298,11 +25466,14 @@ var FaceClusterSchema = object({
25298
25466
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
25299
25467
  * dialog already rendering its key FRAME from the `event-media` plane.
25300
25468
  *
25301
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
25302
- * media key directly, so this needed no new plane and no new access decision.
25469
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
25470
+ * resolves a media row by its own id, so this needed no new plane and no new
25471
+ * access decision.
25303
25472
  */
25304
25473
  var MediaFileLiteSchema$1 = object({
25305
- key: string(),
25474
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
25475
+ * (D482). It was the row's composite key, which embedded the owner. */
25476
+ mediaId: number$1().int(),
25306
25477
  kind: string(),
25307
25478
  url: string(),
25308
25479
  sizeBytes: number$1(),
@@ -25319,7 +25490,7 @@ method(object({
25319
25490
  * covers inline, ~10 KB of base64 per row, on a query this UI mounts
25320
25491
  * four times and the viewer holds at `staleTime: 30_000`.
25321
25492
  *
25322
- * Nothing loses its avatar: `coverMediaKey` is already on every row and
25493
+ * Nothing loses its avatar: `coverMediaId` is already on every row and
25323
25494
  * the `event-media` plane serves that key `immutable` with an ETag.
25324
25495
  *
25325
25496
  * **This is an INPUT field, so it does not reach the addon until the
@@ -27787,8 +27958,8 @@ var VehicleSchema = object({
27787
27958
  id: string(),
27788
27959
  name: string(),
27789
27960
  sampleCount: number$1().int().nonnegative(),
27790
- coverMediaKey: string().optional(),
27791
- /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
27961
+ coverMediaId: number$1().int().optional(),
27962
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaId`. */
27792
27963
  coverBase64: string().optional()
27793
27964
  });
27794
27965
  var VehicleSampleInfoSchema = object({
@@ -27821,7 +27992,7 @@ var PlateInfoSchema = object({
27821
27992
  /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
27822
27993
  plateBbox: BoundingBoxSchema.optional(),
27823
27994
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
27824
- keyFrameMediaKey: string().optional(),
27995
+ keyFrameMediaId: number$1().int().optional(),
27825
27996
  base64: string().optional(),
27826
27997
  /**
27827
27998
  * Same crop as a data-plane URL, always present when the plate has a stored
@@ -27842,11 +28013,14 @@ var PlateInfoSchema = object({
27842
28013
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
27843
28014
  * dialog already rendering its key FRAME from the `event-media` plane.
27844
28015
  *
27845
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
27846
- * media key directly, so this needed no new plane and no new access decision.
28016
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
28017
+ * resolves a media row by its own id, so this needed no new plane and no new
28018
+ * access decision.
27847
28019
  */
27848
28020
  var MediaFileLiteSchema = object({
27849
- key: string(),
28021
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
28022
+ * (D482). It was the row's composite key, which embedded the owner. */
28023
+ mediaId: number$1().int(),
27850
28024
  kind: string(),
27851
28025
  url: string(),
27852
28026
  sizeBytes: number$1(),
@@ -27905,7 +28079,7 @@ method(object({
27905
28079
  }), method(object({
27906
28080
  /** Inline {@link VehicleSchema.coverBase64} on every row. Default
27907
28081
  * `false` — the vehicle twin of `faceGallery.listIdentities`'s
27908
- * option; `coverMediaKey` + the `event-media` plane carry the picture.
28082
+ * option; `coverMediaId` + the `event-media` plane carry the picture.
27909
28083
  * INPUT field: stripped by the hub router until the train ships, which
27910
28084
  * resolves to `false` and is exactly the intended default. */
27911
28085
  includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
@@ -29229,7 +29403,14 @@ var SceneReferenceSchema = object({
29229
29403
  modelId: string(),
29230
29404
  condition: SceneConditionSchema,
29231
29405
  capturedAt: number$1(),
29232
- thumbnailMediaId: string().optional(),
29406
+ /**
29407
+ * The reference thumbnail's media ROW id (D482).
29408
+ *
29409
+ * The name already said `mediaId` while the value was the row's composite
29410
+ * key — a leftover that read as done and was not. It is the row's own id
29411
+ * now, addressed at `/addon/<addonId>/event-media/m/<id>`.
29412
+ */
29413
+ thumbnailMediaId: number$1().int().optional(),
29233
29414
  /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
29234
29415
  * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
29235
29416
  * normalized rect frame a different piece of world, and the scene would
@@ -30043,7 +30224,29 @@ var LoggingSettingsPatchSchema = object({
30043
30224
  * disarmed the channels it did not mention would make the Levels page and
30044
30225
  * the Diagnostics page fight over the same value.
30045
30226
  */
30046
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
30227
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
30228
+ /**
30229
+ * How many days the analytics OPS LOG is kept — the audit trail of what
30230
+ * retention actually deleted.
30231
+ *
30232
+ * It lives HERE, on the logging document, and not on the analytics addon's
30233
+ * settings form, on the operator's instruction: every other log-ish window in
30234
+ * this system is configured through this one document, and a retention audit
30235
+ * configured somewhere else is a second place to look when the question is
30236
+ * "what happened to my data".
30237
+ *
30238
+ * That placement has a cost worth stating: this document ships in the
30239
+ * framework closure, so this knob needs a published `@camstack/server` while
30240
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
30241
+ *
30242
+ * `retention-model.ts` argued this one must NOT follow a device window,
30243
+ * because the audit trail is the history of the SWEEP and not of the camera.
30244
+ * That argument stands and is untouched — what changes is that 30 days stops
30245
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
30246
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
30247
+ * state in which the next 9.5 GiB leak cannot be explained.
30248
+ */
30249
+ opsLogRetentionDays: number$1().int().min(0).max(3650).optional()
30047
30250
  });
30048
30251
  /**
30049
30252
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -30116,6 +30319,14 @@ var LoggingSettingsStateSchema = object({
30116
30319
  channels: array(LogChannelDescriptorSchema).readonly(),
30117
30320
  /** The channels ARMED right now, each with its deadline. */
30118
30321
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
30322
+ /**
30323
+ * The ops-log window in days, as it is in force. `0` = kept forever.
30324
+ *
30325
+ * Reported rather than left to the caller's memory of what it wrote: this is
30326
+ * the value the analytics sweep will actually use, and an operator asking how
30327
+ * long their audit trail lives should not have to infer it from a default.
30328
+ */
30329
+ opsLogRetentionDays: number$1().int().min(0),
30119
30330
  persisted: boolean()
30120
30331
  });
30121
30332
  method(_void(), FeatureManifestSchema), method(_void(), HealthStatusSchema), method(_void(), FeatureManifestSchema), method(_void(), array(NetworkAddressSchema).readonly()), method(_void(), unknown().nullable(), { auth: "admin" }), method(record(string(), unknown()), _null(), {
@@ -30677,7 +30888,7 @@ var StationaryObjectSchema = object({
30677
30888
  /** Enrichment label carried from the source track (identity / plate). */
30678
30889
  label: string().optional(),
30679
30890
  /** Native-resolution key-frame media key for the parked object's best image. */
30680
- keyFrameMediaKey: string().optional()
30891
+ keyFrameMediaId: number$1().int().optional()
30681
30892
  });
30682
30893
  var CameraOccupancySnapshotSchema = object({
30683
30894
  /** Frame timestamp of the inference result that produced this snapshot. */
@@ -31833,6 +32044,12 @@ Object.freeze({
31833
32044
  addonId: null,
31834
32045
  access: "create"
31835
32046
  },
32047
+ "dataStoreProvider.upsert": {
32048
+ capName: "data-store-provider",
32049
+ capScope: "system",
32050
+ addonId: null,
32051
+ access: "create"
32052
+ },
31836
32053
  "dayNight.getOptions": {
31837
32054
  capName: "day-night",
31838
32055
  capScope: "device",
@@ -34269,6 +34486,12 @@ Object.freeze({
34269
34486
  addonId: null,
34270
34487
  access: "view"
34271
34488
  },
34489
+ "pipelineAnalytics.listRecentSummaries": {
34490
+ capName: "pipeline-analytics",
34491
+ capScope: "device",
34492
+ addonId: null,
34493
+ access: "view"
34494
+ },
34272
34495
  "pipelineAnalytics.listRecentTracks": {
34273
34496
  capName: "pipeline-analytics",
34274
34497
  capScope: "device",
@@ -35697,6 +35920,12 @@ Object.freeze({
35697
35920
  addonId: null,
35698
35921
  access: "create"
35699
35922
  },
35923
+ "settingsStore.upsert": {
35924
+ capName: "settings-store",
35925
+ capScope: "system",
35926
+ addonId: null,
35927
+ access: "create"
35928
+ },
35700
35929
  "smtpProvider.getStatus": {
35701
35930
  capName: "smtp-provider",
35702
35931
  capScope: "system",
@@ -38090,6 +38319,11 @@ Object.freeze({
38090
38319
  form: "single",
38091
38320
  optional: true
38092
38321
  }],
38322
+ "pipelineAnalytics.listRecentSummaries": [{
38323
+ name: "deviceIds",
38324
+ form: "array",
38325
+ optional: false
38326
+ }],
38093
38327
  "pipelineAnalytics.listRecentTracks": [{
38094
38328
  name: "deviceIds",
38095
38329
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-ai",
3
- "version": "0.4.104",
3
+ "version": "0.4.106",
4
4
  "description": "AI addon for CamStack — the `llm` collection provider (cloud, LAN, and camstack-managed local llama.cpp profiles) plus the per-node `llm-runtime` managed executor.",
5
5
  "keywords": [
6
6
  "camstack",