@camstack/addon-provider-rtsp 1.2.48 → 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(),
@@ -19349,7 +19476,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19349
19476
  until: number().optional(),
19350
19477
  kinds: array(string()).optional(),
19351
19478
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19352
- }), 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({
19353
19480
  deviceId: number(),
19354
19481
  since: number(),
19355
19482
  until: number(),
@@ -28828,6 +28955,9 @@ method(object({
28828
28955
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28829
28956
  kind: "query",
28830
28957
  auth: "admin"
28958
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
28959
+ kind: "mutation",
28960
+ auth: "admin"
28831
28961
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28832
28962
  kind: "mutation",
28833
28963
  auth: "admin"
@@ -29219,6 +29349,25 @@ var SceneMonitorStatusSchema = object({
29219
29349
  monitors: array(SceneMonitorSchema),
29220
29350
  lastFetchedAt: number()
29221
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
+ });
29222
29371
  var sceneMonitorCapability = {
29223
29372
  name: "scene-monitor",
29224
29373
  scope: "device",
@@ -29228,6 +29377,22 @@ var sceneMonitorCapability = {
29228
29377
  deviceTypes: [DeviceType.Camera],
29229
29378
  methods: {
29230
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()),
29231
29396
  createScene: method(object({
29232
29397
  deviceId: number(),
29233
29398
  label: string(),
@@ -31063,6 +31228,27 @@ var CameraOccupancySnapshotSchema = object({
31063
31228
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31064
31229
  });
31065
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
+ /**
31066
31252
  * Time-series resolution. The history methods return one bucket per
31067
31253
  * step over the requested range. Smaller resolutions cost more
31068
31254
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31119,6 +31305,20 @@ var zoneAnalyticsCapability = {
31119
31305
  * (no inference result emitted since boot or since binding was
31120
31306
  * activated). */
31121
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()),
31122
31322
  /** Time-series object count inside one zone. `className` optional —
31123
31323
  * omit to count every class in the zone. */
31124
31324
  getZoneHistory: method(object({
@@ -35606,6 +35806,12 @@ Object.freeze({
35606
35806
  addonId: null,
35607
35807
  access: "view"
35608
35808
  },
35809
+ "pipelineAnalytics.getKeyEventsBatch": {
35810
+ capName: "pipeline-analytics",
35811
+ capScope: "device",
35812
+ addonId: null,
35813
+ access: "view"
35814
+ },
35609
35815
  "pipelineAnalytics.getMotionEvents": {
35610
35816
  capName: "pipeline-analytics",
35611
35817
  capScope: "device",
@@ -36782,6 +36988,12 @@ Object.freeze({
36782
36988
  addonId: null,
36783
36989
  access: "view"
36784
36990
  },
36991
+ "recording.reconcileLedgerAgainstDisk": {
36992
+ capName: "recording",
36993
+ capScope: "system",
36994
+ addonId: null,
36995
+ access: "create"
36996
+ },
36785
36997
  "recording.refreshStorageLocationsForMigration": {
36786
36998
  capName: "recording",
36787
36999
  capScope: "system",
@@ -36908,6 +37120,12 @@ Object.freeze({
36908
37120
  addonId: null,
36909
37121
  access: "view"
36910
37122
  },
37123
+ "sceneMonitor.listScenesBatch": {
37124
+ capName: "scene-monitor",
37125
+ capScope: "device",
37126
+ addonId: null,
37127
+ access: "view"
37128
+ },
36911
37129
  "sceneMonitor.recheckNow": {
36912
37130
  capName: "scene-monitor",
36913
37131
  capScope: "device",
@@ -38264,6 +38482,12 @@ Object.freeze({
38264
38482
  addonId: null,
38265
38483
  access: "view"
38266
38484
  },
38485
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38486
+ capName: "zone-analytics",
38487
+ capScope: "device",
38488
+ addonId: null,
38489
+ access: "view"
38490
+ },
38267
38491
  "zoneAnalytics.getUnzonedHistory": {
38268
38492
  capName: "zone-analytics",
38269
38493
  capScope: "device",
@@ -39268,6 +39492,11 @@ Object.freeze({
39268
39492
  form: "single",
39269
39493
  optional: false
39270
39494
  }],
39495
+ "pipelineAnalytics.getKeyEventsBatch": [{
39496
+ name: "deviceIds",
39497
+ form: "array",
39498
+ optional: false
39499
+ }],
39271
39500
  "pipelineAnalytics.getMotionEvents": [{
39272
39501
  name: "deviceId",
39273
39502
  form: "single",
@@ -39708,6 +39937,11 @@ Object.freeze({
39708
39937
  form: "single",
39709
39938
  optional: false
39710
39939
  }],
39940
+ "recording.reconcileLedgerAgainstDisk": [{
39941
+ name: "deviceId",
39942
+ form: "single",
39943
+ optional: true
39944
+ }],
39711
39945
  "recording.relocateFootage": [{
39712
39946
  name: "deviceId",
39713
39947
  form: "single",
@@ -39773,6 +40007,11 @@ Object.freeze({
39773
40007
  form: "single",
39774
40008
  optional: false
39775
40009
  }],
40010
+ "sceneMonitor.listScenesBatch": [{
40011
+ name: "deviceIds",
40012
+ form: "array",
40013
+ optional: false
40014
+ }],
39776
40015
  "sceneMonitor.recheckNow": [{
39777
40016
  name: "deviceId",
39778
40017
  form: "single",
@@ -40034,6 +40273,11 @@ Object.freeze({
40034
40273
  form: "single",
40035
40274
  optional: false
40036
40275
  }],
40276
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40277
+ name: "deviceIds",
40278
+ form: "array",
40279
+ optional: false
40280
+ }],
40037
40281
  "zoneAnalytics.getUnzonedHistory": [{
40038
40282
  name: "deviceId",
40039
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(),
@@ -19325,7 +19452,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19325
19452
  until: number().optional(),
19326
19453
  kinds: array(string()).optional(),
19327
19454
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19328
- }), 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({
19329
19456
  deviceId: number(),
19330
19457
  since: number(),
19331
19458
  until: number(),
@@ -28804,6 +28931,9 @@ method(object({
28804
28931
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28805
28932
  kind: "query",
28806
28933
  auth: "admin"
28934
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
28935
+ kind: "mutation",
28936
+ auth: "admin"
28807
28937
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28808
28938
  kind: "mutation",
28809
28939
  auth: "admin"
@@ -29195,6 +29325,25 @@ var SceneMonitorStatusSchema = object({
29195
29325
  monitors: array(SceneMonitorSchema),
29196
29326
  lastFetchedAt: number()
29197
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
+ });
29198
29347
  var sceneMonitorCapability = {
29199
29348
  name: "scene-monitor",
29200
29349
  scope: "device",
@@ -29204,6 +29353,22 @@ var sceneMonitorCapability = {
29204
29353
  deviceTypes: [DeviceType.Camera],
29205
29354
  methods: {
29206
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()),
29207
29372
  createScene: method(object({
29208
29373
  deviceId: number(),
29209
29374
  label: string(),
@@ -31039,6 +31204,27 @@ var CameraOccupancySnapshotSchema = object({
31039
31204
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31040
31205
  });
31041
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
+ /**
31042
31228
  * Time-series resolution. The history methods return one bucket per
31043
31229
  * step over the requested range. Smaller resolutions cost more
31044
31230
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31095,6 +31281,20 @@ var zoneAnalyticsCapability = {
31095
31281
  * (no inference result emitted since boot or since binding was
31096
31282
  * activated). */
31097
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()),
31098
31298
  /** Time-series object count inside one zone. `className` optional —
31099
31299
  * omit to count every class in the zone. */
31100
31300
  getZoneHistory: method(object({
@@ -35582,6 +35782,12 @@ Object.freeze({
35582
35782
  addonId: null,
35583
35783
  access: "view"
35584
35784
  },
35785
+ "pipelineAnalytics.getKeyEventsBatch": {
35786
+ capName: "pipeline-analytics",
35787
+ capScope: "device",
35788
+ addonId: null,
35789
+ access: "view"
35790
+ },
35585
35791
  "pipelineAnalytics.getMotionEvents": {
35586
35792
  capName: "pipeline-analytics",
35587
35793
  capScope: "device",
@@ -36758,6 +36964,12 @@ Object.freeze({
36758
36964
  addonId: null,
36759
36965
  access: "view"
36760
36966
  },
36967
+ "recording.reconcileLedgerAgainstDisk": {
36968
+ capName: "recording",
36969
+ capScope: "system",
36970
+ addonId: null,
36971
+ access: "create"
36972
+ },
36761
36973
  "recording.refreshStorageLocationsForMigration": {
36762
36974
  capName: "recording",
36763
36975
  capScope: "system",
@@ -36884,6 +37096,12 @@ Object.freeze({
36884
37096
  addonId: null,
36885
37097
  access: "view"
36886
37098
  },
37099
+ "sceneMonitor.listScenesBatch": {
37100
+ capName: "scene-monitor",
37101
+ capScope: "device",
37102
+ addonId: null,
37103
+ access: "view"
37104
+ },
36887
37105
  "sceneMonitor.recheckNow": {
36888
37106
  capName: "scene-monitor",
36889
37107
  capScope: "device",
@@ -38240,6 +38458,12 @@ Object.freeze({
38240
38458
  addonId: null,
38241
38459
  access: "view"
38242
38460
  },
38461
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38462
+ capName: "zone-analytics",
38463
+ capScope: "device",
38464
+ addonId: null,
38465
+ access: "view"
38466
+ },
38243
38467
  "zoneAnalytics.getUnzonedHistory": {
38244
38468
  capName: "zone-analytics",
38245
38469
  capScope: "device",
@@ -39244,6 +39468,11 @@ Object.freeze({
39244
39468
  form: "single",
39245
39469
  optional: false
39246
39470
  }],
39471
+ "pipelineAnalytics.getKeyEventsBatch": [{
39472
+ name: "deviceIds",
39473
+ form: "array",
39474
+ optional: false
39475
+ }],
39247
39476
  "pipelineAnalytics.getMotionEvents": [{
39248
39477
  name: "deviceId",
39249
39478
  form: "single",
@@ -39684,6 +39913,11 @@ Object.freeze({
39684
39913
  form: "single",
39685
39914
  optional: false
39686
39915
  }],
39916
+ "recording.reconcileLedgerAgainstDisk": [{
39917
+ name: "deviceId",
39918
+ form: "single",
39919
+ optional: true
39920
+ }],
39687
39921
  "recording.relocateFootage": [{
39688
39922
  name: "deviceId",
39689
39923
  form: "single",
@@ -39749,6 +39983,11 @@ Object.freeze({
39749
39983
  form: "single",
39750
39984
  optional: false
39751
39985
  }],
39986
+ "sceneMonitor.listScenesBatch": [{
39987
+ name: "deviceIds",
39988
+ form: "array",
39989
+ optional: false
39990
+ }],
39752
39991
  "sceneMonitor.recheckNow": [{
39753
39992
  name: "deviceId",
39754
39993
  form: "single",
@@ -40010,6 +40249,11 @@ Object.freeze({
40010
40249
  form: "single",
40011
40250
  optional: false
40012
40251
  }],
40252
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40253
+ name: "deviceIds",
40254
+ form: "array",
40255
+ optional: false
40256
+ }],
40013
40257
  "zoneAnalytics.getUnzonedHistory": [{
40014
40258
  name: "deviceId",
40015
40259
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rtsp",
3
- "version": "1.2.48",
3
+ "version": "1.2.49",
4
4
  "description": "Generic RTSP camera device provider addon for CamStack",
5
5
  "keywords": [
6
6
  "camstack",