@camstack/addon-terminal 0.1.105 → 0.1.106

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/addon.js CHANGED
@@ -5378,7 +5378,7 @@ var ZodIssueCode = {
5378
5378
  var ZodFirstPartyTypeKind;
5379
5379
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5380
5380
  //#endregion
5381
- //#region ../types/dist/sleep-CnLyvg3w.mjs
5381
+ //#region ../types/dist/sleep-DBKu2-U5.mjs
5382
5382
  /**
5383
5383
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5384
5384
  * window to float samples (D455).
@@ -7466,7 +7466,23 @@ var EncodeProfileSchema = object({
7466
7466
  "zerolatency",
7467
7467
  "film",
7468
7468
  "animation"
7469
- ]).optional()
7469
+ ]).optional(),
7470
+ /**
7471
+ * ONE slice per access unit.
7472
+ *
7473
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7474
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7475
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7476
+ * frame-end per frame: the first pictures render and the video then freezes
7477
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7478
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7479
+ *
7480
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7481
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7482
+ * consumers meaning the same thing spelled differently would stop sharing one
7483
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7484
+ */
7485
+ singleSlicePerFrame: boolean().optional()
7470
7486
  }),
7471
7487
  audio: union([literal("passthrough"), object({
7472
7488
  codec: _enum([
@@ -7516,7 +7532,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7516
7532
  audio: "passthrough"
7517
7533
  };
7518
7534
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7519
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7535
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7520
7536
  /**
7521
7537
  * Deep wiring healthcheck — snapshot of active reachability probes across
7522
7538
  * every declared capability + widget of every installed plugin, on every
@@ -20148,13 +20164,109 @@ var SummariesQueryInput = object({
20148
20164
  deviceIds: array(number()).min(1),
20149
20165
  since: number().optional(),
20150
20166
  until: number().optional(),
20151
- limit: number().int().min(1).max(200).default(50)
20167
+ limit: number().int().min(1).max(200).default(50),
20168
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20169
+ * meaning of "dog" on every summary surface. */
20170
+ classes: array(string().min(1).max(60)).max(40).optional()
20152
20171
  });
20153
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20154
20172
  var SummaryDetailSchema = object({
20155
20173
  summary: SummarySchema,
20156
- /** Member track ids, in absorption order. */
20157
- trackIds: array(string()).readonly()
20174
+ /** Members in absorption order, oldest join first. */
20175
+ members: array(object({
20176
+ trackId: string(),
20177
+ /**
20178
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20179
+ * and the track no longer exists to fill it.
20180
+ *
20181
+ * The join, not the latest opinion: class voting can re-decide a track's
20182
+ * class afterwards, and a group's composition is the history of who came
20183
+ * together. A consumer counts these to badge a thumbnail — "two people and a
20184
+ * dog" — rather than being handed a second, denormalised tally that would
20185
+ * drift from the members it claims to describe.
20186
+ */
20187
+ className: string().nullable(),
20188
+ /** `primary` is the one member that carries the group's identity. */
20189
+ role: _enum([
20190
+ "primary",
20191
+ "sibling",
20192
+ "child"
20193
+ ]).nullable(),
20194
+ joinedAt: number(),
20195
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
20196
+ * at read time. Absent when nothing resolved, never an empty string. */
20197
+ label: string().optional(),
20198
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
20199
+ * recognised nobody, and absent for a member whose track has been pruned —
20200
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
20201
+ subLabel: string().optional()
20202
+ })).readonly()
20203
+ });
20204
+ /**
20205
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
20206
+ *
20207
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
20208
+ * we extend when we notice we need more". Enough to draw the card ("two people
20209
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
20210
+ * are on {@link getSummary}: shipping every member of every group so a client
20211
+ * can count them turns a page of two hundred groups into a thousand records to
20212
+ * draw two hundred badges.
20213
+ */
20214
+ var SummaryListItemSchema = object({
20215
+ summary: SummarySchema,
20216
+ /** Class counts, most numerous first; `className: null` is a member whose
20217
+ * class never resolved, counted so the counts add up to `memberCount`. */
20218
+ classes: array(object({
20219
+ className: string().nullable(),
20220
+ count: number().int().positive()
20221
+ })).readonly()
20222
+ });
20223
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
20224
+ /**
20225
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
20226
+ *
20227
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
20228
+ * summaries feed that could only answer "the last N for these cameras"
20229
+ * (`listSummaries`) would make groups available in the timeline and nowhere
20230
+ * else. Two feeds with different ordering contracts would be two ideas of what
20231
+ * "newest" means, on one screen.
20232
+ */
20233
+ var RecentSummariesQueryInput = object({
20234
+ /** Devices to merge. An empty array yields an empty page. */
20235
+ deviceIds: array(number()),
20236
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20237
+ since: number().optional(),
20238
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20239
+ until: number().optional(),
20240
+ limit: number().int().min(1).max(500).default(100),
20241
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20242
+ cursor: string().optional(),
20243
+ /**
20244
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20245
+ * deliberately the same name: the events filter tree emits one list of ticked
20246
+ * taxonomy nodes, and a summaries feed that called it something else would
20247
+ * make the operator's one filter component produce two shapes.
20248
+ *
20249
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20250
+ * me the arrivals with a dog" has to return the group of four people who had
20251
+ * one with them; a filter on the group's own fields could only ever answer
20252
+ * about its primary.
20253
+ *
20254
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20255
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20256
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20257
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20258
+ * that read only the class would answer "animal" and never "dog".
20259
+ *
20260
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20261
+ */
20262
+ classes: array(string().min(1).max(60)).max(40).optional()
20263
+ });
20264
+ var RecentSummariesPageSchema = object({
20265
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20266
+ * {@link listSummaries} — one list shape, so a card renders the same
20267
+ * whichever feed drew it. */
20268
+ summaries: array(SummaryListItemSchema).readonly(),
20269
+ nextCursor: string().nullable()
20158
20270
  });
20159
20271
  var RecentTracksPageSchema = object({
20160
20272
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20568,7 +20680,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20568
20680
  classes: array(string()).optional(),
20569
20681
  /** See {@link ExcludeSourcesDoc}. */
20570
20682
  excludeSources: array(TrackSourceSchema).optional()
20571
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20683
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20572
20684
  kind: "mutation",
20573
20685
  auth: "admin"
20574
20686
  }), 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({
@@ -32469,7 +32581,29 @@ var LoggingSettingsPatchSchema = object({
32469
32581
  * disarmed the channels it did not mention would make the Levels page and
32470
32582
  * the Diagnostics page fight over the same value.
32471
32583
  */
32472
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
32584
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
32585
+ /**
32586
+ * How many days the analytics OPS LOG is kept — the audit trail of what
32587
+ * retention actually deleted.
32588
+ *
32589
+ * It lives HERE, on the logging document, and not on the analytics addon's
32590
+ * settings form, on the operator's instruction: every other log-ish window in
32591
+ * this system is configured through this one document, and a retention audit
32592
+ * configured somewhere else is a second place to look when the question is
32593
+ * "what happened to my data".
32594
+ *
32595
+ * That placement has a cost worth stating: this document ships in the
32596
+ * framework closure, so this knob needs a published `@camstack/server` while
32597
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
32598
+ *
32599
+ * `retention-model.ts` argued this one must NOT follow a device window,
32600
+ * because the audit trail is the history of the SWEEP and not of the camera.
32601
+ * That argument stands and is untouched — what changes is that 30 days stops
32602
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
32603
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
32604
+ * state in which the next 9.5 GiB leak cannot be explained.
32605
+ */
32606
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
32473
32607
  });
32474
32608
  /**
32475
32609
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -32542,6 +32676,14 @@ var LoggingSettingsStateSchema = object({
32542
32676
  channels: array(LogChannelDescriptorSchema).readonly(),
32543
32677
  /** The channels ARMED right now, each with its deadline. */
32544
32678
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
32679
+ /**
32680
+ * The ops-log window in days, as it is in force. `0` = kept forever.
32681
+ *
32682
+ * Reported rather than left to the caller's memory of what it wrote: this is
32683
+ * the value the analytics sweep will actually use, and an operator asking how
32684
+ * long their audit trail lives should not have to infer it from a default.
32685
+ */
32686
+ opsLogRetentionDays: number().int().min(0),
32545
32687
  persisted: boolean()
32546
32688
  });
32547
32689
  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(), {
@@ -38124,6 +38266,12 @@ Object.freeze({
38124
38266
  addonId: null,
38125
38267
  access: "view"
38126
38268
  },
38269
+ "pipelineAnalytics.listRecentSummaries": {
38270
+ capName: "pipeline-analytics",
38271
+ capScope: "device",
38272
+ addonId: null,
38273
+ access: "view"
38274
+ },
38127
38275
  "pipelineAnalytics.listRecentTracks": {
38128
38276
  capName: "pipeline-analytics",
38129
38277
  capScope: "device",
@@ -41951,6 +42099,11 @@ Object.freeze({
41951
42099
  form: "single",
41952
42100
  optional: true
41953
42101
  }],
42102
+ "pipelineAnalytics.listRecentSummaries": [{
42103
+ name: "deviceIds",
42104
+ form: "array",
42105
+ optional: false
42106
+ }],
41954
42107
  "pipelineAnalytics.listRecentTracks": [{
41955
42108
  name: "deviceIds",
41956
42109
  form: "array",
package/dist/addon.mjs CHANGED
@@ -5355,7 +5355,7 @@ var ZodIssueCode = {
5355
5355
  var ZodFirstPartyTypeKind;
5356
5356
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
5357
5357
  //#endregion
5358
- //#region ../types/dist/sleep-CnLyvg3w.mjs
5358
+ //#region ../types/dist/sleep-DBKu2-U5.mjs
5359
5359
  /**
5360
5360
  * The audio chunk plane's byte format, and the ONE expansion from a coded
5361
5361
  * window to float samples (D455).
@@ -7443,7 +7443,23 @@ var EncodeProfileSchema = object({
7443
7443
  "zerolatency",
7444
7444
  "film",
7445
7445
  "animation"
7446
- ]).optional()
7446
+ ]).optional(),
7447
+ /**
7448
+ * ONE slice per access unit.
7449
+ *
7450
+ * `-tune zerolatency` turns on x264's sliced threads, and a frame then leaves
7451
+ * the encoder as five NAL slices that share one RTP timestamp and carry one
7452
+ * marker bit. A libwebrtc depacketiser sees five frame-starts and one
7453
+ * frame-end per frame: the first pictures render and the video then freezes
7454
+ * for good while the audio, on its own plane, plays on. Measured on this hub
7455
+ * 2026-09-13 against the Echo, and reduced to one slice by this flag alone.
7456
+ *
7457
+ * A NAMED field and not a raw flag, because {@link EgressEncodeSchema} omits
7458
+ * `outputArgs` on purpose: an opaque array is part of the sharing key, so two
7459
+ * consumers meaning the same thing spelled differently would stop sharing one
7460
+ * child. Absent means "whatever the encoder does" — today's behaviour.
7461
+ */
7462
+ singleSlicePerFrame: boolean().optional()
7447
7463
  }),
7448
7464
  audio: union([literal("passthrough"), object({
7449
7465
  codec: _enum([
@@ -7493,7 +7509,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
7493
7509
  audio: "passthrough"
7494
7510
  };
7495
7511
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7496
- ({ ...BASE_LIVE_EGRESS_PROFILE });
7512
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
7497
7513
  /**
7498
7514
  * Deep wiring healthcheck — snapshot of active reachability probes across
7499
7515
  * every declared capability + widget of every installed plugin, on every
@@ -20125,13 +20141,109 @@ var SummariesQueryInput = object({
20125
20141
  deviceIds: array(number()).min(1),
20126
20142
  since: number().optional(),
20127
20143
  until: number().optional(),
20128
- limit: number().int().min(1).max(200).default(50)
20144
+ limit: number().int().min(1).max(200).default(50),
20145
+ /** The same member-wise filter {@link listRecentSummaries} takes — one
20146
+ * meaning of "dog" on every summary surface. */
20147
+ classes: array(string().min(1).max(60)).max(40).optional()
20129
20148
  });
20130
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20131
20149
  var SummaryDetailSchema = object({
20132
20150
  summary: SummarySchema,
20133
- /** Member track ids, in absorption order. */
20134
- trackIds: array(string()).readonly()
20151
+ /** Members in absorption order, oldest join first. */
20152
+ members: array(object({
20153
+ trackId: string(),
20154
+ /**
20155
+ * The track's class at the time it JOINED, or `null` when it was unresolved
20156
+ * and the track no longer exists to fill it.
20157
+ *
20158
+ * The join, not the latest opinion: class voting can re-decide a track's
20159
+ * class afterwards, and a group's composition is the history of who came
20160
+ * together. A consumer counts these to badge a thumbnail — "two people and a
20161
+ * dog" — rather than being handed a second, denormalised tally that would
20162
+ * drift from the members it claims to describe.
20163
+ */
20164
+ className: string().nullable(),
20165
+ /** `primary` is the one member that carries the group's identity. */
20166
+ role: _enum([
20167
+ "primary",
20168
+ "sibling",
20169
+ "child"
20170
+ ]).nullable(),
20171
+ joinedAt: number(),
20172
+ /** Tier 1 — the sub-class (`dog`, `van`), resolved from the member's track
20173
+ * at read time. Absent when nothing resolved, never an empty string. */
20174
+ label: string().optional(),
20175
+ /** Tier 2 — the instance: a person's name, a plate. Absent when the pipeline
20176
+ * recognised nobody, and absent for a member whose track has been pruned —
20177
+ * a thumbnail that names the wrong person is worse than one naming nobody. */
20178
+ subLabel: string().optional()
20179
+ })).readonly()
20180
+ });
20181
+ /**
20182
+ * What a LIST of groups carries: the envelope plus what it is MADE OF.
20183
+ *
20184
+ * Coarse on purpose — the operator's rule is "only the rough data on the list,
20185
+ * we extend when we notice we need more". Enough to draw the card ("two people
20186
+ * and a dog") and no more. The MEMBERS, their names and the per-member links
20187
+ * are on {@link getSummary}: shipping every member of every group so a client
20188
+ * can count them turns a page of two hundred groups into a thousand records to
20189
+ * draw two hundred badges.
20190
+ */
20191
+ var SummaryListItemSchema = object({
20192
+ summary: SummarySchema,
20193
+ /** Class counts, most numerous first; `className: null` is a member whose
20194
+ * class never resolved, counted so the counts add up to `memberCount`. */
20195
+ classes: array(object({
20196
+ className: string().nullable(),
20197
+ count: number().int().positive()
20198
+ })).readonly()
20199
+ });
20200
+ var SummariesPageSchema = object({ summaries: array(SummaryListItemSchema).readonly() });
20201
+ /**
20202
+ * The paged feed, deliberately the SAME shape as {@link RecentTracksQueryInput}.
20203
+ *
20204
+ * The reel on the cameras page and the Events gallery both ride a cursor, so a
20205
+ * summaries feed that could only answer "the last N for these cameras"
20206
+ * (`listSummaries`) would make groups available in the timeline and nowhere
20207
+ * else. Two feeds with different ordering contracts would be two ideas of what
20208
+ * "newest" means, on one screen.
20209
+ */
20210
+ var RecentSummariesQueryInput = object({
20211
+ /** Devices to merge. An empty array yields an empty page. */
20212
+ deviceIds: array(number()),
20213
+ /** Window lower bound on `lastActiveAt` (inclusive). */
20214
+ since: number().optional(),
20215
+ /** Window upper bound on `lastActiveAt` (inclusive). */
20216
+ until: number().optional(),
20217
+ limit: number().int().min(1).max(500).default(100),
20218
+ /** Opaque continuation cursor from a previous page's `nextCursor`. */
20219
+ cursor: string().optional(),
20220
+ /**
20221
+ * Restrict to these classes — THE SAME FIELD `listRecentTracks` takes, and
20222
+ * deliberately the same name: the events filter tree emits one list of ticked
20223
+ * taxonomy nodes, and a summaries feed that called it something else would
20224
+ * make the operator's one filter component produce two shapes.
20225
+ *
20226
+ * A question about the MEMBERS: a group matches when ANY of them does. "Show
20227
+ * me the arrivals with a dog" has to return the group of four people who had
20228
+ * one with them; a filter on the group's own fields could only ever answer
20229
+ * about its primary.
20230
+ *
20231
+ * Matched against all THREE tiers of a member — the class (`animal`), the
20232
+ * sub-class (`dog`) and the instance (a name, a plate) — because which tier a
20233
+ * ticked node lives on is the taxonomy's business, not the caller's. `dog` is
20234
+ * an event kind in the tree and a tier-1 label on an `animal` track; a filter
20235
+ * that read only the class would answer "animal" and never "dog".
20236
+ *
20237
+ * ABSENT or EMPTY means no filter — absent is not a selection of none.
20238
+ */
20239
+ classes: array(string().min(1).max(60)).max(40).optional()
20240
+ });
20241
+ var RecentSummariesPageSchema = object({
20242
+ /** Ordered by (`lastActiveAt` DESC, id DESC). Same light item as
20243
+ * {@link listSummaries} — one list shape, so a card renders the same
20244
+ * whichever feed drew it. */
20245
+ summaries: array(SummaryListItemSchema).readonly(),
20246
+ nextCursor: string().nullable()
20135
20247
  });
20136
20248
  var RecentTracksPageSchema = object({
20137
20249
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -20545,7 +20657,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20545
20657
  classes: array(string()).optional(),
20546
20658
  /** See {@link ExcludeSourcesDoc}. */
20547
20659
  excludeSources: array(TrackSourceSchema).optional()
20548
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20660
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(SummariesQueryInput, SummariesPageSchema), method(RecentSummariesQueryInput, RecentSummariesPageSchema), method(object({ id: string() }), SummaryDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20549
20661
  kind: "mutation",
20550
20662
  auth: "admin"
20551
20663
  }), 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({
@@ -32446,7 +32558,29 @@ var LoggingSettingsPatchSchema = object({
32446
32558
  * disarmed the channels it did not mention would make the Levels page and
32447
32559
  * the Diagnostics page fight over the same value.
32448
32560
  */
32449
- channels: array(LogChannelWindowPatchSchema).readonly().optional()
32561
+ channels: array(LogChannelWindowPatchSchema).readonly().optional(),
32562
+ /**
32563
+ * How many days the analytics OPS LOG is kept — the audit trail of what
32564
+ * retention actually deleted.
32565
+ *
32566
+ * It lives HERE, on the logging document, and not on the analytics addon's
32567
+ * settings form, on the operator's instruction: every other log-ish window in
32568
+ * this system is configured through this one document, and a retention audit
32569
+ * configured somewhere else is a second place to look when the question is
32570
+ * "what happened to my data".
32571
+ *
32572
+ * That placement has a cost worth stating: this document ships in the
32573
+ * framework closure, so this knob needs a published `@camstack/server` while
32574
+ * the four windows beside it (D485) reach a node on `camstack deploy`.
32575
+ *
32576
+ * `retention-model.ts` argued this one must NOT follow a device window,
32577
+ * because the audit trail is the history of the SWEEP and not of the camera.
32578
+ * That argument stands and is untouched — what changes is that 30 days stops
32579
+ * being a constant nobody can reach. `0` keeps it forever. The floor is 1 day
32580
+ * rather than 0-as-delete: an audit log truncated to nothing is precisely the
32581
+ * state in which the next 9.5 GiB leak cannot be explained.
32582
+ */
32583
+ opsLogRetentionDays: number().int().min(0).max(3650).optional()
32450
32584
  });
32451
32585
  /**
32452
32586
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -32519,6 +32653,14 @@ var LoggingSettingsStateSchema = object({
32519
32653
  channels: array(LogChannelDescriptorSchema).readonly(),
32520
32654
  /** The channels ARMED right now, each with its deadline. */
32521
32655
  activeChannels: array(LogChannelWindowStateSchema).readonly(),
32656
+ /**
32657
+ * The ops-log window in days, as it is in force. `0` = kept forever.
32658
+ *
32659
+ * Reported rather than left to the caller's memory of what it wrote: this is
32660
+ * the value the analytics sweep will actually use, and an operator asking how
32661
+ * long their audit trail lives should not have to infer it from a default.
32662
+ */
32663
+ opsLogRetentionDays: number().int().min(0),
32522
32664
  persisted: boolean()
32523
32665
  });
32524
32666
  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(), {
@@ -38101,6 +38243,12 @@ Object.freeze({
38101
38243
  addonId: null,
38102
38244
  access: "view"
38103
38245
  },
38246
+ "pipelineAnalytics.listRecentSummaries": {
38247
+ capName: "pipeline-analytics",
38248
+ capScope: "device",
38249
+ addonId: null,
38250
+ access: "view"
38251
+ },
38104
38252
  "pipelineAnalytics.listRecentTracks": {
38105
38253
  capName: "pipeline-analytics",
38106
38254
  capScope: "device",
@@ -41928,6 +42076,11 @@ Object.freeze({
41928
42076
  form: "single",
41929
42077
  optional: true
41930
42078
  }],
42079
+ "pipelineAnalytics.listRecentSummaries": [{
42080
+ name: "deviceIds",
42081
+ form: "array",
42082
+ optional: false
42083
+ }],
41931
42084
  "pipelineAnalytics.listRecentTracks": [{
41932
42085
  name: "deviceIds",
41933
42086
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-terminal",
3
- "version": "0.1.105",
3
+ "version": "0.1.106",
4
4
  "description": "Interactive terminal sessions (pty + xterm) as a CamStack addon",
5
5
  "keywords": [
6
6
  "camstack",