@camstack/addon-export-alexa 1.2.53 → 1.2.55

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.
@@ -8186,6 +8186,20 @@ var RelocateJobSchema = object({
8186
8186
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8187
8187
  */
8188
8188
  rowsReconciled: number().int().nonnegative().optional(),
8189
+ /**
8190
+ * Rows this run FORGOT because the file they name is not on disk.
8191
+ *
8192
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8193
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8194
+ * and the durable row is dropped through the same channel eviction uses. It
8195
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8196
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8197
+ * the same failure as one that quietly skips them (D295).
8198
+ *
8199
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8200
+ * ledger claimed 5.65 GB of footage that no longer existed.
8201
+ */
8202
+ rowsForgotten: number().int().nonnegative().optional(),
8189
8203
  startedAt: number(),
8190
8204
  finishedAt: number().nullable(),
8191
8205
  error: string().nullable()
@@ -8549,6 +8563,91 @@ var RelocateResidueSchema = object({
8549
8563
  segments: number().int().nonnegative(),
8550
8564
  bytes: number().int().nonnegative()
8551
8565
  }).nullable();
8566
+ /**
8567
+ * Ask one location whether its durable hour rows describe the disk — the walk
8568
+ * (D319).
8569
+ *
8570
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8571
+ * missing tool is the question, and the dry run is how they sanity-check the
8572
+ * destructive run before authorising it.
8573
+ */
8574
+ var LedgerWalkInputSchema = object({
8575
+ locationId: string().min(1),
8576
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8577
+ apply: boolean().optional(),
8578
+ /** Narrow to one camera. */
8579
+ deviceId: number().int().positive().optional(),
8580
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8581
+ profiles: array(string().min(1)).optional()
8582
+ });
8583
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8584
+ var LedgerWalkRefusalSchema = _enum([
8585
+ "location-unknown",
8586
+ "source-writable",
8587
+ "no-ledger",
8588
+ "archive-unreadable",
8589
+ "anchor-absent",
8590
+ "anchor-unreadable",
8591
+ "anchor-moved"
8592
+ ]);
8593
+ _enum([
8594
+ "live-tail",
8595
+ "listing-error",
8596
+ "path-mismatch",
8597
+ "durable-refused"
8598
+ ]);
8599
+ /** Every skip reason, always present, always a number — so a reason that never
8600
+ * fired reports as zero rather than absent and the report shape is constant
8601
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8602
+ var LedgerWalkSkipCountsSchema = object({
8603
+ "live-tail": number().int().nonnegative(),
8604
+ "listing-error": number().int().nonnegative(),
8605
+ "path-mismatch": number().int().nonnegative(),
8606
+ "durable-refused": number().int().nonnegative()
8607
+ });
8608
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8609
+ var LedgerWalkDeviceReportSchema = object({
8610
+ deviceId: number().int(),
8611
+ hoursWalked: number().int().nonnegative(),
8612
+ hoursMissing: number().int().nonnegative(),
8613
+ ghostSegments: number().int().nonnegative(),
8614
+ ghostBytes: number().int().nonnegative(),
8615
+ forgottenSegments: number().int().nonnegative(),
8616
+ orphanFiles: number().int().nonnegative()
8617
+ });
8618
+ /**
8619
+ * What one walk claimed, listed, found and (only when armed) forgot.
8620
+ *
8621
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8622
+ * walk that saw a fraction of the location is visible in its own report rather
8623
+ * than in the absence of one.
8624
+ */
8625
+ var LedgerWalkReportSchema = object({
8626
+ locationId: string(),
8627
+ applied: boolean(),
8628
+ refused: LedgerWalkRefusalSchema.nullable(),
8629
+ archiveSegments: number().int().nonnegative().nullable(),
8630
+ archiveBytes: number().int().nonnegative().nullable(),
8631
+ hoursClaimed: number().int().nonnegative(),
8632
+ hoursWalked: number().int().nonnegative(),
8633
+ hoursMissing: number().int().nonnegative(),
8634
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8635
+ listings: number().int().nonnegative(),
8636
+ segmentsClaimed: number().int().nonnegative(),
8637
+ ghostSegments: number().int().nonnegative(),
8638
+ ghostBytes: number().int().nonnegative(),
8639
+ ghostHoursWhole: number().int().nonnegative(),
8640
+ forgottenSegments: number().int().nonnegative(),
8641
+ forgottenBytes: number().int().nonnegative(),
8642
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8643
+ orphanFiles: number().int().nonnegative(),
8644
+ orphanSample: array(string()).readonly(),
8645
+ hoursSkipped: number().int().nonnegative(),
8646
+ skippedByReason: LedgerWalkSkipCountsSchema,
8647
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8648
+ bounded: boolean(),
8649
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8650
+ });
8552
8651
  /** How many rows a media pass would still act on against a given target — the
8553
8652
  * media lane's denominator AND its residue, from ONE derivation so the two can
8554
8653
  * never disagree. `null` = the count could not be taken. */
@@ -18965,13 +19064,15 @@ var ListGroupsPageSchema = object({
18965
19064
  groups: array(AnalyticsGroupRecordSchema).readonly(),
18966
19065
  nextCursor: string().nullable()
18967
19066
  });
19067
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19068
+ var KEY_EVENTS_MAX_LIMIT = 200;
18968
19069
  var KeyEventQueryInput = object({
18969
19070
  deviceId: number(),
18970
19071
  /** Window lower bound (track firstSeen ≥ since). */
18971
19072
  since: number(),
18972
19073
  /** Window upper bound (track firstSeen ≤ until). */
18973
19074
  until: number(),
18974
- limit: number().int().min(1).max(200).default(50),
19075
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18975
19076
  /** Drop tracks scoring below this importance. */
18976
19077
  minImportance: number().min(0).max(1).optional(),
18977
19078
  /** Restrict to a single class (e.g. 'person'). */
@@ -18993,6 +19094,32 @@ var KeyEventSchema = object({
18993
19094
  ...TrackFlagFields,
18994
19095
  ...TrackRetrainFields
18995
19096
  });
19097
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19098
+ var KeyEventBatchQueryInput = object({
19099
+ deviceIds: array(number()).min(1).max(200),
19100
+ since: number(),
19101
+ until: number(),
19102
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19103
+ * across the set, which would let a busy camera starve a quiet one of its
19104
+ * rows and change what the merged feed contains. */
19105
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19106
+ minImportance: number().min(0).max(1).optional(),
19107
+ classFilter: string().optional()
19108
+ });
19109
+ /**
19110
+ * One camera's key events in a batch answer.
19111
+ *
19112
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19113
+ * error rather than throwing, so a camera whose store read failed and one with
19114
+ * no events in the window were ALREADY indistinguishable per camera — the
19115
+ * batch does not make that worse, and the row keeps the deviceId the single
19116
+ * method's output never carried (the caller used to stamp it from the fan-out
19117
+ * key, which only worked because there was one query per camera).
19118
+ */
19119
+ var KeyEventsForDeviceSchema = object({
19120
+ deviceId: number(),
19121
+ events: array(KeyEventSchema).readonly()
19122
+ });
18996
19123
  object({
18997
19124
  trackId: string(),
18998
19125
  className: string(),
@@ -19064,6 +19191,50 @@ var EventStoreFootprintSchema = object({
19064
19191
  totalBytes: number().int(),
19065
19192
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19066
19193
  });
19194
+ /** Event-media footprint for one {@link MediaFileKind}. */
19195
+ var EventMediaKindFootprintSchema = object({
19196
+ kind: MediaFileKindEnum,
19197
+ /** Media rows of this kind. */
19198
+ rows: number().int(),
19199
+ /** Bytes on disk held by those rows. */
19200
+ bytes: number().int()
19201
+ });
19202
+ /**
19203
+ * The media footprint broken down by KIND — the axis a deletion decision
19204
+ * actually turns on.
19205
+ *
19206
+ * A byte total says how much there is; it cannot say what is safe to remove.
19207
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19208
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19209
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19210
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19211
+ * nothing else, so sizing a deletion means summing per kind.
19212
+ *
19213
+ * ## Why `unaccounted*` exists
19214
+ *
19215
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19216
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19217
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19218
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19219
+ * retired code path, or by a version that knew a kind this one does not) would
19220
+ * otherwise vanish from the total silently, and an operator would delete
19221
+ * against a denominator smaller than the disk.
19222
+ *
19223
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19224
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19225
+ */
19226
+ var EventMediaKindBreakdownSchema = object({
19227
+ /** Every media row in scope, from one unfiltered aggregate. */
19228
+ totalRows: number().int(),
19229
+ /** Every media byte in scope, from that same aggregate. */
19230
+ totalBytes: number().int(),
19231
+ /** Per-kind footprint, ordered by bytes descending. */
19232
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19233
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19234
+ unaccountedRows: number().int(),
19235
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19236
+ unaccountedBytes: number().int()
19237
+ });
19067
19238
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19068
19239
  var EventPruneCountsSchema = object({
19069
19240
  motion: number().int(),
@@ -19218,7 +19389,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19218
19389
  until: number().optional(),
19219
19390
  kinds: array(string()).optional(),
19220
19391
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19221
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19392
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19222
19393
  deviceId: number(),
19223
19394
  since: number(),
19224
19395
  until: number(),
@@ -19267,6 +19438,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19267
19438
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19268
19439
  kind: "query",
19269
19440
  auth: "admin"
19441
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19442
+ kind: "query",
19443
+ auth: "admin"
19270
19444
  }), method(object({
19271
19445
  olderThanMs: number(),
19272
19446
  reason: OpsLogReasonSchema.optional()
@@ -21218,6 +21392,20 @@ method(object({
21218
21392
  error: string().optional()
21219
21393
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21220
21394
  providerId: string(),
21395
+ /**
21396
+ * The location this config is an UNSAVED edit of, when there is one.
21397
+ *
21398
+ * `listLocations` replaces every declared secret with the redaction
21399
+ * sentinel, so the edit modal's form state holds the sentinel for any
21400
+ * credential the operator did not retype — and posting that here
21401
+ * without a way to resolve it makes the provider try to authenticate
21402
+ * as `__camstack_redacted__` and report the operator's own working
21403
+ * password as wrong. Given this id, the orchestrator restores each
21404
+ * sentinel from the stored config (same rule as `upsertLocation`)
21405
+ * before dispatching. Omitted by the "Add location" wizard, where
21406
+ * every value was typed just now and nothing is stored yet.
21407
+ */
21408
+ locationId: string().optional(),
21221
21409
  config: record(string(), unknown())
21222
21410
  }), object({
21223
21411
  ok: boolean(),
@@ -26853,6 +27041,9 @@ method(object({
26853
27041
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
26854
27042
  kind: "query",
26855
27043
  auth: "admin"
27044
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
27045
+ kind: "mutation",
27046
+ auth: "admin"
26856
27047
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
26857
27048
  kind: "mutation",
26858
27049
  auth: "admin"
@@ -27244,7 +27435,26 @@ var SceneMonitorStatusSchema = object({
27244
27435
  monitors: array(SceneMonitorSchema),
27245
27436
  lastFetchedAt: number()
27246
27437
  });
27247
- DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
27438
+ /**
27439
+ * One camera's row in a `listScenesBatch` answer.
27440
+ *
27441
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
27442
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
27443
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
27444
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
27445
+ * configured. Fanned out per camera the difference was visible — one query
27446
+ * errored while the others resolved — and a batch that returned only the rows
27447
+ * it managed would have destroyed it, silently, by making an unreachable camera
27448
+ * indistinguishable from one that answered `monitors: []`.
27449
+ *
27450
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
27451
+ * could not be read"; `status.monitors: []` means "read, and it has none".
27452
+ */
27453
+ var SceneMonitorStatusForDeviceSchema = object({
27454
+ deviceId: number(),
27455
+ status: SceneMonitorStatusSchema.nullable()
27456
+ });
27457
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
27248
27458
  deviceId: number(),
27249
27459
  label: string(),
27250
27460
  roi: MaskRectShapeSchema,
@@ -28568,6 +28778,27 @@ var CameraOccupancySnapshotSchema = object({
28568
28778
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
28569
28779
  });
28570
28780
  /**
28781
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
28782
+ *
28783
+ * THREE outcomes, and the single-camera method could only express two of them
28784
+ * because `snapshot: null` was already spoken for:
28785
+ *
28786
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
28787
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
28788
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
28789
+ * - `read: 'unreadable'` — the owner could not answer for this
28790
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
28791
+ *
28792
+ * Collapsing the last two is the failure this field exists to prevent: a
28793
+ * hydration that threw would otherwise render as an empty Stationary section,
28794
+ * which is a definite claim about a camera nobody could read.
28795
+ */
28796
+ var CameraOccupancySnapshotForDeviceSchema = object({
28797
+ deviceId: number(),
28798
+ read: _enum(["read", "unreadable"]),
28799
+ snapshot: CameraOccupancySnapshotSchema.nullable()
28800
+ });
28801
+ /**
28571
28802
  * Time-series resolution. The history methods return one bucket per
28572
28803
  * step over the requested range. Smaller resolutions cost more
28573
28804
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -28591,7 +28822,7 @@ var HistoryPointSchema = object({
28591
28822
  /** Object count averaged over the bucket (rounded to nearest integer). */
28592
28823
  count: number().int().nonnegative()
28593
28824
  });
28594
- DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
28825
+ DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
28595
28826
  deviceId: number(),
28596
28827
  zoneId: string(),
28597
28828
  className: string().optional()
@@ -31872,6 +32103,12 @@ Object.freeze({
31872
32103
  addonId: null,
31873
32104
  access: "view"
31874
32105
  },
32106
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
32107
+ capName: "pipeline-analytics",
32108
+ capScope: "device",
32109
+ addonId: null,
32110
+ access: "view"
32111
+ },
31875
32112
  "pipelineAnalytics.getEventStoreFootprint": {
31876
32113
  capName: "pipeline-analytics",
31877
32114
  capScope: "device",
@@ -31890,6 +32127,12 @@ Object.freeze({
31890
32127
  addonId: null,
31891
32128
  access: "view"
31892
32129
  },
32130
+ "pipelineAnalytics.getKeyEventsBatch": {
32131
+ capName: "pipeline-analytics",
32132
+ capScope: "device",
32133
+ addonId: null,
32134
+ access: "view"
32135
+ },
31893
32136
  "pipelineAnalytics.getMotionEvents": {
31894
32137
  capName: "pipeline-analytics",
31895
32138
  capScope: "device",
@@ -33066,6 +33309,12 @@ Object.freeze({
33066
33309
  addonId: null,
33067
33310
  access: "view"
33068
33311
  },
33312
+ "recording.reconcileLedgerAgainstDisk": {
33313
+ capName: "recording",
33314
+ capScope: "system",
33315
+ addonId: null,
33316
+ access: "create"
33317
+ },
33069
33318
  "recording.refreshStorageLocationsForMigration": {
33070
33319
  capName: "recording",
33071
33320
  capScope: "system",
@@ -33192,6 +33441,12 @@ Object.freeze({
33192
33441
  addonId: null,
33193
33442
  access: "view"
33194
33443
  },
33444
+ "sceneMonitor.listScenesBatch": {
33445
+ capName: "scene-monitor",
33446
+ capScope: "device",
33447
+ addonId: null,
33448
+ access: "view"
33449
+ },
33195
33450
  "sceneMonitor.recheckNow": {
33196
33451
  capName: "scene-monitor",
33197
33452
  capScope: "device",
@@ -34548,6 +34803,12 @@ Object.freeze({
34548
34803
  addonId: null,
34549
34804
  access: "view"
34550
34805
  },
34806
+ "zoneAnalytics.getCurrentSnapshotBatch": {
34807
+ capName: "zone-analytics",
34808
+ capScope: "device",
34809
+ addonId: null,
34810
+ access: "view"
34811
+ },
34551
34812
  "zoneAnalytics.getUnzonedHistory": {
34552
34813
  capName: "zone-analytics",
34553
34814
  capScope: "device",
@@ -35537,6 +35798,11 @@ Object.freeze({
35537
35798
  form: "single",
35538
35799
  optional: false
35539
35800
  }],
35801
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
35802
+ name: "deviceId",
35803
+ form: "single",
35804
+ optional: true
35805
+ }],
35540
35806
  "pipelineAnalytics.getGroup": [{
35541
35807
  name: "deviceId",
35542
35808
  form: "single",
@@ -35547,6 +35813,11 @@ Object.freeze({
35547
35813
  form: "single",
35548
35814
  optional: false
35549
35815
  }],
35816
+ "pipelineAnalytics.getKeyEventsBatch": [{
35817
+ name: "deviceIds",
35818
+ form: "array",
35819
+ optional: false
35820
+ }],
35550
35821
  "pipelineAnalytics.getMotionEvents": [{
35551
35822
  name: "deviceId",
35552
35823
  form: "single",
@@ -35987,6 +36258,11 @@ Object.freeze({
35987
36258
  form: "single",
35988
36259
  optional: false
35989
36260
  }],
36261
+ "recording.reconcileLedgerAgainstDisk": [{
36262
+ name: "deviceId",
36263
+ form: "single",
36264
+ optional: true
36265
+ }],
35990
36266
  "recording.relocateFootage": [{
35991
36267
  name: "deviceId",
35992
36268
  form: "single",
@@ -36052,6 +36328,11 @@ Object.freeze({
36052
36328
  form: "single",
36053
36329
  optional: false
36054
36330
  }],
36331
+ "sceneMonitor.listScenesBatch": [{
36332
+ name: "deviceIds",
36333
+ form: "array",
36334
+ optional: false
36335
+ }],
36055
36336
  "sceneMonitor.recheckNow": [{
36056
36337
  name: "deviceId",
36057
36338
  form: "single",
@@ -36313,6 +36594,11 @@ Object.freeze({
36313
36594
  form: "single",
36314
36595
  optional: false
36315
36596
  }],
36597
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
36598
+ name: "deviceIds",
36599
+ form: "array",
36600
+ optional: false
36601
+ }],
36316
36602
  "zoneAnalytics.getUnzonedHistory": [{
36317
36603
  name: "deviceId",
36318
36604
  form: "single",
@@ -8159,6 +8159,20 @@ var RelocateJobSchema = object({
8159
8159
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8160
8160
  */
8161
8161
  rowsReconciled: number().int().nonnegative().optional(),
8162
+ /**
8163
+ * Rows this run FORGOT because the file they name is not on disk.
8164
+ *
8165
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8166
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8167
+ * and the durable row is dropped through the same channel eviction uses. It
8168
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8169
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8170
+ * the same failure as one that quietly skips them (D295).
8171
+ *
8172
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8173
+ * ledger claimed 5.65 GB of footage that no longer existed.
8174
+ */
8175
+ rowsForgotten: number().int().nonnegative().optional(),
8162
8176
  startedAt: number(),
8163
8177
  finishedAt: number().nullable(),
8164
8178
  error: string().nullable()
@@ -8522,6 +8536,91 @@ var RelocateResidueSchema = object({
8522
8536
  segments: number().int().nonnegative(),
8523
8537
  bytes: number().int().nonnegative()
8524
8538
  }).nullable();
8539
+ /**
8540
+ * Ask one location whether its durable hour rows describe the disk — the walk
8541
+ * (D319).
8542
+ *
8543
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8544
+ * missing tool is the question, and the dry run is how they sanity-check the
8545
+ * destructive run before authorising it.
8546
+ */
8547
+ var LedgerWalkInputSchema = object({
8548
+ locationId: string().min(1),
8549
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8550
+ apply: boolean().optional(),
8551
+ /** Narrow to one camera. */
8552
+ deviceId: number().int().positive().optional(),
8553
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8554
+ profiles: array(string().min(1)).optional()
8555
+ });
8556
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8557
+ var LedgerWalkRefusalSchema = _enum([
8558
+ "location-unknown",
8559
+ "source-writable",
8560
+ "no-ledger",
8561
+ "archive-unreadable",
8562
+ "anchor-absent",
8563
+ "anchor-unreadable",
8564
+ "anchor-moved"
8565
+ ]);
8566
+ _enum([
8567
+ "live-tail",
8568
+ "listing-error",
8569
+ "path-mismatch",
8570
+ "durable-refused"
8571
+ ]);
8572
+ /** Every skip reason, always present, always a number — so a reason that never
8573
+ * fired reports as zero rather than absent and the report shape is constant
8574
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8575
+ var LedgerWalkSkipCountsSchema = object({
8576
+ "live-tail": number().int().nonnegative(),
8577
+ "listing-error": number().int().nonnegative(),
8578
+ "path-mismatch": number().int().nonnegative(),
8579
+ "durable-refused": number().int().nonnegative()
8580
+ });
8581
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8582
+ var LedgerWalkDeviceReportSchema = object({
8583
+ deviceId: number().int(),
8584
+ hoursWalked: number().int().nonnegative(),
8585
+ hoursMissing: number().int().nonnegative(),
8586
+ ghostSegments: number().int().nonnegative(),
8587
+ ghostBytes: number().int().nonnegative(),
8588
+ forgottenSegments: number().int().nonnegative(),
8589
+ orphanFiles: number().int().nonnegative()
8590
+ });
8591
+ /**
8592
+ * What one walk claimed, listed, found and (only when armed) forgot.
8593
+ *
8594
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8595
+ * walk that saw a fraction of the location is visible in its own report rather
8596
+ * than in the absence of one.
8597
+ */
8598
+ var LedgerWalkReportSchema = object({
8599
+ locationId: string(),
8600
+ applied: boolean(),
8601
+ refused: LedgerWalkRefusalSchema.nullable(),
8602
+ archiveSegments: number().int().nonnegative().nullable(),
8603
+ archiveBytes: number().int().nonnegative().nullable(),
8604
+ hoursClaimed: number().int().nonnegative(),
8605
+ hoursWalked: number().int().nonnegative(),
8606
+ hoursMissing: number().int().nonnegative(),
8607
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8608
+ listings: number().int().nonnegative(),
8609
+ segmentsClaimed: number().int().nonnegative(),
8610
+ ghostSegments: number().int().nonnegative(),
8611
+ ghostBytes: number().int().nonnegative(),
8612
+ ghostHoursWhole: number().int().nonnegative(),
8613
+ forgottenSegments: number().int().nonnegative(),
8614
+ forgottenBytes: number().int().nonnegative(),
8615
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8616
+ orphanFiles: number().int().nonnegative(),
8617
+ orphanSample: array(string()).readonly(),
8618
+ hoursSkipped: number().int().nonnegative(),
8619
+ skippedByReason: LedgerWalkSkipCountsSchema,
8620
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8621
+ bounded: boolean(),
8622
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8623
+ });
8525
8624
  /** How many rows a media pass would still act on against a given target — the
8526
8625
  * media lane's denominator AND its residue, from ONE derivation so the two can
8527
8626
  * never disagree. `null` = the count could not be taken. */
@@ -18938,13 +19037,15 @@ var ListGroupsPageSchema = object({
18938
19037
  groups: array(AnalyticsGroupRecordSchema).readonly(),
18939
19038
  nextCursor: string().nullable()
18940
19039
  });
19040
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19041
+ var KEY_EVENTS_MAX_LIMIT = 200;
18941
19042
  var KeyEventQueryInput = object({
18942
19043
  deviceId: number(),
18943
19044
  /** Window lower bound (track firstSeen ≥ since). */
18944
19045
  since: number(),
18945
19046
  /** Window upper bound (track firstSeen ≤ until). */
18946
19047
  until: number(),
18947
- limit: number().int().min(1).max(200).default(50),
19048
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18948
19049
  /** Drop tracks scoring below this importance. */
18949
19050
  minImportance: number().min(0).max(1).optional(),
18950
19051
  /** Restrict to a single class (e.g. 'person'). */
@@ -18966,6 +19067,32 @@ var KeyEventSchema = object({
18966
19067
  ...TrackFlagFields,
18967
19068
  ...TrackRetrainFields
18968
19069
  });
19070
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19071
+ var KeyEventBatchQueryInput = object({
19072
+ deviceIds: array(number()).min(1).max(200),
19073
+ since: number(),
19074
+ until: number(),
19075
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19076
+ * across the set, which would let a busy camera starve a quiet one of its
19077
+ * rows and change what the merged feed contains. */
19078
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19079
+ minImportance: number().min(0).max(1).optional(),
19080
+ classFilter: string().optional()
19081
+ });
19082
+ /**
19083
+ * One camera's key events in a batch answer.
19084
+ *
19085
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19086
+ * error rather than throwing, so a camera whose store read failed and one with
19087
+ * no events in the window were ALREADY indistinguishable per camera — the
19088
+ * batch does not make that worse, and the row keeps the deviceId the single
19089
+ * method's output never carried (the caller used to stamp it from the fan-out
19090
+ * key, which only worked because there was one query per camera).
19091
+ */
19092
+ var KeyEventsForDeviceSchema = object({
19093
+ deviceId: number(),
19094
+ events: array(KeyEventSchema).readonly()
19095
+ });
18969
19096
  object({
18970
19097
  trackId: string(),
18971
19098
  className: string(),
@@ -19037,6 +19164,50 @@ var EventStoreFootprintSchema = object({
19037
19164
  totalBytes: number().int(),
19038
19165
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19039
19166
  });
19167
+ /** Event-media footprint for one {@link MediaFileKind}. */
19168
+ var EventMediaKindFootprintSchema = object({
19169
+ kind: MediaFileKindEnum,
19170
+ /** Media rows of this kind. */
19171
+ rows: number().int(),
19172
+ /** Bytes on disk held by those rows. */
19173
+ bytes: number().int()
19174
+ });
19175
+ /**
19176
+ * The media footprint broken down by KIND — the axis a deletion decision
19177
+ * actually turns on.
19178
+ *
19179
+ * A byte total says how much there is; it cannot say what is safe to remove.
19180
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19181
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19182
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19183
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19184
+ * nothing else, so sizing a deletion means summing per kind.
19185
+ *
19186
+ * ## Why `unaccounted*` exists
19187
+ *
19188
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19189
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19190
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19191
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19192
+ * retired code path, or by a version that knew a kind this one does not) would
19193
+ * otherwise vanish from the total silently, and an operator would delete
19194
+ * against a denominator smaller than the disk.
19195
+ *
19196
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19197
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19198
+ */
19199
+ var EventMediaKindBreakdownSchema = object({
19200
+ /** Every media row in scope, from one unfiltered aggregate. */
19201
+ totalRows: number().int(),
19202
+ /** Every media byte in scope, from that same aggregate. */
19203
+ totalBytes: number().int(),
19204
+ /** Per-kind footprint, ordered by bytes descending. */
19205
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19206
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19207
+ unaccountedRows: number().int(),
19208
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19209
+ unaccountedBytes: number().int()
19210
+ });
19040
19211
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19041
19212
  var EventPruneCountsSchema = object({
19042
19213
  motion: number().int(),
@@ -19191,7 +19362,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19191
19362
  until: number().optional(),
19192
19363
  kinds: array(string()).optional(),
19193
19364
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19194
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19365
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19195
19366
  deviceId: number(),
19196
19367
  since: number(),
19197
19368
  until: number(),
@@ -19240,6 +19411,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19240
19411
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19241
19412
  kind: "query",
19242
19413
  auth: "admin"
19414
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19415
+ kind: "query",
19416
+ auth: "admin"
19243
19417
  }), method(object({
19244
19418
  olderThanMs: number(),
19245
19419
  reason: OpsLogReasonSchema.optional()
@@ -21191,6 +21365,20 @@ method(object({
21191
21365
  error: string().optional()
21192
21366
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21193
21367
  providerId: string(),
21368
+ /**
21369
+ * The location this config is an UNSAVED edit of, when there is one.
21370
+ *
21371
+ * `listLocations` replaces every declared secret with the redaction
21372
+ * sentinel, so the edit modal's form state holds the sentinel for any
21373
+ * credential the operator did not retype — and posting that here
21374
+ * without a way to resolve it makes the provider try to authenticate
21375
+ * as `__camstack_redacted__` and report the operator's own working
21376
+ * password as wrong. Given this id, the orchestrator restores each
21377
+ * sentinel from the stored config (same rule as `upsertLocation`)
21378
+ * before dispatching. Omitted by the "Add location" wizard, where
21379
+ * every value was typed just now and nothing is stored yet.
21380
+ */
21381
+ locationId: string().optional(),
21194
21382
  config: record(string(), unknown())
21195
21383
  }), object({
21196
21384
  ok: boolean(),
@@ -26826,6 +27014,9 @@ method(object({
26826
27014
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
26827
27015
  kind: "query",
26828
27016
  auth: "admin"
27017
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
27018
+ kind: "mutation",
27019
+ auth: "admin"
26829
27020
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
26830
27021
  kind: "mutation",
26831
27022
  auth: "admin"
@@ -27217,7 +27408,26 @@ var SceneMonitorStatusSchema = object({
27217
27408
  monitors: array(SceneMonitorSchema),
27218
27409
  lastFetchedAt: number()
27219
27410
  });
27220
- DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
27411
+ /**
27412
+ * One camera's row in a `listScenesBatch` answer.
27413
+ *
27414
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
27415
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
27416
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
27417
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
27418
+ * configured. Fanned out per camera the difference was visible — one query
27419
+ * errored while the others resolved — and a batch that returned only the rows
27420
+ * it managed would have destroyed it, silently, by making an unreachable camera
27421
+ * indistinguishable from one that answered `monitors: []`.
27422
+ *
27423
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
27424
+ * could not be read"; `status.monitors: []` means "read, and it has none".
27425
+ */
27426
+ var SceneMonitorStatusForDeviceSchema = object({
27427
+ deviceId: number(),
27428
+ status: SceneMonitorStatusSchema.nullable()
27429
+ });
27430
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
27221
27431
  deviceId: number(),
27222
27432
  label: string(),
27223
27433
  roi: MaskRectShapeSchema,
@@ -28541,6 +28751,27 @@ var CameraOccupancySnapshotSchema = object({
28541
28751
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
28542
28752
  });
28543
28753
  /**
28754
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
28755
+ *
28756
+ * THREE outcomes, and the single-camera method could only express two of them
28757
+ * because `snapshot: null` was already spoken for:
28758
+ *
28759
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
28760
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
28761
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
28762
+ * - `read: 'unreadable'` — the owner could not answer for this
28763
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
28764
+ *
28765
+ * Collapsing the last two is the failure this field exists to prevent: a
28766
+ * hydration that threw would otherwise render as an empty Stationary section,
28767
+ * which is a definite claim about a camera nobody could read.
28768
+ */
28769
+ var CameraOccupancySnapshotForDeviceSchema = object({
28770
+ deviceId: number(),
28771
+ read: _enum(["read", "unreadable"]),
28772
+ snapshot: CameraOccupancySnapshotSchema.nullable()
28773
+ });
28774
+ /**
28544
28775
  * Time-series resolution. The history methods return one bucket per
28545
28776
  * step over the requested range. Smaller resolutions cost more
28546
28777
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -28564,7 +28795,7 @@ var HistoryPointSchema = object({
28564
28795
  /** Object count averaged over the bucket (rounded to nearest integer). */
28565
28796
  count: number().int().nonnegative()
28566
28797
  });
28567
- DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
28798
+ DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
28568
28799
  deviceId: number(),
28569
28800
  zoneId: string(),
28570
28801
  className: string().optional()
@@ -31845,6 +32076,12 @@ Object.freeze({
31845
32076
  addonId: null,
31846
32077
  access: "view"
31847
32078
  },
32079
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
32080
+ capName: "pipeline-analytics",
32081
+ capScope: "device",
32082
+ addonId: null,
32083
+ access: "view"
32084
+ },
31848
32085
  "pipelineAnalytics.getEventStoreFootprint": {
31849
32086
  capName: "pipeline-analytics",
31850
32087
  capScope: "device",
@@ -31863,6 +32100,12 @@ Object.freeze({
31863
32100
  addonId: null,
31864
32101
  access: "view"
31865
32102
  },
32103
+ "pipelineAnalytics.getKeyEventsBatch": {
32104
+ capName: "pipeline-analytics",
32105
+ capScope: "device",
32106
+ addonId: null,
32107
+ access: "view"
32108
+ },
31866
32109
  "pipelineAnalytics.getMotionEvents": {
31867
32110
  capName: "pipeline-analytics",
31868
32111
  capScope: "device",
@@ -33039,6 +33282,12 @@ Object.freeze({
33039
33282
  addonId: null,
33040
33283
  access: "view"
33041
33284
  },
33285
+ "recording.reconcileLedgerAgainstDisk": {
33286
+ capName: "recording",
33287
+ capScope: "system",
33288
+ addonId: null,
33289
+ access: "create"
33290
+ },
33042
33291
  "recording.refreshStorageLocationsForMigration": {
33043
33292
  capName: "recording",
33044
33293
  capScope: "system",
@@ -33165,6 +33414,12 @@ Object.freeze({
33165
33414
  addonId: null,
33166
33415
  access: "view"
33167
33416
  },
33417
+ "sceneMonitor.listScenesBatch": {
33418
+ capName: "scene-monitor",
33419
+ capScope: "device",
33420
+ addonId: null,
33421
+ access: "view"
33422
+ },
33168
33423
  "sceneMonitor.recheckNow": {
33169
33424
  capName: "scene-monitor",
33170
33425
  capScope: "device",
@@ -34521,6 +34776,12 @@ Object.freeze({
34521
34776
  addonId: null,
34522
34777
  access: "view"
34523
34778
  },
34779
+ "zoneAnalytics.getCurrentSnapshotBatch": {
34780
+ capName: "zone-analytics",
34781
+ capScope: "device",
34782
+ addonId: null,
34783
+ access: "view"
34784
+ },
34524
34785
  "zoneAnalytics.getUnzonedHistory": {
34525
34786
  capName: "zone-analytics",
34526
34787
  capScope: "device",
@@ -35510,6 +35771,11 @@ Object.freeze({
35510
35771
  form: "single",
35511
35772
  optional: false
35512
35773
  }],
35774
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
35775
+ name: "deviceId",
35776
+ form: "single",
35777
+ optional: true
35778
+ }],
35513
35779
  "pipelineAnalytics.getGroup": [{
35514
35780
  name: "deviceId",
35515
35781
  form: "single",
@@ -35520,6 +35786,11 @@ Object.freeze({
35520
35786
  form: "single",
35521
35787
  optional: false
35522
35788
  }],
35789
+ "pipelineAnalytics.getKeyEventsBatch": [{
35790
+ name: "deviceIds",
35791
+ form: "array",
35792
+ optional: false
35793
+ }],
35523
35794
  "pipelineAnalytics.getMotionEvents": [{
35524
35795
  name: "deviceId",
35525
35796
  form: "single",
@@ -35960,6 +36231,11 @@ Object.freeze({
35960
36231
  form: "single",
35961
36232
  optional: false
35962
36233
  }],
36234
+ "recording.reconcileLedgerAgainstDisk": [{
36235
+ name: "deviceId",
36236
+ form: "single",
36237
+ optional: true
36238
+ }],
35963
36239
  "recording.relocateFootage": [{
35964
36240
  name: "deviceId",
35965
36241
  form: "single",
@@ -36025,6 +36301,11 @@ Object.freeze({
36025
36301
  form: "single",
36026
36302
  optional: false
36027
36303
  }],
36304
+ "sceneMonitor.listScenesBatch": [{
36305
+ name: "deviceIds",
36306
+ form: "array",
36307
+ optional: false
36308
+ }],
36028
36309
  "sceneMonitor.recheckNow": [{
36029
36310
  name: "deviceId",
36030
36311
  form: "single",
@@ -36286,6 +36567,11 @@ Object.freeze({
36286
36567
  form: "single",
36287
36568
  optional: false
36288
36569
  }],
36570
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
36571
+ name: "deviceIds",
36572
+ form: "array",
36573
+ optional: false
36574
+ }],
36289
36575
  "zoneAnalytics.getUnzonedHistory": [{
36290
36576
  name: "deviceId",
36291
36577
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-alexa",
3
- "version": "1.2.53",
3
+ "version": "1.2.55",
4
4
  "description": "Alexa Smart Home Skill export — hub-side OAuth provider + directive handler. The companion AWS Lambda forwards Alexa directives to this addon's /addon/export-alexa/directive endpoint.",
5
5
  "keywords": [
6
6
  "camstack",