@camstack/addon-provider-tuya 0.2.48 → 0.2.50

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.
Files changed (3) hide show
  1. package/dist/addon.js +478 -73
  2. package/dist/addon.mjs +478 -73
  3. package/package.json +1 -1
package/dist/addon.mjs CHANGED
@@ -8837,6 +8837,20 @@ var RelocateJobSchema = object({
8837
8837
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8838
8838
  */
8839
8839
  rowsReconciled: number().int().nonnegative().optional(),
8840
+ /**
8841
+ * Rows this run FORGOT because the file they name is not on disk.
8842
+ *
8843
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8844
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8845
+ * and the durable row is dropped through the same channel eviction uses. It
8846
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8847
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8848
+ * the same failure as one that quietly skips them (D295).
8849
+ *
8850
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8851
+ * ledger claimed 5.65 GB of footage that no longer existed.
8852
+ */
8853
+ rowsForgotten: number().int().nonnegative().optional(),
8840
8854
  startedAt: number(),
8841
8855
  finishedAt: number().nullable(),
8842
8856
  error: string().nullable()
@@ -8901,6 +8915,13 @@ var MediaRelocateModeSchema = _enum([
8901
8915
  ]);
8902
8916
  var RelocateMediaInputSchema = object({
8903
8917
  toLocationId: string(),
8918
+ /**
8919
+ * Restrict the pass to rows currently on this location. Omitted / `'*'` =
8920
+ * every row that is not already on `toLocationId` (the historical
8921
+ * behaviour). A named source is what a from→to migration needs: without it
8922
+ * "move events off disk 2" also emptied disk 1.
8923
+ */
8924
+ fromLocationId: string().optional(),
8904
8925
  throttleMbps: number().min(1).max(1e3).optional(),
8905
8926
  /** Omitted = `move`, the pre-existing behaviour. */
8906
8927
  mode: MediaRelocateModeSchema.optional()
@@ -8968,6 +8989,19 @@ var StorageMigrationDestinationsSchema = object({
8968
8989
  galleryMedia: string().min(1).optional()
8969
8990
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
8970
8991
  /**
8992
+ * Optional named source per class. Omitted = the class's current default
8993
+ * (the historical behaviour). A named source that is NOT the default is a
8994
+ * drain of that disk: bytes move, the default stays, and the source is
8995
+ * disabled when the move finishes.
8996
+ */
8997
+ var StorageMigrationSourcesSchema = object({
8998
+ recordings: string().min(1).optional(),
8999
+ recordingsLow: string().min(1).optional(),
9000
+ eventMedia: string().min(1).optional(),
9001
+ backups: string().min(1).optional(),
9002
+ galleryMedia: string().min(1).optional()
9003
+ }).optional();
9004
+ /**
8971
9005
  * How a migration sequences the cutover against the byte move.
8972
9006
  *
8973
9007
  * - `blocking` — the historical order: pause, move every byte, repoint,
@@ -8989,6 +9023,8 @@ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
8989
9023
  /** Shared input for planning and starting an orchestrated storage migration. */
8990
9024
  var StorageMigrationInputSchema = object({
8991
9025
  destinations: StorageMigrationDestinationsSchema,
9026
+ /** Omitted = each class's current default. */
9027
+ sources: StorageMigrationSourcesSchema,
8992
9028
  throttleMbps: number().min(1).max(1e3).optional(),
8993
9029
  /** Omitted = `blocking`, which stays the default. */
8994
9030
  mode: StorageMigrationModeSchema.optional()
@@ -9068,6 +9104,13 @@ var StorageMigrationMoveSchema = object({
9068
9104
  storageClass: StorageMigrationClassSchema,
9069
9105
  fromLocationId: string(),
9070
9106
  toLocationId: string(),
9107
+ /**
9108
+ * True when `from` was NOT the class default at plan time. The move still
9109
+ * copies bytes, but the default is left alone and the source is disabled
9110
+ * once the copy verifies. Absent on jobs planned before this field existed
9111
+ * — those jobs always repointed, which is `false`.
9112
+ */
9113
+ freezeSource: boolean().optional(),
9071
9114
  moverJobId: string().nullable(),
9072
9115
  state: RelocateJobStateSchema.nullable(),
9073
9116
  error: string().nullable(),
@@ -9081,6 +9124,7 @@ var StorageMigrationJobSchema = object({
9081
9124
  * can tell a seconds-long cutover from a thirty-hour one. */
9082
9125
  mode: StorageMigrationModeSchema,
9083
9126
  destinations: StorageMigrationDestinationsSchema,
9127
+ sources: StorageMigrationSourcesSchema,
9084
9128
  throttleMbps: number(),
9085
9129
  moves: array(StorageMigrationMoveSchema),
9086
9130
  pauseLeaseId: string().nullable(),
@@ -9106,6 +9150,7 @@ var StorageMigrationFindingSchema = object({
9106
9150
  });
9107
9151
  var StorageMigrationPlanSchema = object({
9108
9152
  destinations: StorageMigrationDestinationsSchema,
9153
+ sources: StorageMigrationSourcesSchema,
9109
9154
  /** The mode this plan was built for. A plan is only valid for its mode: the
9110
9155
  * `eventMedia` seal gate and the single-cardinality refusal both depend on
9111
9156
  * it. */
@@ -9113,7 +9158,8 @@ var StorageMigrationPlanSchema = object({
9113
9158
  moves: array(object({
9114
9159
  storageClass: StorageMigrationClassSchema,
9115
9160
  fromLocationId: string(),
9116
- toLocationId: string()
9161
+ toLocationId: string(),
9162
+ freezeSource: boolean().optional()
9117
9163
  })),
9118
9164
  findings: array(StorageMigrationFindingSchema)
9119
9165
  });
@@ -9200,16 +9246,142 @@ var RelocateResidueSchema = object({
9200
9246
  segments: number().int().nonnegative(),
9201
9247
  bytes: number().int().nonnegative()
9202
9248
  }).nullable();
9249
+ /**
9250
+ * Ask one location whether its durable hour rows describe the disk — the walk
9251
+ * (D319).
9252
+ *
9253
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
9254
+ * missing tool is the question, and the dry run is how they sanity-check the
9255
+ * destructive run before authorising it.
9256
+ */
9257
+ var LedgerWalkInputSchema = object({
9258
+ locationId: string().min(1),
9259
+ /** Forget the confirmed-absent rows, rather than only counting them. */
9260
+ apply: boolean().optional(),
9261
+ /** Narrow to one camera. */
9262
+ deviceId: number().int().positive().optional(),
9263
+ /** Narrow to these recording profiles; empty/absent = every profile. */
9264
+ profiles: array(string().min(1)).optional()
9265
+ });
9266
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
9267
+ var LedgerWalkRefusalSchema = _enum([
9268
+ "location-unknown",
9269
+ "source-writable",
9270
+ "no-ledger",
9271
+ "archive-unreadable",
9272
+ "anchor-absent",
9273
+ "anchor-unreadable",
9274
+ "anchor-moved"
9275
+ ]);
9276
+ _enum([
9277
+ "live-tail",
9278
+ "listing-error",
9279
+ "path-mismatch",
9280
+ "durable-refused"
9281
+ ]);
9282
+ /** Every skip reason, always present, always a number — so a reason that never
9283
+ * fired reports as zero rather than absent and the report shape is constant
9284
+ * between passes. Spelled out rather than `z.record` for exactly that. */
9285
+ var LedgerWalkSkipCountsSchema = object({
9286
+ "live-tail": number().int().nonnegative(),
9287
+ "listing-error": number().int().nonnegative(),
9288
+ "path-mismatch": number().int().nonnegative(),
9289
+ "durable-refused": number().int().nonnegative()
9290
+ });
9291
+ /** One camera's share of a walk, so a report names cameras and not rows. */
9292
+ var LedgerWalkDeviceReportSchema = object({
9293
+ deviceId: number().int(),
9294
+ hoursWalked: number().int().nonnegative(),
9295
+ hoursMissing: number().int().nonnegative(),
9296
+ ghostSegments: number().int().nonnegative(),
9297
+ ghostBytes: number().int().nonnegative(),
9298
+ forgottenSegments: number().int().nonnegative(),
9299
+ orphanFiles: number().int().nonnegative()
9300
+ });
9301
+ /**
9302
+ * What one walk claimed, listed, found and (only when armed) forgot.
9303
+ *
9304
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
9305
+ * walk that saw a fraction of the location is visible in its own report rather
9306
+ * than in the absence of one.
9307
+ */
9308
+ var LedgerWalkReportSchema = object({
9309
+ locationId: string(),
9310
+ applied: boolean(),
9311
+ refused: LedgerWalkRefusalSchema.nullable(),
9312
+ archiveSegments: number().int().nonnegative().nullable(),
9313
+ archiveBytes: number().int().nonnegative().nullable(),
9314
+ hoursClaimed: number().int().nonnegative(),
9315
+ hoursWalked: number().int().nonnegative(),
9316
+ hoursMissing: number().int().nonnegative(),
9317
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
9318
+ listings: number().int().nonnegative(),
9319
+ segmentsClaimed: number().int().nonnegative(),
9320
+ ghostSegments: number().int().nonnegative(),
9321
+ ghostBytes: number().int().nonnegative(),
9322
+ ghostHoursWhole: number().int().nonnegative(),
9323
+ forgottenSegments: number().int().nonnegative(),
9324
+ forgottenBytes: number().int().nonnegative(),
9325
+ /** Files under a claimed hour that no durable row names. Never deleted. */
9326
+ orphanFiles: number().int().nonnegative(),
9327
+ orphanSample: array(string()).readonly(),
9328
+ hoursSkipped: number().int().nonnegative(),
9329
+ skippedByReason: LedgerWalkSkipCountsSchema,
9330
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
9331
+ bounded: boolean(),
9332
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
9333
+ });
9203
9334
  /** How many rows a media pass would still act on against a given target — the
9204
9335
  * media lane's denominator AND its residue, from ONE derivation so the two can
9205
9336
  * never disagree. `null` = the count could not be taken. */
9206
9337
  var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
9207
9338
  var RelocatableMediaCountInputSchema = object({
9208
9339
  toLocationId: string().min(1),
9340
+ /** Restrict the count to one source; omitted / `'*'` = every non-target row. */
9341
+ fromLocationId: string().optional(),
9209
9342
  /** Omitted = `move`. */
9210
9343
  mode: MediaRelocateModeSchema.optional()
9211
9344
  });
9212
9345
  /**
9346
+ * Operator cleanup of leftover analytics rows, optional debug media, and
9347
+ * ghost ledger entries on frozen footage locations.
9348
+ *
9349
+ * A pass is tens of minutes on a standing backlog. The hub method RETURNS
9350
+ * `{ jobId }` immediately; progress is `cleanupStatus`. Awaiting the work
9351
+ * is how `addons.custom` hit the 60 s UDS deadline while reclaim continued
9352
+ * with no operator-visible status.
9353
+ */
9354
+ var StorageCleanupPhaseSchema = _enum([
9355
+ "orphans",
9356
+ "debug-media",
9357
+ "ghost-ledger",
9358
+ "done",
9359
+ "failed",
9360
+ "cancelled"
9361
+ ]);
9362
+ var StorageCleanupInputSchema = object({
9363
+ /** Also walk motion stills / track filmstrips. Off by default. */
9364
+ includeDebugMedia: boolean().optional() });
9365
+ var StorageCleanupJobSchema = object({
9366
+ jobId: string(),
9367
+ phase: StorageCleanupPhaseSchema,
9368
+ includeDebugMedia: boolean(),
9369
+ orphansReclaimed: number().int().nonnegative(),
9370
+ orphanBytesReclaimed: number().int().nonnegative(),
9371
+ debugMediaReclaimed: number().int().nonnegative(),
9372
+ debugMediaBytesReclaimed: number().int().nonnegative(),
9373
+ ghostsForgotten: number().int().nonnegative(),
9374
+ ghostBytesForgotten: number().int().nonnegative(),
9375
+ /** Short operator-facing line: current collection, pass, or location. */
9376
+ detail: string().nullable(),
9377
+ cancelRequested: boolean(),
9378
+ startedAt: number(),
9379
+ updatedAt: number(),
9380
+ finishedAt: number().nullable(),
9381
+ error: string().nullable()
9382
+ });
9383
+ var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
9384
+ /**
9213
9385
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
9214
9386
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
9215
9387
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -9237,11 +9409,10 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
9237
9409
  * The default location for a type uses `id === <type>:default` by
9238
9410
  * convention (the bare type ref like `'backups'` resolves to it).
9239
9411
  *
9240
- * `isSystem: true` marks a location as orchestrator-seeded and
9241
- * undeletable. The bootstrap-installed defaults (one per type) carry
9242
- * this flag; operator-added locations don't. Editing the config of
9243
- * a system location is allowed (path migration, provider swap) but
9244
- * deleting it is rejected at the cap level.
9412
+ * `isSystem` is a legacy persisted flag. Seed still creates the initial
9413
+ * `<type>:default` locations; the flag is no longer a lock, a badge, or a
9414
+ * prune selector. New writes leave it false. Deletion is gated on uniqueness
9415
+ * / last-enabled, not on this bit.
9245
9416
  */
9246
9417
  var StorageLocationSchema = object({
9247
9418
  id: string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
@@ -13784,6 +13955,12 @@ method(object({
13784
13955
  }), _void(), {
13785
13956
  kind: "mutation",
13786
13957
  auth: "admin"
13958
+ }), method(object({
13959
+ from: string(),
13960
+ to: string()
13961
+ }), object({ moved: number() }), {
13962
+ kind: "mutation",
13963
+ auth: "admin"
13787
13964
  }), method(object({
13788
13965
  deviceId: number(),
13789
13966
  disabled: boolean()
@@ -19792,53 +19969,15 @@ var RecentTracksPageSchema = object({
19792
19969
  /** Cursor for the next page, or null when this page is the last. */
19793
19970
  nextCursor: string().nullable()
19794
19971
  });
19795
- var LIST_GROUPS_DEFAULT_LIMIT = 40;
19796
- var LIST_GROUPS_MAX_LIMIT = 100;
19797
- var AnalyticsGroupRecordSchema = object({
19798
- id: string(),
19799
- deviceId: number().int(),
19800
- openedAt: number().int(),
19801
- closedAt: number().int(),
19802
- timestamp: number().int(),
19803
- memberCount: number().int(),
19804
- memberTrackIds: array(string()).readonly(),
19805
- className: string(),
19806
- classes: array(string()).readonly(),
19807
- /** Relative event-media path, or null when the group has no picture yet. */
19808
- mediaUrl: string().nullable(),
19809
- singleton: boolean()
19810
- });
19811
- var AnalyticsGroupMemberSchema = object({
19812
- trackId: string(),
19813
- deviceId: number().int(),
19814
- className: string(),
19815
- firstSeen: number().int(),
19816
- lastSeen: number().int(),
19817
- mediaUrl: string().nullable()
19818
- });
19819
- var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
19820
- var ListGroupsQueryInput = object({
19821
- /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
19822
- deviceIds: array(number()),
19823
- /** Window lower bound on `closedAt` (inclusive). */
19824
- since: number().optional(),
19825
- /** Window upper bound on `openedAt` (inclusive). */
19826
- until: number().optional(),
19827
- limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
19828
- /** Opaque continuation cursor from a previous page's `nextCursor`. */
19829
- cursor: string().optional()
19830
- });
19831
- var ListGroupsPageSchema = object({
19832
- groups: array(AnalyticsGroupRecordSchema).readonly(),
19833
- nextCursor: string().nullable()
19834
- });
19972
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19973
+ var KEY_EVENTS_MAX_LIMIT = 200;
19835
19974
  var KeyEventQueryInput = object({
19836
19975
  deviceId: number(),
19837
19976
  /** Window lower bound (track firstSeen ≥ since). */
19838
19977
  since: number(),
19839
19978
  /** Window upper bound (track firstSeen ≤ until). */
19840
19979
  until: number(),
19841
- limit: number().int().min(1).max(200).default(50),
19980
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19842
19981
  /** Drop tracks scoring below this importance. */
19843
19982
  minImportance: number().min(0).max(1).optional(),
19844
19983
  /** Restrict to a single class (e.g. 'person'). */
@@ -19860,6 +19999,32 @@ var KeyEventSchema = object({
19860
19999
  ...TrackFlagFields,
19861
20000
  ...TrackRetrainFields
19862
20001
  });
20002
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
20003
+ var KeyEventBatchQueryInput = object({
20004
+ deviceIds: array(number()).min(1).max(200),
20005
+ since: number(),
20006
+ until: number(),
20007
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
20008
+ * across the set, which would let a busy camera starve a quiet one of its
20009
+ * rows and change what the merged feed contains. */
20010
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
20011
+ minImportance: number().min(0).max(1).optional(),
20012
+ classFilter: string().optional()
20013
+ });
20014
+ /**
20015
+ * One camera's key events in a batch answer.
20016
+ *
20017
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
20018
+ * error rather than throwing, so a camera whose store read failed and one with
20019
+ * no events in the window were ALREADY indistinguishable per camera — the
20020
+ * batch does not make that worse, and the row keeps the deviceId the single
20021
+ * method's output never carried (the caller used to stamp it from the fan-out
20022
+ * key, which only worked because there was one query per camera).
20023
+ */
20024
+ var KeyEventsForDeviceSchema = object({
20025
+ deviceId: number(),
20026
+ events: array(KeyEventSchema).readonly()
20027
+ });
19863
20028
  object({
19864
20029
  trackId: string(),
19865
20030
  className: string(),
@@ -19907,9 +20072,7 @@ var TrackCascadeCountsSchema = object({
19907
20072
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
19908
20073
  plates: number().int(),
19909
20074
  /** Per-track CLIP search vectors removed (best-effort). */
19910
- embeddings: number().int(),
19911
- /** Group membership + group rows removed with their last member (best-effort). */
19912
- groups: number().int()
20075
+ embeddings: number().int()
19913
20076
  });
19914
20077
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
19915
20078
  var DiskReconcileCountsSchema = object({
@@ -20079,6 +20242,47 @@ var RebuildStatusSchema = object({
20079
20242
  /** Present when the pass ended by throwing. */
20080
20243
  error: string().nullable()
20081
20244
  });
20245
+ /**
20246
+ * Acknowledgement that a debug-media reclaim STARTED.
20247
+ *
20248
+ * The pass is paced at ~2 MB/s over a standing 100 GB — tens of minutes — so
20249
+ * it runs detached and this returns immediately. Awaiting it is how the
20250
+ * `addons.custom` door hit the 60 s UDS deadline while the walk carried on
20251
+ * with no status. Poll {@link pipelineAnalyticsCapability.methods.getMediaReclaimStatus}.
20252
+ */
20253
+ var MediaReclaimStartResultSchema = object({
20254
+ started: boolean(),
20255
+ /** True when a pass was already running; the new request is ignored. */
20256
+ alreadyRunning: boolean()
20257
+ });
20258
+ var MediaReclaimInputSchema = object({
20259
+ mode: _enum(["report", "reclaim"]).default("report"),
20260
+ scopes: array(_enum(["motion-still", "track-filmstrip"])).min(1).optional(),
20261
+ deviceIds: array(number().int()).min(1).optional(),
20262
+ restart: boolean().optional(),
20263
+ pageSize: number().int().min(50).max(5e3).optional(),
20264
+ maxRowsPerDevice: number().int().min(1).max(5e5).optional(),
20265
+ maxReclaimPerDevice: number().int().min(1).max(5e5).optional(),
20266
+ maxBytesPerRun: number().int().min(1).optional(),
20267
+ budgetMinutes: number().int().min(1).max(720).optional(),
20268
+ throttleBytesPerSec: number().int().min(64 * 1024).optional(),
20269
+ graceMinutes: number().int().min(1).max(10080).optional()
20270
+ });
20271
+ var MediaReclaimStatusSchema = object({
20272
+ running: boolean(),
20273
+ mode: _enum(["report", "reclaim"]).nullable(),
20274
+ totalExamined: number(),
20275
+ totalEligible: number(),
20276
+ totalReclaimed: number(),
20277
+ totalBytesReclaimed: number(),
20278
+ totalRefused: number(),
20279
+ /** Device+scope windows finished in this pass. */
20280
+ devicesDone: number(),
20281
+ complete: boolean().nullable(),
20282
+ startedAtMs: number().nullable(),
20283
+ finishedAtMs: number().nullable(),
20284
+ error: string().nullable()
20285
+ });
20082
20286
  var ReplayFrameInputSchema = object({
20083
20287
  timestamp: number(),
20084
20288
  frame: PipelineRunResultBridge
@@ -20117,10 +20321,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20117
20321
  * stationary registry). Default false: the timeline lists passages,
20118
20322
  * not parking records (operator decision, 2026-08-15). */
20119
20323
  includeStationary: boolean().optional()
20120
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
20121
- deviceId: number(),
20122
- groupId: string().min(1)
20123
- }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20324
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
20124
20325
  kind: "mutation",
20125
20326
  auth: "admin"
20126
20327
  }), method(DeviceEventQueryInput, array(MotionEventSchema).readonly()), method(ObjectEventQueryInput, array(ObjectEventSchema).readonly()), method(DeviceEventQueryInput, array(AudioEventSchema).readonly()), method(object({ deviceId: number() }), array(EventKindDescriptorSchema).readonly()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(EventKindsForDeviceSchema).readonly()), method(object({
@@ -20129,7 +20330,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20129
20330
  until: number().optional(),
20130
20331
  kinds: array(string()).optional(),
20131
20332
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
20132
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20333
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
20133
20334
  deviceId: number(),
20134
20335
  since: number(),
20135
20336
  until: number(),
@@ -20220,6 +20421,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20220
20421
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20221
20422
  kind: "query",
20222
20423
  auth: "admin"
20424
+ }), method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
20425
+ kind: "mutation",
20426
+ auth: "admin"
20427
+ }), method(object({}), MediaReclaimStatusSchema, {
20428
+ kind: "query",
20429
+ auth: "admin"
20223
20430
  }), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
20224
20431
  kind: "query",
20225
20432
  auth: "admin"
@@ -22190,7 +22397,13 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
22190
22397
  }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
22191
22398
  kind: "mutation",
22192
22399
  auth: "admin"
22193
- });
22400
+ }), method(StorageCleanupInputSchema, object({ jobId: string() }), {
22401
+ kind: "mutation",
22402
+ auth: "admin"
22403
+ }), method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
22404
+ kind: "mutation",
22405
+ auth: "admin"
22406
+ }), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
22194
22407
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
22195
22408
  providerId: string().min(1),
22196
22409
  displayName: string().min(1),
@@ -29291,6 +29504,33 @@ var RecordingRebalanceInputSchema = object({
29291
29504
  minMoveGb: number().min(0).optional()
29292
29505
  });
29293
29506
  /**
29507
+ * Operator-facing placement of one camera onto a recordings location.
29508
+ *
29509
+ * `pinLocationId` is the operator instruction: a location id, or `null` for
29510
+ * Auto (the planner may move this camera). `locationId` is where high/mid
29511
+ * currently write — the plan, which may disagree with the pin when Auto.
29512
+ */
29513
+ var RecordingDevicePlacementSchema = object({
29514
+ deviceId: number().int(),
29515
+ profile: string(),
29516
+ locationId: string()
29517
+ });
29518
+ var RecordingDevicePinSchema = object({
29519
+ deviceId: number().int(),
29520
+ /** Recordings-class location this camera is pinned to. */
29521
+ locationId: string()
29522
+ });
29523
+ var RecordingPlacementViewSchema = object({
29524
+ assignments: array(RecordingDevicePlacementSchema),
29525
+ pins: array(RecordingDevicePinSchema),
29526
+ defaultLocations: record(string(), string())
29527
+ });
29528
+ var RecordingSetDevicePlacementInputSchema = object({
29529
+ deviceId: number().int(),
29530
+ /** `null` = Auto. Otherwise a `recordings:*` location id. */
29531
+ locationId: string().nullable()
29532
+ });
29533
+ /**
29294
29534
  * Result of locating footage at a wall-clock instant for one device/profile.
29295
29535
  * `segment` carries the covering segment's window; `gap` reports the forward
29296
29536
  * nearest covered edge (`null` past the end of footage / when none exists)
@@ -29504,6 +29744,9 @@ method(object({
29504
29744
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29505
29745
  kind: "query",
29506
29746
  auth: "admin"
29747
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29748
+ kind: "mutation",
29749
+ auth: "admin"
29507
29750
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29508
29751
  kind: "mutation",
29509
29752
  auth: "admin"
@@ -29513,6 +29756,12 @@ method(object({
29513
29756
  }), method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
29514
29757
  kind: "mutation",
29515
29758
  auth: "admin"
29759
+ }), method(object({}), RecordingPlacementViewSchema, {
29760
+ kind: "query",
29761
+ auth: "admin"
29762
+ }), method(RecordingSetDevicePlacementInputSchema, object({ ok: literal(true) }), {
29763
+ kind: "mutation",
29764
+ auth: "admin"
29516
29765
  });
29517
29766
  /**
29518
29767
  * `recording-export` cap — render a footage time range into a single downloadable
@@ -29895,6 +30144,25 @@ var SceneMonitorStatusSchema = object({
29895
30144
  monitors: array(SceneMonitorSchema),
29896
30145
  lastFetchedAt: number()
29897
30146
  });
30147
+ /**
30148
+ * One camera's row in a `listScenesBatch` answer.
30149
+ *
30150
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
30151
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
30152
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
30153
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
30154
+ * configured. Fanned out per camera the difference was visible — one query
30155
+ * errored while the others resolved — and a batch that returned only the rows
30156
+ * it managed would have destroyed it, silently, by making an unreachable camera
30157
+ * indistinguishable from one that answered `monitors: []`.
30158
+ *
30159
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
30160
+ * could not be read"; `status.monitors: []` means "read, and it has none".
30161
+ */
30162
+ var SceneMonitorStatusForDeviceSchema = object({
30163
+ deviceId: number(),
30164
+ status: SceneMonitorStatusSchema.nullable()
30165
+ });
29898
30166
  var sceneMonitorCapability = {
29899
30167
  name: "scene-monitor",
29900
30168
  scope: "device",
@@ -29904,6 +30172,22 @@ var sceneMonitorCapability = {
29904
30172
  deviceTypes: [DeviceType.Camera],
29905
30173
  methods: {
29906
30174
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
30175
+ /**
30176
+ * The same answer, for a SET of cameras, in one round trip.
30177
+ *
30178
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
30179
+ * poll behind the push slice. Fanned out client-side that was one query
30180
+ * per camera — 29 round trips through the browser, the hub and the
30181
+ * post-analysis runner every 30 seconds to read an in-memory map the
30182
+ * owner had already merged. The work is unchanged (`statusFor` per
30183
+ * device, all in-process at the owner); what collapses is the transport.
30184
+ *
30185
+ * A camera that cannot answer still gets a row, with `status: null` —
30186
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
30187
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
30188
+ * tell which two are missing, or that any are.
30189
+ */
30190
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29907
30191
  createScene: method(object({
29908
30192
  deviceId: number(),
29909
30193
  label: string(),
@@ -31739,6 +32023,27 @@ var CameraOccupancySnapshotSchema = object({
31739
32023
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31740
32024
  });
31741
32025
  /**
32026
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
32027
+ *
32028
+ * THREE outcomes, and the single-camera method could only express two of them
32029
+ * because `snapshot: null` was already spoken for:
32030
+ *
32031
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
32032
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
32033
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
32034
+ * - `read: 'unreadable'` — the owner could not answer for this
32035
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
32036
+ *
32037
+ * Collapsing the last two is the failure this field exists to prevent: a
32038
+ * hydration that threw would otherwise render as an empty Stationary section,
32039
+ * which is a definite claim about a camera nobody could read.
32040
+ */
32041
+ var CameraOccupancySnapshotForDeviceSchema = object({
32042
+ deviceId: number(),
32043
+ read: _enum(["read", "unreadable"]),
32044
+ snapshot: CameraOccupancySnapshotSchema.nullable()
32045
+ });
32046
+ /**
31742
32047
  * Time-series resolution. The history methods return one bucket per
31743
32048
  * step over the requested range. Smaller resolutions cost more
31744
32049
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31795,6 +32100,20 @@ var zoneAnalyticsCapability = {
31795
32100
  * (no inference result emitted since boot or since binding was
31796
32101
  * activated). */
31797
32102
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
32103
+ /**
32104
+ * The same snapshot, for a SET of cameras, in one round trip.
32105
+ *
32106
+ * The Events page's Stationary section polls this every 15s for every
32107
+ * selected camera. Fanned out client-side that is one query per camera to
32108
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
32109
+ * costs everything. Batched, N transports become one and the per-device
32110
+ * work is unchanged.
32111
+ *
32112
+ * Every requested deviceId gets a row, tagged `read` — see
32113
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
32114
+ * not answer for is `'unreadable'`, never an empty reading.
32115
+ */
32116
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31798
32117
  /** Time-series object count inside one zone. `className` optional —
31799
32118
  * omit to count every class in the zone. */
31800
32119
  getZoneHistory: method(object({
@@ -34512,6 +34831,12 @@ Object.freeze({
34512
34831
  addonId: null,
34513
34832
  access: "delete"
34514
34833
  },
34834
+ "deviceManager.renameLocation": {
34835
+ capName: "device-manager",
34836
+ capScope: "system",
34837
+ addonId: null,
34838
+ access: "create"
34839
+ },
34515
34840
  "deviceManager.runDeviceAction": {
34516
34841
  capName: "device-manager",
34517
34842
  capScope: "system",
@@ -36204,13 +36529,19 @@ Object.freeze({
36204
36529
  addonId: null,
36205
36530
  access: "view"
36206
36531
  },
36207
- "pipelineAnalytics.getGroup": {
36532
+ "pipelineAnalytics.getKeyEvents": {
36208
36533
  capName: "pipeline-analytics",
36209
36534
  capScope: "device",
36210
36535
  addonId: null,
36211
36536
  access: "view"
36212
36537
  },
36213
- "pipelineAnalytics.getKeyEvents": {
36538
+ "pipelineAnalytics.getKeyEventsBatch": {
36539
+ capName: "pipeline-analytics",
36540
+ capScope: "device",
36541
+ addonId: null,
36542
+ access: "view"
36543
+ },
36544
+ "pipelineAnalytics.getMediaReclaimStatus": {
36214
36545
  capName: "pipeline-analytics",
36215
36546
  capScope: "device",
36216
36547
  addonId: null,
@@ -36300,12 +36631,6 @@ Object.freeze({
36300
36631
  addonId: null,
36301
36632
  access: "view"
36302
36633
  },
36303
- "pipelineAnalytics.listGroups": {
36304
- capName: "pipeline-analytics",
36305
- capScope: "device",
36306
- addonId: null,
36307
- access: "view"
36308
- },
36309
36634
  "pipelineAnalytics.listOpsLog": {
36310
36635
  capName: "pipeline-analytics",
36311
36636
  capScope: "device",
@@ -36390,6 +36715,12 @@ Object.freeze({
36390
36715
  addonId: null,
36391
36716
  access: "create"
36392
36717
  },
36718
+ "pipelineAnalytics.reclaimDebugMedia": {
36719
+ capName: "pipeline-analytics",
36720
+ capScope: "device",
36721
+ addonId: null,
36722
+ access: "create"
36723
+ },
36393
36724
  "pipelineAnalytics.reconcileFromDisk": {
36394
36725
  capName: "pipeline-analytics",
36395
36726
  capScope: "device",
@@ -37314,6 +37645,12 @@ Object.freeze({
37314
37645
  addonId: null,
37315
37646
  access: "view"
37316
37647
  },
37648
+ "recording.getPlacement": {
37649
+ capName: "recording",
37650
+ capScope: "system",
37651
+ addonId: null,
37652
+ access: "view"
37653
+ },
37317
37654
  "recording.getPlaybackManifest": {
37318
37655
  capName: "recording",
37319
37656
  capScope: "system",
@@ -37392,6 +37729,12 @@ Object.freeze({
37392
37729
  addonId: null,
37393
37730
  access: "view"
37394
37731
  },
37732
+ "recording.reconcileLedgerAgainstDisk": {
37733
+ capName: "recording",
37734
+ capScope: "system",
37735
+ addonId: null,
37736
+ access: "create"
37737
+ },
37395
37738
  "recording.refreshStorageLocationsForMigration": {
37396
37739
  capName: "recording",
37397
37740
  capScope: "system",
@@ -37434,6 +37777,12 @@ Object.freeze({
37434
37777
  addonId: null,
37435
37778
  access: "create"
37436
37779
  },
37780
+ "recording.setDevicePlacement": {
37781
+ capName: "recording",
37782
+ capScope: "system",
37783
+ addonId: null,
37784
+ access: "create"
37785
+ },
37437
37786
  "recording.startStorageMigrationMove": {
37438
37787
  capName: "recording",
37439
37788
  capScope: "system",
@@ -37518,6 +37867,12 @@ Object.freeze({
37518
37867
  addonId: null,
37519
37868
  access: "view"
37520
37869
  },
37870
+ "sceneMonitor.listScenesBatch": {
37871
+ capName: "scene-monitor",
37872
+ capScope: "device",
37873
+ addonId: null,
37874
+ access: "view"
37875
+ },
37521
37876
  "sceneMonitor.recheckNow": {
37522
37877
  capName: "scene-monitor",
37523
37878
  capScope: "device",
@@ -37872,12 +38227,36 @@ Object.freeze({
37872
38227
  addonId: null,
37873
38228
  access: "create"
37874
38229
  },
38230
+ "storageMigration.cleanupCancel": {
38231
+ capName: "storage-migration",
38232
+ capScope: "system",
38233
+ addonId: null,
38234
+ access: "create"
38235
+ },
38236
+ "storageMigration.cleanupStart": {
38237
+ capName: "storage-migration",
38238
+ capScope: "system",
38239
+ addonId: null,
38240
+ access: "create"
38241
+ },
38242
+ "storageMigration.cleanupStatus": {
38243
+ capName: "storage-migration",
38244
+ capScope: "system",
38245
+ addonId: null,
38246
+ access: "view"
38247
+ },
37875
38248
  "storageMigration.drain": {
37876
38249
  capName: "storage-migration",
37877
38250
  capScope: "system",
37878
38251
  addonId: null,
37879
38252
  access: "create"
37880
38253
  },
38254
+ "storageMigration.history": {
38255
+ capName: "storage-migration",
38256
+ capScope: "system",
38257
+ addonId: null,
38258
+ access: "view"
38259
+ },
37881
38260
  "storageMigration.movers": {
37882
38261
  capName: "storage-migration",
37883
38262
  capScope: "system",
@@ -38874,6 +39253,12 @@ Object.freeze({
38874
39253
  addonId: null,
38875
39254
  access: "view"
38876
39255
  },
39256
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39257
+ capName: "zone-analytics",
39258
+ capScope: "device",
39259
+ addonId: null,
39260
+ access: "view"
39261
+ },
38877
39262
  "zoneAnalytics.getUnzonedHistory": {
38878
39263
  capName: "zone-analytics",
38879
39264
  capScope: "device",
@@ -39868,14 +40253,14 @@ Object.freeze({
39868
40253
  form: "single",
39869
40254
  optional: true
39870
40255
  }],
39871
- "pipelineAnalytics.getGroup": [{
40256
+ "pipelineAnalytics.getKeyEvents": [{
39872
40257
  name: "deviceId",
39873
40258
  form: "single",
39874
40259
  optional: false
39875
40260
  }],
39876
- "pipelineAnalytics.getKeyEvents": [{
39877
- name: "deviceId",
39878
- form: "single",
40261
+ "pipelineAnalytics.getKeyEventsBatch": [{
40262
+ name: "deviceIds",
40263
+ form: "array",
39879
40264
  optional: false
39880
40265
  }],
39881
40266
  "pipelineAnalytics.getMotionEvents": [{
@@ -39933,11 +40318,6 @@ Object.freeze({
39933
40318
  form: "single",
39934
40319
  optional: false
39935
40320
  }],
39936
- "pipelineAnalytics.listGroups": [{
39937
- name: "deviceIds",
39938
- form: "array",
39939
- optional: false
39940
- }],
39941
40321
  "pipelineAnalytics.listOpsLog": [{
39942
40322
  name: "deviceId",
39943
40323
  form: "single",
@@ -39983,6 +40363,11 @@ Object.freeze({
39983
40363
  form: "single",
39984
40364
  optional: true
39985
40365
  }],
40366
+ "pipelineAnalytics.reclaimDebugMedia": [{
40367
+ name: "deviceIds",
40368
+ form: "array",
40369
+ optional: true
40370
+ }],
39986
40371
  "pipelineAnalytics.reconcileFromDisk": [{
39987
40372
  name: "deviceId",
39988
40373
  form: "single",
@@ -40318,6 +40703,11 @@ Object.freeze({
40318
40703
  form: "single",
40319
40704
  optional: false
40320
40705
  }],
40706
+ "recording.reconcileLedgerAgainstDisk": [{
40707
+ name: "deviceId",
40708
+ form: "single",
40709
+ optional: true
40710
+ }],
40321
40711
  "recording.relocateFootage": [{
40322
40712
  name: "deviceId",
40323
40713
  form: "single",
@@ -40343,6 +40733,11 @@ Object.freeze({
40343
40733
  form: "single",
40344
40734
  optional: false
40345
40735
  }],
40736
+ "recording.setDevicePlacement": [{
40737
+ name: "deviceId",
40738
+ form: "single",
40739
+ optional: false
40740
+ }],
40346
40741
  "recording.startStorageMigrationMove": [{
40347
40742
  name: "deviceId",
40348
40743
  form: "single",
@@ -40383,6 +40778,11 @@ Object.freeze({
40383
40778
  form: "single",
40384
40779
  optional: false
40385
40780
  }],
40781
+ "sceneMonitor.listScenesBatch": [{
40782
+ name: "deviceIds",
40783
+ form: "array",
40784
+ optional: false
40785
+ }],
40386
40786
  "sceneMonitor.recheckNow": [{
40387
40787
  name: "deviceId",
40388
40788
  form: "single",
@@ -40644,6 +41044,11 @@ Object.freeze({
40644
41044
  form: "single",
40645
41045
  optional: false
40646
41046
  }],
41047
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41048
+ name: "deviceIds",
41049
+ form: "array",
41050
+ optional: false
41051
+ }],
40647
41052
  "zoneAnalytics.getUnzonedHistory": [{
40648
41053
  name: "deviceId",
40649
41054
  form: "single",