@camstack/addon-provider-rademacher 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
@@ -9003,6 +9003,20 @@ var RelocateJobSchema = object({
9003
9003
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
9004
9004
  */
9005
9005
  rowsReconciled: number().int().nonnegative().optional(),
9006
+ /**
9007
+ * Rows this run FORGOT because the file they name is not on disk.
9008
+ *
9009
+ * The mover derived the path from the row's own fields and `stat`ed it; an
9010
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
9011
+ * and the durable row is dropped through the same channel eviction uses. It
9012
+ * is reported for the same reason `rowsReconciled` is: this is a durable
9013
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
9014
+ * the same failure as one that quietly skips them (D295).
9015
+ *
9016
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
9017
+ * ledger claimed 5.65 GB of footage that no longer existed.
9018
+ */
9019
+ rowsForgotten: number().int().nonnegative().optional(),
9006
9020
  startedAt: number(),
9007
9021
  finishedAt: number().nullable(),
9008
9022
  error: string().nullable()
@@ -9366,6 +9380,91 @@ var RelocateResidueSchema = object({
9366
9380
  segments: number().int().nonnegative(),
9367
9381
  bytes: number().int().nonnegative()
9368
9382
  }).nullable();
9383
+ /**
9384
+ * Ask one location whether its durable hour rows describe the disk — the walk
9385
+ * (D319).
9386
+ *
9387
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
9388
+ * missing tool is the question, and the dry run is how they sanity-check the
9389
+ * destructive run before authorising it.
9390
+ */
9391
+ var LedgerWalkInputSchema = object({
9392
+ locationId: string().min(1),
9393
+ /** Forget the confirmed-absent rows, rather than only counting them. */
9394
+ apply: boolean().optional(),
9395
+ /** Narrow to one camera. */
9396
+ deviceId: number().int().positive().optional(),
9397
+ /** Narrow to these recording profiles; empty/absent = every profile. */
9398
+ profiles: array(string().min(1)).optional()
9399
+ });
9400
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
9401
+ var LedgerWalkRefusalSchema = _enum([
9402
+ "location-unknown",
9403
+ "source-writable",
9404
+ "no-ledger",
9405
+ "archive-unreadable",
9406
+ "anchor-absent",
9407
+ "anchor-unreadable",
9408
+ "anchor-moved"
9409
+ ]);
9410
+ _enum([
9411
+ "live-tail",
9412
+ "listing-error",
9413
+ "path-mismatch",
9414
+ "durable-refused"
9415
+ ]);
9416
+ /** Every skip reason, always present, always a number — so a reason that never
9417
+ * fired reports as zero rather than absent and the report shape is constant
9418
+ * between passes. Spelled out rather than `z.record` for exactly that. */
9419
+ var LedgerWalkSkipCountsSchema = object({
9420
+ "live-tail": number().int().nonnegative(),
9421
+ "listing-error": number().int().nonnegative(),
9422
+ "path-mismatch": number().int().nonnegative(),
9423
+ "durable-refused": number().int().nonnegative()
9424
+ });
9425
+ /** One camera's share of a walk, so a report names cameras and not rows. */
9426
+ var LedgerWalkDeviceReportSchema = object({
9427
+ deviceId: number().int(),
9428
+ hoursWalked: number().int().nonnegative(),
9429
+ hoursMissing: number().int().nonnegative(),
9430
+ ghostSegments: number().int().nonnegative(),
9431
+ ghostBytes: number().int().nonnegative(),
9432
+ forgottenSegments: number().int().nonnegative(),
9433
+ orphanFiles: number().int().nonnegative()
9434
+ });
9435
+ /**
9436
+ * What one walk claimed, listed, found and (only when armed) forgot.
9437
+ *
9438
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
9439
+ * walk that saw a fraction of the location is visible in its own report rather
9440
+ * than in the absence of one.
9441
+ */
9442
+ var LedgerWalkReportSchema = object({
9443
+ locationId: string(),
9444
+ applied: boolean(),
9445
+ refused: LedgerWalkRefusalSchema.nullable(),
9446
+ archiveSegments: number().int().nonnegative().nullable(),
9447
+ archiveBytes: number().int().nonnegative().nullable(),
9448
+ hoursClaimed: number().int().nonnegative(),
9449
+ hoursWalked: number().int().nonnegative(),
9450
+ hoursMissing: number().int().nonnegative(),
9451
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
9452
+ listings: number().int().nonnegative(),
9453
+ segmentsClaimed: number().int().nonnegative(),
9454
+ ghostSegments: number().int().nonnegative(),
9455
+ ghostBytes: number().int().nonnegative(),
9456
+ ghostHoursWhole: number().int().nonnegative(),
9457
+ forgottenSegments: number().int().nonnegative(),
9458
+ forgottenBytes: number().int().nonnegative(),
9459
+ /** Files under a claimed hour that no durable row names. Never deleted. */
9460
+ orphanFiles: number().int().nonnegative(),
9461
+ orphanSample: array(string()).readonly(),
9462
+ hoursSkipped: number().int().nonnegative(),
9463
+ skippedByReason: LedgerWalkSkipCountsSchema,
9464
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
9465
+ bounded: boolean(),
9466
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
9467
+ });
9369
9468
  /** How many rows a media pass would still act on against a given target — the
9370
9469
  * media lane's denominator AND its residue, from ONE derivation so the two can
9371
9470
  * never disagree. `null` = the count could not be taken. */
@@ -19981,13 +20080,15 @@ var ListGroupsPageSchema = object({
19981
20080
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19982
20081
  nextCursor: string().nullable()
19983
20082
  });
20083
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
20084
+ var KEY_EVENTS_MAX_LIMIT = 200;
19984
20085
  var KeyEventQueryInput = object({
19985
20086
  deviceId: number(),
19986
20087
  /** Window lower bound (track firstSeen ≥ since). */
19987
20088
  since: number(),
19988
20089
  /** Window upper bound (track firstSeen ≤ until). */
19989
20090
  until: number(),
19990
- limit: number().int().min(1).max(200).default(50),
20091
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19991
20092
  /** Drop tracks scoring below this importance. */
19992
20093
  minImportance: number().min(0).max(1).optional(),
19993
20094
  /** Restrict to a single class (e.g. 'person'). */
@@ -20009,6 +20110,32 @@ var KeyEventSchema = object({
20009
20110
  ...TrackFlagFields,
20010
20111
  ...TrackRetrainFields
20011
20112
  });
20113
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
20114
+ var KeyEventBatchQueryInput = object({
20115
+ deviceIds: array(number()).min(1).max(200),
20116
+ since: number(),
20117
+ until: number(),
20118
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
20119
+ * across the set, which would let a busy camera starve a quiet one of its
20120
+ * rows and change what the merged feed contains. */
20121
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
20122
+ minImportance: number().min(0).max(1).optional(),
20123
+ classFilter: string().optional()
20124
+ });
20125
+ /**
20126
+ * One camera's key events in a batch answer.
20127
+ *
20128
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
20129
+ * error rather than throwing, so a camera whose store read failed and one with
20130
+ * no events in the window were ALREADY indistinguishable per camera — the
20131
+ * batch does not make that worse, and the row keeps the deviceId the single
20132
+ * method's output never carried (the caller used to stamp it from the fan-out
20133
+ * key, which only worked because there was one query per camera).
20134
+ */
20135
+ var KeyEventsForDeviceSchema = object({
20136
+ deviceId: number(),
20137
+ events: array(KeyEventSchema).readonly()
20138
+ });
20012
20139
  object({
20013
20140
  trackId: string(),
20014
20141
  className: string(),
@@ -20278,7 +20405,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20278
20405
  until: number().optional(),
20279
20406
  kinds: array(string()).optional(),
20280
20407
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20281
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20408
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
20282
20409
  deviceId: number(),
20283
20410
  since: number(),
20284
20411
  until: number(),
@@ -29645,6 +29772,9 @@ method(object({
29645
29772
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29646
29773
  kind: "query",
29647
29774
  auth: "admin"
29775
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29776
+ kind: "mutation",
29777
+ auth: "admin"
29648
29778
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29649
29779
  kind: "mutation",
29650
29780
  auth: "admin"
@@ -30036,6 +30166,25 @@ var SceneMonitorStatusSchema = object({
30036
30166
  monitors: array(SceneMonitorSchema),
30037
30167
  lastFetchedAt: number()
30038
30168
  });
30169
+ /**
30170
+ * One camera's row in a `listScenesBatch` answer.
30171
+ *
30172
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
30173
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
30174
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
30175
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
30176
+ * configured. Fanned out per camera the difference was visible — one query
30177
+ * errored while the others resolved — and a batch that returned only the rows
30178
+ * it managed would have destroyed it, silently, by making an unreachable camera
30179
+ * indistinguishable from one that answered `monitors: []`.
30180
+ *
30181
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
30182
+ * could not be read"; `status.monitors: []` means "read, and it has none".
30183
+ */
30184
+ var SceneMonitorStatusForDeviceSchema = object({
30185
+ deviceId: number(),
30186
+ status: SceneMonitorStatusSchema.nullable()
30187
+ });
30039
30188
  var sceneMonitorCapability = {
30040
30189
  name: "scene-monitor",
30041
30190
  scope: "device",
@@ -30045,6 +30194,22 @@ var sceneMonitorCapability = {
30045
30194
  deviceTypes: [DeviceType.Camera],
30046
30195
  methods: {
30047
30196
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
30197
+ /**
30198
+ * The same answer, for a SET of cameras, in one round trip.
30199
+ *
30200
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
30201
+ * poll behind the push slice. Fanned out client-side that was one query
30202
+ * per camera — 29 round trips through the browser, the hub and the
30203
+ * post-analysis runner every 30 seconds to read an in-memory map the
30204
+ * owner had already merged. The work is unchanged (`statusFor` per
30205
+ * device, all in-process at the owner); what collapses is the transport.
30206
+ *
30207
+ * A camera that cannot answer still gets a row, with `status: null` —
30208
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
30209
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
30210
+ * tell which two are missing, or that any are.
30211
+ */
30212
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
30048
30213
  createScene: method(object({
30049
30214
  deviceId: number(),
30050
30215
  label: string(),
@@ -31880,6 +32045,27 @@ var CameraOccupancySnapshotSchema = object({
31880
32045
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31881
32046
  });
31882
32047
  /**
32048
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
32049
+ *
32050
+ * THREE outcomes, and the single-camera method could only express two of them
32051
+ * because `snapshot: null` was already spoken for:
32052
+ *
32053
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
32054
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
32055
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
32056
+ * - `read: 'unreadable'` — the owner could not answer for this
32057
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
32058
+ *
32059
+ * Collapsing the last two is the failure this field exists to prevent: a
32060
+ * hydration that threw would otherwise render as an empty Stationary section,
32061
+ * which is a definite claim about a camera nobody could read.
32062
+ */
32063
+ var CameraOccupancySnapshotForDeviceSchema = object({
32064
+ deviceId: number(),
32065
+ read: _enum(["read", "unreadable"]),
32066
+ snapshot: CameraOccupancySnapshotSchema.nullable()
32067
+ });
32068
+ /**
31883
32069
  * Time-series resolution. The history methods return one bucket per
31884
32070
  * step over the requested range. Smaller resolutions cost more
31885
32071
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31936,6 +32122,20 @@ var zoneAnalyticsCapability = {
31936
32122
  * (no inference result emitted since boot or since binding was
31937
32123
  * activated). */
31938
32124
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
32125
+ /**
32126
+ * The same snapshot, for a SET of cameras, in one round trip.
32127
+ *
32128
+ * The Events page's Stationary section polls this every 15s for every
32129
+ * selected camera. Fanned out client-side that is one query per camera to
32130
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
32131
+ * costs everything. Batched, N transports become one and the per-device
32132
+ * work is unchanged.
32133
+ *
32134
+ * Every requested deviceId gets a row, tagged `read` — see
32135
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
32136
+ * not answer for is `'unreadable'`, never an empty reading.
32137
+ */
32138
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31939
32139
  /** Time-series object count inside one zone. `className` optional —
31940
32140
  * omit to count every class in the zone. */
31941
32141
  getZoneHistory: method(object({
@@ -36357,6 +36557,12 @@ Object.freeze({
36357
36557
  addonId: null,
36358
36558
  access: "view"
36359
36559
  },
36560
+ "pipelineAnalytics.getKeyEventsBatch": {
36561
+ capName: "pipeline-analytics",
36562
+ capScope: "device",
36563
+ addonId: null,
36564
+ access: "view"
36565
+ },
36360
36566
  "pipelineAnalytics.getMotionEvents": {
36361
36567
  capName: "pipeline-analytics",
36362
36568
  capScope: "device",
@@ -37533,6 +37739,12 @@ Object.freeze({
37533
37739
  addonId: null,
37534
37740
  access: "view"
37535
37741
  },
37742
+ "recording.reconcileLedgerAgainstDisk": {
37743
+ capName: "recording",
37744
+ capScope: "system",
37745
+ addonId: null,
37746
+ access: "create"
37747
+ },
37536
37748
  "recording.refreshStorageLocationsForMigration": {
37537
37749
  capName: "recording",
37538
37750
  capScope: "system",
@@ -37659,6 +37871,12 @@ Object.freeze({
37659
37871
  addonId: null,
37660
37872
  access: "view"
37661
37873
  },
37874
+ "sceneMonitor.listScenesBatch": {
37875
+ capName: "scene-monitor",
37876
+ capScope: "device",
37877
+ addonId: null,
37878
+ access: "view"
37879
+ },
37662
37880
  "sceneMonitor.recheckNow": {
37663
37881
  capName: "scene-monitor",
37664
37882
  capScope: "device",
@@ -39015,6 +39233,12 @@ Object.freeze({
39015
39233
  addonId: null,
39016
39234
  access: "view"
39017
39235
  },
39236
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39237
+ capName: "zone-analytics",
39238
+ capScope: "device",
39239
+ addonId: null,
39240
+ access: "view"
39241
+ },
39018
39242
  "zoneAnalytics.getUnzonedHistory": {
39019
39243
  capName: "zone-analytics",
39020
39244
  capScope: "device",
@@ -40019,6 +40243,11 @@ Object.freeze({
40019
40243
  form: "single",
40020
40244
  optional: false
40021
40245
  }],
40246
+ "pipelineAnalytics.getKeyEventsBatch": [{
40247
+ name: "deviceIds",
40248
+ form: "array",
40249
+ optional: false
40250
+ }],
40022
40251
  "pipelineAnalytics.getMotionEvents": [{
40023
40252
  name: "deviceId",
40024
40253
  form: "single",
@@ -40459,6 +40688,11 @@ Object.freeze({
40459
40688
  form: "single",
40460
40689
  optional: false
40461
40690
  }],
40691
+ "recording.reconcileLedgerAgainstDisk": [{
40692
+ name: "deviceId",
40693
+ form: "single",
40694
+ optional: true
40695
+ }],
40462
40696
  "recording.relocateFootage": [{
40463
40697
  name: "deviceId",
40464
40698
  form: "single",
@@ -40524,6 +40758,11 @@ Object.freeze({
40524
40758
  form: "single",
40525
40759
  optional: false
40526
40760
  }],
40761
+ "sceneMonitor.listScenesBatch": [{
40762
+ name: "deviceIds",
40763
+ form: "array",
40764
+ optional: false
40765
+ }],
40527
40766
  "sceneMonitor.recheckNow": [{
40528
40767
  name: "deviceId",
40529
40768
  form: "single",
@@ -40785,6 +41024,11 @@ Object.freeze({
40785
41024
  form: "single",
40786
41025
  optional: false
40787
41026
  }],
41027
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41028
+ name: "deviceIds",
41029
+ form: "array",
41030
+ optional: false
41031
+ }],
40788
41032
  "zoneAnalytics.getUnzonedHistory": [{
40789
41033
  name: "deviceId",
40790
41034
  form: "single",
package/dist/addon.mjs CHANGED
@@ -9002,6 +9002,20 @@ var RelocateJobSchema = object({
9002
9002
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
9003
9003
  */
9004
9004
  rowsReconciled: number().int().nonnegative().optional(),
9005
+ /**
9006
+ * Rows this run FORGOT because the file they name is not on disk.
9007
+ *
9008
+ * The mover derived the path from the row's own fields and `stat`ed it; an
9009
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
9010
+ * and the durable row is dropped through the same channel eviction uses. It
9011
+ * is reported for the same reason `rowsReconciled` is: this is a durable
9012
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
9013
+ * the same failure as one that quietly skips them (D295).
9014
+ *
9015
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
9016
+ * ledger claimed 5.65 GB of footage that no longer existed.
9017
+ */
9018
+ rowsForgotten: number().int().nonnegative().optional(),
9005
9019
  startedAt: number(),
9006
9020
  finishedAt: number().nullable(),
9007
9021
  error: string().nullable()
@@ -9365,6 +9379,91 @@ var RelocateResidueSchema = object({
9365
9379
  segments: number().int().nonnegative(),
9366
9380
  bytes: number().int().nonnegative()
9367
9381
  }).nullable();
9382
+ /**
9383
+ * Ask one location whether its durable hour rows describe the disk — the walk
9384
+ * (D319).
9385
+ *
9386
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
9387
+ * missing tool is the question, and the dry run is how they sanity-check the
9388
+ * destructive run before authorising it.
9389
+ */
9390
+ var LedgerWalkInputSchema = object({
9391
+ locationId: string().min(1),
9392
+ /** Forget the confirmed-absent rows, rather than only counting them. */
9393
+ apply: boolean().optional(),
9394
+ /** Narrow to one camera. */
9395
+ deviceId: number().int().positive().optional(),
9396
+ /** Narrow to these recording profiles; empty/absent = every profile. */
9397
+ profiles: array(string().min(1)).optional()
9398
+ });
9399
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
9400
+ var LedgerWalkRefusalSchema = _enum([
9401
+ "location-unknown",
9402
+ "source-writable",
9403
+ "no-ledger",
9404
+ "archive-unreadable",
9405
+ "anchor-absent",
9406
+ "anchor-unreadable",
9407
+ "anchor-moved"
9408
+ ]);
9409
+ _enum([
9410
+ "live-tail",
9411
+ "listing-error",
9412
+ "path-mismatch",
9413
+ "durable-refused"
9414
+ ]);
9415
+ /** Every skip reason, always present, always a number — so a reason that never
9416
+ * fired reports as zero rather than absent and the report shape is constant
9417
+ * between passes. Spelled out rather than `z.record` for exactly that. */
9418
+ var LedgerWalkSkipCountsSchema = object({
9419
+ "live-tail": number().int().nonnegative(),
9420
+ "listing-error": number().int().nonnegative(),
9421
+ "path-mismatch": number().int().nonnegative(),
9422
+ "durable-refused": number().int().nonnegative()
9423
+ });
9424
+ /** One camera's share of a walk, so a report names cameras and not rows. */
9425
+ var LedgerWalkDeviceReportSchema = object({
9426
+ deviceId: number().int(),
9427
+ hoursWalked: number().int().nonnegative(),
9428
+ hoursMissing: number().int().nonnegative(),
9429
+ ghostSegments: number().int().nonnegative(),
9430
+ ghostBytes: number().int().nonnegative(),
9431
+ forgottenSegments: number().int().nonnegative(),
9432
+ orphanFiles: number().int().nonnegative()
9433
+ });
9434
+ /**
9435
+ * What one walk claimed, listed, found and (only when armed) forgot.
9436
+ *
9437
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
9438
+ * walk that saw a fraction of the location is visible in its own report rather
9439
+ * than in the absence of one.
9440
+ */
9441
+ var LedgerWalkReportSchema = object({
9442
+ locationId: string(),
9443
+ applied: boolean(),
9444
+ refused: LedgerWalkRefusalSchema.nullable(),
9445
+ archiveSegments: number().int().nonnegative().nullable(),
9446
+ archiveBytes: number().int().nonnegative().nullable(),
9447
+ hoursClaimed: number().int().nonnegative(),
9448
+ hoursWalked: number().int().nonnegative(),
9449
+ hoursMissing: number().int().nonnegative(),
9450
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
9451
+ listings: number().int().nonnegative(),
9452
+ segmentsClaimed: number().int().nonnegative(),
9453
+ ghostSegments: number().int().nonnegative(),
9454
+ ghostBytes: number().int().nonnegative(),
9455
+ ghostHoursWhole: number().int().nonnegative(),
9456
+ forgottenSegments: number().int().nonnegative(),
9457
+ forgottenBytes: number().int().nonnegative(),
9458
+ /** Files under a claimed hour that no durable row names. Never deleted. */
9459
+ orphanFiles: number().int().nonnegative(),
9460
+ orphanSample: array(string()).readonly(),
9461
+ hoursSkipped: number().int().nonnegative(),
9462
+ skippedByReason: LedgerWalkSkipCountsSchema,
9463
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
9464
+ bounded: boolean(),
9465
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
9466
+ });
9368
9467
  /** How many rows a media pass would still act on against a given target — the
9369
9468
  * media lane's denominator AND its residue, from ONE derivation so the two can
9370
9469
  * never disagree. `null` = the count could not be taken. */
@@ -19980,13 +20079,15 @@ var ListGroupsPageSchema = object({
19980
20079
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19981
20080
  nextCursor: string().nullable()
19982
20081
  });
20082
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
20083
+ var KEY_EVENTS_MAX_LIMIT = 200;
19983
20084
  var KeyEventQueryInput = object({
19984
20085
  deviceId: number(),
19985
20086
  /** Window lower bound (track firstSeen ≥ since). */
19986
20087
  since: number(),
19987
20088
  /** Window upper bound (track firstSeen ≤ until). */
19988
20089
  until: number(),
19989
- limit: number().int().min(1).max(200).default(50),
20090
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19990
20091
  /** Drop tracks scoring below this importance. */
19991
20092
  minImportance: number().min(0).max(1).optional(),
19992
20093
  /** Restrict to a single class (e.g. 'person'). */
@@ -20008,6 +20109,32 @@ var KeyEventSchema = object({
20008
20109
  ...TrackFlagFields,
20009
20110
  ...TrackRetrainFields
20010
20111
  });
20112
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
20113
+ var KeyEventBatchQueryInput = object({
20114
+ deviceIds: array(number()).min(1).max(200),
20115
+ since: number(),
20116
+ until: number(),
20117
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
20118
+ * across the set, which would let a busy camera starve a quiet one of its
20119
+ * rows and change what the merged feed contains. */
20120
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
20121
+ minImportance: number().min(0).max(1).optional(),
20122
+ classFilter: string().optional()
20123
+ });
20124
+ /**
20125
+ * One camera's key events in a batch answer.
20126
+ *
20127
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
20128
+ * error rather than throwing, so a camera whose store read failed and one with
20129
+ * no events in the window were ALREADY indistinguishable per camera — the
20130
+ * batch does not make that worse, and the row keeps the deviceId the single
20131
+ * method's output never carried (the caller used to stamp it from the fan-out
20132
+ * key, which only worked because there was one query per camera).
20133
+ */
20134
+ var KeyEventsForDeviceSchema = object({
20135
+ deviceId: number(),
20136
+ events: array(KeyEventSchema).readonly()
20137
+ });
20011
20138
  object({
20012
20139
  trackId: string(),
20013
20140
  className: string(),
@@ -20277,7 +20404,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20277
20404
  until: number().optional(),
20278
20405
  kinds: array(string()).optional(),
20279
20406
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20280
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20407
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
20281
20408
  deviceId: number(),
20282
20409
  since: number(),
20283
20410
  until: number(),
@@ -29644,6 +29771,9 @@ method(object({
29644
29771
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29645
29772
  kind: "query",
29646
29773
  auth: "admin"
29774
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29775
+ kind: "mutation",
29776
+ auth: "admin"
29647
29777
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29648
29778
  kind: "mutation",
29649
29779
  auth: "admin"
@@ -30035,6 +30165,25 @@ var SceneMonitorStatusSchema = object({
30035
30165
  monitors: array(SceneMonitorSchema),
30036
30166
  lastFetchedAt: number()
30037
30167
  });
30168
+ /**
30169
+ * One camera's row in a `listScenesBatch` answer.
30170
+ *
30171
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
30172
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
30173
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
30174
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
30175
+ * configured. Fanned out per camera the difference was visible — one query
30176
+ * errored while the others resolved — and a batch that returned only the rows
30177
+ * it managed would have destroyed it, silently, by making an unreachable camera
30178
+ * indistinguishable from one that answered `monitors: []`.
30179
+ *
30180
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
30181
+ * could not be read"; `status.monitors: []` means "read, and it has none".
30182
+ */
30183
+ var SceneMonitorStatusForDeviceSchema = object({
30184
+ deviceId: number(),
30185
+ status: SceneMonitorStatusSchema.nullable()
30186
+ });
30038
30187
  var sceneMonitorCapability = {
30039
30188
  name: "scene-monitor",
30040
30189
  scope: "device",
@@ -30044,6 +30193,22 @@ var sceneMonitorCapability = {
30044
30193
  deviceTypes: [DeviceType.Camera],
30045
30194
  methods: {
30046
30195
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
30196
+ /**
30197
+ * The same answer, for a SET of cameras, in one round trip.
30198
+ *
30199
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
30200
+ * poll behind the push slice. Fanned out client-side that was one query
30201
+ * per camera — 29 round trips through the browser, the hub and the
30202
+ * post-analysis runner every 30 seconds to read an in-memory map the
30203
+ * owner had already merged. The work is unchanged (`statusFor` per
30204
+ * device, all in-process at the owner); what collapses is the transport.
30205
+ *
30206
+ * A camera that cannot answer still gets a row, with `status: null` —
30207
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
30208
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
30209
+ * tell which two are missing, or that any are.
30210
+ */
30211
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
30047
30212
  createScene: method(object({
30048
30213
  deviceId: number(),
30049
30214
  label: string(),
@@ -31879,6 +32044,27 @@ var CameraOccupancySnapshotSchema = object({
31879
32044
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31880
32045
  });
31881
32046
  /**
32047
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
32048
+ *
32049
+ * THREE outcomes, and the single-camera method could only express two of them
32050
+ * because `snapshot: null` was already spoken for:
32051
+ *
32052
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
32053
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
32054
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
32055
+ * - `read: 'unreadable'` — the owner could not answer for this
32056
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
32057
+ *
32058
+ * Collapsing the last two is the failure this field exists to prevent: a
32059
+ * hydration that threw would otherwise render as an empty Stationary section,
32060
+ * which is a definite claim about a camera nobody could read.
32061
+ */
32062
+ var CameraOccupancySnapshotForDeviceSchema = object({
32063
+ deviceId: number(),
32064
+ read: _enum(["read", "unreadable"]),
32065
+ snapshot: CameraOccupancySnapshotSchema.nullable()
32066
+ });
32067
+ /**
31882
32068
  * Time-series resolution. The history methods return one bucket per
31883
32069
  * step over the requested range. Smaller resolutions cost more
31884
32070
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31935,6 +32121,20 @@ var zoneAnalyticsCapability = {
31935
32121
  * (no inference result emitted since boot or since binding was
31936
32122
  * activated). */
31937
32123
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
32124
+ /**
32125
+ * The same snapshot, for a SET of cameras, in one round trip.
32126
+ *
32127
+ * The Events page's Stationary section polls this every 15s for every
32128
+ * selected camera. Fanned out client-side that is one query per camera to
32129
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
32130
+ * costs everything. Batched, N transports become one and the per-device
32131
+ * work is unchanged.
32132
+ *
32133
+ * Every requested deviceId gets a row, tagged `read` — see
32134
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
32135
+ * not answer for is `'unreadable'`, never an empty reading.
32136
+ */
32137
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31938
32138
  /** Time-series object count inside one zone. `className` optional —
31939
32139
  * omit to count every class in the zone. */
31940
32140
  getZoneHistory: method(object({
@@ -36356,6 +36556,12 @@ Object.freeze({
36356
36556
  addonId: null,
36357
36557
  access: "view"
36358
36558
  },
36559
+ "pipelineAnalytics.getKeyEventsBatch": {
36560
+ capName: "pipeline-analytics",
36561
+ capScope: "device",
36562
+ addonId: null,
36563
+ access: "view"
36564
+ },
36359
36565
  "pipelineAnalytics.getMotionEvents": {
36360
36566
  capName: "pipeline-analytics",
36361
36567
  capScope: "device",
@@ -37532,6 +37738,12 @@ Object.freeze({
37532
37738
  addonId: null,
37533
37739
  access: "view"
37534
37740
  },
37741
+ "recording.reconcileLedgerAgainstDisk": {
37742
+ capName: "recording",
37743
+ capScope: "system",
37744
+ addonId: null,
37745
+ access: "create"
37746
+ },
37535
37747
  "recording.refreshStorageLocationsForMigration": {
37536
37748
  capName: "recording",
37537
37749
  capScope: "system",
@@ -37658,6 +37870,12 @@ Object.freeze({
37658
37870
  addonId: null,
37659
37871
  access: "view"
37660
37872
  },
37873
+ "sceneMonitor.listScenesBatch": {
37874
+ capName: "scene-monitor",
37875
+ capScope: "device",
37876
+ addonId: null,
37877
+ access: "view"
37878
+ },
37661
37879
  "sceneMonitor.recheckNow": {
37662
37880
  capName: "scene-monitor",
37663
37881
  capScope: "device",
@@ -39014,6 +39232,12 @@ Object.freeze({
39014
39232
  addonId: null,
39015
39233
  access: "view"
39016
39234
  },
39235
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39236
+ capName: "zone-analytics",
39237
+ capScope: "device",
39238
+ addonId: null,
39239
+ access: "view"
39240
+ },
39017
39241
  "zoneAnalytics.getUnzonedHistory": {
39018
39242
  capName: "zone-analytics",
39019
39243
  capScope: "device",
@@ -40018,6 +40242,11 @@ Object.freeze({
40018
40242
  form: "single",
40019
40243
  optional: false
40020
40244
  }],
40245
+ "pipelineAnalytics.getKeyEventsBatch": [{
40246
+ name: "deviceIds",
40247
+ form: "array",
40248
+ optional: false
40249
+ }],
40021
40250
  "pipelineAnalytics.getMotionEvents": [{
40022
40251
  name: "deviceId",
40023
40252
  form: "single",
@@ -40458,6 +40687,11 @@ Object.freeze({
40458
40687
  form: "single",
40459
40688
  optional: false
40460
40689
  }],
40690
+ "recording.reconcileLedgerAgainstDisk": [{
40691
+ name: "deviceId",
40692
+ form: "single",
40693
+ optional: true
40694
+ }],
40461
40695
  "recording.relocateFootage": [{
40462
40696
  name: "deviceId",
40463
40697
  form: "single",
@@ -40523,6 +40757,11 @@ Object.freeze({
40523
40757
  form: "single",
40524
40758
  optional: false
40525
40759
  }],
40760
+ "sceneMonitor.listScenesBatch": [{
40761
+ name: "deviceIds",
40762
+ form: "array",
40763
+ optional: false
40764
+ }],
40526
40765
  "sceneMonitor.recheckNow": [{
40527
40766
  name: "deviceId",
40528
40767
  form: "single",
@@ -40784,6 +41023,11 @@ Object.freeze({
40784
41023
  form: "single",
40785
41024
  optional: false
40786
41025
  }],
41026
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41027
+ name: "deviceIds",
41028
+ form: "array",
41029
+ optional: false
41030
+ }],
40787
41031
  "zoneAnalytics.getUnzonedHistory": [{
40788
41032
  name: "deviceId",
40789
41033
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rademacher",
3
- "version": "0.2.47",
3
+ "version": "0.2.48",
4
4
  "description": "Rademacher HomePilot device-provider addon for CamStack — wraps the @apocaliss92/noderademacher local-hub client (roller shutters over the cover cap)",
5
5
  "keywords": [
6
6
  "camstack",