@camstack/addon-provider-gree 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
@@ -8021,6 +8021,20 @@ var RelocateJobSchema = object({
8021
8021
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8022
8022
  */
8023
8023
  rowsReconciled: number().int().nonnegative().optional(),
8024
+ /**
8025
+ * Rows this run FORGOT because the file they name is not on disk.
8026
+ *
8027
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8028
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8029
+ * and the durable row is dropped through the same channel eviction uses. It
8030
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8031
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8032
+ * the same failure as one that quietly skips them (D295).
8033
+ *
8034
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8035
+ * ledger claimed 5.65 GB of footage that no longer existed.
8036
+ */
8037
+ rowsForgotten: number().int().nonnegative().optional(),
8024
8038
  startedAt: number(),
8025
8039
  finishedAt: number().nullable(),
8026
8040
  error: string().nullable()
@@ -8384,6 +8398,91 @@ var RelocateResidueSchema = object({
8384
8398
  segments: number().int().nonnegative(),
8385
8399
  bytes: number().int().nonnegative()
8386
8400
  }).nullable();
8401
+ /**
8402
+ * Ask one location whether its durable hour rows describe the disk — the walk
8403
+ * (D319).
8404
+ *
8405
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8406
+ * missing tool is the question, and the dry run is how they sanity-check the
8407
+ * destructive run before authorising it.
8408
+ */
8409
+ var LedgerWalkInputSchema = object({
8410
+ locationId: string().min(1),
8411
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8412
+ apply: boolean().optional(),
8413
+ /** Narrow to one camera. */
8414
+ deviceId: number().int().positive().optional(),
8415
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8416
+ profiles: array(string().min(1)).optional()
8417
+ });
8418
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8419
+ var LedgerWalkRefusalSchema = _enum([
8420
+ "location-unknown",
8421
+ "source-writable",
8422
+ "no-ledger",
8423
+ "archive-unreadable",
8424
+ "anchor-absent",
8425
+ "anchor-unreadable",
8426
+ "anchor-moved"
8427
+ ]);
8428
+ _enum([
8429
+ "live-tail",
8430
+ "listing-error",
8431
+ "path-mismatch",
8432
+ "durable-refused"
8433
+ ]);
8434
+ /** Every skip reason, always present, always a number — so a reason that never
8435
+ * fired reports as zero rather than absent and the report shape is constant
8436
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8437
+ var LedgerWalkSkipCountsSchema = object({
8438
+ "live-tail": number().int().nonnegative(),
8439
+ "listing-error": number().int().nonnegative(),
8440
+ "path-mismatch": number().int().nonnegative(),
8441
+ "durable-refused": number().int().nonnegative()
8442
+ });
8443
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8444
+ var LedgerWalkDeviceReportSchema = object({
8445
+ deviceId: number().int(),
8446
+ hoursWalked: number().int().nonnegative(),
8447
+ hoursMissing: number().int().nonnegative(),
8448
+ ghostSegments: number().int().nonnegative(),
8449
+ ghostBytes: number().int().nonnegative(),
8450
+ forgottenSegments: number().int().nonnegative(),
8451
+ orphanFiles: number().int().nonnegative()
8452
+ });
8453
+ /**
8454
+ * What one walk claimed, listed, found and (only when armed) forgot.
8455
+ *
8456
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8457
+ * walk that saw a fraction of the location is visible in its own report rather
8458
+ * than in the absence of one.
8459
+ */
8460
+ var LedgerWalkReportSchema = object({
8461
+ locationId: string(),
8462
+ applied: boolean(),
8463
+ refused: LedgerWalkRefusalSchema.nullable(),
8464
+ archiveSegments: number().int().nonnegative().nullable(),
8465
+ archiveBytes: number().int().nonnegative().nullable(),
8466
+ hoursClaimed: number().int().nonnegative(),
8467
+ hoursWalked: number().int().nonnegative(),
8468
+ hoursMissing: number().int().nonnegative(),
8469
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8470
+ listings: number().int().nonnegative(),
8471
+ segmentsClaimed: number().int().nonnegative(),
8472
+ ghostSegments: number().int().nonnegative(),
8473
+ ghostBytes: number().int().nonnegative(),
8474
+ ghostHoursWhole: number().int().nonnegative(),
8475
+ forgottenSegments: number().int().nonnegative(),
8476
+ forgottenBytes: number().int().nonnegative(),
8477
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8478
+ orphanFiles: number().int().nonnegative(),
8479
+ orphanSample: array(string()).readonly(),
8480
+ hoursSkipped: number().int().nonnegative(),
8481
+ skippedByReason: LedgerWalkSkipCountsSchema,
8482
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8483
+ bounded: boolean(),
8484
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8485
+ });
8387
8486
  /** How many rows a media pass would still act on against a given target — the
8388
8487
  * media lane's denominator AND its residue, from ONE derivation so the two can
8389
8488
  * never disagree. `null` = the count could not be taken. */
@@ -18999,13 +19098,15 @@ var ListGroupsPageSchema = object({
18999
19098
  groups: array(AnalyticsGroupRecordSchema).readonly(),
19000
19099
  nextCursor: string().nullable()
19001
19100
  });
19101
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19102
+ var KEY_EVENTS_MAX_LIMIT = 200;
19002
19103
  var KeyEventQueryInput = object({
19003
19104
  deviceId: number(),
19004
19105
  /** Window lower bound (track firstSeen ≥ since). */
19005
19106
  since: number(),
19006
19107
  /** Window upper bound (track firstSeen ≤ until). */
19007
19108
  until: number(),
19008
- limit: number().int().min(1).max(200).default(50),
19109
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19009
19110
  /** Drop tracks scoring below this importance. */
19010
19111
  minImportance: number().min(0).max(1).optional(),
19011
19112
  /** Restrict to a single class (e.g. 'person'). */
@@ -19027,6 +19128,32 @@ var KeyEventSchema = object({
19027
19128
  ...TrackFlagFields,
19028
19129
  ...TrackRetrainFields
19029
19130
  });
19131
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19132
+ var KeyEventBatchQueryInput = object({
19133
+ deviceIds: array(number()).min(1).max(200),
19134
+ since: number(),
19135
+ until: number(),
19136
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19137
+ * across the set, which would let a busy camera starve a quiet one of its
19138
+ * rows and change what the merged feed contains. */
19139
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19140
+ minImportance: number().min(0).max(1).optional(),
19141
+ classFilter: string().optional()
19142
+ });
19143
+ /**
19144
+ * One camera's key events in a batch answer.
19145
+ *
19146
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19147
+ * error rather than throwing, so a camera whose store read failed and one with
19148
+ * no events in the window were ALREADY indistinguishable per camera — the
19149
+ * batch does not make that worse, and the row keeps the deviceId the single
19150
+ * method's output never carried (the caller used to stamp it from the fan-out
19151
+ * key, which only worked because there was one query per camera).
19152
+ */
19153
+ var KeyEventsForDeviceSchema = object({
19154
+ deviceId: number(),
19155
+ events: array(KeyEventSchema).readonly()
19156
+ });
19030
19157
  object({
19031
19158
  trackId: string(),
19032
19159
  className: string(),
@@ -19098,6 +19225,50 @@ var EventStoreFootprintSchema = object({
19098
19225
  totalBytes: number().int(),
19099
19226
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19100
19227
  });
19228
+ /** Event-media footprint for one {@link MediaFileKind}. */
19229
+ var EventMediaKindFootprintSchema = object({
19230
+ kind: MediaFileKindEnum,
19231
+ /** Media rows of this kind. */
19232
+ rows: number().int(),
19233
+ /** Bytes on disk held by those rows. */
19234
+ bytes: number().int()
19235
+ });
19236
+ /**
19237
+ * The media footprint broken down by KIND — the axis a deletion decision
19238
+ * actually turns on.
19239
+ *
19240
+ * A byte total says how much there is; it cannot say what is safe to remove.
19241
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19242
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19243
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19244
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19245
+ * nothing else, so sizing a deletion means summing per kind.
19246
+ *
19247
+ * ## Why `unaccounted*` exists
19248
+ *
19249
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19250
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19251
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19252
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19253
+ * retired code path, or by a version that knew a kind this one does not) would
19254
+ * otherwise vanish from the total silently, and an operator would delete
19255
+ * against a denominator smaller than the disk.
19256
+ *
19257
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19258
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19259
+ */
19260
+ var EventMediaKindBreakdownSchema = object({
19261
+ /** Every media row in scope, from one unfiltered aggregate. */
19262
+ totalRows: number().int(),
19263
+ /** Every media byte in scope, from that same aggregate. */
19264
+ totalBytes: number().int(),
19265
+ /** Per-kind footprint, ordered by bytes descending. */
19266
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19267
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19268
+ unaccountedRows: number().int(),
19269
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19270
+ unaccountedBytes: number().int()
19271
+ });
19101
19272
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19102
19273
  var EventPruneCountsSchema = object({
19103
19274
  motion: number().int(),
@@ -19252,7 +19423,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19252
19423
  until: number().optional(),
19253
19424
  kinds: array(string()).optional(),
19254
19425
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19255
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19426
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19256
19427
  deviceId: number(),
19257
19428
  since: number(),
19258
19429
  until: number(),
@@ -19301,6 +19472,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19301
19472
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19302
19473
  kind: "query",
19303
19474
  auth: "admin"
19475
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19476
+ kind: "query",
19477
+ auth: "admin"
19304
19478
  }), method(object({
19305
19479
  olderThanMs: number(),
19306
19480
  reason: OpsLogReasonSchema.optional()
@@ -21252,6 +21426,20 @@ method(object({
21252
21426
  error: string().optional()
21253
21427
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21254
21428
  providerId: string(),
21429
+ /**
21430
+ * The location this config is an UNSAVED edit of, when there is one.
21431
+ *
21432
+ * `listLocations` replaces every declared secret with the redaction
21433
+ * sentinel, so the edit modal's form state holds the sentinel for any
21434
+ * credential the operator did not retype — and posting that here
21435
+ * without a way to resolve it makes the provider try to authenticate
21436
+ * as `__camstack_redacted__` and report the operator's own working
21437
+ * password as wrong. Given this id, the orchestrator restores each
21438
+ * sentinel from the stored config (same rule as `upsertLocation`)
21439
+ * before dispatching. Omitted by the "Add location" wizard, where
21440
+ * every value was typed just now and nothing is stored yet.
21441
+ */
21442
+ locationId: string().optional(),
21255
21443
  config: record(string(), unknown())
21256
21444
  }), object({
21257
21445
  ok: boolean(),
@@ -28602,6 +28790,9 @@ method(object({
28602
28790
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28603
28791
  kind: "query",
28604
28792
  auth: "admin"
28793
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
28794
+ kind: "mutation",
28795
+ auth: "admin"
28605
28796
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28606
28797
  kind: "mutation",
28607
28798
  auth: "admin"
@@ -28993,6 +29184,25 @@ var SceneMonitorStatusSchema = object({
28993
29184
  monitors: array(SceneMonitorSchema),
28994
29185
  lastFetchedAt: number()
28995
29186
  });
29187
+ /**
29188
+ * One camera's row in a `listScenesBatch` answer.
29189
+ *
29190
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29191
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29192
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29193
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29194
+ * configured. Fanned out per camera the difference was visible — one query
29195
+ * errored while the others resolved — and a batch that returned only the rows
29196
+ * it managed would have destroyed it, silently, by making an unreachable camera
29197
+ * indistinguishable from one that answered `monitors: []`.
29198
+ *
29199
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29200
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29201
+ */
29202
+ var SceneMonitorStatusForDeviceSchema = object({
29203
+ deviceId: number(),
29204
+ status: SceneMonitorStatusSchema.nullable()
29205
+ });
28996
29206
  var sceneMonitorCapability = {
28997
29207
  name: "scene-monitor",
28998
29208
  scope: "device",
@@ -29002,6 +29212,22 @@ var sceneMonitorCapability = {
29002
29212
  deviceTypes: [DeviceType.Camera],
29003
29213
  methods: {
29004
29214
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29215
+ /**
29216
+ * The same answer, for a SET of cameras, in one round trip.
29217
+ *
29218
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29219
+ * poll behind the push slice. Fanned out client-side that was one query
29220
+ * per camera — 29 round trips through the browser, the hub and the
29221
+ * post-analysis runner every 30 seconds to read an in-memory map the
29222
+ * owner had already merged. The work is unchanged (`statusFor` per
29223
+ * device, all in-process at the owner); what collapses is the transport.
29224
+ *
29225
+ * A camera that cannot answer still gets a row, with `status: null` —
29226
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29227
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29228
+ * tell which two are missing, or that any are.
29229
+ */
29230
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29005
29231
  createScene: method(object({
29006
29232
  deviceId: number(),
29007
29233
  label: string(),
@@ -30837,6 +31063,27 @@ var CameraOccupancySnapshotSchema = object({
30837
31063
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
30838
31064
  });
30839
31065
  /**
31066
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31067
+ *
31068
+ * THREE outcomes, and the single-camera method could only express two of them
31069
+ * because `snapshot: null` was already spoken for:
31070
+ *
31071
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31072
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31073
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31074
+ * - `read: 'unreadable'` — the owner could not answer for this
31075
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31076
+ *
31077
+ * Collapsing the last two is the failure this field exists to prevent: a
31078
+ * hydration that threw would otherwise render as an empty Stationary section,
31079
+ * which is a definite claim about a camera nobody could read.
31080
+ */
31081
+ var CameraOccupancySnapshotForDeviceSchema = object({
31082
+ deviceId: number(),
31083
+ read: _enum(["read", "unreadable"]),
31084
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31085
+ });
31086
+ /**
30840
31087
  * Time-series resolution. The history methods return one bucket per
30841
31088
  * step over the requested range. Smaller resolutions cost more
30842
31089
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -30893,6 +31140,20 @@ var zoneAnalyticsCapability = {
30893
31140
  * (no inference result emitted since boot or since binding was
30894
31141
  * activated). */
30895
31142
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31143
+ /**
31144
+ * The same snapshot, for a SET of cameras, in one round trip.
31145
+ *
31146
+ * The Events page's Stationary section polls this every 15s for every
31147
+ * selected camera. Fanned out client-side that is one query per camera to
31148
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31149
+ * costs everything. Batched, N transports become one and the per-device
31150
+ * work is unchanged.
31151
+ *
31152
+ * Every requested deviceId gets a row, tagged `read` — see
31153
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31154
+ * not answer for is `'unreadable'`, never an empty reading.
31155
+ */
31156
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
30896
31157
  /** Time-series object count inside one zone. `className` optional —
30897
31158
  * omit to count every class in the zone. */
30898
31159
  getZoneHistory: method(object({
@@ -35290,6 +35551,12 @@ Object.freeze({
35290
35551
  addonId: null,
35291
35552
  access: "view"
35292
35553
  },
35554
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
35555
+ capName: "pipeline-analytics",
35556
+ capScope: "device",
35557
+ addonId: null,
35558
+ access: "view"
35559
+ },
35293
35560
  "pipelineAnalytics.getEventStoreFootprint": {
35294
35561
  capName: "pipeline-analytics",
35295
35562
  capScope: "device",
@@ -35308,6 +35575,12 @@ Object.freeze({
35308
35575
  addonId: null,
35309
35576
  access: "view"
35310
35577
  },
35578
+ "pipelineAnalytics.getKeyEventsBatch": {
35579
+ capName: "pipeline-analytics",
35580
+ capScope: "device",
35581
+ addonId: null,
35582
+ access: "view"
35583
+ },
35311
35584
  "pipelineAnalytics.getMotionEvents": {
35312
35585
  capName: "pipeline-analytics",
35313
35586
  capScope: "device",
@@ -36484,6 +36757,12 @@ Object.freeze({
36484
36757
  addonId: null,
36485
36758
  access: "view"
36486
36759
  },
36760
+ "recording.reconcileLedgerAgainstDisk": {
36761
+ capName: "recording",
36762
+ capScope: "system",
36763
+ addonId: null,
36764
+ access: "create"
36765
+ },
36487
36766
  "recording.refreshStorageLocationsForMigration": {
36488
36767
  capName: "recording",
36489
36768
  capScope: "system",
@@ -36610,6 +36889,12 @@ Object.freeze({
36610
36889
  addonId: null,
36611
36890
  access: "view"
36612
36891
  },
36892
+ "sceneMonitor.listScenesBatch": {
36893
+ capName: "scene-monitor",
36894
+ capScope: "device",
36895
+ addonId: null,
36896
+ access: "view"
36897
+ },
36613
36898
  "sceneMonitor.recheckNow": {
36614
36899
  capName: "scene-monitor",
36615
36900
  capScope: "device",
@@ -37966,6 +38251,12 @@ Object.freeze({
37966
38251
  addonId: null,
37967
38252
  access: "view"
37968
38253
  },
38254
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38255
+ capName: "zone-analytics",
38256
+ capScope: "device",
38257
+ addonId: null,
38258
+ access: "view"
38259
+ },
37969
38260
  "zoneAnalytics.getUnzonedHistory": {
37970
38261
  capName: "zone-analytics",
37971
38262
  capScope: "device",
@@ -38955,6 +39246,11 @@ Object.freeze({
38955
39246
  form: "single",
38956
39247
  optional: false
38957
39248
  }],
39249
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
39250
+ name: "deviceId",
39251
+ form: "single",
39252
+ optional: true
39253
+ }],
38958
39254
  "pipelineAnalytics.getGroup": [{
38959
39255
  name: "deviceId",
38960
39256
  form: "single",
@@ -38965,6 +39261,11 @@ Object.freeze({
38965
39261
  form: "single",
38966
39262
  optional: false
38967
39263
  }],
39264
+ "pipelineAnalytics.getKeyEventsBatch": [{
39265
+ name: "deviceIds",
39266
+ form: "array",
39267
+ optional: false
39268
+ }],
38968
39269
  "pipelineAnalytics.getMotionEvents": [{
38969
39270
  name: "deviceId",
38970
39271
  form: "single",
@@ -39405,6 +39706,11 @@ Object.freeze({
39405
39706
  form: "single",
39406
39707
  optional: false
39407
39708
  }],
39709
+ "recording.reconcileLedgerAgainstDisk": [{
39710
+ name: "deviceId",
39711
+ form: "single",
39712
+ optional: true
39713
+ }],
39408
39714
  "recording.relocateFootage": [{
39409
39715
  name: "deviceId",
39410
39716
  form: "single",
@@ -39470,6 +39776,11 @@ Object.freeze({
39470
39776
  form: "single",
39471
39777
  optional: false
39472
39778
  }],
39779
+ "sceneMonitor.listScenesBatch": [{
39780
+ name: "deviceIds",
39781
+ form: "array",
39782
+ optional: false
39783
+ }],
39473
39784
  "sceneMonitor.recheckNow": [{
39474
39785
  name: "deviceId",
39475
39786
  form: "single",
@@ -39731,6 +40042,11 @@ Object.freeze({
39731
40042
  form: "single",
39732
40043
  optional: false
39733
40044
  }],
40045
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40046
+ name: "deviceIds",
40047
+ form: "array",
40048
+ optional: false
40049
+ }],
39734
40050
  "zoneAnalytics.getUnzonedHistory": [{
39735
40051
  name: "deviceId",
39736
40052
  form: "single",
package/dist/addon.mjs CHANGED
@@ -8020,6 +8020,20 @@ var RelocateJobSchema = object({
8020
8020
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8021
8021
  */
8022
8022
  rowsReconciled: number().int().nonnegative().optional(),
8023
+ /**
8024
+ * Rows this run FORGOT because the file they name is not on disk.
8025
+ *
8026
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8027
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8028
+ * and the durable row is dropped through the same channel eviction uses. It
8029
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8030
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8031
+ * the same failure as one that quietly skips them (D295).
8032
+ *
8033
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8034
+ * ledger claimed 5.65 GB of footage that no longer existed.
8035
+ */
8036
+ rowsForgotten: number().int().nonnegative().optional(),
8023
8037
  startedAt: number(),
8024
8038
  finishedAt: number().nullable(),
8025
8039
  error: string().nullable()
@@ -8383,6 +8397,91 @@ var RelocateResidueSchema = object({
8383
8397
  segments: number().int().nonnegative(),
8384
8398
  bytes: number().int().nonnegative()
8385
8399
  }).nullable();
8400
+ /**
8401
+ * Ask one location whether its durable hour rows describe the disk — the walk
8402
+ * (D319).
8403
+ *
8404
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8405
+ * missing tool is the question, and the dry run is how they sanity-check the
8406
+ * destructive run before authorising it.
8407
+ */
8408
+ var LedgerWalkInputSchema = object({
8409
+ locationId: string().min(1),
8410
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8411
+ apply: boolean().optional(),
8412
+ /** Narrow to one camera. */
8413
+ deviceId: number().int().positive().optional(),
8414
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8415
+ profiles: array(string().min(1)).optional()
8416
+ });
8417
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8418
+ var LedgerWalkRefusalSchema = _enum([
8419
+ "location-unknown",
8420
+ "source-writable",
8421
+ "no-ledger",
8422
+ "archive-unreadable",
8423
+ "anchor-absent",
8424
+ "anchor-unreadable",
8425
+ "anchor-moved"
8426
+ ]);
8427
+ _enum([
8428
+ "live-tail",
8429
+ "listing-error",
8430
+ "path-mismatch",
8431
+ "durable-refused"
8432
+ ]);
8433
+ /** Every skip reason, always present, always a number — so a reason that never
8434
+ * fired reports as zero rather than absent and the report shape is constant
8435
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8436
+ var LedgerWalkSkipCountsSchema = object({
8437
+ "live-tail": number().int().nonnegative(),
8438
+ "listing-error": number().int().nonnegative(),
8439
+ "path-mismatch": number().int().nonnegative(),
8440
+ "durable-refused": number().int().nonnegative()
8441
+ });
8442
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8443
+ var LedgerWalkDeviceReportSchema = object({
8444
+ deviceId: number().int(),
8445
+ hoursWalked: number().int().nonnegative(),
8446
+ hoursMissing: number().int().nonnegative(),
8447
+ ghostSegments: number().int().nonnegative(),
8448
+ ghostBytes: number().int().nonnegative(),
8449
+ forgottenSegments: number().int().nonnegative(),
8450
+ orphanFiles: number().int().nonnegative()
8451
+ });
8452
+ /**
8453
+ * What one walk claimed, listed, found and (only when armed) forgot.
8454
+ *
8455
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8456
+ * walk that saw a fraction of the location is visible in its own report rather
8457
+ * than in the absence of one.
8458
+ */
8459
+ var LedgerWalkReportSchema = object({
8460
+ locationId: string(),
8461
+ applied: boolean(),
8462
+ refused: LedgerWalkRefusalSchema.nullable(),
8463
+ archiveSegments: number().int().nonnegative().nullable(),
8464
+ archiveBytes: number().int().nonnegative().nullable(),
8465
+ hoursClaimed: number().int().nonnegative(),
8466
+ hoursWalked: number().int().nonnegative(),
8467
+ hoursMissing: number().int().nonnegative(),
8468
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8469
+ listings: number().int().nonnegative(),
8470
+ segmentsClaimed: number().int().nonnegative(),
8471
+ ghostSegments: number().int().nonnegative(),
8472
+ ghostBytes: number().int().nonnegative(),
8473
+ ghostHoursWhole: number().int().nonnegative(),
8474
+ forgottenSegments: number().int().nonnegative(),
8475
+ forgottenBytes: number().int().nonnegative(),
8476
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8477
+ orphanFiles: number().int().nonnegative(),
8478
+ orphanSample: array(string()).readonly(),
8479
+ hoursSkipped: number().int().nonnegative(),
8480
+ skippedByReason: LedgerWalkSkipCountsSchema,
8481
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8482
+ bounded: boolean(),
8483
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8484
+ });
8386
8485
  /** How many rows a media pass would still act on against a given target — the
8387
8486
  * media lane's denominator AND its residue, from ONE derivation so the two can
8388
8487
  * never disagree. `null` = the count could not be taken. */
@@ -18998,13 +19097,15 @@ var ListGroupsPageSchema = object({
18998
19097
  groups: array(AnalyticsGroupRecordSchema).readonly(),
18999
19098
  nextCursor: string().nullable()
19000
19099
  });
19100
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19101
+ var KEY_EVENTS_MAX_LIMIT = 200;
19001
19102
  var KeyEventQueryInput = object({
19002
19103
  deviceId: number(),
19003
19104
  /** Window lower bound (track firstSeen ≥ since). */
19004
19105
  since: number(),
19005
19106
  /** Window upper bound (track firstSeen ≤ until). */
19006
19107
  until: number(),
19007
- limit: number().int().min(1).max(200).default(50),
19108
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19008
19109
  /** Drop tracks scoring below this importance. */
19009
19110
  minImportance: number().min(0).max(1).optional(),
19010
19111
  /** Restrict to a single class (e.g. 'person'). */
@@ -19026,6 +19127,32 @@ var KeyEventSchema = object({
19026
19127
  ...TrackFlagFields,
19027
19128
  ...TrackRetrainFields
19028
19129
  });
19130
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19131
+ var KeyEventBatchQueryInput = object({
19132
+ deviceIds: array(number()).min(1).max(200),
19133
+ since: number(),
19134
+ until: number(),
19135
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19136
+ * across the set, which would let a busy camera starve a quiet one of its
19137
+ * rows and change what the merged feed contains. */
19138
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19139
+ minImportance: number().min(0).max(1).optional(),
19140
+ classFilter: string().optional()
19141
+ });
19142
+ /**
19143
+ * One camera's key events in a batch answer.
19144
+ *
19145
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19146
+ * error rather than throwing, so a camera whose store read failed and one with
19147
+ * no events in the window were ALREADY indistinguishable per camera — the
19148
+ * batch does not make that worse, and the row keeps the deviceId the single
19149
+ * method's output never carried (the caller used to stamp it from the fan-out
19150
+ * key, which only worked because there was one query per camera).
19151
+ */
19152
+ var KeyEventsForDeviceSchema = object({
19153
+ deviceId: number(),
19154
+ events: array(KeyEventSchema).readonly()
19155
+ });
19029
19156
  object({
19030
19157
  trackId: string(),
19031
19158
  className: string(),
@@ -19097,6 +19224,50 @@ var EventStoreFootprintSchema = object({
19097
19224
  totalBytes: number().int(),
19098
19225
  devices: array(EventStoreDeviceFootprintSchema).readonly()
19099
19226
  });
19227
+ /** Event-media footprint for one {@link MediaFileKind}. */
19228
+ var EventMediaKindFootprintSchema = object({
19229
+ kind: MediaFileKindEnum,
19230
+ /** Media rows of this kind. */
19231
+ rows: number().int(),
19232
+ /** Bytes on disk held by those rows. */
19233
+ bytes: number().int()
19234
+ });
19235
+ /**
19236
+ * The media footprint broken down by KIND — the axis a deletion decision
19237
+ * actually turns on.
19238
+ *
19239
+ * A byte total says how much there is; it cannot say what is safe to remove.
19240
+ * The deletable set (the periodic `snapshot` filmstrip, the surplus per-edge
19241
+ * motion stills) and the keep set (`firstFrame`, rolling `lastFrame`,
19242
+ * `thumbnail`/`thumbnailSmall`, `keyFrame`/`keyFrameSmall`, the face/plate
19243
+ * buffers, gallery media, the CLIP `crop`) are distinguished by `kind` and by
19244
+ * nothing else, so sizing a deletion means summing per kind.
19245
+ *
19246
+ * ## Why `unaccounted*` exists
19247
+ *
19248
+ * `kinds` is enumerated from {@link MediaFileKindEnum} — the closed set the
19249
+ * writers use — and summed one kind at a time. `totalRows` / `totalBytes` come
19250
+ * from a SEPARATE unfiltered aggregate over the same rows, never from adding
19251
+ * `kinds` up. A row whose stored `kind` is not in the enum (written by a
19252
+ * retired code path, or by a version that knew a kind this one does not) would
19253
+ * otherwise vanish from the total silently, and an operator would delete
19254
+ * against a denominator smaller than the disk.
19255
+ *
19256
+ * `unaccountedRows` / `unaccountedBytes` are the difference. They are normally
19257
+ * zero; a non-zero value is a real finding and must be shown, not rounded away.
19258
+ */
19259
+ var EventMediaKindBreakdownSchema = object({
19260
+ /** Every media row in scope, from one unfiltered aggregate. */
19261
+ totalRows: number().int(),
19262
+ /** Every media byte in scope, from that same aggregate. */
19263
+ totalBytes: number().int(),
19264
+ /** Per-kind footprint, ordered by bytes descending. */
19265
+ kinds: array(EventMediaKindFootprintSchema).readonly(),
19266
+ /** `totalRows` minus the summed `kinds` rows — see the schema note. */
19267
+ unaccountedRows: number().int(),
19268
+ /** `totalBytes` minus the summed `kinds` bytes — see the schema note. */
19269
+ unaccountedBytes: number().int()
19270
+ });
19100
19271
  /** Per-kind counts returned by the event-prune / device-delete mutations. */
19101
19272
  var EventPruneCountsSchema = object({
19102
19273
  motion: number().int(),
@@ -19251,7 +19422,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19251
19422
  until: number().optional(),
19252
19423
  kinds: array(string()).optional(),
19253
19424
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19254
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19425
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19255
19426
  deviceId: number(),
19256
19427
  since: number(),
19257
19428
  until: number(),
@@ -19300,6 +19471,9 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19300
19471
  }), TrackFlagsSchema, { kind: "mutation" }), method(object({}), EventStoreFootprintSchema, {
19301
19472
  kind: "query",
19302
19473
  auth: "admin"
19474
+ }), method(object({ deviceId: number().int().optional() }), EventMediaKindBreakdownSchema, {
19475
+ kind: "query",
19476
+ auth: "admin"
19303
19477
  }), method(object({
19304
19478
  olderThanMs: number(),
19305
19479
  reason: OpsLogReasonSchema.optional()
@@ -21251,6 +21425,20 @@ method(object({
21251
21425
  error: string().optional()
21252
21426
  }), { auth: "admin" }), method(_void(), array(ProviderListEntrySchema).readonly()), method(object({
21253
21427
  providerId: string(),
21428
+ /**
21429
+ * The location this config is an UNSAVED edit of, when there is one.
21430
+ *
21431
+ * `listLocations` replaces every declared secret with the redaction
21432
+ * sentinel, so the edit modal's form state holds the sentinel for any
21433
+ * credential the operator did not retype — and posting that here
21434
+ * without a way to resolve it makes the provider try to authenticate
21435
+ * as `__camstack_redacted__` and report the operator's own working
21436
+ * password as wrong. Given this id, the orchestrator restores each
21437
+ * sentinel from the stored config (same rule as `upsertLocation`)
21438
+ * before dispatching. Omitted by the "Add location" wizard, where
21439
+ * every value was typed just now and nothing is stored yet.
21440
+ */
21441
+ locationId: string().optional(),
21254
21442
  config: record(string(), unknown())
21255
21443
  }), object({
21256
21444
  ok: boolean(),
@@ -28601,6 +28789,9 @@ method(object({
28601
28789
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28602
28790
  kind: "query",
28603
28791
  auth: "admin"
28792
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
28793
+ kind: "mutation",
28794
+ auth: "admin"
28604
28795
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28605
28796
  kind: "mutation",
28606
28797
  auth: "admin"
@@ -28992,6 +29183,25 @@ var SceneMonitorStatusSchema = object({
28992
29183
  monitors: array(SceneMonitorSchema),
28993
29184
  lastFetchedAt: number()
28994
29185
  });
29186
+ /**
29187
+ * One camera's row in a `listScenesBatch` answer.
29188
+ *
29189
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29190
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29191
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29192
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29193
+ * configured. Fanned out per camera the difference was visible — one query
29194
+ * errored while the others resolved — and a batch that returned only the rows
29195
+ * it managed would have destroyed it, silently, by making an unreachable camera
29196
+ * indistinguishable from one that answered `monitors: []`.
29197
+ *
29198
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29199
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29200
+ */
29201
+ var SceneMonitorStatusForDeviceSchema = object({
29202
+ deviceId: number(),
29203
+ status: SceneMonitorStatusSchema.nullable()
29204
+ });
28995
29205
  var sceneMonitorCapability = {
28996
29206
  name: "scene-monitor",
28997
29207
  scope: "device",
@@ -29001,6 +29211,22 @@ var sceneMonitorCapability = {
29001
29211
  deviceTypes: [DeviceType.Camera],
29002
29212
  methods: {
29003
29213
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29214
+ /**
29215
+ * The same answer, for a SET of cameras, in one round trip.
29216
+ *
29217
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29218
+ * poll behind the push slice. Fanned out client-side that was one query
29219
+ * per camera — 29 round trips through the browser, the hub and the
29220
+ * post-analysis runner every 30 seconds to read an in-memory map the
29221
+ * owner had already merged. The work is unchanged (`statusFor` per
29222
+ * device, all in-process at the owner); what collapses is the transport.
29223
+ *
29224
+ * A camera that cannot answer still gets a row, with `status: null` —
29225
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29226
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29227
+ * tell which two are missing, or that any are.
29228
+ */
29229
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29004
29230
  createScene: method(object({
29005
29231
  deviceId: number(),
29006
29232
  label: string(),
@@ -30836,6 +31062,27 @@ var CameraOccupancySnapshotSchema = object({
30836
31062
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
30837
31063
  });
30838
31064
  /**
31065
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31066
+ *
31067
+ * THREE outcomes, and the single-camera method could only express two of them
31068
+ * because `snapshot: null` was already spoken for:
31069
+ *
31070
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31071
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31072
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31073
+ * - `read: 'unreadable'` — the owner could not answer for this
31074
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31075
+ *
31076
+ * Collapsing the last two is the failure this field exists to prevent: a
31077
+ * hydration that threw would otherwise render as an empty Stationary section,
31078
+ * which is a definite claim about a camera nobody could read.
31079
+ */
31080
+ var CameraOccupancySnapshotForDeviceSchema = object({
31081
+ deviceId: number(),
31082
+ read: _enum(["read", "unreadable"]),
31083
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31084
+ });
31085
+ /**
30839
31086
  * Time-series resolution. The history methods return one bucket per
30840
31087
  * step over the requested range. Smaller resolutions cost more
30841
31088
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -30892,6 +31139,20 @@ var zoneAnalyticsCapability = {
30892
31139
  * (no inference result emitted since boot or since binding was
30893
31140
  * activated). */
30894
31141
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31142
+ /**
31143
+ * The same snapshot, for a SET of cameras, in one round trip.
31144
+ *
31145
+ * The Events page's Stationary section polls this every 15s for every
31146
+ * selected camera. Fanned out client-side that is one query per camera to
31147
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31148
+ * costs everything. Batched, N transports become one and the per-device
31149
+ * work is unchanged.
31150
+ *
31151
+ * Every requested deviceId gets a row, tagged `read` — see
31152
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31153
+ * not answer for is `'unreadable'`, never an empty reading.
31154
+ */
31155
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
30895
31156
  /** Time-series object count inside one zone. `className` optional —
30896
31157
  * omit to count every class in the zone. */
30897
31158
  getZoneHistory: method(object({
@@ -35289,6 +35550,12 @@ Object.freeze({
35289
35550
  addonId: null,
35290
35551
  access: "view"
35291
35552
  },
35553
+ "pipelineAnalytics.getEventMediaFootprintByKind": {
35554
+ capName: "pipeline-analytics",
35555
+ capScope: "device",
35556
+ addonId: null,
35557
+ access: "view"
35558
+ },
35292
35559
  "pipelineAnalytics.getEventStoreFootprint": {
35293
35560
  capName: "pipeline-analytics",
35294
35561
  capScope: "device",
@@ -35307,6 +35574,12 @@ Object.freeze({
35307
35574
  addonId: null,
35308
35575
  access: "view"
35309
35576
  },
35577
+ "pipelineAnalytics.getKeyEventsBatch": {
35578
+ capName: "pipeline-analytics",
35579
+ capScope: "device",
35580
+ addonId: null,
35581
+ access: "view"
35582
+ },
35310
35583
  "pipelineAnalytics.getMotionEvents": {
35311
35584
  capName: "pipeline-analytics",
35312
35585
  capScope: "device",
@@ -36483,6 +36756,12 @@ Object.freeze({
36483
36756
  addonId: null,
36484
36757
  access: "view"
36485
36758
  },
36759
+ "recording.reconcileLedgerAgainstDisk": {
36760
+ capName: "recording",
36761
+ capScope: "system",
36762
+ addonId: null,
36763
+ access: "create"
36764
+ },
36486
36765
  "recording.refreshStorageLocationsForMigration": {
36487
36766
  capName: "recording",
36488
36767
  capScope: "system",
@@ -36609,6 +36888,12 @@ Object.freeze({
36609
36888
  addonId: null,
36610
36889
  access: "view"
36611
36890
  },
36891
+ "sceneMonitor.listScenesBatch": {
36892
+ capName: "scene-monitor",
36893
+ capScope: "device",
36894
+ addonId: null,
36895
+ access: "view"
36896
+ },
36612
36897
  "sceneMonitor.recheckNow": {
36613
36898
  capName: "scene-monitor",
36614
36899
  capScope: "device",
@@ -37965,6 +38250,12 @@ Object.freeze({
37965
38250
  addonId: null,
37966
38251
  access: "view"
37967
38252
  },
38253
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38254
+ capName: "zone-analytics",
38255
+ capScope: "device",
38256
+ addonId: null,
38257
+ access: "view"
38258
+ },
37968
38259
  "zoneAnalytics.getUnzonedHistory": {
37969
38260
  capName: "zone-analytics",
37970
38261
  capScope: "device",
@@ -38954,6 +39245,11 @@ Object.freeze({
38954
39245
  form: "single",
38955
39246
  optional: false
38956
39247
  }],
39248
+ "pipelineAnalytics.getEventMediaFootprintByKind": [{
39249
+ name: "deviceId",
39250
+ form: "single",
39251
+ optional: true
39252
+ }],
38957
39253
  "pipelineAnalytics.getGroup": [{
38958
39254
  name: "deviceId",
38959
39255
  form: "single",
@@ -38964,6 +39260,11 @@ Object.freeze({
38964
39260
  form: "single",
38965
39261
  optional: false
38966
39262
  }],
39263
+ "pipelineAnalytics.getKeyEventsBatch": [{
39264
+ name: "deviceIds",
39265
+ form: "array",
39266
+ optional: false
39267
+ }],
38967
39268
  "pipelineAnalytics.getMotionEvents": [{
38968
39269
  name: "deviceId",
38969
39270
  form: "single",
@@ -39404,6 +39705,11 @@ Object.freeze({
39404
39705
  form: "single",
39405
39706
  optional: false
39406
39707
  }],
39708
+ "recording.reconcileLedgerAgainstDisk": [{
39709
+ name: "deviceId",
39710
+ form: "single",
39711
+ optional: true
39712
+ }],
39407
39713
  "recording.relocateFootage": [{
39408
39714
  name: "deviceId",
39409
39715
  form: "single",
@@ -39469,6 +39775,11 @@ Object.freeze({
39469
39775
  form: "single",
39470
39776
  optional: false
39471
39777
  }],
39778
+ "sceneMonitor.listScenesBatch": [{
39779
+ name: "deviceIds",
39780
+ form: "array",
39781
+ optional: false
39782
+ }],
39472
39783
  "sceneMonitor.recheckNow": [{
39473
39784
  name: "deviceId",
39474
39785
  form: "single",
@@ -39730,6 +40041,11 @@ Object.freeze({
39730
40041
  form: "single",
39731
40042
  optional: false
39732
40043
  }],
40044
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40045
+ name: "deviceIds",
40046
+ form: "array",
40047
+ optional: false
40048
+ }],
39733
40049
  "zoneAnalytics.getUnzonedHistory": [{
39734
40050
  name: "deviceId",
39735
40051
  form: "single",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-provider-gree",
3
- "version": "0.2.46",
3
+ "version": "0.2.48",
4
4
  "description": "Gree air-conditioner device-provider addon for CamStack — wraps the @apocaliss92/nodegree local-UDP client (LAN discovery + AES control), exposing climate-control and fan-control",
5
5
  "keywords": [
6
6
  "camstack",