@camstack/addon-provider-reolink 1.2.71 → 1.2.72

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
@@ -8308,6 +8308,20 @@ var RelocateJobSchema = object({
8308
8308
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8309
8309
  */
8310
8310
  rowsReconciled: number().int().nonnegative().optional(),
8311
+ /**
8312
+ * Rows this run FORGOT because the file they name is not on disk.
8313
+ *
8314
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8315
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8316
+ * and the durable row is dropped through the same channel eviction uses. It
8317
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8318
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8319
+ * the same failure as one that quietly skips them (D295).
8320
+ *
8321
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8322
+ * ledger claimed 5.65 GB of footage that no longer existed.
8323
+ */
8324
+ rowsForgotten: number().int().nonnegative().optional(),
8311
8325
  startedAt: number(),
8312
8326
  finishedAt: number().nullable(),
8313
8327
  error: string().nullable()
@@ -8671,6 +8685,91 @@ var RelocateResidueSchema = object({
8671
8685
  segments: number().int().nonnegative(),
8672
8686
  bytes: number().int().nonnegative()
8673
8687
  }).nullable();
8688
+ /**
8689
+ * Ask one location whether its durable hour rows describe the disk — the walk
8690
+ * (D319).
8691
+ *
8692
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8693
+ * missing tool is the question, and the dry run is how they sanity-check the
8694
+ * destructive run before authorising it.
8695
+ */
8696
+ var LedgerWalkInputSchema = object({
8697
+ locationId: string().min(1),
8698
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8699
+ apply: boolean().optional(),
8700
+ /** Narrow to one camera. */
8701
+ deviceId: number().int().positive().optional(),
8702
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8703
+ profiles: array(string().min(1)).optional()
8704
+ });
8705
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8706
+ var LedgerWalkRefusalSchema = _enum([
8707
+ "location-unknown",
8708
+ "source-writable",
8709
+ "no-ledger",
8710
+ "archive-unreadable",
8711
+ "anchor-absent",
8712
+ "anchor-unreadable",
8713
+ "anchor-moved"
8714
+ ]);
8715
+ _enum([
8716
+ "live-tail",
8717
+ "listing-error",
8718
+ "path-mismatch",
8719
+ "durable-refused"
8720
+ ]);
8721
+ /** Every skip reason, always present, always a number — so a reason that never
8722
+ * fired reports as zero rather than absent and the report shape is constant
8723
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8724
+ var LedgerWalkSkipCountsSchema = object({
8725
+ "live-tail": number().int().nonnegative(),
8726
+ "listing-error": number().int().nonnegative(),
8727
+ "path-mismatch": number().int().nonnegative(),
8728
+ "durable-refused": number().int().nonnegative()
8729
+ });
8730
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8731
+ var LedgerWalkDeviceReportSchema = object({
8732
+ deviceId: number().int(),
8733
+ hoursWalked: number().int().nonnegative(),
8734
+ hoursMissing: number().int().nonnegative(),
8735
+ ghostSegments: number().int().nonnegative(),
8736
+ ghostBytes: number().int().nonnegative(),
8737
+ forgottenSegments: number().int().nonnegative(),
8738
+ orphanFiles: number().int().nonnegative()
8739
+ });
8740
+ /**
8741
+ * What one walk claimed, listed, found and (only when armed) forgot.
8742
+ *
8743
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8744
+ * walk that saw a fraction of the location is visible in its own report rather
8745
+ * than in the absence of one.
8746
+ */
8747
+ var LedgerWalkReportSchema = object({
8748
+ locationId: string(),
8749
+ applied: boolean(),
8750
+ refused: LedgerWalkRefusalSchema.nullable(),
8751
+ archiveSegments: number().int().nonnegative().nullable(),
8752
+ archiveBytes: number().int().nonnegative().nullable(),
8753
+ hoursClaimed: number().int().nonnegative(),
8754
+ hoursWalked: number().int().nonnegative(),
8755
+ hoursMissing: number().int().nonnegative(),
8756
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8757
+ listings: number().int().nonnegative(),
8758
+ segmentsClaimed: number().int().nonnegative(),
8759
+ ghostSegments: number().int().nonnegative(),
8760
+ ghostBytes: number().int().nonnegative(),
8761
+ ghostHoursWhole: number().int().nonnegative(),
8762
+ forgottenSegments: number().int().nonnegative(),
8763
+ forgottenBytes: number().int().nonnegative(),
8764
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8765
+ orphanFiles: number().int().nonnegative(),
8766
+ orphanSample: array(string()).readonly(),
8767
+ hoursSkipped: number().int().nonnegative(),
8768
+ skippedByReason: LedgerWalkSkipCountsSchema,
8769
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8770
+ bounded: boolean(),
8771
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8772
+ });
8674
8773
  /** How many rows a media pass would still act on against a given target — the
8675
8774
  * media lane's denominator AND its residue, from ONE derivation so the two can
8676
8775
  * never disagree. `null` = the count could not be taken. */
@@ -19619,13 +19718,15 @@ var ListGroupsPageSchema = object({
19619
19718
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19620
19719
  nextCursor: string().nullable()
19621
19720
  });
19721
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19722
+ var KEY_EVENTS_MAX_LIMIT = 200;
19622
19723
  var KeyEventQueryInput = object({
19623
19724
  deviceId: number(),
19624
19725
  /** Window lower bound (track firstSeen ≥ since). */
19625
19726
  since: number(),
19626
19727
  /** Window upper bound (track firstSeen ≤ until). */
19627
19728
  until: number(),
19628
- limit: number().int().min(1).max(200).default(50),
19729
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19629
19730
  /** Drop tracks scoring below this importance. */
19630
19731
  minImportance: number().min(0).max(1).optional(),
19631
19732
  /** Restrict to a single class (e.g. 'person'). */
@@ -19647,6 +19748,32 @@ var KeyEventSchema = object({
19647
19748
  ...TrackFlagFields,
19648
19749
  ...TrackRetrainFields
19649
19750
  });
19751
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19752
+ var KeyEventBatchQueryInput = object({
19753
+ deviceIds: array(number()).min(1).max(200),
19754
+ since: number(),
19755
+ until: number(),
19756
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19757
+ * across the set, which would let a busy camera starve a quiet one of its
19758
+ * rows and change what the merged feed contains. */
19759
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19760
+ minImportance: number().min(0).max(1).optional(),
19761
+ classFilter: string().optional()
19762
+ });
19763
+ /**
19764
+ * One camera's key events in a batch answer.
19765
+ *
19766
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19767
+ * error rather than throwing, so a camera whose store read failed and one with
19768
+ * no events in the window were ALREADY indistinguishable per camera — the
19769
+ * batch does not make that worse, and the row keeps the deviceId the single
19770
+ * method's output never carried (the caller used to stamp it from the fan-out
19771
+ * key, which only worked because there was one query per camera).
19772
+ */
19773
+ var KeyEventsForDeviceSchema = object({
19774
+ deviceId: number(),
19775
+ events: array(KeyEventSchema).readonly()
19776
+ });
19650
19777
  object({
19651
19778
  trackId: string(),
19652
19779
  className: string(),
@@ -19916,7 +20043,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19916
20043
  until: number().optional(),
19917
20044
  kinds: array(string()).optional(),
19918
20045
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19919
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20046
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19920
20047
  deviceId: number(),
19921
20048
  since: number(),
19922
20049
  until: number(),
@@ -29474,6 +29601,9 @@ method(object({
29474
29601
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29475
29602
  kind: "query",
29476
29603
  auth: "admin"
29604
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29605
+ kind: "mutation",
29606
+ auth: "admin"
29477
29607
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29478
29608
  kind: "mutation",
29479
29609
  auth: "admin"
@@ -29865,6 +29995,25 @@ var SceneMonitorStatusSchema = object({
29865
29995
  monitors: array(SceneMonitorSchema),
29866
29996
  lastFetchedAt: number()
29867
29997
  });
29998
+ /**
29999
+ * One camera's row in a `listScenesBatch` answer.
30000
+ *
30001
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
30002
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
30003
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
30004
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
30005
+ * configured. Fanned out per camera the difference was visible — one query
30006
+ * errored while the others resolved — and a batch that returned only the rows
30007
+ * it managed would have destroyed it, silently, by making an unreachable camera
30008
+ * indistinguishable from one that answered `monitors: []`.
30009
+ *
30010
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
30011
+ * could not be read"; `status.monitors: []` means "read, and it has none".
30012
+ */
30013
+ var SceneMonitorStatusForDeviceSchema = object({
30014
+ deviceId: number(),
30015
+ status: SceneMonitorStatusSchema.nullable()
30016
+ });
29868
30017
  var sceneMonitorCapability = {
29869
30018
  name: "scene-monitor",
29870
30019
  scope: "device",
@@ -29874,6 +30023,22 @@ var sceneMonitorCapability = {
29874
30023
  deviceTypes: [DeviceType.Camera],
29875
30024
  methods: {
29876
30025
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
30026
+ /**
30027
+ * The same answer, for a SET of cameras, in one round trip.
30028
+ *
30029
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
30030
+ * poll behind the push slice. Fanned out client-side that was one query
30031
+ * per camera — 29 round trips through the browser, the hub and the
30032
+ * post-analysis runner every 30 seconds to read an in-memory map the
30033
+ * owner had already merged. The work is unchanged (`statusFor` per
30034
+ * device, all in-process at the owner); what collapses is the transport.
30035
+ *
30036
+ * A camera that cannot answer still gets a row, with `status: null` —
30037
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
30038
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
30039
+ * tell which two are missing, or that any are.
30040
+ */
30041
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29877
30042
  createScene: method(object({
29878
30043
  deviceId: number(),
29879
30044
  label: string(),
@@ -31907,6 +32072,27 @@ var CameraOccupancySnapshotSchema = object({
31907
32072
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31908
32073
  });
31909
32074
  /**
32075
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
32076
+ *
32077
+ * THREE outcomes, and the single-camera method could only express two of them
32078
+ * because `snapshot: null` was already spoken for:
32079
+ *
32080
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
32081
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
32082
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
32083
+ * - `read: 'unreadable'` — the owner could not answer for this
32084
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
32085
+ *
32086
+ * Collapsing the last two is the failure this field exists to prevent: a
32087
+ * hydration that threw would otherwise render as an empty Stationary section,
32088
+ * which is a definite claim about a camera nobody could read.
32089
+ */
32090
+ var CameraOccupancySnapshotForDeviceSchema = object({
32091
+ deviceId: number(),
32092
+ read: _enum(["read", "unreadable"]),
32093
+ snapshot: CameraOccupancySnapshotSchema.nullable()
32094
+ });
32095
+ /**
31910
32096
  * Time-series resolution. The history methods return one bucket per
31911
32097
  * step over the requested range. Smaller resolutions cost more
31912
32098
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31963,6 +32149,20 @@ var zoneAnalyticsCapability = {
31963
32149
  * (no inference result emitted since boot or since binding was
31964
32150
  * activated). */
31965
32151
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
32152
+ /**
32153
+ * The same snapshot, for a SET of cameras, in one round trip.
32154
+ *
32155
+ * The Events page's Stationary section polls this every 15s for every
32156
+ * selected camera. Fanned out client-side that is one query per camera to
32157
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
32158
+ * costs everything. Batched, N transports become one and the per-device
32159
+ * work is unchanged.
32160
+ *
32161
+ * Every requested deviceId gets a row, tagged `read` — see
32162
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
32163
+ * not answer for is `'unreadable'`, never an empty reading.
32164
+ */
32165
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31966
32166
  /** Time-series object count inside one zone. `className` optional —
31967
32167
  * omit to count every class in the zone. */
31968
32168
  getZoneHistory: method(object({
@@ -36551,6 +36751,12 @@ Object.freeze({
36551
36751
  addonId: null,
36552
36752
  access: "view"
36553
36753
  },
36754
+ "pipelineAnalytics.getKeyEventsBatch": {
36755
+ capName: "pipeline-analytics",
36756
+ capScope: "device",
36757
+ addonId: null,
36758
+ access: "view"
36759
+ },
36554
36760
  "pipelineAnalytics.getMotionEvents": {
36555
36761
  capName: "pipeline-analytics",
36556
36762
  capScope: "device",
@@ -37727,6 +37933,12 @@ Object.freeze({
37727
37933
  addonId: null,
37728
37934
  access: "view"
37729
37935
  },
37936
+ "recording.reconcileLedgerAgainstDisk": {
37937
+ capName: "recording",
37938
+ capScope: "system",
37939
+ addonId: null,
37940
+ access: "create"
37941
+ },
37730
37942
  "recording.refreshStorageLocationsForMigration": {
37731
37943
  capName: "recording",
37732
37944
  capScope: "system",
@@ -37853,6 +38065,12 @@ Object.freeze({
37853
38065
  addonId: null,
37854
38066
  access: "view"
37855
38067
  },
38068
+ "sceneMonitor.listScenesBatch": {
38069
+ capName: "scene-monitor",
38070
+ capScope: "device",
38071
+ addonId: null,
38072
+ access: "view"
38073
+ },
37856
38074
  "sceneMonitor.recheckNow": {
37857
38075
  capName: "scene-monitor",
37858
38076
  capScope: "device",
@@ -39209,6 +39427,12 @@ Object.freeze({
39209
39427
  addonId: null,
39210
39428
  access: "view"
39211
39429
  },
39430
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39431
+ capName: "zone-analytics",
39432
+ capScope: "device",
39433
+ addonId: null,
39434
+ access: "view"
39435
+ },
39212
39436
  "zoneAnalytics.getUnzonedHistory": {
39213
39437
  capName: "zone-analytics",
39214
39438
  capScope: "device",
@@ -40213,6 +40437,11 @@ Object.freeze({
40213
40437
  form: "single",
40214
40438
  optional: false
40215
40439
  }],
40440
+ "pipelineAnalytics.getKeyEventsBatch": [{
40441
+ name: "deviceIds",
40442
+ form: "array",
40443
+ optional: false
40444
+ }],
40216
40445
  "pipelineAnalytics.getMotionEvents": [{
40217
40446
  name: "deviceId",
40218
40447
  form: "single",
@@ -40653,6 +40882,11 @@ Object.freeze({
40653
40882
  form: "single",
40654
40883
  optional: false
40655
40884
  }],
40885
+ "recording.reconcileLedgerAgainstDisk": [{
40886
+ name: "deviceId",
40887
+ form: "single",
40888
+ optional: true
40889
+ }],
40656
40890
  "recording.relocateFootage": [{
40657
40891
  name: "deviceId",
40658
40892
  form: "single",
@@ -40718,6 +40952,11 @@ Object.freeze({
40718
40952
  form: "single",
40719
40953
  optional: false
40720
40954
  }],
40955
+ "sceneMonitor.listScenesBatch": [{
40956
+ name: "deviceIds",
40957
+ form: "array",
40958
+ optional: false
40959
+ }],
40721
40960
  "sceneMonitor.recheckNow": [{
40722
40961
  name: "deviceId",
40723
40962
  form: "single",
@@ -40979,6 +41218,11 @@ Object.freeze({
40979
41218
  form: "single",
40980
41219
  optional: false
40981
41220
  }],
41221
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41222
+ name: "deviceIds",
41223
+ form: "array",
41224
+ optional: false
41225
+ }],
40982
41226
  "zoneAnalytics.getUnzonedHistory": [{
40983
41227
  name: "deviceId",
40984
41228
  form: "single",
package/dist/addon.mjs CHANGED
@@ -8303,6 +8303,20 @@ var RelocateJobSchema = object({
8303
8303
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8304
8304
  */
8305
8305
  rowsReconciled: number().int().nonnegative().optional(),
8306
+ /**
8307
+ * Rows this run FORGOT because the file they name is not on disk.
8308
+ *
8309
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8310
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8311
+ * and the durable row is dropped through the same channel eviction uses. It
8312
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8313
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8314
+ * the same failure as one that quietly skips them (D295).
8315
+ *
8316
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8317
+ * ledger claimed 5.65 GB of footage that no longer existed.
8318
+ */
8319
+ rowsForgotten: number().int().nonnegative().optional(),
8306
8320
  startedAt: number(),
8307
8321
  finishedAt: number().nullable(),
8308
8322
  error: string().nullable()
@@ -8666,6 +8680,91 @@ var RelocateResidueSchema = object({
8666
8680
  segments: number().int().nonnegative(),
8667
8681
  bytes: number().int().nonnegative()
8668
8682
  }).nullable();
8683
+ /**
8684
+ * Ask one location whether its durable hour rows describe the disk — the walk
8685
+ * (D319).
8686
+ *
8687
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8688
+ * missing tool is the question, and the dry run is how they sanity-check the
8689
+ * destructive run before authorising it.
8690
+ */
8691
+ var LedgerWalkInputSchema = object({
8692
+ locationId: string().min(1),
8693
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8694
+ apply: boolean().optional(),
8695
+ /** Narrow to one camera. */
8696
+ deviceId: number().int().positive().optional(),
8697
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8698
+ profiles: array(string().min(1)).optional()
8699
+ });
8700
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8701
+ var LedgerWalkRefusalSchema = _enum([
8702
+ "location-unknown",
8703
+ "source-writable",
8704
+ "no-ledger",
8705
+ "archive-unreadable",
8706
+ "anchor-absent",
8707
+ "anchor-unreadable",
8708
+ "anchor-moved"
8709
+ ]);
8710
+ _enum([
8711
+ "live-tail",
8712
+ "listing-error",
8713
+ "path-mismatch",
8714
+ "durable-refused"
8715
+ ]);
8716
+ /** Every skip reason, always present, always a number — so a reason that never
8717
+ * fired reports as zero rather than absent and the report shape is constant
8718
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8719
+ var LedgerWalkSkipCountsSchema = object({
8720
+ "live-tail": number().int().nonnegative(),
8721
+ "listing-error": number().int().nonnegative(),
8722
+ "path-mismatch": number().int().nonnegative(),
8723
+ "durable-refused": number().int().nonnegative()
8724
+ });
8725
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8726
+ var LedgerWalkDeviceReportSchema = object({
8727
+ deviceId: number().int(),
8728
+ hoursWalked: number().int().nonnegative(),
8729
+ hoursMissing: number().int().nonnegative(),
8730
+ ghostSegments: number().int().nonnegative(),
8731
+ ghostBytes: number().int().nonnegative(),
8732
+ forgottenSegments: number().int().nonnegative(),
8733
+ orphanFiles: number().int().nonnegative()
8734
+ });
8735
+ /**
8736
+ * What one walk claimed, listed, found and (only when armed) forgot.
8737
+ *
8738
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8739
+ * walk that saw a fraction of the location is visible in its own report rather
8740
+ * than in the absence of one.
8741
+ */
8742
+ var LedgerWalkReportSchema = object({
8743
+ locationId: string(),
8744
+ applied: boolean(),
8745
+ refused: LedgerWalkRefusalSchema.nullable(),
8746
+ archiveSegments: number().int().nonnegative().nullable(),
8747
+ archiveBytes: number().int().nonnegative().nullable(),
8748
+ hoursClaimed: number().int().nonnegative(),
8749
+ hoursWalked: number().int().nonnegative(),
8750
+ hoursMissing: number().int().nonnegative(),
8751
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8752
+ listings: number().int().nonnegative(),
8753
+ segmentsClaimed: number().int().nonnegative(),
8754
+ ghostSegments: number().int().nonnegative(),
8755
+ ghostBytes: number().int().nonnegative(),
8756
+ ghostHoursWhole: number().int().nonnegative(),
8757
+ forgottenSegments: number().int().nonnegative(),
8758
+ forgottenBytes: number().int().nonnegative(),
8759
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8760
+ orphanFiles: number().int().nonnegative(),
8761
+ orphanSample: array(string()).readonly(),
8762
+ hoursSkipped: number().int().nonnegative(),
8763
+ skippedByReason: LedgerWalkSkipCountsSchema,
8764
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8765
+ bounded: boolean(),
8766
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8767
+ });
8669
8768
  /** How many rows a media pass would still act on against a given target — the
8670
8769
  * media lane's denominator AND its residue, from ONE derivation so the two can
8671
8770
  * never disagree. `null` = the count could not be taken. */
@@ -19614,13 +19713,15 @@ var ListGroupsPageSchema = object({
19614
19713
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19615
19714
  nextCursor: string().nullable()
19616
19715
  });
19716
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19717
+ var KEY_EVENTS_MAX_LIMIT = 200;
19617
19718
  var KeyEventQueryInput = object({
19618
19719
  deviceId: number(),
19619
19720
  /** Window lower bound (track firstSeen ≥ since). */
19620
19721
  since: number(),
19621
19722
  /** Window upper bound (track firstSeen ≤ until). */
19622
19723
  until: number(),
19623
- limit: number().int().min(1).max(200).default(50),
19724
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19624
19725
  /** Drop tracks scoring below this importance. */
19625
19726
  minImportance: number().min(0).max(1).optional(),
19626
19727
  /** Restrict to a single class (e.g. 'person'). */
@@ -19642,6 +19743,32 @@ var KeyEventSchema = object({
19642
19743
  ...TrackFlagFields,
19643
19744
  ...TrackRetrainFields
19644
19745
  });
19746
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19747
+ var KeyEventBatchQueryInput = object({
19748
+ deviceIds: array(number()).min(1).max(200),
19749
+ since: number(),
19750
+ until: number(),
19751
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19752
+ * across the set, which would let a busy camera starve a quiet one of its
19753
+ * rows and change what the merged feed contains. */
19754
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19755
+ minImportance: number().min(0).max(1).optional(),
19756
+ classFilter: string().optional()
19757
+ });
19758
+ /**
19759
+ * One camera's key events in a batch answer.
19760
+ *
19761
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19762
+ * error rather than throwing, so a camera whose store read failed and one with
19763
+ * no events in the window were ALREADY indistinguishable per camera — the
19764
+ * batch does not make that worse, and the row keeps the deviceId the single
19765
+ * method's output never carried (the caller used to stamp it from the fan-out
19766
+ * key, which only worked because there was one query per camera).
19767
+ */
19768
+ var KeyEventsForDeviceSchema = object({
19769
+ deviceId: number(),
19770
+ events: array(KeyEventSchema).readonly()
19771
+ });
19645
19772
  object({
19646
19773
  trackId: string(),
19647
19774
  className: string(),
@@ -19911,7 +20038,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19911
20038
  until: number().optional(),
19912
20039
  kinds: array(string()).optional(),
19913
20040
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19914
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20041
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19915
20042
  deviceId: number(),
19916
20043
  since: number(),
19917
20044
  until: number(),
@@ -29469,6 +29596,9 @@ method(object({
29469
29596
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29470
29597
  kind: "query",
29471
29598
  auth: "admin"
29599
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29600
+ kind: "mutation",
29601
+ auth: "admin"
29472
29602
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29473
29603
  kind: "mutation",
29474
29604
  auth: "admin"
@@ -29860,6 +29990,25 @@ var SceneMonitorStatusSchema = object({
29860
29990
  monitors: array(SceneMonitorSchema),
29861
29991
  lastFetchedAt: number()
29862
29992
  });
29993
+ /**
29994
+ * One camera's row in a `listScenesBatch` answer.
29995
+ *
29996
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29997
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29998
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29999
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
30000
+ * configured. Fanned out per camera the difference was visible — one query
30001
+ * errored while the others resolved — and a batch that returned only the rows
30002
+ * it managed would have destroyed it, silently, by making an unreachable camera
30003
+ * indistinguishable from one that answered `monitors: []`.
30004
+ *
30005
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
30006
+ * could not be read"; `status.monitors: []` means "read, and it has none".
30007
+ */
30008
+ var SceneMonitorStatusForDeviceSchema = object({
30009
+ deviceId: number(),
30010
+ status: SceneMonitorStatusSchema.nullable()
30011
+ });
29863
30012
  var sceneMonitorCapability = {
29864
30013
  name: "scene-monitor",
29865
30014
  scope: "device",
@@ -29869,6 +30018,22 @@ var sceneMonitorCapability = {
29869
30018
  deviceTypes: [DeviceType.Camera],
29870
30019
  methods: {
29871
30020
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
30021
+ /**
30022
+ * The same answer, for a SET of cameras, in one round trip.
30023
+ *
30024
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
30025
+ * poll behind the push slice. Fanned out client-side that was one query
30026
+ * per camera — 29 round trips through the browser, the hub and the
30027
+ * post-analysis runner every 30 seconds to read an in-memory map the
30028
+ * owner had already merged. The work is unchanged (`statusFor` per
30029
+ * device, all in-process at the owner); what collapses is the transport.
30030
+ *
30031
+ * A camera that cannot answer still gets a row, with `status: null` —
30032
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
30033
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
30034
+ * tell which two are missing, or that any are.
30035
+ */
30036
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29872
30037
  createScene: method(object({
29873
30038
  deviceId: number(),
29874
30039
  label: string(),
@@ -31902,6 +32067,27 @@ var CameraOccupancySnapshotSchema = object({
31902
32067
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31903
32068
  });
31904
32069
  /**
32070
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
32071
+ *
32072
+ * THREE outcomes, and the single-camera method could only express two of them
32073
+ * because `snapshot: null` was already spoken for:
32074
+ *
32075
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
32076
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
32077
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
32078
+ * - `read: 'unreadable'` — the owner could not answer for this
32079
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
32080
+ *
32081
+ * Collapsing the last two is the failure this field exists to prevent: a
32082
+ * hydration that threw would otherwise render as an empty Stationary section,
32083
+ * which is a definite claim about a camera nobody could read.
32084
+ */
32085
+ var CameraOccupancySnapshotForDeviceSchema = object({
32086
+ deviceId: number(),
32087
+ read: _enum(["read", "unreadable"]),
32088
+ snapshot: CameraOccupancySnapshotSchema.nullable()
32089
+ });
32090
+ /**
31905
32091
  * Time-series resolution. The history methods return one bucket per
31906
32092
  * step over the requested range. Smaller resolutions cost more
31907
32093
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31958,6 +32144,20 @@ var zoneAnalyticsCapability = {
31958
32144
  * (no inference result emitted since boot or since binding was
31959
32145
  * activated). */
31960
32146
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
32147
+ /**
32148
+ * The same snapshot, for a SET of cameras, in one round trip.
32149
+ *
32150
+ * The Events page's Stationary section polls this every 15s for every
32151
+ * selected camera. Fanned out client-side that is one query per camera to
32152
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
32153
+ * costs everything. Batched, N transports become one and the per-device
32154
+ * work is unchanged.
32155
+ *
32156
+ * Every requested deviceId gets a row, tagged `read` — see
32157
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
32158
+ * not answer for is `'unreadable'`, never an empty reading.
32159
+ */
32160
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31961
32161
  /** Time-series object count inside one zone. `className` optional —
31962
32162
  * omit to count every class in the zone. */
31963
32163
  getZoneHistory: method(object({
@@ -36546,6 +36746,12 @@ Object.freeze({
36546
36746
  addonId: null,
36547
36747
  access: "view"
36548
36748
  },
36749
+ "pipelineAnalytics.getKeyEventsBatch": {
36750
+ capName: "pipeline-analytics",
36751
+ capScope: "device",
36752
+ addonId: null,
36753
+ access: "view"
36754
+ },
36549
36755
  "pipelineAnalytics.getMotionEvents": {
36550
36756
  capName: "pipeline-analytics",
36551
36757
  capScope: "device",
@@ -37722,6 +37928,12 @@ Object.freeze({
37722
37928
  addonId: null,
37723
37929
  access: "view"
37724
37930
  },
37931
+ "recording.reconcileLedgerAgainstDisk": {
37932
+ capName: "recording",
37933
+ capScope: "system",
37934
+ addonId: null,
37935
+ access: "create"
37936
+ },
37725
37937
  "recording.refreshStorageLocationsForMigration": {
37726
37938
  capName: "recording",
37727
37939
  capScope: "system",
@@ -37848,6 +38060,12 @@ Object.freeze({
37848
38060
  addonId: null,
37849
38061
  access: "view"
37850
38062
  },
38063
+ "sceneMonitor.listScenesBatch": {
38064
+ capName: "scene-monitor",
38065
+ capScope: "device",
38066
+ addonId: null,
38067
+ access: "view"
38068
+ },
37851
38069
  "sceneMonitor.recheckNow": {
37852
38070
  capName: "scene-monitor",
37853
38071
  capScope: "device",
@@ -39204,6 +39422,12 @@ Object.freeze({
39204
39422
  addonId: null,
39205
39423
  access: "view"
39206
39424
  },
39425
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39426
+ capName: "zone-analytics",
39427
+ capScope: "device",
39428
+ addonId: null,
39429
+ access: "view"
39430
+ },
39207
39431
  "zoneAnalytics.getUnzonedHistory": {
39208
39432
  capName: "zone-analytics",
39209
39433
  capScope: "device",
@@ -40208,6 +40432,11 @@ Object.freeze({
40208
40432
  form: "single",
40209
40433
  optional: false
40210
40434
  }],
40435
+ "pipelineAnalytics.getKeyEventsBatch": [{
40436
+ name: "deviceIds",
40437
+ form: "array",
40438
+ optional: false
40439
+ }],
40211
40440
  "pipelineAnalytics.getMotionEvents": [{
40212
40441
  name: "deviceId",
40213
40442
  form: "single",
@@ -40648,6 +40877,11 @@ Object.freeze({
40648
40877
  form: "single",
40649
40878
  optional: false
40650
40879
  }],
40880
+ "recording.reconcileLedgerAgainstDisk": [{
40881
+ name: "deviceId",
40882
+ form: "single",
40883
+ optional: true
40884
+ }],
40651
40885
  "recording.relocateFootage": [{
40652
40886
  name: "deviceId",
40653
40887
  form: "single",
@@ -40713,6 +40947,11 @@ Object.freeze({
40713
40947
  form: "single",
40714
40948
  optional: false
40715
40949
  }],
40950
+ "sceneMonitor.listScenesBatch": [{
40951
+ name: "deviceIds",
40952
+ form: "array",
40953
+ optional: false
40954
+ }],
40716
40955
  "sceneMonitor.recheckNow": [{
40717
40956
  name: "deviceId",
40718
40957
  form: "single",
@@ -40974,6 +41213,11 @@ Object.freeze({
40974
41213
  form: "single",
40975
41214
  optional: false
40976
41215
  }],
41216
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41217
+ name: "deviceIds",
41218
+ form: "array",
41219
+ optional: false
41220
+ }],
40977
41221
  "zoneAnalytics.getUnzonedHistory": [{
40978
41222
  name: "deviceId",
40979
41223
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-reolink",
3
- "version": "1.2.71",
3
+ "version": "1.2.72",
4
4
  "description": "Reolink camera device provider addon for CamStack — native Baichuan protocol",
5
5
  "keywords": [
6
6
  "camstack",