@camstack/addon-provider-homematic 1.2.49 → 1.2.51

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
@@ -8028,6 +8028,20 @@ var RelocateJobSchema = object({
8028
8028
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8029
8029
  */
8030
8030
  rowsReconciled: number().int().nonnegative().optional(),
8031
+ /**
8032
+ * Rows this run FORGOT because the file they name is not on disk.
8033
+ *
8034
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8035
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8036
+ * and the durable row is dropped through the same channel eviction uses. It
8037
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8038
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8039
+ * the same failure as one that quietly skips them (D295).
8040
+ *
8041
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8042
+ * ledger claimed 5.65 GB of footage that no longer existed.
8043
+ */
8044
+ rowsForgotten: number().int().nonnegative().optional(),
8031
8045
  startedAt: number(),
8032
8046
  finishedAt: number().nullable(),
8033
8047
  error: string().nullable()
@@ -8092,6 +8106,13 @@ var MediaRelocateModeSchema = _enum([
8092
8106
  ]);
8093
8107
  var RelocateMediaInputSchema = object({
8094
8108
  toLocationId: string(),
8109
+ /**
8110
+ * Restrict the pass to rows currently on this location. Omitted / `'*'` =
8111
+ * every row that is not already on `toLocationId` (the historical
8112
+ * behaviour). A named source is what a from→to migration needs: without it
8113
+ * "move events off disk 2" also emptied disk 1.
8114
+ */
8115
+ fromLocationId: string().optional(),
8095
8116
  throttleMbps: number().min(1).max(1e3).optional(),
8096
8117
  /** Omitted = `move`, the pre-existing behaviour. */
8097
8118
  mode: MediaRelocateModeSchema.optional()
@@ -8159,6 +8180,19 @@ var StorageMigrationDestinationsSchema = object({
8159
8180
  galleryMedia: string().min(1).optional()
8160
8181
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
8161
8182
  /**
8183
+ * Optional named source per class. Omitted = the class's current default
8184
+ * (the historical behaviour). A named source that is NOT the default is a
8185
+ * drain of that disk: bytes move, the default stays, and the source is
8186
+ * disabled when the move finishes.
8187
+ */
8188
+ var StorageMigrationSourcesSchema = object({
8189
+ recordings: string().min(1).optional(),
8190
+ recordingsLow: string().min(1).optional(),
8191
+ eventMedia: string().min(1).optional(),
8192
+ backups: string().min(1).optional(),
8193
+ galleryMedia: string().min(1).optional()
8194
+ }).optional();
8195
+ /**
8162
8196
  * How a migration sequences the cutover against the byte move.
8163
8197
  *
8164
8198
  * - `blocking` — the historical order: pause, move every byte, repoint,
@@ -8180,6 +8214,8 @@ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
8180
8214
  /** Shared input for planning and starting an orchestrated storage migration. */
8181
8215
  var StorageMigrationInputSchema = object({
8182
8216
  destinations: StorageMigrationDestinationsSchema,
8217
+ /** Omitted = each class's current default. */
8218
+ sources: StorageMigrationSourcesSchema,
8183
8219
  throttleMbps: number().min(1).max(1e3).optional(),
8184
8220
  /** Omitted = `blocking`, which stays the default. */
8185
8221
  mode: StorageMigrationModeSchema.optional()
@@ -8259,6 +8295,13 @@ var StorageMigrationMoveSchema = object({
8259
8295
  storageClass: StorageMigrationClassSchema,
8260
8296
  fromLocationId: string(),
8261
8297
  toLocationId: string(),
8298
+ /**
8299
+ * True when `from` was NOT the class default at plan time. The move still
8300
+ * copies bytes, but the default is left alone and the source is disabled
8301
+ * once the copy verifies. Absent on jobs planned before this field existed
8302
+ * — those jobs always repointed, which is `false`.
8303
+ */
8304
+ freezeSource: boolean().optional(),
8262
8305
  moverJobId: string().nullable(),
8263
8306
  state: RelocateJobStateSchema.nullable(),
8264
8307
  error: string().nullable(),
@@ -8272,6 +8315,7 @@ var StorageMigrationJobSchema = object({
8272
8315
  * can tell a seconds-long cutover from a thirty-hour one. */
8273
8316
  mode: StorageMigrationModeSchema,
8274
8317
  destinations: StorageMigrationDestinationsSchema,
8318
+ sources: StorageMigrationSourcesSchema,
8275
8319
  throttleMbps: number(),
8276
8320
  moves: array(StorageMigrationMoveSchema),
8277
8321
  pauseLeaseId: string().nullable(),
@@ -8297,6 +8341,7 @@ var StorageMigrationFindingSchema = object({
8297
8341
  });
8298
8342
  var StorageMigrationPlanSchema = object({
8299
8343
  destinations: StorageMigrationDestinationsSchema,
8344
+ sources: StorageMigrationSourcesSchema,
8300
8345
  /** The mode this plan was built for. A plan is only valid for its mode: the
8301
8346
  * `eventMedia` seal gate and the single-cardinality refusal both depend on
8302
8347
  * it. */
@@ -8304,7 +8349,8 @@ var StorageMigrationPlanSchema = object({
8304
8349
  moves: array(object({
8305
8350
  storageClass: StorageMigrationClassSchema,
8306
8351
  fromLocationId: string(),
8307
- toLocationId: string()
8352
+ toLocationId: string(),
8353
+ freezeSource: boolean().optional()
8308
8354
  })),
8309
8355
  findings: array(StorageMigrationFindingSchema)
8310
8356
  });
@@ -8391,16 +8437,142 @@ var RelocateResidueSchema = object({
8391
8437
  segments: number().int().nonnegative(),
8392
8438
  bytes: number().int().nonnegative()
8393
8439
  }).nullable();
8440
+ /**
8441
+ * Ask one location whether its durable hour rows describe the disk — the walk
8442
+ * (D319).
8443
+ *
8444
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8445
+ * missing tool is the question, and the dry run is how they sanity-check the
8446
+ * destructive run before authorising it.
8447
+ */
8448
+ var LedgerWalkInputSchema = object({
8449
+ locationId: string().min(1),
8450
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8451
+ apply: boolean().optional(),
8452
+ /** Narrow to one camera. */
8453
+ deviceId: number().int().positive().optional(),
8454
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8455
+ profiles: array(string().min(1)).optional()
8456
+ });
8457
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8458
+ var LedgerWalkRefusalSchema = _enum([
8459
+ "location-unknown",
8460
+ "source-writable",
8461
+ "no-ledger",
8462
+ "archive-unreadable",
8463
+ "anchor-absent",
8464
+ "anchor-unreadable",
8465
+ "anchor-moved"
8466
+ ]);
8467
+ _enum([
8468
+ "live-tail",
8469
+ "listing-error",
8470
+ "path-mismatch",
8471
+ "durable-refused"
8472
+ ]);
8473
+ /** Every skip reason, always present, always a number — so a reason that never
8474
+ * fired reports as zero rather than absent and the report shape is constant
8475
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8476
+ var LedgerWalkSkipCountsSchema = object({
8477
+ "live-tail": number().int().nonnegative(),
8478
+ "listing-error": number().int().nonnegative(),
8479
+ "path-mismatch": number().int().nonnegative(),
8480
+ "durable-refused": number().int().nonnegative()
8481
+ });
8482
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8483
+ var LedgerWalkDeviceReportSchema = object({
8484
+ deviceId: number().int(),
8485
+ hoursWalked: number().int().nonnegative(),
8486
+ hoursMissing: number().int().nonnegative(),
8487
+ ghostSegments: number().int().nonnegative(),
8488
+ ghostBytes: number().int().nonnegative(),
8489
+ forgottenSegments: number().int().nonnegative(),
8490
+ orphanFiles: number().int().nonnegative()
8491
+ });
8492
+ /**
8493
+ * What one walk claimed, listed, found and (only when armed) forgot.
8494
+ *
8495
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8496
+ * walk that saw a fraction of the location is visible in its own report rather
8497
+ * than in the absence of one.
8498
+ */
8499
+ var LedgerWalkReportSchema = object({
8500
+ locationId: string(),
8501
+ applied: boolean(),
8502
+ refused: LedgerWalkRefusalSchema.nullable(),
8503
+ archiveSegments: number().int().nonnegative().nullable(),
8504
+ archiveBytes: number().int().nonnegative().nullable(),
8505
+ hoursClaimed: number().int().nonnegative(),
8506
+ hoursWalked: number().int().nonnegative(),
8507
+ hoursMissing: number().int().nonnegative(),
8508
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8509
+ listings: number().int().nonnegative(),
8510
+ segmentsClaimed: number().int().nonnegative(),
8511
+ ghostSegments: number().int().nonnegative(),
8512
+ ghostBytes: number().int().nonnegative(),
8513
+ ghostHoursWhole: number().int().nonnegative(),
8514
+ forgottenSegments: number().int().nonnegative(),
8515
+ forgottenBytes: number().int().nonnegative(),
8516
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8517
+ orphanFiles: number().int().nonnegative(),
8518
+ orphanSample: array(string()).readonly(),
8519
+ hoursSkipped: number().int().nonnegative(),
8520
+ skippedByReason: LedgerWalkSkipCountsSchema,
8521
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8522
+ bounded: boolean(),
8523
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8524
+ });
8394
8525
  /** How many rows a media pass would still act on against a given target — the
8395
8526
  * media lane's denominator AND its residue, from ONE derivation so the two can
8396
8527
  * never disagree. `null` = the count could not be taken. */
8397
8528
  var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
8398
8529
  var RelocatableMediaCountInputSchema = object({
8399
8530
  toLocationId: string().min(1),
8531
+ /** Restrict the count to one source; omitted / `'*'` = every non-target row. */
8532
+ fromLocationId: string().optional(),
8400
8533
  /** Omitted = `move`. */
8401
8534
  mode: MediaRelocateModeSchema.optional()
8402
8535
  });
8403
8536
  /**
8537
+ * Operator cleanup of leftover analytics rows, optional debug media, and
8538
+ * ghost ledger entries on frozen footage locations.
8539
+ *
8540
+ * A pass is tens of minutes on a standing backlog. The hub method RETURNS
8541
+ * `{ jobId }` immediately; progress is `cleanupStatus`. Awaiting the work
8542
+ * is how `addons.custom` hit the 60 s UDS deadline while reclaim continued
8543
+ * with no operator-visible status.
8544
+ */
8545
+ var StorageCleanupPhaseSchema = _enum([
8546
+ "orphans",
8547
+ "debug-media",
8548
+ "ghost-ledger",
8549
+ "done",
8550
+ "failed",
8551
+ "cancelled"
8552
+ ]);
8553
+ var StorageCleanupInputSchema = object({
8554
+ /** Also walk motion stills / track filmstrips. Off by default. */
8555
+ includeDebugMedia: boolean().optional() });
8556
+ var StorageCleanupJobSchema = object({
8557
+ jobId: string(),
8558
+ phase: StorageCleanupPhaseSchema,
8559
+ includeDebugMedia: boolean(),
8560
+ orphansReclaimed: number().int().nonnegative(),
8561
+ orphanBytesReclaimed: number().int().nonnegative(),
8562
+ debugMediaReclaimed: number().int().nonnegative(),
8563
+ debugMediaBytesReclaimed: number().int().nonnegative(),
8564
+ ghostsForgotten: number().int().nonnegative(),
8565
+ ghostBytesForgotten: number().int().nonnegative(),
8566
+ /** Short operator-facing line: current collection, pass, or location. */
8567
+ detail: string().nullable(),
8568
+ cancelRequested: boolean(),
8569
+ startedAt: number(),
8570
+ updatedAt: number(),
8571
+ finishedAt: number().nullable(),
8572
+ error: string().nullable()
8573
+ });
8574
+ var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
8575
+ /**
8404
8576
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8405
8577
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8406
8578
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8428,11 +8600,10 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8428
8600
  * The default location for a type uses `id === <type>:default` by
8429
8601
  * convention (the bare type ref like `'backups'` resolves to it).
8430
8602
  *
8431
- * `isSystem: true` marks a location as orchestrator-seeded and
8432
- * undeletable. The bootstrap-installed defaults (one per type) carry
8433
- * this flag; operator-added locations don't. Editing the config of
8434
- * a system location is allowed (path migration, provider swap) but
8435
- * deleting it is rejected at the cap level.
8603
+ * `isSystem` is a legacy persisted flag. Seed still creates the initial
8604
+ * `<type>:default` locations; the flag is no longer a lock, a badge, or a
8605
+ * prune selector. New writes leave it false. Deletion is gated on uniqueness
8606
+ * / last-enabled, not on this bit.
8436
8607
  */
8437
8608
  var StorageLocationSchema = object({
8438
8609
  id: string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
@@ -13024,6 +13195,12 @@ method(object({
13024
13195
  }), _void(), {
13025
13196
  kind: "mutation",
13026
13197
  auth: "admin"
13198
+ }), method(object({
13199
+ from: string(),
13200
+ to: string()
13201
+ }), object({ moved: number() }), {
13202
+ kind: "mutation",
13203
+ auth: "admin"
13027
13204
  }), method(object({
13028
13205
  deviceId: number(),
13029
13206
  disabled: boolean()
@@ -19032,53 +19209,15 @@ var RecentTracksPageSchema = object({
19032
19209
  /** Cursor for the next page, or null when this page is the last. */
19033
19210
  nextCursor: string().nullable()
19034
19211
  });
19035
- var LIST_GROUPS_DEFAULT_LIMIT = 40;
19036
- var LIST_GROUPS_MAX_LIMIT = 100;
19037
- var AnalyticsGroupRecordSchema = object({
19038
- id: string(),
19039
- deviceId: number().int(),
19040
- openedAt: number().int(),
19041
- closedAt: number().int(),
19042
- timestamp: number().int(),
19043
- memberCount: number().int(),
19044
- memberTrackIds: array(string()).readonly(),
19045
- className: string(),
19046
- classes: array(string()).readonly(),
19047
- /** Relative event-media path, or null when the group has no picture yet. */
19048
- mediaUrl: string().nullable(),
19049
- singleton: boolean()
19050
- });
19051
- var AnalyticsGroupMemberSchema = object({
19052
- trackId: string(),
19053
- deviceId: number().int(),
19054
- className: string(),
19055
- firstSeen: number().int(),
19056
- lastSeen: number().int(),
19057
- mediaUrl: string().nullable()
19058
- });
19059
- var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
19060
- var ListGroupsQueryInput = object({
19061
- /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
19062
- deviceIds: array(number()),
19063
- /** Window lower bound on `closedAt` (inclusive). */
19064
- since: number().optional(),
19065
- /** Window upper bound on `openedAt` (inclusive). */
19066
- until: number().optional(),
19067
- limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
19068
- /** Opaque continuation cursor from a previous page's `nextCursor`. */
19069
- cursor: string().optional()
19070
- });
19071
- var ListGroupsPageSchema = object({
19072
- groups: array(AnalyticsGroupRecordSchema).readonly(),
19073
- nextCursor: string().nullable()
19074
- });
19212
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19213
+ var KEY_EVENTS_MAX_LIMIT = 200;
19075
19214
  var KeyEventQueryInput = object({
19076
19215
  deviceId: number(),
19077
19216
  /** Window lower bound (track firstSeen ≥ since). */
19078
19217
  since: number(),
19079
19218
  /** Window upper bound (track firstSeen ≤ until). */
19080
19219
  until: number(),
19081
- limit: number().int().min(1).max(200).default(50),
19220
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19082
19221
  /** Drop tracks scoring below this importance. */
19083
19222
  minImportance: number().min(0).max(1).optional(),
19084
19223
  /** Restrict to a single class (e.g. 'person'). */
@@ -19100,6 +19239,32 @@ var KeyEventSchema = object({
19100
19239
  ...TrackFlagFields,
19101
19240
  ...TrackRetrainFields
19102
19241
  });
19242
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19243
+ var KeyEventBatchQueryInput = object({
19244
+ deviceIds: array(number()).min(1).max(200),
19245
+ since: number(),
19246
+ until: number(),
19247
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19248
+ * across the set, which would let a busy camera starve a quiet one of its
19249
+ * rows and change what the merged feed contains. */
19250
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19251
+ minImportance: number().min(0).max(1).optional(),
19252
+ classFilter: string().optional()
19253
+ });
19254
+ /**
19255
+ * One camera's key events in a batch answer.
19256
+ *
19257
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19258
+ * error rather than throwing, so a camera whose store read failed and one with
19259
+ * no events in the window were ALREADY indistinguishable per camera — the
19260
+ * batch does not make that worse, and the row keeps the deviceId the single
19261
+ * method's output never carried (the caller used to stamp it from the fan-out
19262
+ * key, which only worked because there was one query per camera).
19263
+ */
19264
+ var KeyEventsForDeviceSchema = object({
19265
+ deviceId: number(),
19266
+ events: array(KeyEventSchema).readonly()
19267
+ });
19103
19268
  object({
19104
19269
  trackId: string(),
19105
19270
  className: string(),
@@ -19147,9 +19312,7 @@ var TrackCascadeCountsSchema = object({
19147
19312
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
19148
19313
  plates: number().int(),
19149
19314
  /** Per-track CLIP search vectors removed (best-effort). */
19150
- embeddings: number().int(),
19151
- /** Group membership + group rows removed with their last member (best-effort). */
19152
- groups: number().int()
19315
+ embeddings: number().int()
19153
19316
  });
19154
19317
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
19155
19318
  var DiskReconcileCountsSchema = object({
@@ -19319,6 +19482,47 @@ var RebuildStatusSchema = object({
19319
19482
  /** Present when the pass ended by throwing. */
19320
19483
  error: string().nullable()
19321
19484
  });
19485
+ /**
19486
+ * Acknowledgement that a debug-media reclaim STARTED.
19487
+ *
19488
+ * The pass is paced at ~2 MB/s over a standing 100 GB — tens of minutes — so
19489
+ * it runs detached and this returns immediately. Awaiting it is how the
19490
+ * `addons.custom` door hit the 60 s UDS deadline while the walk carried on
19491
+ * with no status. Poll {@link pipelineAnalyticsCapability.methods.getMediaReclaimStatus}.
19492
+ */
19493
+ var MediaReclaimStartResultSchema = object({
19494
+ started: boolean(),
19495
+ /** True when a pass was already running; the new request is ignored. */
19496
+ alreadyRunning: boolean()
19497
+ });
19498
+ var MediaReclaimInputSchema = object({
19499
+ mode: _enum(["report", "reclaim"]).default("report"),
19500
+ scopes: array(_enum(["motion-still", "track-filmstrip"])).min(1).optional(),
19501
+ deviceIds: array(number().int()).min(1).optional(),
19502
+ restart: boolean().optional(),
19503
+ pageSize: number().int().min(50).max(5e3).optional(),
19504
+ maxRowsPerDevice: number().int().min(1).max(5e5).optional(),
19505
+ maxReclaimPerDevice: number().int().min(1).max(5e5).optional(),
19506
+ maxBytesPerRun: number().int().min(1).optional(),
19507
+ budgetMinutes: number().int().min(1).max(720).optional(),
19508
+ throttleBytesPerSec: number().int().min(64 * 1024).optional(),
19509
+ graceMinutes: number().int().min(1).max(10080).optional()
19510
+ });
19511
+ var MediaReclaimStatusSchema = object({
19512
+ running: boolean(),
19513
+ mode: _enum(["report", "reclaim"]).nullable(),
19514
+ totalExamined: number(),
19515
+ totalEligible: number(),
19516
+ totalReclaimed: number(),
19517
+ totalBytesReclaimed: number(),
19518
+ totalRefused: number(),
19519
+ /** Device+scope windows finished in this pass. */
19520
+ devicesDone: number(),
19521
+ complete: boolean().nullable(),
19522
+ startedAtMs: number().nullable(),
19523
+ finishedAtMs: number().nullable(),
19524
+ error: string().nullable()
19525
+ });
19322
19526
  var ReplayFrameInputSchema = object({
19323
19527
  timestamp: number(),
19324
19528
  frame: PipelineRunResultBridge
@@ -19357,10 +19561,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19357
19561
  * stationary registry). Default false: the timeline lists passages,
19358
19562
  * not parking records (operator decision, 2026-08-15). */
19359
19563
  includeStationary: boolean().optional()
19360
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
19361
- deviceId: number(),
19362
- groupId: string().min(1)
19363
- }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
19564
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19364
19565
  kind: "mutation",
19365
19566
  auth: "admin"
19366
19567
  }), 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({
@@ -19369,7 +19570,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19369
19570
  until: number().optional(),
19370
19571
  kinds: array(string()).optional(),
19371
19572
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19372
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19573
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19373
19574
  deviceId: number(),
19374
19575
  since: number(),
19375
19576
  until: number(),
@@ -19460,6 +19661,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19460
19661
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19461
19662
  kind: "query",
19462
19663
  auth: "admin"
19664
+ }), method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
19665
+ kind: "mutation",
19666
+ auth: "admin"
19667
+ }), method(object({}), MediaReclaimStatusSchema, {
19668
+ kind: "query",
19669
+ auth: "admin"
19463
19670
  }), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
19464
19671
  kind: "query",
19465
19672
  auth: "admin"
@@ -21430,7 +21637,13 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
21430
21637
  }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
21431
21638
  kind: "mutation",
21432
21639
  auth: "admin"
21433
- });
21640
+ }), method(StorageCleanupInputSchema, object({ jobId: string() }), {
21641
+ kind: "mutation",
21642
+ auth: "admin"
21643
+ }), method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
21644
+ kind: "mutation",
21645
+ auth: "admin"
21646
+ }), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
21434
21647
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
21435
21648
  providerId: string().min(1),
21436
21649
  displayName: string().min(1),
@@ -28523,6 +28736,33 @@ var RecordingRebalanceInputSchema = object({
28523
28736
  minMoveGb: number().min(0).optional()
28524
28737
  });
28525
28738
  /**
28739
+ * Operator-facing placement of one camera onto a recordings location.
28740
+ *
28741
+ * `pinLocationId` is the operator instruction: a location id, or `null` for
28742
+ * Auto (the planner may move this camera). `locationId` is where high/mid
28743
+ * currently write — the plan, which may disagree with the pin when Auto.
28744
+ */
28745
+ var RecordingDevicePlacementSchema = object({
28746
+ deviceId: number().int(),
28747
+ profile: string(),
28748
+ locationId: string()
28749
+ });
28750
+ var RecordingDevicePinSchema = object({
28751
+ deviceId: number().int(),
28752
+ /** Recordings-class location this camera is pinned to. */
28753
+ locationId: string()
28754
+ });
28755
+ var RecordingPlacementViewSchema = object({
28756
+ assignments: array(RecordingDevicePlacementSchema),
28757
+ pins: array(RecordingDevicePinSchema),
28758
+ defaultLocations: record(string(), string())
28759
+ });
28760
+ var RecordingSetDevicePlacementInputSchema = object({
28761
+ deviceId: number().int(),
28762
+ /** `null` = Auto. Otherwise a `recordings:*` location id. */
28763
+ locationId: string().nullable()
28764
+ });
28765
+ /**
28526
28766
  * Result of locating footage at a wall-clock instant for one device/profile.
28527
28767
  * `segment` carries the covering segment's window; `gap` reports the forward
28528
28768
  * nearest covered edge (`null` past the end of footage / when none exists)
@@ -28736,6 +28976,9 @@ method(object({
28736
28976
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28737
28977
  kind: "query",
28738
28978
  auth: "admin"
28979
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
28980
+ kind: "mutation",
28981
+ auth: "admin"
28739
28982
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28740
28983
  kind: "mutation",
28741
28984
  auth: "admin"
@@ -28745,6 +28988,12 @@ method(object({
28745
28988
  }), method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
28746
28989
  kind: "mutation",
28747
28990
  auth: "admin"
28991
+ }), method(object({}), RecordingPlacementViewSchema, {
28992
+ kind: "query",
28993
+ auth: "admin"
28994
+ }), method(RecordingSetDevicePlacementInputSchema, object({ ok: literal(true) }), {
28995
+ kind: "mutation",
28996
+ auth: "admin"
28748
28997
  });
28749
28998
  /**
28750
28999
  * `recording-export` cap — render a footage time range into a single downloadable
@@ -29127,6 +29376,25 @@ var SceneMonitorStatusSchema = object({
29127
29376
  monitors: array(SceneMonitorSchema),
29128
29377
  lastFetchedAt: number()
29129
29378
  });
29379
+ /**
29380
+ * One camera's row in a `listScenesBatch` answer.
29381
+ *
29382
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29383
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29384
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29385
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29386
+ * configured. Fanned out per camera the difference was visible — one query
29387
+ * errored while the others resolved — and a batch that returned only the rows
29388
+ * it managed would have destroyed it, silently, by making an unreachable camera
29389
+ * indistinguishable from one that answered `monitors: []`.
29390
+ *
29391
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29392
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29393
+ */
29394
+ var SceneMonitorStatusForDeviceSchema = object({
29395
+ deviceId: number(),
29396
+ status: SceneMonitorStatusSchema.nullable()
29397
+ });
29130
29398
  var sceneMonitorCapability = {
29131
29399
  name: "scene-monitor",
29132
29400
  scope: "device",
@@ -29136,6 +29404,22 @@ var sceneMonitorCapability = {
29136
29404
  deviceTypes: [DeviceType.Camera],
29137
29405
  methods: {
29138
29406
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29407
+ /**
29408
+ * The same answer, for a SET of cameras, in one round trip.
29409
+ *
29410
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29411
+ * poll behind the push slice. Fanned out client-side that was one query
29412
+ * per camera — 29 round trips through the browser, the hub and the
29413
+ * post-analysis runner every 30 seconds to read an in-memory map the
29414
+ * owner had already merged. The work is unchanged (`statusFor` per
29415
+ * device, all in-process at the owner); what collapses is the transport.
29416
+ *
29417
+ * A camera that cannot answer still gets a row, with `status: null` —
29418
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29419
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29420
+ * tell which two are missing, or that any are.
29421
+ */
29422
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29139
29423
  createScene: method(object({
29140
29424
  deviceId: number(),
29141
29425
  label: string(),
@@ -30971,6 +31255,27 @@ var CameraOccupancySnapshotSchema = object({
30971
31255
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
30972
31256
  });
30973
31257
  /**
31258
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31259
+ *
31260
+ * THREE outcomes, and the single-camera method could only express two of them
31261
+ * because `snapshot: null` was already spoken for:
31262
+ *
31263
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31264
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31265
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31266
+ * - `read: 'unreadable'` — the owner could not answer for this
31267
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31268
+ *
31269
+ * Collapsing the last two is the failure this field exists to prevent: a
31270
+ * hydration that threw would otherwise render as an empty Stationary section,
31271
+ * which is a definite claim about a camera nobody could read.
31272
+ */
31273
+ var CameraOccupancySnapshotForDeviceSchema = object({
31274
+ deviceId: number(),
31275
+ read: _enum(["read", "unreadable"]),
31276
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31277
+ });
31278
+ /**
30974
31279
  * Time-series resolution. The history methods return one bucket per
30975
31280
  * step over the requested range. Smaller resolutions cost more
30976
31281
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31027,6 +31332,20 @@ var zoneAnalyticsCapability = {
31027
31332
  * (no inference result emitted since boot or since binding was
31028
31333
  * activated). */
31029
31334
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31335
+ /**
31336
+ * The same snapshot, for a SET of cameras, in one round trip.
31337
+ *
31338
+ * The Events page's Stationary section polls this every 15s for every
31339
+ * selected camera. Fanned out client-side that is one query per camera to
31340
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31341
+ * costs everything. Batched, N transports become one and the per-device
31342
+ * work is unchanged.
31343
+ *
31344
+ * Every requested deviceId gets a row, tagged `read` — see
31345
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31346
+ * not answer for is `'unreadable'`, never an empty reading.
31347
+ */
31348
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31030
31349
  /** Time-series object count inside one zone. `className` optional —
31031
31350
  * omit to count every class in the zone. */
31032
31351
  getZoneHistory: method(object({
@@ -33744,6 +34063,12 @@ Object.freeze({
33744
34063
  addonId: null,
33745
34064
  access: "delete"
33746
34065
  },
34066
+ "deviceManager.renameLocation": {
34067
+ capName: "device-manager",
34068
+ capScope: "system",
34069
+ addonId: null,
34070
+ access: "create"
34071
+ },
33747
34072
  "deviceManager.runDeviceAction": {
33748
34073
  capName: "device-manager",
33749
34074
  capScope: "system",
@@ -35436,13 +35761,19 @@ Object.freeze({
35436
35761
  addonId: null,
35437
35762
  access: "view"
35438
35763
  },
35439
- "pipelineAnalytics.getGroup": {
35764
+ "pipelineAnalytics.getKeyEvents": {
35765
+ capName: "pipeline-analytics",
35766
+ capScope: "device",
35767
+ addonId: null,
35768
+ access: "view"
35769
+ },
35770
+ "pipelineAnalytics.getKeyEventsBatch": {
35440
35771
  capName: "pipeline-analytics",
35441
35772
  capScope: "device",
35442
35773
  addonId: null,
35443
35774
  access: "view"
35444
35775
  },
35445
- "pipelineAnalytics.getKeyEvents": {
35776
+ "pipelineAnalytics.getMediaReclaimStatus": {
35446
35777
  capName: "pipeline-analytics",
35447
35778
  capScope: "device",
35448
35779
  addonId: null,
@@ -35532,12 +35863,6 @@ Object.freeze({
35532
35863
  addonId: null,
35533
35864
  access: "view"
35534
35865
  },
35535
- "pipelineAnalytics.listGroups": {
35536
- capName: "pipeline-analytics",
35537
- capScope: "device",
35538
- addonId: null,
35539
- access: "view"
35540
- },
35541
35866
  "pipelineAnalytics.listOpsLog": {
35542
35867
  capName: "pipeline-analytics",
35543
35868
  capScope: "device",
@@ -35622,6 +35947,12 @@ Object.freeze({
35622
35947
  addonId: null,
35623
35948
  access: "create"
35624
35949
  },
35950
+ "pipelineAnalytics.reclaimDebugMedia": {
35951
+ capName: "pipeline-analytics",
35952
+ capScope: "device",
35953
+ addonId: null,
35954
+ access: "create"
35955
+ },
35625
35956
  "pipelineAnalytics.reconcileFromDisk": {
35626
35957
  capName: "pipeline-analytics",
35627
35958
  capScope: "device",
@@ -36546,6 +36877,12 @@ Object.freeze({
36546
36877
  addonId: null,
36547
36878
  access: "view"
36548
36879
  },
36880
+ "recording.getPlacement": {
36881
+ capName: "recording",
36882
+ capScope: "system",
36883
+ addonId: null,
36884
+ access: "view"
36885
+ },
36549
36886
  "recording.getPlaybackManifest": {
36550
36887
  capName: "recording",
36551
36888
  capScope: "system",
@@ -36624,6 +36961,12 @@ Object.freeze({
36624
36961
  addonId: null,
36625
36962
  access: "view"
36626
36963
  },
36964
+ "recording.reconcileLedgerAgainstDisk": {
36965
+ capName: "recording",
36966
+ capScope: "system",
36967
+ addonId: null,
36968
+ access: "create"
36969
+ },
36627
36970
  "recording.refreshStorageLocationsForMigration": {
36628
36971
  capName: "recording",
36629
36972
  capScope: "system",
@@ -36666,6 +37009,12 @@ Object.freeze({
36666
37009
  addonId: null,
36667
37010
  access: "create"
36668
37011
  },
37012
+ "recording.setDevicePlacement": {
37013
+ capName: "recording",
37014
+ capScope: "system",
37015
+ addonId: null,
37016
+ access: "create"
37017
+ },
36669
37018
  "recording.startStorageMigrationMove": {
36670
37019
  capName: "recording",
36671
37020
  capScope: "system",
@@ -36750,6 +37099,12 @@ Object.freeze({
36750
37099
  addonId: null,
36751
37100
  access: "view"
36752
37101
  },
37102
+ "sceneMonitor.listScenesBatch": {
37103
+ capName: "scene-monitor",
37104
+ capScope: "device",
37105
+ addonId: null,
37106
+ access: "view"
37107
+ },
36753
37108
  "sceneMonitor.recheckNow": {
36754
37109
  capName: "scene-monitor",
36755
37110
  capScope: "device",
@@ -37104,12 +37459,36 @@ Object.freeze({
37104
37459
  addonId: null,
37105
37460
  access: "create"
37106
37461
  },
37462
+ "storageMigration.cleanupCancel": {
37463
+ capName: "storage-migration",
37464
+ capScope: "system",
37465
+ addonId: null,
37466
+ access: "create"
37467
+ },
37468
+ "storageMigration.cleanupStart": {
37469
+ capName: "storage-migration",
37470
+ capScope: "system",
37471
+ addonId: null,
37472
+ access: "create"
37473
+ },
37474
+ "storageMigration.cleanupStatus": {
37475
+ capName: "storage-migration",
37476
+ capScope: "system",
37477
+ addonId: null,
37478
+ access: "view"
37479
+ },
37107
37480
  "storageMigration.drain": {
37108
37481
  capName: "storage-migration",
37109
37482
  capScope: "system",
37110
37483
  addonId: null,
37111
37484
  access: "create"
37112
37485
  },
37486
+ "storageMigration.history": {
37487
+ capName: "storage-migration",
37488
+ capScope: "system",
37489
+ addonId: null,
37490
+ access: "view"
37491
+ },
37113
37492
  "storageMigration.movers": {
37114
37493
  capName: "storage-migration",
37115
37494
  capScope: "system",
@@ -38106,6 +38485,12 @@ Object.freeze({
38106
38485
  addonId: null,
38107
38486
  access: "view"
38108
38487
  },
38488
+ "zoneAnalytics.getCurrentSnapshotBatch": {
38489
+ capName: "zone-analytics",
38490
+ capScope: "device",
38491
+ addonId: null,
38492
+ access: "view"
38493
+ },
38109
38494
  "zoneAnalytics.getUnzonedHistory": {
38110
38495
  capName: "zone-analytics",
38111
38496
  capScope: "device",
@@ -39100,14 +39485,14 @@ Object.freeze({
39100
39485
  form: "single",
39101
39486
  optional: true
39102
39487
  }],
39103
- "pipelineAnalytics.getGroup": [{
39488
+ "pipelineAnalytics.getKeyEvents": [{
39104
39489
  name: "deviceId",
39105
39490
  form: "single",
39106
39491
  optional: false
39107
39492
  }],
39108
- "pipelineAnalytics.getKeyEvents": [{
39109
- name: "deviceId",
39110
- form: "single",
39493
+ "pipelineAnalytics.getKeyEventsBatch": [{
39494
+ name: "deviceIds",
39495
+ form: "array",
39111
39496
  optional: false
39112
39497
  }],
39113
39498
  "pipelineAnalytics.getMotionEvents": [{
@@ -39165,11 +39550,6 @@ Object.freeze({
39165
39550
  form: "single",
39166
39551
  optional: false
39167
39552
  }],
39168
- "pipelineAnalytics.listGroups": [{
39169
- name: "deviceIds",
39170
- form: "array",
39171
- optional: false
39172
- }],
39173
39553
  "pipelineAnalytics.listOpsLog": [{
39174
39554
  name: "deviceId",
39175
39555
  form: "single",
@@ -39215,6 +39595,11 @@ Object.freeze({
39215
39595
  form: "single",
39216
39596
  optional: true
39217
39597
  }],
39598
+ "pipelineAnalytics.reclaimDebugMedia": [{
39599
+ name: "deviceIds",
39600
+ form: "array",
39601
+ optional: true
39602
+ }],
39218
39603
  "pipelineAnalytics.reconcileFromDisk": [{
39219
39604
  name: "deviceId",
39220
39605
  form: "single",
@@ -39550,6 +39935,11 @@ Object.freeze({
39550
39935
  form: "single",
39551
39936
  optional: false
39552
39937
  }],
39938
+ "recording.reconcileLedgerAgainstDisk": [{
39939
+ name: "deviceId",
39940
+ form: "single",
39941
+ optional: true
39942
+ }],
39553
39943
  "recording.relocateFootage": [{
39554
39944
  name: "deviceId",
39555
39945
  form: "single",
@@ -39575,6 +39965,11 @@ Object.freeze({
39575
39965
  form: "single",
39576
39966
  optional: false
39577
39967
  }],
39968
+ "recording.setDevicePlacement": [{
39969
+ name: "deviceId",
39970
+ form: "single",
39971
+ optional: false
39972
+ }],
39578
39973
  "recording.startStorageMigrationMove": [{
39579
39974
  name: "deviceId",
39580
39975
  form: "single",
@@ -39615,6 +40010,11 @@ Object.freeze({
39615
40010
  form: "single",
39616
40011
  optional: false
39617
40012
  }],
40013
+ "sceneMonitor.listScenesBatch": [{
40014
+ name: "deviceIds",
40015
+ form: "array",
40016
+ optional: false
40017
+ }],
39618
40018
  "sceneMonitor.recheckNow": [{
39619
40019
  name: "deviceId",
39620
40020
  form: "single",
@@ -39876,6 +40276,11 @@ Object.freeze({
39876
40276
  form: "single",
39877
40277
  optional: false
39878
40278
  }],
40279
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40280
+ name: "deviceIds",
40281
+ form: "array",
40282
+ optional: false
40283
+ }],
39879
40284
  "zoneAnalytics.getUnzonedHistory": [{
39880
40285
  name: "deviceId",
39881
40286
  form: "single",