@camstack/addon-provider-amcrest 0.2.49 → 0.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
@@ -8011,6 +8011,20 @@ var RelocateJobSchema = object({
8011
8011
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8012
8012
  */
8013
8013
  rowsReconciled: number().int().nonnegative().optional(),
8014
+ /**
8015
+ * Rows this run FORGOT because the file they name is not on disk.
8016
+ *
8017
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8018
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8019
+ * and the durable row is dropped through the same channel eviction uses. It
8020
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8021
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8022
+ * the same failure as one that quietly skips them (D295).
8023
+ *
8024
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8025
+ * ledger claimed 5.65 GB of footage that no longer existed.
8026
+ */
8027
+ rowsForgotten: number().int().nonnegative().optional(),
8014
8028
  startedAt: number(),
8015
8029
  finishedAt: number().nullable(),
8016
8030
  error: string().nullable()
@@ -8075,6 +8089,13 @@ var MediaRelocateModeSchema = _enum([
8075
8089
  ]);
8076
8090
  var RelocateMediaInputSchema = object({
8077
8091
  toLocationId: string(),
8092
+ /**
8093
+ * Restrict the pass to rows currently on this location. Omitted / `'*'` =
8094
+ * every row that is not already on `toLocationId` (the historical
8095
+ * behaviour). A named source is what a from→to migration needs: without it
8096
+ * "move events off disk 2" also emptied disk 1.
8097
+ */
8098
+ fromLocationId: string().optional(),
8078
8099
  throttleMbps: number().min(1).max(1e3).optional(),
8079
8100
  /** Omitted = `move`, the pre-existing behaviour. */
8080
8101
  mode: MediaRelocateModeSchema.optional()
@@ -8142,6 +8163,19 @@ var StorageMigrationDestinationsSchema = object({
8142
8163
  galleryMedia: string().min(1).optional()
8143
8164
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
8144
8165
  /**
8166
+ * Optional named source per class. Omitted = the class's current default
8167
+ * (the historical behaviour). A named source that is NOT the default is a
8168
+ * drain of that disk: bytes move, the default stays, and the source is
8169
+ * disabled when the move finishes.
8170
+ */
8171
+ var StorageMigrationSourcesSchema = object({
8172
+ recordings: string().min(1).optional(),
8173
+ recordingsLow: string().min(1).optional(),
8174
+ eventMedia: string().min(1).optional(),
8175
+ backups: string().min(1).optional(),
8176
+ galleryMedia: string().min(1).optional()
8177
+ }).optional();
8178
+ /**
8145
8179
  * How a migration sequences the cutover against the byte move.
8146
8180
  *
8147
8181
  * - `blocking` — the historical order: pause, move every byte, repoint,
@@ -8163,6 +8197,8 @@ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
8163
8197
  /** Shared input for planning and starting an orchestrated storage migration. */
8164
8198
  var StorageMigrationInputSchema = object({
8165
8199
  destinations: StorageMigrationDestinationsSchema,
8200
+ /** Omitted = each class's current default. */
8201
+ sources: StorageMigrationSourcesSchema,
8166
8202
  throttleMbps: number().min(1).max(1e3).optional(),
8167
8203
  /** Omitted = `blocking`, which stays the default. */
8168
8204
  mode: StorageMigrationModeSchema.optional()
@@ -8242,6 +8278,13 @@ var StorageMigrationMoveSchema = object({
8242
8278
  storageClass: StorageMigrationClassSchema,
8243
8279
  fromLocationId: string(),
8244
8280
  toLocationId: string(),
8281
+ /**
8282
+ * True when `from` was NOT the class default at plan time. The move still
8283
+ * copies bytes, but the default is left alone and the source is disabled
8284
+ * once the copy verifies. Absent on jobs planned before this field existed
8285
+ * — those jobs always repointed, which is `false`.
8286
+ */
8287
+ freezeSource: boolean().optional(),
8245
8288
  moverJobId: string().nullable(),
8246
8289
  state: RelocateJobStateSchema.nullable(),
8247
8290
  error: string().nullable(),
@@ -8255,6 +8298,7 @@ var StorageMigrationJobSchema = object({
8255
8298
  * can tell a seconds-long cutover from a thirty-hour one. */
8256
8299
  mode: StorageMigrationModeSchema,
8257
8300
  destinations: StorageMigrationDestinationsSchema,
8301
+ sources: StorageMigrationSourcesSchema,
8258
8302
  throttleMbps: number(),
8259
8303
  moves: array(StorageMigrationMoveSchema),
8260
8304
  pauseLeaseId: string().nullable(),
@@ -8280,6 +8324,7 @@ var StorageMigrationFindingSchema = object({
8280
8324
  });
8281
8325
  var StorageMigrationPlanSchema = object({
8282
8326
  destinations: StorageMigrationDestinationsSchema,
8327
+ sources: StorageMigrationSourcesSchema,
8283
8328
  /** The mode this plan was built for. A plan is only valid for its mode: the
8284
8329
  * `eventMedia` seal gate and the single-cardinality refusal both depend on
8285
8330
  * it. */
@@ -8287,7 +8332,8 @@ var StorageMigrationPlanSchema = object({
8287
8332
  moves: array(object({
8288
8333
  storageClass: StorageMigrationClassSchema,
8289
8334
  fromLocationId: string(),
8290
- toLocationId: string()
8335
+ toLocationId: string(),
8336
+ freezeSource: boolean().optional()
8291
8337
  })),
8292
8338
  findings: array(StorageMigrationFindingSchema)
8293
8339
  });
@@ -8374,16 +8420,142 @@ var RelocateResidueSchema = object({
8374
8420
  segments: number().int().nonnegative(),
8375
8421
  bytes: number().int().nonnegative()
8376
8422
  }).nullable();
8423
+ /**
8424
+ * Ask one location whether its durable hour rows describe the disk — the walk
8425
+ * (D319).
8426
+ *
8427
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8428
+ * missing tool is the question, and the dry run is how they sanity-check the
8429
+ * destructive run before authorising it.
8430
+ */
8431
+ var LedgerWalkInputSchema = object({
8432
+ locationId: string().min(1),
8433
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8434
+ apply: boolean().optional(),
8435
+ /** Narrow to one camera. */
8436
+ deviceId: number().int().positive().optional(),
8437
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8438
+ profiles: array(string().min(1)).optional()
8439
+ });
8440
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8441
+ var LedgerWalkRefusalSchema = _enum([
8442
+ "location-unknown",
8443
+ "source-writable",
8444
+ "no-ledger",
8445
+ "archive-unreadable",
8446
+ "anchor-absent",
8447
+ "anchor-unreadable",
8448
+ "anchor-moved"
8449
+ ]);
8450
+ _enum([
8451
+ "live-tail",
8452
+ "listing-error",
8453
+ "path-mismatch",
8454
+ "durable-refused"
8455
+ ]);
8456
+ /** Every skip reason, always present, always a number — so a reason that never
8457
+ * fired reports as zero rather than absent and the report shape is constant
8458
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8459
+ var LedgerWalkSkipCountsSchema = object({
8460
+ "live-tail": number().int().nonnegative(),
8461
+ "listing-error": number().int().nonnegative(),
8462
+ "path-mismatch": number().int().nonnegative(),
8463
+ "durable-refused": number().int().nonnegative()
8464
+ });
8465
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8466
+ var LedgerWalkDeviceReportSchema = object({
8467
+ deviceId: number().int(),
8468
+ hoursWalked: number().int().nonnegative(),
8469
+ hoursMissing: number().int().nonnegative(),
8470
+ ghostSegments: number().int().nonnegative(),
8471
+ ghostBytes: number().int().nonnegative(),
8472
+ forgottenSegments: number().int().nonnegative(),
8473
+ orphanFiles: number().int().nonnegative()
8474
+ });
8475
+ /**
8476
+ * What one walk claimed, listed, found and (only when armed) forgot.
8477
+ *
8478
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8479
+ * walk that saw a fraction of the location is visible in its own report rather
8480
+ * than in the absence of one.
8481
+ */
8482
+ var LedgerWalkReportSchema = object({
8483
+ locationId: string(),
8484
+ applied: boolean(),
8485
+ refused: LedgerWalkRefusalSchema.nullable(),
8486
+ archiveSegments: number().int().nonnegative().nullable(),
8487
+ archiveBytes: number().int().nonnegative().nullable(),
8488
+ hoursClaimed: number().int().nonnegative(),
8489
+ hoursWalked: number().int().nonnegative(),
8490
+ hoursMissing: number().int().nonnegative(),
8491
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8492
+ listings: number().int().nonnegative(),
8493
+ segmentsClaimed: number().int().nonnegative(),
8494
+ ghostSegments: number().int().nonnegative(),
8495
+ ghostBytes: number().int().nonnegative(),
8496
+ ghostHoursWhole: number().int().nonnegative(),
8497
+ forgottenSegments: number().int().nonnegative(),
8498
+ forgottenBytes: number().int().nonnegative(),
8499
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8500
+ orphanFiles: number().int().nonnegative(),
8501
+ orphanSample: array(string()).readonly(),
8502
+ hoursSkipped: number().int().nonnegative(),
8503
+ skippedByReason: LedgerWalkSkipCountsSchema,
8504
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8505
+ bounded: boolean(),
8506
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8507
+ });
8377
8508
  /** How many rows a media pass would still act on against a given target — the
8378
8509
  * media lane's denominator AND its residue, from ONE derivation so the two can
8379
8510
  * never disagree. `null` = the count could not be taken. */
8380
8511
  var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
8381
8512
  var RelocatableMediaCountInputSchema = object({
8382
8513
  toLocationId: string().min(1),
8514
+ /** Restrict the count to one source; omitted / `'*'` = every non-target row. */
8515
+ fromLocationId: string().optional(),
8383
8516
  /** Omitted = `move`. */
8384
8517
  mode: MediaRelocateModeSchema.optional()
8385
8518
  });
8386
8519
  /**
8520
+ * Operator cleanup of leftover analytics rows, optional debug media, and
8521
+ * ghost ledger entries on frozen footage locations.
8522
+ *
8523
+ * A pass is tens of minutes on a standing backlog. The hub method RETURNS
8524
+ * `{ jobId }` immediately; progress is `cleanupStatus`. Awaiting the work
8525
+ * is how `addons.custom` hit the 60 s UDS deadline while reclaim continued
8526
+ * with no operator-visible status.
8527
+ */
8528
+ var StorageCleanupPhaseSchema = _enum([
8529
+ "orphans",
8530
+ "debug-media",
8531
+ "ghost-ledger",
8532
+ "done",
8533
+ "failed",
8534
+ "cancelled"
8535
+ ]);
8536
+ var StorageCleanupInputSchema = object({
8537
+ /** Also walk motion stills / track filmstrips. Off by default. */
8538
+ includeDebugMedia: boolean().optional() });
8539
+ var StorageCleanupJobSchema = object({
8540
+ jobId: string(),
8541
+ phase: StorageCleanupPhaseSchema,
8542
+ includeDebugMedia: boolean(),
8543
+ orphansReclaimed: number().int().nonnegative(),
8544
+ orphanBytesReclaimed: number().int().nonnegative(),
8545
+ debugMediaReclaimed: number().int().nonnegative(),
8546
+ debugMediaBytesReclaimed: number().int().nonnegative(),
8547
+ ghostsForgotten: number().int().nonnegative(),
8548
+ ghostBytesForgotten: number().int().nonnegative(),
8549
+ /** Short operator-facing line: current collection, pass, or location. */
8550
+ detail: string().nullable(),
8551
+ cancelRequested: boolean(),
8552
+ startedAt: number(),
8553
+ updatedAt: number(),
8554
+ finishedAt: number().nullable(),
8555
+ error: string().nullable()
8556
+ });
8557
+ var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
8558
+ /**
8387
8559
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8388
8560
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8389
8561
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8411,11 +8583,10 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8411
8583
  * The default location for a type uses `id === <type>:default` by
8412
8584
  * convention (the bare type ref like `'backups'` resolves to it).
8413
8585
  *
8414
- * `isSystem: true` marks a location as orchestrator-seeded and
8415
- * undeletable. The bootstrap-installed defaults (one per type) carry
8416
- * this flag; operator-added locations don't. Editing the config of
8417
- * a system location is allowed (path migration, provider swap) but
8418
- * deleting it is rejected at the cap level.
8586
+ * `isSystem` is a legacy persisted flag. Seed still creates the initial
8587
+ * `<type>:default` locations; the flag is no longer a lock, a badge, or a
8588
+ * prune selector. New writes leave it false. Deletion is gated on uniqueness
8589
+ * / last-enabled, not on this bit.
8419
8590
  */
8420
8591
  var StorageLocationSchema = object({
8421
8592
  id: string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
@@ -12941,6 +13112,12 @@ method(object({
12941
13112
  }), _void(), {
12942
13113
  kind: "mutation",
12943
13114
  auth: "admin"
13115
+ }), method(object({
13116
+ from: string(),
13117
+ to: string()
13118
+ }), object({ moved: number() }), {
13119
+ kind: "mutation",
13120
+ auth: "admin"
12944
13121
  }), method(object({
12945
13122
  deviceId: number(),
12946
13123
  disabled: boolean()
@@ -18949,53 +19126,15 @@ var RecentTracksPageSchema = object({
18949
19126
  /** Cursor for the next page, or null when this page is the last. */
18950
19127
  nextCursor: string().nullable()
18951
19128
  });
18952
- var LIST_GROUPS_DEFAULT_LIMIT = 40;
18953
- var LIST_GROUPS_MAX_LIMIT = 100;
18954
- var AnalyticsGroupRecordSchema = object({
18955
- id: string(),
18956
- deviceId: number().int(),
18957
- openedAt: number().int(),
18958
- closedAt: number().int(),
18959
- timestamp: number().int(),
18960
- memberCount: number().int(),
18961
- memberTrackIds: array(string()).readonly(),
18962
- className: string(),
18963
- classes: array(string()).readonly(),
18964
- /** Relative event-media path, or null when the group has no picture yet. */
18965
- mediaUrl: string().nullable(),
18966
- singleton: boolean()
18967
- });
18968
- var AnalyticsGroupMemberSchema = object({
18969
- trackId: string(),
18970
- deviceId: number().int(),
18971
- className: string(),
18972
- firstSeen: number().int(),
18973
- lastSeen: number().int(),
18974
- mediaUrl: string().nullable()
18975
- });
18976
- var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
18977
- var ListGroupsQueryInput = object({
18978
- /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
18979
- deviceIds: array(number()),
18980
- /** Window lower bound on `closedAt` (inclusive). */
18981
- since: number().optional(),
18982
- /** Window upper bound on `openedAt` (inclusive). */
18983
- until: number().optional(),
18984
- limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
18985
- /** Opaque continuation cursor from a previous page's `nextCursor`. */
18986
- cursor: string().optional()
18987
- });
18988
- var ListGroupsPageSchema = object({
18989
- groups: array(AnalyticsGroupRecordSchema).readonly(),
18990
- nextCursor: string().nullable()
18991
- });
19129
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19130
+ var KEY_EVENTS_MAX_LIMIT = 200;
18992
19131
  var KeyEventQueryInput = object({
18993
19132
  deviceId: number(),
18994
19133
  /** Window lower bound (track firstSeen ≥ since). */
18995
19134
  since: number(),
18996
19135
  /** Window upper bound (track firstSeen ≤ until). */
18997
19136
  until: number(),
18998
- limit: number().int().min(1).max(200).default(50),
19137
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18999
19138
  /** Drop tracks scoring below this importance. */
19000
19139
  minImportance: number().min(0).max(1).optional(),
19001
19140
  /** Restrict to a single class (e.g. 'person'). */
@@ -19017,6 +19156,32 @@ var KeyEventSchema = object({
19017
19156
  ...TrackFlagFields,
19018
19157
  ...TrackRetrainFields
19019
19158
  });
19159
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19160
+ var KeyEventBatchQueryInput = object({
19161
+ deviceIds: array(number()).min(1).max(200),
19162
+ since: number(),
19163
+ until: number(),
19164
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19165
+ * across the set, which would let a busy camera starve a quiet one of its
19166
+ * rows and change what the merged feed contains. */
19167
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19168
+ minImportance: number().min(0).max(1).optional(),
19169
+ classFilter: string().optional()
19170
+ });
19171
+ /**
19172
+ * One camera's key events in a batch answer.
19173
+ *
19174
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19175
+ * error rather than throwing, so a camera whose store read failed and one with
19176
+ * no events in the window were ALREADY indistinguishable per camera — the
19177
+ * batch does not make that worse, and the row keeps the deviceId the single
19178
+ * method's output never carried (the caller used to stamp it from the fan-out
19179
+ * key, which only worked because there was one query per camera).
19180
+ */
19181
+ var KeyEventsForDeviceSchema = object({
19182
+ deviceId: number(),
19183
+ events: array(KeyEventSchema).readonly()
19184
+ });
19020
19185
  object({
19021
19186
  trackId: string(),
19022
19187
  className: string(),
@@ -19064,9 +19229,7 @@ var TrackCascadeCountsSchema = object({
19064
19229
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
19065
19230
  plates: number().int(),
19066
19231
  /** Per-track CLIP search vectors removed (best-effort). */
19067
- embeddings: number().int(),
19068
- /** Group membership + group rows removed with their last member (best-effort). */
19069
- groups: number().int()
19232
+ embeddings: number().int()
19070
19233
  });
19071
19234
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
19072
19235
  var DiskReconcileCountsSchema = object({
@@ -19236,6 +19399,47 @@ var RebuildStatusSchema = object({
19236
19399
  /** Present when the pass ended by throwing. */
19237
19400
  error: string().nullable()
19238
19401
  });
19402
+ /**
19403
+ * Acknowledgement that a debug-media reclaim STARTED.
19404
+ *
19405
+ * The pass is paced at ~2 MB/s over a standing 100 GB — tens of minutes — so
19406
+ * it runs detached and this returns immediately. Awaiting it is how the
19407
+ * `addons.custom` door hit the 60 s UDS deadline while the walk carried on
19408
+ * with no status. Poll {@link pipelineAnalyticsCapability.methods.getMediaReclaimStatus}.
19409
+ */
19410
+ var MediaReclaimStartResultSchema = object({
19411
+ started: boolean(),
19412
+ /** True when a pass was already running; the new request is ignored. */
19413
+ alreadyRunning: boolean()
19414
+ });
19415
+ var MediaReclaimInputSchema = object({
19416
+ mode: _enum(["report", "reclaim"]).default("report"),
19417
+ scopes: array(_enum(["motion-still", "track-filmstrip"])).min(1).optional(),
19418
+ deviceIds: array(number().int()).min(1).optional(),
19419
+ restart: boolean().optional(),
19420
+ pageSize: number().int().min(50).max(5e3).optional(),
19421
+ maxRowsPerDevice: number().int().min(1).max(5e5).optional(),
19422
+ maxReclaimPerDevice: number().int().min(1).max(5e5).optional(),
19423
+ maxBytesPerRun: number().int().min(1).optional(),
19424
+ budgetMinutes: number().int().min(1).max(720).optional(),
19425
+ throttleBytesPerSec: number().int().min(64 * 1024).optional(),
19426
+ graceMinutes: number().int().min(1).max(10080).optional()
19427
+ });
19428
+ var MediaReclaimStatusSchema = object({
19429
+ running: boolean(),
19430
+ mode: _enum(["report", "reclaim"]).nullable(),
19431
+ totalExamined: number(),
19432
+ totalEligible: number(),
19433
+ totalReclaimed: number(),
19434
+ totalBytesReclaimed: number(),
19435
+ totalRefused: number(),
19436
+ /** Device+scope windows finished in this pass. */
19437
+ devicesDone: number(),
19438
+ complete: boolean().nullable(),
19439
+ startedAtMs: number().nullable(),
19440
+ finishedAtMs: number().nullable(),
19441
+ error: string().nullable()
19442
+ });
19239
19443
  var ReplayFrameInputSchema = object({
19240
19444
  timestamp: number(),
19241
19445
  frame: PipelineRunResultBridge
@@ -19274,10 +19478,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19274
19478
  * stationary registry). Default false: the timeline lists passages,
19275
19479
  * not parking records (operator decision, 2026-08-15). */
19276
19480
  includeStationary: boolean().optional()
19277
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
19278
- deviceId: number(),
19279
- groupId: string().min(1)
19280
- }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
19481
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19281
19482
  kind: "mutation",
19282
19483
  auth: "admin"
19283
19484
  }), 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({
@@ -19286,7 +19487,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19286
19487
  until: number().optional(),
19287
19488
  kinds: array(string()).optional(),
19288
19489
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19289
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19490
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19290
19491
  deviceId: number(),
19291
19492
  since: number(),
19292
19493
  until: number(),
@@ -19377,6 +19578,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19377
19578
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19378
19579
  kind: "query",
19379
19580
  auth: "admin"
19581
+ }), method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
19582
+ kind: "mutation",
19583
+ auth: "admin"
19584
+ }), method(object({}), MediaReclaimStatusSchema, {
19585
+ kind: "query",
19586
+ auth: "admin"
19380
19587
  }), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
19381
19588
  kind: "query",
19382
19589
  auth: "admin"
@@ -21451,7 +21658,13 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
21451
21658
  }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
21452
21659
  kind: "mutation",
21453
21660
  auth: "admin"
21454
- });
21661
+ }), method(StorageCleanupInputSchema, object({ jobId: string() }), {
21662
+ kind: "mutation",
21663
+ auth: "admin"
21664
+ }), method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
21665
+ kind: "mutation",
21666
+ auth: "admin"
21667
+ }), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
21455
21668
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
21456
21669
  providerId: string().min(1),
21457
21670
  displayName: string().min(1),
@@ -28632,6 +28845,33 @@ var RecordingRebalanceInputSchema = object({
28632
28845
  minMoveGb: number().min(0).optional()
28633
28846
  });
28634
28847
  /**
28848
+ * Operator-facing placement of one camera onto a recordings location.
28849
+ *
28850
+ * `pinLocationId` is the operator instruction: a location id, or `null` for
28851
+ * Auto (the planner may move this camera). `locationId` is where high/mid
28852
+ * currently write — the plan, which may disagree with the pin when Auto.
28853
+ */
28854
+ var RecordingDevicePlacementSchema = object({
28855
+ deviceId: number().int(),
28856
+ profile: string(),
28857
+ locationId: string()
28858
+ });
28859
+ var RecordingDevicePinSchema = object({
28860
+ deviceId: number().int(),
28861
+ /** Recordings-class location this camera is pinned to. */
28862
+ locationId: string()
28863
+ });
28864
+ var RecordingPlacementViewSchema = object({
28865
+ assignments: array(RecordingDevicePlacementSchema),
28866
+ pins: array(RecordingDevicePinSchema),
28867
+ defaultLocations: record(string(), string())
28868
+ });
28869
+ var RecordingSetDevicePlacementInputSchema = object({
28870
+ deviceId: number().int(),
28871
+ /** `null` = Auto. Otherwise a `recordings:*` location id. */
28872
+ locationId: string().nullable()
28873
+ });
28874
+ /**
28635
28875
  * Result of locating footage at a wall-clock instant for one device/profile.
28636
28876
  * `segment` carries the covering segment's window; `gap` reports the forward
28637
28877
  * nearest covered edge (`null` past the end of footage / when none exists)
@@ -28845,6 +29085,9 @@ method(object({
28845
29085
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
28846
29086
  kind: "query",
28847
29087
  auth: "admin"
29088
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29089
+ kind: "mutation",
29090
+ auth: "admin"
28848
29091
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
28849
29092
  kind: "mutation",
28850
29093
  auth: "admin"
@@ -28854,6 +29097,12 @@ method(object({
28854
29097
  }), method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
28855
29098
  kind: "mutation",
28856
29099
  auth: "admin"
29100
+ }), method(object({}), RecordingPlacementViewSchema, {
29101
+ kind: "query",
29102
+ auth: "admin"
29103
+ }), method(RecordingSetDevicePlacementInputSchema, object({ ok: literal(true) }), {
29104
+ kind: "mutation",
29105
+ auth: "admin"
28857
29106
  });
28858
29107
  /**
28859
29108
  * `recording-export` cap — render a footage time range into a single downloadable
@@ -29236,6 +29485,25 @@ var SceneMonitorStatusSchema = object({
29236
29485
  monitors: array(SceneMonitorSchema),
29237
29486
  lastFetchedAt: number()
29238
29487
  });
29488
+ /**
29489
+ * One camera's row in a `listScenesBatch` answer.
29490
+ *
29491
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
29492
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
29493
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
29494
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
29495
+ * configured. Fanned out per camera the difference was visible — one query
29496
+ * errored while the others resolved — and a batch that returned only the rows
29497
+ * it managed would have destroyed it, silently, by making an unreachable camera
29498
+ * indistinguishable from one that answered `monitors: []`.
29499
+ *
29500
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
29501
+ * could not be read"; `status.monitors: []` means "read, and it has none".
29502
+ */
29503
+ var SceneMonitorStatusForDeviceSchema = object({
29504
+ deviceId: number(),
29505
+ status: SceneMonitorStatusSchema.nullable()
29506
+ });
29239
29507
  var sceneMonitorCapability = {
29240
29508
  name: "scene-monitor",
29241
29509
  scope: "device",
@@ -29245,6 +29513,22 @@ var sceneMonitorCapability = {
29245
29513
  deviceTypes: [DeviceType.Camera],
29246
29514
  methods: {
29247
29515
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
29516
+ /**
29517
+ * The same answer, for a SET of cameras, in one round trip.
29518
+ *
29519
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
29520
+ * poll behind the push slice. Fanned out client-side that was one query
29521
+ * per camera — 29 round trips through the browser, the hub and the
29522
+ * post-analysis runner every 30 seconds to read an in-memory map the
29523
+ * owner had already merged. The work is unchanged (`statusFor` per
29524
+ * device, all in-process at the owner); what collapses is the transport.
29525
+ *
29526
+ * A camera that cannot answer still gets a row, with `status: null` —
29527
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
29528
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
29529
+ * tell which two are missing, or that any are.
29530
+ */
29531
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29248
29532
  createScene: method(object({
29249
29533
  deviceId: number(),
29250
29534
  label: string(),
@@ -31278,6 +31562,27 @@ var CameraOccupancySnapshotSchema = object({
31278
31562
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31279
31563
  });
31280
31564
  /**
31565
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
31566
+ *
31567
+ * THREE outcomes, and the single-camera method could only express two of them
31568
+ * because `snapshot: null` was already spoken for:
31569
+ *
31570
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
31571
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
31572
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
31573
+ * - `read: 'unreadable'` — the owner could not answer for this
31574
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
31575
+ *
31576
+ * Collapsing the last two is the failure this field exists to prevent: a
31577
+ * hydration that threw would otherwise render as an empty Stationary section,
31578
+ * which is a definite claim about a camera nobody could read.
31579
+ */
31580
+ var CameraOccupancySnapshotForDeviceSchema = object({
31581
+ deviceId: number(),
31582
+ read: _enum(["read", "unreadable"]),
31583
+ snapshot: CameraOccupancySnapshotSchema.nullable()
31584
+ });
31585
+ /**
31281
31586
  * Time-series resolution. The history methods return one bucket per
31282
31587
  * step over the requested range. Smaller resolutions cost more
31283
31588
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31334,6 +31639,20 @@ var zoneAnalyticsCapability = {
31334
31639
  * (no inference result emitted since boot or since binding was
31335
31640
  * activated). */
31336
31641
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
31642
+ /**
31643
+ * The same snapshot, for a SET of cameras, in one round trip.
31644
+ *
31645
+ * The Events page's Stationary section polls this every 15s for every
31646
+ * selected camera. Fanned out client-side that is one query per camera to
31647
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
31648
+ * costs everything. Batched, N transports become one and the per-device
31649
+ * work is unchanged.
31650
+ *
31651
+ * Every requested deviceId gets a row, tagged `read` — see
31652
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
31653
+ * not answer for is `'unreadable'`, never an empty reading.
31654
+ */
31655
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31337
31656
  /** Time-series object count inside one zone. `className` optional —
31338
31657
  * omit to count every class in the zone. */
31339
31658
  getZoneHistory: method(object({
@@ -34257,6 +34576,12 @@ Object.freeze({
34257
34576
  addonId: null,
34258
34577
  access: "delete"
34259
34578
  },
34579
+ "deviceManager.renameLocation": {
34580
+ capName: "device-manager",
34581
+ capScope: "system",
34582
+ addonId: null,
34583
+ access: "create"
34584
+ },
34260
34585
  "deviceManager.runDeviceAction": {
34261
34586
  capName: "device-manager",
34262
34587
  capScope: "system",
@@ -35949,13 +36274,19 @@ Object.freeze({
35949
36274
  addonId: null,
35950
36275
  access: "view"
35951
36276
  },
35952
- "pipelineAnalytics.getGroup": {
36277
+ "pipelineAnalytics.getKeyEvents": {
35953
36278
  capName: "pipeline-analytics",
35954
36279
  capScope: "device",
35955
36280
  addonId: null,
35956
36281
  access: "view"
35957
36282
  },
35958
- "pipelineAnalytics.getKeyEvents": {
36283
+ "pipelineAnalytics.getKeyEventsBatch": {
36284
+ capName: "pipeline-analytics",
36285
+ capScope: "device",
36286
+ addonId: null,
36287
+ access: "view"
36288
+ },
36289
+ "pipelineAnalytics.getMediaReclaimStatus": {
35959
36290
  capName: "pipeline-analytics",
35960
36291
  capScope: "device",
35961
36292
  addonId: null,
@@ -36045,12 +36376,6 @@ Object.freeze({
36045
36376
  addonId: null,
36046
36377
  access: "view"
36047
36378
  },
36048
- "pipelineAnalytics.listGroups": {
36049
- capName: "pipeline-analytics",
36050
- capScope: "device",
36051
- addonId: null,
36052
- access: "view"
36053
- },
36054
36379
  "pipelineAnalytics.listOpsLog": {
36055
36380
  capName: "pipeline-analytics",
36056
36381
  capScope: "device",
@@ -36135,6 +36460,12 @@ Object.freeze({
36135
36460
  addonId: null,
36136
36461
  access: "create"
36137
36462
  },
36463
+ "pipelineAnalytics.reclaimDebugMedia": {
36464
+ capName: "pipeline-analytics",
36465
+ capScope: "device",
36466
+ addonId: null,
36467
+ access: "create"
36468
+ },
36138
36469
  "pipelineAnalytics.reconcileFromDisk": {
36139
36470
  capName: "pipeline-analytics",
36140
36471
  capScope: "device",
@@ -37059,6 +37390,12 @@ Object.freeze({
37059
37390
  addonId: null,
37060
37391
  access: "view"
37061
37392
  },
37393
+ "recording.getPlacement": {
37394
+ capName: "recording",
37395
+ capScope: "system",
37396
+ addonId: null,
37397
+ access: "view"
37398
+ },
37062
37399
  "recording.getPlaybackManifest": {
37063
37400
  capName: "recording",
37064
37401
  capScope: "system",
@@ -37137,6 +37474,12 @@ Object.freeze({
37137
37474
  addonId: null,
37138
37475
  access: "view"
37139
37476
  },
37477
+ "recording.reconcileLedgerAgainstDisk": {
37478
+ capName: "recording",
37479
+ capScope: "system",
37480
+ addonId: null,
37481
+ access: "create"
37482
+ },
37140
37483
  "recording.refreshStorageLocationsForMigration": {
37141
37484
  capName: "recording",
37142
37485
  capScope: "system",
@@ -37179,6 +37522,12 @@ Object.freeze({
37179
37522
  addonId: null,
37180
37523
  access: "create"
37181
37524
  },
37525
+ "recording.setDevicePlacement": {
37526
+ capName: "recording",
37527
+ capScope: "system",
37528
+ addonId: null,
37529
+ access: "create"
37530
+ },
37182
37531
  "recording.startStorageMigrationMove": {
37183
37532
  capName: "recording",
37184
37533
  capScope: "system",
@@ -37263,6 +37612,12 @@ Object.freeze({
37263
37612
  addonId: null,
37264
37613
  access: "view"
37265
37614
  },
37615
+ "sceneMonitor.listScenesBatch": {
37616
+ capName: "scene-monitor",
37617
+ capScope: "device",
37618
+ addonId: null,
37619
+ access: "view"
37620
+ },
37266
37621
  "sceneMonitor.recheckNow": {
37267
37622
  capName: "scene-monitor",
37268
37623
  capScope: "device",
@@ -37617,12 +37972,36 @@ Object.freeze({
37617
37972
  addonId: null,
37618
37973
  access: "create"
37619
37974
  },
37975
+ "storageMigration.cleanupCancel": {
37976
+ capName: "storage-migration",
37977
+ capScope: "system",
37978
+ addonId: null,
37979
+ access: "create"
37980
+ },
37981
+ "storageMigration.cleanupStart": {
37982
+ capName: "storage-migration",
37983
+ capScope: "system",
37984
+ addonId: null,
37985
+ access: "create"
37986
+ },
37987
+ "storageMigration.cleanupStatus": {
37988
+ capName: "storage-migration",
37989
+ capScope: "system",
37990
+ addonId: null,
37991
+ access: "view"
37992
+ },
37620
37993
  "storageMigration.drain": {
37621
37994
  capName: "storage-migration",
37622
37995
  capScope: "system",
37623
37996
  addonId: null,
37624
37997
  access: "create"
37625
37998
  },
37999
+ "storageMigration.history": {
38000
+ capName: "storage-migration",
38001
+ capScope: "system",
38002
+ addonId: null,
38003
+ access: "view"
38004
+ },
37626
38005
  "storageMigration.movers": {
37627
38006
  capName: "storage-migration",
37628
38007
  capScope: "system",
@@ -38619,6 +38998,12 @@ Object.freeze({
38619
38998
  addonId: null,
38620
38999
  access: "view"
38621
39000
  },
39001
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39002
+ capName: "zone-analytics",
39003
+ capScope: "device",
39004
+ addonId: null,
39005
+ access: "view"
39006
+ },
38622
39007
  "zoneAnalytics.getUnzonedHistory": {
38623
39008
  capName: "zone-analytics",
38624
39009
  capScope: "device",
@@ -39613,14 +39998,14 @@ Object.freeze({
39613
39998
  form: "single",
39614
39999
  optional: true
39615
40000
  }],
39616
- "pipelineAnalytics.getGroup": [{
40001
+ "pipelineAnalytics.getKeyEvents": [{
39617
40002
  name: "deviceId",
39618
40003
  form: "single",
39619
40004
  optional: false
39620
40005
  }],
39621
- "pipelineAnalytics.getKeyEvents": [{
39622
- name: "deviceId",
39623
- form: "single",
40006
+ "pipelineAnalytics.getKeyEventsBatch": [{
40007
+ name: "deviceIds",
40008
+ form: "array",
39624
40009
  optional: false
39625
40010
  }],
39626
40011
  "pipelineAnalytics.getMotionEvents": [{
@@ -39678,11 +40063,6 @@ Object.freeze({
39678
40063
  form: "single",
39679
40064
  optional: false
39680
40065
  }],
39681
- "pipelineAnalytics.listGroups": [{
39682
- name: "deviceIds",
39683
- form: "array",
39684
- optional: false
39685
- }],
39686
40066
  "pipelineAnalytics.listOpsLog": [{
39687
40067
  name: "deviceId",
39688
40068
  form: "single",
@@ -39728,6 +40108,11 @@ Object.freeze({
39728
40108
  form: "single",
39729
40109
  optional: true
39730
40110
  }],
40111
+ "pipelineAnalytics.reclaimDebugMedia": [{
40112
+ name: "deviceIds",
40113
+ form: "array",
40114
+ optional: true
40115
+ }],
39731
40116
  "pipelineAnalytics.reconcileFromDisk": [{
39732
40117
  name: "deviceId",
39733
40118
  form: "single",
@@ -40063,6 +40448,11 @@ Object.freeze({
40063
40448
  form: "single",
40064
40449
  optional: false
40065
40450
  }],
40451
+ "recording.reconcileLedgerAgainstDisk": [{
40452
+ name: "deviceId",
40453
+ form: "single",
40454
+ optional: true
40455
+ }],
40066
40456
  "recording.relocateFootage": [{
40067
40457
  name: "deviceId",
40068
40458
  form: "single",
@@ -40088,6 +40478,11 @@ Object.freeze({
40088
40478
  form: "single",
40089
40479
  optional: false
40090
40480
  }],
40481
+ "recording.setDevicePlacement": [{
40482
+ name: "deviceId",
40483
+ form: "single",
40484
+ optional: false
40485
+ }],
40091
40486
  "recording.startStorageMigrationMove": [{
40092
40487
  name: "deviceId",
40093
40488
  form: "single",
@@ -40128,6 +40523,11 @@ Object.freeze({
40128
40523
  form: "single",
40129
40524
  optional: false
40130
40525
  }],
40526
+ "sceneMonitor.listScenesBatch": [{
40527
+ name: "deviceIds",
40528
+ form: "array",
40529
+ optional: false
40530
+ }],
40131
40531
  "sceneMonitor.recheckNow": [{
40132
40532
  name: "deviceId",
40133
40533
  form: "single",
@@ -40389,6 +40789,11 @@ Object.freeze({
40389
40789
  form: "single",
40390
40790
  optional: false
40391
40791
  }],
40792
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
40793
+ name: "deviceIds",
40794
+ form: "array",
40795
+ optional: false
40796
+ }],
40392
40797
  "zoneAnalytics.getUnzonedHistory": [{
40393
40798
  name: "deviceId",
40394
40799
  form: "single",