@camstack/addon-export-hap 1.2.59 → 1.2.60

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.
@@ -8684,6 +8684,20 @@ var RelocateJobSchema = object({
8684
8684
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8685
8685
  */
8686
8686
  rowsReconciled: number().int().nonnegative().optional(),
8687
+ /**
8688
+ * Rows this run FORGOT because the file they name is not on disk.
8689
+ *
8690
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8691
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8692
+ * and the durable row is dropped through the same channel eviction uses. It
8693
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8694
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8695
+ * the same failure as one that quietly skips them (D295).
8696
+ *
8697
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8698
+ * ledger claimed 5.65 GB of footage that no longer existed.
8699
+ */
8700
+ rowsForgotten: number().int().nonnegative().optional(),
8687
8701
  startedAt: number(),
8688
8702
  finishedAt: number().nullable(),
8689
8703
  error: string().nullable()
@@ -9047,6 +9061,91 @@ var RelocateResidueSchema = object({
9047
9061
  segments: number().int().nonnegative(),
9048
9062
  bytes: number().int().nonnegative()
9049
9063
  }).nullable();
9064
+ /**
9065
+ * Ask one location whether its durable hour rows describe the disk — the walk
9066
+ * (D319).
9067
+ *
9068
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
9069
+ * missing tool is the question, and the dry run is how they sanity-check the
9070
+ * destructive run before authorising it.
9071
+ */
9072
+ var LedgerWalkInputSchema = object({
9073
+ locationId: string().min(1),
9074
+ /** Forget the confirmed-absent rows, rather than only counting them. */
9075
+ apply: boolean().optional(),
9076
+ /** Narrow to one camera. */
9077
+ deviceId: number().int().positive().optional(),
9078
+ /** Narrow to these recording profiles; empty/absent = every profile. */
9079
+ profiles: array(string().min(1)).optional()
9080
+ });
9081
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
9082
+ var LedgerWalkRefusalSchema = _enum([
9083
+ "location-unknown",
9084
+ "source-writable",
9085
+ "no-ledger",
9086
+ "archive-unreadable",
9087
+ "anchor-absent",
9088
+ "anchor-unreadable",
9089
+ "anchor-moved"
9090
+ ]);
9091
+ _enum([
9092
+ "live-tail",
9093
+ "listing-error",
9094
+ "path-mismatch",
9095
+ "durable-refused"
9096
+ ]);
9097
+ /** Every skip reason, always present, always a number — so a reason that never
9098
+ * fired reports as zero rather than absent and the report shape is constant
9099
+ * between passes. Spelled out rather than `z.record` for exactly that. */
9100
+ var LedgerWalkSkipCountsSchema = object({
9101
+ "live-tail": number().int().nonnegative(),
9102
+ "listing-error": number().int().nonnegative(),
9103
+ "path-mismatch": number().int().nonnegative(),
9104
+ "durable-refused": number().int().nonnegative()
9105
+ });
9106
+ /** One camera's share of a walk, so a report names cameras and not rows. */
9107
+ var LedgerWalkDeviceReportSchema = object({
9108
+ deviceId: number().int(),
9109
+ hoursWalked: number().int().nonnegative(),
9110
+ hoursMissing: number().int().nonnegative(),
9111
+ ghostSegments: number().int().nonnegative(),
9112
+ ghostBytes: number().int().nonnegative(),
9113
+ forgottenSegments: number().int().nonnegative(),
9114
+ orphanFiles: number().int().nonnegative()
9115
+ });
9116
+ /**
9117
+ * What one walk claimed, listed, found and (only when armed) forgot.
9118
+ *
9119
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
9120
+ * walk that saw a fraction of the location is visible in its own report rather
9121
+ * than in the absence of one.
9122
+ */
9123
+ var LedgerWalkReportSchema = object({
9124
+ locationId: string(),
9125
+ applied: boolean(),
9126
+ refused: LedgerWalkRefusalSchema.nullable(),
9127
+ archiveSegments: number().int().nonnegative().nullable(),
9128
+ archiveBytes: number().int().nonnegative().nullable(),
9129
+ hoursClaimed: number().int().nonnegative(),
9130
+ hoursWalked: number().int().nonnegative(),
9131
+ hoursMissing: number().int().nonnegative(),
9132
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
9133
+ listings: number().int().nonnegative(),
9134
+ segmentsClaimed: number().int().nonnegative(),
9135
+ ghostSegments: number().int().nonnegative(),
9136
+ ghostBytes: number().int().nonnegative(),
9137
+ ghostHoursWhole: number().int().nonnegative(),
9138
+ forgottenSegments: number().int().nonnegative(),
9139
+ forgottenBytes: number().int().nonnegative(),
9140
+ /** Files under a claimed hour that no durable row names. Never deleted. */
9141
+ orphanFiles: number().int().nonnegative(),
9142
+ orphanSample: array(string()).readonly(),
9143
+ hoursSkipped: number().int().nonnegative(),
9144
+ skippedByReason: LedgerWalkSkipCountsSchema,
9145
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
9146
+ bounded: boolean(),
9147
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
9148
+ });
9050
9149
  /** How many rows a media pass would still act on against a given target — the
9051
9150
  * media lane's denominator AND its residue, from ONE derivation so the two can
9052
9151
  * never disagree. `null` = the count could not be taken. */
@@ -19455,13 +19554,15 @@ var ListGroupsPageSchema = object({
19455
19554
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19456
19555
  nextCursor: string().nullable()
19457
19556
  });
19557
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19558
+ var KEY_EVENTS_MAX_LIMIT = 200;
19458
19559
  var KeyEventQueryInput = object({
19459
19560
  deviceId: number(),
19460
19561
  /** Window lower bound (track firstSeen ≥ since). */
19461
19562
  since: number(),
19462
19563
  /** Window upper bound (track firstSeen ≤ until). */
19463
19564
  until: number(),
19464
- limit: number().int().min(1).max(200).default(50),
19565
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19465
19566
  /** Drop tracks scoring below this importance. */
19466
19567
  minImportance: number().min(0).max(1).optional(),
19467
19568
  /** Restrict to a single class (e.g. 'person'). */
@@ -19483,6 +19584,32 @@ var KeyEventSchema = object({
19483
19584
  ...TrackFlagFields,
19484
19585
  ...TrackRetrainFields
19485
19586
  });
19587
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19588
+ var KeyEventBatchQueryInput = object({
19589
+ deviceIds: array(number()).min(1).max(200),
19590
+ since: number(),
19591
+ until: number(),
19592
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19593
+ * across the set, which would let a busy camera starve a quiet one of its
19594
+ * rows and change what the merged feed contains. */
19595
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19596
+ minImportance: number().min(0).max(1).optional(),
19597
+ classFilter: string().optional()
19598
+ });
19599
+ /**
19600
+ * One camera's key events in a batch answer.
19601
+ *
19602
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19603
+ * error rather than throwing, so a camera whose store read failed and one with
19604
+ * no events in the window were ALREADY indistinguishable per camera — the
19605
+ * batch does not make that worse, and the row keeps the deviceId the single
19606
+ * method's output never carried (the caller used to stamp it from the fan-out
19607
+ * key, which only worked because there was one query per camera).
19608
+ */
19609
+ var KeyEventsForDeviceSchema = object({
19610
+ deviceId: number(),
19611
+ events: array(KeyEventSchema).readonly()
19612
+ });
19486
19613
  object({
19487
19614
  trackId: string(),
19488
19615
  className: string(),
@@ -19752,7 +19879,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19752
19879
  until: number().optional(),
19753
19880
  kinds: array(string()).optional(),
19754
19881
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19755
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19882
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19756
19883
  deviceId: number(),
19757
19884
  since: number(),
19758
19885
  until: number(),
@@ -27452,6 +27579,9 @@ method(object({
27452
27579
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
27453
27580
  kind: "query",
27454
27581
  auth: "admin"
27582
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
27583
+ kind: "mutation",
27584
+ auth: "admin"
27455
27585
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
27456
27586
  kind: "mutation",
27457
27587
  auth: "admin"
@@ -27843,7 +27973,26 @@ var SceneMonitorStatusSchema = object({
27843
27973
  monitors: array(SceneMonitorSchema),
27844
27974
  lastFetchedAt: number()
27845
27975
  });
27846
- DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
27976
+ /**
27977
+ * One camera's row in a `listScenesBatch` answer.
27978
+ *
27979
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
27980
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
27981
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
27982
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
27983
+ * configured. Fanned out per camera the difference was visible — one query
27984
+ * errored while the others resolved — and a batch that returned only the rows
27985
+ * it managed would have destroyed it, silently, by making an unreachable camera
27986
+ * indistinguishable from one that answered `monitors: []`.
27987
+ *
27988
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
27989
+ * could not be read"; `status.monitors: []` means "read, and it has none".
27990
+ */
27991
+ var SceneMonitorStatusForDeviceSchema = object({
27992
+ deviceId: number(),
27993
+ status: SceneMonitorStatusSchema.nullable()
27994
+ });
27995
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
27847
27996
  deviceId: number(),
27848
27997
  label: string(),
27849
27998
  roi: MaskRectShapeSchema,
@@ -29192,6 +29341,27 @@ var CameraOccupancySnapshotSchema = object({
29192
29341
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
29193
29342
  });
29194
29343
  /**
29344
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
29345
+ *
29346
+ * THREE outcomes, and the single-camera method could only express two of them
29347
+ * because `snapshot: null` was already spoken for:
29348
+ *
29349
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
29350
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
29351
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
29352
+ * - `read: 'unreadable'` — the owner could not answer for this
29353
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
29354
+ *
29355
+ * Collapsing the last two is the failure this field exists to prevent: a
29356
+ * hydration that threw would otherwise render as an empty Stationary section,
29357
+ * which is a definite claim about a camera nobody could read.
29358
+ */
29359
+ var CameraOccupancySnapshotForDeviceSchema = object({
29360
+ deviceId: number(),
29361
+ read: _enum(["read", "unreadable"]),
29362
+ snapshot: CameraOccupancySnapshotSchema.nullable()
29363
+ });
29364
+ /**
29195
29365
  * Time-series resolution. The history methods return one bucket per
29196
29366
  * step over the requested range. Smaller resolutions cost more
29197
29367
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -29215,7 +29385,7 @@ var HistoryPointSchema = object({
29215
29385
  /** Object count averaged over the bucket (rounded to nearest integer). */
29216
29386
  count: number().int().nonnegative()
29217
29387
  });
29218
- DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
29388
+ DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
29219
29389
  deviceId: number(),
29220
29390
  zoneId: string(),
29221
29391
  className: string().optional()
@@ -32520,6 +32690,12 @@ Object.freeze({
32520
32690
  addonId: null,
32521
32691
  access: "view"
32522
32692
  },
32693
+ "pipelineAnalytics.getKeyEventsBatch": {
32694
+ capName: "pipeline-analytics",
32695
+ capScope: "device",
32696
+ addonId: null,
32697
+ access: "view"
32698
+ },
32523
32699
  "pipelineAnalytics.getMotionEvents": {
32524
32700
  capName: "pipeline-analytics",
32525
32701
  capScope: "device",
@@ -33696,6 +33872,12 @@ Object.freeze({
33696
33872
  addonId: null,
33697
33873
  access: "view"
33698
33874
  },
33875
+ "recording.reconcileLedgerAgainstDisk": {
33876
+ capName: "recording",
33877
+ capScope: "system",
33878
+ addonId: null,
33879
+ access: "create"
33880
+ },
33699
33881
  "recording.refreshStorageLocationsForMigration": {
33700
33882
  capName: "recording",
33701
33883
  capScope: "system",
@@ -33822,6 +34004,12 @@ Object.freeze({
33822
34004
  addonId: null,
33823
34005
  access: "view"
33824
34006
  },
34007
+ "sceneMonitor.listScenesBatch": {
34008
+ capName: "scene-monitor",
34009
+ capScope: "device",
34010
+ addonId: null,
34011
+ access: "view"
34012
+ },
33825
34013
  "sceneMonitor.recheckNow": {
33826
34014
  capName: "scene-monitor",
33827
34015
  capScope: "device",
@@ -35178,6 +35366,12 @@ Object.freeze({
35178
35366
  addonId: null,
35179
35367
  access: "view"
35180
35368
  },
35369
+ "zoneAnalytics.getCurrentSnapshotBatch": {
35370
+ capName: "zone-analytics",
35371
+ capScope: "device",
35372
+ addonId: null,
35373
+ access: "view"
35374
+ },
35181
35375
  "zoneAnalytics.getUnzonedHistory": {
35182
35376
  capName: "zone-analytics",
35183
35377
  capScope: "device",
@@ -36182,6 +36376,11 @@ Object.freeze({
36182
36376
  form: "single",
36183
36377
  optional: false
36184
36378
  }],
36379
+ "pipelineAnalytics.getKeyEventsBatch": [{
36380
+ name: "deviceIds",
36381
+ form: "array",
36382
+ optional: false
36383
+ }],
36185
36384
  "pipelineAnalytics.getMotionEvents": [{
36186
36385
  name: "deviceId",
36187
36386
  form: "single",
@@ -36622,6 +36821,11 @@ Object.freeze({
36622
36821
  form: "single",
36623
36822
  optional: false
36624
36823
  }],
36824
+ "recording.reconcileLedgerAgainstDisk": [{
36825
+ name: "deviceId",
36826
+ form: "single",
36827
+ optional: true
36828
+ }],
36625
36829
  "recording.relocateFootage": [{
36626
36830
  name: "deviceId",
36627
36831
  form: "single",
@@ -36687,6 +36891,11 @@ Object.freeze({
36687
36891
  form: "single",
36688
36892
  optional: false
36689
36893
  }],
36894
+ "sceneMonitor.listScenesBatch": [{
36895
+ name: "deviceIds",
36896
+ form: "array",
36897
+ optional: false
36898
+ }],
36690
36899
  "sceneMonitor.recheckNow": [{
36691
36900
  name: "deviceId",
36692
36901
  form: "single",
@@ -36948,6 +37157,11 @@ Object.freeze({
36948
37157
  form: "single",
36949
37158
  optional: false
36950
37159
  }],
37160
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
37161
+ name: "deviceIds",
37162
+ form: "array",
37163
+ optional: false
37164
+ }],
36951
37165
  "zoneAnalytics.getUnzonedHistory": [{
36952
37166
  name: "deviceId",
36953
37167
  form: "single",
@@ -8672,6 +8672,20 @@ var RelocateJobSchema = object({
8672
8672
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8673
8673
  */
8674
8674
  rowsReconciled: number().int().nonnegative().optional(),
8675
+ /**
8676
+ * Rows this run FORGOT because the file they name is not on disk.
8677
+ *
8678
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8679
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8680
+ * and the durable row is dropped through the same channel eviction uses. It
8681
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8682
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8683
+ * the same failure as one that quietly skips them (D295).
8684
+ *
8685
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8686
+ * ledger claimed 5.65 GB of footage that no longer existed.
8687
+ */
8688
+ rowsForgotten: number().int().nonnegative().optional(),
8675
8689
  startedAt: number(),
8676
8690
  finishedAt: number().nullable(),
8677
8691
  error: string().nullable()
@@ -9035,6 +9049,91 @@ var RelocateResidueSchema = object({
9035
9049
  segments: number().int().nonnegative(),
9036
9050
  bytes: number().int().nonnegative()
9037
9051
  }).nullable();
9052
+ /**
9053
+ * Ask one location whether its durable hour rows describe the disk — the walk
9054
+ * (D319).
9055
+ *
9056
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
9057
+ * missing tool is the question, and the dry run is how they sanity-check the
9058
+ * destructive run before authorising it.
9059
+ */
9060
+ var LedgerWalkInputSchema = object({
9061
+ locationId: string().min(1),
9062
+ /** Forget the confirmed-absent rows, rather than only counting them. */
9063
+ apply: boolean().optional(),
9064
+ /** Narrow to one camera. */
9065
+ deviceId: number().int().positive().optional(),
9066
+ /** Narrow to these recording profiles; empty/absent = every profile. */
9067
+ profiles: array(string().min(1)).optional()
9068
+ });
9069
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
9070
+ var LedgerWalkRefusalSchema = _enum([
9071
+ "location-unknown",
9072
+ "source-writable",
9073
+ "no-ledger",
9074
+ "archive-unreadable",
9075
+ "anchor-absent",
9076
+ "anchor-unreadable",
9077
+ "anchor-moved"
9078
+ ]);
9079
+ _enum([
9080
+ "live-tail",
9081
+ "listing-error",
9082
+ "path-mismatch",
9083
+ "durable-refused"
9084
+ ]);
9085
+ /** Every skip reason, always present, always a number — so a reason that never
9086
+ * fired reports as zero rather than absent and the report shape is constant
9087
+ * between passes. Spelled out rather than `z.record` for exactly that. */
9088
+ var LedgerWalkSkipCountsSchema = object({
9089
+ "live-tail": number().int().nonnegative(),
9090
+ "listing-error": number().int().nonnegative(),
9091
+ "path-mismatch": number().int().nonnegative(),
9092
+ "durable-refused": number().int().nonnegative()
9093
+ });
9094
+ /** One camera's share of a walk, so a report names cameras and not rows. */
9095
+ var LedgerWalkDeviceReportSchema = object({
9096
+ deviceId: number().int(),
9097
+ hoursWalked: number().int().nonnegative(),
9098
+ hoursMissing: number().int().nonnegative(),
9099
+ ghostSegments: number().int().nonnegative(),
9100
+ ghostBytes: number().int().nonnegative(),
9101
+ forgottenSegments: number().int().nonnegative(),
9102
+ orphanFiles: number().int().nonnegative()
9103
+ });
9104
+ /**
9105
+ * What one walk claimed, listed, found and (only when armed) forgot.
9106
+ *
9107
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
9108
+ * walk that saw a fraction of the location is visible in its own report rather
9109
+ * than in the absence of one.
9110
+ */
9111
+ var LedgerWalkReportSchema = object({
9112
+ locationId: string(),
9113
+ applied: boolean(),
9114
+ refused: LedgerWalkRefusalSchema.nullable(),
9115
+ archiveSegments: number().int().nonnegative().nullable(),
9116
+ archiveBytes: number().int().nonnegative().nullable(),
9117
+ hoursClaimed: number().int().nonnegative(),
9118
+ hoursWalked: number().int().nonnegative(),
9119
+ hoursMissing: number().int().nonnegative(),
9120
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
9121
+ listings: number().int().nonnegative(),
9122
+ segmentsClaimed: number().int().nonnegative(),
9123
+ ghostSegments: number().int().nonnegative(),
9124
+ ghostBytes: number().int().nonnegative(),
9125
+ ghostHoursWhole: number().int().nonnegative(),
9126
+ forgottenSegments: number().int().nonnegative(),
9127
+ forgottenBytes: number().int().nonnegative(),
9128
+ /** Files under a claimed hour that no durable row names. Never deleted. */
9129
+ orphanFiles: number().int().nonnegative(),
9130
+ orphanSample: array(string()).readonly(),
9131
+ hoursSkipped: number().int().nonnegative(),
9132
+ skippedByReason: LedgerWalkSkipCountsSchema,
9133
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
9134
+ bounded: boolean(),
9135
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
9136
+ });
9038
9137
  /** How many rows a media pass would still act on against a given target — the
9039
9138
  * media lane's denominator AND its residue, from ONE derivation so the two can
9040
9139
  * never disagree. `null` = the count could not be taken. */
@@ -19443,13 +19542,15 @@ var ListGroupsPageSchema = object({
19443
19542
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19444
19543
  nextCursor: string().nullable()
19445
19544
  });
19545
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19546
+ var KEY_EVENTS_MAX_LIMIT = 200;
19446
19547
  var KeyEventQueryInput = object({
19447
19548
  deviceId: number(),
19448
19549
  /** Window lower bound (track firstSeen ≥ since). */
19449
19550
  since: number(),
19450
19551
  /** Window upper bound (track firstSeen ≤ until). */
19451
19552
  until: number(),
19452
- limit: number().int().min(1).max(200).default(50),
19553
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19453
19554
  /** Drop tracks scoring below this importance. */
19454
19555
  minImportance: number().min(0).max(1).optional(),
19455
19556
  /** Restrict to a single class (e.g. 'person'). */
@@ -19471,6 +19572,32 @@ var KeyEventSchema = object({
19471
19572
  ...TrackFlagFields,
19472
19573
  ...TrackRetrainFields
19473
19574
  });
19575
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19576
+ var KeyEventBatchQueryInput = object({
19577
+ deviceIds: array(number()).min(1).max(200),
19578
+ since: number(),
19579
+ until: number(),
19580
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19581
+ * across the set, which would let a busy camera starve a quiet one of its
19582
+ * rows and change what the merged feed contains. */
19583
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19584
+ minImportance: number().min(0).max(1).optional(),
19585
+ classFilter: string().optional()
19586
+ });
19587
+ /**
19588
+ * One camera's key events in a batch answer.
19589
+ *
19590
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19591
+ * error rather than throwing, so a camera whose store read failed and one with
19592
+ * no events in the window were ALREADY indistinguishable per camera — the
19593
+ * batch does not make that worse, and the row keeps the deviceId the single
19594
+ * method's output never carried (the caller used to stamp it from the fan-out
19595
+ * key, which only worked because there was one query per camera).
19596
+ */
19597
+ var KeyEventsForDeviceSchema = object({
19598
+ deviceId: number(),
19599
+ events: array(KeyEventSchema).readonly()
19600
+ });
19474
19601
  object({
19475
19602
  trackId: string(),
19476
19603
  className: string(),
@@ -19740,7 +19867,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19740
19867
  until: number().optional(),
19741
19868
  kinds: array(string()).optional(),
19742
19869
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19743
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19870
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19744
19871
  deviceId: number(),
19745
19872
  since: number(),
19746
19873
  until: number(),
@@ -27440,6 +27567,9 @@ method(object({
27440
27567
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
27441
27568
  kind: "query",
27442
27569
  auth: "admin"
27570
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
27571
+ kind: "mutation",
27572
+ auth: "admin"
27443
27573
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
27444
27574
  kind: "mutation",
27445
27575
  auth: "admin"
@@ -27831,7 +27961,26 @@ var SceneMonitorStatusSchema = object({
27831
27961
  monitors: array(SceneMonitorSchema),
27832
27962
  lastFetchedAt: number()
27833
27963
  });
27834
- DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
27964
+ /**
27965
+ * One camera's row in a `listScenesBatch` answer.
27966
+ *
27967
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
27968
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
27969
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
27970
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
27971
+ * configured. Fanned out per camera the difference was visible — one query
27972
+ * errored while the others resolved — and a batch that returned only the rows
27973
+ * it managed would have destroyed it, silently, by making an unreachable camera
27974
+ * indistinguishable from one that answered `monitors: []`.
27975
+ *
27976
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
27977
+ * could not be read"; `status.monitors: []` means "read, and it has none".
27978
+ */
27979
+ var SceneMonitorStatusForDeviceSchema = object({
27980
+ deviceId: number(),
27981
+ status: SceneMonitorStatusSchema.nullable()
27982
+ });
27983
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
27835
27984
  deviceId: number(),
27836
27985
  label: string(),
27837
27986
  roi: MaskRectShapeSchema,
@@ -29180,6 +29329,27 @@ var CameraOccupancySnapshotSchema = object({
29180
29329
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
29181
29330
  });
29182
29331
  /**
29332
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
29333
+ *
29334
+ * THREE outcomes, and the single-camera method could only express two of them
29335
+ * because `snapshot: null` was already spoken for:
29336
+ *
29337
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
29338
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
29339
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
29340
+ * - `read: 'unreadable'` — the owner could not answer for this
29341
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
29342
+ *
29343
+ * Collapsing the last two is the failure this field exists to prevent: a
29344
+ * hydration that threw would otherwise render as an empty Stationary section,
29345
+ * which is a definite claim about a camera nobody could read.
29346
+ */
29347
+ var CameraOccupancySnapshotForDeviceSchema = object({
29348
+ deviceId: number(),
29349
+ read: _enum(["read", "unreadable"]),
29350
+ snapshot: CameraOccupancySnapshotSchema.nullable()
29351
+ });
29352
+ /**
29183
29353
  * Time-series resolution. The history methods return one bucket per
29184
29354
  * step over the requested range. Smaller resolutions cost more
29185
29355
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -29203,7 +29373,7 @@ var HistoryPointSchema = object({
29203
29373
  /** Object count averaged over the bucket (rounded to nearest integer). */
29204
29374
  count: number().int().nonnegative()
29205
29375
  });
29206
- DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
29376
+ DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
29207
29377
  deviceId: number(),
29208
29378
  zoneId: string(),
29209
29379
  className: string().optional()
@@ -32508,6 +32678,12 @@ Object.freeze({
32508
32678
  addonId: null,
32509
32679
  access: "view"
32510
32680
  },
32681
+ "pipelineAnalytics.getKeyEventsBatch": {
32682
+ capName: "pipeline-analytics",
32683
+ capScope: "device",
32684
+ addonId: null,
32685
+ access: "view"
32686
+ },
32511
32687
  "pipelineAnalytics.getMotionEvents": {
32512
32688
  capName: "pipeline-analytics",
32513
32689
  capScope: "device",
@@ -33684,6 +33860,12 @@ Object.freeze({
33684
33860
  addonId: null,
33685
33861
  access: "view"
33686
33862
  },
33863
+ "recording.reconcileLedgerAgainstDisk": {
33864
+ capName: "recording",
33865
+ capScope: "system",
33866
+ addonId: null,
33867
+ access: "create"
33868
+ },
33687
33869
  "recording.refreshStorageLocationsForMigration": {
33688
33870
  capName: "recording",
33689
33871
  capScope: "system",
@@ -33810,6 +33992,12 @@ Object.freeze({
33810
33992
  addonId: null,
33811
33993
  access: "view"
33812
33994
  },
33995
+ "sceneMonitor.listScenesBatch": {
33996
+ capName: "scene-monitor",
33997
+ capScope: "device",
33998
+ addonId: null,
33999
+ access: "view"
34000
+ },
33813
34001
  "sceneMonitor.recheckNow": {
33814
34002
  capName: "scene-monitor",
33815
34003
  capScope: "device",
@@ -35166,6 +35354,12 @@ Object.freeze({
35166
35354
  addonId: null,
35167
35355
  access: "view"
35168
35356
  },
35357
+ "zoneAnalytics.getCurrentSnapshotBatch": {
35358
+ capName: "zone-analytics",
35359
+ capScope: "device",
35360
+ addonId: null,
35361
+ access: "view"
35362
+ },
35169
35363
  "zoneAnalytics.getUnzonedHistory": {
35170
35364
  capName: "zone-analytics",
35171
35365
  capScope: "device",
@@ -36170,6 +36364,11 @@ Object.freeze({
36170
36364
  form: "single",
36171
36365
  optional: false
36172
36366
  }],
36367
+ "pipelineAnalytics.getKeyEventsBatch": [{
36368
+ name: "deviceIds",
36369
+ form: "array",
36370
+ optional: false
36371
+ }],
36173
36372
  "pipelineAnalytics.getMotionEvents": [{
36174
36373
  name: "deviceId",
36175
36374
  form: "single",
@@ -36610,6 +36809,11 @@ Object.freeze({
36610
36809
  form: "single",
36611
36810
  optional: false
36612
36811
  }],
36812
+ "recording.reconcileLedgerAgainstDisk": [{
36813
+ name: "deviceId",
36814
+ form: "single",
36815
+ optional: true
36816
+ }],
36613
36817
  "recording.relocateFootage": [{
36614
36818
  name: "deviceId",
36615
36819
  form: "single",
@@ -36675,6 +36879,11 @@ Object.freeze({
36675
36879
  form: "single",
36676
36880
  optional: false
36677
36881
  }],
36882
+ "sceneMonitor.listScenesBatch": [{
36883
+ name: "deviceIds",
36884
+ form: "array",
36885
+ optional: false
36886
+ }],
36678
36887
  "sceneMonitor.recheckNow": [{
36679
36888
  name: "deviceId",
36680
36889
  form: "single",
@@ -36936,6 +37145,11 @@ Object.freeze({
36936
37145
  form: "single",
36937
37146
  optional: false
36938
37147
  }],
37148
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
37149
+ name: "deviceIds",
37150
+ form: "array",
37151
+ optional: false
37152
+ }],
36939
37153
  "zoneAnalytics.getUnzonedHistory": [{
36940
37154
  name: "deviceId",
36941
37155
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-export-hap",
3
- "version": "1.2.59",
3
+ "version": "1.2.60",
4
4
  "description": "HomeKit (HAP) exporter for CamStack devices. Publishes each exposed device as its own HomeKit accessory: cameras and doorbells with SRTP streaming, HomeKit Secure Video, motion, two-way audio, PTZ and battery; switches, lights, locks and sensors through a capability→service table.",
5
5
  "keywords": [
6
6
  "camstack",