@camstack/addon-smtp-nodemailer 1.2.47 → 1.2.48

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.
@@ -8039,6 +8039,20 @@ var RelocateJobSchema = object({
8039
8039
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8040
8040
  */
8041
8041
  rowsReconciled: number().int().nonnegative().optional(),
8042
+ /**
8043
+ * Rows this run FORGOT because the file they name is not on disk.
8044
+ *
8045
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8046
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8047
+ * and the durable row is dropped through the same channel eviction uses. It
8048
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8049
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8050
+ * the same failure as one that quietly skips them (D295).
8051
+ *
8052
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8053
+ * ledger claimed 5.65 GB of footage that no longer existed.
8054
+ */
8055
+ rowsForgotten: number().int().nonnegative().optional(),
8042
8056
  startedAt: number(),
8043
8057
  finishedAt: number().nullable(),
8044
8058
  error: string().nullable()
@@ -8402,6 +8416,91 @@ var RelocateResidueSchema = object({
8402
8416
  segments: number().int().nonnegative(),
8403
8417
  bytes: number().int().nonnegative()
8404
8418
  }).nullable();
8419
+ /**
8420
+ * Ask one location whether its durable hour rows describe the disk — the walk
8421
+ * (D319).
8422
+ *
8423
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8424
+ * missing tool is the question, and the dry run is how they sanity-check the
8425
+ * destructive run before authorising it.
8426
+ */
8427
+ var LedgerWalkInputSchema = object({
8428
+ locationId: string().min(1),
8429
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8430
+ apply: boolean().optional(),
8431
+ /** Narrow to one camera. */
8432
+ deviceId: number().int().positive().optional(),
8433
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8434
+ profiles: array(string().min(1)).optional()
8435
+ });
8436
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8437
+ var LedgerWalkRefusalSchema = _enum([
8438
+ "location-unknown",
8439
+ "source-writable",
8440
+ "no-ledger",
8441
+ "archive-unreadable",
8442
+ "anchor-absent",
8443
+ "anchor-unreadable",
8444
+ "anchor-moved"
8445
+ ]);
8446
+ _enum([
8447
+ "live-tail",
8448
+ "listing-error",
8449
+ "path-mismatch",
8450
+ "durable-refused"
8451
+ ]);
8452
+ /** Every skip reason, always present, always a number — so a reason that never
8453
+ * fired reports as zero rather than absent and the report shape is constant
8454
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8455
+ var LedgerWalkSkipCountsSchema = object({
8456
+ "live-tail": number().int().nonnegative(),
8457
+ "listing-error": number().int().nonnegative(),
8458
+ "path-mismatch": number().int().nonnegative(),
8459
+ "durable-refused": number().int().nonnegative()
8460
+ });
8461
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8462
+ var LedgerWalkDeviceReportSchema = object({
8463
+ deviceId: number().int(),
8464
+ hoursWalked: number().int().nonnegative(),
8465
+ hoursMissing: number().int().nonnegative(),
8466
+ ghostSegments: number().int().nonnegative(),
8467
+ ghostBytes: number().int().nonnegative(),
8468
+ forgottenSegments: number().int().nonnegative(),
8469
+ orphanFiles: number().int().nonnegative()
8470
+ });
8471
+ /**
8472
+ * What one walk claimed, listed, found and (only when armed) forgot.
8473
+ *
8474
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8475
+ * walk that saw a fraction of the location is visible in its own report rather
8476
+ * than in the absence of one.
8477
+ */
8478
+ var LedgerWalkReportSchema = object({
8479
+ locationId: string(),
8480
+ applied: boolean(),
8481
+ refused: LedgerWalkRefusalSchema.nullable(),
8482
+ archiveSegments: number().int().nonnegative().nullable(),
8483
+ archiveBytes: number().int().nonnegative().nullable(),
8484
+ hoursClaimed: number().int().nonnegative(),
8485
+ hoursWalked: number().int().nonnegative(),
8486
+ hoursMissing: number().int().nonnegative(),
8487
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8488
+ listings: number().int().nonnegative(),
8489
+ segmentsClaimed: number().int().nonnegative(),
8490
+ ghostSegments: number().int().nonnegative(),
8491
+ ghostBytes: number().int().nonnegative(),
8492
+ ghostHoursWhole: number().int().nonnegative(),
8493
+ forgottenSegments: number().int().nonnegative(),
8494
+ forgottenBytes: number().int().nonnegative(),
8495
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8496
+ orphanFiles: number().int().nonnegative(),
8497
+ orphanSample: array(string()).readonly(),
8498
+ hoursSkipped: number().int().nonnegative(),
8499
+ skippedByReason: LedgerWalkSkipCountsSchema,
8500
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8501
+ bounded: boolean(),
8502
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8503
+ });
8405
8504
  /** How many rows a media pass would still act on against a given target — the
8406
8505
  * media lane's denominator AND its residue, from ONE derivation so the two can
8407
8506
  * never disagree. `null` = the count could not be taken. */
@@ -18675,13 +18774,15 @@ var ListGroupsPageSchema = object({
18675
18774
  groups: array(AnalyticsGroupRecordSchema).readonly(),
18676
18775
  nextCursor: string().nullable()
18677
18776
  });
18777
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
18778
+ var KEY_EVENTS_MAX_LIMIT = 200;
18678
18779
  var KeyEventQueryInput = object({
18679
18780
  deviceId: number(),
18680
18781
  /** Window lower bound (track firstSeen ≥ since). */
18681
18782
  since: number(),
18682
18783
  /** Window upper bound (track firstSeen ≤ until). */
18683
18784
  until: number(),
18684
- limit: number().int().min(1).max(200).default(50),
18785
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18685
18786
  /** Drop tracks scoring below this importance. */
18686
18787
  minImportance: number().min(0).max(1).optional(),
18687
18788
  /** Restrict to a single class (e.g. 'person'). */
@@ -18703,6 +18804,32 @@ var KeyEventSchema = object({
18703
18804
  ...TrackFlagFields,
18704
18805
  ...TrackRetrainFields
18705
18806
  });
18807
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
18808
+ var KeyEventBatchQueryInput = object({
18809
+ deviceIds: array(number()).min(1).max(200),
18810
+ since: number(),
18811
+ until: number(),
18812
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
18813
+ * across the set, which would let a busy camera starve a quiet one of its
18814
+ * rows and change what the merged feed contains. */
18815
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18816
+ minImportance: number().min(0).max(1).optional(),
18817
+ classFilter: string().optional()
18818
+ });
18819
+ /**
18820
+ * One camera's key events in a batch answer.
18821
+ *
18822
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
18823
+ * error rather than throwing, so a camera whose store read failed and one with
18824
+ * no events in the window were ALREADY indistinguishable per camera — the
18825
+ * batch does not make that worse, and the row keeps the deviceId the single
18826
+ * method's output never carried (the caller used to stamp it from the fan-out
18827
+ * key, which only worked because there was one query per camera).
18828
+ */
18829
+ var KeyEventsForDeviceSchema = object({
18830
+ deviceId: number(),
18831
+ events: array(KeyEventSchema).readonly()
18832
+ });
18706
18833
  object({
18707
18834
  trackId: string(),
18708
18835
  className: string(),
@@ -18972,7 +19099,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18972
19099
  until: number().optional(),
18973
19100
  kinds: array(string()).optional(),
18974
19101
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
18975
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19102
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
18976
19103
  deviceId: number(),
18977
19104
  since: number(),
18978
19105
  until: number(),
@@ -26637,6 +26764,9 @@ method(object({
26637
26764
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
26638
26765
  kind: "query",
26639
26766
  auth: "admin"
26767
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
26768
+ kind: "mutation",
26769
+ auth: "admin"
26640
26770
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
26641
26771
  kind: "mutation",
26642
26772
  auth: "admin"
@@ -27028,7 +27158,26 @@ var SceneMonitorStatusSchema = object({
27028
27158
  monitors: array(SceneMonitorSchema),
27029
27159
  lastFetchedAt: number()
27030
27160
  });
27031
- DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
27161
+ /**
27162
+ * One camera's row in a `listScenesBatch` answer.
27163
+ *
27164
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
27165
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
27166
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
27167
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
27168
+ * configured. Fanned out per camera the difference was visible — one query
27169
+ * errored while the others resolved — and a batch that returned only the rows
27170
+ * it managed would have destroyed it, silently, by making an unreachable camera
27171
+ * indistinguishable from one that answered `monitors: []`.
27172
+ *
27173
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
27174
+ * could not be read"; `status.monitors: []` means "read, and it has none".
27175
+ */
27176
+ var SceneMonitorStatusForDeviceSchema = object({
27177
+ deviceId: number(),
27178
+ status: SceneMonitorStatusSchema.nullable()
27179
+ });
27180
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
27032
27181
  deviceId: number(),
27033
27182
  label: string(),
27034
27183
  roi: MaskRectShapeSchema,
@@ -28352,6 +28501,27 @@ var CameraOccupancySnapshotSchema = object({
28352
28501
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
28353
28502
  });
28354
28503
  /**
28504
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
28505
+ *
28506
+ * THREE outcomes, and the single-camera method could only express two of them
28507
+ * because `snapshot: null` was already spoken for:
28508
+ *
28509
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
28510
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
28511
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
28512
+ * - `read: 'unreadable'` — the owner could not answer for this
28513
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
28514
+ *
28515
+ * Collapsing the last two is the failure this field exists to prevent: a
28516
+ * hydration that threw would otherwise render as an empty Stationary section,
28517
+ * which is a definite claim about a camera nobody could read.
28518
+ */
28519
+ var CameraOccupancySnapshotForDeviceSchema = object({
28520
+ deviceId: number(),
28521
+ read: _enum(["read", "unreadable"]),
28522
+ snapshot: CameraOccupancySnapshotSchema.nullable()
28523
+ });
28524
+ /**
28355
28525
  * Time-series resolution. The history methods return one bucket per
28356
28526
  * step over the requested range. Smaller resolutions cost more
28357
28527
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -28375,7 +28545,7 @@ var HistoryPointSchema = object({
28375
28545
  /** Object count averaged over the bucket (rounded to nearest integer). */
28376
28546
  count: number().int().nonnegative()
28377
28547
  });
28378
- DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
28548
+ DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
28379
28549
  deviceId: number(),
28380
28550
  zoneId: string(),
28381
28551
  className: string().optional()
@@ -31680,6 +31850,12 @@ Object.freeze({
31680
31850
  addonId: null,
31681
31851
  access: "view"
31682
31852
  },
31853
+ "pipelineAnalytics.getKeyEventsBatch": {
31854
+ capName: "pipeline-analytics",
31855
+ capScope: "device",
31856
+ addonId: null,
31857
+ access: "view"
31858
+ },
31683
31859
  "pipelineAnalytics.getMotionEvents": {
31684
31860
  capName: "pipeline-analytics",
31685
31861
  capScope: "device",
@@ -32856,6 +33032,12 @@ Object.freeze({
32856
33032
  addonId: null,
32857
33033
  access: "view"
32858
33034
  },
33035
+ "recording.reconcileLedgerAgainstDisk": {
33036
+ capName: "recording",
33037
+ capScope: "system",
33038
+ addonId: null,
33039
+ access: "create"
33040
+ },
32859
33041
  "recording.refreshStorageLocationsForMigration": {
32860
33042
  capName: "recording",
32861
33043
  capScope: "system",
@@ -32982,6 +33164,12 @@ Object.freeze({
32982
33164
  addonId: null,
32983
33165
  access: "view"
32984
33166
  },
33167
+ "sceneMonitor.listScenesBatch": {
33168
+ capName: "scene-monitor",
33169
+ capScope: "device",
33170
+ addonId: null,
33171
+ access: "view"
33172
+ },
32985
33173
  "sceneMonitor.recheckNow": {
32986
33174
  capName: "scene-monitor",
32987
33175
  capScope: "device",
@@ -34338,6 +34526,12 @@ Object.freeze({
34338
34526
  addonId: null,
34339
34527
  access: "view"
34340
34528
  },
34529
+ "zoneAnalytics.getCurrentSnapshotBatch": {
34530
+ capName: "zone-analytics",
34531
+ capScope: "device",
34532
+ addonId: null,
34533
+ access: "view"
34534
+ },
34341
34535
  "zoneAnalytics.getUnzonedHistory": {
34342
34536
  capName: "zone-analytics",
34343
34537
  capScope: "device",
@@ -35342,6 +35536,11 @@ Object.freeze({
35342
35536
  form: "single",
35343
35537
  optional: false
35344
35538
  }],
35539
+ "pipelineAnalytics.getKeyEventsBatch": [{
35540
+ name: "deviceIds",
35541
+ form: "array",
35542
+ optional: false
35543
+ }],
35345
35544
  "pipelineAnalytics.getMotionEvents": [{
35346
35545
  name: "deviceId",
35347
35546
  form: "single",
@@ -35782,6 +35981,11 @@ Object.freeze({
35782
35981
  form: "single",
35783
35982
  optional: false
35784
35983
  }],
35984
+ "recording.reconcileLedgerAgainstDisk": [{
35985
+ name: "deviceId",
35986
+ form: "single",
35987
+ optional: true
35988
+ }],
35785
35989
  "recording.relocateFootage": [{
35786
35990
  name: "deviceId",
35787
35991
  form: "single",
@@ -35847,6 +36051,11 @@ Object.freeze({
35847
36051
  form: "single",
35848
36052
  optional: false
35849
36053
  }],
36054
+ "sceneMonitor.listScenesBatch": [{
36055
+ name: "deviceIds",
36056
+ form: "array",
36057
+ optional: false
36058
+ }],
35850
36059
  "sceneMonitor.recheckNow": [{
35851
36060
  name: "deviceId",
35852
36061
  form: "single",
@@ -36108,6 +36317,11 @@ Object.freeze({
36108
36317
  form: "single",
36109
36318
  optional: false
36110
36319
  }],
36320
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
36321
+ name: "deviceIds",
36322
+ form: "array",
36323
+ optional: false
36324
+ }],
36111
36325
  "zoneAnalytics.getUnzonedHistory": [{
36112
36326
  name: "deviceId",
36113
36327
  form: "single",
@@ -8037,6 +8037,20 @@ var RelocateJobSchema = object({
8037
8037
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8038
8038
  */
8039
8039
  rowsReconciled: number().int().nonnegative().optional(),
8040
+ /**
8041
+ * Rows this run FORGOT because the file they name is not on disk.
8042
+ *
8043
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8044
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8045
+ * and the durable row is dropped through the same channel eviction uses. It
8046
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8047
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8048
+ * the same failure as one that quietly skips them (D295).
8049
+ *
8050
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8051
+ * ledger claimed 5.65 GB of footage that no longer existed.
8052
+ */
8053
+ rowsForgotten: number().int().nonnegative().optional(),
8040
8054
  startedAt: number(),
8041
8055
  finishedAt: number().nullable(),
8042
8056
  error: string().nullable()
@@ -8400,6 +8414,91 @@ var RelocateResidueSchema = object({
8400
8414
  segments: number().int().nonnegative(),
8401
8415
  bytes: number().int().nonnegative()
8402
8416
  }).nullable();
8417
+ /**
8418
+ * Ask one location whether its durable hour rows describe the disk — the walk
8419
+ * (D319).
8420
+ *
8421
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8422
+ * missing tool is the question, and the dry run is how they sanity-check the
8423
+ * destructive run before authorising it.
8424
+ */
8425
+ var LedgerWalkInputSchema = object({
8426
+ locationId: string().min(1),
8427
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8428
+ apply: boolean().optional(),
8429
+ /** Narrow to one camera. */
8430
+ deviceId: number().int().positive().optional(),
8431
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8432
+ profiles: array(string().min(1)).optional()
8433
+ });
8434
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8435
+ var LedgerWalkRefusalSchema = _enum([
8436
+ "location-unknown",
8437
+ "source-writable",
8438
+ "no-ledger",
8439
+ "archive-unreadable",
8440
+ "anchor-absent",
8441
+ "anchor-unreadable",
8442
+ "anchor-moved"
8443
+ ]);
8444
+ _enum([
8445
+ "live-tail",
8446
+ "listing-error",
8447
+ "path-mismatch",
8448
+ "durable-refused"
8449
+ ]);
8450
+ /** Every skip reason, always present, always a number — so a reason that never
8451
+ * fired reports as zero rather than absent and the report shape is constant
8452
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8453
+ var LedgerWalkSkipCountsSchema = object({
8454
+ "live-tail": number().int().nonnegative(),
8455
+ "listing-error": number().int().nonnegative(),
8456
+ "path-mismatch": number().int().nonnegative(),
8457
+ "durable-refused": number().int().nonnegative()
8458
+ });
8459
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8460
+ var LedgerWalkDeviceReportSchema = object({
8461
+ deviceId: number().int(),
8462
+ hoursWalked: number().int().nonnegative(),
8463
+ hoursMissing: number().int().nonnegative(),
8464
+ ghostSegments: number().int().nonnegative(),
8465
+ ghostBytes: number().int().nonnegative(),
8466
+ forgottenSegments: number().int().nonnegative(),
8467
+ orphanFiles: number().int().nonnegative()
8468
+ });
8469
+ /**
8470
+ * What one walk claimed, listed, found and (only when armed) forgot.
8471
+ *
8472
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8473
+ * walk that saw a fraction of the location is visible in its own report rather
8474
+ * than in the absence of one.
8475
+ */
8476
+ var LedgerWalkReportSchema = object({
8477
+ locationId: string(),
8478
+ applied: boolean(),
8479
+ refused: LedgerWalkRefusalSchema.nullable(),
8480
+ archiveSegments: number().int().nonnegative().nullable(),
8481
+ archiveBytes: number().int().nonnegative().nullable(),
8482
+ hoursClaimed: number().int().nonnegative(),
8483
+ hoursWalked: number().int().nonnegative(),
8484
+ hoursMissing: number().int().nonnegative(),
8485
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8486
+ listings: number().int().nonnegative(),
8487
+ segmentsClaimed: number().int().nonnegative(),
8488
+ ghostSegments: number().int().nonnegative(),
8489
+ ghostBytes: number().int().nonnegative(),
8490
+ ghostHoursWhole: number().int().nonnegative(),
8491
+ forgottenSegments: number().int().nonnegative(),
8492
+ forgottenBytes: number().int().nonnegative(),
8493
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8494
+ orphanFiles: number().int().nonnegative(),
8495
+ orphanSample: array(string()).readonly(),
8496
+ hoursSkipped: number().int().nonnegative(),
8497
+ skippedByReason: LedgerWalkSkipCountsSchema,
8498
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8499
+ bounded: boolean(),
8500
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8501
+ });
8403
8502
  /** How many rows a media pass would still act on against a given target — the
8404
8503
  * media lane's denominator AND its residue, from ONE derivation so the two can
8405
8504
  * never disagree. `null` = the count could not be taken. */
@@ -18673,13 +18772,15 @@ var ListGroupsPageSchema = object({
18673
18772
  groups: array(AnalyticsGroupRecordSchema).readonly(),
18674
18773
  nextCursor: string().nullable()
18675
18774
  });
18775
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
18776
+ var KEY_EVENTS_MAX_LIMIT = 200;
18676
18777
  var KeyEventQueryInput = object({
18677
18778
  deviceId: number(),
18678
18779
  /** Window lower bound (track firstSeen ≥ since). */
18679
18780
  since: number(),
18680
18781
  /** Window upper bound (track firstSeen ≤ until). */
18681
18782
  until: number(),
18682
- limit: number().int().min(1).max(200).default(50),
18783
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18683
18784
  /** Drop tracks scoring below this importance. */
18684
18785
  minImportance: number().min(0).max(1).optional(),
18685
18786
  /** Restrict to a single class (e.g. 'person'). */
@@ -18701,6 +18802,32 @@ var KeyEventSchema = object({
18701
18802
  ...TrackFlagFields,
18702
18803
  ...TrackRetrainFields
18703
18804
  });
18805
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
18806
+ var KeyEventBatchQueryInput = object({
18807
+ deviceIds: array(number()).min(1).max(200),
18808
+ since: number(),
18809
+ until: number(),
18810
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
18811
+ * across the set, which would let a busy camera starve a quiet one of its
18812
+ * rows and change what the merged feed contains. */
18813
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18814
+ minImportance: number().min(0).max(1).optional(),
18815
+ classFilter: string().optional()
18816
+ });
18817
+ /**
18818
+ * One camera's key events in a batch answer.
18819
+ *
18820
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
18821
+ * error rather than throwing, so a camera whose store read failed and one with
18822
+ * no events in the window were ALREADY indistinguishable per camera — the
18823
+ * batch does not make that worse, and the row keeps the deviceId the single
18824
+ * method's output never carried (the caller used to stamp it from the fan-out
18825
+ * key, which only worked because there was one query per camera).
18826
+ */
18827
+ var KeyEventsForDeviceSchema = object({
18828
+ deviceId: number(),
18829
+ events: array(KeyEventSchema).readonly()
18830
+ });
18704
18831
  object({
18705
18832
  trackId: string(),
18706
18833
  className: string(),
@@ -18970,7 +19097,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
18970
19097
  until: number().optional(),
18971
19098
  kinds: array(string()).optional(),
18972
19099
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
18973
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19100
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
18974
19101
  deviceId: number(),
18975
19102
  since: number(),
18976
19103
  until: number(),
@@ -26635,6 +26762,9 @@ method(object({
26635
26762
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
26636
26763
  kind: "query",
26637
26764
  auth: "admin"
26765
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
26766
+ kind: "mutation",
26767
+ auth: "admin"
26638
26768
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
26639
26769
  kind: "mutation",
26640
26770
  auth: "admin"
@@ -27026,7 +27156,26 @@ var SceneMonitorStatusSchema = object({
27026
27156
  monitors: array(SceneMonitorSchema),
27027
27157
  lastFetchedAt: number()
27028
27158
  });
27029
- DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
27159
+ /**
27160
+ * One camera's row in a `listScenesBatch` answer.
27161
+ *
27162
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
27163
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
27164
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
27165
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
27166
+ * configured. Fanned out per camera the difference was visible — one query
27167
+ * errored while the others resolved — and a batch that returned only the rows
27168
+ * it managed would have destroyed it, silently, by making an unreachable camera
27169
+ * indistinguishable from one that answered `monitors: []`.
27170
+ *
27171
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
27172
+ * could not be read"; `status.monitors: []` means "read, and it has none".
27173
+ */
27174
+ var SceneMonitorStatusForDeviceSchema = object({
27175
+ deviceId: number(),
27176
+ status: SceneMonitorStatusSchema.nullable()
27177
+ });
27178
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
27030
27179
  deviceId: number(),
27031
27180
  label: string(),
27032
27181
  roi: MaskRectShapeSchema,
@@ -28350,6 +28499,27 @@ var CameraOccupancySnapshotSchema = object({
28350
28499
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
28351
28500
  });
28352
28501
  /**
28502
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
28503
+ *
28504
+ * THREE outcomes, and the single-camera method could only express two of them
28505
+ * because `snapshot: null` was already spoken for:
28506
+ *
28507
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
28508
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
28509
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
28510
+ * - `read: 'unreadable'` — the owner could not answer for this
28511
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
28512
+ *
28513
+ * Collapsing the last two is the failure this field exists to prevent: a
28514
+ * hydration that threw would otherwise render as an empty Stationary section,
28515
+ * which is a definite claim about a camera nobody could read.
28516
+ */
28517
+ var CameraOccupancySnapshotForDeviceSchema = object({
28518
+ deviceId: number(),
28519
+ read: _enum(["read", "unreadable"]),
28520
+ snapshot: CameraOccupancySnapshotSchema.nullable()
28521
+ });
28522
+ /**
28353
28523
  * Time-series resolution. The history methods return one bucket per
28354
28524
  * step over the requested range. Smaller resolutions cost more
28355
28525
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -28373,7 +28543,7 @@ var HistoryPointSchema = object({
28373
28543
  /** Object count averaged over the bucket (rounded to nearest integer). */
28374
28544
  count: number().int().nonnegative()
28375
28545
  });
28376
- DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
28546
+ DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
28377
28547
  deviceId: number(),
28378
28548
  zoneId: string(),
28379
28549
  className: string().optional()
@@ -31678,6 +31848,12 @@ Object.freeze({
31678
31848
  addonId: null,
31679
31849
  access: "view"
31680
31850
  },
31851
+ "pipelineAnalytics.getKeyEventsBatch": {
31852
+ capName: "pipeline-analytics",
31853
+ capScope: "device",
31854
+ addonId: null,
31855
+ access: "view"
31856
+ },
31681
31857
  "pipelineAnalytics.getMotionEvents": {
31682
31858
  capName: "pipeline-analytics",
31683
31859
  capScope: "device",
@@ -32854,6 +33030,12 @@ Object.freeze({
32854
33030
  addonId: null,
32855
33031
  access: "view"
32856
33032
  },
33033
+ "recording.reconcileLedgerAgainstDisk": {
33034
+ capName: "recording",
33035
+ capScope: "system",
33036
+ addonId: null,
33037
+ access: "create"
33038
+ },
32857
33039
  "recording.refreshStorageLocationsForMigration": {
32858
33040
  capName: "recording",
32859
33041
  capScope: "system",
@@ -32980,6 +33162,12 @@ Object.freeze({
32980
33162
  addonId: null,
32981
33163
  access: "view"
32982
33164
  },
33165
+ "sceneMonitor.listScenesBatch": {
33166
+ capName: "scene-monitor",
33167
+ capScope: "device",
33168
+ addonId: null,
33169
+ access: "view"
33170
+ },
32983
33171
  "sceneMonitor.recheckNow": {
32984
33172
  capName: "scene-monitor",
32985
33173
  capScope: "device",
@@ -34336,6 +34524,12 @@ Object.freeze({
34336
34524
  addonId: null,
34337
34525
  access: "view"
34338
34526
  },
34527
+ "zoneAnalytics.getCurrentSnapshotBatch": {
34528
+ capName: "zone-analytics",
34529
+ capScope: "device",
34530
+ addonId: null,
34531
+ access: "view"
34532
+ },
34339
34533
  "zoneAnalytics.getUnzonedHistory": {
34340
34534
  capName: "zone-analytics",
34341
34535
  capScope: "device",
@@ -35340,6 +35534,11 @@ Object.freeze({
35340
35534
  form: "single",
35341
35535
  optional: false
35342
35536
  }],
35537
+ "pipelineAnalytics.getKeyEventsBatch": [{
35538
+ name: "deviceIds",
35539
+ form: "array",
35540
+ optional: false
35541
+ }],
35343
35542
  "pipelineAnalytics.getMotionEvents": [{
35344
35543
  name: "deviceId",
35345
35544
  form: "single",
@@ -35780,6 +35979,11 @@ Object.freeze({
35780
35979
  form: "single",
35781
35980
  optional: false
35782
35981
  }],
35982
+ "recording.reconcileLedgerAgainstDisk": [{
35983
+ name: "deviceId",
35984
+ form: "single",
35985
+ optional: true
35986
+ }],
35783
35987
  "recording.relocateFootage": [{
35784
35988
  name: "deviceId",
35785
35989
  form: "single",
@@ -35845,6 +36049,11 @@ Object.freeze({
35845
36049
  form: "single",
35846
36050
  optional: false
35847
36051
  }],
36052
+ "sceneMonitor.listScenesBatch": [{
36053
+ name: "deviceIds",
36054
+ form: "array",
36055
+ optional: false
36056
+ }],
35848
36057
  "sceneMonitor.recheckNow": [{
35849
36058
  name: "deviceId",
35850
36059
  form: "single",
@@ -36106,6 +36315,11 @@ Object.freeze({
36106
36315
  form: "single",
36107
36316
  optional: false
36108
36317
  }],
36318
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
36319
+ name: "deviceIds",
36320
+ form: "array",
36321
+ optional: false
36322
+ }],
36109
36323
  "zoneAnalytics.getUnzonedHistory": [{
36110
36324
  name: "deviceId",
36111
36325
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-smtp-nodemailer",
3
- "version": "1.2.47",
3
+ "version": "1.2.48",
4
4
  "description": "SMTP email provider addon for CamStack — wraps `nodemailer` and registers a `smtp-provider` cap collection entry. Used by magic-link login + notifier addons.",
5
5
  "keywords": [
6
6
  "camstack",