@camstack/addon-provider-rademacher 0.2.46 → 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(),
@@ -20080,6 +20207,50 @@ var EventStoreFootprintSchema = object({
20080
20207
  totalBytes: number().int(),
20081
20208
  devices: array(EventStoreDeviceFootprintSchema).readonly()
20082
20209
  });
20210
+ /** Event-media footprint for one {@link MediaFileKind}. */
20211
+ var EventMediaKindFootprintSchema = object({
20212
+ kind: MediaFileKindEnum,
20213
+ /** Media rows of this kind. */
20214
+ rows: number().int(),
20215
+ /** Bytes on disk held by those rows. */
20216
+ bytes: number().int()
20217
+ });
20218
+ /**
20219
+ * The media footprint broken down by KIND — the axis a deletion decision
20220
+ * actually turns on.
20221
+ *
20222
+ * A byte total says how much there is; it cannot say what is safe to remove.
20223
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
20224
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
20225
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
20226
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
20227
+ * nothing else, so sizing a deletion means summing per kind.
20228
+ *
20229
+ * ## Why `unaccounted*` exists
20230
+ *
20231
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
20232
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
20233
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
20234
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
20235
+ * retired code path, or by a version that knew a kind this one does not) would
20236
+ * otherwise vanish from the total silently, and an operator would delete
20237
+ * against a denominator smaller than the disk.
20238
+ *
20239
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
20240
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
20241
+ */
20242
+ var EventMediaKindBreakdownSchema = object({
20243
+ /** Every media row in scope, from one unfiltered aggregate. */
20244
+ totalRows: number().int(),
20245
+ /** Every media byte in scope, from that same aggregate. */
20246
+ totalBytes: number().int(),
20247
+ /** Per-kind footprint, ordered by bytes descending. */
20248
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
20249
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
20250
+ unaccountedRows: number().int(),
20251
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
20252
+ unaccountedBytes: number().int()
20253
+ });
20083
20254
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
20084
20255
  var EventPruneCountsSchema = object({
20085
20256
  motion: number().int(),
@@ -20234,7 +20405,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20234
20405
  until: number().optional(),
20235
20406
  kinds: array(string()).optional(),
20236
20407
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20237
- }), 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({
20238
20409
  deviceId: number(),
20239
20410
  since: number(),
20240
20411
  until: number(),
@@ -20283,6 +20454,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20283
20454
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
20284
20455
  kind: "query",
20285
20456
  auth: "admin"
20457
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
20458
+ kind: "query",
20459
+ auth: "admin"
20286
20460
  }), method(object({
20287
20461
  olderThanMs: number(),
20288
20462
  reason: OpsLogReasonSchema.optional()
@@ -22234,6 +22408,20 @@ method(object({
22234
22408
  error: string().optional()
22235
22409
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
22236
22410
  providerId: string(),
22411
+ /**
22412
+ * The location this config is an UNSAVED edit of, when there is one.
22413
+ *
22414
+ * `listLocations` replaces every declared secret with the redaction
22415
+ * sentinel, so the edit modal's form state holds the sentinel for any
22416
+ * credential the operator did not retype — and posting that here
22417
+ * without a way to resolve it makes the provider try to authenticate
22418
+ * as `__camstack_redacted__` and report the operator's own working
22419
+ * password as wrong. Given this id, the orchestrator restores each
22420
+ * sentinel from the stored config (same rule as `upsertLocation`)
22421
+ * before dispatching. Omitted by the "Add location" wizard, where
22422
+ * every value was typed just now and nothing is stored yet.
22423
+ */
22424
+ locationId: string().optional(),
22237
22425
  config: record(string(), unknown())
22238
22426
  }), object({
22239
22427
  ok: boolean(),
@@ -29584,6 +29772,9 @@ method(object({
29584
29772
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29585
29773
  kind: "query",
29586
29774
  auth: "admin"
29775
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29776
+ kind: "mutation",
29777
+ auth: "admin"
29587
29778
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29588
29779
  kind: "mutation",
29589
29780
  auth: "admin"
@@ -29975,6 +30166,25 @@ var SceneMonitorStatusSchema = object({
29975
30166
  monitors: array(SceneMonitorSchema),
29976
30167
  lastFetchedAt: number()
29977
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
+ });
29978
30188
  var sceneMonitorCapability = {
29979
30189
  name: "scene-monitor",
29980
30190
  scope: "device",
@@ -29984,6 +30194,22 @@ var sceneMonitorCapability = {
29984
30194
  deviceTypes: [DeviceType.Camera],
29985
30195
  methods: {
29986
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()),
29987
30213
  createScene: method(object({
29988
30214
  deviceId: number(),
29989
30215
  label: string(),
@@ -31819,6 +32045,27 @@ var CameraOccupancySnapshotSchema = object({
31819
32045
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31820
32046
  });
31821
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
+ /**
31822
32069
  * Time-series resolution. The history methods return one bucket per
31823
32070
  * step over the requested range. Smaller resolutions cost more
31824
32071
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31875,6 +32122,20 @@ var zoneAnalyticsCapability = {
31875
32122
  * (no inference result emitted since boot or since binding was
31876
32123
  * activated). */
31877
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()),
31878
32139
  /** Time-series object count inside one zone. `className` optional —
31879
32140
  * omit to count every class in the zone. */
31880
32141
  getZoneHistory: method(object({
@@ -36272,6 +36533,12 @@ Object.freeze({
36272
36533
  addonId: null,
36273
36534
  access: "view"
36274
36535
  },
36536
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
36537
+ capName: "pipeline-analytics",
36538
+ capScope: "device",
36539
+ addonId: null,
36540
+ access: "view"
36541
+ },
36275
36542
  "pipelineAnalytics.getEventStoreFootprint": {
36276
36543
  capName: "pipeline-analytics",
36277
36544
  capScope: "device",
@@ -36290,6 +36557,12 @@ Object.freeze({
36290
36557
  addonId: null,
36291
36558
  access: "view"
36292
36559
  },
36560
+ "pipelineAnalytics.getKeyEventsBatch": {
36561
+ capName: "pipeline-analytics",
36562
+ capScope: "device",
36563
+ addonId: null,
36564
+ access: "view"
36565
+ },
36293
36566
  "pipelineAnalytics.getMotionEvents": {
36294
36567
  capName: "pipeline-analytics",
36295
36568
  capScope: "device",
@@ -37466,6 +37739,12 @@ Object.freeze({
37466
37739
  addonId: null,
37467
37740
  access: "view"
37468
37741
  },
37742
+ "recording.reconcileLedgerAgainstDisk": {
37743
+ capName: "recording",
37744
+ capScope: "system",
37745
+ addonId: null,
37746
+ access: "create"
37747
+ },
37469
37748
  "recording.refreshStorageLocationsForMigration": {
37470
37749
  capName: "recording",
37471
37750
  capScope: "system",
@@ -37592,6 +37871,12 @@ Object.freeze({
37592
37871
  addonId: null,
37593
37872
  access: "view"
37594
37873
  },
37874
+ "sceneMonitor.listScenesBatch": {
37875
+ capName: "scene-monitor",
37876
+ capScope: "device",
37877
+ addonId: null,
37878
+ access: "view"
37879
+ },
37595
37880
  "sceneMonitor.recheckNow": {
37596
37881
  capName: "scene-monitor",
37597
37882
  capScope: "device",
@@ -38948,6 +39233,12 @@ Object.freeze({
38948
39233
  addonId: null,
38949
39234
  access: "view"
38950
39235
  },
39236
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39237
+ capName: "zone-analytics",
39238
+ capScope: "device",
39239
+ addonId: null,
39240
+ access: "view"
39241
+ },
38951
39242
  "zoneAnalytics.getUnzonedHistory": {
38952
39243
  capName: "zone-analytics",
38953
39244
  capScope: "device",
@@ -39937,6 +40228,11 @@ Object.freeze({
39937
40228
  form: "single",
39938
40229
  optional: false
39939
40230
  }],
40231
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
40232
+ name: "deviceId",
40233
+ form: "single",
40234
+ optional: true
40235
+ }],
39940
40236
  "pipelineAnalytics.getGroup": [{
39941
40237
  name: "deviceId",
39942
40238
  form: "single",
@@ -39947,6 +40243,11 @@ Object.freeze({
39947
40243
  form: "single",
39948
40244
  optional: false
39949
40245
  }],
40246
+ "pipelineAnalytics.getKeyEventsBatch": [{
40247
+ name: "deviceIds",
40248
+ form: "array",
40249
+ optional: false
40250
+ }],
39950
40251
  "pipelineAnalytics.getMotionEvents": [{
39951
40252
  name: "deviceId",
39952
40253
  form: "single",
@@ -40387,6 +40688,11 @@ Object.freeze({
40387
40688
  form: "single",
40388
40689
  optional: false
40389
40690
  }],
40691
+ "recording.reconcileLedgerAgainstDisk": [{
40692
+ name: "deviceId",
40693
+ form: "single",
40694
+ optional: true
40695
+ }],
40390
40696
  "recording.relocateFootage": [{
40391
40697
  name: "deviceId",
40392
40698
  form: "single",
@@ -40452,6 +40758,11 @@ Object.freeze({
40452
40758
  form: "single",
40453
40759
  optional: false
40454
40760
  }],
40761
+ "sceneMonitor.listScenesBatch": [{
40762
+ name: "deviceIds",
40763
+ form: "array",
40764
+ optional: false
40765
+ }],
40455
40766
  "sceneMonitor.recheckNow": [{
40456
40767
  name: "deviceId",
40457
40768
  form: "single",
@@ -40713,6 +41024,11 @@ Object.freeze({
40713
41024
  form: "single",
40714
41025
  optional: false
40715
41026
  }],
41027
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41028
+ name: "deviceIds",
41029
+ form: "array",
41030
+ optional: false
41031
+ }],
40716
41032
  "zoneAnalytics.getUnzonedHistory": [{
40717
41033
  name: "deviceId",
40718
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(),
@@ -20079,6 +20206,50 @@ var EventStoreFootprintSchema = object({
20079
20206
  totalBytes: number().int(),
20080
20207
  devices: array(EventStoreDeviceFootprintSchema).readonly()
20081
20208
  });
20209
+ /** Event-media footprint for one {@link MediaFileKind}. */
20210
+ var EventMediaKindFootprintSchema = object({
20211
+ kind: MediaFileKindEnum,
20212
+ /** Media rows of this kind. */
20213
+ rows: number().int(),
20214
+ /** Bytes on disk held by those rows. */
20215
+ bytes: number().int()
20216
+ });
20217
+ /**
20218
+ * The media footprint broken down by KIND — the axis a deletion decision
20219
+ * actually turns on.
20220
+ *
20221
+ * A byte total says how much there is; it cannot say what is safe to remove.
20222
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
20223
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
20224
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
20225
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
20226
+ * nothing else, so sizing a deletion means summing per kind.
20227
+ *
20228
+ * ## Why `unaccounted*` exists
20229
+ *
20230
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
20231
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
20232
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
20233
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
20234
+ * retired code path, or by a version that knew a kind this one does not) would
20235
+ * otherwise vanish from the total silently, and an operator would delete
20236
+ * against a denominator smaller than the disk.
20237
+ *
20238
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
20239
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
20240
+ */
20241
+ var EventMediaKindBreakdownSchema = object({
20242
+ /** Every media row in scope, from one unfiltered aggregate. */
20243
+ totalRows: number().int(),
20244
+ /** Every media byte in scope, from that same aggregate. */
20245
+ totalBytes: number().int(),
20246
+ /** Per-kind footprint, ordered by bytes descending. */
20247
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
20248
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
20249
+ unaccountedRows: number().int(),
20250
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
20251
+ unaccountedBytes: number().int()
20252
+ });
20082
20253
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
20083
20254
  var EventPruneCountsSchema = object({
20084
20255
  motion: number().int(),
@@ -20233,7 +20404,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20233
20404
  until: number().optional(),
20234
20405
  kinds: array(string()).optional(),
20235
20406
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20236
- }), 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({
20237
20408
  deviceId: number(),
20238
20409
  since: number(),
20239
20410
  until: number(),
@@ -20282,6 +20453,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20282
20453
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
20283
20454
  kind: "query",
20284
20455
  auth: "admin"
20456
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
20457
+ kind: "query",
20458
+ auth: "admin"
20285
20459
  }), method(object({
20286
20460
  olderThanMs: number(),
20287
20461
  reason: OpsLogReasonSchema.optional()
@@ -22233,6 +22407,20 @@ method(object({
22233
22407
  error: string().optional()
22234
22408
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
22235
22409
  providerId: string(),
22410
+ /**
22411
+ * The location this config is an UNSAVED edit of, when there is one.
22412
+ *
22413
+ * `listLocations` replaces every declared secret with the redaction
22414
+ * sentinel, so the edit modal's form state holds the sentinel for any
22415
+ * credential the operator did not retype — and posting that here
22416
+ * without a way to resolve it makes the provider try to authenticate
22417
+ * as `__camstack_redacted__` and report the operator's own working
22418
+ * password as wrong. Given this id, the orchestrator restores each
22419
+ * sentinel from the stored config (same rule as `upsertLocation`)
22420
+ * before dispatching. Omitted by the "Add location" wizard, where
22421
+ * every value was typed just now and nothing is stored yet.
22422
+ */
22423
+ locationId: string().optional(),
22236
22424
  config: record(string(), unknown())
22237
22425
  }), object({
22238
22426
  ok: boolean(),
@@ -29583,6 +29771,9 @@ method(object({
29583
29771
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29584
29772
  kind: "query",
29585
29773
  auth: "admin"
29774
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29775
+ kind: "mutation",
29776
+ auth: "admin"
29586
29777
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29587
29778
  kind: "mutation",
29588
29779
  auth: "admin"
@@ -29974,6 +30165,25 @@ var SceneMonitorStatusSchema = object({
29974
30165
  monitors: array(SceneMonitorSchema),
29975
30166
  lastFetchedAt: number()
29976
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
+ });
29977
30187
  var sceneMonitorCapability = {
29978
30188
  name: "scene-monitor",
29979
30189
  scope: "device",
@@ -29983,6 +30193,22 @@ var sceneMonitorCapability = {
29983
30193
  deviceTypes: [DeviceType.Camera],
29984
30194
  methods: {
29985
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()),
29986
30212
  createScene: method(object({
29987
30213
  deviceId: number(),
29988
30214
  label: string(),
@@ -31818,6 +32044,27 @@ var CameraOccupancySnapshotSchema = object({
31818
32044
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31819
32045
  });
31820
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
+ /**
31821
32068
  * Time-series resolution. The history methods return one bucket per
31822
32069
  * step over the requested range. Smaller resolutions cost more
31823
32070
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31874,6 +32121,20 @@ var zoneAnalyticsCapability = {
31874
32121
  * (no inference result emitted since boot or since binding was
31875
32122
  * activated). */
31876
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()),
31877
32138
  /** Time-series object count inside one zone. `className` optional —
31878
32139
  * omit to count every class in the zone. */
31879
32140
  getZoneHistory: method(object({
@@ -36271,6 +36532,12 @@ Object.freeze({
36271
36532
  addonId: null,
36272
36533
  access: "view"
36273
36534
  },
36535
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
36536
+ capName: "pipeline-analytics",
36537
+ capScope: "device",
36538
+ addonId: null,
36539
+ access: "view"
36540
+ },
36274
36541
  "pipelineAnalytics.getEventStoreFootprint": {
36275
36542
  capName: "pipeline-analytics",
36276
36543
  capScope: "device",
@@ -36289,6 +36556,12 @@ Object.freeze({
36289
36556
  addonId: null,
36290
36557
  access: "view"
36291
36558
  },
36559
+ "pipelineAnalytics.getKeyEventsBatch": {
36560
+ capName: "pipeline-analytics",
36561
+ capScope: "device",
36562
+ addonId: null,
36563
+ access: "view"
36564
+ },
36292
36565
  "pipelineAnalytics.getMotionEvents": {
36293
36566
  capName: "pipeline-analytics",
36294
36567
  capScope: "device",
@@ -37465,6 +37738,12 @@ Object.freeze({
37465
37738
  addonId: null,
37466
37739
  access: "view"
37467
37740
  },
37741
+ "recording.reconcileLedgerAgainstDisk": {
37742
+ capName: "recording",
37743
+ capScope: "system",
37744
+ addonId: null,
37745
+ access: "create"
37746
+ },
37468
37747
  "recording.refreshStorageLocationsForMigration": {
37469
37748
  capName: "recording",
37470
37749
  capScope: "system",
@@ -37591,6 +37870,12 @@ Object.freeze({
37591
37870
  addonId: null,
37592
37871
  access: "view"
37593
37872
  },
37873
+ "sceneMonitor.listScenesBatch": {
37874
+ capName: "scene-monitor",
37875
+ capScope: "device",
37876
+ addonId: null,
37877
+ access: "view"
37878
+ },
37594
37879
  "sceneMonitor.recheckNow": {
37595
37880
  capName: "scene-monitor",
37596
37881
  capScope: "device",
@@ -38947,6 +39232,12 @@ Object.freeze({
38947
39232
  addonId: null,
38948
39233
  access: "view"
38949
39234
  },
39235
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39236
+ capName: "zone-analytics",
39237
+ capScope: "device",
39238
+ addonId: null,
39239
+ access: "view"
39240
+ },
38950
39241
  "zoneAnalytics.getUnzonedHistory": {
38951
39242
  capName: "zone-analytics",
38952
39243
  capScope: "device",
@@ -39936,6 +40227,11 @@ Object.freeze({
39936
40227
  form: "single",
39937
40228
  optional: false
39938
40229
  }],
40230
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
40231
+ name: "deviceId",
40232
+ form: "single",
40233
+ optional: true
40234
+ }],
39939
40235
  "pipelineAnalytics.getGroup": [{
39940
40236
  name: "deviceId",
39941
40237
  form: "single",
@@ -39946,6 +40242,11 @@ Object.freeze({
39946
40242
  form: "single",
39947
40243
  optional: false
39948
40244
  }],
40245
+ "pipelineAnalytics.getKeyEventsBatch": [{
40246
+ name: "deviceIds",
40247
+ form: "array",
40248
+ optional: false
40249
+ }],
39949
40250
  "pipelineAnalytics.getMotionEvents": [{
39950
40251
  name: "deviceId",
39951
40252
  form: "single",
@@ -40386,6 +40687,11 @@ Object.freeze({
40386
40687
  form: "single",
40387
40688
  optional: false
40388
40689
  }],
40690
+ "recording.reconcileLedgerAgainstDisk": [{
40691
+ name: "deviceId",
40692
+ form: "single",
40693
+ optional: true
40694
+ }],
40389
40695
  "recording.relocateFootage": [{
40390
40696
  name: "deviceId",
40391
40697
  form: "single",
@@ -40451,6 +40757,11 @@ Object.freeze({
40451
40757
  form: "single",
40452
40758
  optional: false
40453
40759
  }],
40760
+ "sceneMonitor.listScenesBatch": [{
40761
+ name: "deviceIds",
40762
+ form: "array",
40763
+ optional: false
40764
+ }],
40454
40765
  "sceneMonitor.recheckNow": [{
40455
40766
  name: "deviceId",
40456
40767
  form: "single",
@@ -40712,6 +41023,11 @@ Object.freeze({
40712
41023
  form: "single",
40713
41024
  optional: false
40714
41025
  }],
41026
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41027
+ name: "deviceIds",
41028
+ form: "array",
41029
+ optional: false
41030
+ }],
40715
41031
  "zoneAnalytics.getUnzonedHistory": [{
40716
41032
  name: "deviceId",
40717
41033
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-rademacher",
3
- "version": "0.2.46",
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",