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