@camstack/addon-provider-tuya 0.2.99 → 0.2.101

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
@@ -819,7 +819,7 @@ async function discoverDevices(options = {}, binder = createDgramBinder()) {
819
819
  return [...byId.values()];
820
820
  }
821
821
  //#endregion
822
- //#region ../types/dist/event-category-DAXzJeTX.mjs
822
+ //#region ../types/dist/event-category-ZyX6jcse.mjs
823
823
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
824
824
  EventCategory["SystemBoot"] = "system.boot";
825
825
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1380,7 +1380,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1380
1380
  * a package zone — a newly-appeared stationary object of a package class
1381
1381
  * that cleared the class / zone / dwell / size gates. Payload:
1382
1382
  * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
1383
- * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
1383
+ * entryId, className, zoneIds, keyFrameMediaId?, bbox, timestamp }`.
1384
1384
  * Telemetry (D8): the durable record is the `package-events` store row;
1385
1385
  * this bus topic drives notifier rules + live UI. See
1386
1386
  * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
@@ -6166,7 +6166,7 @@ var ZodIssueCode = {
6166
6166
  var ZodFirstPartyTypeKind;
6167
6167
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6168
6168
  //#endregion
6169
- //#region ../types/dist/sleep-BnujYGPe.mjs
6169
+ //#region ../types/dist/sleep-DBKu2-U5.mjs
6170
6170
  /**
6171
6171
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6172
6172
  * window to float samples (D455).
@@ -8133,13 +8133,30 @@ new Set([
8133
8133
  "scene",
8134
8134
  "motion",
8135
8135
  "object",
8136
- "audio"
8136
+ "audio",
8137
+ "mosaic"
8137
8138
  ]);
8138
- new Set([
8139
+ /**
8140
+ * The owner types that are event tables, in the order the event-media resolver
8141
+ * should consider them. Exported so a consumer asking "is this key an event?"
8142
+ * does not re-spell the list and drift from it.
8143
+ */
8144
+ var EVENT_OWNER_TYPES = [
8139
8145
  "motion",
8140
8146
  "object",
8141
8147
  "audio"
8142
- ]);
8148
+ ];
8149
+ /**
8150
+ * The same list as a Zod enum, for the cap inputs that must NAME the table
8151
+ * (D482: `getEventMedia` / `listEventMedia` take an owner, and an owner is
8152
+ * `(table, id)`).
8153
+ *
8154
+ * Built FROM {@link EVENT_OWNER_TYPES} rather than re-spelled: a fourth event
8155
+ * table would otherwise be accepted by the codec and refused at the door, with
8156
+ * nothing failing until a caller asked.
8157
+ */
8158
+ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
8159
+ new Set(EVENT_OWNER_TYPES);
8143
8160
  var EncodeProfileSchema = object({
8144
8161
  video: object({
8145
8162
  codec: _enum([
@@ -8178,7 +8195,23 @@ var EncodeProfileSchema = object({
8178
8195
  "zerolatency",
8179
8196
  "film",
8180
8197
  "animation"
8181
- ]).optional()
8198
+ ]).optional(),
8199
+ /**
8200
+ * ONE slice per access unit.
8201
+ *
8202
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
8203
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
8204
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
8205
+ * frame-end per frame: the first pictures render and the video then freezes
8206
+ * for good while the audio, on its own plane, plays on. Measured on this hub
8207
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
8208
+ *
8209
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
8210
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
8211
+ * consumers meaning the same thing spelled differently would stop sharing one
8212
+ * child. Absent means "whatever the encoder does" — today's behaviour.
8213
+ */
8214
+ singleSlicePerFrame: boolean().optional()
8182
8215
  }),
8183
8216
  audio: union([literal("passthrough"), object({
8184
8217
  codec: _enum([
@@ -8228,7 +8261,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
8228
8261
  audio: "passthrough"
8229
8262
  };
8230
8263
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
8231
- ({ ...BASE_LIVE_EGRESS_PROFILE });
8264
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
8232
8265
  /**
8233
8266
  * Deep wiring healthcheck — snapshot of active reachability probes across
8234
8267
  * every declared capability + widget of every installed plugin, on every
@@ -13338,6 +13371,16 @@ method(object({
13338
13371
  filter: QueryFilterSchema.optional(),
13339
13372
  columns: array(string()).readonly().optional()
13340
13373
  }), array(SettingsRecordSchema).readonly()), method(object({
13374
+ namespace: string().optional(),
13375
+ collection: string(),
13376
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
13377
+ * index on the collection. Never empty. */
13378
+ conflict: array(string()).readonly(),
13379
+ record: BulkRecordSchema
13380
+ }), object({
13381
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
13382
+ * row's own id on an update. */
13383
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
13341
13384
  namespace: string().optional(),
13342
13385
  collection: string(),
13343
13386
  record: object({
@@ -13472,6 +13515,14 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13472
13515
  id: union([string(), number()]) }), {
13473
13516
  kind: "mutation",
13474
13517
  auth: "admin"
13518
+ }), method(object({
13519
+ namespace: string().optional(),
13520
+ collection: string(),
13521
+ conflict: array(string()).readonly(),
13522
+ record: BulkRecordSchema
13523
+ }), object({ id: union([string(), number()]) }), {
13524
+ kind: "mutation",
13525
+ auth: "admin"
13475
13526
  }), method(object({
13476
13527
  namespace: string().optional(),
13477
13528
  collection: string(),
@@ -19971,8 +20022,8 @@ var TrackPositionSchema = object({
19971
20022
  var TrackSnapshotSchema = object({
19972
20023
  timestamp: number(),
19973
20024
  position: TrackPositionSchema,
19974
- /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
19975
- mediaKey: string()
20025
+ /** The snapshot's media row id (D482); resolve via `listTrackMedia({ trackId })`. */
20026
+ mediaId: number().int()
19976
20027
  });
19977
20028
  /**
19978
20029
  * Normalized 0..1 trajectory envelope (min/max over every position bbox,
@@ -20576,14 +20627,14 @@ var ObjectEventSchema = object({
20576
20627
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20577
20628
  frameWidth: number().optional(),
20578
20629
  frameHeight: number().optional(),
20579
- /** MediaStore key for the crop attached to this event (if any). */
20580
- mediaKey: string().optional(),
20581
- /** Design B: MediaStore key of the track's native-resolution key frame (the
20630
+ /** Media row id of the crop attached to this event (if any) — D482. */
20631
+ mediaId: number().int().optional(),
20632
+ /** Design B: media row id of the track's native-resolution key frame (the
20582
20633
  * best-detection full frame). Resolve via the event-media data-plane
20583
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`) for a detail view that
20634
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`) for a detail view that
20584
20635
  * draws `bbox` over the native frame. Absent on legacy rows / non-decoded
20585
- * sources — consumers fall back to `mediaKey` (the tight crop). */
20586
- keyFrameMediaKey: string().optional(),
20636
+ * sources — consumers fall back to `mediaId` (the tight crop). */
20637
+ keyFrameMediaId: number().int().optional(),
20587
20638
  /** Populated by B5 (recording playback URL for this event). */
20588
20639
  mediaUrl: string().optional(),
20589
20640
  /** The parent track's key-event importance [0,1], propagated to every object
@@ -20633,7 +20684,7 @@ var MediaFileKindEnum = _enum([
20633
20684
  * hub-main's heap on the way past — for an `<img>` that would have cached it.
20634
20685
  *
20635
20686
  * `url` points at the `event-media` data plane
20636
- * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
20687
+ * (`/addon/<addonId>/event-media/m/<mediaId>` — D482), which serves the same blob
20637
20688
  * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
20638
20689
  * render a `?variant=thumb`, and streams. The hub gate in front of it requires
20639
20690
  * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
@@ -20641,10 +20692,13 @@ var MediaFileKindEnum = _enum([
20641
20692
  * `data-plane-access.ts` for the rule and the one gap it does not close
20642
20693
  * (per-device scoping).
20643
20694
  *
20644
- * The URL is built from the row's **stored** key, which is not always its
20645
- * published `kind`: a track's face/plate crop is stored as `crop` under
20695
+ * The URL is built from the row's own id, which since D482 says nothing about
20696
+ * the row's owner or kind — and that is the point. The published `kind` is not
20697
+ * the stored one (a track's face/plate crop is stored as `crop` under
20646
20698
  * `('face'|'plate', '<prefix>-<trackId>')` and published as
20647
- * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
20699
+ * `faceCrop`/`plateCrop`), and under the old composite key the URL had to be
20700
+ * taken from `row.key` verbatim or it 404'd exactly those two rows. An id
20701
+ * cannot be mis-reconstructed, because there is nothing in it to reconstruct.
20648
20702
  *
20649
20703
  * ## `base64` is TRANSITIONAL and is going away
20650
20704
  *
@@ -20657,7 +20711,19 @@ var MediaFileKindEnum = _enum([
20657
20711
  * `analytics-query-facade.ts`; nothing else reads it.
20658
20712
  */
20659
20713
  var MediaFileSchema = object({
20660
- key: string(),
20714
+ /**
20715
+ * The media row's OWN id — the rowid SQLite assigned it (D482).
20716
+ *
20717
+ * It used to be the row's composite key, `<ownerType>:<ownerId>:<fileKind>
20718
+ * [:<timestampMs>]`, which embedded a FOREIGN id. D474 made event ids
20719
+ * per-table rowid aliases, so `object:101314` and `motion:101314` are two
20720
+ * different rows that spell the same number, and a track whose
20721
+ * `bestEventId` was an object event was served a motion event's snapshot —
20722
+ * a night-time thumbnail on an afternoon track, with no error anywhere. A
20723
+ * surrogate id cannot name the wrong row's owner, because it names no owner
20724
+ * at all.
20725
+ */
20726
+ mediaId: number().int(),
20661
20727
  kind: MediaFileKindEnum,
20662
20728
  sizeBytes: number(),
20663
20729
  timestamp: number()
@@ -20765,7 +20831,7 @@ var RetrainTrackSchema = object({
20765
20831
  });
20766
20832
  /** A frame the picker may offer — an index row, no blob was read to produce it. */
20767
20833
  var RetrainFrameCandidateSchema = object({
20768
- mediaKey: string(),
20834
+ mediaId: number().int(),
20769
20835
  kind: MediaFileKindEnum,
20770
20836
  timestamp: number(),
20771
20837
  sizeBytes: number().int(),
@@ -20779,7 +20845,7 @@ var RetrainFrameSchema = object({
20779
20845
  deviceId: number(),
20780
20846
  trackId: string(),
20781
20847
  /** Provenance only. It may already point at nothing — that is expected. */
20782
- sourceMediaKey: string(),
20848
+ sourceMediaId: number().int(),
20783
20849
  sourceKind: MediaFileKindEnum,
20784
20850
  sizeBytes: number().int(),
20785
20851
  width: number().int(),
@@ -20795,7 +20861,7 @@ var RetrainCopyRefusalSchema = _enum([
20795
20861
  var RetrainFrameSelectionSchema = object({
20796
20862
  copied: array(RetrainFrameSchema).readonly(),
20797
20863
  refused: array(object({
20798
- sourceMediaKey: string(),
20864
+ sourceMediaId: number().int(),
20799
20865
  reason: RetrainCopyRefusalSchema
20800
20866
  })).readonly()
20801
20867
  });
@@ -20803,7 +20869,7 @@ var RetrainFrameListSchema = object({
20803
20869
  candidates: array(RetrainFrameCandidateSchema).readonly(),
20804
20870
  copies: array(RetrainFrameSchema).readonly(),
20805
20871
  /** What the page pre-selects — the native key frame when one survives. */
20806
- autoPickMediaKey: string().optional()
20872
+ autoPickMediaId: number().int().optional()
20807
20873
  });
20808
20874
  /** What the operator asked the assist to look for. */
20809
20875
  var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
@@ -20898,9 +20964,11 @@ var RecentTracksQueryInput = object({
20898
20964
  });
20899
20965
  /**
20900
20966
  * A Summary (D359): the per-camera session envelope that references tracks.
20901
- * `groupShotKey` is the media key of its group shot (`ownerKind: 'summary'`,
20902
- * served by the event-media plane by key), null until the first frame with a
20903
- * member visible has been materialised.
20967
+ * `groupShotMediaId` is the media ROW id of its group shot (`ownerKind:
20968
+ * 'summary'`, served by the event-media plane at `/m/<id>`), null until the
20969
+ * first frame with a member visible has been materialised (D482 — it was the
20970
+ * row's composite key, which embedded the owner and could name another table's
20971
+ * row once event ids became per-table rowids).
20904
20972
  */
20905
20973
  var SummarySchema = object({
20906
20974
  id: string(),
@@ -20909,19 +20977,115 @@ var SummarySchema = object({
20909
20977
  lastActiveAt: number(),
20910
20978
  sealedAt: number().nullable(),
20911
20979
  memberCount: number().int().nonnegative(),
20912
- groupShotKey: string().nullable()
20980
+ groupShotMediaId: number().int().nullable()
20913
20981
  });
20914
20982
  var SummariesQueryInput = object({
20915
20983
  deviceIds: array(number()).min(1),
20916
20984
  since: number().optional(),
20917
20985
  until: number().optional(),
20918
- limit: number().int().min(1).max(200).default(50)
20986
+ limit: number().int().min(1).max(200).default(50),
20987
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20988
+ * meaning of "dog" on every summary surface. */
20989
+ classes: array(string().min(1).max(60)).max(40).optional()
20919
20990
  });
20920
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20921
20991
  var SummaryDetailSchema = object({
20922
20992
  summary: SummarySchema,
20923
- /** Member track ids, in absorption order. */
20924
- trackIds: array(string()).readonly()
20993
+ /** Members in absorption order, oldest join first. */
20994
+ members: array(object({
20995
+ trackId: string(),
20996
+ /**
20997
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20998
+ * and the track no longer exists to fill it.
20999
+ *
21000
+ * The join, not the latest opinion: class voting can re-decide a track's
21001
+ * class afterwards, and a group's composition is the history of who came
21002
+ * together. A consumer counts these to badge a thumbnail — "two people and a
21003
+ * dog" — rather than being handed a second, denormalised tally that would
21004
+ * drift from the members it claims to describe.
21005
+ */
21006
+ className: string().nullable(),
21007
+ /** `primary` is the one member that carries the group's identity. */
21008
+ role: _enum([
21009
+ "primary",
21010
+ "sibling",
21011
+ "child"
21012
+ ]).nullable(),
21013
+ joinedAt: number(),
21014
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
21015
+ * at read time. Absent when nothing resolved, never an empty string. */
21016
+ label: string().optional(),
21017
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
21018
+ * recognised nobody, and absent for a member whose track has been pruned —
21019
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
21020
+ subLabel: string().optional()
21021
+ })).readonly()
21022
+ });
21023
+ /**
21024
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
21025
+ *
21026
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
21027
+ * we extend when we notice we need more". Enough to draw the card ("two people
21028
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
21029
+ * are on {@link getSummary}: shipping every member of every group so a client
21030
+ * can count them turns a page of two hundred groups into a thousand records to
21031
+ * draw two hundred badges.
21032
+ */
21033
+ var SummaryListItemSchema = object({
21034
+ summary: SummarySchema,
21035
+ /** Class counts, most numerous first; `className: null` is a member whose
21036
+ * class never resolved, counted so the counts add up to `memberCount`. */
21037
+ classes: array(object({
21038
+ className: string().nullable(),
21039
+ count: number().int().positive()
21040
+ })).readonly()
21041
+ });
21042
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
21043
+ /**
21044
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
21045
+ *
21046
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
21047
+ * summaries feed that could only answer "the last N for these cameras"
21048
+ * (`listSummaries`) would make groups available in the timeline and nowhere
21049
+ * else. Two feeds with different ordering contracts would be two ideas of what
21050
+ * "newest" means, on one screen.
21051
+ */
21052
+ var RecentSummariesQueryInput = object({
21053
+ /** Devices to merge. An empty array yields an empty page. */
21054
+ deviceIds: array(number()),
21055
+ /** Window lower bound on `lastActiveAt` (inclusive). */
21056
+ since: number().optional(),
21057
+ /** Window upper bound on `lastActiveAt` (inclusive). */
21058
+ until: number().optional(),
21059
+ limit: number().int().min(1).max(500).default(100),
21060
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
21061
+ cursor: string().optional(),
21062
+ /**
21063
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
21064
+ * deliberately the same name: the events filter tree emits one list of ticked
21065
+ * taxonomy nodes, and a summaries feed that called it something else would
21066
+ * make the operator's one filter component produce two shapes.
21067
+ *
21068
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
21069
+ * me the arrivals with a dog" has to return the group of four people who had
21070
+ * one with them; a filter on the group's own fields could only ever answer
21071
+ * about its primary.
21072
+ *
21073
+ * Matched against all THREE tiers of a member — the class (`animal`), the
21074
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
21075
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
21076
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
21077
+ * that read only the class would answer "animal" and never "dog".
21078
+ *
21079
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
21080
+ */
21081
+ classes: array(string().min(1).max(60)).max(40).optional()
21082
+ });
21083
+ var RecentSummariesPageSchema = object({
21084
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
21085
+ * {@link listSummaries} — one list shape, so a card renders the same
21086
+ * whichever feed drew it. */
21087
+ summaries: array(SummaryListItemSchema).readonly(),
21088
+ nextCursor: string().nullable()
20925
21089
  });
20926
21090
  var RecentTracksPageSchema = object({
20927
21091
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -21335,7 +21499,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21335
21499
  classes: array(string()).optional(),
21336
21500
  /** See {@link ExcludeSourcesDoc}. */
21337
21501
  excludeSources: array(TrackSourceSchema).optional()
21338
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
21502
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
21339
21503
  kind: "mutation",
21340
21504
  auth: "admin"
21341
21505
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -21463,7 +21627,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21463
21627
  }), method(object({
21464
21628
  deviceId: number(),
21465
21629
  trackId: string(),
21466
- mediaKeys: array(string()).min(1)
21630
+ mediaIds: array(number().int()).min(1)
21467
21631
  }), RetrainFrameSelectionSchema, {
21468
21632
  kind: "mutation",
21469
21633
  auth: "admin"
@@ -21531,7 +21695,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21531
21695
  kind: "query",
21532
21696
  auth: "admin"
21533
21697
  }), method(object({
21534
- eventId: string(),
21698
+ /** Which of the three event tables owns the row. */
21699
+ ownerType: EventOwnerTypeSchema,
21700
+ /** The event's rowid in that table (D474: an event id is a number). */
21701
+ eventId: number().int(),
21535
21702
  kind: MediaFileKindEnum.optional(),
21536
21703
  deviceId: number()
21537
21704
  }), array(MediaFileSchema).readonly()), method(object({
@@ -21542,7 +21709,8 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21542
21709
  trackId: string(),
21543
21710
  deviceId: number()
21544
21711
  }), array(MediaFileInfoSchema).readonly()), method(object({
21545
- eventId: string(),
21712
+ ownerType: EventOwnerTypeSchema,
21713
+ eventId: number().int(),
21546
21714
  deviceId: number()
21547
21715
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21548
21716
  kind: "mutation",
@@ -26743,8 +26911,8 @@ var IdentitySchema = object({
26743
26911
  id: string(),
26744
26912
  name: string(),
26745
26913
  sampleCount: number().int().nonnegative(),
26746
- coverMediaKey: string().optional(),
26747
- /** Inline base64 of the cover sample's crop, resolved from `coverMediaKey`.
26914
+ coverMediaId: number().int().optional(),
26915
+ /** Inline base64 of the cover sample's crop, resolved from `coverMediaId`.
26748
26916
  * Lets the UI render the person's face avatar instead of a placeholder icon. */
26749
26917
  coverBase64: string().optional()
26750
26918
  });
@@ -26770,8 +26938,8 @@ var IdentitySampleAuditRowSchema = object({
26770
26938
  enrolledAt: number().int(),
26771
26939
  deviceId: number().int().optional(),
26772
26940
  source: _enum(["detected", "photo"]),
26773
- /** The enrolled crop's media key, so the panel can show the evidence. */
26774
- mediaKey: string().optional(),
26941
+ /** The enrolled crop's media row id, so the panel can show the evidence. */
26942
+ mediaId: number().int().optional(),
26775
26943
  /**
26776
26944
  * False = the sample is stamped with a DIFFERENT face model, and a cosine
26777
26945
  * across feature spaces is a well-formed float with no meaning. Every number
@@ -26840,15 +27008,15 @@ var FaceInfoSchema = object({
26840
27008
  */
26841
27009
  cropUrl: string().optional(),
26842
27010
  /** Design B: the face bbox (pixel space) on the key frame — lets a detail
26843
- * view draw the box over the native `keyFrameMediaKey` frame. Absent on
27011
+ * view draw the box over the native `keyFrameMediaId` frame. Absent on
26844
27012
  * legacy rows written before design B. */
26845
27013
  faceBbox: BoundingBoxSchema.optional(),
26846
27014
  /** Design B: MediaStore key of the track's native-resolution key frame.
26847
27015
  * Fetch the native JPEG via the event-media data-plane
26848
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`). Absent when the
27016
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`). Absent when the
26849
27017
  * track produced no key frame (e.g. native/onboard source) — the UI falls
26850
27018
  * back to the inline `base64` face crop. */
26851
- keyFrameMediaKey: string().optional(),
27019
+ keyFrameMediaId: number().int().optional(),
26852
27020
  /** Winning identity-match cosine (0..1) for this face's track, when an
26853
27021
  * identity was auto-confirmed. Lets the UI surface WHY a face was assigned
26854
27022
  * (confidence badge / low-confidence audit). Absent on legacy rows and on
@@ -26917,11 +27085,14 @@ var FaceClusterSchema = object({
26917
27085
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
26918
27086
  * dialog already rendering its key FRAME from the `event-media` plane.
26919
27087
  *
26920
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
26921
- * media key directly, so this needed no new plane and no new access decision.
27088
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
27089
+ * resolves a media row by its own id, so this needed no new plane and no new
27090
+ * access decision.
26922
27091
  */
26923
27092
  var MediaFileLiteSchema$1 = object({
26924
- key: string(),
27093
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
27094
+ * (D482). It was the row's composite key, which embedded the owner. */
27095
+ mediaId: number().int(),
26925
27096
  kind: string(),
26926
27097
  url: string(),
26927
27098
  sizeBytes: number(),
@@ -26938,7 +27109,7 @@ method(object({
26938
27109
  * covers inline, ~10 KB of base64 per row, on a query this UI mounts
26939
27110
  * four times and the viewer holds at `staleTime: 30_000`.
26940
27111
  *
26941
- * Nothing loses its avatar: `coverMediaKey` is already on every row and
27112
+ * Nothing loses its avatar: `coverMediaId` is already on every row and
26942
27113
  * the `event-media` plane serves that key `immutable` with an ETag.
26943
27114
  *
26944
27115
  * **This is an INPUT field, so it does not reach the addon until the
@@ -30301,8 +30472,8 @@ var VehicleSchema = object({
30301
30472
  id: string(),
30302
30473
  name: string(),
30303
30474
  sampleCount: number().int().nonnegative(),
30304
- coverMediaKey: string().optional(),
30305
- /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
30475
+ coverMediaId: number().int().optional(),
30476
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaId`. */
30306
30477
  coverBase64: string().optional()
