@camstack/addon-provider-tuya 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
@@ -6166,7 +6166,7 @@ var ZodIssueCode = {
6166
6166
  var ZodFirstPartyTypeKind;
6167
6167
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6168
6168
  //#endregion
6169
- //#region ../types/dist/sleep-CnLyvg3w.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).
@@ -8195,7 +8195,23 @@ var EncodeProfileSchema = object({
8195
8195
  "zerolatency",
8196
8196
  "film",
8197
8197
  "animation"
8198
- ]).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()
8199
8215
  }),
8200
8216
  audio: union([literal("passthrough"), object({
8201
8217
  codec: _enum([
@@ -8245,7 +8261,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
8245
8261
  audio: "passthrough"
8246
8262
  };
8247
8263
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
8248
- ({ ...BASE_LIVE_EGRESS_PROFILE });
8264
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
8249
8265
  /**
8250
8266
  * Deep wiring healthcheck — snapshot of active reachability probes across
8251
8267
  * every declared capability + widget of every installed plugin, on every
@@ -20967,13 +20983,109 @@ var SummariesQueryInput = object({
20967
20983
  deviceIds: array(number()).min(1),
20968
20984
  since: number().optional(),
20969
20985
  until: number().optional(),
20970
- 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()
20971
20990
  });
20972
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20973
20991
  var SummaryDetailSchema = object({
20974
20992
  summary: SummarySchema,
20975
- /** Member track ids, in absorption order. */
20976
- 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()
20977
21089
  });
20978
21090
  var RecentTracksPageSchema = object({
20979
21091
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -21387,7 +21499,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21387
21499
  classes: array(string()).optional(),
21388
21500
  /** See {@link ExcludeSourcesDoc}. */
21389
21501
  excludeSources: array(TrackSourceSchema).optional()
21390
- }), 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(), {
21391
21503
  kind: "mutation",
21392
21504
  auth: "admin"
21393
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({
@@ -33148,7 +33260,29 @@ var LoggingSettingsPatchSchema = object({
33148
33260
  * disarmed the channels it did not mention would make the Levels page and
33149
33261
  * the Diagnostics page fight over the same value.
33150
33262
  */
33151
- 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()
33152
33286
  });
33153
33287
  /**
33154
33288
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -33221,6 +33355,14 @@ var LoggingSettingsStateSchema = object({
33221
33355
  channels: array(LogChannelDescriptorSchema).readonly(),
33222
33356
  /** The channels ARMED right now, each with its deadline. */
33223
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),
33224
33366
  persisted: boolean()
33225
33367
  });
33226
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(), {
@@ -39221,6 +39363,12 @@ Object.freeze({
39221
39363
  addonId: null,
39222
39364
  access: "view"
39223
39365
  },
39366
+ "pipelineAnalytics.listRecentSummaries": {
39367
+ capName: "pipeline-analytics",
39368
+ capScope: "device",
39369
+ addonId: null,
39370
+ access: "view"
39371
+ },
39224
39372
  "pipelineAnalytics.listRecentTracks": {
39225
39373
  capName: "pipeline-analytics",
39226
39374
  capScope: "device",
@@ -43048,6 +43196,11 @@ Object.freeze({
43048
43196
  form: "single",
43049
43197
  optional: true
43050
43198
  }],
43199
+ "pipelineAnalytics.listRecentSummaries": [{
43200
+ name: "deviceIds",
43201
+ form: "array",
43202
+ optional: false
43203
+ }],
43051
43204
  "pipelineAnalytics.listRecentTracks": [{
43052
43205
  name: "deviceIds",
43053
43206
  form: "array",
package/dist/addon.mjs CHANGED
@@ -6165,7 +6165,7 @@ var ZodIssueCode = {
6165
6165
  var ZodFirstPartyTypeKind;
6166
6166
  ZodFirstPartyTypeKind || (ZodFirstPartyTypeKind = {});
6167
6167
  //#endregion
6168
- //#region ../types/dist/sleep-CnLyvg3w.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).
@@ -8194,7 +8194,23 @@ var EncodeProfileSchema = object({
8194
8194
  "zerolatency",
8195
8195
  "film",
8196
8196
  "animation"
8197
- ]).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()
8198
8214
  }),
8199
8215
  audio: union([literal("passthrough"), object({
8200
8216
  codec: _enum([
@@ -8244,7 +8260,7 @@ var BASE_LIVE_EGRESS_PROFILE = {
8244
8260
  audio: "passthrough"
8245
8261
  };
8246
8262
  ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
8247
- ({ ...BASE_LIVE_EGRESS_PROFILE });
8263
+ ({ ...BASE_LIVE_EGRESS_PROFILE }), { ...BASE_LIVE_EGRESS_PROFILE.video };
8248
8264
  /**
8249
8265
  * Deep wiring healthcheck — snapshot of active reachability probes across
8250
8266
  * every declared capability + widget of every installed plugin, on every
@@ -20966,13 +20982,109 @@ var SummariesQueryInput = object({
20966
20982
  deviceIds: array(number()).min(1),
20967
20983
  since: number().optional(),
20968
20984
  until: number().optional(),
20969
- 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()
20970
20989
  });
20971
- var SummariesPageSchema = object({ summaries: array(SummarySchema).readonly() });
20972
20990
  var SummaryDetailSchema = object({
20973
20991
  summary: SummarySchema,
20974
- /** Member track ids, in absorption order. */
20975
- 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()
20976
21088
  });
20977
21089
  var RecentTracksPageSchema = object({
20978
21090
  /** Merged page, ordered by (`lastSeen` DESC, `trackId` DESC). */
@@ -21386,7 +21498,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
21386
21498
  classes: array(string()).optional(),
21387
21499
  /** See {@link ExcludeSourcesDoc}. */
21388
21500
  excludeSources: array(TrackSourceSchema).optional()
21389
- }), 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(), {
21390
21502
  kind: "mutation",
21391
21503
  auth: "admin"
21392
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({
@@ -33147,7 +33259,29 @@ var LoggingSettingsPatchSchema = object({
33147
33259
  * disarmed the channels it did not mention would make the Levels page and
33148
33260
  * the Diagnostics page fight over the same value.
33149
33261
  */
33150
- 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()
33151
33285
  });
33152
33286
  /**
33153
33287
  * Which LAYER of the hierarchy is addressed. Absent = the cluster layer.
@@ -33220,6 +33354,14 @@ var LoggingSettingsStateSchema = object({
33220
33354
  channels: array(LogChannelDescriptorSchema).readonly(),
33221
33355
  /** The channels ARMED right now, each with its deadline. */
33222
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),
33223
33365
  persisted: boolean()
33224
33366
  });
33225
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(), {
@@ -39220,6 +39362,12 @@ Object.freeze({
39220
39362
  addonId: null,
39221
39363
  access: "view"
39222
39364
  },
39365
+ "pipelineAnalytics.listRecentSummaries": {
39366
+ capName: "pipeline-analytics",
39367
+ capScope: "device",
39368
+ addonId: null,
39369
+ access: "view"
39370
+ },
39223
39371
  "pipelineAnalytics.listRecentTracks": {
39224
39372
  capName: "pipeline-analytics",
39225
39373
  capScope: "device",
@@ -43047,6 +43195,11 @@ Object.freeze({
43047
43195
  form: "single",
43048
43196
  optional: true
43049
43197
  }],
43198
+ "pipelineAnalytics.listRecentSummaries": [{
43199
+ name: "deviceIds",
43200
+ form: "array",
43201
+ optional: false
43202
+ }],
43050
43203
  "pipelineAnalytics.listRecentTracks": [{
43051
43204
  name: "deviceIds",
43052
43205
  form: "array",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-tuya",
3
- "version": "0.2.100",
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",