@camstack/addon-provider-unifi 0.2.100 → 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.
package/dist/addon.js CHANGED
@@ -5346,7 +5346,7 @@ var ZodIssueCode = {
5346
5346
  var ZodFirstPartyTypeKind;
5347
5347
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5348
5348
  //#endregion
5349
- //#region ../types/dist/sleep-CnLyvg3w.mjs
5349
+ //#region ../types/dist/sleep-DBKu2-U5.mjs
5350
5350
  /**
5351
5351
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5352
5352
  * window to float samples (D455).
@@ -7375,7 +7375,23 @@ var EncodeProfileSchema = object({
7375
7375
  "zerolatency",
7376
7376
  "film",
7377
7377
  "animation"
7378
- ]).optional()
7378
+ ]).optional(),
7379
+ /**
7380
+ * ONE slice per access unit.
7381
+ *
7382
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7383
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7384
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7385
+ * frame-end per frame: the first pictures render and the video then freezes
7386
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7387
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7388
+ *
7389
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7390
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7391
+ * consumers meaning the same thing spelled differently would stop sharing one
7392
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7393
+ */
7394
+ singleSlicePerFrame: boolean().optional()
7379
7395
  }),
7380
7396
  audio: union([literal("passthrough"), object({
7381
7397
  codec: _enum([
@@ -7425,7 +7441,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7425
7441
  audio: "passthrough"
7426
7442
  };
7427
7443
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7428
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7444
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7429
7445
  /**
7430
7446
  * Deep wiring healthcheck — snapshot of active reachability probes across
7431
7447
  * every declared capability + widget of every installed plugin, on every
@@ -20130,13 +20146,109 @@ var SummariesQueryInput = object({
20130
20146
  deviceIds: array(number()).min(1),
20131
20147
  since: number().optional(),
20132
20148
  until: number().optional(),
20133
- limit: number().int().min(1).max(200).default(50)
20149
+ limit: number().int().min(1).max(200).default(50),
20150
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20151
+ * meaning of "dog" on every summary surface. */
20152
+ classes: array(string().min(1).max(60)).max(40).optional()
20134
20153
  });
20135
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20136
20154
  var SummaryDetailSchema = object({
20137
20155
  summary: SummarySchema,
20138
- /** Member track ids, in absorption order. */
20139
- trackIds: array(string()).readonly()
20156
+ /** Members in absorption order, oldest join first. */
20157
+ members: array(object({
20158
+ trackId: string(),
20159
+ /**
20160
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20161
+ * and the track no longer exists to fill it.
20162
+ *
20163
+ * The join, not the latest opinion: class voting can re-decide a track's
20164
+ * class afterwards, and a group's composition is the history of who came
20165
+ * together. A consumer counts these to badge a thumbnail — "two people and a
20166
+ * dog" — rather than being handed a second, denormalised tally that would
20167
+ * drift from the members it claims to describe.
20168
+ */
20169
+ className: string().nullable(),
20170
+ /** `primary` is the one member that carries the group's identity. */
20171
+ role: _enum([
20172
+ "primary",
20173
+ "sibling",
20174
+ "child"
20175
+ ]).nullable(),
20176
+ joinedAt: number(),
20177
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
20178
+ * at read time. Absent when nothing resolved, never an empty string. */
20179
+ label: string().optional(),
20180
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
20181
+ * recognised nobody, and absent for a member whose track has been pruned —
20182
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
20183
+ subLabel: string().optional()
20184
+ })).readonly()
20185
+ });
20186
+ /**
20187
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
20188
+ *
20189
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
20190
+ * we extend when we notice we need more". Enough to draw the card ("two people
20191
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
20192
+ * are on {@link getSummary}: shipping every member of every group so a client
20193
+ * can count them turns a page of two hundred groups into a thousand records to
20194
+ * draw two hundred badges.
20195
+ */
20196
+ var SummaryListItemSchema = object({
20197
+ summary: SummarySchema,
20198
+ /** Class counts, most numerous first; `className: null` is a member whose
20199
+ * class never resolved, counted so the counts add up to `memberCount`. */
20200
+ classes: array(object({
20201
+ className: string().nullable(),
20202
+ count: number().int().positive()
20203
+ })).readonly()
20204
+ });
20205
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
20206
+ /**
20207
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
20208
+ *
20209
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
20210
+ * summaries feed that could only answer "the last N for these cameras"
20211
+ * (`listSummaries`) would make groups available in the timeline and nowhere
20212
+ * else. Two feeds with different ordering contracts would be two ideas of what
20213
+ * "newest" means, on one screen.
20214
+ */
20215
+ var RecentSummariesQueryInput = object({
20216
+ /** Devices to merge. An empty array yields an empty page. */
20217
+ deviceIds: array(number()),
20218
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20219
+ since: number().optional(),
20220
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20221
+ until: number().optional(),
20222
+ limit: number().int().min(1).max(500).default(100),
20223
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20224
+ cursor: string().optional(),
20225
+ /**
20226
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20227
+ * deliberately the same name: the events filter tree emits one list of ticked
20228
+ * taxonomy nodes, and a summaries feed that called it something else would
20229
+ * make the operator's one filter component produce two shapes.
20230
+ *
20231
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20232
+ * me the arrivals with a dog" has to return the group of four people who had
20233
+ * one with them; a filter on the group's own fields could only ever answer
20234
+ * about its primary.
20235
+ *
20236
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20237
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20238
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20239
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20240
+ * that read only the class would answer "animal" and never "dog".
20241
+ *
20242
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20243
+ */
20244
+ classes: array(string().min(1).max(60)).max(40).optional()
20245
+ });
20246
+ var RecentSummariesPageSchema = object({
20247
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20248
+ * {@link listSummaries} — one list shape, so a card renders the same
20249
+ * whichever feed drew it. */
20250
+ summaries: array(SummaryListItemSchema).readonly(),
20251
+ nextCursor: string().nullable()
20140
20252
  });
20141
20253
  var RecentTracksPageSchema = object({
20142
20254
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20550,7 +20662,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20550
20662
  classes: array(string()).optional(),
20551
20663
  /** See {@link ExcludeSourcesDoc}. */
20552
20664
  excludeSources: array(TrackSourceSchema).optional()
20553
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20665
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20554
20666
  kind: "mutation",
20555
20667
  auth: "admin"
20556
20668
  }), 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({
@@ -32320,7 +32432,29 @@ var LoggingSettingsPatchSchema = object({
32320
32432
  * disarmed the channels it did not mention would make the Levels page and
32321
32433
  * the Diagnostics page fight over the same value.
32322
32434
  */
32323
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
32435
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
32436
+ /**
32437
+ * How many days the analytics OPS LOG is kept — the audit trail of what
32438
+ * retention actually deleted.
32439
+ *
32440
+ * It lives HERE, on the logging document, and not on the analytics addon's
32441
+ * settings form, on the operator's instruction: every other log-ish window in
32442
+ * this system is configured through this one document, and a retention audit
32443
+ * configured somewhere else is a second place to look when the question is
32444
+ * "what happened to my data".
32445
+ *
32446
+ * That placement has a cost worth stating: this document ships in the
32447
+ * framework closure, so this knob needs a published `@camstack/server` while
32448
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
32449
+ *
32450
+ * `retention-model.ts` argued this one must NOT follow a device window,
32451
+ * because the audit trail is the history of the SWEEP and not of the camera.
32452
+ * That argument stands and is untouched — what changes is that 30 days stops
32453
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
32454
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
32455
+ * state in which the next 9.5 GiB leak cannot be explained.
32456
+ */
32457
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
32324
32458
  });
32325
32459
  /**
32326
32460
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -32393,6 +32527,14 @@ var LoggingSettingsStateSchema = object({
32393
32527
  channels: array(LogChannelDescriptorSchema).readonly(),
32394
32528
  /** The channels ARMED right now, each with its deadline. */
32395
32529
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
32530
+ /**
32531
+ * The ops-log window in days, as it is in force. `0` = kept forever.
32532
+ *
32533
+ * Reported rather than left to the caller's memory of what it wrote: this is
32534
+ * the value the analytics sweep will actually use, and an operator asking how
32535
+ * long their audit trail lives should not have to infer it from a default.
32536
+ */
32537
+ opsLogRetentionDays: number().int().min(0),
32396
32538
  persisted: boolean()
32397
32539
  });
32398
32540
  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(), {
@@ -38393,6 +38535,12 @@ Object.freeze({
38393
38535
  addonId: null,
38394
38536
  access: "view"
38395
38537
  },
38538
+ "pipelineAnalytics.listRecentSummaries": {
38539
+ capName: "pipeline-analytics",
38540
+ capScope: "device",
38541
+ addonId: null,
38542
+ access: "view"
38543
+ },
38396
38544
  "pipelineAnalytics.listRecentTracks": {
38397
38545
  capName: "pipeline-analytics",
38398
38546
  capScope: "device",
@@ -42220,6 +42368,11 @@ Object.freeze({
42220
42368
  form: "single",
42221
42369
  optional: true
42222
42370
  }],
42371
+ "pipelineAnalytics.listRecentSummaries": [{
42372
+ name: "deviceIds",
42373
+ form: "array",
42374
+ optional: false
42375
+ }],
42223
42376
  "pipelineAnalytics.listRecentTracks": [{
42224
42377
  name: "deviceIds",
42225
42378
  form: "array",
package/dist/addon.mjs CHANGED
@@ -5345,7 +5345,7 @@ var ZodIssueCode = {
5345
5345
  var ZodFirstPartyTypeKind;
5346
5346
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5347
5347
  //#endregion
5348
- //#region ../types/dist/sleep-CnLyvg3w.mjs
5348
+ //#region ../types/dist/sleep-DBKu2-U5.mjs
5349
5349
  /**
5350
5350
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5351
5351
  * window to float samples (D455).
@@ -7374,7 +7374,23 @@ var EncodeProfileSchema = object({
7374
7374
  "zerolatency",
7375
7375
  "film",
7376
7376
  "animation"
7377
- ]).optional()
7377
+ ]).optional(),
7378
+ /**
7379
+ * ONE slice per access unit.
7380
+ *
7381
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7382
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7383
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7384
+ * frame-end per frame: the first pictures render and the video then freezes
7385
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7386
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7387
+ *
7388
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7389
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7390
+ * consumers meaning the same thing spelled differently would stop sharing one
7391
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7392
+ */
7393
+ singleSlicePerFrame: boolean().optional()
7378
7394
  }),
7379
7395
  audio: union([literal("passthrough"), object({
7380
7396
  codec: _enum([
@@ -7424,7 +7440,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7424
7440
  audio: "passthrough"
7425
7441
  };
7426
7442
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7427
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7443
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7428
7444
  /**
7429
7445
  * Deep wiring healthcheck — snapshot of active reachability probes across
7430
7446
  * every declared capability + widget of every installed plugin, on every
@@ -20129,13 +20145,109 @@ var SummariesQueryInput = object({
20129
20145
  deviceIds: array(number()).min(1),
20130
20146
  since: number().optional(),
20131
20147
  until: number().optional(),
20132
- limit: number().int().min(1).max(200).default(50)
20148
+ limit: number().int().min(1).max(200).default(50),
20149
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20150
+ * meaning of "dog" on every summary surface. */
20151
+ classes: array(string().min(1).max(60)).max(40).optional()
20133
20152
  });
20134
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20135
20153
  var SummaryDetailSchema = object({
20136
20154
  summary: SummarySchema,
20137
- /** Member track ids, in absorption order. */
20138
- trackIds: array(string()).readonly()
20155
+ /** Members in absorption order, oldest join first. */
20156
+ members: array(object({
20157
+ trackId: string(),
20158
+ /**
20159
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20160
+ * and the track no longer exists to fill it.
20161
+ *
20162
+ * The join, not the latest opinion: class voting can re-decide a track's
20163
+ * class afterwards, and a group's composition is the history of who came
20164
+ * together. A consumer counts these to badge a thumbnail — "two people and a
20165
+ * dog" — rather than being handed a second, denormalised tally that would
20166
+ * drift from the members it claims to describe.
20167
+ */
20168
+ className: string().nullable(),
20169
+ /** `primary` is the one member that carries the group's identity. */
20170
+ role: _enum([
20171
+ "primary",
20172
+ "sibling",
20173
+ "child"
20174
+ ]).nullable(),
20175
+ joinedAt: number(),
20176
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
20177
+ * at read time. Absent when nothing resolved, never an empty string. */
20178
+ label: string().optional(),
20179
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
20180
+ * recognised nobody, and absent for a member whose track has been pruned —
20181
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
20182
+ subLabel: string().optional()
20183
+ })).readonly()
20184
+ });
20185
+ /**
20186
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
20187
+ *
20188
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
20189
+ * we extend when we notice we need more". Enough to draw the card ("two people
20190
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
20191
+ * are on {@link getSummary}: shipping every member of every group so a client
20192
+ * can count them turns a page of two hundred groups into a thousand records to
20193
+ * draw two hundred badges.
20194
+ */
20195
+ var SummaryListItemSchema = object({
20196
+ summary: SummarySchema,
20197
+ /** Class counts, most numerous first; `className: null` is a member whose
20198
+ * class never resolved, counted so the counts add up to `memberCount`. */
20199
+ classes: array(object({
20200
+ className: string().nullable(),
20201
+ count: number().int().positive()
20202
+ })).readonly()
20203
+ });
20204
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
20205
+ /**
20206
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
20207
+ *
20208
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
20209
+ * summaries feed that could only answer "the last N for these cameras"
20210
+ * (`listSummaries`) would make groups available in the timeline and nowhere
20211
+ * else. Two feeds with different ordering contracts would be two ideas of what
20212
+ * "newest" means, on one screen.
20213
+ */
20214
+ var RecentSummariesQueryInput = object({
20215
+ /** Devices to merge. An empty array yields an empty page. */
20216
+ deviceIds: array(number()),
20217
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20218
+ since: number().optional(),
20219
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20220
+ until: number().optional(),
20221
+ limit: number().int().min(1).max(500).default(100),
20222
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20223
+ cursor: string().optional(),
20224
+ /**
20225
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20226
+ * deliberately the same name: the events filter tree emits one list of ticked
20227
+ * taxonomy nodes, and a summaries feed that called it something else would
20228
+ * make the operator's one filter component produce two shapes.
20229
+ *
20230
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20231
+ * me the arrivals with a dog" has to return the group of four people who had
20232
+ * one with them; a filter on the group's own fields could only ever answer
20233
+ * about its primary.
20234
+ *
20235
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20236
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20237
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20238
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20239
+ * that read only the class would answer "animal" and never "dog".
20240
+ *
20241
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20242
+ */
20243
+ classes: array(string().min(1).max(60)).max(40).optional()
20244
+ });
20245
+ var RecentSummariesPageSchema = object({
20246
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20247
+ * {@link listSummaries} — one list shape, so a card renders the same
20248
+ * whichever feed drew it. */
20249
+ summaries: array(SummaryListItemSchema).readonly(),
20250
+ nextCursor: string().nullable()
20139
20251
  });
20140
20252
  var RecentTracksPageSchema = object({
20141
20253
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20549,7 +20661,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20549
20661
  classes: array(string()).optional(),
20550
20662
  /** See {@link ExcludeSourcesDoc}. */
20551
20663
  excludeSources: array(TrackSourceSchema).optional()
20552
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20664
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20553
20665
  kind: "mutation",
20554
20666
  auth: "admin"
20555
20667
  }), 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({
@@ -32319,7 +32431,29 @@ var LoggingSettingsPatchSchema = object({
32319
32431
  * disarmed the channels it did not mention would make the Levels page and
32320
32432
  * the Diagnostics page fight over the same value.
32321
32433
  */
32322
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
32434
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
32435
+ /**
32436
+ * How many days the analytics OPS LOG is kept — the audit trail of what
32437
+ * retention actually deleted.
32438
+ *
32439
+ * It lives HERE, on the logging document, and not on the analytics addon's
32440
+ * settings form, on the operator's instruction: every other log-ish window in
32441
+ * this system is configured through this one document, and a retention audit
32442
+ * configured somewhere else is a second place to look when the question is
32443
+ * "what happened to my data".
32444
+ *
32445
+ * That placement has a cost worth stating: this document ships in the
32446
+ * framework closure, so this knob needs a published `@camstack/server` while
32447
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
32448
+ *
32449
+ * `retention-model.ts` argued this one must NOT follow a device window,
32450
+ * because the audit trail is the history of the SWEEP and not of the camera.
32451
+ * That argument stands and is untouched — what changes is that 30 days stops
32452
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
32453
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
32454
+ * state in which the next 9.5 GiB leak cannot be explained.
32455
+ */
32456
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
32323
32457
  });
32324
32458
  /**
32325
32459
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -32392,6 +32526,14 @@ var LoggingSettingsStateSchema = object({
32392
32526
  channels: array(LogChannelDescriptorSchema).readonly(),
32393
32527
  /** The channels ARMED right now, each with its deadline. */
32394
32528
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
32529
+ /**
32530
+ * The ops-log window in days, as it is in force. `0` = kept forever.
32531
+ *
32532
+ * Reported rather than left to the caller's memory of what it wrote: this is
32533
+ * the value the analytics sweep will actually use, and an operator asking how
32534
+ * long their audit trail lives should not have to infer it from a default.
32535
+ */
32536
+ opsLogRetentionDays: number().int().min(0),
32395
32537
  persisted: boolean()
32396
32538
  });
32397
32539
  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(), {
@@ -38392,6 +38534,12 @@ Object.freeze({
38392
38534
  addonId: null,
38393
38535
  access: "view"
38394
38536
  },
38537
+ "pipelineAnalytics.listRecentSummaries": {
38538
+ capName: "pipeline-analytics",
38539
+ capScope: "device",
38540
+ addonId: null,
38541
+ access: "view"
38542
+ },
38395
38543
  "pipelineAnalytics.listRecentTracks": {
38396
38544
  capName: "pipeline-analytics",
38397
38545
  capScope: "device",
@@ -42219,6 +42367,11 @@ Object.freeze({
42219
42367
  form: "single",
42220
42368
  optional: true
42221
42369
  }],
42370
+ "pipelineAnalytics.listRecentSummaries": [{
42371
+ name: "deviceIds",
42372
+ form: "array",
42373
+ optional: false
42374
+ }],
42222
42375
  "pipelineAnalytics.listRecentTracks": [{
42223
42376
  name: "deviceIds",
42224
42377
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-unifi",
3
- "version": "0.2.100",
3
+ "version": "0.2.101",
4
4
  "description": "UniFi Network controller device-provider addon for CamStack — local-controller infra switches/APs (as containers) + network-client presence. NO cameras/Protect.",
5
5
  "keywords": [
6
6
  "camstack",