@camstack/addon-matter-broker 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
@@ -8032,6 +8032,20 @@ var RelocateJobSchema = object({
8032
8032
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8033
8033
  */
8034
8034
  rowsReconciled: number().int().nonnegative().optional(),
8035
+ /**
8036
+ * Rows this run FORGOT because the file they name is not on disk.
8037
+ *
8038
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8039
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8040
+ * and the durable row is dropped through the same channel eviction uses. It
8041
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8042
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8043
+ * the same failure as one that quietly skips them (D295).
8044
+ *
8045
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8046
+ * ledger claimed 5.65 GB of footage that no longer existed.
8047
+ */
8048
+ rowsForgotten: number().int().nonnegative().optional(),
8035
8049
  startedAt: number(),
8036
8050
  finishedAt: number().nullable(),
8037
8051
  error: string$2().nullable()
@@ -8395,6 +8409,91 @@ var RelocateResidueSchema = object({
8395
8409
  segments: number().int().nonnegative(),
8396
8410
  bytes: number().int().nonnegative()
8397
8411
  }).nullable();
8412
+ /**
8413
+ * Ask one location whether its durable hour rows describe the disk — the walk
8414
+ * (D319).
8415
+ *
8416
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8417
+ * missing tool is the question, and the dry run is how they sanity-check the
8418
+ * destructive run before authorising it.
8419
+ */
8420
+ var LedgerWalkInputSchema = object({
8421
+ locationId: string$2().min(1),
8422
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8423
+ apply: boolean().optional(),
8424
+ /** Narrow to one camera. */
8425
+ deviceId: number().int().positive().optional(),
8426
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8427
+ profiles: array(string$2().min(1)).optional()
8428
+ });
8429
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8430
+ var LedgerWalkRefusalSchema = _enum([
8431
+ "location-unknown",
8432
+ "source-writable",
8433
+ "no-ledger",
8434
+ "archive-unreadable",
8435
+ "anchor-absent",
8436
+ "anchor-unreadable",
8437
+ "anchor-moved"
8438
+ ]);
8439
+ _enum([
8440
+ "live-tail",
8441
+ "listing-error",
8442
+ "path-mismatch",
8443
+ "durable-refused"
8444
+ ]);
8445
+ /** Every skip reason, always present, always a number — so a reason that never
8446
+ * fired reports as zero rather than absent and the report shape is constant
8447
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8448
+ var LedgerWalkSkipCountsSchema = object({
8449
+ "live-tail": number().int().nonnegative(),
8450
+ "listing-error": number().int().nonnegative(),
8451
+ "path-mismatch": number().int().nonnegative(),
8452
+ "durable-refused": number().int().nonnegative()
8453
+ });
8454
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8455
+ var LedgerWalkDeviceReportSchema = object({
8456
+ deviceId: number().int(),
8457
+ hoursWalked: number().int().nonnegative(),
8458
+ hoursMissing: number().int().nonnegative(),
8459
+ ghostSegments: number().int().nonnegative(),
8460
+ ghostBytes: number().int().nonnegative(),
8461
+ forgottenSegments: number().int().nonnegative(),
8462
+ orphanFiles: number().int().nonnegative()
8463
+ });
8464
+ /**
8465
+ * What one walk claimed, listed, found and (only when armed) forgot.
8466
+ *
8467
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8468
+ * walk that saw a fraction of the location is visible in its own report rather
8469
+ * than in the absence of one.
8470
+ */
8471
+ var LedgerWalkReportSchema = object({
8472
+ locationId: string$2(),
8473
+ applied: boolean(),
8474
+ refused: LedgerWalkRefusalSchema.nullable(),
8475
+ archiveSegments: number().int().nonnegative().nullable(),
8476
+ archiveBytes: number().int().nonnegative().nullable(),
8477
+ hoursClaimed: number().int().nonnegative(),
8478
+ hoursWalked: number().int().nonnegative(),
8479
+ hoursMissing: number().int().nonnegative(),
8480
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8481
+ listings: number().int().nonnegative(),
8482
+ segmentsClaimed: number().int().nonnegative(),
8483
+ ghostSegments: number().int().nonnegative(),
8484
+ ghostBytes: number().int().nonnegative(),
8485
+ ghostHoursWhole: number().int().nonnegative(),
8486
+ forgottenSegments: number().int().nonnegative(),
8487
+ forgottenBytes: number().int().nonnegative(),
8488
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8489
+ orphanFiles: number().int().nonnegative(),
8490
+ orphanSample: array(string$2()).readonly(),
8491
+ hoursSkipped: number().int().nonnegative(),
8492
+ skippedByReason: LedgerWalkSkipCountsSchema,
8493
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8494
+ bounded: boolean(),
8495
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8496
+ });
8398
8497
  /** How many rows a media pass would still act on against a given target — the
8399
8498
  * media lane's denominator AND its residue, from ONE derivation so the two can
8400
8499
  * never disagree. `null` = the count could not be taken. */
@@ -19076,13 +19175,15 @@ var ListGroupsPageSchema = object({
19076
19175
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19077
19176
  nextCursor: string$2().nullable()
19078
19177
  });
19178
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19179
+ var KEY_EVENTS_MAX_LIMIT = 200;
19079
19180
  var KeyEventQueryInput = object({
19080
19181
  deviceId: number(),
19081
19182
  /** Window lower bound (track firstSeen ≥ since). */
19082
19183
  since: number(),
19083
19184
  /** Window upper bound (track firstSeen ≤ until). */
19084
19185
  until: number(),
19085
- limit: number().int().min(1).max(200).default(50),
19186
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19086
19187
  /** Drop tracks scoring below this importance. */
19087
19188
  minImportance: number().min(0).max(1).optional(),
19088
19189
  /** Restrict to a single class (e.g. 'person'). */
@@ -19104,6 +19205,32 @@ var KeyEventSchema = object({
19104
19205
  ...TrackFlagFields,
19105
19206
  ...TrackRetrainFields
19106
19207
  });
19208
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19209
+ var KeyEventBatchQueryInput = object({
19210
+ deviceIds: array(number()).min(1).max(200),
19211
+ since: number(),
19212
+ until: number(),
19213
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19214
+ * across the set, which would let a busy camera starve a quiet one of its
19215
+ * rows and change what the merged feed contains. */
19216
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19217
+ minImportance: number().min(0).max(1).optional(),
19218
+ classFilter: string$2().optional()
19219
+ });
19220
+ /**
19221
+ * One camera's key events in a batch answer.
19222
+ *
19223
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19224
+ * error rather than throwing, so a camera whose store read failed and one with
19225
+ * no events in the window were ALREADY indistinguishable per camera — the
19226
+ * batch does not make that worse, and the row keeps the deviceId the single
19227
+ * method's output never carried (the caller used to stamp it from the fan-out
19228
+ * key, which only worked because there was one query per camera).
19229
+ */
19230
+ var KeyEventsForDeviceSchema = object({
19231
+ deviceId: number(),
19232
+ events: array(KeyEventSchema).readonly()
19233
+ });
19107
19234
  object({
19108
19235
  trackId: string$2(),
19109
19236
  className: string$2(),
@@ -19373,7 +19500,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19373
19500
  until: number().optional(),
19374
19501
  kinds: array(string$2()).optional(),
19375
19502
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19376
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19503
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19377
19504
  deviceId: number(),
19378
19505
  since: number(),
19379
19506
  until: number(),
@@ -28757,6 +28884,9 @@ method(object({
28757
28884
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28758
28885
  kind: "query",
28759
28886
  auth: "admin"
28887
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
28888
+ kind: "mutation",
28889
+ auth: "admin"
28760
28890
  }), method(object({ jobId: string$2() }), object({ cancelled: boolean() }), {
28761
28891
  kind: "mutation",
28762
28892
  auth: "admin"
@@ -29148,6 +29278,25 @@ var SceneMonitorStatusSchema = object({
29148
29278
  monitors: array(SceneMonitorSchema),
29149
29279
  lastFetchedAt: number()
29150
29280
  });
29281
+ /**
29282
+ * One camera's row in a `listScenesBatch` answer.
29283
+ *
29284
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29285
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29286
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29287
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29288
+ * configured. Fanned out per camera the difference was visible — one query
29289
+ * errored while the others resolved — and a batch that returned only the rows
29290
+ * it managed would have destroyed it, silently, by making an unreachable camera
29291
+ * indistinguishable from one that answered `monitors: []`.
29292
+ *
29293
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29294
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29295
+ */
29296
+ var SceneMonitorStatusForDeviceSchema = object({
29297
+ deviceId: number(),
29298
+ status: SceneMonitorStatusSchema.nullable()
29299
+ });
29151
29300
  var sceneMonitorCapability = {
29152
29301
  name: "scene-monitor",
29153
29302
  scope: "device",
@@ -29157,6 +29306,22 @@ var sceneMonitorCapability = {
29157
29306
  deviceTypes: [DeviceType.Camera],
29158
29307
  methods: {
29159
29308
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29309
+ /**
29310
+ * The same answer, for a SET of cameras, in one round trip.
29311
+ *
29312
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29313
+ * poll behind the push slice. Fanned out client-side that was one query
29314
+ * per camera — 29 round trips through the browser, the hub and the
29315
+ * post-analysis runner every 30 seconds to read an in-memory map the
29316
+ * owner had already merged. The work is unchanged (`statusFor` per
29317
+ * device, all in-process at the owner); what collapses is the transport.
29318
+ *
29319
+ * A camera that cannot answer still gets a row, with `status: null` —
29320
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29321
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29322
+ * tell which two are missing, or that any are.
29323
+ */
29324
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29160
29325
  createScene: method(object({
29161
29326
  deviceId: number(),
29162
29327
  label: string$2(),
@@ -30992,6 +31157,27 @@ var CameraOccupancySnapshotSchema = object({
30992
31157
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
30993
31158
  });
30994
31159
  /**
31160
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31161
+ *
31162
+ * THREE outcomes, and the single-camera method could only express two of them
31163
+ * because `snapshot: null` was already spoken for:
31164
+ *
31165
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31166
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31167
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31168
+ * - `read: 'unreadable'` — the owner could not answer for this
31169
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31170
+ *
31171
+ * Collapsing the last two is the failure this field exists to prevent: a
31172
+ * hydration that threw would otherwise render as an empty Stationary section,
31173
+ * which is a definite claim about a camera nobody could read.
31174
+ */
31175
+ var CameraOccupancySnapshotForDeviceSchema = object({
31176
+ deviceId: number(),
31177
+ read: _enum(["read", "unreadable"]),
31178
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31179
+ });
31180
+ /**
30995
31181
  * Time-series resolution. The history methods return one bucket per
30996
31182
  * step over the requested range. Smaller resolutions cost more
30997
31183
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31048,6 +31234,20 @@ var zoneAnalyticsCapability = {
31048
31234
  * (no inference result emitted since boot or since binding was
31049
31235
  * activated). */
31050
31236
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31237
+ /**
31238
+ * The same snapshot, for a SET of cameras, in one round trip.
31239
+ *
31240
+ * The Events page's Stationary section polls this every 15s for every
31241
+ * selected camera. Fanned out client-side that is one query per camera to
31242
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31243
+ * costs everything. Batched, N transports become one and the per-device
31244
+ * work is unchanged.
31245
+ *
31246
+ * Every requested deviceId gets a row, tagged `read` — see
31247
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31248
+ * not answer for is `'unreadable'`, never an empty reading.
31249
+ */
31250
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31051
31251
  /** Time-series object count inside one zone. `className` optional —
31052
31252
  * omit to count every class in the zone. */
31053
31253
  getZoneHistory: method(object({
@@ -35469,6 +35669,12 @@ Object.freeze({
35469
35669
  addonId: null,
35470
35670
  access: "view"
35471
35671
  },
35672
+ "pipelineAnalytics.getKeyEventsBatch": {
35673
+ capName: "pipeline-analytics",
35674
+ capScope: "device",
35675
+ addonId: null,
35676
+ access: "view"
35677
+ },
35472
35678
  "pipelineAnalytics.getMotionEvents": {
35473
35679
  capName: "pipeline-analytics",
35474
35680
  capScope: "device",
@@ -36645,6 +36851,12 @@ Object.freeze({
36645
36851
  addonId: null,
36646
36852
  access: "view"
36647
36853
  },
36854
+ "recording.reconcileLedgerAgainstDisk": {
36855
+ capName: "recording",
36856
+ capScope: "system",
36857
+ addonId: null,
36858
+ access: "create"
36859
+ },
36648
36860
  "recording.refreshStorageLocationsForMigration": {
36649
36861
  capName: "recording",
36650
36862
  capScope: "system",
@@ -36771,6 +36983,12 @@ Object.freeze({
36771
36983
  addonId: null,
36772
36984
  access: "view"
36773
36985
  },
36986
+ "sceneMonitor.listScenesBatch": {
36987
+ capName: "scene-monitor",
36988
+ capScope: "device",
36989
+ addonId: null,
36990
+ access: "view"
36991
+ },
36774
36992
  "sceneMonitor.recheckNow": {
36775
36993
  capName: "scene-monitor",
36776
36994
  capScope: "device",
@@ -38127,6 +38345,12 @@ Object.freeze({
38127
38345
  addonId: null,
38128
38346
  access: "view"
38129
38347
  },
38348
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38349
+ capName: "zone-analytics",
38350
+ capScope: "device",
38351
+ addonId: null,
38352
+ access: "view"
38353
+ },
38130
38354
  "zoneAnalytics.getUnzonedHistory": {
38131
38355
  capName: "zone-analytics",
38132
38356
  capScope: "device",
@@ -39131,6 +39355,11 @@ Object.freeze({
39131
39355
  form: "single",
39132
39356
  optional: false
39133
39357
  }],
39358
+ "pipelineAnalytics.getKeyEventsBatch": [{
39359
+ name: "deviceIds",
39360
+ form: "array",
39361
+ optional: false
39362
+ }],
39134
39363
  "pipelineAnalytics.getMotionEvents": [{
39135
39364
  name: "deviceId",
39136
39365
  form: "single",
@@ -39571,6 +39800,11 @@ Object.freeze({
39571
39800
  form: "single",
39572
39801
  optional: false
39573
39802
  }],
39803
+ "recording.reconcileLedgerAgainstDisk": [{
39804
+ name: "deviceId",
39805
+ form: "single",
39806
+ optional: true
39807
+ }],
39574
39808
  "recording.relocateFootage": [{
39575
39809
  name: "deviceId",
39576
39810
  form: "single",
@@ -39636,6 +39870,11 @@ Object.freeze({
39636
39870
  form: "single",
39637
39871
  optional: false
39638
39872
  }],
39873
+ "sceneMonitor.listScenesBatch": [{
39874
+ name: "deviceIds",
39875
+ form: "array",
39876
+ optional: false
39877
+ }],
39639
39878
  "sceneMonitor.recheckNow": [{
39640
39879
  name: "deviceId",
39641
39880
  form: "single",
@@ -39897,6 +40136,11 @@ Object.freeze({
39897
40136
  form: "single",
39898
40137
  optional: false
39899
40138
  }],
40139
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40140
+ name: "deviceIds",
40141
+ form: "array",
40142
+ optional: false
40143
+ }],
39900
40144
  "zoneAnalytics.getUnzonedHistory": [{
39901
40145
  name: "deviceId",
39902
40146
  form: "single",
package/dist/addon.mjs CHANGED
@@ -8030,6 +8030,20 @@ var RelocateJobSchema = object({
8030
8030
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8031
8031
  */
8032
8032
  rowsReconciled: number().int().nonnegative().optional(),
8033
+ /**
8034
+ * Rows this run FORGOT because the file they name is not on disk.
8035
+ *
8036
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8037
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8038
+ * and the durable row is dropped through the same channel eviction uses. It
8039
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8040
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8041
+ * the same failure as one that quietly skips them (D295).
8042
+ *
8043
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8044
+ * ledger claimed 5.65 GB of footage that no longer existed.
8045
+ */
8046
+ rowsForgotten: number().int().nonnegative().optional(),
8033
8047
  startedAt: number(),
8034
8048
  finishedAt: number().nullable(),
8035
8049
  error: string$2().nullable()
@@ -8393,6 +8407,91 @@ var RelocateResidueSchema = object({
8393
8407
  segments: number().int().nonnegative(),
8394
8408
  bytes: number().int().nonnegative()
8395
8409
  }).nullable();
8410
+ /**
8411
+ * Ask one location whether its durable hour rows describe the disk — the walk
8412
+ * (D319).
8413
+ *
8414
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8415
+ * missing tool is the question, and the dry run is how they sanity-check the
8416
+ * destructive run before authorising it.
8417
+ */
8418
+ var LedgerWalkInputSchema = object({
8419
+ locationId: string$2().min(1),
8420
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8421
+ apply: boolean().optional(),
8422
+ /** Narrow to one camera. */
8423
+ deviceId: number().int().positive().optional(),
8424
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8425
+ profiles: array(string$2().min(1)).optional()
8426
+ });
8427
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8428
+ var LedgerWalkRefusalSchema = _enum([
8429
+ "location-unknown",
8430
+ "source-writable",
8431
+ "no-ledger",
8432
+ "archive-unreadable",
8433
+ "anchor-absent",
8434
+ "anchor-unreadable",
8435
+ "anchor-moved"
8436
+ ]);
8437
+ _enum([
8438
+ "live-tail",
8439
+ "listing-error",
8440
+ "path-mismatch",
8441
+ "durable-refused"
8442
+ ]);
8443
+ /** Every skip reason, always present, always a number — so a reason that never
8444
+ * fired reports as zero rather than absent and the report shape is constant
8445
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8446
+ var LedgerWalkSkipCountsSchema = object({
8447
+ "live-tail": number().int().nonnegative(),
8448
+ "listing-error": number().int().nonnegative(),
8449
+ "path-mismatch": number().int().nonnegative(),
8450
+ "durable-refused": number().int().nonnegative()
8451
+ });
8452
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8453
+ var LedgerWalkDeviceReportSchema = object({
8454
+ deviceId: number().int(),
8455
+ hoursWalked: number().int().nonnegative(),
8456
+ hoursMissing: number().int().nonnegative(),
8457
+ ghostSegments: number().int().nonnegative(),
8458
+ ghostBytes: number().int().nonnegative(),
8459
+ forgottenSegments: number().int().nonnegative(),
8460
+ orphanFiles: number().int().nonnegative()
8461
+ });
8462
+ /**
8463
+ * What one walk claimed, listed, found and (only when armed) forgot.
8464
+ *
8465
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8466
+ * walk that saw a fraction of the location is visible in its own report rather
8467
+ * than in the absence of one.
8468
+ */
8469
+ var LedgerWalkReportSchema = object({
8470
+ locationId: string$2(),
8471
+ applied: boolean(),
8472
+ refused: LedgerWalkRefusalSchema.nullable(),
8473
+ archiveSegments: number().int().nonnegative().nullable(),
8474
+ archiveBytes: number().int().nonnegative().nullable(),
8475
+ hoursClaimed: number().int().nonnegative(),
8476
+ hoursWalked: number().int().nonnegative(),
8477
+ hoursMissing: number().int().nonnegative(),
8478
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8479
+ listings: number().int().nonnegative(),
8480
+ segmentsClaimed: number().int().nonnegative(),
8481
+ ghostSegments: number().int().nonnegative(),
8482
+ ghostBytes: number().int().nonnegative(),
8483
+ ghostHoursWhole: number().int().nonnegative(),
8484
+ forgottenSegments: number().int().nonnegative(),
8485
+ forgottenBytes: number().int().nonnegative(),
8486
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8487
+ orphanFiles: number().int().nonnegative(),
8488
+ orphanSample: array(string$2()).readonly(),
8489
+ hoursSkipped: number().int().nonnegative(),
8490
+ skippedByReason: LedgerWalkSkipCountsSchema,
8491
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8492
+ bounded: boolean(),
8493
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8494
+ });
8396
8495
  /** How many rows a media pass would still act on against a given target — the
8397
8496
  * media lane's denominator AND its residue, from ONE derivation so the two can
8398
8497
  * never disagree. `null` = the count could not be taken. */
@@ -19074,13 +19173,15 @@ var ListGroupsPageSchema = object({
19074
19173
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19075
19174
  nextCursor: string$2().nullable()
19076
19175
  });
19176
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19177
+ var KEY_EVENTS_MAX_LIMIT = 200;
19077
19178
  var KeyEventQueryInput = object({
19078
19179
  deviceId: number(),
19079
19180
  /** Window lower bound (track firstSeen ≥ since). */
19080
19181
  since: number(),
19081
19182
  /** Window upper bound (track firstSeen ≤ until). */
19082
19183
  until: number(),
19083
- limit: number().int().min(1).max(200).default(50),
19184
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19084
19185
  /** Drop tracks scoring below this importance. */
19085
19186
  minImportance: number().min(0).max(1).optional(),
19086
19187
  /** Restrict to a single class (e.g. 'person'). */
@@ -19102,6 +19203,32 @@ var KeyEventSchema = object({
19102
19203
  ...TrackFlagFields,
19103
19204
  ...TrackRetrainFields
19104
19205
  });
19206
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19207
+ var KeyEventBatchQueryInput = object({
19208
+ deviceIds: array(number()).min(1).max(200),
19209
+ since: number(),
19210
+ until: number(),
19211
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19212
+ * across the set, which would let a busy camera starve a quiet one of its
19213
+ * rows and change what the merged feed contains. */
19214
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19215
+ minImportance: number().min(0).max(1).optional(),
19216
+ classFilter: string$2().optional()
19217
+ });
19218
+ /**
19219
+ * One camera's key events in a batch answer.
19220
+ *
19221
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19222
+ * error rather than throwing, so a camera whose store read failed and one with
19223
+ * no events in the window were ALREADY indistinguishable per camera — the
19224
+ * batch does not make that worse, and the row keeps the deviceId the single
19225
+ * method's output never carried (the caller used to stamp it from the fan-out
19226
+ * key, which only worked because there was one query per camera).
19227
+ */
19228
+ var KeyEventsForDeviceSchema = object({
19229
+ deviceId: number(),
19230
+ events: array(KeyEventSchema).readonly()
19231
+ });
19105
19232
  object({
19106
19233
  trackId: string$2(),
19107
19234
  className: string$2(),
@@ -19371,7 +19498,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19371
19498
  until: number().optional(),
19372
19499
  kinds: array(string$2()).optional(),
19373
19500
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19374
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19501
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19375
19502
  deviceId: number(),
19376
19503
  since: number(),
19377
19504
  until: number(),
@@ -28755,6 +28882,9 @@ method(object({
28755
28882
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28756
28883
  kind: "query",
28757
28884
  auth: "admin"
28885
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
28886
+ kind: "mutation",
28887
+ auth: "admin"
28758
28888
  }), method(object({ jobId: string$2() }), object({ cancelled: boolean() }), {
28759
28889
  kind: "mutation",
28760
28890
  auth: "admin"
@@ -29146,6 +29276,25 @@ var SceneMonitorStatusSchema = object({
29146
29276
  monitors: array(SceneMonitorSchema),
29147
29277
  lastFetchedAt: number()
29148
29278
  });
29279
+ /**
29280
+ * One camera's row in a `listScenesBatch` answer.
29281
+ *
29282
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29283
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29284
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29285
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29286
+ * configured. Fanned out per camera the difference was visible — one query
29287
+ * errored while the others resolved — and a batch that returned only the rows
29288
+ * it managed would have destroyed it, silently, by making an unreachable camera
29289
+ * indistinguishable from one that answered `monitors: []`.
29290
+ *
29291
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29292
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29293
+ */
29294
+ var SceneMonitorStatusForDeviceSchema = object({
29295
+ deviceId: number(),
29296
+ status: SceneMonitorStatusSchema.nullable()
29297
+ });
29149
29298
  var sceneMonitorCapability = {
29150
29299
  name: "scene-monitor",
29151
29300
  scope: "device",
@@ -29155,6 +29304,22 @@ var sceneMonitorCapability = {
29155
29304
  deviceTypes: [DeviceType.Camera],
29156
29305
  methods: {
29157
29306
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29307
+ /**
29308
+ * The same answer, for a SET of cameras, in one round trip.
29309
+ *
29310
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29311
+ * poll behind the push slice. Fanned out client-side that was one query
29312
+ * per camera — 29 round trips through the browser, the hub and the
29313
+ * post-analysis runner every 30 seconds to read an in-memory map the
29314
+ * owner had already merged. The work is unchanged (`statusFor` per
29315
+ * device, all in-process at the owner); what collapses is the transport.
29316
+ *
29317
+ * A camera that cannot answer still gets a row, with `status: null` —
29318
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29319
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29320
+ * tell which two are missing, or that any are.
29321
+ */
29322
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29158
29323
  createScene: method(object({
29159
29324
  deviceId: number(),
29160
29325
  label: string$2(),
@@ -30990,6 +31155,27 @@ var CameraOccupancySnapshotSchema = object({
30990
31155
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
30991
31156
  });
30992
31157
  /**
31158
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31159
+ *
31160
+ * THREE outcomes, and the single-camera method could only express two of them
31161
+ * because `snapshot: null` was already spoken for:
31162
+ *
31163
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31164
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31165
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31166
+ * - `read: 'unreadable'` — the owner could not answer for this
31167
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31168
+ *
31169
+ * Collapsing the last two is the failure this field exists to prevent: a
31170
+ * hydration that threw would otherwise render as an empty Stationary section,
31171
+ * which is a definite claim about a camera nobody could read.
31172
+ */
31173
+ var CameraOccupancySnapshotForDeviceSchema = object({
31174
+ deviceId: number(),
31175
+ read: _enum(["read", "unreadable"]),
31176
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31177
+ });
31178
+ /**
30993
31179
  * Time-series resolution. The history methods return one bucket per
30994
31180
  * step over the requested range. Smaller resolutions cost more
30995
31181
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31046,6 +31232,20 @@ var zoneAnalyticsCapability = {
31046
31232
  * (no inference result emitted since boot or since binding was
31047
31233
  * activated). */
31048
31234
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31235
+ /**
31236
+ * The same snapshot, for a SET of cameras, in one round trip.
31237
+ *
31238
+ * The Events page's Stationary section polls this every 15s for every
31239
+ * selected camera. Fanned out client-side that is one query per camera to
31240
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31241
+ * costs everything. Batched, N transports become one and the per-device
31242
+ * work is unchanged.
31243
+ *
31244
+ * Every requested deviceId gets a row, tagged `read` — see
31245
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31246
+ * not answer for is `'unreadable'`, never an empty reading.
31247
+ */
31248
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31049
31249
  /** Time-series object count inside one zone. `className` optional —
31050
31250
  * omit to count every class in the zone. */
31051
31251
  getZoneHistory: method(object({
@@ -35467,6 +35667,12 @@ Object.freeze({
35467
35667
  addonId: null,
35468
35668
  access: "view"
35469
35669
  },
35670
+ "pipelineAnalytics.getKeyEventsBatch": {
35671
+ capName: "pipeline-analytics",
35672
+ capScope: "device",
35673
+ addonId: null,
35674
+ access: "view"
35675
+ },
35470
35676
  "pipelineAnalytics.getMotionEvents": {
35471
35677
  capName: "pipeline-analytics",
35472
35678
  capScope: "device",
@@ -36643,6 +36849,12 @@ Object.freeze({
36643
36849
  addonId: null,
36644
36850
  access: "view"
36645
36851
  },
36852
+ "recording.reconcileLedgerAgainstDisk": {
36853
+ capName: "recording",
36854
+ capScope: "system",
36855
+ addonId: null,
36856
+ access: "create"
36857
+ },
36646
36858
  "recording.refreshStorageLocationsForMigration": {
36647
36859
  capName: "recording",
36648
36860
  capScope: "system",
@@ -36769,6 +36981,12 @@ Object.freeze({
36769
36981
  addonId: null,
36770
36982
  access: "view"
36771
36983
  },
36984
+ "sceneMonitor.listScenesBatch": {
36985
+ capName: "scene-monitor",
36986
+ capScope: "device",
36987
+ addonId: null,
36988
+ access: "view"
36989
+ },
36772
36990
  "sceneMonitor.recheckNow": {
36773
36991
  capName: "scene-monitor",
36774
36992
  capScope: "device",
@@ -38125,6 +38343,12 @@ Object.freeze({
38125
38343
  addonId: null,
38126
38344
  access: "view"
38127
38345
  },
38346
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38347
+ capName: "zone-analytics",
38348
+ capScope: "device",
38349
+ addonId: null,
38350
+ access: "view"
38351
+ },
38128
38352
  "zoneAnalytics.getUnzonedHistory": {
38129
38353
  capName: "zone-analytics",
38130
38354
  capScope: "device",
@@ -39129,6 +39353,11 @@ Object.freeze({
39129
39353
  form: "single",
39130
39354
  optional: false
39131
39355
  }],
39356
+ "pipelineAnalytics.getKeyEventsBatch": [{
39357
+ name: "deviceIds",
39358
+ form: "array",
39359
+ optional: false
39360
+ }],
39132
39361
  "pipelineAnalytics.getMotionEvents": [{
39133
39362
  name: "deviceId",
39134
39363
  form: "single",
@@ -39569,6 +39798,11 @@ Object.freeze({
39569
39798
  form: "single",
39570
39799
  optional: false
39571
39800
  }],
39801
+ "recording.reconcileLedgerAgainstDisk": [{
39802
+ name: "deviceId",
39803
+ form: "single",
39804
+ optional: true
39805
+ }],
39572
39806
  "recording.relocateFootage": [{
39573
39807
  name: "deviceId",
39574
39808
  form: "single",
@@ -39634,6 +39868,11 @@ Object.freeze({
39634
39868
  form: "single",
39635
39869
  optional: false
39636
39870
  }],
39871
+ "sceneMonitor.listScenesBatch": [{
39872
+ name: "deviceIds",
39873
+ form: "array",
39874
+ optional: false
39875
+ }],
39637
39876
  "sceneMonitor.recheckNow": [{
39638
39877
  name: "deviceId",
39639
39878
  form: "single",
@@ -39895,6 +40134,11 @@ Object.freeze({
39895
40134
  form: "single",
39896
40135
  optional: false
39897
40136
  }],
40137
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40138
+ name: "deviceIds",
40139
+ form: "array",
40140
+ optional: false
40141
+ }],
39898
40142
  "zoneAnalytics.getUnzonedHistory": [{
39899
40143
  name: "deviceId",
39900
40144
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-matter-broker",
3
- "version": "0.2.47",
3
+ "version": "0.2.48",
4
4
  "description": "Matter broker addon for CamStack — owns a Matter fabric (commissioning + the long-lived controller) via the matter.js controller and brokers commissioned Matter nodes into CamStack",
5
5
  "keywords": [
6
6
  "camstack",