@camstack/addon-provider-hikvision 1.2.55 → 1.2.57

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
@@ -8013,6 +8013,20 @@ var RelocateJobSchema = object({
8013
8013
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8014
8014
  */
8015
8015
  rowsReconciled: number().int().nonnegative().optional(),
8016
+ /**
8017
+ * Rows this run FORGOT because the file they name is not on disk.
8018
+ *
8019
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8020
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8021
+ * and the durable row is dropped through the same channel eviction uses. It
8022
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8023
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8024
+ * the same failure as one that quietly skips them (D295).
8025
+ *
8026
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8027
+ * ledger claimed 5.65 GB of footage that no longer existed.
8028
+ */
8029
+ rowsForgotten: number().int().nonnegative().optional(),
8016
8030
  startedAt: number(),
8017
8031
  finishedAt: number().nullable(),
8018
8032
  error: string().nullable()
@@ -8376,6 +8390,91 @@ var RelocateResidueSchema = object({
8376
8390
  segments: number().int().nonnegative(),
8377
8391
  bytes: number().int().nonnegative()
8378
8392
  }).nullable();
8393
+ /**
8394
+ * Ask one location whether its durable hour rows describe the disk — the walk
8395
+ * (D319).
8396
+ *
8397
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8398
+ * missing tool is the question, and the dry run is how they sanity-check the
8399
+ * destructive run before authorising it.
8400
+ */
8401
+ var LedgerWalkInputSchema = object({
8402
+ locationId: string().min(1),
8403
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8404
+ apply: boolean().optional(),
8405
+ /** Narrow to one camera. */
8406
+ deviceId: number().int().positive().optional(),
8407
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8408
+ profiles: array(string().min(1)).optional()
8409
+ });
8410
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8411
+ var LedgerWalkRefusalSchema = _enum([
8412
+ "location-unknown",
8413
+ "source-writable",
8414
+ "no-ledger",
8415
+ "archive-unreadable",
8416
+ "anchor-absent",
8417
+ "anchor-unreadable",
8418
+ "anchor-moved"
8419
+ ]);
8420
+ _enum([
8421
+ "live-tail",
8422
+ "listing-error",
8423
+ "path-mismatch",
8424
+ "durable-refused"
8425
+ ]);
8426
+ /** Every skip reason, always present, always a number — so a reason that never
8427
+ * fired reports as zero rather than absent and the report shape is constant
8428
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8429
+ var LedgerWalkSkipCountsSchema = object({
8430
+ "live-tail": number().int().nonnegative(),
8431
+ "listing-error": number().int().nonnegative(),
8432
+ "path-mismatch": number().int().nonnegative(),
8433
+ "durable-refused": number().int().nonnegative()
8434
+ });
8435
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8436
+ var LedgerWalkDeviceReportSchema = object({
8437
+ deviceId: number().int(),
8438
+ hoursWalked: number().int().nonnegative(),
8439
+ hoursMissing: number().int().nonnegative(),
8440
+ ghostSegments: number().int().nonnegative(),
8441
+ ghostBytes: number().int().nonnegative(),
8442
+ forgottenSegments: number().int().nonnegative(),
8443
+ orphanFiles: number().int().nonnegative()
8444
+ });
8445
+ /**
8446
+ * What one walk claimed, listed, found and (only when armed) forgot.
8447
+ *
8448
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8449
+ * walk that saw a fraction of the location is visible in its own report rather
8450
+ * than in the absence of one.
8451
+ */
8452
+ var LedgerWalkReportSchema = object({
8453
+ locationId: string(),
8454
+ applied: boolean(),
8455
+ refused: LedgerWalkRefusalSchema.nullable(),
8456
+ archiveSegments: number().int().nonnegative().nullable(),
8457
+ archiveBytes: number().int().nonnegative().nullable(),
8458
+ hoursClaimed: number().int().nonnegative(),
8459
+ hoursWalked: number().int().nonnegative(),
8460
+ hoursMissing: number().int().nonnegative(),
8461
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8462
+ listings: number().int().nonnegative(),
8463
+ segmentsClaimed: number().int().nonnegative(),
8464
+ ghostSegments: number().int().nonnegative(),
8465
+ ghostBytes: number().int().nonnegative(),
8466
+ ghostHoursWhole: number().int().nonnegative(),
8467
+ forgottenSegments: number().int().nonnegative(),
8468
+ forgottenBytes: number().int().nonnegative(),
8469
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8470
+ orphanFiles: number().int().nonnegative(),
8471
+ orphanSample: array(string()).readonly(),
8472
+ hoursSkipped: number().int().nonnegative(),
8473
+ skippedByReason: LedgerWalkSkipCountsSchema,
8474
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8475
+ bounded: boolean(),
8476
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8477
+ });
8379
8478
  /** How many rows a media pass would still act on against a given target — the
8380
8479
  * media lane's denominator AND its residue, from ONE derivation so the two can
8381
8480
  * never disagree. `null` = the count could not be taken. */
@@ -19303,13 +19402,15 @@ var ListGroupsPageSchema = object({
19303
19402
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19304
19403
  nextCursor: string().nullable()
19305
19404
  });
19405
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19406
+ var KEY_EVENTS_MAX_LIMIT = 200;
19306
19407
  var KeyEventQueryInput = object({
19307
19408
  deviceId: number(),
19308
19409
  /** Window lower bound (track firstSeen ≥ since). */
19309
19410
  since: number(),
19310
19411
  /** Window upper bound (track firstSeen ≤ until). */
19311
19412
  until: number(),
19312
- limit: number().int().min(1).max(200).default(50),
19413
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19313
19414
  /** Drop tracks scoring below this importance. */
19314
19415
  minImportance: number().min(0).max(1).optional(),
19315
19416
  /** Restrict to a single class (e.g. 'person'). */
@@ -19331,6 +19432,32 @@ var KeyEventSchema = object({
19331
19432
  ...TrackFlagFields,
19332
19433
  ...TrackRetrainFields
19333
19434
  });
19435
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19436
+ var KeyEventBatchQueryInput = object({
19437
+ deviceIds: array(number()).min(1).max(200),
19438
+ since: number(),
19439
+ until: number(),
19440
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19441
+ * across the set, which would let a busy camera starve a quiet one of its
19442
+ * rows and change what the merged feed contains. */
19443
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19444
+ minImportance: number().min(0).max(1).optional(),
19445
+ classFilter: string().optional()
19446
+ });
19447
+ /**
19448
+ * One camera's key events in a batch answer.
19449
+ *
19450
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19451
+ * error rather than throwing, so a camera whose store read failed and one with
19452
+ * no events in the window were ALREADY indistinguishable per camera — the
19453
+ * batch does not make that worse, and the row keeps the deviceId the single
19454
+ * method's output never carried (the caller used to stamp it from the fan-out
19455
+ * key, which only worked because there was one query per camera).
19456
+ */
19457
+ var KeyEventsForDeviceSchema = object({
19458
+ deviceId: number(),
19459
+ events: array(KeyEventSchema).readonly()
19460
+ });
19334
19461
  object({
19335
19462
  trackId: string(),
19336
19463
  className: string(),
@@ -19402,6 +19529,50 @@ var EventStoreFootprintSchema = object({
19402
19529
  totalBytes: number().int(),
19403
19530
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19404
19531
  });
19532
+ /** Event-media footprint for one {@link MediaFileKind}. */
19533
+ var EventMediaKindFootprintSchema = object({
19534
+ kind: MediaFileKindEnum,
19535
+ /** Media rows of this kind. */
19536
+ rows: number().int(),
19537
+ /** Bytes on disk held by those rows. */
19538
+ bytes: number().int()
19539
+ });
19540
+ /**
19541
+ * The media footprint broken down by KIND — the axis a deletion decision
19542
+ * actually turns on.
19543
+ *
19544
+ * A byte total says how much there is; it cannot say what is safe to remove.
19545
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19546
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19547
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19548
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19549
+ * nothing else, so sizing a deletion means summing per kind.
19550
+ *
19551
+ * ## Why `unaccounted*` exists
19552
+ *
19553
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19554
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19555
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19556
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19557
+ * retired code path, or by a version that knew a kind this one does not) would
19558
+ * otherwise vanish from the total silently, and an operator would delete
19559
+ * against a denominator smaller than the disk.
19560
+ *
19561
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19562
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19563
+ */
19564
+ var EventMediaKindBreakdownSchema = object({
19565
+ /** Every media row in scope, from one unfiltered aggregate. */
19566
+ totalRows: number().int(),
19567
+ /** Every media byte in scope, from that same aggregate. */
19568
+ totalBytes: number().int(),
19569
+ /** Per-kind footprint, ordered by bytes descending. */
19570
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19571
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19572
+ unaccountedRows: number().int(),
19573
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19574
+ unaccountedBytes: number().int()
19575
+ });
19405
19576
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19406
19577
  var EventPruneCountsSchema = object({
19407
19578
  motion: number().int(),
@@ -19556,7 +19727,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19556
19727
  until: number().optional(),
19557
19728
  kinds: array(string()).optional(),
19558
19729
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19559
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19730
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19560
19731
  deviceId: number(),
19561
19732
  since: number(),
19562
19733
  until: number(),
@@ -19605,6 +19776,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19605
19776
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19606
19777
  kind: "query",
19607
19778
  auth: "admin"
19779
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19780
+ kind: "query",
19781
+ auth: "admin"
19608
19782
  }), method(object({
19609
19783
  olderThanMs: number(),
19610
19784
  reason: OpsLogReasonSchema.optional()
@@ -21660,6 +21834,20 @@ method(object({
21660
21834
  error: string().optional()
21661
21835
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21662
21836
  providerId: string(),
21837
+ /**
21838
+ * The location this config is an UNSAVED edit of, when there is one.
21839
+ *
21840
+ * `listLocations` replaces every declared secret with the redaction
21841
+ * sentinel, so the edit modal's form state holds the sentinel for any
21842
+ * credential the operator did not retype — and posting that here
21843
+ * without a way to resolve it makes the provider try to authenticate
21844
+ * as `__camstack_redacted__` and report the operator's own working
21845
+ * password as wrong. Given this id, the orchestrator restores each
21846
+ * sentinel from the stored config (same rule as `upsertLocation`)
21847
+ * before dispatching. Omitted by the "Add location" wizard, where
21848
+ * every value was typed just now and nothing is stored yet.
21849
+ */
21850
+ locationId: string().optional(),
21663
21851
  config: record(string(), unknown())
21664
21852
  }), object({
21665
21853
  ok: boolean(),
@@ -29143,6 +29331,9 @@ method(object({
29143
29331
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29144
29332
  kind: "query",
29145
29333
  auth: "admin"
29334
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29335
+ kind: "mutation",
29336
+ auth: "admin"
29146
29337
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29147
29338
  kind: "mutation",
29148
29339
  auth: "admin"
@@ -29534,6 +29725,25 @@ var SceneMonitorStatusSchema = object({
29534
29725
  monitors: array(SceneMonitorSchema),
29535
29726
  lastFetchedAt: number()
29536
29727
  });
29728
+ /**
29729
+ * One camera's row in a `listScenesBatch` answer.
29730
+ *
29731
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29732
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29733
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29734
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29735
+ * configured. Fanned out per camera the difference was visible — one query
29736
+ * errored while the others resolved — and a batch that returned only the rows
29737
+ * it managed would have destroyed it, silently, by making an unreachable camera
29738
+ * indistinguishable from one that answered `monitors: []`.
29739
+ *
29740
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29741
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29742
+ */
29743
+ var SceneMonitorStatusForDeviceSchema = object({
29744
+ deviceId: number(),
29745
+ status: SceneMonitorStatusSchema.nullable()
29746
+ });
29537
29747
  var sceneMonitorCapability = {
29538
29748
  name: "scene-monitor",
29539
29749
  scope: "device",
@@ -29543,6 +29753,22 @@ var sceneMonitorCapability = {
29543
29753
  deviceTypes: [DeviceType.Camera],
29544
29754
  methods: {
29545
29755
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29756
+ /**
29757
+ * The same answer, for a SET of cameras, in one round trip.
29758
+ *
29759
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29760
+ * poll behind the push slice. Fanned out client-side that was one query
29761
+ * per camera — 29 round trips through the browser, the hub and the
29762
+ * post-analysis runner every 30 seconds to read an in-memory map the
29763
+ * owner had already merged. The work is unchanged (`statusFor` per
29764
+ * device, all in-process at the owner); what collapses is the transport.
29765
+ *
29766
+ * A camera that cannot answer still gets a row, with `status: null` —
29767
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29768
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29769
+ * tell which two are missing, or that any are.
29770
+ */
29771
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29546
29772
  createScene: method(object({
29547
29773
  deviceId: number(),
29548
29774
  label: string(),
@@ -31576,6 +31802,27 @@ var CameraOccupancySnapshotSchema = object({
31576
31802
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31577
31803
  });
31578
31804
  /**
31805
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31806
+ *
31807
+ * THREE outcomes, and the single-camera method could only express two of them
31808
+ * because `snapshot: null` was already spoken for:
31809
+ *
31810
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31811
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31812
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31813
+ * - `read: 'unreadable'` — the owner could not answer for this
31814
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31815
+ *
31816
+ * Collapsing the last two is the failure this field exists to prevent: a
31817
+ * hydration that threw would otherwise render as an empty Stationary section,
31818
+ * which is a definite claim about a camera nobody could read.
31819
+ */
31820
+ var CameraOccupancySnapshotForDeviceSchema = object({
31821
+ deviceId: number(),
31822
+ read: _enum(["read", "unreadable"]),
31823
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31824
+ });
31825
+ /**
31579
31826
  * Time-series resolution. The history methods return one bucket per
31580
31827
  * step over the requested range. Smaller resolutions cost more
31581
31828
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31632,6 +31879,20 @@ var zoneAnalyticsCapability = {
31632
31879
  * (no inference result emitted since boot or since binding was
31633
31880
  * activated). */
31634
31881
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31882
+ /**
31883
+ * The same snapshot, for a SET of cameras, in one round trip.
31884
+ *
31885
+ * The Events page's Stationary section polls this every 15s for every
31886
+ * selected camera. Fanned out client-side that is one query per camera to
31887
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31888
+ * costs everything. Batched, N transports become one and the per-device
31889
+ * work is unchanged.
31890
+ *
31891
+ * Every requested deviceId gets a row, tagged `read` — see
31892
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31893
+ * not answer for is `'unreadable'`, never an empty reading.
31894
+ */
31895
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31635
31896
  /** Time-series object count inside one zone. `className` optional —
31636
31897
  * omit to count every class in the zone. */
31637
31898
  getZoneHistory: method(object({
@@ -36262,6 +36523,12 @@ Object.freeze({
36262
36523
  addonId: null,
36263
36524
  access: "view"
36264
36525
  },
36526
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
36527
+ capName: "pipeline-analytics",
36528
+ capScope: "device",
36529
+ addonId: null,
36530
+ access: "view"
36531
+ },
36265
36532
  "pipelineAnalytics.getEventStoreFootprint": {
36266
36533
  capName: "pipeline-analytics",
36267
36534
  capScope: "device",
@@ -36280,6 +36547,12 @@ Object.freeze({
36280
36547
  addonId: null,
36281
36548
  access: "view"
36282
36549
  },
36550
+ "pipelineAnalytics.getKeyEventsBatch": {
36551
+ capName: "pipeline-analytics",
36552
+ capScope: "device",
36553
+ addonId: null,
36554
+ access: "view"
36555
+ },
36283
36556
  "pipelineAnalytics.getMotionEvents": {
36284
36557
  capName: "pipeline-analytics",
36285
36558
  capScope: "device",
@@ -37456,6 +37729,12 @@ Object.freeze({
37456
37729
  addonId: null,
37457
37730
  access: "view"
37458
37731
  },
37732
+ "recording.reconcileLedgerAgainstDisk": {
37733
+ capName: "recording",
37734
+ capScope: "system",
37735
+ addonId: null,
37736
+ access: "create"
37737
+ },
37459
37738
  "recording.refreshStorageLocationsForMigration": {
37460
37739
  capName: "recording",
37461
37740
  capScope: "system",
@@ -37582,6 +37861,12 @@ Object.freeze({
37582
37861
  addonId: null,
37583
37862
  access: "view"
37584
37863
  },
37864
+ "sceneMonitor.listScenesBatch": {
37865
+ capName: "scene-monitor",
37866
+ capScope: "device",
37867
+ addonId: null,
37868
+ access: "view"
37869
+ },
37585
37870
  "sceneMonitor.recheckNow": {
37586
37871
  capName: "scene-monitor",
37587
37872
  capScope: "device",
@@ -38938,6 +39223,12 @@ Object.freeze({
38938
39223
  addonId: null,
38939
39224
  access: "view"
38940
39225
  },
39226
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39227
+ capName: "zone-analytics",
39228
+ capScope: "device",
39229
+ addonId: null,
39230
+ access: "view"
39231
+ },
38941
39232
  "zoneAnalytics.getUnzonedHistory": {
38942
39233
  capName: "zone-analytics",
38943
39234
  capScope: "device",
@@ -39927,6 +40218,11 @@ Object.freeze({
39927
40218
  form: "single",
39928
40219
  optional: false
39929
40220
  }],
40221
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
40222
+ name: "deviceId",
40223
+ form: "single",
40224
+ optional: true
40225
+ }],
39930
40226
  "pipelineAnalytics.getGroup": [{
39931
40227
  name: "deviceId",
39932
40228
  form: "single",
@@ -39937,6 +40233,11 @@ Object.freeze({
39937
40233
  form: "single",
39938
40234
  optional: false
39939
40235
  }],
40236
+ "pipelineAnalytics.getKeyEventsBatch": [{
40237
+ name: "deviceIds",
40238
+ form: "array",
40239
+ optional: false
40240
+ }],
39940
40241
  "pipelineAnalytics.getMotionEvents": [{
39941
40242
  name: "deviceId",
39942
40243
  form: "single",
@@ -40377,6 +40678,11 @@ Object.freeze({
40377
40678
  form: "single",
40378
40679
  optional: false
40379
40680
  }],
40681
+ "recording.reconcileLedgerAgainstDisk": [{
40682
+ name: "deviceId",
40683
+ form: "single",
40684
+ optional: true
40685
+ }],
40380
40686
  "recording.relocateFootage": [{
40381
40687
  name: "deviceId",
40382
40688
  form: "single",
@@ -40442,6 +40748,11 @@ Object.freeze({
40442
40748
  form: "single",
40443
40749
  optional: false
40444
40750
  }],
40751
+ "sceneMonitor.listScenesBatch": [{
40752
+ name: "deviceIds",
40753
+ form: "array",
40754
+ optional: false
40755
+ }],
40445
40756
  "sceneMonitor.recheckNow": [{
40446
40757
  name: "deviceId",
40447
40758
  form: "single",
@@ -40703,6 +41014,11 @@ Object.freeze({
40703
41014
  form: "single",
40704
41015
  optional: false
40705
41016
  }],
41017
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41018
+ name: "deviceIds",
41019
+ form: "array",
41020
+ optional: false
41021
+ }],
40706
41022
  "zoneAnalytics.getUnzonedHistory": [{
40707
41023
  name: "deviceId",
40708
41024
  form: "single",
package/dist/addon.mjs CHANGED
@@ -8014,6 +8014,20 @@ var RelocateJobSchema = object({
8014
8014
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8015
8015
  */
8016
8016
  rowsReconciled: number().int().nonnegative().optional(),
8017
+ /**
8018
+ * Rows this run FORGOT because the file they name is not on disk.
8019
+ *
8020
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8021
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8022
+ * and the durable row is dropped through the same channel eviction uses. It
8023
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8024
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8025
+ * the same failure as one that quietly skips them (D295).
8026
+ *
8027
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8028
+ * ledger claimed 5.65 GB of footage that no longer existed.
8029
+ */
8030
+ rowsForgotten: number().int().nonnegative().optional(),
8017
8031
  startedAt: number(),
8018
8032
  finishedAt: number().nullable(),
8019
8033
  error: string().nullable()
@@ -8377,6 +8391,91 @@ var RelocateResidueSchema = object({
8377
8391
  segments: number().int().nonnegative(),
8378
8392
  bytes: number().int().nonnegative()
8379
8393
  }).nullable();
8394
+ /**
8395
+ * Ask one location whether its durable hour rows describe the disk — the walk
8396
+ * (D319).
8397
+ *
8398
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8399
+ * missing tool is the question, and the dry run is how they sanity-check the
8400
+ * destructive run before authorising it.
8401
+ */
8402
+ var LedgerWalkInputSchema = object({
8403
+ locationId: string().min(1),
8404
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8405
+ apply: boolean().optional(),
8406
+ /** Narrow to one camera. */
8407
+ deviceId: number().int().positive().optional(),
8408
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8409
+ profiles: array(string().min(1)).optional()
8410
+ });
8411
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8412
+ var LedgerWalkRefusalSchema = _enum([
8413
+ "location-unknown",
8414
+ "source-writable",
8415
+ "no-ledger",
8416
+ "archive-unreadable",
8417
+ "anchor-absent",
8418
+ "anchor-unreadable",
8419
+ "anchor-moved"
8420
+ ]);
8421
+ _enum([
8422
+ "live-tail",
8423
+ "listing-error",
8424
+ "path-mismatch",
8425
+ "durable-refused"
8426
+ ]);
8427
+ /** Every skip reason, always present, always a number — so a reason that never
8428
+ * fired reports as zero rather than absent and the report shape is constant
8429
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8430
+ var LedgerWalkSkipCountsSchema = object({
8431
+ "live-tail": number().int().nonnegative(),
8432
+ "listing-error": number().int().nonnegative(),
8433
+ "path-mismatch": number().int().nonnegative(),
8434
+ "durable-refused": number().int().nonnegative()
8435
+ });
8436
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8437
+ var LedgerWalkDeviceReportSchema = object({
8438
+ deviceId: number().int(),
8439
+ hoursWalked: number().int().nonnegative(),
8440
+ hoursMissing: number().int().nonnegative(),
8441
+ ghostSegments: number().int().nonnegative(),
8442
+ ghostBytes: number().int().nonnegative(),
8443
+ forgottenSegments: number().int().nonnegative(),
8444
+ orphanFiles: number().int().nonnegative()
8445
+ });
8446
+ /**
8447
+ * What one walk claimed, listed, found and (only when armed) forgot.
8448
+ *
8449
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8450
+ * walk that saw a fraction of the location is visible in its own report rather
8451
+ * than in the absence of one.
8452
+ */
8453
+ var LedgerWalkReportSchema = object({
8454
+ locationId: string(),
8455
+ applied: boolean(),
8456
+ refused: LedgerWalkRefusalSchema.nullable(),
8457
+ archiveSegments: number().int().nonnegative().nullable(),
8458
+ archiveBytes: number().int().nonnegative().nullable(),
8459
+ hoursClaimed: number().int().nonnegative(),
8460
+ hoursWalked: number().int().nonnegative(),
8461
+ hoursMissing: number().int().nonnegative(),
8462
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8463
+ listings: number().int().nonnegative(),
8464
+ segmentsClaimed: number().int().nonnegative(),
8465
+ ghostSegments: number().int().nonnegative(),
8466
+ ghostBytes: number().int().nonnegative(),
8467
+ ghostHoursWhole: number().int().nonnegative(),
8468
+ forgottenSegments: number().int().nonnegative(),
8469
+ forgottenBytes: number().int().nonnegative(),
8470
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8471
+ orphanFiles: number().int().nonnegative(),
8472
+ orphanSample: array(string()).readonly(),
8473
+ hoursSkipped: number().int().nonnegative(),
8474
+ skippedByReason: LedgerWalkSkipCountsSchema,
8475
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8476
+ bounded: boolean(),
8477
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8478
+ });
8380
8479
  /** How many rows a media pass would still act on against a given target — the
8381
8480
  * media lane's denominator AND its residue, from ONE derivation so the two can
8382
8481
  * never disagree. `null` = the count could not be taken. */
@@ -19304,13 +19403,15 @@ var ListGroupsPageSchema = object({
19304
19403
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19305
19404
  nextCursor: string().nullable()
19306
19405
  });
19406
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19407
+ var KEY_EVENTS_MAX_LIMIT = 200;
19307
19408
  var KeyEventQueryInput = object({
19308
19409
  deviceId: number(),
19309
19410
  /** Window lower bound (track firstSeen ≥ since). */
19310
19411
  since: number(),
19311
19412
  /** Window upper bound (track firstSeen ≤ until). */
19312
19413
  until: number(),
19313
- limit: number().int().min(1).max(200).default(50),
19414
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19314
19415
  /** Drop tracks scoring below this importance. */
19315
19416
  minImportance: number().min(0).max(1).optional(),
19316
19417
  /** Restrict to a single class (e.g. 'person'). */
@@ -19332,6 +19433,32 @@ var KeyEventSchema = object({
19332
19433
  ...TrackFlagFields,
19333
19434
  ...TrackRetrainFields
19334
19435
  });
19436
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19437
+ var KeyEventBatchQueryInput = object({
19438
+ deviceIds: array(number()).min(1).max(200),
19439
+ since: number(),
19440
+ until: number(),
19441
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19442
+ * across the set, which would let a busy camera starve a quiet one of its
19443
+ * rows and change what the merged feed contains. */
19444
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19445
+ minImportance: number().min(0).max(1).optional(),
19446
+ classFilter: string().optional()
19447
+ });
19448
+ /**
19449
+ * One camera's key events in a batch answer.
19450
+ *
19451
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19452
+ * error rather than throwing, so a camera whose store read failed and one with
19453
+ * no events in the window were ALREADY indistinguishable per camera — the
19454
+ * batch does not make that worse, and the row keeps the deviceId the single
19455
+ * method's output never carried (the caller used to stamp it from the fan-out
19456
+ * key, which only worked because there was one query per camera).
19457
+ */
19458
+ var KeyEventsForDeviceSchema = object({
19459
+ deviceId: number(),
19460
+ events: array(KeyEventSchema).readonly()
19461
+ });
19335
19462
  object({
19336
19463
  trackId: string(),
19337
19464
  className: string(),
@@ -19403,6 +19530,50 @@ var EventStoreFootprintSchema = object({
19403
19530
  totalBytes: number().int(),
19404
19531
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19405
19532
  });
19533
+ /** Event-media footprint for one {@link MediaFileKind}. */
19534
+ var EventMediaKindFootprintSchema = object({
19535
+ kind: MediaFileKindEnum,
19536
+ /** Media rows of this kind. */
19537
+ rows: number().int(),
19538
+ /** Bytes on disk held by those rows. */
19539
+ bytes: number().int()
19540
+ });
19541
+ /**
19542
+ * The media footprint broken down by KIND — the axis a deletion decision
19543
+ * actually turns on.
19544
+ *
19545
+ * A byte total says how much there is; it cannot say what is safe to remove.
19546
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19547
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19548
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19549
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19550
+ * nothing else, so sizing a deletion means summing per kind.
19551
+ *
19552
+ * ## Why `unaccounted*` exists
19553
+ *
19554
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19555
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19556
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19557
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19558
+ * retired code path, or by a version that knew a kind this one does not) would
19559
+ * otherwise vanish from the total silently, and an operator would delete
19560
+ * against a denominator smaller than the disk.
19561
+ *
19562
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19563
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19564
+ */
19565
+ var EventMediaKindBreakdownSchema = object({
19566
+ /** Every media row in scope, from one unfiltered aggregate. */
19567
+ totalRows: number().int(),
19568
+ /** Every media byte in scope, from that same aggregate. */
19569
+ totalBytes: number().int(),
19570
+ /** Per-kind footprint, ordered by bytes descending. */
19571
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19572
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19573
+ unaccountedRows: number().int(),
19574
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19575
+ unaccountedBytes: number().int()
19576
+ });
19406
19577
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19407
19578
  var EventPruneCountsSchema = object({
19408
19579
  motion: number().int(),
@@ -19557,7 +19728,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19557
19728
  until: number().optional(),
19558
19729
  kinds: array(string()).optional(),
19559
19730
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19560
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19731
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19561
19732
  deviceId: number(),
19562
19733
  since: number(),
19563
19734
  until: number(),
@@ -19606,6 +19777,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19606
19777
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19607
19778
  kind: "query",
19608
19779
  auth: "admin"
19780
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19781
+ kind: "query",
19782
+ auth: "admin"
19609
19783
  }), method(object({
19610
19784
  olderThanMs: number(),
19611
19785
  reason: OpsLogReasonSchema.optional()
@@ -21661,6 +21835,20 @@ method(object({
21661
21835
  error: string().optional()
21662
21836
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21663
21837
  providerId: string(),
21838
+ /**
21839
+ * The location this config is an UNSAVED edit of, when there is one.
21840
+ *
21841
+ * `listLocations` replaces every declared secret with the redaction
21842
+ * sentinel, so the edit modal's form state holds the sentinel for any
21843
+ * credential the operator did not retype — and posting that here
21844
+ * without a way to resolve it makes the provider try to authenticate
21845
+ * as `__camstack_redacted__` and report the operator's own working
21846
+ * password as wrong. Given this id, the orchestrator restores each
21847
+ * sentinel from the stored config (same rule as `upsertLocation`)
21848
+ * before dispatching. Omitted by the "Add location" wizard, where
21849
+ * every value was typed just now and nothing is stored yet.
21850
+ */
21851
+ locationId: string().optional(),
21664
21852
  config: record(string(), unknown())
21665
21853
  }), object({
21666
21854
  ok: boolean(),
@@ -29144,6 +29332,9 @@ method(object({
29144
29332
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29145
29333
  kind: "query",
29146
29334
  auth: "admin"
29335
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29336
+ kind: "mutation",
29337
+ auth: "admin"
29147
29338
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29148
29339
  kind: "mutation",
29149
29340
  auth: "admin"
@@ -29535,6 +29726,25 @@ var SceneMonitorStatusSchema = object({
29535
29726
  monitors: array(SceneMonitorSchema),
29536
29727
  lastFetchedAt: number()
29537
29728
  });
29729
+ /**
29730
+ * One camera's row in a `listScenesBatch` answer.
29731
+ *
29732
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29733
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29734
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29735
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29736
+ * configured. Fanned out per camera the difference was visible — one query
29737
+ * errored while the others resolved — and a batch that returned only the rows
29738
+ * it managed would have destroyed it, silently, by making an unreachable camera
29739
+ * indistinguishable from one that answered `monitors: []`.
29740
+ *
29741
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29742
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29743
+ */
29744
+ var SceneMonitorStatusForDeviceSchema = object({
29745
+ deviceId: number(),
29746
+ status: SceneMonitorStatusSchema.nullable()
29747
+ });
29538
29748
  var sceneMonitorCapability = {
29539
29749
  name: "scene-monitor",
29540
29750
  scope: "device",
@@ -29544,6 +29754,22 @@ var sceneMonitorCapability = {
29544
29754
  deviceTypes: [DeviceType.Camera],
29545
29755
  methods: {
29546
29756
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29757
+ /**
29758
+ * The same answer, for a SET of cameras, in one round trip.
29759
+ *
29760
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29761
+ * poll behind the push slice. Fanned out client-side that was one query
29762
+ * per camera — 29 round trips through the browser, the hub and the
29763
+ * post-analysis runner every 30 seconds to read an in-memory map the
29764
+ * owner had already merged. The work is unchanged (`statusFor` per
29765
+ * device, all in-process at the owner); what collapses is the transport.
29766
+ *
29767
+ * A camera that cannot answer still gets a row, with `status: null` —
29768
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29769
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29770
+ * tell which two are missing, or that any are.
29771
+ */
29772
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29547
29773
  createScene: method(object({
29548
29774
  deviceId: number(),
29549
29775
  label: string(),
@@ -31577,6 +31803,27 @@ var CameraOccupancySnapshotSchema = object({
31577
31803
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31578
31804
  });
31579
31805
  /**
31806
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31807
+ *
31808
+ * THREE outcomes, and the single-camera method could only express two of them
31809
+ * because `snapshot: null` was already spoken for:
31810
+ *
31811
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31812
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31813
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31814
+ * - `read: 'unreadable'` — the owner could not answer for this
31815
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31816
+ *
31817
+ * Collapsing the last two is the failure this field exists to prevent: a
31818
+ * hydration that threw would otherwise render as an empty Stationary section,
31819
+ * which is a definite claim about a camera nobody could read.
31820
+ */
31821
+ var CameraOccupancySnapshotForDeviceSchema = object({
31822
+ deviceId: number(),
31823
+ read: _enum(["read", "unreadable"]),
31824
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31825
+ });
31826
+ /**
31580
31827
  * Time-series resolution. The history methods return one bucket per
31581
31828
  * step over the requested range. Smaller resolutions cost more
31582
31829
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31633,6 +31880,20 @@ var zoneAnalyticsCapability = {
31633
31880
  * (no inference result emitted since boot or since binding was
31634
31881
  * activated). */
31635
31882
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31883
+ /**
31884
+ * The same snapshot, for a SET of cameras, in one round trip.
31885
+ *
31886
+ * The Events page's Stationary section polls this every 15s for every
31887
+ * selected camera. Fanned out client-side that is one query per camera to
31888
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31889
+ * costs everything. Batched, N transports become one and the per-device
31890
+ * work is unchanged.
31891
+ *
31892
+ * Every requested deviceId gets a row, tagged `read` — see
31893
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31894
+ * not answer for is `'unreadable'`, never an empty reading.
31895
+ */
31896
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31636
31897
  /** Time-series object count inside one zone. `className` optional —
31637
31898
  * omit to count every class in the zone. */
31638
31899
  getZoneHistory: method(object({
@@ -36263,6 +36524,12 @@ Object.freeze({
36263
36524
  addonId: null,
36264
36525
  access: "view"
36265
36526
  },
36527
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
36528
+ capName: "pipeline-analytics",
36529
+ capScope: "device",
36530
+ addonId: null,
36531
+ access: "view"
36532
+ },
36266
36533
  "pipelineAnalytics.getEventStoreFootprint": {
36267
36534
  capName: "pipeline-analytics",
36268
36535
  capScope: "device",
@@ -36281,6 +36548,12 @@ Object.freeze({
36281
36548
  addonId: null,
36282
36549
  access: "view"
36283
36550
  },
36551
+ "pipelineAnalytics.getKeyEventsBatch": {
36552
+ capName: "pipeline-analytics",
36553
+ capScope: "device",
36554
+ addonId: null,
36555
+ access: "view"
36556
+ },
36284
36557
  "pipelineAnalytics.getMotionEvents": {
36285
36558
  capName: "pipeline-analytics",
36286
36559
  capScope: "device",
@@ -37457,6 +37730,12 @@ Object.freeze({
37457
37730
  addonId: null,
37458
37731
  access: "view"
37459
37732
  },
37733
+ "recording.reconcileLedgerAgainstDisk": {
37734
+ capName: "recording",
37735
+ capScope: "system",
37736
+ addonId: null,
37737
+ access: "create"
37738
+ },
37460
37739
  "recording.refreshStorageLocationsForMigration": {
37461
37740
  capName: "recording",
37462
37741
  capScope: "system",
@@ -37583,6 +37862,12 @@ Object.freeze({
37583
37862
  addonId: null,
37584
37863
  access: "view"
37585
37864
  },
37865
+ "sceneMonitor.listScenesBatch": {
37866
+ capName: "scene-monitor",
37867
+ capScope: "device",
37868
+ addonId: null,
37869
+ access: "view"
37870
+ },
37586
37871
  "sceneMonitor.recheckNow": {
37587
37872
  capName: "scene-monitor",
37588
37873
  capScope: "device",
@@ -38939,6 +39224,12 @@ Object.freeze({
38939
39224
  addonId: null,
38940
39225
  access: "view"
38941
39226
  },
39227
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39228
+ capName: "zone-analytics",
39229
+ capScope: "device",
39230
+ addonId: null,
39231
+ access: "view"
39232
+ },
38942
39233
  "zoneAnalytics.getUnzonedHistory": {
38943
39234
  capName: "zone-analytics",
38944
39235
  capScope: "device",
@@ -39928,6 +40219,11 @@ Object.freeze({
39928
40219
  form: "single",
39929
40220
  optional: false
39930
40221
  }],
40222
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
40223
+ name: "deviceId",
40224
+ form: "single",
40225
+ optional: true
40226
+ }],
39931
40227
  "pipelineAnalytics.getGroup": [{
39932
40228
  name: "deviceId",
39933
40229
  form: "single",
@@ -39938,6 +40234,11 @@ Object.freeze({
39938
40234
  form: "single",
39939
40235
  optional: false
39940
40236
  }],
40237
+ "pipelineAnalytics.getKeyEventsBatch": [{
40238
+ name: "deviceIds",
40239
+ form: "array",
40240
+ optional: false
40241
+ }],
39941
40242
  "pipelineAnalytics.getMotionEvents": [{
39942
40243
  name: "deviceId",
39943
40244
  form: "single",
@@ -40378,6 +40679,11 @@ Object.freeze({
40378
40679
  form: "single",
40379
40680
  optional: false
40380
40681
  }],
40682
+ "recording.reconcileLedgerAgainstDisk": [{
40683
+ name: "deviceId",
40684
+ form: "single",
40685
+ optional: true
40686
+ }],
40381
40687
  "recording.relocateFootage": [{
40382
40688
  name: "deviceId",
40383
40689
  form: "single",
@@ -40443,6 +40749,11 @@ Object.freeze({
40443
40749
  form: "single",
40444
40750
  optional: false
40445
40751
  }],
40752
+ "sceneMonitor.listScenesBatch": [{
40753
+ name: "deviceIds",
40754
+ form: "array",
40755
+ optional: false
40756
+ }],
40446
40757
  "sceneMonitor.recheckNow": [{
40447
40758
  name: "deviceId",
40448
40759
  form: "single",
@@ -40704,6 +41015,11 @@ Object.freeze({
40704
41015
  form: "single",
40705
41016
  optional: false
40706
41017
  }],
41018
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41019
+ name: "deviceIds",
41020
+ form: "array",
41021
+ optional: false
41022
+ }],
40707
41023
  "zoneAnalytics.getUnzonedHistory": [{
40708
41024
  name: "deviceId",
40709
41025
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-hikvision",
3
- "version": "1.2.55",
3
+ "version": "1.2.57",
4
4
  "description": "Hikvision camera device provider addon for CamStack — ISAPI over HTTP(S) with digest auth (snapshot, alarm stream, RTSP discovery)",
5
5
  "keywords": [
6
6
  "camstack",