@camstack/addon-notifiers 1.2.52 → 1.2.54

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 +450 -75
  2. package/dist/addon.mjs +450 -75
  3. package/package.json +1 -1
package/dist/addon.js CHANGED
@@ -8151,6 +8151,20 @@ var RelocateJobSchema = object({
8151
8151
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8152
8152
  */
8153
8153
  rowsReconciled: number().int().nonnegative().optional(),
8154
+ /**
8155
+ * Rows this run FORGOT because the file they name is not on disk.
8156
+ *
8157
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8158
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8159
+ * and the durable row is dropped through the same channel eviction uses. It
8160
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8161
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8162
+ * the same failure as one that quietly skips them (D295).
8163
+ *
8164
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8165
+ * ledger claimed 5.65 GB of footage that no longer existed.
8166
+ */
8167
+ rowsForgotten: number().int().nonnegative().optional(),
8154
8168
  startedAt: number(),
8155
8169
  finishedAt: number().nullable(),
8156
8170
  error: string().nullable()
@@ -8215,6 +8229,13 @@ var MediaRelocateModeSchema = _enum([
8215
8229
  ]);
8216
8230
  var RelocateMediaInputSchema = object({
8217
8231
  toLocationId: string(),
8232
+ /**
8233
+ * Restrict the pass to rows currently on this location. Omitted / `'*'` =
8234
+ * every row that is not already on `toLocationId` (the historical
8235
+ * behaviour). A named source is what a from→to migration needs: without it
8236
+ * "move events off disk 2" also emptied disk 1.
8237
+ */
8238
+ fromLocationId: string().optional(),
8218
8239
  throttleMbps: number().min(1).max(1e3).optional(),
8219
8240
  /** Omitted = `move`, the pre-existing behaviour. */
8220
8241
  mode: MediaRelocateModeSchema.optional()
@@ -8282,6 +8303,19 @@ var StorageMigrationDestinationsSchema = object({
8282
8303
  galleryMedia: string().min(1).optional()
8283
8304
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
8284
8305
  /**
8306
+ * Optional named source per class. Omitted = the class's current default
8307
+ * (the historical behaviour). A named source that is NOT the default is a
8308
+ * drain of that disk: bytes move, the default stays, and the source is
8309
+ * disabled when the move finishes.
8310
+ */
8311
+ var StorageMigrationSourcesSchema = object({
8312
+ recordings: string().min(1).optional(),
8313
+ recordingsLow: string().min(1).optional(),
8314
+ eventMedia: string().min(1).optional(),
8315
+ backups: string().min(1).optional(),
8316
+ galleryMedia: string().min(1).optional()
8317
+ }).optional();
8318
+ /**
8285
8319
  * How a migration sequences the cutover against the byte move.
8286
8320
  *
8287
8321
  * - `blocking` — the historical order: pause, move every byte, repoint,
@@ -8303,6 +8337,8 @@ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
8303
8337
  /** Shared input for planning and starting an orchestrated storage migration. */
8304
8338
  var StorageMigrationInputSchema = object({
8305
8339
  destinations: StorageMigrationDestinationsSchema,
8340
+ /** Omitted = each class's current default. */
8341
+ sources: StorageMigrationSourcesSchema,
8306
8342
  throttleMbps: number().min(1).max(1e3).optional(),
8307
8343
  /** Omitted = `blocking`, which stays the default. */
8308
8344
  mode: StorageMigrationModeSchema.optional()
@@ -8382,6 +8418,13 @@ var StorageMigrationMoveSchema = object({
8382
8418
  storageClass: StorageMigrationClassSchema,
8383
8419
  fromLocationId: string(),
8384
8420
  toLocationId: string(),
8421
+ /**
8422
+ * True when `from` was NOT the class default at plan time. The move still
8423
+ * copies bytes, but the default is left alone and the source is disabled
8424
+ * once the copy verifies. Absent on jobs planned before this field existed
8425
+ * — those jobs always repointed, which is `false`.
8426
+ */
8427
+ freezeSource: boolean().optional(),
8385
8428
  moverJobId: string().nullable(),
8386
8429
  state: RelocateJobStateSchema.nullable(),
8387
8430
  error: string().nullable(),
@@ -8395,6 +8438,7 @@ var StorageMigrationJobSchema = object({
8395
8438
  * can tell a seconds-long cutover from a thirty-hour one. */
8396
8439
  mode: StorageMigrationModeSchema,
8397
8440
  destinations: StorageMigrationDestinationsSchema,
8441
+ sources: StorageMigrationSourcesSchema,
8398
8442
  throttleMbps: number(),
8399
8443
  moves: array(StorageMigrationMoveSchema),
8400
8444
  pauseLeaseId: string().nullable(),
@@ -8420,6 +8464,7 @@ var StorageMigrationFindingSchema = object({
8420
8464
  });
8421
8465
  var StorageMigrationPlanSchema = object({
8422
8466
  destinations: StorageMigrationDestinationsSchema,
8467
+ sources: StorageMigrationSourcesSchema,
8423
8468
  /** The mode this plan was built for. A plan is only valid for its mode: the
8424
8469
  * `eventMedia` seal gate and the single-cardinality refusal both depend on
8425
8470
  * it. */
@@ -8427,7 +8472,8 @@ var StorageMigrationPlanSchema = object({
8427
8472
  moves: array(object({
8428
8473
  storageClass: StorageMigrationClassSchema,
8429
8474
  fromLocationId: string(),
8430
- toLocationId: string()
8475
+ toLocationId: string(),
8476
+ freezeSource: boolean().optional()
8431
8477
  })),
8432
8478
  findings: array(StorageMigrationFindingSchema)
8433
8479
  });
@@ -8514,16 +8560,142 @@ var RelocateResidueSchema = object({
8514
8560
  segments: number().int().nonnegative(),
8515
8561
  bytes: number().int().nonnegative()
8516
8562
  }).nullable();
8563
+ /**
8564
+ * Ask one location whether its durable hour rows describe the disk — the walk
8565
+ * (D319).
8566
+ *
8567
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8568
+ * missing tool is the question, and the dry run is how they sanity-check the
8569
+ * destructive run before authorising it.
8570
+ */
8571
+ var LedgerWalkInputSchema = object({
8572
+ locationId: string().min(1),
8573
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8574
+ apply: boolean().optional(),
8575
+ /** Narrow to one camera. */
8576
+ deviceId: number().int().positive().optional(),
8577
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8578
+ profiles: array(string().min(1)).optional()
8579
+ });
8580
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8581
+ var LedgerWalkRefusalSchema = _enum([
8582
+ "location-unknown",
8583
+ "source-writable",
8584
+ "no-ledger",
8585
+ "archive-unreadable",
8586
+ "anchor-absent",
8587
+ "anchor-unreadable",
8588
+ "anchor-moved"
8589
+ ]);
8590
+ _enum([
8591
+ "live-tail",
8592
+ "listing-error",
8593
+ "path-mismatch",
8594
+ "durable-refused"
8595
+ ]);
8596
+ /** Every skip reason, always present, always a number — so a reason that never
8597
+ * fired reports as zero rather than absent and the report shape is constant
8598
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8599
+ var LedgerWalkSkipCountsSchema = object({
8600
+ "live-tail": number().int().nonnegative(),
8601
+ "listing-error": number().int().nonnegative(),
8602
+ "path-mismatch": number().int().nonnegative(),
8603
+ "durable-refused": number().int().nonnegative()
8604
+ });
8605
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8606
+ var LedgerWalkDeviceReportSchema = object({
8607
+ deviceId: number().int(),
8608
+ hoursWalked: number().int().nonnegative(),
8609
+ hoursMissing: number().int().nonnegative(),
8610
+ ghostSegments: number().int().nonnegative(),
8611
+ ghostBytes: number().int().nonnegative(),
8612
+ forgottenSegments: number().int().nonnegative(),
8613
+ orphanFiles: number().int().nonnegative()
8614
+ });
8615
+ /**
8616
+ * What one walk claimed, listed, found and (only when armed) forgot.
8617
+ *
8618
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8619
+ * walk that saw a fraction of the location is visible in its own report rather
8620
+ * than in the absence of one.
8621
+ */
8622
+ var LedgerWalkReportSchema = object({
8623
+ locationId: string(),
8624
+ applied: boolean(),
8625
+ refused: LedgerWalkRefusalSchema.nullable(),
8626
+ archiveSegments: number().int().nonnegative().nullable(),
8627
+ archiveBytes: number().int().nonnegative().nullable(),
8628
+ hoursClaimed: number().int().nonnegative(),
8629
+ hoursWalked: number().int().nonnegative(),
8630
+ hoursMissing: number().int().nonnegative(),
8631
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8632
+ listings: number().int().nonnegative(),
8633
+ segmentsClaimed: number().int().nonnegative(),
8634
+ ghostSegments: number().int().nonnegative(),
8635
+ ghostBytes: number().int().nonnegative(),
8636
+ ghostHoursWhole: number().int().nonnegative(),
8637
+ forgottenSegments: number().int().nonnegative(),
8638
+ forgottenBytes: number().int().nonnegative(),
8639
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8640
+ orphanFiles: number().int().nonnegative(),
8641
+ orphanSample: array(string()).readonly(),
8642
+ hoursSkipped: number().int().nonnegative(),
8643
+ skippedByReason: LedgerWalkSkipCountsSchema,
8644
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8645
+ bounded: boolean(),
8646
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8647
+ });
8517
8648
  /** How many rows a media pass would still act on against a given target — the
8518
8649
  * media lane's denominator AND its residue, from ONE derivation so the two can
8519
8650
  * never disagree. `null` = the count could not be taken. */
8520
8651
  var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
8521
8652
  var RelocatableMediaCountInputSchema = object({
8522
8653
  toLocationId: string().min(1),
8654
+ /** Restrict the count to one source; omitted / `'*'` = every non-target row. */
8655
+ fromLocationId: string().optional(),
8523
8656
  /** Omitted = `move`. */
8524
8657
  mode: MediaRelocateModeSchema.optional()
8525
8658
  });
8526
8659
  /**
8660
+ * Operator cleanup of leftover analytics rows, optional debug media, and
8661
+ * ghost ledger entries on frozen footage locations.
8662
+ *
8663
+ * A pass is tens of minutes on a standing backlog. The hub method RETURNS
8664
+ * `{ jobId }` immediately; progress is `cleanupStatus`. Awaiting the work
8665
+ * is how `addons.custom` hit the 60 s UDS deadline while reclaim continued
8666
+ * with no operator-visible status.
8667
+ */
8668
+ var StorageCleanupPhaseSchema = _enum([
8669
+ "orphans",
8670
+ "debug-media",
8671
+ "ghost-ledger",
8672
+ "done",
8673
+ "failed",
8674
+ "cancelled"
8675
+ ]);
8676
+ var StorageCleanupInputSchema = object({
8677
+ /** Also walk motion stills / track filmstrips. Off by default. */
8678
+ includeDebugMedia: boolean().optional() });
8679
+ var StorageCleanupJobSchema = object({
8680
+ jobId: string(),
8681
+ phase: StorageCleanupPhaseSchema,
8682
+ includeDebugMedia: boolean(),
8683
+ orphansReclaimed: number().int().nonnegative(),
8684
+ orphanBytesReclaimed: number().int().nonnegative(),
8685
+ debugMediaReclaimed: number().int().nonnegative(),
8686
+ debugMediaBytesReclaimed: number().int().nonnegative(),
8687
+ ghostsForgotten: number().int().nonnegative(),
8688
+ ghostBytesForgotten: number().int().nonnegative(),
8689
+ /** Short operator-facing line: current collection, pass, or location. */
8690
+ detail: string().nullable(),
8691
+ cancelRequested: boolean(),
8692
+ startedAt: number(),
8693
+ updatedAt: number(),
8694
+ finishedAt: number().nullable(),
8695
+ error: string().nullable()
8696
+ });
8697
+ var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
8698
+ /**
8527
8699
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8528
8700
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8529
8701
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8551,11 +8723,10 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8551
8723
  * The default location for a type uses `id === <type>:default` by
8552
8724
  * convention (the bare type ref like `'backups'` resolves to it).
8553
8725
  *
8554
- * `isSystem: true` marks a location as orchestrator-seeded and
8555
- * undeletable. The bootstrap-installed defaults (one per type) carry
8556
- * this flag; operator-added locations don't. Editing the config of
8557
- * a system location is allowed (path migration, provider swap) but
8558
- * deleting it is rejected at the cap level.
8726
+ * `isSystem` is a legacy persisted flag. Seed still creates the initial
8727
+ * `<type>:default` locations; the flag is no longer a lock, a badge, or a
8728
+ * prune selector. New writes leave it false. Deletion is gated on uniqueness
8729
+ * / last-enabled, not on this bit.
8559
8730
  */
8560
8731
  var StorageLocationSchema = object({
8561
8732
  id: string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
@@ -12858,6 +13029,12 @@ method(object({
12858
13029
  }), _void(), {
12859
13030
  kind: "mutation",
12860
13031
  auth: "admin"
13032
+ }), method(object({
13033
+ from: string(),
13034
+ to: string()
13035
+ }), object({ moved: number() }), {
13036
+ kind: "mutation",
13037
+ auth: "admin"
12861
13038
  }), method(object({
12862
13039
  deviceId: number(),
12863
13040
  disabled: boolean()
@@ -18802,53 +18979,15 @@ var RecentTracksPageSchema = object({
18802
18979
  /** Cursor for the next page, or null when this page is the last. */
18803
18980
  nextCursor: string().nullable()
18804
18981
  });
18805
- var LIST_GROUPS_DEFAULT_LIMIT = 40;
18806
- var LIST_GROUPS_MAX_LIMIT = 100;
18807
- var AnalyticsGroupRecordSchema = object({
18808
- id: string(),
18809
- deviceId: number().int(),
18810
- openedAt: number().int(),
18811
- closedAt: number().int(),
18812
- timestamp: number().int(),
18813
- memberCount: number().int(),
18814
- memberTrackIds: array(string()).readonly(),
18815
- className: string(),
18816
- classes: array(string()).readonly(),
18817
- /** Relative event-media path, or null when the group has no picture yet. */
18818
- mediaUrl: string().nullable(),
18819
- singleton: boolean()
18820
- });
18821
- var AnalyticsGroupMemberSchema = object({
18822
- trackId: string(),
18823
- deviceId: number().int(),
18824
- className: string(),
18825
- firstSeen: number().int(),
18826
- lastSeen: number().int(),
18827
- mediaUrl: string().nullable()
18828
- });
18829
- var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
18830
- var ListGroupsQueryInput = object({
18831
- /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
18832
- deviceIds: array(number()),
18833
- /** Window lower bound on `closedAt` (inclusive). */
18834
- since: number().optional(),
18835
- /** Window upper bound on `openedAt` (inclusive). */
18836
- until: number().optional(),
18837
- limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
18838
- /** Opaque continuation cursor from a previous page's `nextCursor`. */
18839
- cursor: string().optional()
18840
- });
18841
- var ListGroupsPageSchema = object({
18842
- groups: array(AnalyticsGroupRecordSchema).readonly(),
18843
- nextCursor: string().nullable()
18844
- });
18982
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
18983
+ var KEY_EVENTS_MAX_LIMIT = 200;
18845
18984
  var KeyEventQueryInput = object({
18846
18985
  deviceId: number(),
18847
18986
  /** Window lower bound (track firstSeen ≥ since). */
18848
18987
  since: number(),
18849
18988
  /** Window upper bound (track firstSeen ≤ until). */
18850
18989
  until: number(),
18851
- limit: number().int().min(1).max(200).default(50),
18990
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
18852
18991
  /** Drop tracks scoring below this importance. */
18853
18992
  minImportance: number().min(0).max(1).optional(),
18854
18993
  /** Restrict to a single class (e.g. 'person'). */
@@ -18870,6 +19009,32 @@ var KeyEventSchema = object({
18870
19009
  ...TrackFlagFields,
18871
19010
  ...TrackRetrainFields
18872
19011
  });
19012
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19013
+ var KeyEventBatchQueryInput = object({
19014
+ deviceIds: array(number()).min(1).max(200),
19015
+ since: number(),
19016
+ until: number(),
19017
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19018
+ * across the set, which would let a busy camera starve a quiet one of its
19019
+ * rows and change what the merged feed contains. */
19020
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19021
+ minImportance: number().min(0).max(1).optional(),
19022
+ classFilter: string().optional()
19023
+ });
19024
+ /**
19025
+ * One camera's key events in a batch answer.
19026
+ *
19027
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19028
+ * error rather than throwing, so a camera whose store read failed and one with
19029
+ * no events in the window were ALREADY indistinguishable per camera — the
19030
+ * batch does not make that worse, and the row keeps the deviceId the single
19031
+ * method's output never carried (the caller used to stamp it from the fan-out
19032
+ * key, which only worked because there was one query per camera).
19033
+ */
19034
+ var KeyEventsForDeviceSchema = object({
19035
+ deviceId: number(),
19036
+ events: array(KeyEventSchema).readonly()
19037
+ });
18873
19038
  object({
18874
19039
  trackId: string(),
18875
19040
  className: string(),
@@ -18917,9 +19082,7 @@ var TrackCascadeCountsSchema = object({
18917
19082
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
18918
19083
  plates: number().int(),
18919
19084
  /** Per-track CLIP search vectors removed (best-effort). */
18920
- embeddings: number().int(),
18921
- /** Group membership + group rows removed with their last member (best-effort). */
18922
- groups: number().int()
19085
+ embeddings: number().int()
18923
19086
  });
18924
19087
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
18925
19088
  var DiskReconcileCountsSchema = object({
@@ -19089,6 +19252,47 @@ var RebuildStatusSchema = object({
19089
19252
  /** Present when the pass ended by throwing. */
19090
19253
  error: string().nullable()
19091
19254
  });
19255
+ /**
19256
+ * Acknowledgement that a debug-media reclaim STARTED.
19257
+ *
19258
+ * The pass is paced at ~2 MB/s over a standing 100 GB — tens of minutes — so
19259
+ * it runs detached and this returns immediately. Awaiting it is how the
19260
+ * `addons.custom` door hit the 60 s UDS deadline while the walk carried on
19261
+ * with no status. Poll {@link pipelineAnalyticsCapability.methods.getMediaReclaimStatus}.
19262
+ */
19263
+ var MediaReclaimStartResultSchema = object({
19264
+ started: boolean(),
19265
+ /** True when a pass was already running; the new request is ignored. */
19266
+ alreadyRunning: boolean()
19267
+ });
19268
+ var MediaReclaimInputSchema = object({
19269
+ mode: _enum(["report", "reclaim"]).default("report"),
19270
+ scopes: array(_enum(["motion-still", "track-filmstrip"])).min(1).optional(),
19271
+ deviceIds: array(number().int()).min(1).optional(),
19272
+ restart: boolean().optional(),
19273
+ pageSize: number().int().min(50).max(5e3).optional(),
19274
+ maxRowsPerDevice: number().int().min(1).max(5e5).optional(),
19275
+ maxReclaimPerDevice: number().int().min(1).max(5e5).optional(),
19276
+ maxBytesPerRun: number().int().min(1).optional(),
19277
+ budgetMinutes: number().int().min(1).max(720).optional(),
19278
+ throttleBytesPerSec: number().int().min(64 * 1024).optional(),
19279
+ graceMinutes: number().int().min(1).max(10080).optional()
19280
+ });
19281
+ var MediaReclaimStatusSchema = object({
19282
+ running: boolean(),
19283
+ mode: _enum(["report", "reclaim"]).nullable(),
19284
+ totalExamined: number(),
19285
+ totalEligible: number(),
19286
+ totalReclaimed: number(),
19287
+ totalBytesReclaimed: number(),
19288
+ totalRefused: number(),
19289
+ /** Device+scope windows finished in this pass. */
19290
+ devicesDone: number(),
19291
+ complete: boolean().nullable(),
19292
+ startedAtMs: number().nullable(),
19293
+ finishedAtMs: number().nullable(),
19294
+ error: string().nullable()
19295
+ });
19092
19296
  var ReplayFrameInputSchema = object({
19093
19297
  timestamp: number(),
19094
19298
  frame: PipelineRunResultBridge
@@ -19127,10 +19331,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19127
19331
  * stationary registry). Default false: the timeline lists passages,
19128
19332
  * not parking records (operator decision, 2026-08-15). */
19129
19333
  includeStationary: boolean().optional()
19130
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
19131
- deviceId: number(),
19132
- groupId: string().min(1)
19133
- }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
19334
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19134
19335
  kind: "mutation",
19135
19336
  auth: "admin"
19136
19337
  }), 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({
@@ -19139,7 +19340,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19139
19340
  until: number().optional(),
19140
19341
  kinds: array(string()).optional(),
19141
19342
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19142
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
19343
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19143
19344
  deviceId: number(),
19144
19345
  since: number(),
19145
19346
  until: number(),
@@ -19230,6 +19431,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19230
19431
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
19231
19432
  kind: "query",
19232
19433
  auth: "admin"
19434
+ }), method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
19435
+ kind: "mutation",
19436
+ auth: "admin"
19437
+ }), method(object({}), MediaReclaimStatusSchema, {
19438
+ kind: "query",
19439
+ auth: "admin"
19233
19440
  }), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
19234
19441
  kind: "query",
19235
19442
  auth: "admin"
@@ -21200,7 +21407,13 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
21200
21407
  }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
21201
21408
  kind: "mutation",
21202
21409
  auth: "admin"
21203
- });
21410
+ }), method(StorageCleanupInputSchema, object({ jobId: string() }), {
21411
+ kind: "mutation",
21412
+ auth: "admin"
21413
+ }), method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
21414
+ kind: "mutation",
21415
+ auth: "admin"
21416
+ }), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
21204
21417
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
21205
21418
  providerId: string().min(1),
21206
21419
  displayName: string().min(1),
@@ -26578,6 +26791,33 @@ var RecordingRebalanceInputSchema = object({
26578
26791
  minMoveGb: number().min(0).optional()
26579
26792
  });
26580
26793
  /**
26794
+ * Operator-facing placement of one camera onto a recordings location.
26795
+ *
26796
+ * `pinLocationId` is the operator instruction: a location id, or `null` for
26797
+ * Auto (the planner may move this camera). `locationId` is where high/mid
26798
+ * currently write — the plan, which may disagree with the pin when Auto.
26799
+ */
26800
+ var RecordingDevicePlacementSchema = object({
26801
+ deviceId: number().int(),
26802
+ profile: string(),
26803
+ locationId: string()
26804
+ });
26805
+ var RecordingDevicePinSchema = object({
26806
+ deviceId: number().int(),
26807
+ /** Recordings-class location this camera is pinned to. */
26808
+ locationId: string()
26809
+ });
26810
+ var RecordingPlacementViewSchema = object({
26811
+ assignments: array(RecordingDevicePlacementSchema),
26812
+ pins: array(RecordingDevicePinSchema),
26813
+ defaultLocations: record(string(), string())
26814
+ });
26815
+ var RecordingSetDevicePlacementInputSchema = object({
26816
+ deviceId: number().int(),
26817
+ /** `null` = Auto. Otherwise a `recordings:*` location id. */
26818
+ locationId: string().nullable()
26819
+ });
26820
+ /**
26581
26821
  * Result of locating footage at a wall-clock instant for one device/profile.
26582
26822
  * `segment` carries the covering segment's window; `gap` reports the forward
26583
26823
  * nearest covered edge (`null` past the end of footage / when none exists)
@@ -26791,6 +27031,9 @@ method(object({
26791
27031
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
26792
27032
  kind: "query",
26793
27033
  auth: "admin"
27034
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
27035
+ kind: "mutation",
27036
+ auth: "admin"
26794
27037
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
26795
27038
  kind: "mutation",
26796
27039
  auth: "admin"
@@ -26800,6 +27043,12 @@ method(object({
26800
27043
  }), method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
26801
27044
  kind: "mutation",
26802
27045
  auth: "admin"
27046
+ }), method(object({}), RecordingPlacementViewSchema, {
27047
+ kind: "query",
27048
+ auth: "admin"
27049
+ }), method(RecordingSetDevicePlacementInputSchema, object({ ok: literal(true) }), {
27050
+ kind: "mutation",
27051
+ auth: "admin"
26803
27052
  });
26804
27053
  /**
26805
27054
  * `recording-export` cap — render a footage time range into a single downloadable
@@ -27182,7 +27431,26 @@ var SceneMonitorStatusSchema = object({
27182
27431
  monitors: array(SceneMonitorSchema),
27183
27432
  lastFetchedAt: number()
27184
27433
  });
27185
- DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({
27434
+ /**
27435
+ * One camera's row in a `listScenesBatch` answer.
27436
+ *
27437
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
27438
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
27439
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
27440
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
27441
+ * configured. Fanned out per camera the difference was visible — one query
27442
+ * errored while the others resolved — and a batch that returned only the rows
27443
+ * it managed would have destroyed it, silently, by making an unreachable camera
27444
+ * indistinguishable from one that answered `monitors: []`.
27445
+ *
27446
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
27447
+ * could not be read"; `status.monitors: []` means "read, and it has none".
27448
+ */
27449
+ var SceneMonitorStatusForDeviceSchema = object({
27450
+ deviceId: number(),
27451
+ status: SceneMonitorStatusSchema.nullable()
27452
+ });
27453
+ DeviceType.Camera, method(object({ deviceId: number() }), SceneMonitorStatusSchema), method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()), method(object({
27186
27454
  deviceId: number(),
27187
27455
  label: string(),
27188
27456
  roi: MaskRectShapeSchema,
@@ -28506,6 +28774,27 @@ var CameraOccupancySnapshotSchema = object({
28506
28774
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
28507
28775
  });
28508
28776
  /**
28777
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
28778
+ *
28779
+ * THREE outcomes, and the single-camera method could only express two of them
28780
+ * because `snapshot: null` was already spoken for:
28781
+ *
28782
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
28783
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
28784
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
28785
+ * - `read: 'unreadable'` — the owner could not answer for this
28786
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
28787
+ *
28788
+ * Collapsing the last two is the failure this field exists to prevent: a
28789
+ * hydration that threw would otherwise render as an empty Stationary section,
28790
+ * which is a definite claim about a camera nobody could read.
28791
+ */
28792
+ var CameraOccupancySnapshotForDeviceSchema = object({
28793
+ deviceId: number(),
28794
+ read: _enum(["read", "unreadable"]),
28795
+ snapshot: CameraOccupancySnapshotSchema.nullable()
28796
+ });
28797
+ /**
28509
28798
  * Time-series resolution. The history methods return one bucket per
28510
28799
  * step over the requested range. Smaller resolutions cost more
28511
28800
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -28529,7 +28818,7 @@ var HistoryPointSchema = object({
28529
28818
  /** Object count averaged over the bucket (rounded to nearest integer). */
28530
28819
  count: number().int().nonnegative()
28531
28820
  });
28532
- DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({
28821
+ DeviceType.Camera, method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()), method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()), method(object({
28533
28822
  deviceId: number(),
28534
28823
  zoneId: string(),
28535
28824
  className: string().optional()
@@ -30130,6 +30419,12 @@ Object.freeze({
30130
30419
  addonId: null,
30131
30420
  access: "delete"
30132
30421
  },
30422
+ "deviceManager.renameLocation": {
30423
+ capName: "device-manager",
30424
+ capScope: "system",
30425
+ addonId: null,
30426
+ access: "create"
30427
+ },
30133
30428
  "deviceManager.runDeviceAction": {
30134
30429
  capName: "device-manager",
30135
30430
  capScope: "system",
@@ -31822,13 +32117,19 @@ Object.freeze({
31822
32117
  addonId: null,
31823
32118
  access: "view"
31824
32119
  },
31825
- "pipelineAnalytics.getGroup": {
32120
+ "pipelineAnalytics.getKeyEvents": {
31826
32121
  capName: "pipeline-analytics",
31827
32122
  capScope: "device",
31828
32123
  addonId: null,
31829
32124
  access: "view"
31830
32125
  },
31831
- "pipelineAnalytics.getKeyEvents": {
32126
+ "pipelineAnalytics.getKeyEventsBatch": {
32127
+ capName: "pipeline-analytics",
32128
+ capScope: "device",
32129
+ addonId: null,
32130
+ access: "view"
32131
+ },
32132
+ "pipelineAnalytics.getMediaReclaimStatus": {
31832
32133
  capName: "pipeline-analytics",
31833
32134
  capScope: "device",
31834
32135
  addonId: null,
@@ -31918,12 +32219,6 @@ Object.freeze({
31918
32219
  addonId: null,
31919
32220
  access: "view"
31920
32221
  },
31921
- "pipelineAnalytics.listGroups": {
31922
- capName: "pipeline-analytics",
31923
- capScope: "device",
31924
- addonId: null,
31925
- access: "view"
31926
- },
31927
32222
  "pipelineAnalytics.listOpsLog": {
31928
32223
  capName: "pipeline-analytics",
31929
32224
  capScope: "device",
@@ -32008,6 +32303,12 @@ Object.freeze({
32008
32303
  addonId: null,
32009
32304
  access: "create"
32010
32305
  },
32306
+ "pipelineAnalytics.reclaimDebugMedia": {
32307
+ capName: "pipeline-analytics",
32308
+ capScope: "device",
32309
+ addonId: null,
32310
+ access: "create"
32311
+ },
32011
32312
  "pipelineAnalytics.reconcileFromDisk": {
32012
32313
  capName: "pipeline-analytics",
32013
32314
  capScope: "device",
@@ -32932,6 +33233,12 @@ Object.freeze({
32932
33233
  addonId: null,
32933
33234
  access: "view"
32934
33235
  },
33236
+ "recording.getPlacement": {
33237
+ capName: "recording",
33238
+ capScope: "system",
33239
+ addonId: null,
33240
+ access: "view"
33241
+ },
32935
33242
  "recording.getPlaybackManifest": {
32936
33243
  capName: "recording",
32937
33244
  capScope: "system",
@@ -33010,6 +33317,12 @@ Object.freeze({
33010
33317
  addonId: null,
33011
33318
  access: "view"
33012
33319
  },
33320
+ "recording.reconcileLedgerAgainstDisk": {
33321
+ capName: "recording",
33322
+ capScope: "system",
33323
+ addonId: null,
33324
+ access: "create"
33325
+ },
33013
33326
  "recording.refreshStorageLocationsForMigration": {
33014
33327
  capName: "recording",
33015
33328
  capScope: "system",
@@ -33052,6 +33365,12 @@ Object.freeze({
33052
33365
  addonId: null,
33053
33366
  access: "create"
33054
33367
  },
33368
+ "recording.setDevicePlacement": {
33369
+ capName: "recording",
33370
+ capScope: "system",
33371
+ addonId: null,
33372
+ access: "create"
33373
+ },
33055
33374
  "recording.startStorageMigrationMove": {
33056
33375
  capName: "recording",
33057
33376
  capScope: "system",
@@ -33136,6 +33455,12 @@ Object.freeze({
33136
33455
  addonId: null,
33137
33456
  access: "view"
33138
33457
  },
33458
+ "sceneMonitor.listScenesBatch": {
33459
+ capName: "scene-monitor",
33460
+ capScope: "device",
33461
+ addonId: null,
33462
+ access: "view"
33463
+ },
33139
33464
  "sceneMonitor.recheckNow": {
33140
33465
  capName: "scene-monitor",
33141
33466
  capScope: "device",
@@ -33490,12 +33815,36 @@ Object.freeze({
33490
33815
  addonId: null,
33491
33816
  access: "create"
33492
33817
  },
33818
+ "storageMigration.cleanupCancel": {
33819
+ capName: "storage-migration",
33820
+ capScope: "system",
33821
+ addonId: null,
33822
+ access: "create"
33823
+ },
33824
+ "storageMigration.cleanupStart": {
33825
+ capName: "storage-migration",
33826
+ capScope: "system",
33827
+ addonId: null,
33828
+ access: "create"
33829
+ },
33830
+ "storageMigration.cleanupStatus": {
33831
+ capName: "storage-migration",
33832
+ capScope: "system",
33833
+ addonId: null,
33834
+ access: "view"
33835
+ },
33493
33836
  "storageMigration.drain": {
33494
33837
  capName: "storage-migration",
33495
33838
  capScope: "system",
33496
33839
  addonId: null,
33497
33840
  access: "create"
33498
33841
  },
33842
+ "storageMigration.history": {
33843
+ capName: "storage-migration",
33844
+ capScope: "system",
33845
+ addonId: null,
33846
+ access: "view"
33847
+ },
33499
33848
  "storageMigration.movers": {
33500
33849
  capName: "storage-migration",
33501
33850
  capScope: "system",
@@ -34492,6 +34841,12 @@ Object.freeze({
34492
34841
  addonId: null,
34493
34842
  access: "view"
34494
34843
  },
34844
+ "zoneAnalytics.getCurrentSnapshotBatch": {
34845
+ capName: "zone-analytics",
34846
+ capScope: "device",
34847
+ addonId: null,
34848
+ access: "view"
34849
+ },
34495
34850
  "zoneAnalytics.getUnzonedHistory": {
34496
34851
  capName: "zone-analytics",
34497
34852
  capScope: "device",
@@ -35486,14 +35841,14 @@ Object.freeze({
35486
35841
  form: "single",
35487
35842
  optional: true
35488
35843
  }],
35489
- "pipelineAnalytics.getGroup": [{
35844
+ "pipelineAnalytics.getKeyEvents": [{
35490
35845
  name: "deviceId",
35491
35846
  form: "single",
35492
35847
  optional: false
35493
35848
  }],
35494
- "pipelineAnalytics.getKeyEvents": [{
35495
- name: "deviceId",
35496
- form: "single",
35849
+ "pipelineAnalytics.getKeyEventsBatch": [{
35850
+ name: "deviceIds",
35851
+ form: "array",
35497
35852
  optional: false
35498
35853
  }],
35499
35854
  "pipelineAnalytics.getMotionEvents": [{
@@ -35551,11 +35906,6 @@ Object.freeze({
35551
35906
  form: "single",
35552
35907
  optional: false
35553
35908
  }],
35554
- "pipelineAnalytics.listGroups": [{
35555
- name: "deviceIds",
35556
- form: "array",
35557
- optional: false
35558
- }],
35559
35909
  "pipelineAnalytics.listOpsLog": [{
35560
35910
  name: "deviceId",
35561
35911
  form: "single",
@@ -35601,6 +35951,11 @@ Object.freeze({
35601
35951
  form: "single",
35602
35952
  optional: true
35603
35953
  }],
35954
+ "pipelineAnalytics.reclaimDebugMedia": [{
35955
+ name: "deviceIds",
35956
+ form: "array",
35957
+ optional: true
35958
+ }],
35604
35959
  "pipelineAnalytics.reconcileFromDisk": [{
35605
35960
  name: "deviceId",
35606
35961
  form: "single",
@@ -35936,6 +36291,11 @@ Object.freeze({
35936
36291
  form: "single",
35937
36292
  optional: false
35938
36293
  }],
36294
+ "recording.reconcileLedgerAgainstDisk": [{
36295
+ name: "deviceId",
36296
+ form: "single",
36297
+ optional: true
36298
+ }],
35939
36299
  "recording.relocateFootage": [{
35940
36300
  name: "deviceId",
35941
36301
  form: "single",
@@ -35961,6 +36321,11 @@ Object.freeze({
35961
36321
  form: "single",
35962
36322
  optional: false
35963
36323
  }],
36324
+ "recording.setDevicePlacement": [{
36325
+ name: "deviceId",
36326
+ form: "single",
36327
+ optional: false
36328
+ }],
35964
36329
  "recording.startStorageMigrationMove": [{
35965
36330
  name: "deviceId",
35966
36331
  form: "single",
@@ -36001,6 +36366,11 @@ Object.freeze({
36001
36366
  form: "single",
36002
36367
  optional: false
36003
36368
  }],
36369
+ "sceneMonitor.listScenesBatch": [{
36370
+ name: "deviceIds",
36371
+ form: "array",
36372
+ optional: false
36373
+ }],
36004
36374
  "sceneMonitor.recheckNow": [{
36005
36375
  name: "deviceId",
36006
36376
  form: "single",
@@ -36262,6 +36632,11 @@ Object.freeze({
36262
36632
  form: "single",
36263
36633
  optional: false
36264
36634
  }],
36635
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
36636
+ name: "deviceIds",
36637
+ form: "array",
36638
+ optional: false
36639
+ }],
36265
36640
  "zoneAnalytics.getUnzonedHistory": [{
36266
36641
  name: "deviceId",
36267
36642
  form: "single",