30307
30478
  });
30308
30479
  var VehicleSampleInfoSchema = object({
@@ -30335,7 +30506,7 @@ var PlateInfoSchema = object({
30335
30506
  /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
30336
30507
  plateBbox: BoundingBoxSchema.optional(),
30337
30508
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
30338
- keyFrameMediaKey: string().optional(),
30509
+ keyFrameMediaId: number().int().optional(),
30339
30510
  base64: string().optional(),
30340
30511
  /**
30341
30512
  * Same crop as a data-plane URL, always present when the plate has a stored
@@ -30356,11 +30527,14 @@ var PlateInfoSchema = object({
30356
30527
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
30357
30528
  * dialog already rendering its key FRAME from the `event-media` plane.
30358
30529
  *
30359
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
30360
- * media key directly, so this needed no new plane and no new access decision.
30530
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
30531
+ * resolves a media row by its own id, so this needed no new plane and no new
30532
+ * access decision.
30361
30533
  */
30362
30534
  var MediaFileLiteSchema = object({
30363
- key: string(),
30535
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
30536
+ * (D482). It was the row's composite key, which embedded the owner. */
30537
+ mediaId: number().int(),
30364
30538
  kind: string(),
30365
30539
  url: string(),
30366
30540
  sizeBytes: number(),
@@ -30419,7 +30593,7 @@ method(object({
30419
30593
  }), method(object({
30420
30594
  /** Inline {@link VehicleSchema.coverBase64} on every row. Default
30421
30595
  * `false` — the vehicle twin of `faceGallery.listIdentities`'s
30422
- * option; `coverMediaKey` + the `event-media` plane carry the picture.
30596
+ * option; `coverMediaId` + the `event-media` plane carry the picture.
30423
30597
  * INPUT field: stripped by the hub router until the train ships, which
30424
30598
  * resolves to `false` and is exactly the intended default. */
30425
30599
  includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
@@ -31988,7 +32162,14 @@ var SceneReferenceSchema = object({
31988
32162
  modelId: string(),
31989
32163
  condition: SceneConditionSchema,
31990
32164
  capturedAt: number(),
31991
- thumbnailMediaId: string().optional(),
32165
+ /**
32166
+ * The reference thumbnail's media ROW id (D482).
32167
+ *
32168
+ * The name already said `mediaId` while the value was the row's composite
32169
+ * key — a leftover that read as done and was not. It is the row's own id
32170
+ * now, addressed at `/addon/<addonId>/event-media/m/<id>`.
32171
+ */
32172
+ thumbnailMediaId: number().int().optional(),
31992
32173
  /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
31993
32174
  * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
31994
32175
  * normalized rect frame a different piece of world, and the scene would
@@ -33079,7 +33260,29 @@ var LoggingSettingsPatchSchema = object({
33079
33260
  * disarmed the channels it did not mention would make the Levels page and
33080
33261
  * the Diagnostics page fight over the same value.
33081
33262
  */
33082
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
33263
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
33264
+ /**
33265
+ * How many days the analytics OPS LOG is kept — the audit trail of what
33266
+ * retention actually deleted.
33267
+ *
33268
+ * It lives HERE, on the logging document, and not on the analytics addon's
33269
+ * settings form, on the operator's instruction: every other log-ish window in
33270
+ * this system is configured through this one document, and a retention audit
33271
+ * configured somewhere else is a second place to look when the question is
33272
+ * "what happened to my data".
33273
+ *
33274
+ * That placement has a cost worth stating: this document ships in the
33275
+ * framework closure, so this knob needs a published `@camstack/server` while
33276
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
33277
+ *
33278
+ * `retention-model.ts` argued this one must NOT follow a device window,
33279
+ * because the audit trail is the history of the SWEEP and not of the camera.
33280
+ * That argument stands and is untouched — what changes is that 30 days stops
33281
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
33282
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
33283
+ * state in which the next 9.5 GiB leak cannot be explained.
33284
+ */
33285
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
33083
33286
  });
33084
33287
  /**
33085
33288
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -33152,6 +33355,14 @@ var LoggingSettingsStateSchema = object({
33152
33355
  channels: array(LogChannelDescriptorSchema).readonly(),
33153
33356
  /** The channels ARMED right now, each with its deadline. */
33154
33357
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
33358
+ /**
33359
+ * The ops-log window in days, as it is in force. `0` = kept forever.
33360
+ *
33361
+ * Reported rather than left to the caller's memory of what it wrote: this is
33362
+ * the value the analytics sweep will actually use, and an operator asking how
33363
+ * long their audit trail lives should not have to infer it from a default.
33364
+ */
33365
+ opsLogRetentionDays: number().int().min(0),
33155
33366
  persisted: boolean()
33156
33367
  });
33157
33368
  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(), {
@@ -33972,7 +34183,7 @@ var StationaryObjectSchema = object({
33972
34183
  /** Enrichment label carried from the source track (identity / plate). */
33973
34184
  label: string().optional(),
33974
34185
  /** Native-resolution key-frame media key for the parked object's best image. */
33975
- keyFrameMediaKey: string().optional()
34186
+ keyFrameMediaId: number().int().optional()
33976
34187
  });
33977
34188
  var CameraOccupancySnapshotSchema = object({
33978
34189
  /** Frame timestamp of the inference result that produced this snapshot. */
@@ -36710,6 +36921,12 @@ Object.freeze({
36710
36921
  addonId: null,
36711
36922
  access: "create"
36712
36923
  },
36924
+ "dataStoreProvider.upsert": {
36925
+ capName: "data-store-provider",
36926
+ capScope: "system",
36927
+ addonId: null,
36928
+ access: "create"
36929
+ },
36713
36930
  "dayNight.getOptions": {
36714
36931
  capName: "day-night",
36715
36932
  capScope: "device",
@@ -39146,6 +39363,12 @@ Object.freeze({
39146
39363
  addonId: null,
39147
39364
  access: "view"
39148
39365
  },
39366
+ "pipelineAnalytics.listRecentSummaries": {
39367
+ capName: "pipeline-analytics",
39368
+ capScope: "device",
39369
+ addonId: null,
39370
+ access: "view"
39371
+ },
39149
39372
  "pipelineAnalytics.listRecentTracks": {
39150
39373
  capName: "pipeline-analytics",
39151
39374
  capScope: "device",
@@ -40574,6 +40797,12 @@ Object.freeze({
40574
40797
  addonId: null,
40575
40798
  access: "create"
40576
40799
  },
40800
+ "settingsStore.upsert": {
40801
+ capName: "settings-store",
40802
+ capScope: "system",
40803
+ addonId: null,
40804
+ access: "create"
40805
+ },
40577
40806
  "smtpProvider.getStatus": {
40578
40807
  capName: "smtp-provider",
40579
40808
  capScope: "system",
@@ -42967,6 +43196,11 @@ Object.freeze({
42967
43196
  form: "single",
42968
43197
  optional: true
42969
43198
  }],
43199
+ "pipelineAnalytics.listRecentSummaries": [{
43200
+ name: "deviceIds",
43201
+ form: "array",
43202
+ optional: false
43203
+ }],
42970
43204
  "pipelineAnalytics.listRecentTracks": [{
42971
43205
  name: "deviceIds",
42972
43206
  form: "array",
package/dist/addon.mjs CHANGED
@@ -818,7 +818,7 @@ async function discoverDevices(options = {}, binder = createDgramBinder()) {
818
818
  return [...byId.values()];
819
819
  }
820
820
  //#endregion
821
- //#region ../types/dist/event-category-DAXzJeTX.mjs
821
+ //#region ../types/dist/event-category-ZyX6jcse.mjs
822
822
  var EventCategory = /* @__PURE__ */ function(EventCategory) {
823
823
  EventCategory["SystemBoot"] = "system.boot";
824
824
  EventCategory["SystemAddonsReady"] = "system.addons-ready";
@@ -1379,7 +1379,7 @@ var EventCategory = /* @__PURE__ */ function(EventCategory) {
1379
1379
  * a package zone — a newly-appeared stationary object of a package class
1380
1380
  * that cleared the class / zone / dwell / size gates. Payload:
1381
1381
  * `PipelineAnalyticsPackageDeliveredPayload` carrying `{ deviceId,
1382
- * entryId, className, zoneIds, keyFrameMediaKey?, bbox, timestamp }`.
1382
+ * entryId, className, zoneIds, keyFrameMediaId?, bbox, timestamp }`.
1383
1383
  * Telemetry (D8): the durable record is the `package-events` store row;
1384
1384
  * this bus topic drives notifier rules + live UI. See
1385
1385
  * docs/superpowers/specs/2026-07-17-package-zones-design.md §5.1.
@@ -6165,7 +6165,7 @@ var ZodIssueCode = {
6165
6165
  var ZodFirstPartyTypeKind;
6166
6166
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6167
6167
  //#endregion
6168
- //#region ../types/dist/sleep-BnujYGPe.mjs
6168
+ //#region ../types/dist/sleep-DBKu2-U5.mjs
6169
6169
  /**
6170
6170
  * The audio chunk plane's byte format, and the ONE expansion from a coded
6171
6171
  * window to float samples (D455).
@@ -8132,13 +8132,30 @@ new Set([
8132
8132
  "scene",
8133
8133
  "motion",
8134
8134
  "object",
8135
- "audio"
8135
+ "audio",
8136
+ "mosaic"
8136
8137
  ]);
8137
- new Set([
8138
+ /**
8139
+ * The owner types that are event tables, in the order the event-media resolver
8140
+ * should consider them. Exported so a consumer asking "is this key an event?"
8141
+ * does not re-spell the list and drift from it.
8142
+ */
8143
+ var EVENT_OWNER_TYPES = [
8138
8144
  "motion",
8139
8145
  "object",
8140
8146
  "audio"
8141
- ]);
8147
+ ];
8148
+ /**
8149
+ * The same list as a Zod enum, for the cap inputs that must NAME the table
8150
+ * (D482: `getEventMedia` / `listEventMedia` take an owner, and an owner is
8151
+ * `(table, id)`).
8152
+ *
8153
+ * Built FROM {@link EVENT_OWNER_TYPES} rather than re-spelled: a fourth event
8154
+ * table would otherwise be accepted by the codec and refused at the door, with
8155
+ * nothing failing until a caller asked.
8156
+ */
8157
+ var EventOwnerTypeSchema = _enum(EVENT_OWNER_TYPES);
8158
+ new Set(EVENT_OWNER_TYPES);
8142
8159
  var EncodeProfileSchema = object({
8143
8160
  video: object({
8144
8161
  codec: _enum([
@@ -8177,7 +8194,23 @@ var EncodeProfileSchema = object({
8177
8194
  "zerolatency",
8178
8195
  "film",
8179
8196
  "animation"
8180
- ]).optional()
8197
+ ]).optional(),
8198
+ /**
8199
+ * ONE slice per access unit.
8200
+ *
8201
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
8202
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
8203
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
8204
+ * frame-end per frame: the first pictures render and the video then freezes
8205
+ * for good while the audio, on its own plane, plays on. Measured on this hub
8206
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
8207
+ *
8208
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
8209
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
8210
+ * consumers meaning the same thing spelled differently would stop sharing one
8211
+ * child. Absent means "whatever the encoder does" — today's behaviour.
8212
+ */
8213
+ singleSlicePerFrame: boolean().optional()
8181
8214
  }),
8182
8215
  audio: union([literal("passthrough"), object({
8183
8216
  codec: _enum([
@@ -8227,7 +8260,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
8227
8260
  audio: "passthrough"
8228
8261
  };
8229
8262
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
8230
- ({ ...BASE_LIVE_EGRESS_PROFILE });
8263
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
8231
8264
  /**
8232
8265
  * Deep wiring healthcheck — snapshot of active reachability probes across
8233
8266
  * every declared capability + widget of every installed plugin, on every
@@ -13337,6 +13370,16 @@ method(object({
13337
13370
  filter: QueryFilterSchema.optional(),
13338
13371
  columns: array(string()).readonly().optional()
13339
13372
  }), array(SettingsRecordSchema).readonly()), method(object({
13373
+ namespace: string().optional(),
13374
+ collection: string(),
13375
+ /** Declared columns identifying the slot; must be covered by a UNIQUE
13376
+ * index on the collection. Never empty. */
13377
+ conflict: array(string()).readonly(),
13378
+ record: BulkRecordSchema
13379
+ }), object({
13380
+ /** The id the row HAS: assigned by SQLite on an insert, or the existing
13381
+ * row's own id on an update. */
13382
+ id: union([string(), number()]) }), { kind: "mutation" }), method(object({
13340
13383
  namespace: string().optional(),
13341
13384
  collection: string(),
13342
13385
  record: object({
@@ -13471,6 +13514,14 @@ method(_void(), EngineInfoSchema, { auth: "admin" }), method(object({
13471
13514
  id: union([string(), number()]) }), {
13472
13515
  kind: "mutation",
13473
13516
  auth: "admin"
13517
+ }), method(object({
13518
+ namespace: string().optional(),
13519
+ collection: string(),
13520
+ conflict: array(string()).readonly(),
13521
+ record: BulkRecordSchema
13522
+ }), object({ id: union([string(), number()]) }), {
13523
+ kind: "mutation",
13524
+ auth: "admin"
13474
13525
  }), method(object({
13475
13526
  namespace: string().optional(),
13476
13527
  collection: string(),
@@ -19970,8 +20021,8 @@ var TrackPositionSchema = object({
19970
20021
  var TrackSnapshotSchema = object({
19971
20022
  timestamp: number(),
19972
20023
  position: TrackPositionSchema,
19973
- /** MediaStore key; resolve via `getTrackMedia({ trackId })`. */
19974
- mediaKey: string()
20024
+ /** The snapshot's media row id (D482); resolve via `listTrackMedia({ trackId })`. */
20025
+ mediaId: number().int()
19975
20026
  });
19976
20027
  /**
19977
20028
  * Normalized 0..1 trajectory envelope (min/max over every position bbox,
@@ -20575,14 +20626,14 @@ var ObjectEventSchema = object({
20575
20626
  * pixel-space `bbox` onto a displayed image. Omitted in slim projection. */
20576
20627
  frameWidth: number().optional(),
20577
20628
  frameHeight: number().optional(),
20578
- /** MediaStore key for the crop attached to this event (if any). */
20579
- mediaKey: string().optional(),
20580
- /** Design B: MediaStore key of the track's native-resolution key frame (the
20629
+ /** Media row id of the crop attached to this event (if any) — D482. */
20630
+ mediaId: number().int().optional(),
20631
+ /** Design B: media row id of the track's native-resolution key frame (the
20581
20632
  * best-detection full frame). Resolve via the event-media data-plane
20582
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`) for a detail view that
20633
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`) for a detail view that
20583
20634
  * draws `bbox` over the native frame. Absent on legacy rows / non-decoded
20584
- * sources — consumers fall back to `mediaKey` (the tight crop). */
20585
- keyFrameMediaKey: string().optional(),
20635
+ * sources — consumers fall back to `mediaId` (the tight crop). */
20636
+ keyFrameMediaId: number().int().optional(),
20586
20637
  /** Populated by B5 (recording playback URL for this event). */
20587
20638
  mediaUrl: string().optional(),
20588
20639
  /** The parent track's key-event importance [0,1], propagated to every object
@@ -20632,7 +20683,7 @@ var MediaFileKindEnum = _enum([
20632
20683
  * hub-main's heap on the way past — for an `<img>` that would have cached it.
20633
20684
  *
20634
20685
  * `url` points at the `event-media` data plane
20635
- * (`/addon/<addonId>/event-media/<storedKey>`), which serves the same blob
20686
+ * (`/addon/<addonId>/event-media/m/<mediaId>` — D482), which serves the same blob
20636
20687
  * with an ETag and `Cache-Control: immutable`, honours conditional GETs, can
20637
20688
  * render a `?variant=thumb`, and streams. The hub gate in front of it requires
20638
20689
  * a bearer or the session cookie (`access: 'authenticated'`), so the bytes are
@@ -20640,10 +20691,13 @@ var MediaFileKindEnum = _enum([
20640
20691
  * `data-plane-access.ts` for the rule and the one gap it does not close
20641
20692
  * (per-device scoping).
20642
20693
  *
20643
- * The URL is built from the row's **stored** key, which is not always its
20644
- * published `kind`: a track's face/plate crop is stored as `crop` under
20694
+ * The URL is built from the row's own id, which since D482 says nothing about
20695
+ * the row's owner or kind — and that is the point. The published `kind` is not
20696
+ * the stored one (a track's face/plate crop is stored as `crop` under
20645
20697
  * `('face'|'plate', '<prefix>-<trackId>')` and published as
20646
- * `faceCrop`/`plateCrop`. `MediaStore.getByKey` knows only the stored key.
20698
+ * `faceCrop`/`plateCrop`), and under the old composite key the URL had to be
20699
+ * taken from `row.key` verbatim or it 404'd exactly those two rows. An id
20700
+ * cannot be mis-reconstructed, because there is nothing in it to reconstruct.
20647
20701
  *
20648
20702
  * ## `base64` is TRANSITIONAL and is going away
20649
20703
  *
@@ -20656,7 +20710,19 @@ var MediaFileKindEnum = _enum([
20656
20710
  * `analytics-query-facade.ts`; nothing else reads it.
20657
20711
  */
20658
20712
  var MediaFileSchema = object({
20659
- key: string(),
20713
+ /**
20714
+ * The media row's OWN id — the rowid SQLite assigned it (D482).
20715
+ *
20716
+ * It used to be the row's composite key, `<ownerType>:<ownerId>:<fileKind>
20717
+ * [:<timestampMs>]`, which embedded a FOREIGN id. D474 made event ids
20718
+ * per-table rowid aliases, so `object:101314` and `motion:101314` are two
20719
+ * different rows that spell the same number, and a track whose
20720
+ * `bestEventId` was an object event was served a motion event's snapshot —
20721
+ * a night-time thumbnail on an afternoon track, with no error anywhere. A
20722
+ * surrogate id cannot name the wrong row's owner, because it names no owner
20723
+ * at all.
20724
+ */
20725
+ mediaId: number().int(),
20660
20726
  kind: MediaFileKindEnum,
20661
20727
  sizeBytes: number(),
20662
20728
  timestamp: number()
@@ -20764,7 +20830,7 @@ var RetrainTrackSchema = object({
20764
20830
  });
20765
20831
  /** A frame the picker may offer — an index row, no blob was read to produce it. */
20766
20832
  var RetrainFrameCandidateSchema = object({
20767
- mediaKey: string(),
20833
+ mediaId: number().int(),
20768
20834
  kind: MediaFileKindEnum,
20769
20835
  timestamp: number(),
20770
20836
  sizeBytes: number().int(),
@@ -20778,7 +20844,7 @@ var RetrainFrameSchema = object({
20778
20844
  deviceId: number(),
20779
20845
  trackId: string(),
20780
20846
  /** Provenance only. It may already point at nothing — that is expected. */
20781
- sourceMediaKey: string(),
20847
+ sourceMediaId: number().int(),
20782
20848
  sourceKind: MediaFileKindEnum,
20783
20849
  sizeBytes: number().int(),
20784
20850
  width: number().int(),
@@ -20794,7 +20860,7 @@ var RetrainCopyRefusalSchema = _enum([
20794
20860
  var RetrainFrameSelectionSchema = object({
20795
20861
  copied: array(RetrainFrameSchema).readonly(),
20796
20862
  refused: array(object({
20797
- sourceMediaKey: string(),
20863
+ sourceMediaId: number().int(),
20798
20864
  reason: RetrainCopyRefusalSchema
20799
20865
  })).readonly()
20800
20866
  });
@@ -20802,7 +20868,7 @@ var RetrainFrameListSchema = object({
20802
20868
  candidates: array(RetrainFrameCandidateSchema).readonly(),
20803
20869
  copies: array(RetrainFrameSchema).readonly(),
20804
20870
  /** What the page pre-selects — the native key frame when one survives. */
20805
- autoPickMediaKey: string().optional()
20871
+ autoPickMediaId: number().int().optional()
20806
20872
  });
20807
20873
  /** What the operator asked the assist to look for. */
20808
20874
  var RetrainAssistSubjectSchema = discriminatedUnion("kind", [object({
@@ -20897,9 +20963,11 @@ var RecentTracksQueryInput = object({
20897
20963
  });
20898
20964
  /**
20899
20965
  * A Summary (D359): the per-camera session envelope that references tracks.
20900
- * `groupShotKey` is the media key of its group shot (`ownerKind: 'summary'`,
20901
- * served by the event-media plane by key), null until the first frame with a
20902
- * member visible has been materialised.
20966
+ * `groupShotMediaId` is the media ROW id of its group shot (`ownerKind:
20967
+ * 'summary'`, served by the event-media plane at `/m/<id>`), null until the
20968
+ * first frame with a member visible has been materialised (D482 — it was the
20969
+ * row's composite key, which embedded the owner and could name another table's
20970
+ * row once event ids became per-table rowids).
20903
20971
  */
20904
20972
  var SummarySchema = object({
20905
20973
  id: string(),
@@ -20908,19 +20976,115 @@ var SummarySchema = object({
20908
20976
  lastActiveAt: number(),
20909
20977
  sealedAt: number().nullable(),
20910
20978
  memberCount: number().int().nonnegative(),
20911
- groupShotKey: string().nullable()
20979
+ groupShotMediaId: number().int().nullable()
20912
20980
  });
20913
20981
  var SummariesQueryInput = object({
20914
20982
  deviceIds: array(number()).min(1),
20915
20983
  since: number().optional(),
20916
20984
  until: number().optional(),
20917
- limit: number().int().min(1).max(200).default(50)
20985
+ limit: number().int().min(1).max(200).default(50),
20986
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20987
+ * meaning of "dog" on every summary surface. */
20988
+ classes: array(string().min(1).max(60)).max(40).optional()
20918
20989
  });
20919
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20920
20990
  var SummaryDetailSchema = object({
20921
20991
  summary: SummarySchema,
20922
- /** Member track ids, in absorption order. */
20923
- trackIds: array(string()).readonly()
20992
+ /** Members in absorption order, oldest join first. */
20993
+ members: array(object({
20994
+ trackId: string(),
20995
+ /**
20996
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20997
+ * and the track no longer exists to fill it.
20998
+ *
20999
+ * The join, not the latest opinion: class voting can re-decide a track's
21000
+ * class afterwards, and a group's composition is the history of who came
21001
+ * together. A consumer counts these to badge a thumbnail — "two people and a
21002
+ * dog" — rather than being handed a second, denormalised tally that would
21003
+ * drift from the members it claims to describe.
21004
+ */
21005
+ className: string().nullable(),
21006
+ /** `primary` is the one member that carries the group's identity. */
21007
+ role: _enum([
21008
+ "primary",
21009
+ "sibling",
21010
+ "child"
21011
+ ]).nullable(),
21012
+ joinedAt: number(),
21013
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
21014
+ * at read time. Absent when nothing resolved, never an empty string. */
21015
+ label: string().optional(),
21016
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
21017
+ * recognised nobody, and absent for a member whose track has been pruned —
21018
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
21019
+ subLabel: string().optional()
21020
+ })).readonly()
21021
+ });
21022
+ /**
21023
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
21024
+ *
21025
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
21026
+ * we extend when we notice we need more". Enough to draw the card ("two people
21027
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
21028
+ * are on {@link getSummary}: shipping every member of every group so a client
21029
+ * can count them turns a page of two hundred groups into a thousand records to
21030
+ * draw two hundred badges.
21031
+ */
21032
+ var SummaryListItemSchema = object({
21033
+ summary: SummarySchema,
21034
+ /** Class counts, most numerous first; `className: null` is a member whose
21035
+ * class never resolved, counted so the counts add up to `memberCount`. */
21036
+ classes: array(object({
21037
+ className: string().nullable(),
21038
+ count: number().int().positive()
21039
+ })).readonly()
21040
+ });
21041
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
21042
+ /**
21043
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
21044
+ *
21045
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
21046
+ * summaries feed that could only answer "the last N for these cameras"
21047
+ * (`listSummaries`) would make groups available in the timeline and nowhere
21048
+ * else. Two feeds with different ordering contracts would be two ideas of what
21049
+ * "newest" means, on one screen.
21050
+ */
21051
+ var RecentSummariesQueryInput = object({
21052
+ /** Devices to merge. An empty array yields an empty page. */
21053
+ deviceIds: array(number()),
21054
+ /** Window lower bound on `lastActiveAt` (inclusive). */
21055
+ since: number().optional(),
21056
+ /** Window upper bound on `lastActiveAt` (inclusive). */
21057
+ until: number().optional(),
21058
+ limit: number().int().min(1).max(500).default(100),
21059
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
21060
+ cursor: string().optional(),
21061
+ /**
21062
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
21063
+ * deliberately the same name: the events filter tree emits one list of ticked
21064
+ * taxonomy nodes, and a summaries feed that called it something else would
21065
+ * make the operator's one filter component produce two shapes.
21066
+ *
21067
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
21068
+ * me the arrivals with a dog" has to return the group of four people who had
21069
+ * one with them; a filter on the group's own fields could only ever answer
21070
+ * about its primary.
21071
+ *
21072
+ * Matched against all THREE tiers of a member — the class (`animal`), the
21073
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
21074
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
21075
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
21076
+ * that read only the class would answer "animal" and never "dog".
21077
+ *
21078
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
21079
+ */
21080
+ classes: array(string().min(1).max(60)).max(40).optional()
21081
+ });
21082
+ var RecentSummariesPageSchema = object({
21083
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
21084
+ * {@link listSummaries} — one list shape, so a card renders the same
21085
+ * whichever feed drew it. */
21086
+ summaries: array(SummaryListItemSchema).readonly(),
21087
+ nextCursor: string().nullable()
20924
21088
  });
20925
21089
  var RecentTracksPageSchema = object({
20926
21090
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -21334,7 +21498,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21334
21498
  classes: array(string()).optional(),
21335
21499
  /** See {@link ExcludeSourcesDoc}. */
21336
21500
  excludeSources: array(TrackSourceSchema).optional()
21337
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
21501
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
21338
21502
  kind: "mutation",
21339
21503
  auth: "admin"
21340
21504
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -21462,7 +21626,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21462
21626
  }), method(object({
21463
21627
  deviceId: number(),
21464
21628
  trackId: string(),
21465
- mediaKeys: array(string()).min(1)
21629
+ mediaIds: array(number().int()).min(1)
21466
21630
  }), RetrainFrameSelectionSchema, {
21467
21631
  kind: "mutation",
21468
21632
  auth: "admin"
@@ -21530,7 +21694,10 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21530
21694
  kind: "query",
21531
21695
  auth: "admin"
21532
21696
  }), method(object({
21533
- eventId: string(),
21697
+ /** Which of the three event tables owns the row. */
21698
+ ownerType: EventOwnerTypeSchema,
21699
+ /** The event's rowid in that table (D474: an event id is a number). */
21700
+ eventId: number().int(),
21534
21701
  kind: MediaFileKindEnum.optional(),
21535
21702
  deviceId: number()
21536
21703
  }), array(MediaFileSchema).readonly()), method(object({
@@ -21541,7 +21708,8 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21541
21708
  trackId: string(),
21542
21709
  deviceId: number()
21543
21710
  }), array(MediaFileInfoSchema).readonly()), method(object({
21544
- eventId: string(),
21711
+ ownerType: EventOwnerTypeSchema,
21712
+ eventId: number().int(),
21545
21713
  deviceId: number()
21546
21714
  }), array(MediaFileInfoSchema).readonly()), method(SearchObjectEventsInput, array(ScoredObjectEventSchema).readonly()), method(object({}), WipeObjectEmbeddingsResultSchema, {
21547
21715
  kind: "mutation",
@@ -26742,8 +26910,8 @@ var IdentitySchema = object({
26742
26910
  id: string(),
26743
26911
  name: string(),
26744
26912
  sampleCount: number().int().nonnegative(),
26745
- coverMediaKey: string().optional(),
26746
- /** Inline base64 of the cover sample's crop, resolved from `coverMediaKey`.
26913
+ coverMediaId: number().int().optional(),
26914
+ /** Inline base64 of the cover sample's crop, resolved from `coverMediaId`.
26747
26915
  * Lets the UI render the person's face avatar instead of a placeholder icon. */
26748
26916
  coverBase64: string().optional()
26749
26917
  });
@@ -26769,8 +26937,8 @@ var IdentitySampleAuditRowSchema = object({
26769
26937
  enrolledAt: number().int(),
26770
26938
  deviceId: number().int().optional(),
26771
26939
  source: _enum(["detected", "photo"]),
26772
- /** The enrolled crop's media key, so the panel can show the evidence. */
26773
- mediaKey: string().optional(),
26940
+ /** The enrolled crop's media row id, so the panel can show the evidence. */
26941
+ mediaId: number().int().optional(),
26774
26942
  /**
26775
26943
  * False = the sample is stamped with a DIFFERENT face model, and a cosine
26776
26944
  * across feature spaces is a well-formed float with no meaning. Every number
@@ -26839,15 +27007,15 @@ var FaceInfoSchema = object({
26839
27007
  */
26840
27008
  cropUrl: string().optional(),
26841
27009
  /** Design B: the face bbox (pixel space) on the key frame — lets a detail
26842
- * view draw the box over the native `keyFrameMediaKey` frame. Absent on
27010
+ * view draw the box over the native `keyFrameMediaId` frame. Absent on
26843
27011
  * legacy rows written before design B. */
26844
27012
  faceBbox: BoundingBoxSchema.optional(),
26845
27013
  /** Design B: MediaStore key of the track's native-resolution key frame.
26846
27014
  * Fetch the native JPEG via the event-media data-plane
26847
- * (`/addon/<addonId>/event-media/<keyFrameMediaKey>`). Absent when the
27015
+ * (`/addon/<addonId>/event-media/m/<keyFrameMediaId>`). Absent when the
26848
27016
  * track produced no key frame (e.g. native/onboard source) — the UI falls
26849
27017
  * back to the inline `base64` face crop. */
26850
- keyFrameMediaKey: string().optional(),
27018
+ keyFrameMediaId: number().int().optional(),
26851
27019
  /** Winning identity-match cosine (0..1) for this face's track, when an
26852
27020
  * identity was auto-confirmed. Lets the UI surface WHY a face was assigned
26853
27021
  * (confidence badge / low-confidence audit). Absent on legacy rows and on
@@ -26916,11 +27084,14 @@ var FaceClusterSchema = object({
26916
27084
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
26917
27085
  * dialog already rendering its key FRAME from the `event-media` plane.
26918
27086
  *
26919
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
26920
- * media key directly, so this needed no new plane and no new access decision.
27087
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
27088
+ * resolves a media row by its own id, so this needed no new plane and no new
27089
+ * access decision.
26921
27090
  */
26922
27091
  var MediaFileLiteSchema$1 = object({
26923
- key: string(),
27092
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
27093
+ * (D482). It was the row's composite key, which embedded the owner. */
27094
+ mediaId: number().int(),
26924
27095
  kind: string(),
26925
27096
  url: string(),
26926
27097
  sizeBytes: number(),
@@ -26937,7 +27108,7 @@ method(object({
26937
27108
  * covers inline, ~10 KB of base64 per row, on a query this UI mounts
26938
27109
  * four times and the viewer holds at `staleTime: 30_000`.
26939
27110
  *
26940
- * Nothing loses its avatar: `coverMediaKey` is already on every row and
27111
+ * Nothing loses its avatar: `coverMediaId` is already on every row and
26941
27112
  * the `event-media` plane serves that key `immutable` with an ETag.
26942
27113
  *
26943
27114
  * **This is an INPUT field, so it does not reach the addon until the
@@ -30300,8 +30471,8 @@ var VehicleSchema = object({
30300
30471
  id: string(),
30301
30472
  name: string(),
30302
30473
  sampleCount: number().int().nonnegative(),
30303
- coverMediaKey: string().optional(),
30304
- /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaKey`. */
30474
+ coverMediaId: number().int().optional(),
30475
+ /** Inline base64 of the cover sample's plate crop, resolved from `coverMediaId`. */
30305
30476
  coverBase64: string().optional()
30306
30477
  });
30307
30478
  var VehicleSampleInfoSchema = object({
@@ -30334,7 +30505,7 @@ var PlateInfoSchema = object({
30334
30505
  /** keyFrame parity: the plate bbox (pixel space) on the native key frame. */
30335
30506
  plateBbox: BoundingBoxSchema.optional(),
30336
30507
  /** keyFrame parity: MediaStore key of the track's native-resolution key frame. */
30337
- keyFrameMediaKey: string().optional(),
30508
+ keyFrameMediaId: number().int().optional(),
30338
30509
  base64: string().optional(),
30339
30510
  /**
30340
30511
  * Same crop as a data-plane URL, always present when the plate has a stored
@@ -30355,11 +30526,14 @@ var PlateInfoSchema = object({
30355
30526
  * `data:image/jpeg;base64,…` from a row whose `key` sat right beside it, in a
30356
30527
  * dialog already rendering its key FRAME from the `event-media` plane.
30357
30528
  *
30358
- * `url` is `/addon/<addonId>/event-media/<encoded key>`. The plane resolves a
30359
- * media key directly, so this needed no new plane and no new access decision.
30529
+ * `url` is `/addon/<addonId>/event-media/m/<mediaId>` (D482). The plane
30530
+ * resolves a media row by its own id, so this needed no new plane and no new
30531
+ * access decision.
30360
30532
  */
30361
30533
  var MediaFileLiteSchema = object({
30362
- key: string(),
30534
+ /** The media row's own id — the same address `MediaFileRef.mediaId` carries
30535
+ * (D482). It was the row's composite key, which embedded the owner. */
30536
+ mediaId: number().int(),
30363
30537
  kind: string(),
30364
30538
  url: string(),
30365
30539
  sizeBytes: number(),
@@ -30418,7 +30592,7 @@ method(object({
30418
30592
  }), method(object({
30419
30593
  /** Inline {@link VehicleSchema.coverBase64} on every row. Default
30420
30594
  * `false` — the vehicle twin of `faceGallery.listIdentities`'s
30421
- * option; `coverMediaKey` + the `event-media` plane carry the picture.
30595
+ * option; `coverMediaId` + the `event-media` plane carry the picture.
30422
30596
  * INPUT field: stripped by the hub router until the train ships, which
30423
30597
  * resolves to `false` and is exactly the intended default. */
30424
30598
  includeCrops: boolean().optional() }).optional(), array(VehicleSchema).readonly()), method(object({ name: string().min(1) }), VehicleSchema, {
@@ -31987,7 +32161,14 @@ var SceneReferenceSchema = object({
31987
32161
  modelId: string(),
31988
32162
  condition: SceneConditionSchema,
31989
32163
  capturedAt: number(),
31990
- thumbnailMediaId: string().optional(),
32164
+ /**
32165
+ * The reference thumbnail's media ROW id (D482).
32166
+ *
32167
+ * The name already said `mediaId` while the value was the row's composite
32168
+ * key — a leftover that read as done and was not. It is the row's own id
32169
+ * now, addressed at `/addon/<addonId>/event-media/m/<id>`.
32170
+ */
32171
+ thumbnailMediaId: number().int().optional(),
31991
32172
  /** Whole-frame (downscaled) embedding captured alongside the ROI crop. The
31992
32173
  * anti-view-shift anchor: a bumped camera, a PTZ preset or a re-aim makes the
31993
32174
  * normalized rect frame a different piece of world, and the scene would
@@ -33078,7 +33259,29 @@ var LoggingSettingsPatchSchema = object({
33078
33259
  * disarmed the channels it did not mention would make the Levels page and
33079
33260
  * the Diagnostics page fight over the same value.
33080
33261
  */
33081
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
33262
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
33263
+ /**
33264
+ * How many days the analytics OPS LOG is kept — the audit trail of what
33265
+ * retention actually deleted.
33266
+ *
33267
+ * It lives HERE, on the logging document, and not on the analytics addon's
33268
+ * settings form, on the operator's instruction: every other log-ish window in
33269
+ * this system is configured through this one document, and a retention audit
33270
+ * configured somewhere else is a second place to look when the question is
33271
+ * "what happened to my data".
33272
+ *
33273
+ * That placement has a cost worth stating: this document ships in the
33274
+ * framework closure, so this knob needs a published `@camstack/server` while
33275
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
33276
+ *
33277
+ * `retention-model.ts` argued this one must NOT follow a device window,
33278
+ * because the audit trail is the history of the SWEEP and not of the camera.
33279
+ * That argument stands and is untouched — what changes is that 30 days stops
33280
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
33281
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
33282
+ * state in which the next 9.5 GiB leak cannot be explained.
33283
+ */
33284
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
33082
33285
  });
33083
33286
  /**
33084
33287
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -33151,6 +33354,14 @@ var LoggingSettingsStateSchema = object({
33151
33354
  channels: array(LogChannelDescriptorSchema).readonly(),
33152
33355
  /** The channels ARMED right now, each with its deadline. */
33153
33356
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
33357
+ /**
33358
+ * The ops-log window in days, as it is in force. `0` = kept forever.
33359
+ *
33360
+ * Reported rather than left to the caller's memory of what it wrote: this is
33361
+ * the value the analytics sweep will actually use, and an operator asking how
33362
+ * long their audit trail lives should not have to infer it from a default.
33363
+ */
33364
+ opsLogRetentionDays: number().int().min(0),
33154
33365
  persisted: boolean()
33155
33366
  });
33156
33367
  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(), {
@@ -33971,7 +34182,7 @@ var StationaryObjectSchema = object({
33971
34182
  /** Enrichment label carried from the source track (identity / plate). */
33972
34183
  label: string().optional(),
33973
34184
  /** Native-resolution key-frame media key for the parked object's best image. */
33974
- keyFrameMediaKey: string().optional()
34185
+ keyFrameMediaId: number().int().optional()
33975
34186
  });
33976
34187
  var CameraOccupancySnapshotSchema = object({
33977
34188
  /** Frame timestamp of the inference result that produced this snapshot. */
@@ -36709,6 +36920,12 @@ Object.freeze({
36709
36920
  addonId: null,
36710
36921
  access: "create"
36711
36922
  },
36923
+ "dataStoreProvider.upsert": {
36924
+ capName: "data-store-provider",
36925
+ capScope: "system",
36926
+ addonId: null,
36927
+ access: "create"
36928
+ },
36712
36929
  "dayNight.getOptions": {
36713
36930
  capName: "day-night",
36714
36931
  capScope: "device",
@@ -39145,6 +39362,12 @@ Object.freeze({
39145
39362
  addonId: null,
39146
39363
  access: "view"
39147
39364
  },
39365
+ "pipelineAnalytics.listRecentSummaries": {
39366
+ capName: "pipeline-analytics",
39367
+ capScope: "device",
39368
+ addonId: null,
39369
+ access: "view"
39370
+ },
39148
39371
  "pipelineAnalytics.listRecentTracks": {
39149
39372
  capName: "pipeline-analytics",
39150
39373
  capScope: "device",
@@ -40573,6 +40796,12 @@ Object.freeze({
40573
40796
  addonId: null,
40574
40797
  access: "create"
40575
40798
  },
40799
+ "settingsStore.upsert": {
40800
+ capName: "settings-store",
40801
+ capScope: "system",
40802
+ addonId: null,
40803
+ access: "create"
40804
+ },
40576
40805
  "smtpProvider.getStatus": {
40577
40806
  capName: "smtp-provider",
40578
40807
  capScope: "system",
@@ -42966,6 +43195,11 @@ Object.freeze({
42966
43195
  form: "single",
42967
43196
  optional: true
42968
43197
  }],
43198
+ "pipelineAnalytics.listRecentSummaries": [{
43199
+ name: "deviceIds",
43200
+ form: "array",
43201
+ optional: false
43202
+ }],
42969
43203
  "pipelineAnalytics.listRecentTracks": [{
42970
43204
  name: "deviceIds",
42971
43205
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-tuya",
3
- "version": "0.2.99",
3
+ "version": "0.2.101",
4
4
  "description": "Tuya / Smart Life device-provider addon for CamStack — account-onboarded (Tuya IoT cloud fetch of device localKeys) + LOCAL DP control via the @apocaliss92/nodetuya encrypted-LAN client, exposing switch / water-heater-family kettle entities",
5
5
  "keywords": [
6
6
  "camstack",