@camstack/addon-import-alexa 0.2.47 → 0.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.
package/dist/addon.js CHANGED
@@ -8176,6 +8176,20 @@ var RelocateJobSchema = object({
8176
8176
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8177
8177
  */
8178
8178
  rowsReconciled: number().int().nonnegative().optional(),
8179
+ /**
8180
+ * Rows this run FORGOT because the file they name is not on disk.
8181
+ *
8182
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8183
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8184
+ * and the durable row is dropped through the same channel eviction uses. It
8185
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8186
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8187
+ * the same failure as one that quietly skips them (D295).
8188
+ *
8189
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8190
+ * ledger claimed 5.65 GB of footage that no longer existed.
8191
+ */
8192
+ rowsForgotten: number().int().nonnegative().optional(),
8179
8193
  startedAt: number(),
8180
8194
  finishedAt: number().nullable(),
8181
8195
  error: string().nullable()
@@ -8539,6 +8553,91 @@ var RelocateResidueSchema = object({
8539
8553
  segments: number().int().nonnegative(),
8540
8554
  bytes: number().int().nonnegative()
8541
8555
  }).nullable();
8556
+ /**
8557
+ * Ask one location whether its durable hour rows describe the disk — the walk
8558
+ * (D319).
8559
+ *
8560
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8561
+ * missing tool is the question, and the dry run is how they sanity-check the
8562
+ * destructive run before authorising it.
8563
+ */
8564
+ var LedgerWalkInputSchema = object({
8565
+ locationId: string().min(1),
8566
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8567
+ apply: boolean().optional(),
8568
+ /** Narrow to one camera. */
8569
+ deviceId: number().int().positive().optional(),
8570
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8571
+ profiles: array(string().min(1)).optional()
8572
+ });
8573
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8574
+ var LedgerWalkRefusalSchema = _enum([
8575
+ "location-unknown",
8576
+ "source-writable",
8577
+ "no-ledger",
8578
+ "archive-unreadable",
8579
+ "anchor-absent",
8580
+ "anchor-unreadable",
8581
+ "anchor-moved"
8582
+ ]);
8583
+ _enum([
8584
+ "live-tail",
8585
+ "listing-error",
8586
+ "path-mismatch",
8587
+ "durable-refused"
8588
+ ]);
8589
+ /** Every skip reason, always present, always a number — so a reason that never
8590
+ * fired reports as zero rather than absent and the report shape is constant
8591
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8592
+ var LedgerWalkSkipCountsSchema = object({
8593
+ "live-tail": number().int().nonnegative(),
8594
+ "listing-error": number().int().nonnegative(),
8595
+ "path-mismatch": number().int().nonnegative(),
8596
+ "durable-refused": number().int().nonnegative()
8597
+ });
8598
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8599
+ var LedgerWalkDeviceReportSchema = object({
8600
+ deviceId: number().int(),
8601
+ hoursWalked: number().int().nonnegative(),
8602
+ hoursMissing: number().int().nonnegative(),
8603
+ ghostSegments: number().int().nonnegative(),
8604
+ ghostBytes: number().int().nonnegative(),
8605
+ forgottenSegments: number().int().nonnegative(),
8606
+ orphanFiles: number().int().nonnegative()
8607
+ });
8608
+ /**
8609
+ * What one walk claimed, listed, found and (only when armed) forgot.
8610
+ *
8611
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8612
+ * walk that saw a fraction of the location is visible in its own report rather
8613
+ * than in the absence of one.
8614
+ */
8615
+ var LedgerWalkReportSchema = object({
8616
+ locationId: string(),
8617
+ applied: boolean(),
8618
+ refused: LedgerWalkRefusalSchema.nullable(),
8619
+ archiveSegments: number().int().nonnegative().nullable(),
8620
+ archiveBytes: number().int().nonnegative().nullable(),
8621
+ hoursClaimed: number().int().nonnegative(),
8622
+ hoursWalked: number().int().nonnegative(),
8623
+ hoursMissing: number().int().nonnegative(),
8624
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8625
+ listings: number().int().nonnegative(),
8626
+ segmentsClaimed: number().int().nonnegative(),
8627
+ ghostSegments: number().int().nonnegative(),
8628
+ ghostBytes: number().int().nonnegative(),
8629
+ ghostHoursWhole: number().int().nonnegative(),
8630
+ forgottenSegments: number().int().nonnegative(),
8631
+ forgottenBytes: number().int().nonnegative(),
8632
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8633
+ orphanFiles: number().int().nonnegative(),
8634
+ orphanSample: array(string()).readonly(),
8635
+ hoursSkipped: number().int().nonnegative(),
8636
+ skippedByReason: LedgerWalkSkipCountsSchema,
8637
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8638
+ bounded: boolean(),
8639
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8640
+ });
8542
8641
  /** How many rows a media pass would still act on against a given target — the
8543
8642
  * media lane's denominator AND its residue, from ONE derivation so the two can
8544
8643
  * never disagree. `null` = the count could not be taken. */
@@ -19239,13 +19338,15 @@ var ListGroupsPageSchema = object({
19239
19338
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19240
19339
  nextCursor: string().nullable()
19241
19340
  });
19341
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19342
+ var KEY_EVENTS_MAX_LIMIT = 200;
19242
19343
  var KeyEventQueryInput = object({
19243
19344
  deviceId: number(),
19244
19345
  /** Window lower bound (track firstSeen ≥ since). */
19245
19346
  since: number(),
19246
19347
  /** Window upper bound (track firstSeen ≤ until). */
19247
19348
  until: number(),
19248
- limit: number().int().min(1).max(200).default(50),
19349
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19249
19350
  /** Drop tracks scoring below this importance. */
19250
19351
  minImportance: number().min(0).max(1).optional(),
19251
19352
  /** Restrict to a single class (e.g. 'person'). */
@@ -19267,6 +19368,32 @@ var KeyEventSchema = object({
19267
19368
  ...TrackFlagFields,
19268
19369
  ...TrackRetrainFields
19269
19370
  });
19371
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19372
+ var KeyEventBatchQueryInput = object({
19373
+ deviceIds: array(number()).min(1).max(200),
19374
+ since: number(),
19375
+ until: number(),
19376
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19377
+ * across the set, which would let a busy camera starve a quiet one of its
19378
+ * rows and change what the merged feed contains. */
19379
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19380
+ minImportance: number().min(0).max(1).optional(),
19381
+ classFilter: string().optional()
19382
+ });
19383
+ /**
19384
+ * One camera's key events in a batch answer.
19385
+ *
19386
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19387
+ * error rather than throwing, so a camera whose store read failed and one with
19388
+ * no events in the window were ALREADY indistinguishable per camera — the
19389
+ * batch does not make that worse, and the row keeps the deviceId the single
19390
+ * method's output never carried (the caller used to stamp it from the fan-out
19391
+ * key, which only worked because there was one query per camera).
19392
+ */
19393
+ var KeyEventsForDeviceSchema = object({
19394
+ deviceId: number(),
19395
+ events: array(KeyEventSchema).readonly()
19396
+ });
19270
19397
  object({
19271
19398
  trackId: string(),
19272
19399
  className: string(),
@@ -19536,7 +19663,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19536
19663
  until: number().optional(),
19537
19664
  kinds: array(string()).optional(),
19538
19665
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19539
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19666
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19540
19667
  deviceId: number(),
19541
19668
  since: number(),
19542
19669
  until: number(),
@@ -28920,6 +29047,9 @@ method(object({
28920
29047
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28921
29048
  kind: "query",
28922
29049
  auth: "admin"
29050
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29051
+ kind: "mutation",
29052
+ auth: "admin"
28923
29053
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28924
29054
  kind: "mutation",
28925
29055
  auth: "admin"
@@ -29311,6 +29441,25 @@ var SceneMonitorStatusSchema = object({
29311
29441
  monitors: array(SceneMonitorSchema),
29312
29442
  lastFetchedAt: number()
29313
29443
  });
29444
+ /**
29445
+ * One camera's row in a `listScenesBatch` answer.
29446
+ *
29447
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29448
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29449
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29450
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29451
+ * configured. Fanned out per camera the difference was visible — one query
29452
+ * errored while the others resolved — and a batch that returned only the rows
29453
+ * it managed would have destroyed it, silently, by making an unreachable camera
29454
+ * indistinguishable from one that answered `monitors: []`.
29455
+ *
29456
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29457
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29458
+ */
29459
+ var SceneMonitorStatusForDeviceSchema = object({
29460
+ deviceId: number(),
29461
+ status: SceneMonitorStatusSchema.nullable()
29462
+ });
29314
29463
  var sceneMonitorCapability = {
29315
29464
  name: "scene-monitor",
29316
29465
  scope: "device",
@@ -29320,6 +29469,22 @@ var sceneMonitorCapability = {
29320
29469
  deviceTypes: [DeviceType.Camera],
29321
29470
  methods: {
29322
29471
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29472
+ /**
29473
+ * The same answer, for a SET of cameras, in one round trip.
29474
+ *
29475
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29476
+ * poll behind the push slice. Fanned out client-side that was one query
29477
+ * per camera — 29 round trips through the browser, the hub and the
29478
+ * post-analysis runner every 30 seconds to read an in-memory map the
29479
+ * owner had already merged. The work is unchanged (`statusFor` per
29480
+ * device, all in-process at the owner); what collapses is the transport.
29481
+ *
29482
+ * A camera that cannot answer still gets a row, with `status: null` —
29483
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29484
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29485
+ * tell which two are missing, or that any are.
29486
+ */
29487
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29323
29488
  createScene: method(object({
29324
29489
  deviceId: number(),
29325
29490
  label: string(),
@@ -31155,6 +31320,27 @@ var CameraOccupancySnapshotSchema = object({
31155
31320
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31156
31321
  });
31157
31322
  /**
31323
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31324
+ *
31325
+ * THREE outcomes, and the single-camera method could only express two of them
31326
+ * because `snapshot: null` was already spoken for:
31327
+ *
31328
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31329
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31330
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31331
+ * - `read: 'unreadable'` — the owner could not answer for this
31332
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31333
+ *
31334
+ * Collapsing the last two is the failure this field exists to prevent: a
31335
+ * hydration that threw would otherwise render as an empty Stationary section,
31336
+ * which is a definite claim about a camera nobody could read.
31337
+ */
31338
+ var CameraOccupancySnapshotForDeviceSchema = object({
31339
+ deviceId: number(),
31340
+ read: _enum(["read", "unreadable"]),
31341
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31342
+ });
31343
+ /**
31158
31344
  * Time-series resolution. The history methods return one bucket per
31159
31345
  * step over the requested range. Smaller resolutions cost more
31160
31346
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31211,6 +31397,20 @@ var zoneAnalyticsCapability = {
31211
31397
  * (no inference result emitted since boot or since binding was
31212
31398
  * activated). */
31213
31399
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31400
+ /**
31401
+ * The same snapshot, for a SET of cameras, in one round trip.
31402
+ *
31403
+ * The Events page's Stationary section polls this every 15s for every
31404
+ * selected camera. Fanned out client-side that is one query per camera to
31405
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31406
+ * costs everything. Batched, N transports become one and the per-device
31407
+ * work is unchanged.
31408
+ *
31409
+ * Every requested deviceId gets a row, tagged `read` — see
31410
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31411
+ * not answer for is `'unreadable'`, never an empty reading.
31412
+ */
31413
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31214
31414
  /** Time-series object count inside one zone. `className` optional —
31215
31415
  * omit to count every class in the zone. */
31216
31416
  getZoneHistory: method(object({
@@ -35632,6 +35832,12 @@ Object.freeze({
35632
35832
  addonId: null,
35633
35833
  access: "view"
35634
35834
  },
35835
+ "pipelineAnalytics.getKeyEventsBatch": {
35836
+ capName: "pipeline-analytics",
35837
+ capScope: "device",
35838
+ addonId: null,
35839
+ access: "view"
35840
+ },
35635
35841
  "pipelineAnalytics.getMotionEvents": {
35636
35842
  capName: "pipeline-analytics",
35637
35843
  capScope: "device",
@@ -36808,6 +37014,12 @@ Object.freeze({
36808
37014
  addonId: null,
36809
37015
  access: "view"
36810
37016
  },
37017
+ "recording.reconcileLedgerAgainstDisk": {
37018
+ capName: "recording",
37019
+ capScope: "system",
37020
+ addonId: null,
37021
+ access: "create"
37022
+ },
36811
37023
  "recording.refreshStorageLocationsForMigration": {
36812
37024
  capName: "recording",
36813
37025
  capScope: "system",
@@ -36934,6 +37146,12 @@ Object.freeze({
36934
37146
  addonId: null,
36935
37147
  access: "view"
36936
37148
  },
37149
+ "sceneMonitor.listScenesBatch": {
37150
+ capName: "scene-monitor",
37151
+ capScope: "device",
37152
+ addonId: null,
37153
+ access: "view"
37154
+ },
36937
37155
  "sceneMonitor.recheckNow": {
36938
37156
  capName: "scene-monitor",
36939
37157
  capScope: "device",
@@ -38290,6 +38508,12 @@ Object.freeze({
38290
38508
  addonId: null,
38291
38509
  access: "view"
38292
38510
  },
38511
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38512
+ capName: "zone-analytics",
38513
+ capScope: "device",
38514
+ addonId: null,
38515
+ access: "view"
38516
+ },
38293
38517
  "zoneAnalytics.getUnzonedHistory": {
38294
38518
  capName: "zone-analytics",
38295
38519
  capScope: "device",
@@ -39294,6 +39518,11 @@ Object.freeze({
39294
39518
  form: "single",
39295
39519
  optional: false
39296
39520
  }],
39521
+ "pipelineAnalytics.getKeyEventsBatch": [{
39522
+ name: "deviceIds",
39523
+ form: "array",
39524
+ optional: false
39525
+ }],
39297
39526
  "pipelineAnalytics.getMotionEvents": [{
39298
39527
  name: "deviceId",
39299
39528
  form: "single",
@@ -39734,6 +39963,11 @@ Object.freeze({
39734
39963
  form: "single",
39735
39964
  optional: false
39736
39965
  }],
39966
+ "recording.reconcileLedgerAgainstDisk": [{
39967
+ name: "deviceId",
39968
+ form: "single",
39969
+ optional: true
39970
+ }],
39737
39971
  "recording.relocateFootage": [{
39738
39972
  name: "deviceId",
39739
39973
  form: "single",
@@ -39799,6 +40033,11 @@ Object.freeze({
39799
40033
  form: "single",
39800
40034
  optional: false
39801
40035
  }],
40036
+ "sceneMonitor.listScenesBatch": [{
40037
+ name: "deviceIds",
40038
+ form: "array",
40039
+ optional: false
40040
+ }],
39802
40041
  "sceneMonitor.recheckNow": [{
39803
40042
  name: "deviceId",
39804
40043
  form: "single",
@@ -40060,6 +40299,11 @@ Object.freeze({
40060
40299
  form: "single",
40061
40300
  optional: false
40062
40301
  }],
40302
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40303
+ name: "deviceIds",
40304
+ form: "array",
40305
+ optional: false
40306
+ }],
40063
40307
  "zoneAnalytics.getUnzonedHistory": [{
40064
40308
  name: "deviceId",
40065
40309
  form: "single",
package/dist/addon.mjs CHANGED
@@ -8176,6 +8176,20 @@ var RelocateJobSchema = object({
8176
8176
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8177
8177
  */
8178
8178
  rowsReconciled: number().int().nonnegative().optional(),
8179
+ /**
8180
+ * Rows this run FORGOT because the file they name is not on disk.
8181
+ *
8182
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8183
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8184
+ * and the durable row is dropped through the same channel eviction uses. It
8185
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8186
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8187
+ * the same failure as one that quietly skips them (D295).
8188
+ *
8189
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8190
+ * ledger claimed 5.65 GB of footage that no longer existed.
8191
+ */
8192
+ rowsForgotten: number().int().nonnegative().optional(),
8179
8193
  startedAt: number(),
8180
8194
  finishedAt: number().nullable(),
8181
8195
  error: string().nullable()
@@ -8539,6 +8553,91 @@ var RelocateResidueSchema = object({
8539
8553
  segments: number().int().nonnegative(),
8540
8554
  bytes: number().int().nonnegative()
8541
8555
  }).nullable();
8556
+ /**
8557
+ * Ask one location whether its durable hour rows describe the disk — the walk
8558
+ * (D319).
8559
+ *
8560
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8561
+ * missing tool is the question, and the dry run is how they sanity-check the
8562
+ * destructive run before authorising it.
8563
+ */
8564
+ var LedgerWalkInputSchema = object({
8565
+ locationId: string().min(1),
8566
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8567
+ apply: boolean().optional(),
8568
+ /** Narrow to one camera. */
8569
+ deviceId: number().int().positive().optional(),
8570
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8571
+ profiles: array(string().min(1)).optional()
8572
+ });
8573
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8574
+ var LedgerWalkRefusalSchema = _enum([
8575
+ "location-unknown",
8576
+ "source-writable",
8577
+ "no-ledger",
8578
+ "archive-unreadable",
8579
+ "anchor-absent",
8580
+ "anchor-unreadable",
8581
+ "anchor-moved"
8582
+ ]);
8583
+ _enum([
8584
+ "live-tail",
8585
+ "listing-error",
8586
+ "path-mismatch",
8587
+ "durable-refused"
8588
+ ]);
8589
+ /** Every skip reason, always present, always a number — so a reason that never
8590
+ * fired reports as zero rather than absent and the report shape is constant
8591
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8592
+ var LedgerWalkSkipCountsSchema = object({
8593
+ "live-tail": number().int().nonnegative(),
8594
+ "listing-error": number().int().nonnegative(),
8595
+ "path-mismatch": number().int().nonnegative(),
8596
+ "durable-refused": number().int().nonnegative()
8597
+ });
8598
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8599
+ var LedgerWalkDeviceReportSchema = object({
8600
+ deviceId: number().int(),
8601
+ hoursWalked: number().int().nonnegative(),
8602
+ hoursMissing: number().int().nonnegative(),
8603
+ ghostSegments: number().int().nonnegative(),
8604
+ ghostBytes: number().int().nonnegative(),
8605
+ forgottenSegments: number().int().nonnegative(),
8606
+ orphanFiles: number().int().nonnegative()
8607
+ });
8608
+ /**
8609
+ * What one walk claimed, listed, found and (only when armed) forgot.
8610
+ *
8611
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8612
+ * walk that saw a fraction of the location is visible in its own report rather
8613
+ * than in the absence of one.
8614
+ */
8615
+ var LedgerWalkReportSchema = object({
8616
+ locationId: string(),
8617
+ applied: boolean(),
8618
+ refused: LedgerWalkRefusalSchema.nullable(),
8619
+ archiveSegments: number().int().nonnegative().nullable(),
8620
+ archiveBytes: number().int().nonnegative().nullable(),
8621
+ hoursClaimed: number().int().nonnegative(),
8622
+ hoursWalked: number().int().nonnegative(),
8623
+ hoursMissing: number().int().nonnegative(),
8624
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8625
+ listings: number().int().nonnegative(),
8626
+ segmentsClaimed: number().int().nonnegative(),
8627
+ ghostSegments: number().int().nonnegative(),
8628
+ ghostBytes: number().int().nonnegative(),
8629
+ ghostHoursWhole: number().int().nonnegative(),
8630
+ forgottenSegments: number().int().nonnegative(),
8631
+ forgottenBytes: number().int().nonnegative(),
8632
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8633
+ orphanFiles: number().int().nonnegative(),
8634
+ orphanSample: array(string()).readonly(),
8635
+ hoursSkipped: number().int().nonnegative(),
8636
+ skippedByReason: LedgerWalkSkipCountsSchema,
8637
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8638
+ bounded: boolean(),
8639
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8640
+ });
8542
8641
  /** How many rows a media pass would still act on against a given target — the
8543
8642
  * media lane's denominator AND its residue, from ONE derivation so the two can
8544
8643
  * never disagree. `null` = the count could not be taken. */
@@ -19239,13 +19338,15 @@ var ListGroupsPageSchema = object({
19239
19338
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19240
19339
  nextCursor: string().nullable()
19241
19340
  });
19341
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19342
+ var KEY_EVENTS_MAX_LIMIT = 200;
19242
19343
  var KeyEventQueryInput = object({
19243
19344
  deviceId: number(),
19244
19345
  /** Window lower bound (track firstSeen ≥ since). */
19245
19346
  since: number(),
19246
19347
  /** Window upper bound (track firstSeen ≤ until). */
19247
19348
  until: number(),
19248
- limit: number().int().min(1).max(200).default(50),
19349
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19249
19350
  /** Drop tracks scoring below this importance. */
19250
19351
  minImportance: number().min(0).max(1).optional(),
19251
19352
  /** Restrict to a single class (e.g. 'person'). */
@@ -19267,6 +19368,32 @@ var KeyEventSchema = object({
19267
19368
  ...TrackFlagFields,
19268
19369
  ...TrackRetrainFields
19269
19370
  });
19371
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19372
+ var KeyEventBatchQueryInput = object({
19373
+ deviceIds: array(number()).min(1).max(200),
19374
+ since: number(),
19375
+ until: number(),
19376
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19377
+ * across the set, which would let a busy camera starve a quiet one of its
19378
+ * rows and change what the merged feed contains. */
19379
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19380
+ minImportance: number().min(0).max(1).optional(),
19381
+ classFilter: string().optional()
19382
+ });
19383
+ /**
19384
+ * One camera's key events in a batch answer.
19385
+ *
19386
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19387
+ * error rather than throwing, so a camera whose store read failed and one with
19388
+ * no events in the window were ALREADY indistinguishable per camera — the
19389
+ * batch does not make that worse, and the row keeps the deviceId the single
19390
+ * method's output never carried (the caller used to stamp it from the fan-out
19391
+ * key, which only worked because there was one query per camera).
19392
+ */
19393
+ var KeyEventsForDeviceSchema = object({
19394
+ deviceId: number(),
19395
+ events: array(KeyEventSchema).readonly()
19396
+ });
19270
19397
  object({
19271
19398
  trackId: string(),
19272
19399
  className: string(),
@@ -19536,7 +19663,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19536
19663
  until: number().optional(),
19537
19664
  kinds: array(string()).optional(),
19538
19665
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19539
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19666
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19540
19667
  deviceId: number(),
19541
19668
  since: number(),
19542
19669
  until: number(),
@@ -28920,6 +29047,9 @@ method(object({
28920
29047
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28921
29048
  kind: "query",
28922
29049
  auth: "admin"
29050
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29051
+ kind: "mutation",
29052
+ auth: "admin"
28923
29053
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28924
29054
  kind: "mutation",
28925
29055
  auth: "admin"
@@ -29311,6 +29441,25 @@ var SceneMonitorStatusSchema = object({
29311
29441
  monitors: array(SceneMonitorSchema),
29312
29442
  lastFetchedAt: number()
29313
29443
  });
29444
+ /**
29445
+ * One camera's row in a `listScenesBatch` answer.
29446
+ *
29447
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29448
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29449
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29450
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29451
+ * configured. Fanned out per camera the difference was visible — one query
29452
+ * errored while the others resolved — and a batch that returned only the rows
29453
+ * it managed would have destroyed it, silently, by making an unreachable camera
29454
+ * indistinguishable from one that answered `monitors: []`.
29455
+ *
29456
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29457
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29458
+ */
29459
+ var SceneMonitorStatusForDeviceSchema = object({
29460
+ deviceId: number(),
29461
+ status: SceneMonitorStatusSchema.nullable()
29462
+ });
29314
29463
  var sceneMonitorCapability = {
29315
29464
  name: "scene-monitor",
29316
29465
  scope: "device",
@@ -29320,6 +29469,22 @@ var sceneMonitorCapability = {
29320
29469
  deviceTypes: [DeviceType.Camera],
29321
29470
  methods: {
29322
29471
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29472
+ /**
29473
+ * The same answer, for a SET of cameras, in one round trip.
29474
+ *
29475
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29476
+ * poll behind the push slice. Fanned out client-side that was one query
29477
+ * per camera — 29 round trips through the browser, the hub and the
29478
+ * post-analysis runner every 30 seconds to read an in-memory map the
29479
+ * owner had already merged. The work is unchanged (`statusFor` per
29480
+ * device, all in-process at the owner); what collapses is the transport.
29481
+ *
29482
+ * A camera that cannot answer still gets a row, with `status: null` —
29483
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29484
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29485
+ * tell which two are missing, or that any are.
29486
+ */
29487
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29323
29488
  createScene: method(object({
29324
29489
  deviceId: number(),
29325
29490
  label: string(),
@@ -31155,6 +31320,27 @@ var CameraOccupancySnapshotSchema = object({
31155
31320
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31156
31321
  });
31157
31322
  /**
31323
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31324
+ *
31325
+ * THREE outcomes, and the single-camera method could only express two of them
31326
+ * because `snapshot: null` was already spoken for:
31327
+ *
31328
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31329
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31330
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31331
+ * - `read: 'unreadable'` — the owner could not answer for this
31332
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31333
+ *
31334
+ * Collapsing the last two is the failure this field exists to prevent: a
31335
+ * hydration that threw would otherwise render as an empty Stationary section,
31336
+ * which is a definite claim about a camera nobody could read.
31337
+ */
31338
+ var CameraOccupancySnapshotForDeviceSchema = object({
31339
+ deviceId: number(),
31340
+ read: _enum(["read", "unreadable"]),
31341
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31342
+ });
31343
+ /**
31158
31344
  * Time-series resolution. The history methods return one bucket per
31159
31345
  * step over the requested range. Smaller resolutions cost more
31160
31346
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31211,6 +31397,20 @@ var zoneAnalyticsCapability = {
31211
31397
  * (no inference result emitted since boot or since binding was
31212
31398
  * activated). */
31213
31399
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31400
+ /**
31401
+ * The same snapshot, for a SET of cameras, in one round trip.
31402
+ *
31403
+ * The Events page's Stationary section polls this every 15s for every
31404
+ * selected camera. Fanned out client-side that is one query per camera to
31405
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31406
+ * costs everything. Batched, N transports become one and the per-device
31407
+ * work is unchanged.
31408
+ *
31409
+ * Every requested deviceId gets a row, tagged `read` — see
31410
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31411
+ * not answer for is `'unreadable'`, never an empty reading.
31412
+ */
31413
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31214
31414
  /** Time-series object count inside one zone. `className` optional —
31215
31415
  * omit to count every class in the zone. */
31216
31416
  getZoneHistory: method(object({
@@ -35632,6 +35832,12 @@ Object.freeze({
35632
35832
  addonId: null,
35633
35833
  access: "view"
35634
35834
  },
35835
+ "pipelineAnalytics.getKeyEventsBatch": {
35836
+ capName: "pipeline-analytics",
35837
+ capScope: "device",
35838
+ addonId: null,
35839
+ access: "view"
35840
+ },
35635
35841
  "pipelineAnalytics.getMotionEvents": {
35636
35842
  capName: "pipeline-analytics",
35637
35843
  capScope: "device",
@@ -36808,6 +37014,12 @@ Object.freeze({
36808
37014
  addonId: null,
36809
37015
  access: "view"
36810
37016
  },
37017
+ "recording.reconcileLedgerAgainstDisk": {
37018
+ capName: "recording",
37019
+ capScope: "system",
37020
+ addonId: null,
37021
+ access: "create"
37022
+ },
36811
37023
  "recording.refreshStorageLocationsForMigration": {
36812
37024
  capName: "recording",
36813
37025
  capScope: "system",
@@ -36934,6 +37146,12 @@ Object.freeze({
36934
37146
  addonId: null,
36935
37147
  access: "view"
36936
37148
  },
37149
+ "sceneMonitor.listScenesBatch": {
37150
+ capName: "scene-monitor",
37151
+ capScope: "device",
37152
+ addonId: null,
37153
+ access: "view"
37154
+ },
36937
37155
  "sceneMonitor.recheckNow": {
36938
37156
  capName: "scene-monitor",
36939
37157
  capScope: "device",
@@ -38290,6 +38508,12 @@ Object.freeze({
38290
38508
  addonId: null,
38291
38509
  access: "view"
38292
38510
  },
38511
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38512
+ capName: "zone-analytics",
38513
+ capScope: "device",
38514
+ addonId: null,
38515
+ access: "view"
38516
+ },
38293
38517
  "zoneAnalytics.getUnzonedHistory": {
38294
38518
  capName: "zone-analytics",
38295
38519
  capScope: "device",
@@ -39294,6 +39518,11 @@ Object.freeze({
39294
39518
  form: "single",
39295
39519
  optional: false
39296
39520
  }],
39521
+ "pipelineAnalytics.getKeyEventsBatch": [{
39522
+ name: "deviceIds",
39523
+ form: "array",
39524
+ optional: false
39525
+ }],
39297
39526
  "pipelineAnalytics.getMotionEvents": [{
39298
39527
  name: "deviceId",
39299
39528
  form: "single",
@@ -39734,6 +39963,11 @@ Object.freeze({
39734
39963
  form: "single",
39735
39964
  optional: false
39736
39965
  }],
39966
+ "recording.reconcileLedgerAgainstDisk": [{
39967
+ name: "deviceId",
39968
+ form: "single",
39969
+ optional: true
39970
+ }],
39737
39971
  "recording.relocateFootage": [{
39738
39972
  name: "deviceId",
39739
39973
  form: "single",
@@ -39799,6 +40033,11 @@ Object.freeze({
39799
40033
  form: "single",
39800
40034
  optional: false
39801
40035
  }],
40036
+ "sceneMonitor.listScenesBatch": [{
40037
+ name: "deviceIds",
40038
+ form: "array",
40039
+ optional: false
40040
+ }],
39802
40041
  "sceneMonitor.recheckNow": [{
39803
40042
  name: "deviceId",
39804
40043
  form: "single",
@@ -40060,6 +40299,11 @@ Object.freeze({
40060
40299
  form: "single",
40061
40300
  optional: false
40062
40301
  }],
40302
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40303
+ name: "deviceIds",
40304
+ form: "array",
40305
+ optional: false
40306
+ }],
40063
40307
  "zoneAnalytics.getUnzonedHistory": [{
40064
40308
  name: "deviceId",
40065
40309
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-import-alexa",
3
- "version": "0.2.47",
3
+ "version": "0.2.48",
4
4
  "description": "Alexa device-import provider for CamStack — imports the smart-home devices in a user's Alexa account via the unofficial alexa-remote2 cookie/token client (the inverse of the Alexa exporter)",
5
5
  "keywords": [
6
6
  "camstack",