@camstack/addon-provider-reolink 1.2.71 → 1.2.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/dist/addon.js +478 -73
  2. package/dist/addon.mjs +478 -73
  3. package/package.json +1 -1
package/dist/addon.mjs CHANGED
@@ -8303,6 +8303,20 @@ var RelocateJobSchema = object({
8303
8303
  * in its row, not in its name, so `MediaRelocateEngine` never reconciles one.
8304
8304
  */
8305
8305
  rowsReconciled: number().int().nonnegative().optional(),
8306
+ /**
8307
+ * Rows this run FORGOT because the file they name is not on disk.
8308
+ *
8309
+ * The mover derived the path from the row's own fields and `stat`ed it; an
8310
+ * ENOENT there is a per-path confirmation that the segment is gone (D296),
8311
+ * and the durable row is dropped through the same channel eviction uses. It
8312
+ * is reported for the same reason `rowsReconciled` is: this is a durable
8313
+ * mutation nobody asked for, and a migration that quietly erases hour rows is
8314
+ * the same failure as one that quietly skips them (D295).
8315
+ *
8316
+ * The production drain of 2026-08-30 would have reported 11 074 here — the
8317
+ * ledger claimed 5.65 GB of footage that no longer existed.
8318
+ */
8319
+ rowsForgotten: number().int().nonnegative().optional(),
8306
8320
  startedAt: number(),
8307
8321
  finishedAt: number().nullable(),
8308
8322
  error: string().nullable()
@@ -8367,6 +8381,13 @@ var MediaRelocateModeSchema = _enum([
8367
8381
  ]);
8368
8382
  var RelocateMediaInputSchema = object({
8369
8383
  toLocationId: string(),
8384
+ /**
8385
+ * Restrict the pass to rows currently on this location. Omitted / `'*'` =
8386
+ * every row that is not already on `toLocationId` (the historical
8387
+ * behaviour). A named source is what a from→to migration needs: without it
8388
+ * "move events off disk 2" also emptied disk 1.
8389
+ */
8390
+ fromLocationId: string().optional(),
8370
8391
  throttleMbps: number().min(1).max(1e3).optional(),
8371
8392
  /** Omitted = `move`, the pre-existing behaviour. */
8372
8393
  mode: MediaRelocateModeSchema.optional()
@@ -8434,6 +8455,19 @@ var StorageMigrationDestinationsSchema = object({
8434
8455
  galleryMedia: string().min(1).optional()
8435
8456
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
8436
8457
  /**
8458
+ * Optional named source per class. Omitted = the class's current default
8459
+ * (the historical behaviour). A named source that is NOT the default is a
8460
+ * drain of that disk: bytes move, the default stays, and the source is
8461
+ * disabled when the move finishes.
8462
+ */
8463
+ var StorageMigrationSourcesSchema = object({
8464
+ recordings: string().min(1).optional(),
8465
+ recordingsLow: string().min(1).optional(),
8466
+ eventMedia: string().min(1).optional(),
8467
+ backups: string().min(1).optional(),
8468
+ galleryMedia: string().min(1).optional()
8469
+ }).optional();
8470
+ /**
8437
8471
  * How a migration sequences the cutover against the byte move.
8438
8472
  *
8439
8473
  * - `blocking` — the historical order: pause, move every byte, repoint,
@@ -8455,6 +8489,8 @@ var StorageMigrationModeSchema = _enum(["blocking", "nonBlocking"]);
8455
8489
  /** Shared input for planning and starting an orchestrated storage migration. */
8456
8490
  var StorageMigrationInputSchema = object({
8457
8491
  destinations: StorageMigrationDestinationsSchema,
8492
+ /** Omitted = each class's current default. */
8493
+ sources: StorageMigrationSourcesSchema,
8458
8494
  throttleMbps: number().min(1).max(1e3).optional(),
8459
8495
  /** Omitted = `blocking`, which stays the default. */
8460
8496
  mode: StorageMigrationModeSchema.optional()
@@ -8534,6 +8570,13 @@ var StorageMigrationMoveSchema = object({
8534
8570
  storageClass: StorageMigrationClassSchema,
8535
8571
  fromLocationId: string(),
8536
8572
  toLocationId: string(),
8573
+ /**
8574
+ * True when `from` was NOT the class default at plan time. The move still
8575
+ * copies bytes, but the default is left alone and the source is disabled
8576
+ * once the copy verifies. Absent on jobs planned before this field existed
8577
+ * — those jobs always repointed, which is `false`.
8578
+ */
8579
+ freezeSource: boolean().optional(),
8537
8580
  moverJobId: string().nullable(),
8538
8581
  state: RelocateJobStateSchema.nullable(),
8539
8582
  error: string().nullable(),
@@ -8547,6 +8590,7 @@ var StorageMigrationJobSchema = object({
8547
8590
  * can tell a seconds-long cutover from a thirty-hour one. */
8548
8591
  mode: StorageMigrationModeSchema,
8549
8592
  destinations: StorageMigrationDestinationsSchema,
8593
+ sources: StorageMigrationSourcesSchema,
8550
8594
  throttleMbps: number(),
8551
8595
  moves: array(StorageMigrationMoveSchema),
8552
8596
  pauseLeaseId: string().nullable(),
@@ -8572,6 +8616,7 @@ var StorageMigrationFindingSchema = object({
8572
8616
  });
8573
8617
  var StorageMigrationPlanSchema = object({
8574
8618
  destinations: StorageMigrationDestinationsSchema,
8619
+ sources: StorageMigrationSourcesSchema,
8575
8620
  /** The mode this plan was built for. A plan is only valid for its mode: the
8576
8621
  * `eventMedia` seal gate and the single-cardinality refusal both depend on
8577
8622
  * it. */
@@ -8579,7 +8624,8 @@ var StorageMigrationPlanSchema = object({
8579
8624
  moves: array(object({
8580
8625
  storageClass: StorageMigrationClassSchema,
8581
8626
  fromLocationId: string(),
8582
- toLocationId: string()
8627
+ toLocationId: string(),
8628
+ freezeSource: boolean().optional()
8583
8629
  })),
8584
8630
  findings: array(StorageMigrationFindingSchema)
8585
8631
  });
@@ -8666,16 +8712,142 @@ var RelocateResidueSchema = object({
8666
8712
  segments: number().int().nonnegative(),
8667
8713
  bytes: number().int().nonnegative()
8668
8714
  }).nullable();
8715
+ /**
8716
+ * Ask one location whether its durable hour rows describe the disk — the walk
8717
+ * (D319).
8718
+ *
8719
+ * `apply` DEFAULTS TO FALSE and that default is the product: the operator's
8720
+ * missing tool is the question, and the dry run is how they sanity-check the
8721
+ * destructive run before authorising it.
8722
+ */
8723
+ var LedgerWalkInputSchema = object({
8724
+ locationId: string().min(1),
8725
+ /** Forget the confirmed-absent rows, rather than only counting them. */
8726
+ apply: boolean().optional(),
8727
+ /** Narrow to one camera. */
8728
+ deviceId: number().int().positive().optional(),
8729
+ /** Narrow to these recording profiles; empty/absent = every profile. */
8730
+ profiles: array(string().min(1)).optional()
8731
+ });
8732
+ /** Why a whole walk did nothing. Every one leaves the ledger untouched. */
8733
+ var LedgerWalkRefusalSchema = _enum([
8734
+ "location-unknown",
8735
+ "source-writable",
8736
+ "no-ledger",
8737
+ "archive-unreadable",
8738
+ "anchor-absent",
8739
+ "anchor-unreadable",
8740
+ "anchor-moved"
8741
+ ]);
8742
+ _enum([
8743
+ "live-tail",
8744
+ "listing-error",
8745
+ "path-mismatch",
8746
+ "durable-refused"
8747
+ ]);
8748
+ /** Every skip reason, always present, always a number — so a reason that never
8749
+ * fired reports as zero rather than absent and the report shape is constant
8750
+ * between passes. Spelled out rather than `z.record` for exactly that. */
8751
+ var LedgerWalkSkipCountsSchema = object({
8752
+ "live-tail": number().int().nonnegative(),
8753
+ "listing-error": number().int().nonnegative(),
8754
+ "path-mismatch": number().int().nonnegative(),
8755
+ "durable-refused": number().int().nonnegative()
8756
+ });
8757
+ /** One camera's share of a walk, so a report names cameras and not rows. */
8758
+ var LedgerWalkDeviceReportSchema = object({
8759
+ deviceId: number().int(),
8760
+ hoursWalked: number().int().nonnegative(),
8761
+ hoursMissing: number().int().nonnegative(),
8762
+ ghostSegments: number().int().nonnegative(),
8763
+ ghostBytes: number().int().nonnegative(),
8764
+ forgottenSegments: number().int().nonnegative(),
8765
+ orphanFiles: number().int().nonnegative()
8766
+ });
8767
+ /**
8768
+ * What one walk claimed, listed, found and (only when armed) forgot.
8769
+ *
8770
+ * `archiveSegments` is the **M** and `segmentsClaimed` the **N** (D295), so a
8771
+ * walk that saw a fraction of the location is visible in its own report rather
8772
+ * than in the absence of one.
8773
+ */
8774
+ var LedgerWalkReportSchema = object({
8775
+ locationId: string(),
8776
+ applied: boolean(),
8777
+ refused: LedgerWalkRefusalSchema.nullable(),
8778
+ archiveSegments: number().int().nonnegative().nullable(),
8779
+ archiveBytes: number().int().nonnegative().nullable(),
8780
+ hoursClaimed: number().int().nonnegative(),
8781
+ hoursWalked: number().int().nonnegative(),
8782
+ hoursMissing: number().int().nonnegative(),
8783
+ /** `readdir` calls issued — the cost, stated in the unit that is paid. */
8784
+ listings: number().int().nonnegative(),
8785
+ segmentsClaimed: number().int().nonnegative(),
8786
+ ghostSegments: number().int().nonnegative(),
8787
+ ghostBytes: number().int().nonnegative(),
8788
+ ghostHoursWhole: number().int().nonnegative(),
8789
+ forgottenSegments: number().int().nonnegative(),
8790
+ forgottenBytes: number().int().nonnegative(),
8791
+ /** Files under a claimed hour that no durable row names. Never deleted. */
8792
+ orphanFiles: number().int().nonnegative(),
8793
+ orphanSample: array(string()).readonly(),
8794
+ hoursSkipped: number().int().nonnegative(),
8795
+ skippedByReason: LedgerWalkSkipCountsSchema,
8796
+ /** The walk stopped at its per-pass hour bound with claims unwalked. */
8797
+ bounded: boolean(),
8798
+ byDevice: array(LedgerWalkDeviceReportSchema).readonly()
8799
+ });
8669
8800
  /** How many rows a media pass would still act on against a given target — the
8670
8801
  * media lane's denominator AND its residue, from ONE derivation so the two can
8671
8802
  * never disagree. `null` = the count could not be taken. */
8672
8803
  var RelocatableMediaCountSchema = object({ rows: number().int().nonnegative() }).nullable();
8673
8804
  var RelocatableMediaCountInputSchema = object({
8674
8805
  toLocationId: string().min(1),
8806
+ /** Restrict the count to one source; omitted / `'*'` = every non-target row. */
8807
+ fromLocationId: string().optional(),
8675
8808
  /** Omitted = `move`. */
8676
8809
  mode: MediaRelocateModeSchema.optional()
8677
8810
  });
8678
8811
  /**
8812
+ * Operator cleanup of leftover analytics rows, optional debug media, and
8813
+ * ghost ledger entries on frozen footage locations.
8814
+ *
8815
+ * A pass is tens of minutes on a standing backlog. The hub method RETURNS
8816
+ * `{ jobId }` immediately; progress is `cleanupStatus`. Awaiting the work
8817
+ * is how `addons.custom` hit the 60 s UDS deadline while reclaim continued
8818
+ * with no operator-visible status.
8819
+ */
8820
+ var StorageCleanupPhaseSchema = _enum([
8821
+ "orphans",
8822
+ "debug-media",
8823
+ "ghost-ledger",
8824
+ "done",
8825
+ "failed",
8826
+ "cancelled"
8827
+ ]);
8828
+ var StorageCleanupInputSchema = object({
8829
+ /** Also walk motion stills / track filmstrips. Off by default. */
8830
+ includeDebugMedia: boolean().optional() });
8831
+ var StorageCleanupJobSchema = object({
8832
+ jobId: string(),
8833
+ phase: StorageCleanupPhaseSchema,
8834
+ includeDebugMedia: boolean(),
8835
+ orphansReclaimed: number().int().nonnegative(),
8836
+ orphanBytesReclaimed: number().int().nonnegative(),
8837
+ debugMediaReclaimed: number().int().nonnegative(),
8838
+ debugMediaBytesReclaimed: number().int().nonnegative(),
8839
+ ghostsForgotten: number().int().nonnegative(),
8840
+ ghostBytesForgotten: number().int().nonnegative(),
8841
+ /** Short operator-facing line: current collection, pass, or location. */
8842
+ detail: string().nullable(),
8843
+ cancelRequested: boolean(),
8844
+ startedAt: number(),
8845
+ updatedAt: number(),
8846
+ finishedAt: number().nullable(),
8847
+ error: string().nullable()
8848
+ });
8849
+ var StorageCleanupStatusInputSchema = object({ jobId: string().optional() });
8850
+ /**
8679
8851
  * `StorageLocationType` — an addon-declared id that identifies the *kind* of
8680
8852
  * storage a location serves. Defined here (not in `capabilities/storage.cap.ts`)
8681
8853
  * so the persisted record schema and the consumer-facing cap can both consume it
@@ -8703,11 +8875,10 @@ var StorageLocationTypeSchema = string().regex(/^[a-z][a-zA-Z0-9-]*$/);
8703
8875
  * The default location for a type uses `id === <type>:default` by
8704
8876
  * convention (the bare type ref like `'backups'` resolves to it).
8705
8877
  *
8706
- * `isSystem: true` marks a location as orchestrator-seeded and
8707
- * undeletable. The bootstrap-installed defaults (one per type) carry
8708
- * this flag; operator-added locations don't. Editing the config of
8709
- * a system location is allowed (path migration, provider swap) but
8710
- * deleting it is rejected at the cap level.
8878
+ * `isSystem` is a legacy persisted flag. Seed still creates the initial
8879
+ * `<type>:default` locations; the flag is no longer a lock, a badge, or a
8880
+ * prune selector. New writes leave it false. Deletion is gated on uniqueness
8881
+ * / last-enabled, not on this bit.
8711
8882
  */
8712
8883
  var StorageLocationSchema = object({
8713
8884
  id: string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
@@ -13526,6 +13697,12 @@ method(object({
13526
13697
  }), _void(), {
13527
13698
  kind: "mutation",
13528
13699
  auth: "admin"
13700
+ }), method(object({
13701
+ from: string(),
13702
+ to: string()
13703
+ }), object({ moved: number() }), {
13704
+ kind: "mutation",
13705
+ auth: "admin"
13529
13706
  }), method(object({
13530
13707
  deviceId: number(),
13531
13708
  disabled: boolean()
@@ -19574,53 +19751,15 @@ var RecentTracksPageSchema = object({
19574
19751
  /** Cursor for the next page, or null when this page is the last. */
19575
19752
  nextCursor: string().nullable()
19576
19753
  });
19577
- var LIST_GROUPS_DEFAULT_LIMIT = 40;
19578
- var LIST_GROUPS_MAX_LIMIT = 100;
19579
- var AnalyticsGroupRecordSchema = object({
19580
- id: string(),
19581
- deviceId: number().int(),
19582
- openedAt: number().int(),
19583
- closedAt: number().int(),
19584
- timestamp: number().int(),
19585
- memberCount: number().int(),
19586
- memberTrackIds: array(string()).readonly(),
19587
- className: string(),
19588
- classes: array(string()).readonly(),
19589
- /** Relative event-media path, or null when the group has no picture yet. */
19590
- mediaUrl: string().nullable(),
19591
- singleton: boolean()
19592
- });
19593
- var AnalyticsGroupMemberSchema = object({
19594
- trackId: string(),
19595
- deviceId: number().int(),
19596
- className: string(),
19597
- firstSeen: number().int(),
19598
- lastSeen: number().int(),
19599
- mediaUrl: string().nullable()
19600
- });
19601
- var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: array(AnalyticsGroupMemberSchema).readonly() });
19602
- var ListGroupsQueryInput = object({
19603
- /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
19604
- deviceIds: array(number()),
19605
- /** Window lower bound on `closedAt` (inclusive). */
19606
- since: number().optional(),
19607
- /** Window upper bound on `openedAt` (inclusive). */
19608
- until: number().optional(),
19609
- limit: number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
19610
- /** Opaque continuation cursor from a previous page's `nextCursor`. */
19611
- cursor: string().optional()
19612
- });
19613
- var ListGroupsPageSchema = object({
19614
- groups: array(AnalyticsGroupRecordSchema).readonly(),
19615
- nextCursor: string().nullable()
19616
- });
19754
+ var KEY_EVENTS_DEFAULT_LIMIT = 50;
19755
+ var KEY_EVENTS_MAX_LIMIT = 200;
19617
19756
  var KeyEventQueryInput = object({
19618
19757
  deviceId: number(),
19619
19758
  /** Window lower bound (track firstSeen ≥ since). */
19620
19759
  since: number(),
19621
19760
  /** Window upper bound (track firstSeen ≤ until). */
19622
19761
  until: number(),
19623
- limit: number().int().min(1).max(200).default(50),
19762
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19624
19763
  /** Drop tracks scoring below this importance. */
19625
19764
  minImportance: number().min(0).max(1).optional(),
19626
19765
  /** Restrict to a single class (e.g. 'person'). */
@@ -19642,6 +19781,32 @@ var KeyEventSchema = object({
19642
19781
  ...TrackFlagFields,
19643
19782
  ...TrackRetrainFields
19644
19783
  });
19784
+ /** `getKeyEvents`' window and filters, asked of a SET of cameras at once. */
19785
+ var KeyEventBatchQueryInput = object({
19786
+ deviceIds: array(number()).min(1).max(200),
19787
+ since: number(),
19788
+ until: number(),
19789
+ /** Applied PER CAMERA, exactly as `getKeyEvents.limit` is — never a total
19790
+ * across the set, which would let a busy camera starve a quiet one of its
19791
+ * rows and change what the merged feed contains. */
19792
+ limit: number().int().min(1).max(KEY_EVENTS_MAX_LIMIT).default(KEY_EVENTS_DEFAULT_LIMIT),
19793
+ minImportance: number().min(0).max(1).optional(),
19794
+ classFilter: string().optional()
19795
+ });
19796
+ /**
19797
+ * One camera's key events in a batch answer.
19798
+ *
19799
+ * The row exists for every requested id. `getKeyEvents` degrades to `[]` on
19800
+ * error rather than throwing, so a camera whose store read failed and one with
19801
+ * no events in the window were ALREADY indistinguishable per camera — the
19802
+ * batch does not make that worse, and the row keeps the deviceId the single
19803
+ * method's output never carried (the caller used to stamp it from the fan-out
19804
+ * key, which only worked because there was one query per camera).
19805
+ */
19806
+ var KeyEventsForDeviceSchema = object({
19807
+ deviceId: number(),
19808
+ events: array(KeyEventSchema).readonly()
19809
+ });
19645
19810
  object({
19646
19811
  trackId: string(),
19647
19812
  className: string(),
@@ -19689,9 +19854,7 @@ var TrackCascadeCountsSchema = object({
19689
19854
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
19690
19855
  plates: number().int(),
19691
19856
  /** Per-track CLIP search vectors removed (best-effort). */
19692
- embeddings: number().int(),
19693
- /** Group membership + group rows removed with their last member (best-effort). */
19694
- groups: number().int()
19857
+ embeddings: number().int()
19695
19858
  });
19696
19859
  /** Disk-wins reconcile: media rows whose blobs are gone, then empty tracks. */
19697
19860
  var DiskReconcileCountsSchema = object({
@@ -19861,6 +20024,47 @@ var RebuildStatusSchema = object({
19861
20024
  /** Present when the pass ended by throwing. */
19862
20025
  error: string().nullable()
19863
20026
  });
20027
+ /**
20028
+ * Acknowledgement that a debug-media reclaim STARTED.
20029
+ *
20030
+ * The pass is paced at ~2 MB/s over a standing 100 GB — tens of minutes — so
20031
+ * it runs detached and this returns immediately. Awaiting it is how the
20032
+ * `addons.custom` door hit the 60 s UDS deadline while the walk carried on
20033
+ * with no status. Poll {@link pipelineAnalyticsCapability.methods.getMediaReclaimStatus}.
20034
+ */
20035
+ var MediaReclaimStartResultSchema = object({
20036
+ started: boolean(),
20037
+ /** True when a pass was already running; the new request is ignored. */
20038
+ alreadyRunning: boolean()
20039
+ });
20040
+ var MediaReclaimInputSchema = object({
20041
+ mode: _enum(["report", "reclaim"]).default("report"),
20042
+ scopes: array(_enum(["motion-still", "track-filmstrip"])).min(1).optional(),
20043
+ deviceIds: array(number().int()).min(1).optional(),
20044
+ restart: boolean().optional(),
20045
+ pageSize: number().int().min(50).max(5e3).optional(),
20046
+ maxRowsPerDevice: number().int().min(1).max(5e5).optional(),
20047
+ maxReclaimPerDevice: number().int().min(1).max(5e5).optional(),
20048
+ maxBytesPerRun: number().int().min(1).optional(),
20049
+ budgetMinutes: number().int().min(1).max(720).optional(),
20050
+ throttleBytesPerSec: number().int().min(64 * 1024).optional(),
20051
+ graceMinutes: number().int().min(1).max(10080).optional()
20052
+ });
20053
+ var MediaReclaimStatusSchema = object({
20054
+ running: boolean(),
20055
+ mode: _enum(["report", "reclaim"]).nullable(),
20056
+ totalExamined: number(),
20057
+ totalEligible: number(),
20058
+ totalReclaimed: number(),
20059
+ totalBytesReclaimed: number(),
20060
+ totalRefused: number(),
20061
+ /** Device+scope windows finished in this pass. */
20062
+ devicesDone: number(),
20063
+ complete: boolean().nullable(),
20064
+ startedAtMs: number().nullable(),
20065
+ finishedAtMs: number().nullable(),
20066
+ error: string().nullable()
20067
+ });
19864
20068
  var ReplayFrameInputSchema = object({
19865
20069
  timestamp: number(),
19866
20070
  frame: PipelineRunResultBridge
@@ -19899,10 +20103,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19899
20103
  * stationary registry). Default false: the timeline lists passages,
19900
20104
  * not parking records (operator decision, 2026-08-15). */
19901
20105
  includeStationary: boolean().optional()
19902
- }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(ListGroupsQueryInput, ListGroupsPageSchema), method(object({
19903
- deviceId: number(),
19904
- groupId: string().min(1)
19905
- }), AnalyticsGroupDetailSchema.nullable()), method(object({ deviceId: number() }), _void(), {
20106
+ }), array(TrackSchema).readonly()), method(RecentTracksQueryInput, RecentTracksPageSchema), method(object({ deviceId: number() }), _void(), {
19906
20107
  kind: "mutation",
19907
20108
  auth: "admin"
19908
20109
  }), 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({
@@ -19911,7 +20112,7 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
19911
20112
  until: number().optional(),
19912
20113
  kinds: array(string()).optional(),
19913
20114
  limit: number().int().min(1).max(MAX_EVENT_QUERY_LIMIT).default(DEFAULT_EVENT_QUERY_LIMIT)
19914
- }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(object({
20115
+ }), array(SensorEventSchema).readonly()), method(KeyEventQueryInput, array(KeyEventSchema).readonly()), method(KeyEventBatchQueryInput, array(KeyEventsForDeviceSchema).readonly()), method(object({
19915
20116
  deviceId: number(),
19916
20117
  since: number(),
19917
20118
  until: number(),
@@ -20002,6 +20203,12 @@ DeviceType.Camera, method(object({ deviceId: number() }), array(TrackSchema).rea
20002
20203
  }), method(OpsLogQueryInputSchema, array(OpsLogEntrySchema).readonly(), {
20003
20204
  kind: "query",
20004
20205
  auth: "admin"
20206
+ }), method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
20207
+ kind: "mutation",
20208
+ auth: "admin"
20209
+ }), method(object({}), MediaReclaimStatusSchema, {
20210
+ kind: "query",
20211
+ auth: "admin"
20005
20212
  }), method(object({ deviceIds: array(number()).optional() }), TrainingExportSummarySchema, {
20006
20213
  kind: "query",
20007
20214
  auth: "admin"
@@ -22076,7 +22283,13 @@ method(StorageMigrationInputSchema, StorageMigrationPlanSchema, { auth: "admin"
22076
22283
  }), method(object({}), array(StorageMigrationMoverSchema).readonly(), { auth: "admin" }), method(object({}), array(StorageMigrationResidueSchema).readonly(), { auth: "admin" }), method(StorageMigrationDrainInputSchema, object({ jobId: string() }), {
22077
22284
  kind: "mutation",
22078
22285
  auth: "admin"
22079
- });
22286
+ }), method(StorageCleanupInputSchema, object({ jobId: string() }), {
22287
+ kind: "mutation",
22288
+ auth: "admin"
22289
+ }), method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
22290
+ kind: "mutation",
22291
+ auth: "admin"
22292
+ }), method(object({}), array(StorageMigrationJobSchema).readonly(), { auth: "admin" });
22080
22293
  var ProviderInfoSchema = discriminatedUnion("shouldSaveDiskSpace", [object({
22081
22294
  providerId: string().min(1),
22082
22295
  displayName: string().min(1),
@@ -29256,6 +29469,33 @@ var RecordingRebalanceInputSchema = object({
29256
29469
  minMoveGb: number().min(0).optional()
29257
29470
  });
29258
29471
  /**
29472
+ * Operator-facing placement of one camera onto a recordings location.
29473
+ *
29474
+ * `pinLocationId` is the operator instruction: a location id, or `null` for
29475
+ * Auto (the planner may move this camera). `locationId` is where high/mid
29476
+ * currently write — the plan, which may disagree with the pin when Auto.
29477
+ */
29478
+ var RecordingDevicePlacementSchema = object({
29479
+ deviceId: number().int(),
29480
+ profile: string(),
29481
+ locationId: string()
29482
+ });
29483
+ var RecordingDevicePinSchema = object({
29484
+ deviceId: number().int(),
29485
+ /** Recordings-class location this camera is pinned to. */
29486
+ locationId: string()
29487
+ });
29488
+ var RecordingPlacementViewSchema = object({
29489
+ assignments: array(RecordingDevicePlacementSchema),
29490
+ pins: array(RecordingDevicePinSchema),
29491
+ defaultLocations: record(string(), string())
29492
+ });
29493
+ var RecordingSetDevicePlacementInputSchema = object({
29494
+ deviceId: number().int(),
29495
+ /** `null` = Auto. Otherwise a `recordings:*` location id. */
29496
+ locationId: string().nullable()
29497
+ });
29498
+ /**
29259
29499
  * Result of locating footage at a wall-clock instant for one device/profile.
29260
29500
  * `segment` carries the covering segment's window; `gap` reports the forward
29261
29501
  * nearest covered edge (`null` past the end of footage / when none exists)
@@ -29469,6 +29709,9 @@ method(object({
29469
29709
  }), method(RelocateResidueInputSchema, RelocateResidueSchema, {
29470
29710
  kind: "query",
29471
29711
  auth: "admin"
29712
+ }), method(LedgerWalkInputSchema, LedgerWalkReportSchema, {
29713
+ kind: "mutation",
29714
+ auth: "admin"
29472
29715
  }), method(object({ jobId: string() }), object({ cancelled: boolean() }), {
29473
29716
  kind: "mutation",
29474
29717
  auth: "admin"
@@ -29478,6 +29721,12 @@ method(object({
29478
29721
  }), method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
29479
29722
  kind: "mutation",
29480
29723
  auth: "admin"
29724
+ }), method(object({}), RecordingPlacementViewSchema, {
29725
+ kind: "query",
29726
+ auth: "admin"
29727
+ }), method(RecordingSetDevicePlacementInputSchema, object({ ok: literal(true) }), {
29728
+ kind: "mutation",
29729
+ auth: "admin"
29481
29730
  });
29482
29731
  /**
29483
29732
  * `recording-export` cap — render a footage time range into a single downloadable
@@ -29860,6 +30109,25 @@ var SceneMonitorStatusSchema = object({
29860
30109
  monitors: array(SceneMonitorSchema),
29861
30110
  lastFetchedAt: number()
29862
30111
  });
30112
+ /**
30113
+ * One camera's row in a `listScenesBatch` answer.
30114
+ *
30115
+ * `status` is NULLABLE and the nullability is the whole point. `scene-monitor`
30116
+ * is a `defaultActive` wrapper, so every camera is asked; a camera whose
30117
+ * provider is absent (pipeline-analytics not deployed, the post-processing node
30118
+ * down) cannot answer, and that is not the same fact as a camera with no scenes
30119
+ * configured. Fanned out per camera the difference was visible — one query
30120
+ * errored while the others resolved — and a batch that returned only the rows
30121
+ * it managed would have destroyed it, silently, by making an unreachable camera
30122
+ * indistinguishable from one that answered `monitors: []`.
30123
+ *
30124
+ * So: EVERY requested deviceId gets a row. `status: null` means "this camera
30125
+ * could not be read"; `status.monitors: []` means "read, and it has none".
30126
+ */
30127
+ var SceneMonitorStatusForDeviceSchema = object({
30128
+ deviceId: number(),
30129
+ status: SceneMonitorStatusSchema.nullable()
30130
+ });
29863
30131
  var sceneMonitorCapability = {
29864
30132
  name: "scene-monitor",
29865
30133
  scope: "device",
@@ -29869,6 +30137,22 @@ var sceneMonitorCapability = {
29869
30137
  deviceTypes: [DeviceType.Camera],
29870
30138
  methods: {
29871
30139
  listScenes: method(object({ deviceId: number() }), SceneMonitorStatusSchema),
30140
+ /**
30141
+ * The same answer, for a SET of cameras, in one round trip.
30142
+ *
30143
+ * `/scenes` renders every camera and re-reads them on a 30s safety-net
30144
+ * poll behind the push slice. Fanned out client-side that was one query
30145
+ * per camera — 29 round trips through the browser, the hub and the
30146
+ * post-analysis runner every 30 seconds to read an in-memory map the
30147
+ * owner had already merged. The work is unchanged (`statusFor` per
30148
+ * device, all in-process at the owner); what collapses is the transport.
30149
+ *
30150
+ * A camera that cannot answer still gets a row, with `status: null` —
30151
+ * see {@link SceneMonitorStatusForDeviceSchema}. Never fewer rows than
30152
+ * ids: a caller that asked for twenty-nine and got twenty-seven cannot
30153
+ * tell which two are missing, or that any are.
30154
+ */
30155
+ listScenesBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(SceneMonitorStatusForDeviceSchema).readonly()),
29872
30156
  createScene: method(object({
29873
30157
  deviceId: number(),
29874
30158
  label: string(),
@@ -31902,6 +32186,27 @@ var CameraOccupancySnapshotSchema = object({
31902
32186
  stationaryObjects: array(StationaryObjectSchema).readonly().optional()
31903
32187
  });
31904
32188
  /**
32189
+ * One camera's row in a `getCurrentSnapshotBatch` answer.
32190
+ *
32191
+ * THREE outcomes, and the single-camera method could only express two of them
32192
+ * because `snapshot: null` was already spoken for:
32193
+ *
32194
+ * - `read: 'read'`, `snapshot` present — the live occupancy reading.
32195
+ * - `read: 'read'`, `snapshot: null` — read, and this camera has no reading
32196
+ * yet: no frame since boot, and no parked-object registry to hydrate from.
32197
+ * - `read: 'unreadable'` — the owner could not answer for this
32198
+ * camera. `snapshot` is null, and it does NOT mean "nothing parked here".
32199
+ *
32200
+ * Collapsing the last two is the failure this field exists to prevent: a
32201
+ * hydration that threw would otherwise render as an empty Stationary section,
32202
+ * which is a definite claim about a camera nobody could read.
32203
+ */
32204
+ var CameraOccupancySnapshotForDeviceSchema = object({
32205
+ deviceId: number(),
32206
+ read: _enum(["read", "unreadable"]),
32207
+ snapshot: CameraOccupancySnapshotSchema.nullable()
32208
+ });
32209
+ /**
31905
32210
  * Time-series resolution. The history methods return one bucket per
31906
32211
  * step over the requested range. Smaller resolutions cost more
31907
32212
  * memory + bandwidth; bound to discrete steps so caller cannot ask
@@ -31958,6 +32263,20 @@ var zoneAnalyticsCapability = {
31958
32263
  * (no inference result emitted since boot or since binding was
31959
32264
  * activated). */
31960
32265
  getCurrentSnapshot: method(object({ deviceId: number() }), CameraOccupancySnapshotSchema.nullable()),
32266
+ /**
32267
+ * The same snapshot, for a SET of cameras, in one round trip.
32268
+ *
32269
+ * The Events page's Stationary section polls this every 15s for every
32270
+ * selected camera. Fanned out client-side that is one query per camera to
32271
+ * read a `Map.get` at the owner — the answer costs nothing, the round trip
32272
+ * costs everything. Batched, N transports become one and the per-device
32273
+ * work is unchanged.
32274
+ *
32275
+ * Every requested deviceId gets a row, tagged `read` — see
32276
+ * {@link CameraOccupancySnapshotForDeviceSchema}. A camera the owner could
32277
+ * not answer for is `'unreadable'`, never an empty reading.
32278
+ */
32279
+ getCurrentSnapshotBatch: method(object({ deviceIds: array(number()).min(1).max(200) }), array(CameraOccupancySnapshotForDeviceSchema).readonly()),
31961
32280
  /** Time-series object count inside one zone. `className` optional —
31962
32281
  * omit to count every class in the zone. */
31963
32282
  getZoneHistory: method(object({
@@ -34842,6 +35161,12 @@ Object.freeze({
34842
35161
  addonId: null,
34843
35162
  access: "delete"
34844
35163
  },
35164
+ "deviceManager.renameLocation": {
35165
+ capName: "device-manager",
35166
+ capScope: "system",
35167
+ addonId: null,
35168
+ access: "create"
35169
+ },
34845
35170
  "deviceManager.runDeviceAction": {
34846
35171
  capName: "device-manager",
34847
35172
  capScope: "system",
@@ -36534,13 +36859,19 @@ Object.freeze({
36534
36859
  addonId: null,
36535
36860
  access: "view"
36536
36861
  },
36537
- "pipelineAnalytics.getGroup": {
36862
+ "pipelineAnalytics.getKeyEvents": {
36538
36863
  capName: "pipeline-analytics",
36539
36864
  capScope: "device",
36540
36865
  addonId: null,
36541
36866
  access: "view"
36542
36867
  },
36543
- "pipelineAnalytics.getKeyEvents": {
36868
+ "pipelineAnalytics.getKeyEventsBatch": {
36869
+ capName: "pipeline-analytics",
36870
+ capScope: "device",
36871
+ addonId: null,
36872
+ access: "view"
36873
+ },
36874
+ "pipelineAnalytics.getMediaReclaimStatus": {
36544
36875
  capName: "pipeline-analytics",
36545
36876
  capScope: "device",
36546
36877
  addonId: null,
@@ -36630,12 +36961,6 @@ Object.freeze({
36630
36961
  addonId: null,
36631
36962
  access: "view"
36632
36963
  },
36633
- "pipelineAnalytics.listGroups": {
36634
- capName: "pipeline-analytics",
36635
- capScope: "device",
36636
- addonId: null,
36637
- access: "view"
36638
- },
36639
36964
  "pipelineAnalytics.listOpsLog": {
36640
36965
  capName: "pipeline-analytics",
36641
36966
  capScope: "device",
@@ -36720,6 +37045,12 @@ Object.freeze({
36720
37045
  addonId: null,
36721
37046
  access: "create"
36722
37047
  },
37048
+ "pipelineAnalytics.reclaimDebugMedia": {
37049
+ capName: "pipeline-analytics",
37050
+ capScope: "device",
37051
+ addonId: null,
37052
+ access: "create"
37053
+ },
36723
37054
  "pipelineAnalytics.reconcileFromDisk": {
36724
37055
  capName: "pipeline-analytics",
36725
37056
  capScope: "device",
@@ -37644,6 +37975,12 @@ Object.freeze({
37644
37975
  addonId: null,
37645
37976
  access: "view"
37646
37977
  },
37978
+ "recording.getPlacement": {
37979
+ capName: "recording",
37980
+ capScope: "system",
37981
+ addonId: null,
37982
+ access: "view"
37983
+ },
37647
37984
  "recording.getPlaybackManifest": {
37648
37985
  capName: "recording",
37649
37986
  capScope: "system",
@@ -37722,6 +38059,12 @@ Object.freeze({
37722
38059
  addonId: null,
37723
38060
  access: "view"
37724
38061
  },
38062
+ "recording.reconcileLedgerAgainstDisk": {
38063
+ capName: "recording",
38064
+ capScope: "system",
38065
+ addonId: null,
38066
+ access: "create"
38067
+ },
37725
38068
  "recording.refreshStorageLocationsForMigration": {
37726
38069
  capName: "recording",
37727
38070
  capScope: "system",
@@ -37764,6 +38107,12 @@ Object.freeze({
37764
38107
  addonId: null,
37765
38108
  access: "create"
37766
38109
  },
38110
+ "recording.setDevicePlacement": {
38111
+ capName: "recording",
38112
+ capScope: "system",
38113
+ addonId: null,
38114
+ access: "create"
38115
+ },
37767
38116
  "recording.startStorageMigrationMove": {
37768
38117
  capName: "recording",
37769
38118
  capScope: "system",
@@ -37848,6 +38197,12 @@ Object.freeze({
37848
38197
  addonId: null,
37849
38198
  access: "view"
37850
38199
  },
38200
+ "sceneMonitor.listScenesBatch": {
38201
+ capName: "scene-monitor",
38202
+ capScope: "device",
38203
+ addonId: null,
38204
+ access: "view"
38205
+ },
37851
38206
  "sceneMonitor.recheckNow": {
37852
38207
  capName: "scene-monitor",
37853
38208
  capScope: "device",
@@ -38202,12 +38557,36 @@ Object.freeze({
38202
38557
  addonId: null,
38203
38558
  access: "create"
38204
38559
  },
38560
+ "storageMigration.cleanupCancel": {
38561
+ capName: "storage-migration",
38562
+ capScope: "system",
38563
+ addonId: null,
38564
+ access: "create"
38565
+ },
38566
+ "storageMigration.cleanupStart": {
38567
+ capName: "storage-migration",
38568
+ capScope: "system",
38569
+ addonId: null,
38570
+ access: "create"
38571
+ },
38572
+ "storageMigration.cleanupStatus": {
38573
+ capName: "storage-migration",
38574
+ capScope: "system",
38575
+ addonId: null,
38576
+ access: "view"
38577
+ },
38205
38578
  "storageMigration.drain": {
38206
38579
  capName: "storage-migration",
38207
38580
  capScope: "system",
38208
38581
  addonId: null,
38209
38582
  access: "create"
38210
38583
  },
38584
+ "storageMigration.history": {
38585
+ capName: "storage-migration",
38586
+ capScope: "system",
38587
+ addonId: null,
38588
+ access: "view"
38589
+ },
38211
38590
  "storageMigration.movers": {
38212
38591
  capName: "storage-migration",
38213
38592
  capScope: "system",
@@ -39204,6 +39583,12 @@ Object.freeze({
39204
39583
  addonId: null,
39205
39584
  access: "view"
39206
39585
  },
39586
+ "zoneAnalytics.getCurrentSnapshotBatch": {
39587
+ capName: "zone-analytics",
39588
+ capScope: "device",
39589
+ addonId: null,
39590
+ access: "view"
39591
+ },
39207
39592
  "zoneAnalytics.getUnzonedHistory": {
39208
39593
  capName: "zone-analytics",
39209
39594
  capScope: "device",
@@ -40198,14 +40583,14 @@ Object.freeze({
40198
40583
  form: "single",
40199
40584
  optional: true
40200
40585
  }],
40201
- "pipelineAnalytics.getGroup": [{
40586
+ "pipelineAnalytics.getKeyEvents": [{
40202
40587
  name: "deviceId",
40203
40588
  form: "single",
40204
40589
  optional: false
40205
40590
  }],
40206
- "pipelineAnalytics.getKeyEvents": [{
40207
- name: "deviceId",
40208
- form: "single",
40591
+ "pipelineAnalytics.getKeyEventsBatch": [{
40592
+ name: "deviceIds",
40593
+ form: "array",
40209
40594
  optional: false
40210
40595
  }],
40211
40596
  "pipelineAnalytics.getMotionEvents": [{
@@ -40263,11 +40648,6 @@ Object.freeze({
40263
40648
  form: "single",
40264
40649
  optional: false
40265
40650
  }],
40266
- "pipelineAnalytics.listGroups": [{
40267
- name: "deviceIds",
40268
- form: "array",
40269
- optional: false
40270
- }],
40271
40651
  "pipelineAnalytics.listOpsLog": [{
40272
40652
  name: "deviceId",
40273
40653
  form: "single",
@@ -40313,6 +40693,11 @@ Object.freeze({
40313
40693
  form: "single",
40314
40694
  optional: true
40315
40695
  }],
40696
+ "pipelineAnalytics.reclaimDebugMedia": [{
40697
+ name: "deviceIds",
40698
+ form: "array",
40699
+ optional: true
40700
+ }],
40316
40701
  "pipelineAnalytics.reconcileFromDisk": [{
40317
40702
  name: "deviceId",
40318
40703
  form: "single",
@@ -40648,6 +41033,11 @@ Object.freeze({
40648
41033
  form: "single",
40649
41034
  optional: false
40650
41035
  }],
41036
+ "recording.reconcileLedgerAgainstDisk": [{
41037
+ name: "deviceId",
41038
+ form: "single",
41039
+ optional: true
41040
+ }],
40651
41041
  "recording.relocateFootage": [{
40652
41042
  name: "deviceId",
40653
41043
  form: "single",
@@ -40673,6 +41063,11 @@ Object.freeze({
40673
41063
  form: "single",
40674
41064
  optional: false
40675
41065
  }],
41066
+ "recording.setDevicePlacement": [{
41067
+ name: "deviceId",
41068
+ form: "single",
41069
+ optional: false
41070
+ }],
40676
41071
  "recording.startStorageMigrationMove": [{
40677
41072
  name: "deviceId",
40678
41073
  form: "single",
@@ -40713,6 +41108,11 @@ Object.freeze({
40713
41108
  form: "single",
40714
41109
  optional: false
40715
41110
  }],
41111
+ "sceneMonitor.listScenesBatch": [{
41112
+ name: "deviceIds",
41113
+ form: "array",
41114
+ optional: false
41115
+ }],
40716
41116
  "sceneMonitor.recheckNow": [{
40717
41117
  name: "deviceId",
40718
41118
  form: "single",
@@ -40974,6 +41374,11 @@ Object.freeze({
40974
41374
  form: "single",
40975
41375
  optional: false
40976
41376
  }],
41377
+ "zoneAnalytics.getCurrentSnapshotBatch": [{
41378
+ name: "deviceIds",
41379
+ form: "array",
41380
+ optional: false
41381
+ }],
40977
41382
  "zoneAnalytics.getUnzonedHistory": [{
40978
41383
  name: "deviceId",
40979
41384
  form: "single",