@camstack/addon-provider-rtsp 1.2.47 → 1.2.49

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
@@ -8074,6 +8074,20 @@ var RelocateJobSchema = object({
8074
8074
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8075
8075
  */
8076
8076
  rowsReconciled: number().int().nonnegative().optional(),
8077
+ /**
8078
+ * Rows this run FORGOT because the file they name is not on disk.
8079
+ *
8080
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8081
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8082
+ * and the durable row is dropped through the same channel eviction uses. It
8083
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8084
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8085
+ * the same failure as one that quietly skips them (D295).
8086
+ *
8087
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8088
+ * ledger claimed 5.65 GB of footage that no longer existed.
8089
+ */
8090
+ rowsForgotten: number().int().nonnegative().optional(),
8077
8091
  startedAt: number(),
8078
8092
  finishedAt: number().nullable(),
8079
8093
  error: string().nullable()
@@ -8437,6 +8451,91 @@ var RelocateResidueSchema = object({
8437
8451
  segments: number().int().nonnegative(),
8438
8452
  bytes: number().int().nonnegative()
8439
8453
  }).nullable();
8454
+ /**
8455
+ * Ask one location whether its durable hour rows describe the disk — the walk
8456
+ * (D319).
8457
+ *
8458
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8459
+ * missing tool is the question, and the dry run is how they sanity-check the
8460
+ * destructive run before authorising it.
8461
+ */
8462
+ var LedgerWalkInputSchema = object({
8463
+ locationId: string().min(1),
8464
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8465
+ apply: boolean().optional(),
8466
+ /** Narrow to one camera. */
8467
+ deviceId: number().int().positive().optional(),
8468
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8469
+ profiles: array(string().min(1)).optional()
8470
+ });
8471
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8472
+ var LedgerWalkRefusalSchema = _enum([
8473
+ "location-unknown",
8474
+ "source-writable",
8475
+ "no-ledger",
8476
+ "archive-unreadable",
8477
+ "anchor-absent",
8478
+ "anchor-unreadable",
8479
+ "anchor-moved"
8480
+ ]);
8481
+ _enum([
8482
+ "live-tail",
8483
+ "listing-error",
8484
+ "path-mismatch",
8485
+ "durable-refused"
8486
+ ]);
8487
+ /** Every skip reason, always present, always a number — so a reason that never
8488
+ * fired reports as zero rather than absent and the report shape is constant
8489
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8490
+ var LedgerWalkSkipCountsSchema = object({
8491
+ "live-tail": number().int().nonnegative(),
8492
+ "listing-error": number().int().nonnegative(),
8493
+ "path-mismatch": number().int().nonnegative(),
8494
+ "durable-refused": number().int().nonnegative()
8495
+ });
8496
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8497
+ var LedgerWalkDeviceReportSchema = object({
8498
+ deviceId: number().int(),
8499
+ hoursWalked: number().int().nonnegative(),
8500
+ hoursMissing: number().int().nonnegative(),
8501
+ ghostSegments: number().int().nonnegative(),
8502
+ ghostBytes: number().int().nonnegative(),
8503
+ forgottenSegments: number().int().nonnegative(),
8504
+ orphanFiles: number().int().nonnegative()
8505
+ });
8506
+ /**
8507
+ * What one walk claimed, listed, found and (only when armed) forgot.
8508
+ *
8509
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8510
+ * walk that saw a fraction of the location is visible in its own report rather
8511
+ * than in the absence of one.
8512
+ */
8513
+ var LedgerWalkReportSchema = object({
8514
+ locationId: string(),
8515
+ applied: boolean(),
8516
+ refused: LedgerWalkRefusalSchema.nullable(),
8517
+ archiveSegments: number().int().nonnegative().nullable(),
8518
+ archiveBytes: number().int().nonnegative().nullable(),
8519
+ hoursClaimed: number().int().nonnegative(),
8520
+ hoursWalked: number().int().nonnegative(),
8521
+ hoursMissing: number().int().nonnegative(),
8522
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8523
+ listings: number().int().nonnegative(),
8524
+ segmentsClaimed: number().int().nonnegative(),
8525
+ ghostSegments: number().int().nonnegative(),
8526
+ ghostBytes: number().int().nonnegative(),
8527
+ ghostHoursWhole: number().int().nonnegative(),
8528
+ forgottenSegments: number().int().nonnegative(),
8529
+ forgottenBytes: number().int().nonnegative(),
8530
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8531
+ orphanFiles: number().int().nonnegative(),
8532
+ orphanSample: array(string()).readonly(),
8533
+ hoursSkipped: number().int().nonnegative(),
8534
+ skippedByReason: LedgerWalkSkipCountsSchema,
8535
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8536
+ bounded: boolean(),
8537
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8538
+ });
8440
8539
  /** How many rows a media pass would still act on against a given target — the
8441
8540
  * media lane's denominator AND its residue, from ONE derivation so the two can
8442
8541
  * never disagree. `null` = the count could not be taken. */
@@ -19052,13 +19151,15 @@ var ListGroupsPageSchema = object({
19052
19151
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19053
19152
  nextCursor: string().nullable()
19054
19153
  });
19154
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19155
+ var KEY_EVENTS_MAX_LIMIT = 200;
19055
19156
  var KeyEventQueryInput = object({
19056
19157
  deviceId: number(),
19057
19158
  /** Window lower bound (track firstSeen ≥ since). */
19058
19159
  since: number(),
19059
19160
  /** Window upper bound (track firstSeen ≤ until). */
19060
19161
  until: number(),
19061
- limit: number().int().min(1).max(200).default(50),
19162
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19062
19163
  /** Drop tracks scoring below this importance. */
19063
19164
  minImportance: number().min(0).max(1).optional(),
19064
19165
  /** Restrict to a single class (e.g. 'person'). */
@@ -19080,6 +19181,32 @@ var KeyEventSchema = object({
19080
19181
  ...TrackFlagFields,
19081
19182
  ...TrackRetrainFields
19082
19183
  });
19184
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19185
+ var KeyEventBatchQueryInput = object({
19186
+ deviceIds: array(number()).min(1).max(200),
19187
+ since: number(),
19188
+ until: number(),
19189
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19190
+ * across the set, which would let a busy camera starve a quiet one of its
19191
+ * rows and change what the merged feed contains. */
19192
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19193
+ minImportance: number().min(0).max(1).optional(),
19194
+ classFilter: string().optional()
19195
+ });
19196
+ /**
19197
+ * One camera's key events in a batch answer.
19198
+ *
19199
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19200
+ * error rather than throwing, so a camera whose store read failed and one with
19201
+ * no events in the window were ALREADY indistinguishable per camera — the
19202
+ * batch does not make that worse, and the row keeps the deviceId the single
19203
+ * method's output never carried (the caller used to stamp it from the fan-out
19204
+ * key, which only worked because there was one query per camera).
19205
+ */
19206
+ var KeyEventsForDeviceSchema = object({
19207
+ deviceId: number(),
19208
+ events: array(KeyEventSchema).readonly()
19209
+ });
19083
19210
  object({
19084
19211
  trackId: string(),
19085
19212
  className: string(),
@@ -19151,6 +19278,50 @@ var EventStoreFootprintSchema = object({
19151
19278
  totalBytes: number().int(),
19152
19279
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19153
19280
  });
19281
+ /** Event-media footprint for one {@link MediaFileKind}. */
19282
+ var EventMediaKindFootprintSchema = object({
19283
+ kind: MediaFileKindEnum,
19284
+ /** Media rows of this kind. */
19285
+ rows: number().int(),
19286
+ /** Bytes on disk held by those rows. */
19287
+ bytes: number().int()
19288
+ });
19289
+ /**
19290
+ * The media footprint broken down by KIND — the axis a deletion decision
19291
+ * actually turns on.
19292
+ *
19293
+ * A byte total says how much there is; it cannot say what is safe to remove.
19294
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19295
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19296
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19297
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19298
+ * nothing else, so sizing a deletion means summing per kind.
19299
+ *
19300
+ * ## Why `unaccounted*` exists
19301
+ *
19302
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19303
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19304
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19305
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19306
+ * retired code path, or by a version that knew a kind this one does not) would
19307
+ * otherwise vanish from the total silently, and an operator would delete
19308
+ * against a denominator smaller than the disk.
19309
+ *
19310
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19311
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19312
+ */
19313
+ var EventMediaKindBreakdownSchema = object({
19314
+ /** Every media row in scope, from one unfiltered aggregate. */
19315
+ totalRows: number().int(),
19316
+ /** Every media byte in scope, from that same aggregate. */
19317
+ totalBytes: number().int(),
19318
+ /** Per-kind footprint, ordered by bytes descending. */
19319
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19320
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19321
+ unaccountedRows: number().int(),
19322
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19323
+ unaccountedBytes: number().int()
19324
+ });
19154
19325
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19155
19326
  var EventPruneCountsSchema = object({
19156
19327
  motion: number().int(),
@@ -19305,7 +19476,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19305
19476
  until: number().optional(),
19306
19477
  kinds: array(string()).optional(),
19307
19478
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19308
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19479
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19309
19480
  deviceId: number(),
19310
19481
  since: number(),
19311
19482
  until: number(),
@@ -19354,6 +19525,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19354
19525
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19355
19526
  kind: "query",
19356
19527
  auth: "admin"
19528
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19529
+ kind: "query",
19530
+ auth: "admin"
19357
19531
  }), method(object({
19358
19532
  olderThanMs: number(),
19359
19533
  reason: OpsLogReasonSchema.optional()
@@ -21409,6 +21583,20 @@ method(object({
21409
21583
  error: string().optional()
21410
21584
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21411
21585
  providerId: string(),
21586
+ /**
21587
+ * The location this config is an UNSAVED edit of, when there is one.
21588
+ *
21589
+ * `listLocations` replaces every declared secret with the redaction
21590
+ * sentinel, so the edit modal's form state holds the sentinel for any
21591
+ * credential the operator did not retype — and posting that here
21592
+ * without a way to resolve it makes the provider try to authenticate
21593
+ * as `__camstack_redacted__` and report the operator's own working
21594
+ * password as wrong. Given this id, the orchestrator restores each
21595
+ * sentinel from the stored config (same rule as `upsertLocation`)
21596
+ * before dispatching. Omitted by the "Add location" wizard, where
21597
+ * every value was typed just now and nothing is stored yet.
21598
+ */
21599
+ locationId: string().optional(),
21412
21600
  config: record(string(), unknown())
21413
21601
  }), object({
21414
21602
  ok: boolean(),
@@ -28767,6 +28955,9 @@ method(object({
28767
28955
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28768
28956
  kind: "query",
28769
28957
  auth: "admin"
28958
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
28959
+ kind: "mutation",
28960
+ auth: "admin"
28770
28961
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28771
28962
  kind: "mutation",
28772
28963
  auth: "admin"
@@ -29158,6 +29349,25 @@ var SceneMonitorStatusSchema = object({
29158
29349
  monitors: array(SceneMonitorSchema),
29159
29350
  lastFetchedAt: number()
29160
29351
  });
29352
+ /**
29353
+ * One camera's row in a `listScenesBatch` answer.
29354
+ *
29355
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29356
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29357
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29358
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29359
+ * configured. Fanned out per camera the difference was visible — one query
29360
+ * errored while the others resolved — and a batch that returned only the rows
29361
+ * it managed would have destroyed it, silently, by making an unreachable camera
29362
+ * indistinguishable from one that answered `monitors: []`.
29363
+ *
29364
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29365
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29366
+ */
29367
+ var SceneMonitorStatusForDeviceSchema = object({
29368
+ deviceId: number(),
29369
+ status: SceneMonitorStatusSchema.nullable()
29370
+ });
29161
29371
  var sceneMonitorCapability = {
29162
29372
  name: "scene-monitor",
29163
29373
  scope: "device",
@@ -29167,6 +29377,22 @@ var sceneMonitorCapability = {
29167
29377
  deviceTypes: [DeviceType.Camera],
29168
29378
  methods: {
29169
29379
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29380
+ /**
29381
+ * The same answer, for a SET of cameras, in one round trip.
29382
+ *
29383
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29384
+ * poll behind the push slice. Fanned out client-side that was one query
29385
+ * per camera — 29 round trips through the browser, the hub and the
29386
+ * post-analysis runner every 30 seconds to read an in-memory map the
29387
+ * owner had already merged. The work is unchanged (`statusFor` per
29388
+ * device, all in-process at the owner); what collapses is the transport.
29389
+ *
29390
+ * A camera that cannot answer still gets a row, with `status: null` —
29391
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29392
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29393
+ * tell which two are missing, or that any are.
29394
+ */
29395
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29170
29396
  createScene: method(object({
29171
29397
  deviceId: number(),
29172
29398
  label: string(),
@@ -31002,6 +31228,27 @@ var CameraOccupancySnapshotSchema = object({
31002
31228
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31003
31229
  });
31004
31230
  /**
31231
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31232
+ *
31233
+ * THREE outcomes, and the single-camera method could only express two of them
31234
+ * because `snapshot: null` was already spoken for:
31235
+ *
31236
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31237
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31238
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31239
+ * - `read: 'unreadable'` — the owner could not answer for this
31240
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31241
+ *
31242
+ * Collapsing the last two is the failure this field exists to prevent: a
31243
+ * hydration that threw would otherwise render as an empty Stationary section,
31244
+ * which is a definite claim about a camera nobody could read.
31245
+ */
31246
+ var CameraOccupancySnapshotForDeviceSchema = object({
31247
+ deviceId: number(),
31248
+ read: _enum(["read", "unreadable"]),
31249
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31250
+ });
31251
+ /**
31005
31252
  * Time-series resolution. The history methods return one bucket per
31006
31253
  * step over the requested range. Smaller resolutions cost more
31007
31254
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31058,6 +31305,20 @@ var zoneAnalyticsCapability = {
31058
31305
  * (no inference result emitted since boot or since binding was
31059
31306
  * activated). */
31060
31307
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31308
+ /**
31309
+ * The same snapshot, for a SET of cameras, in one round trip.
31310
+ *
31311
+ * The Events page's Stationary section polls this every 15s for every
31312
+ * selected camera. Fanned out client-side that is one query per camera to
31313
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31314
+ * costs everything. Batched, N transports become one and the per-device
31315
+ * work is unchanged.
31316
+ *
31317
+ * Every requested deviceId gets a row, tagged `read` — see
31318
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31319
+ * not answer for is `'unreadable'`, never an empty reading.
31320
+ */
31321
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31061
31322
  /** Time-series object count inside one zone. `className` optional —
31062
31323
  * omit to count every class in the zone. */
31063
31324
  getZoneHistory: method(object({
@@ -35521,6 +35782,12 @@ Object.freeze({
35521
35782
  addonId: null,
35522
35783
  access: "view"
35523
35784
  },
35785
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
35786
+ capName: "pipeline-analytics",
35787
+ capScope: "device",
35788
+ addonId: null,
35789
+ access: "view"
35790
+ },
35524
35791
  "pipelineAnalytics.getEventStoreFootprint": {
35525
35792
  capName: "pipeline-analytics",
35526
35793
  capScope: "device",
@@ -35539,6 +35806,12 @@ Object.freeze({
35539
35806
  addonId: null,
35540
35807
  access: "view"
35541
35808
  },
35809
+ "pipelineAnalytics.getKeyEventsBatch": {
35810
+ capName: "pipeline-analytics",
35811
+ capScope: "device",
35812
+ addonId: null,
35813
+ access: "view"
35814
+ },
35542
35815
  "pipelineAnalytics.getMotionEvents": {
35543
35816
  capName: "pipeline-analytics",
35544
35817
  capScope: "device",
@@ -36715,6 +36988,12 @@ Object.freeze({
36715
36988
  addonId: null,
36716
36989
  access: "view"
36717
36990
  },
36991
+ "recording.reconcileLedgerAgainstDisk": {
36992
+ capName: "recording",
36993
+ capScope: "system",
36994
+ addonId: null,
36995
+ access: "create"
36996
+ },
36718
36997
  "recording.refreshStorageLocationsForMigration": {
36719
36998
  capName: "recording",
36720
36999
  capScope: "system",
@@ -36841,6 +37120,12 @@ Object.freeze({
36841
37120
  addonId: null,
36842
37121
  access: "view"
36843
37122
  },
37123
+ "sceneMonitor.listScenesBatch": {
37124
+ capName: "scene-monitor",
37125
+ capScope: "device",
37126
+ addonId: null,
37127
+ access: "view"
37128
+ },
36844
37129
  "sceneMonitor.recheckNow": {
36845
37130
  capName: "scene-monitor",
36846
37131
  capScope: "device",
@@ -38197,6 +38482,12 @@ Object.freeze({
38197
38482
  addonId: null,
38198
38483
  access: "view"
38199
38484
  },
38485
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38486
+ capName: "zone-analytics",
38487
+ capScope: "device",
38488
+ addonId: null,
38489
+ access: "view"
38490
+ },
38200
38491
  "zoneAnalytics.getUnzonedHistory": {
38201
38492
  capName: "zone-analytics",
38202
38493
  capScope: "device",
@@ -39186,6 +39477,11 @@ Object.freeze({
39186
39477
  form: "single",
39187
39478
  optional: false
39188
39479
  }],
39480
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
39481
+ name: "deviceId",
39482
+ form: "single",
39483
+ optional: true
39484
+ }],
39189
39485
  "pipelineAnalytics.getGroup": [{
39190
39486
  name: "deviceId",
39191
39487
  form: "single",
@@ -39196,6 +39492,11 @@ Object.freeze({
39196
39492
  form: "single",
39197
39493
  optional: false
39198
39494
  }],
39495
+ "pipelineAnalytics.getKeyEventsBatch": [{
39496
+ name: "deviceIds",
39497
+ form: "array",
39498
+ optional: false
39499
+ }],
39199
39500
  "pipelineAnalytics.getMotionEvents": [{
39200
39501
  name: "deviceId",
39201
39502
  form: "single",
@@ -39636,6 +39937,11 @@ Object.freeze({
39636
39937
  form: "single",
39637
39938
  optional: false
39638
39939
  }],
39940
+ "recording.reconcileLedgerAgainstDisk": [{
39941
+ name: "deviceId",
39942
+ form: "single",
39943
+ optional: true
39944
+ }],
39639
39945
  "recording.relocateFootage": [{
39640
39946
  name: "deviceId",
39641
39947
  form: "single",
@@ -39701,6 +40007,11 @@ Object.freeze({
39701
40007
  form: "single",
39702
40008
  optional: false
39703
40009
  }],
40010
+ "sceneMonitor.listScenesBatch": [{
40011
+ name: "deviceIds",
40012
+ form: "array",
40013
+ optional: false
40014
+ }],
39704
40015
  "sceneMonitor.recheckNow": [{
39705
40016
  name: "deviceId",
39706
40017
  form: "single",
@@ -39962,6 +40273,11 @@ Object.freeze({
39962
40273
  form: "single",
39963
40274
  optional: false
39964
40275
  }],
40276
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40277
+ name: "deviceIds",
40278
+ form: "array",
40279
+ optional: false
40280
+ }],
39965
40281
  "zoneAnalytics.getUnzonedHistory": [{
39966
40282
  name: "deviceId",
39967
40283
  form: "single",
package/dist/addon.mjs CHANGED
@@ -8050,6 +8050,20 @@ var RelocateJobSchema = object({
8050
8050
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8051
8051
  */
8052
8052
  rowsReconciled: number().int().nonnegative().optional(),
8053
+ /**
8054
+ * Rows this run FORGOT because the file they name is not on disk.
8055
+ *
8056
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8057
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8058
+ * and the durable row is dropped through the same channel eviction uses. It
8059
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8060
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8061
+ * the same failure as one that quietly skips them (D295).
8062
+ *
8063
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8064
+ * ledger claimed 5.65 GB of footage that no longer existed.
8065
+ */
8066
+ rowsForgotten: number().int().nonnegative().optional(),
8053
8067
  startedAt: number(),
8054
8068
  finishedAt: number().nullable(),
8055
8069
  error: string().nullable()
@@ -8413,6 +8427,91 @@ var RelocateResidueSchema = object({
8413
8427
  segments: number().int().nonnegative(),
8414
8428
  bytes: number().int().nonnegative()
8415
8429
  }).nullable();
8430
+ /**
8431
+ * Ask one location whether its durable hour rows describe the disk — the walk
8432
+ * (D319).
8433
+ *
8434
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8435
+ * missing tool is the question, and the dry run is how they sanity-check the
8436
+ * destructive run before authorising it.
8437
+ */
8438
+ var LedgerWalkInputSchema = object({
8439
+ locationId: string().min(1),
8440
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8441
+ apply: boolean().optional(),
8442
+ /** Narrow to one camera. */
8443
+ deviceId: number().int().positive().optional(),
8444
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8445
+ profiles: array(string().min(1)).optional()
8446
+ });
8447
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8448
+ var LedgerWalkRefusalSchema = _enum([
8449
+ "location-unknown",
8450
+ "source-writable",
8451
+ "no-ledger",
8452
+ "archive-unreadable",
8453
+ "anchor-absent",
8454
+ "anchor-unreadable",
8455
+ "anchor-moved"
8456
+ ]);
8457
+ _enum([
8458
+ "live-tail",
8459
+ "listing-error",
8460
+ "path-mismatch",
8461
+ "durable-refused"
8462
+ ]);
8463
+ /** Every skip reason, always present, always a number — so a reason that never
8464
+ * fired reports as zero rather than absent and the report shape is constant
8465
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8466
+ var LedgerWalkSkipCountsSchema = object({
8467
+ "live-tail": number().int().nonnegative(),
8468
+ "listing-error": number().int().nonnegative(),
8469
+ "path-mismatch": number().int().nonnegative(),
8470
+ "durable-refused": number().int().nonnegative()
8471
+ });
8472
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8473
+ var LedgerWalkDeviceReportSchema = object({
8474
+ deviceId: number().int(),
8475
+ hoursWalked: number().int().nonnegative(),
8476
+ hoursMissing: number().int().nonnegative(),
8477
+ ghostSegments: number().int().nonnegative(),
8478
+ ghostBytes: number().int().nonnegative(),
8479
+ forgottenSegments: number().int().nonnegative(),
8480
+ orphanFiles: number().int().nonnegative()
8481
+ });
8482
+ /**
8483
+ * What one walk claimed, listed, found and (only when armed) forgot.
8484
+ *
8485
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8486
+ * walk that saw a fraction of the location is visible in its own report rather
8487
+ * than in the absence of one.
8488
+ */
8489
+ var LedgerWalkReportSchema = object({
8490
+ locationId: string(),
8491
+ applied: boolean(),
8492
+ refused: LedgerWalkRefusalSchema.nullable(),
8493
+ archiveSegments: number().int().nonnegative().nullable(),
8494
+ archiveBytes: number().int().nonnegative().nullable(),
8495
+ hoursClaimed: number().int().nonnegative(),
8496
+ hoursWalked: number().int().nonnegative(),
8497
+ hoursMissing: number().int().nonnegative(),
8498
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8499
+ listings: number().int().nonnegative(),
8500
+ segmentsClaimed: number().int().nonnegative(),
8501
+ ghostSegments: number().int().nonnegative(),
8502
+ ghostBytes: number().int().nonnegative(),
8503
+ ghostHoursWhole: number().int().nonnegative(),
8504
+ forgottenSegments: number().int().nonnegative(),
8505
+ forgottenBytes: number().int().nonnegative(),
8506
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8507
+ orphanFiles: number().int().nonnegative(),
8508
+ orphanSample: array(string()).readonly(),
8509
+ hoursSkipped: number().int().nonnegative(),
8510
+ skippedByReason: LedgerWalkSkipCountsSchema,
8511
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8512
+ bounded: boolean(),
8513
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8514
+ });
8416
8515
  /** How many rows a media pass would still act on against a given target — the
8417
8516
  * media lane's denominator AND its residue, from ONE derivation so the two can
8418
8517
  * never disagree. `null` = the count could not be taken. */
@@ -19028,13 +19127,15 @@ var ListGroupsPageSchema = object({
19028
19127
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19029
19128
  nextCursor: string().nullable()
19030
19129
  });
19130
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19131
+ var KEY_EVENTS_MAX_LIMIT = 200;
19031
19132
  var KeyEventQueryInput = object({
19032
19133
  deviceId: number(),
19033
19134
  /** Window lower bound (track firstSeen ≥ since). */
19034
19135
  since: number(),
19035
19136
  /** Window upper bound (track firstSeen ≤ until). */
19036
19137
  until: number(),
19037
- limit: number().int().min(1).max(200).default(50),
19138
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19038
19139
  /** Drop tracks scoring below this importance. */
19039
19140
  minImportance: number().min(0).max(1).optional(),
19040
19141
  /** Restrict to a single class (e.g. 'person'). */
@@ -19056,6 +19157,32 @@ var KeyEventSchema = object({
19056
19157
  ...TrackFlagFields,
19057
19158
  ...TrackRetrainFields
19058
19159
  });
19160
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19161
+ var KeyEventBatchQueryInput = object({
19162
+ deviceIds: array(number()).min(1).max(200),
19163
+ since: number(),
19164
+ until: number(),
19165
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19166
+ * across the set, which would let a busy camera starve a quiet one of its
19167
+ * rows and change what the merged feed contains. */
19168
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19169
+ minImportance: number().min(0).max(1).optional(),
19170
+ classFilter: string().optional()
19171
+ });
19172
+ /**
19173
+ * One camera's key events in a batch answer.
19174
+ *
19175
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19176
+ * error rather than throwing, so a camera whose store read failed and one with
19177
+ * no events in the window were ALREADY indistinguishable per camera — the
19178
+ * batch does not make that worse, and the row keeps the deviceId the single
19179
+ * method's output never carried (the caller used to stamp it from the fan-out
19180
+ * key, which only worked because there was one query per camera).
19181
+ */
19182
+ var KeyEventsForDeviceSchema = object({
19183
+ deviceId: number(),
19184
+ events: array(KeyEventSchema).readonly()
19185
+ });
19059
19186
  object({
19060
19187
  trackId: string(),
19061
19188
  className: string(),
@@ -19127,6 +19254,50 @@ var EventStoreFootprintSchema = object({
19127
19254
  totalBytes: number().int(),
19128
19255
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19129
19256
  });
19257
+ /** Event-media footprint for one {@link MediaFileKind}. */
19258
+ var EventMediaKindFootprintSchema = object({
19259
+ kind: MediaFileKindEnum,
19260
+ /** Media rows of this kind. */
19261
+ rows: number().int(),
19262
+ /** Bytes on disk held by those rows. */
19263
+ bytes: number().int()
19264
+ });
19265
+ /**
19266
+ * The media footprint broken down by KIND — the axis a deletion decision
19267
+ * actually turns on.
19268
+ *
19269
+ * A byte total says how much there is; it cannot say what is safe to remove.
19270
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19271
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19272
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19273
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19274
+ * nothing else, so sizing a deletion means summing per kind.
19275
+ *
19276
+ * ## Why `unaccounted*` exists
19277
+ *
19278
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19279
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19280
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19281
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19282
+ * retired code path, or by a version that knew a kind this one does not) would
19283
+ * otherwise vanish from the total silently, and an operator would delete
19284
+ * against a denominator smaller than the disk.
19285
+ *
19286
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19287
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19288
+ */
19289
+ var EventMediaKindBreakdownSchema = object({
19290
+ /** Every media row in scope, from one unfiltered aggregate. */
19291
+ totalRows: number().int(),
19292
+ /** Every media byte in scope, from that same aggregate. */
19293
+ totalBytes: number().int(),
19294
+ /** Per-kind footprint, ordered by bytes descending. */
19295
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19296
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19297
+ unaccountedRows: number().int(),
19298
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19299
+ unaccountedBytes: number().int()
19300
+ });
19130
19301
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19131
19302
  var EventPruneCountsSchema = object({
19132
19303
  motion: number().int(),
@@ -19281,7 +19452,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19281
19452
  until: number().optional(),
19282
19453
  kinds: array(string()).optional(),
19283
19454
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19284
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19455
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19285
19456
  deviceId: number(),
19286
19457
  since: number(),
19287
19458
  until: number(),
@@ -19330,6 +19501,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19330
19501
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19331
19502
  kind: "query",
19332
19503
  auth: "admin"
19504
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19505
+ kind: "query",
19506
+ auth: "admin"
19333
19507
  }), method(object({
19334
19508
  olderThanMs: number(),
19335
19509
  reason: OpsLogReasonSchema.optional()
@@ -21385,6 +21559,20 @@ method(object({
21385
21559
  error: string().optional()
21386
21560
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21387
21561
  providerId: string(),
21562
+ /**
21563
+ * The location this config is an UNSAVED edit of, when there is one.
21564
+ *
21565
+ * `listLocations` replaces every declared secret with the redaction
21566
+ * sentinel, so the edit modal's form state holds the sentinel for any
21567
+ * credential the operator did not retype — and posting that here
21568
+ * without a way to resolve it makes the provider try to authenticate
21569
+ * as `__camstack_redacted__` and report the operator's own working
21570
+ * password as wrong. Given this id, the orchestrator restores each
21571
+ * sentinel from the stored config (same rule as `upsertLocation`)
21572
+ * before dispatching. Omitted by the "Add location" wizard, where
21573
+ * every value was typed just now and nothing is stored yet.
21574
+ */
21575
+ locationId: string().optional(),
21388
21576
  config: record(string(), unknown())
21389
21577
  }), object({
21390
21578
  ok: boolean(),
@@ -28743,6 +28931,9 @@ method(object({
28743
28931
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28744
28932
  kind: "query",
28745
28933
  auth: "admin"
28934
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
28935
+ kind: "mutation",
28936
+ auth: "admin"
28746
28937
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28747
28938
  kind: "mutation",
28748
28939
  auth: "admin"
@@ -29134,6 +29325,25 @@ var SceneMonitorStatusSchema = object({
29134
29325
  monitors: array(SceneMonitorSchema),
29135
29326
  lastFetchedAt: number()
29136
29327
  });
29328
+ /**
29329
+ * One camera's row in a `listScenesBatch` answer.
29330
+ *
29331
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29332
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29333
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29334
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29335
+ * configured. Fanned out per camera the difference was visible — one query
29336
+ * errored while the others resolved — and a batch that returned only the rows
29337
+ * it managed would have destroyed it, silently, by making an unreachable camera
29338
+ * indistinguishable from one that answered `monitors: []`.
29339
+ *
29340
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29341
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29342
+ */
29343
+ var SceneMonitorStatusForDeviceSchema = object({
29344
+ deviceId: number(),
29345
+ status: SceneMonitorStatusSchema.nullable()
29346
+ });
29137
29347
  var sceneMonitorCapability = {
29138
29348
  name: "scene-monitor",
29139
29349
  scope: "device",
@@ -29143,6 +29353,22 @@ var sceneMonitorCapability = {
29143
29353
  deviceTypes: [DeviceType.Camera],
29144
29354
  methods: {
29145
29355
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29356
+ /**
29357
+ * The same answer, for a SET of cameras, in one round trip.
29358
+ *
29359
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29360
+ * poll behind the push slice. Fanned out client-side that was one query
29361
+ * per camera — 29 round trips through the browser, the hub and the
29362
+ * post-analysis runner every 30 seconds to read an in-memory map the
29363
+ * owner had already merged. The work is unchanged (`statusFor` per
29364
+ * device, all in-process at the owner); what collapses is the transport.
29365
+ *
29366
+ * A camera that cannot answer still gets a row, with `status: null` —
29367
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29368
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29369
+ * tell which two are missing, or that any are.
29370
+ */
29371
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29146
29372
  createScene: method(object({
29147
29373
  deviceId: number(),
29148
29374
  label: string(),
@@ -30978,6 +31204,27 @@ var CameraOccupancySnapshotSchema = object({
30978
31204
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
30979
31205
  });
30980
31206
  /**
31207
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31208
+ *
31209
+ * THREE outcomes, and the single-camera method could only express two of them
31210
+ * because `snapshot: null` was already spoken for:
31211
+ *
31212
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31213
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31214
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31215
+ * - `read: 'unreadable'` — the owner could not answer for this
31216
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31217
+ *
31218
+ * Collapsing the last two is the failure this field exists to prevent: a
31219
+ * hydration that threw would otherwise render as an empty Stationary section,
31220
+ * which is a definite claim about a camera nobody could read.
31221
+ */
31222
+ var CameraOccupancySnapshotForDeviceSchema = object({
31223
+ deviceId: number(),
31224
+ read: _enum(["read", "unreadable"]),
31225
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31226
+ });
31227
+ /**
30981
31228
  * Time-series resolution. The history methods return one bucket per
30982
31229
  * step over the requested range. Smaller resolutions cost more
30983
31230
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31034,6 +31281,20 @@ var zoneAnalyticsCapability = {
31034
31281
  * (no inference result emitted since boot or since binding was
31035
31282
  * activated). */
31036
31283
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31284
+ /**
31285
+ * The same snapshot, for a SET of cameras, in one round trip.
31286
+ *
31287
+ * The Events page's Stationary section polls this every 15s for every
31288
+ * selected camera. Fanned out client-side that is one query per camera to
31289
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31290
+ * costs everything. Batched, N transports become one and the per-device
31291
+ * work is unchanged.
31292
+ *
31293
+ * Every requested deviceId gets a row, tagged `read` — see
31294
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31295
+ * not answer for is `'unreadable'`, never an empty reading.
31296
+ */
31297
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31037
31298
  /** Time-series object count inside one zone. `className` optional —
31038
31299
  * omit to count every class in the zone. */
31039
31300
  getZoneHistory: method(object({
@@ -35497,6 +35758,12 @@ Object.freeze({
35497
35758
  addonId: null,
35498
35759
  access: "view"
35499
35760
  },
35761
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
35762
+ capName: "pipeline-analytics",
35763
+ capScope: "device",
35764
+ addonId: null,
35765
+ access: "view"
35766
+ },
35500
35767
  "pipelineAnalytics.getEventStoreFootprint": {
35501
35768
  capName: "pipeline-analytics",
35502
35769
  capScope: "device",
@@ -35515,6 +35782,12 @@ Object.freeze({
35515
35782
  addonId: null,
35516
35783
  access: "view"
35517
35784
  },
35785
+ "pipelineAnalytics.getKeyEventsBatch": {
35786
+ capName: "pipeline-analytics",
35787
+ capScope: "device",
35788
+ addonId: null,
35789
+ access: "view"
35790
+ },
35518
35791
  "pipelineAnalytics.getMotionEvents": {
35519
35792
  capName: "pipeline-analytics",
35520
35793
  capScope: "device",
@@ -36691,6 +36964,12 @@ Object.freeze({
36691
36964
  addonId: null,
36692
36965
  access: "view"
36693
36966
  },
36967
+ "recording.reconcileLedgerAgainstDisk": {
36968
+ capName: "recording",
36969
+ capScope: "system",
36970
+ addonId: null,
36971
+ access: "create"
36972
+ },
36694
36973
  "recording.refreshStorageLocationsForMigration": {
36695
36974
  capName: "recording",
36696
36975
  capScope: "system",
@@ -36817,6 +37096,12 @@ Object.freeze({
36817
37096
  addonId: null,
36818
37097
  access: "view"
36819
37098
  },
37099
+ "sceneMonitor.listScenesBatch": {
37100
+ capName: "scene-monitor",
37101
+ capScope: "device",
37102
+ addonId: null,
37103
+ access: "view"
37104
+ },
36820
37105
  "sceneMonitor.recheckNow": {
36821
37106
  capName: "scene-monitor",
36822
37107
  capScope: "device",
@@ -38173,6 +38458,12 @@ Object.freeze({
38173
38458
  addonId: null,
38174
38459
  access: "view"
38175
38460
  },
38461
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38462
+ capName: "zone-analytics",
38463
+ capScope: "device",
38464
+ addonId: null,
38465
+ access: "view"
38466
+ },
38176
38467
  "zoneAnalytics.getUnzonedHistory": {
38177
38468
  capName: "zone-analytics",
38178
38469
  capScope: "device",
@@ -39162,6 +39453,11 @@ Object.freeze({
39162
39453
  form: "single",
39163
39454
  optional: false
39164
39455
  }],
39456
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
39457
+ name: "deviceId",
39458
+ form: "single",
39459
+ optional: true
39460
+ }],
39165
39461
  "pipelineAnalytics.getGroup": [{
39166
39462
  name: "deviceId",
39167
39463
  form: "single",
@@ -39172,6 +39468,11 @@ Object.freeze({
39172
39468
  form: "single",
39173
39469
  optional: false
39174
39470
  }],
39471
+ "pipelineAnalytics.getKeyEventsBatch": [{
39472
+ name: "deviceIds",
39473
+ form: "array",
39474
+ optional: false
39475
+ }],
39175
39476
  "pipelineAnalytics.getMotionEvents": [{
39176
39477
  name: "deviceId",
39177
39478
  form: "single",
@@ -39612,6 +39913,11 @@ Object.freeze({
39612
39913
  form: "single",
39613
39914
  optional: false
39614
39915
  }],
39916
+ "recording.reconcileLedgerAgainstDisk": [{
39917
+ name: "deviceId",
39918
+ form: "single",
39919
+ optional: true
39920
+ }],
39615
39921
  "recording.relocateFootage": [{
39616
39922
  name: "deviceId",
39617
39923
  form: "single",
@@ -39677,6 +39983,11 @@ Object.freeze({
39677
39983
  form: "single",
39678
39984
  optional: false
39679
39985
  }],
39986
+ "sceneMonitor.listScenesBatch": [{
39987
+ name: "deviceIds",
39988
+ form: "array",
39989
+ optional: false
39990
+ }],
39680
39991
  "sceneMonitor.recheckNow": [{
39681
39992
  name: "deviceId",
39682
39993
  form: "single",
@@ -39938,6 +40249,11 @@ Object.freeze({
39938
40249
  form: "single",
39939
40250
  optional: false
39940
40251
  }],
40252
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40253
+ name: "deviceIds",
40254
+ form: "array",
40255
+ optional: false
40256
+ }],
39941
40257
  "zoneAnalytics.getUnzonedHistory": [{
39942
40258
  name: "deviceId",
39943
40259
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rtsp",
3
- "version": "1.2.47",
3
+ "version": "1.2.49",
4
4
  "description": "Generic RTSP camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",