camstack 1.2.70 → 1.2.72

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.
@@ -23633,9 +23633,9 @@ var require_zod = __commonJS({
23633
23633
  }
23634
23634
  });
23635
23635
 
23636
- // ../system/dist/dist-BhE8zNfY.js
23637
- var require_dist_BhE8zNfY = __commonJS({
23638
- "../system/dist/dist-BhE8zNfY.js"(exports) {
23636
+ // ../system/dist/dist-DCdtLXgx.js
23637
+ var require_dist_DCdtLXgx = __commonJS({
23638
+ "../system/dist/dist-DCdtLXgx.js"(exports) {
23639
23639
  "use strict";
23640
23640
  var zod = require_zod();
23641
23641
  var EventCategory = /* @__PURE__ */ (function(EventCategory2) {
@@ -26326,6 +26326,13 @@ var require_dist_BhE8zNfY = __commonJS({
26326
26326
  ]);
26327
26327
  var RelocateMediaInputSchema = zod.z.object({
26328
26328
  toLocationId: zod.z.string(),
26329
+ /**
26330
+ * Restrict the pass to rows currently on this location. Omitted / `'*'` =
26331
+ * every row that is not already on `toLocationId` (the historical
26332
+ * behaviour). A named source is what a from→to migration needs: without it
26333
+ * "move events off disk 2" also emptied disk 1.
26334
+ */
26335
+ fromLocationId: zod.z.string().optional(),
26329
26336
  throttleMbps: zod.z.number().min(1).max(1e3).optional(),
26330
26337
  /** Omitted = `move`, the pre-existing behaviour. */
26331
26338
  mode: MediaRelocateModeSchema.optional()
@@ -26357,9 +26364,18 @@ var require_dist_BhE8zNfY = __commonJS({
26357
26364
  backups: zod.z.string().min(1).optional(),
26358
26365
  galleryMedia: zod.z.string().min(1).optional()
26359
26366
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
26367
+ var StorageMigrationSourcesSchema = zod.z.object({
26368
+ recordings: zod.z.string().min(1).optional(),
26369
+ recordingsLow: zod.z.string().min(1).optional(),
26370
+ eventMedia: zod.z.string().min(1).optional(),
26371
+ backups: zod.z.string().min(1).optional(),
26372
+ galleryMedia: zod.z.string().min(1).optional()
26373
+ }).optional();
26360
26374
  var StorageMigrationModeSchema = zod.z.enum(["blocking", "nonBlocking"]);
26361
26375
  var StorageMigrationInputSchema = zod.z.object({
26362
26376
  destinations: StorageMigrationDestinationsSchema,
26377
+ /** Omitted = each class's current default. */
26378
+ sources: StorageMigrationSourcesSchema,
26363
26379
  throttleMbps: zod.z.number().min(1).max(1e3).optional(),
26364
26380
  /** Omitted = `blocking`, which stays the default. */
26365
26381
  mode: StorageMigrationModeSchema.optional()
@@ -26405,6 +26421,13 @@ var require_dist_BhE8zNfY = __commonJS({
26405
26421
  storageClass: StorageMigrationClassSchema,
26406
26422
  fromLocationId: zod.z.string(),
26407
26423
  toLocationId: zod.z.string(),
26424
+ /**
26425
+ * True when `from` was NOT the class default at plan time. The move still
26426
+ * copies bytes, but the default is left alone and the source is disabled
26427
+ * once the copy verifies. Absent on jobs planned before this field existed
26428
+ * — those jobs always repointed, which is `false`.
26429
+ */
26430
+ freezeSource: zod.z.boolean().optional(),
26408
26431
  moverJobId: zod.z.string().nullable(),
26409
26432
  state: RelocateJobStateSchema.nullable(),
26410
26433
  error: zod.z.string().nullable(),
@@ -26418,6 +26441,7 @@ var require_dist_BhE8zNfY = __commonJS({
26418
26441
  * can tell a seconds-long cutover from a thirty-hour one. */
26419
26442
  mode: StorageMigrationModeSchema,
26420
26443
  destinations: StorageMigrationDestinationsSchema,
26444
+ sources: StorageMigrationSourcesSchema,
26421
26445
  throttleMbps: zod.z.number(),
26422
26446
  moves: zod.z.array(StorageMigrationMoveSchema),
26423
26447
  pauseLeaseId: zod.z.string().nullable(),
@@ -26444,6 +26468,7 @@ var require_dist_BhE8zNfY = __commonJS({
26444
26468
  });
26445
26469
  var StorageMigrationPlanSchema = zod.z.object({
26446
26470
  destinations: StorageMigrationDestinationsSchema,
26471
+ sources: StorageMigrationSourcesSchema,
26447
26472
  /** The mode this plan was built for. A plan is only valid for its mode: the
26448
26473
  * `eventMedia` seal gate and the single-cardinality refusal both depend on
26449
26474
  * it. */
@@ -26451,7 +26476,8 @@ var require_dist_BhE8zNfY = __commonJS({
26451
26476
  moves: zod.z.array(zod.z.object({
26452
26477
  storageClass: StorageMigrationClassSchema,
26453
26478
  fromLocationId: zod.z.string(),
26454
- toLocationId: zod.z.string()
26479
+ toLocationId: zod.z.string(),
26480
+ freezeSource: zod.z.boolean().optional()
26455
26481
  })),
26456
26482
  findings: zod.z.array(StorageMigrationFindingSchema)
26457
26483
  });
@@ -26563,9 +26589,42 @@ var require_dist_BhE8zNfY = __commonJS({
26563
26589
  var RelocatableMediaCountSchema = zod.z.object({ rows: zod.z.number().int().nonnegative() }).nullable();
26564
26590
  var RelocatableMediaCountInputSchema = zod.z.object({
26565
26591
  toLocationId: zod.z.string().min(1),
26592
+ /** Restrict the count to one source; omitted / `'*'` = every non-target row. */
26593
+ fromLocationId: zod.z.string().optional(),
26566
26594
  /** Omitted = `move`. */
26567
26595
  mode: MediaRelocateModeSchema.optional()
26568
26596
  });
26597
+ var StorageCleanupPhaseSchema = zod.z.enum([
26598
+ "orphans",
26599
+ "debug-media",
26600
+ "ghost-ledger",
26601
+ "done",
26602
+ "failed",
26603
+ "cancelled"
26604
+ ]);
26605
+ var StorageCleanupInputSchema = zod.z.object({
26606
+ /** Also walk motion stills / track filmstrips. Off by default. */
26607
+ includeDebugMedia: zod.z.boolean().optional()
26608
+ });
26609
+ var StorageCleanupJobSchema = zod.z.object({
26610
+ jobId: zod.z.string(),
26611
+ phase: StorageCleanupPhaseSchema,
26612
+ includeDebugMedia: zod.z.boolean(),
26613
+ orphansReclaimed: zod.z.number().int().nonnegative(),
26614
+ orphanBytesReclaimed: zod.z.number().int().nonnegative(),
26615
+ debugMediaReclaimed: zod.z.number().int().nonnegative(),
26616
+ debugMediaBytesReclaimed: zod.z.number().int().nonnegative(),
26617
+ ghostsForgotten: zod.z.number().int().nonnegative(),
26618
+ ghostBytesForgotten: zod.z.number().int().nonnegative(),
26619
+ /** Short operator-facing line: current collection, pass, or location. */
26620
+ detail: zod.z.string().nullable(),
26621
+ cancelRequested: zod.z.boolean(),
26622
+ startedAt: zod.z.number(),
26623
+ updatedAt: zod.z.number(),
26624
+ finishedAt: zod.z.number().nullable(),
26625
+ error: zod.z.string().nullable()
26626
+ });
26627
+ var StorageCleanupStatusInputSchema = zod.z.object({ jobId: zod.z.string().optional() });
26569
26628
  var StorageLocationTypeSchema = zod.z.string().regex(/^[a-z][a-zA-Z0-9-]*$/);
26570
26629
  var StorageLocationSchema = zod.z.object({
26571
26630
  id: zod.z.string().regex(/^[a-z][a-zA-Z0-9-]*:[a-zA-Z0-9-]+$/),
@@ -31416,6 +31475,27 @@ var require_dist_BhE8zNfY = __commonJS({
31416
31475
  kind: "mutation",
31417
31476
  auth: "admin"
31418
31477
  }),
31478
+ /** Rename a room: replace the label in the registry IN PLACE and move
31479
+ * every device that carried it, as ONE server operation.
31480
+ *
31481
+ * The admin UI used to do this as `addLocation(to)` → N ×
31482
+ * `setLocation(device, to)` → `removeLocation(from)` from the browser.
31483
+ * Nothing bound those three together, so a closed tab left the fleet
31484
+ * split across two rooms — one of which the operator believed was gone.
31485
+ *
31486
+ * `from` is matched the way the registry matches everywhere else
31487
+ * (trimmed, case-insensitive) and need NOT be registered: the registry is
31488
+ * a suggestion list, and the devices that most need a rename are exactly
31489
+ * the ones carrying a label nobody registered. `to` renames onto an
31490
+ * existing room by MERGING into it, leaving no duplicate label. An empty
31491
+ * `to` throws before anything is written. */
31492
+ renameLocation: method(zod.z.object({
31493
+ from: zod.z.string(),
31494
+ to: zod.z.string()
31495
+ }), zod.z.object({ moved: zod.z.number() }), {
31496
+ kind: "mutation",
31497
+ auth: "admin"
31498
+ }),
31419
31499
  /** Soft-disable / re-enable the device. Drivers consult
31420
31500
  * `BaseDevice.disabled` to gate lifecycle hooks. */
31421
31501
  setDisabled: method(zod.z.object({
@@ -37037,46 +37117,6 @@ var require_dist_BhE8zNfY = __commonJS({
37037
37117
  /** Cursor for the next page, or null when this page is the last. */
37038
37118
  nextCursor: zod.z.string().nullable()
37039
37119
  });
37040
- var LIST_GROUPS_DEFAULT_LIMIT = 40;
37041
- var LIST_GROUPS_MAX_LIMIT = 100;
37042
- var AnalyticsGroupRecordSchema = zod.z.object({
37043
- id: zod.z.string(),
37044
- deviceId: zod.z.number().int(),
37045
- openedAt: zod.z.number().int(),
37046
- closedAt: zod.z.number().int(),
37047
- timestamp: zod.z.number().int(),
37048
- memberCount: zod.z.number().int(),
37049
- memberTrackIds: zod.z.array(zod.z.string()).readonly(),
37050
- className: zod.z.string(),
37051
- classes: zod.z.array(zod.z.string()).readonly(),
37052
- /** Relative event-media path, or null when the group has no picture yet. */
37053
- mediaUrl: zod.z.string().nullable(),
37054
- singleton: zod.z.boolean()
37055
- });
37056
- var AnalyticsGroupMemberSchema = zod.z.object({
37057
- trackId: zod.z.string(),
37058
- deviceId: zod.z.number().int(),
37059
- className: zod.z.string(),
37060
- firstSeen: zod.z.number().int(),
37061
- lastSeen: zod.z.number().int(),
37062
- mediaUrl: zod.z.string().nullable()
37063
- });
37064
- var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: zod.z.array(AnalyticsGroupMemberSchema).readonly() });
37065
- var ListGroupsQueryInput = zod.z.object({
37066
- /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
37067
- deviceIds: zod.z.array(zod.z.number()),
37068
- /** Window lower bound on `closedAt` (inclusive). */
37069
- since: zod.z.number().optional(),
37070
- /** Window upper bound on `openedAt` (inclusive). */
37071
- until: zod.z.number().optional(),
37072
- limit: zod.z.number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
37073
- /** Opaque continuation cursor from a previous page's `nextCursor`. */
37074
- cursor: zod.z.string().optional()
37075
- });
37076
- var ListGroupsPageSchema = zod.z.object({
37077
- groups: zod.z.array(AnalyticsGroupRecordSchema).readonly(),
37078
- nextCursor: zod.z.string().nullable()
37079
- });
37080
37120
  var KEY_EVENTS_DEFAULT_LIMIT = 50;
37081
37121
  var KEY_EVENTS_MAX_LIMIT = 200;
37082
37122
  var KeyEventQueryInput = zod.z.object({
@@ -37169,9 +37209,7 @@ var require_dist_BhE8zNfY = __commonJS({
37169
37209
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
37170
37210
  plates: zod.z.number().int(),
37171
37211
  /** Per-track CLIP search vectors removed (best-effort). */
37172
- embeddings: zod.z.number().int(),
37173
- /** Group membership + group rows removed with their last member (best-effort). */
37174
- groups: zod.z.number().int()
37212
+ embeddings: zod.z.number().int()
37175
37213
  });
37176
37214
  var DiskReconcileCountsSchema = zod.z.object({
37177
37215
  mediaDropped: zod.z.number().int(),
@@ -37384,16 +37422,6 @@ var require_dist_BhE8zNfY = __commonJS({
37384
37422
  * are not included (same contract as `listTracks`).
37385
37423
  */
37386
37424
  listRecentTracks: method(RecentTracksQueryInput, RecentTracksPageSchema),
37387
- /**
37388
- * Batched co-moving group listing — the Groups feed. Same merge/cursor
37389
- * contract as {@link listRecentTracks}. A group is a sealed partition of
37390
- * one session; `getGroup` is the detail with members.
37391
- */
37392
- listGroups: method(ListGroupsQueryInput, ListGroupsPageSchema),
37393
- getGroup: method(zod.z.object({
37394
- deviceId: zod.z.number(),
37395
- groupId: zod.z.string().min(1)
37396
- }), AnalyticsGroupDetailSchema.nullable()),
37397
37425
  clearTracks: method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), {
37398
37426
  kind: "mutation",
37399
37427
  auth: "admin"
@@ -40343,7 +40371,23 @@ var require_dist_BhE8zNfY = __commonJS({
40343
40371
  drain: method(StorageMigrationDrainInputSchema, zod.z.object({ jobId: zod.z.string() }), {
40344
40372
  kind: "mutation",
40345
40373
  auth: "admin"
40346
- })
40374
+ }),
40375
+ /**
40376
+ * One operator cleanup of leftover analytics (DB + blobs) and ghost
40377
+ * ledger rows on frozen footage locations. Optional debug-media sweep.
40378
+ * Returns immediately; poll {@link cleanupStatus}.
40379
+ */
40380
+ cleanupStart: method(StorageCleanupInputSchema, zod.z.object({ jobId: zod.z.string() }), {
40381
+ kind: "mutation",
40382
+ auth: "admin"
40383
+ }),
40384
+ cleanupStatus: method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }),
40385
+ cleanupCancel: method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
40386
+ kind: "mutation",
40387
+ auth: "admin"
40388
+ }),
40389
+ /** Recent finished migration jobs, newest first. The live job is `status`. */
40390
+ history: method(zod.z.object({}), zod.z.array(StorageMigrationJobSchema).readonly(), { auth: "admin" })
40347
40391
  }
40348
40392
  };
40349
40393
  var ProviderInfoSchema = zod.z.discriminatedUnion("shouldSaveDiskSpace", [zod.z.object({
@@ -47135,6 +47179,26 @@ var require_dist_BhE8zNfY = __commonJS({
47135
47179
  /** Ignore piles smaller than this (default 1 GB). */
47136
47180
  minMoveGb: zod.z.number().min(0).optional()
47137
47181
  });
47182
+ var RecordingDevicePlacementSchema = zod.z.object({
47183
+ deviceId: zod.z.number().int(),
47184
+ profile: zod.z.string(),
47185
+ locationId: zod.z.string()
47186
+ });
47187
+ var RecordingDevicePinSchema = zod.z.object({
47188
+ deviceId: zod.z.number().int(),
47189
+ /** Recordings-class location this camera is pinned to. */
47190
+ locationId: zod.z.string()
47191
+ });
47192
+ var RecordingPlacementViewSchema = zod.z.object({
47193
+ assignments: zod.z.array(RecordingDevicePlacementSchema),
47194
+ pins: zod.z.array(RecordingDevicePinSchema),
47195
+ defaultLocations: zod.z.record(zod.z.string(), zod.z.string())
47196
+ });
47197
+ var RecordingSetDevicePlacementInputSchema = zod.z.object({
47198
+ deviceId: zod.z.number().int(),
47199
+ /** `null` = Auto. Otherwise a `recordings:*` location id. */
47200
+ locationId: zod.z.string().nullable()
47201
+ });
47138
47202
  var LocateSegmentResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
47139
47203
  kind: zod.z.literal("segment"),
47140
47204
  startMs: zod.z.number(),
@@ -47453,6 +47517,22 @@ var require_dist_BhE8zNfY = __commonJS({
47453
47517
  startStorageRebalance: method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
47454
47518
  kind: "mutation",
47455
47519
  auth: "admin"
47520
+ }),
47521
+ /**
47522
+ * The placement plan in force plus operator pins. Drives the Locations
47523
+ * admin page: Auto vs a named recordings location, per camera.
47524
+ */
47525
+ getPlacement: method(zod.z.object({}), RecordingPlacementViewSchema, {
47526
+ kind: "query",
47527
+ auth: "admin"
47528
+ }),
47529
+ /**
47530
+ * Pin a camera to a recordings location, or clear the pin (Auto). High and
47531
+ * mid follow the pin; low stays with the recordingsLow planner.
47532
+ */
47533
+ setDevicePlacement: method(RecordingSetDevicePlacementInputSchema, zod.z.object({ ok: zod.z.literal(true) }), {
47534
+ kind: "mutation",
47535
+ auth: "admin"
47456
47536
  })
47457
47537
  }
47458
47538
  };
@@ -51856,6 +51936,12 @@ var require_dist_BhE8zNfY = __commonJS({
51856
51936
  addonId: null,
51857
51937
  access: "delete"
51858
51938
  },
51939
+ "deviceManager.renameLocation": {
51940
+ capName: "device-manager",
51941
+ capScope: "system",
51942
+ addonId: null,
51943
+ access: "create"
51944
+ },
51859
51945
  "deviceManager.runDeviceAction": {
51860
51946
  capName: "device-manager",
51861
51947
  capScope: "system",
@@ -53548,12 +53634,6 @@ var require_dist_BhE8zNfY = __commonJS({
53548
53634
  addonId: null,
53549
53635
  access: "view"
53550
53636
  },
53551
- "pipelineAnalytics.getGroup": {
53552
- capName: "pipeline-analytics",
53553
- capScope: "device",
53554
- addonId: null,
53555
- access: "view"
53556
- },
53557
53637
  "pipelineAnalytics.getKeyEvents": {
53558
53638
  capName: "pipeline-analytics",
53559
53639
  capScope: "device",
@@ -53656,12 +53736,6 @@ var require_dist_BhE8zNfY = __commonJS({
53656
53736
  addonId: null,
53657
53737
  access: "view"
53658
53738
  },
53659
- "pipelineAnalytics.listGroups": {
53660
- capName: "pipeline-analytics",
53661
- capScope: "device",
53662
- addonId: null,
53663
- access: "view"
53664
- },
53665
53739
  "pipelineAnalytics.listOpsLog": {
53666
53740
  capName: "pipeline-analytics",
53667
53741
  capScope: "device",
@@ -54676,6 +54750,12 @@ var require_dist_BhE8zNfY = __commonJS({
54676
54750
  addonId: null,
54677
54751
  access: "view"
54678
54752
  },
54753
+ "recording.getPlacement": {
54754
+ capName: "recording",
54755
+ capScope: "system",
54756
+ addonId: null,
54757
+ access: "view"
54758
+ },
54679
54759
  "recording.getPlaybackManifest": {
54680
54760
  capName: "recording",
54681
54761
  capScope: "system",
@@ -54802,6 +54882,12 @@ var require_dist_BhE8zNfY = __commonJS({
54802
54882
  addonId: null,
54803
54883
  access: "create"
54804
54884
  },
54885
+ "recording.setDevicePlacement": {
54886
+ capName: "recording",
54887
+ capScope: "system",
54888
+ addonId: null,
54889
+ access: "create"
54890
+ },
54805
54891
  "recording.startStorageMigrationMove": {
54806
54892
  capName: "recording",
54807
54893
  capScope: "system",
@@ -55246,12 +55332,36 @@ var require_dist_BhE8zNfY = __commonJS({
55246
55332
  addonId: null,
55247
55333
  access: "create"
55248
55334
  },
55335
+ "storageMigration.cleanupCancel": {
55336
+ capName: "storage-migration",
55337
+ capScope: "system",
55338
+ addonId: null,
55339
+ access: "create"
55340
+ },
55341
+ "storageMigration.cleanupStart": {
55342
+ capName: "storage-migration",
55343
+ capScope: "system",
55344
+ addonId: null,
55345
+ access: "create"
55346
+ },
55347
+ "storageMigration.cleanupStatus": {
55348
+ capName: "storage-migration",
55349
+ capScope: "system",
55350
+ addonId: null,
55351
+ access: "view"
55352
+ },
55249
55353
  "storageMigration.drain": {
55250
55354
  capName: "storage-migration",
55251
55355
  capScope: "system",
55252
55356
  addonId: null,
55253
55357
  access: "create"
55254
55358
  },
55359
+ "storageMigration.history": {
55360
+ capName: "storage-migration",
55361
+ capScope: "system",
55362
+ addonId: null,
55363
+ access: "view"
55364
+ },
55255
55365
  "storageMigration.movers": {
55256
55366
  capName: "storage-migration",
55257
55367
  capScope: "system",
@@ -57248,11 +57358,6 @@ var require_dist_BhE8zNfY = __commonJS({
57248
57358
  form: "single",
57249
57359
  optional: true
57250
57360
  }],
57251
- "pipelineAnalytics.getGroup": [{
57252
- name: "deviceId",
57253
- form: "single",
57254
- optional: false
57255
- }],
57256
57361
  "pipelineAnalytics.getKeyEvents": [{
57257
57362
  name: "deviceId",
57258
57363
  form: "single",
@@ -57318,11 +57423,6 @@ var require_dist_BhE8zNfY = __commonJS({
57318
57423
  form: "single",
57319
57424
  optional: false
57320
57425
  }],
57321
- "pipelineAnalytics.listGroups": [{
57322
- name: "deviceIds",
57323
- form: "array",
57324
- optional: false
57325
- }],
57326
57426
  "pipelineAnalytics.listOpsLog": [{
57327
57427
  name: "deviceId",
57328
57428
  form: "single",
@@ -57738,6 +57838,11 @@ var require_dist_BhE8zNfY = __commonJS({
57738
57838
  form: "single",
57739
57839
  optional: false
57740
57840
  }],
57841
+ "recording.setDevicePlacement": [{
57842
+ name: "deviceId",
57843
+ form: "single",
57844
+ optional: false
57845
+ }],
57741
57846
  "recording.startStorageMigrationMove": [{
57742
57847
  name: "deviceId",
57743
57848
  form: "single",
@@ -58978,6 +59083,12 @@ var require_dist_BhE8zNfY = __commonJS({
58978
59083
  return ScopedTokenSchema;
58979
59084
  }
58980
59085
  });
59086
+ Object.defineProperty(exports, "StorageCleanupJobSchema", {
59087
+ enumerable: true,
59088
+ get: function() {
59089
+ return StorageCleanupJobSchema;
59090
+ }
59091
+ });
58981
59092
  Object.defineProperty(exports, "StorageLocationTypeSchema", {
58982
59093
  enumerable: true,
58983
59094
  get: function() {
@@ -59482,7 +59593,7 @@ var require_alerts_addon = __commonJS({
59482
59593
  [Symbol.toStringTag]: { value: "Module" }
59483
59594
  });
59484
59595
  require_chunk_Cek0wNdY();
59485
- var require_dist10 = require_dist_BhE8zNfY();
59596
+ var require_dist10 = require_dist_DCdtLXgx();
59486
59597
  function selectExpired(alerts, cutoffMs) {
59487
59598
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
59488
59599
  }
@@ -60301,7 +60412,7 @@ var require_console_logging = __commonJS({
60301
60412
  [Symbol.toStringTag]: { value: "Module" }
60302
60413
  });
60303
60414
  require_chunk_Cek0wNdY();
60304
- var require_dist10 = require_dist_BhE8zNfY();
60415
+ var require_dist10 = require_dist_DCdtLXgx();
60305
60416
  var require_formatter = require_formatter_DqAKDlvN();
60306
60417
  var LEVEL_RANK = {
60307
60418
  debug: 0,
@@ -60395,7 +60506,7 @@ var require_core_blocks_addon = __commonJS({
60395
60506
  "use strict";
60396
60507
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60397
60508
  var require_chunk = require_chunk_Cek0wNdY();
60398
- var require_dist10 = require_dist_BhE8zNfY();
60509
+ var require_dist10 = require_dist_DCdtLXgx();
60399
60510
  var node_crypto = __require("crypto");
60400
60511
  var node_fs_promises = __require("fs/promises");
60401
60512
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -61292,11 +61403,11 @@ var require_core_blocks = __commonJS({
61292
61403
  }
61293
61404
  });
61294
61405
 
61295
- // ../system/dist/retired-settings-keys-CqDbI-vK.js
61296
- var require_retired_settings_keys_CqDbI_vK = __commonJS({
61297
- "../system/dist/retired-settings-keys-CqDbI-vK.js"(exports) {
61406
+ // ../system/dist/retired-settings-keys-DobfRRgq.js
61407
+ var require_retired_settings_keys_DobfRRgq = __commonJS({
61408
+ "../system/dist/retired-settings-keys-DobfRRgq.js"(exports) {
61298
61409
  "use strict";
61299
- var require_dist10 = require_dist_BhE8zNfY();
61410
+ var require_dist10 = require_dist_DCdtLXgx();
61300
61411
  function settingsStoreIsAuthoritativeHere(env) {
61301
61412
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
61302
61413
  return raw === "" || raw === "hub";
@@ -63510,8 +63621,8 @@ var require_device_manager_addon = __commonJS({
63510
63621
  [Symbol.toStringTag]: { value: "Module" }
63511
63622
  });
63512
63623
  require_chunk_Cek0wNdY();
63513
- var require_dist10 = require_dist_BhE8zNfY();
63514
- var require_retired_settings_keys = require_retired_settings_keys_CqDbI_vK();
63624
+ var require_dist10 = require_dist_DCdtLXgx();
63625
+ var require_retired_settings_keys = require_retired_settings_keys_DobfRRgq();
63515
63626
  var node_crypto = __require("crypto");
63516
63627
  var _camstack_types_node = require_node();
63517
63628
  var JOB_HISTORY = 20;
@@ -66268,20 +66379,17 @@ var require_device_manager_addon = __commonJS({
66268
66379
  const cascaded = [];
66269
66380
  const failed = [];
66270
66381
  await pctx.metaStore.withMetaWriteLock(async () => {
66271
- const persisted = await pctx.metaStore.resolvePersistedById(deviceId);
66272
- if (!persisted) throw new Error(`[device-manager] setLocation: unknown device id=${deviceId}`);
66382
+ if (!await pctx.metaStore.resolvePersistedById(deviceId)) throw new Error(`[device-manager] setLocation: unknown device id=${deviceId}`);
66273
66383
  await pctx.metaStore.rows.patch(deviceId, { location });
66274
- if (persisted.meta.type === require_dist10.DeviceType.Container) {
66275
- const descendants = await collectDescendants(pctx.metaStore, deviceId);
66276
- for (const descendant of descendants) try {
66277
- await pctx.metaStore.rows.patch(descendant.id, { location });
66278
- cascaded.push(descendant.id);
66279
- } catch (err) {
66280
- failed.push({
66281
- id: descendant.id,
66282
- error: err
66283
- });
66284
- }
66384
+ const descendants = await collectDescendants(pctx.metaStore, deviceId);
66385
+ for (const descendant of descendants) try {
66386
+ await pctx.metaStore.rows.patch(descendant.id, { location });
66387
+ cascaded.push(descendant.id);
66388
+ } catch (err) {
66389
+ failed.push({
66390
+ id: descendant.id,
66391
+ error: err
66392
+ });
66285
66393
  }
66286
66394
  });
66287
66395
  pctx.host.ctx.eventBus.emit({
@@ -66312,7 +66420,7 @@ var require_device_manager_addon = __commonJS({
66312
66420
  value: location
66313
66421
  }
66314
66422
  });
66315
- if (cascaded.length > 0 || failed.length > 0) pctx.host.ctx.logger.info("setLocation: cascaded location to container descendants", {
66423
+ if (cascaded.length > 0 || failed.length > 0) pctx.host.ctx.logger.info("setLocation: cascaded location to descendants", {
66316
66424
  tags: { deviceId },
66317
66425
  meta: {
66318
66426
  location,
@@ -66628,6 +66736,54 @@ var require_device_manager_addon = __commonJS({
66628
66736
  await pctx.settings.writeAddonStore({ locations: [...current, trimmed] });
66629
66737
  });
66630
66738
  }
66739
+ async function renameLocation(pctx, input) {
66740
+ const from = input.from.trim();
66741
+ const to = input.to.trim();
66742
+ if (from.length === 0) throw new Error("[device-manager] renameLocation: `from` must be non-empty");
66743
+ if (to.length === 0) throw new Error("[device-manager] renameLocation: `to` must be non-empty");
66744
+ const fromKey = from.toLowerCase();
66745
+ const toKey = to.toLowerCase();
66746
+ await pctx.bindingsDeps.withAddonStoreWriteLock(async () => {
66747
+ const current = (await pctx.metaStore.readStore()).locations ?? [];
66748
+ if (!current.some((l) => l.trim().toLowerCase() === fromKey)) return;
66749
+ const kept = current.filter((l) => {
66750
+ const key = l.trim().toLowerCase();
66751
+ return key !== fromKey && key !== toKey;
66752
+ });
66753
+ await pctx.settings.writeAddonStore({ locations: [...kept, to] });
66754
+ });
66755
+ const moved = await pctx.metaStore.withMetaWriteLock(async () => {
66756
+ const out = [];
66757
+ for (const row of await pctx.metaStore.rows.listAll()) {
66758
+ const location = row.meta.location;
66759
+ if (typeof location !== "string") continue;
66760
+ if (location.trim().toLowerCase() !== fromKey) continue;
66761
+ await pctx.metaStore.rows.patch(row.meta.id, { location: to });
66762
+ out.push(row.meta.id);
66763
+ }
66764
+ return out;
66765
+ });
66766
+ for (const deviceId of moved) pctx.host.ctx.eventBus.emit({
66767
+ id: (0, node_crypto.randomUUID)(),
66768
+ timestamp: /* @__PURE__ */ new Date(),
66769
+ source: {
66770
+ type: "device",
66771
+ id: deviceId
66772
+ },
66773
+ category: require_dist10.EventCategory.DeviceMetaChanged,
66774
+ data: {
66775
+ deviceId,
66776
+ field: "location",
66777
+ value: to
66778
+ }
66779
+ });
66780
+ pctx.host.ctx.logger.info("renameLocation: room renamed", { meta: {
66781
+ from,
66782
+ to,
66783
+ moved: moved.length
66784
+ } });
66785
+ return { moved: moved.length };
66786
+ }
66631
66787
  async function removeLocation(pctx, input) {
66632
66788
  const trimmed = input.name.trim();
66633
66789
  if (trimmed.length === 0) return;
@@ -68121,6 +68277,7 @@ var require_device_manager_addon = __commonJS({
68121
68277
  listLocations: () => listLocations(pctx),
68122
68278
  addLocation: (input) => addLocation(pctx, input),
68123
68279
  removeLocation: (input) => removeLocation(pctx, input),
68280
+ renameLocation: (input) => renameLocation(pctx, input),
68124
68281
  listPersistedByAddon: (input) => listPersistedByAddon(pctx, input),
68125
68282
  listAll: (input) => listAll(pctx, input),
68126
68283
  getDevice: (input) => getDevice(pctx, input),
@@ -68327,7 +68484,7 @@ var require_hub_forwarder = __commonJS({
68327
68484
  [Symbol.toStringTag]: { value: "Module" }
68328
68485
  });
68329
68486
  require_chunk_Cek0wNdY();
68330
- var require_dist10 = require_dist_BhE8zNfY();
68487
+ var require_dist10 = require_dist_DCdtLXgx();
68331
68488
  var require_formatter = require_formatter_DqAKDlvN();
68332
68489
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
68333
68490
  var HubForwarderDestination = class {
@@ -68464,7 +68621,7 @@ var require_liveness_monitor_addon = __commonJS({
68464
68621
  "use strict";
68465
68622
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
68466
68623
  require_chunk_Cek0wNdY();
68467
- var require_dist10 = require_dist_BhE8zNfY();
68624
+ var require_dist10 = require_dist_DCdtLXgx();
68468
68625
  var NO_DEVICES = "liveness:no-devices";
68469
68626
  var ALL_OFFLINE = "liveness:all-devices-offline";
68470
68627
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -68654,7 +68811,7 @@ var require_local_auth_addon = __commonJS({
68654
68811
  [Symbol.toStringTag]: { value: "Module" }
68655
68812
  });
68656
68813
  var require_chunk = require_chunk_Cek0wNdY();
68657
- var require_dist10 = require_dist_BhE8zNfY();
68814
+ var require_dist10 = require_dist_DCdtLXgx();
68658
68815
  var node_crypto = __require("crypto");
68659
68816
  node_crypto = require_chunk.__toESM(node_crypto);
68660
68817
  var crypto$1 = __require("crypto");
@@ -76467,7 +76624,7 @@ var require_loki_logging = __commonJS({
76467
76624
  [Symbol.toStringTag]: { value: "Module" }
76468
76625
  });
76469
76626
  require_chunk_Cek0wNdY();
76470
- var require_dist10 = require_dist_BhE8zNfY();
76627
+ var require_dist10 = require_dist_DCdtLXgx();
76471
76628
  function sanitizeLabelName(raw) {
76472
76629
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
76473
76630
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -77032,7 +77189,7 @@ var require_native_metrics_addon = __commonJS({
77032
77189
  [Symbol.toStringTag]: { value: "Module" }
77033
77190
  });
77034
77191
  var require_chunk = require_chunk_Cek0wNdY();
77035
- var require_dist10 = require_dist_BhE8zNfY();
77192
+ var require_dist10 = require_dist_DCdtLXgx();
77036
77193
  var node_fs_promises = __require("fs/promises");
77037
77194
  var node_child_process = __require("child_process");
77038
77195
  var node_util = __require("util");
@@ -79654,7 +79811,7 @@ var require_filesystem_storage_addon = __commonJS({
79654
79811
  [Symbol.toStringTag]: { value: "Module" }
79655
79812
  });
79656
79813
  var require_chunk = require_chunk_Cek0wNdY();
79657
- var require_dist10 = require_dist_BhE8zNfY();
79814
+ var require_dist10 = require_dist_DCdtLXgx();
79658
79815
  var node_crypto = __require("crypto");
79659
79816
  var node_fs_promises = __require("fs/promises");
79660
79817
  var node_path = __require("path");
@@ -80770,8 +80927,8 @@ var require_sqlite_settings_addon = __commonJS({
80770
80927
  [Symbol.toStringTag]: { value: "Module" }
80771
80928
  });
80772
80929
  var require_chunk = require_chunk_Cek0wNdY();
80773
- var require_dist10 = require_dist_BhE8zNfY();
80774
- var require_retired_settings_keys = require_retired_settings_keys_CqDbI_vK();
80930
+ var require_dist10 = require_dist_DCdtLXgx();
80931
+ var require_retired_settings_keys = require_retired_settings_keys_DobfRRgq();
80775
80932
  var node_crypto = __require("crypto");
80776
80933
  var node_fs = __require("fs");
80777
80934
  var node_module = __require("module");
@@ -81041,15 +81198,28 @@ var require_sqlite_settings_addon = __commonJS({
81041
81198
  const wanted = sqliteMmapSizeFor(fileSizeBytes);
81042
81199
  return wanted > currentMmapBytes ? wanted : null;
81043
81200
  }
81044
- var RETIRED_COLLECTIONS = [{
81045
- collection: "devices",
81046
- owner: "integration-registry",
81047
- reason: "the device half of the integration-registry is deleted; the fleet is `device-manager:devices` and always was (0 rows here against 974 live devices)"
81048
- }, {
81049
- collection: "device_settings_kv",
81050
- owner: "integration-registry",
81051
- reason: "per-device settings of a device table that never had a row; the live per-camera store is the device-manager row plus `addon-device-settings`"
81052
- }];
81201
+ var RETIRED_COLLECTIONS = [
81202
+ {
81203
+ collection: "devices",
81204
+ owner: "integration-registry",
81205
+ reason: "the device half of the integration-registry is deleted; the fleet is `device-manager:devices` and always was (0 rows here against 974 live devices)"
81206
+ },
81207
+ {
81208
+ collection: "device_settings_kv",
81209
+ owner: "integration-registry",
81210
+ reason: "per-device settings of a device table that never had a row; the live per-camera store is the device-manager row plus `addon-device-settings`"
81211
+ },
81212
+ {
81213
+ collection: "pipeline-analytics:track-groups",
81214
+ owner: "pipeline-analytics",
81215
+ reason: "the co-moving group entity is deleted (operator decision 2026-09-01, after the 2026-08-24 kill switch); nothing declares, writes or reads it"
81216
+ },
81217
+ {
81218
+ collection: "pipeline-analytics:track-group-members",
81219
+ owner: "pipeline-analytics",
81220
+ reason: "membership rows of the deleted group entity; the track cascade that used to carry them away no longer has a group leg"
81221
+ }
81222
+ ];
81053
81223
  function retiredCollectionStoreOf(backend) {
81054
81224
  return { async drop(collection) {
81055
81225
  return backend.dropRetiredTable(collection);
@@ -83180,7 +83350,8 @@ var require_storage_orchestrator_addon = __commonJS({
83180
83350
  [Symbol.toStringTag]: { value: "Module" }
83181
83351
  });
83182
83352
  var require_chunk = require_chunk_Cek0wNdY();
83183
- var require_dist10 = require_dist_BhE8zNfY();
83353
+ var require_dist10 = require_dist_DCdtLXgx();
83354
+ var zod = require_zod();
83184
83355
  var node_crypto = __require("crypto");
83185
83356
  var node_fs_promises = __require("fs/promises");
83186
83357
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -83402,17 +83573,22 @@ var require_storage_orchestrator_addon = __commonJS({
83402
83573
  for (const storageClass of STORAGE_CLASSES) {
83403
83574
  const targetId = input.destinations[storageClass];
83404
83575
  if (targetId === void 0) continue;
83405
- const source = this.deps.locations.getDefaultLocation(storageClass);
83406
- if (!source) throw new Error(`No default storage location for "${storageClass}"`);
83576
+ const namedSourceId = input.sources?.[storageClass];
83577
+ const defaultLoc = this.deps.locations.getDefaultLocation(storageClass);
83578
+ const source = namedSourceId ? this.deps.locations.getLocationById(namedSourceId) : defaultLoc;
83579
+ if (!source) throw new Error(namedSourceId ? `Storage location "${namedSourceId}" not found` : `No default storage location for "${storageClass}"`);
83580
+ if (source.type !== storageClass) throw new Error(`Storage location "${source.id}" is type "${source.type}", expected "${storageClass}"`);
83407
83581
  const target = this.deps.locations.getLocationById(targetId);
83408
83582
  if (!target) throw new Error(`Storage location "${targetId}" not found`);
83409
83583
  if (target.type !== storageClass) throw new Error(`Storage location "${targetId}" is type "${target.type}", expected "${storageClass}"`);
83410
- if (source.id === target.id) throw new Error(`Storage location "${targetId}" is already the "${storageClass}" default`);
83584
+ if (source.id === target.id) throw new Error(namedSourceId ? `Source and destination are the same location ("${targetId}")` : `Storage location "${targetId}" is already the "${storageClass}" default`);
83585
+ const freezeSource = defaultLoc !== void 0 && source.id !== defaultLoc.id;
83411
83586
  if (!MOVER_CLASSES.includes(storageClass)) throw new Error(`No mover owns "${storageClass}" \u2014 the migration can repoint its default but cannot move its bytes. Move it by hand, then repoint the default with upsertLocation.`);
83412
83587
  moves.push({
83413
83588
  storageClass,
83414
83589
  fromLocationId: source.id,
83415
83590
  toLocationId: target.id,
83591
+ freezeSource,
83416
83592
  moverJobId: null,
83417
83593
  state: null,
83418
83594
  error: null,
@@ -83443,11 +83619,13 @@ var require_storage_orchestrator_addon = __commonJS({
83443
83619
  }
83444
83620
  return {
83445
83621
  destinations: input.destinations,
83622
+ sources: input.sources,
83446
83623
  mode,
83447
- moves: moves.map(({ storageClass, fromLocationId, toLocationId }) => ({
83624
+ moves: moves.map(({ storageClass, fromLocationId, toLocationId, freezeSource }) => ({
83448
83625
  storageClass,
83449
83626
  fromLocationId,
83450
- toLocationId
83627
+ toLocationId,
83628
+ freezeSource
83451
83629
  })),
83452
83630
  findings
83453
83631
  };
@@ -83477,12 +83655,12 @@ var require_storage_orchestrator_addon = __commonJS({
83477
83655
  this.startReserved = true;
83478
83656
  try {
83479
83657
  const existing = await this.status();
83480
- if (existing && isTerminal(existing) && existing.pausedParticipants.length > 0) {
83658
+ if (existing && isTerminal$1(existing) && existing.pausedParticipants.length > 0) {
83481
83659
  await this.releaseAfterTerminal(existing);
83482
83660
  await this.persist(existing);
83483
83661
  if (existing.pausedParticipants.length > 0) throw new Error(`storage migration ${existing.jobId} still holds maintenance leases`);
83484
83662
  }
83485
- if (existing && !isTerminal(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
83663
+ if (existing && !isTerminal$1(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
83486
83664
  const plan = await this.plan(input);
83487
83665
  const now = this.deps.now();
83488
83666
  const job = {
@@ -83490,6 +83668,7 @@ var require_storage_orchestrator_addon = __commonJS({
83490
83668
  phase: "planning",
83491
83669
  mode: plan.mode,
83492
83670
  destinations: input.destinations,
83671
+ sources: input.sources,
83493
83672
  throttleMbps: input.throttleMbps ?? 40,
83494
83673
  moves: plan.moves.map((move) => ({
83495
83674
  ...move,
@@ -83521,10 +83700,15 @@ var require_storage_orchestrator_addon = __commonJS({
83521
83700
  if (jobId !== void 0 && job?.jobId !== jobId) return null;
83522
83701
  return job;
83523
83702
  }
83703
+ /** Finished jobs, newest first, excluding the one `status` currently shows. */
83704
+ async history() {
83705
+ const current = await this.status();
83706
+ return (await this.deps.history?.get() ?? []).filter((job) => job.jobId !== current?.jobId);
83707
+ }
83524
83708
  async cancel(jobId) {
83525
83709
  const job = await this.status(jobId);
83526
83710
  const cutoverInFlight = job !== null && job.repointed && (job.phase === "refreshing" || job.phase === "resuming");
83527
- if (!job || isTerminal(job) || cutoverInFlight) return false;
83711
+ if (!job || isTerminal$1(job) || cutoverInFlight) return false;
83528
83712
  job.cancelRequested = true;
83529
83713
  await this.persist(job);
83530
83714
  await Promise.all(job.moves.filter((move) => move.moverJobId !== null).map((move) => this.cancelMove(move)));
@@ -83581,18 +83765,22 @@ var require_storage_orchestrator_addon = __commonJS({
83581
83765
  const target = this.deps.locations.getDefaultLocation(storageClass);
83582
83766
  if (!target) continue;
83583
83767
  if (laneOf(storageClass) === "media") {
83584
- const count = await unanswerable(this.deps.participants.analytics.residue({
83585
- toLocationId: target.id,
83586
- mode: "move"
83587
- }));
83588
- if (count !== null && count.rows === 0) continue;
83589
- out.push({
83590
- storageClass,
83591
- fromLocationId: "*",
83592
- toLocationId: target.id,
83593
- items: count?.rows ?? null,
83594
- bytes: null
83595
- });
83768
+ for (const source of this.deps.locations.listLocations({ type: storageClass })) {
83769
+ if (source.id === target.id) continue;
83770
+ const count = await unanswerable(this.deps.participants.analytics.residue({
83771
+ toLocationId: target.id,
83772
+ fromLocationId: source.id,
83773
+ mode: "move"
83774
+ }));
83775
+ if (count !== null && count.rows === 0) continue;
83776
+ out.push({
83777
+ storageClass,
83778
+ fromLocationId: source.id,
83779
+ toLocationId: target.id,
83780
+ items: count?.rows ?? null,
83781
+ bytes: null
83782
+ });
83783
+ }
83596
83784
  continue;
83597
83785
  }
83598
83786
  for (const source of this.deps.locations.listLocations({ type: storageClass })) {
@@ -83646,12 +83834,12 @@ var require_storage_orchestrator_addon = __commonJS({
83646
83834
  this.startReserved = true;
83647
83835
  try {
83648
83836
  const existing = await this.status();
83649
- if (existing && isTerminal(existing) && existing.pausedParticipants.length > 0) {
83837
+ if (existing && isTerminal$1(existing) && existing.pausedParticipants.length > 0) {
83650
83838
  await this.releaseAfterTerminal(existing);
83651
83839
  await this.persist(existing);
83652
83840
  if (existing.pausedParticipants.length > 0) throw new Error(`storage migration ${existing.jobId} still holds maintenance leases`);
83653
83841
  }
83654
- if (existing && !isTerminal(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
83842
+ if (existing && !isTerminal$1(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
83655
83843
  for (const storageClass of input.classes) {
83656
83844
  if (MOVER_CLASSES.includes(storageClass)) continue;
83657
83845
  throw new Error(`No mover owns "${storageClass}" \u2014 there is nothing that can drain it. Move it by hand.`);
@@ -83716,7 +83904,7 @@ var require_storage_orchestrator_addon = __commonJS({
83716
83904
  await this.persist(job);
83717
83905
  return;
83718
83906
  }
83719
- if (isTerminal(job)) {
83907
+ if (isTerminal$1(job)) {
83720
83908
  if (job.pausedParticipants.length > 0) {
83721
83909
  await this.releaseAfterTerminal(job);
83722
83910
  await this.persist(job);
@@ -83777,11 +83965,12 @@ var require_storage_orchestrator_addon = __commonJS({
83777
83965
  if (job.phase === "verifying" && !nonBlocking) {
83778
83966
  await this.verifyMoves(job);
83779
83967
  if (job.cancelRequested) return this.finishCancelled(job);
83968
+ await this.freezeDrainedSources(job);
83780
83969
  await this.setPhase(job, "repointing");
83781
83970
  }
83782
83971
  if (job.phase === "repointing") {
83783
- const targets = new Map(job.moves.map((move) => [move.storageClass, move.toLocationId]));
83784
- await this.deps.locations.setDefaultLocations(targets);
83972
+ const targets = new Map(job.moves.filter((move) => move.freezeSource !== true).map((move) => [move.storageClass, move.toLocationId]));
83973
+ if (targets.size > 0) await this.deps.locations.setDefaultLocations(targets);
83785
83974
  job.repointed = true;
83786
83975
  await this.setPhase(job, "refreshing");
83787
83976
  }
@@ -83804,6 +83993,7 @@ var require_storage_orchestrator_addon = __commonJS({
83804
83993
  if (job.phase === "verifying" && nonBlocking) {
83805
83994
  await this.verifyMoves(job);
83806
83995
  if (job.cancelRequested) return this.finishCancelled(job);
83996
+ await this.freezeDrainedSources(job);
83807
83997
  await this.setPhase(job, "done");
83808
83998
  }
83809
83999
  } catch (err) {
@@ -83819,7 +84009,7 @@ var require_storage_orchestrator_addon = __commonJS({
83819
84009
  await this.releaseAfterTerminal(job);
83820
84010
  await this.persist(job);
83821
84011
  } finally {
83822
- if (isTerminal(job)) {
84012
+ if (isTerminal$1(job)) {
83823
84013
  await this.releaseAfterTerminal(job);
83824
84014
  this.active = job;
83825
84015
  }
@@ -83980,6 +84170,24 @@ var require_storage_orchestrator_addon = __commonJS({
83980
84170
  for (const move of job.moves) if (move.state !== "done") throw new Error(`${move.storageClass} move did not complete verification`);
83981
84171
  }
83982
84172
  /**
84173
+ * After a from→to copy whose source was NOT the class default: stop new
84174
+ * writes to `from`. The default is unchanged (that is the whole point of
84175
+ * naming a non-default source). A location that is already disabled is
84176
+ * left alone.
84177
+ */
84178
+ freezeDrainedSources(job) {
84179
+ for (const move of job.moves) {
84180
+ if (move.freezeSource !== true) continue;
84181
+ const loc = this.deps.locations.getLocationById(move.fromLocationId);
84182
+ if (!loc || loc.enabled === false) continue;
84183
+ const { createdAt: _c, updatedAt: _u, capacity: _cap, ...rest } = loc;
84184
+ this.deps.locations.upsertLocation({
84185
+ ...rest,
84186
+ enabled: false
84187
+ });
84188
+ }
84189
+ }
84190
+ /**
83983
84191
  * Arm one class's mover.
83984
84192
  *
83985
84193
  * `draining` runs after every writer has been resumed, so there is no lease
@@ -83992,6 +84200,7 @@ var require_storage_orchestrator_addon = __commonJS({
83992
84200
  if (laneOf(move.storageClass) === "media") {
83993
84201
  const input2 = {
83994
84202
  toLocationId: move.toLocationId,
84203
+ fromLocationId: move.fromLocationId,
83995
84204
  throttleMbps: job.throttleMbps,
83996
84205
  mode: "move"
83997
84206
  };
@@ -84025,7 +84234,7 @@ var require_storage_orchestrator_addon = __commonJS({
84025
84234
  }
84026
84235
  async setPhase(job, phase) {
84027
84236
  job.phase = phase;
84028
- if (isTerminal(job)) job.finishedAt = this.deps.now();
84237
+ if (isTerminal$1(job)) job.finishedAt = this.deps.now();
84029
84238
  await this.persist(job);
84030
84239
  }
84031
84240
  async finishCancelled(job) {
@@ -84037,6 +84246,9 @@ var require_storage_orchestrator_addon = __commonJS({
84037
84246
  async persist(job) {
84038
84247
  job.updatedAt = this.deps.now();
84039
84248
  await this.deps.state.set(job);
84249
+ if (!isTerminal$1(job) || this.deps.history === void 0) return;
84250
+ const prev = [...await this.deps.history.get() ?? []];
84251
+ await this.deps.history.set([job, ...prev.filter((row) => row.jobId !== job.jobId)].slice(0, HISTORY_CAP));
84040
84252
  }
84041
84253
  };
84042
84254
  async function unanswerable(read) {
@@ -84050,9 +84262,10 @@ var require_storage_orchestrator_addon = __commonJS({
84050
84262
  if (job.pauseLeaseId === null) throw new Error("storage migration has no maintenance lease");
84051
84263
  return job.pauseLeaseId;
84052
84264
  }
84053
- function isTerminal(job) {
84265
+ function isTerminal$1(job) {
84054
84266
  return job.phase === "done" || job.phase === "failed" || job.phase === "cancelled";
84055
84267
  }
84268
+ var HISTORY_CAP = 20;
84056
84269
  function laneOf(storageClass) {
84057
84270
  return storageClass === "eventMedia" ? "media" : "footage";
84058
84271
  }
@@ -84105,6 +84318,46 @@ var require_storage_orchestrator_addon = __commonJS({
84105
84318
  function canProbeOccupancyLocally(locality) {
84106
84319
  return locality === true;
84107
84320
  }
84321
+ var STORAGE_CLASS_SUBTREES = {
84322
+ eventMedia: ["events"],
84323
+ recordingsLow: ["low"],
84324
+ recordings: ["high", "mid"]
84325
+ };
84326
+ function isDotEntry(name) {
84327
+ return name.startsWith(".");
84328
+ }
84329
+ async function dirHasAnyEntry(dir) {
84330
+ let handle;
84331
+ try {
84332
+ handle = await (0, node_fs_promises.opendir)(dir);
84333
+ } catch (err) {
84334
+ if (err.code === "ENOENT") return false;
84335
+ throw err;
84336
+ }
84337
+ try {
84338
+ return await handle.read() !== null;
84339
+ } finally {
84340
+ await handle.close();
84341
+ }
84342
+ }
84343
+ async function probeLocalDirectoryOccupancy(input) {
84344
+ let names;
84345
+ try {
84346
+ names = await (0, node_fs_promises.readdir)(input.basePath);
84347
+ } catch (err) {
84348
+ if (err.code === "ENOENT") return "empty";
84349
+ return "unknown";
84350
+ }
84351
+ const visible = names.filter((name) => !isDotEntry(name));
84352
+ const subtrees = STORAGE_CLASS_SUBTREES[input.type];
84353
+ if (subtrees === void 0) return visible.length > 0 ? "occupied" : "empty";
84354
+ try {
84355
+ for (const camera of visible) for (const subtree of subtrees) if (await dirHasAnyEntry(node_path.default.join(input.basePath, camera, subtree))) return "occupied";
84356
+ } catch {
84357
+ return "unknown";
84358
+ }
84359
+ return "empty";
84360
+ }
84108
84361
  function resolveEngine(getEngines) {
84109
84362
  const engines = getEngines();
84110
84363
  if (engines.length === 0) throw new Error("settings-store: no data-store-provider engine is registered \u2014 the data door has nothing behind it");
@@ -84194,6 +84447,164 @@ var require_storage_orchestrator_addon = __commonJS({
84194
84447
  }
84195
84448
  return out;
84196
84449
  }
84450
+ var FOOTAGE_TYPES = /* @__PURE__ */ new Set(["recordings", "recordingsLow"]);
84451
+ function isTerminal(job) {
84452
+ return job.phase === "done" || job.phase === "failed" || job.phase === "cancelled";
84453
+ }
84454
+ function footageFrozen(location) {
84455
+ return location.enabled === false || location.config["readOnly"] === true;
84456
+ }
84457
+ var StorageCleanupCoordinator = class {
84458
+ deps;
84459
+ active = null;
84460
+ startReserved = false;
84461
+ constructor(deps) {
84462
+ this.deps = deps;
84463
+ }
84464
+ async start(input) {
84465
+ if (this.startReserved) throw new Error("storage cleanup is already active");
84466
+ this.startReserved = true;
84467
+ try {
84468
+ const existing = await this.status();
84469
+ if (existing && !isTerminal(existing)) throw new Error(`storage cleanup is already active (${existing.jobId})`);
84470
+ const now = this.deps.now();
84471
+ const job = {
84472
+ jobId: this.deps.newId(),
84473
+ phase: "orphans",
84474
+ includeDebugMedia: input.includeDebugMedia === true,
84475
+ orphansReclaimed: 0,
84476
+ orphanBytesReclaimed: 0,
84477
+ debugMediaReclaimed: 0,
84478
+ debugMediaBytesReclaimed: 0,
84479
+ ghostsForgotten: 0,
84480
+ ghostBytesForgotten: 0,
84481
+ detail: "Starting orphan reclaim",
84482
+ cancelRequested: false,
84483
+ startedAt: now,
84484
+ updatedAt: now,
84485
+ finishedAt: null,
84486
+ error: null
84487
+ };
84488
+ await this.persist(job);
84489
+ this.active = job;
84490
+ this.run(job);
84491
+ return job.jobId;
84492
+ } finally {
84493
+ this.startReserved = false;
84494
+ }
84495
+ }
84496
+ async status(jobId) {
84497
+ const job = this.active ?? await this.deps.state.get();
84498
+ if (jobId !== void 0 && job?.jobId !== jobId) return null;
84499
+ return job;
84500
+ }
84501
+ async cancel(jobId) {
84502
+ const job = await this.status(jobId);
84503
+ if (!job || isTerminal(job)) return false;
84504
+ job.cancelRequested = true;
84505
+ job.detail = "Cancel requested \u2014 finishing the current step";
84506
+ await this.persist(job);
84507
+ return true;
84508
+ }
84509
+ async run(job) {
84510
+ try {
84511
+ await this.reclaimOrphans(job);
84512
+ if (await this.stopped(job)) return;
84513
+ if (job.includeDebugMedia) {
84514
+ await this.setPhase(job, "debug-media", "Reclaiming debug media");
84515
+ await this.reclaimDebugMedia(job);
84516
+ if (await this.stopped(job)) return;
84517
+ }
84518
+ await this.setPhase(job, "ghost-ledger", "Forgetting ghost footage rows");
84519
+ await this.forgetGhosts(job);
84520
+ if (await this.stopped(job)) return;
84521
+ await this.finish(job, "done", null);
84522
+ } catch (err) {
84523
+ const message = err instanceof Error ? err.message : String(err);
84524
+ await this.finish(job, "failed", message);
84525
+ }
84526
+ }
84527
+ async reclaimOrphans(job) {
84528
+ let restart = true;
84529
+ for (; ; ) {
84530
+ if (await this.stopped(job)) return;
84531
+ const start = await this.deps.participants.startOrphan(restart);
84532
+ restart = false;
84533
+ if (!start.started && !start.alreadyRunning) throw new Error("orphan reclaim did not start");
84534
+ const status = await this.waitUntilIdle(() => this.deps.participants.orphanStatus(), job, (s) => `Orphan reclaim: ${s.totalReclaimed} rows / ${String(s.totalBytesReclaimed)} bytes`);
84535
+ job.orphansReclaimed += status.totalReclaimed;
84536
+ job.orphanBytesReclaimed += status.totalBytesReclaimed;
84537
+ job.detail = `Orphans: ${job.orphansReclaimed} rows, ${String(job.orphanBytesReclaimed)} bytes`;
84538
+ await this.persist(job);
84539
+ if (status.error) throw new Error(status.error);
84540
+ if (status.complete === true) return;
84541
+ }
84542
+ }
84543
+ async reclaimDebugMedia(job) {
84544
+ const start = await this.deps.participants.startDebugMedia();
84545
+ if (!start.started && !start.alreadyRunning) throw new Error("debug-media reclaim did not start");
84546
+ const status = await this.waitUntilIdle(() => this.deps.participants.debugMediaStatus(), job, (s) => `Debug media: ${s.totalReclaimed} files`);
84547
+ job.debugMediaReclaimed = status.totalReclaimed;
84548
+ job.debugMediaBytesReclaimed = status.totalBytesReclaimed;
84549
+ await this.persist(job);
84550
+ if (status.error) throw new Error(status.error);
84551
+ }
84552
+ async forgetGhosts(job) {
84553
+ const frozen = this.deps.locations.listLocations().filter((l) => FOOTAGE_TYPES.has(l.type) && footageFrozen(l));
84554
+ if (frozen.length === 0) {
84555
+ job.detail = "No frozen footage locations";
84556
+ await this.persist(job);
84557
+ return;
84558
+ }
84559
+ for (const loc of frozen) {
84560
+ if (await this.stopped(job)) return;
84561
+ job.detail = `Ghost ledger: ${loc.id}`;
84562
+ await this.persist(job);
84563
+ const report = await this.deps.participants.walkLedger({
84564
+ locationId: loc.id,
84565
+ apply: true
84566
+ });
84567
+ job.ghostsForgotten += report.forgottenSegments;
84568
+ job.ghostBytesForgotten += report.forgottenBytes;
84569
+ await this.persist(job);
84570
+ }
84571
+ }
84572
+ async waitUntilIdle(read, job, detail) {
84573
+ const pollMs = this.deps.pollMs ?? 2e3;
84574
+ const sleep = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
84575
+ for (; ; ) {
84576
+ const status = await read();
84577
+ job.detail = detail(status);
84578
+ await this.persist(job);
84579
+ if (!status.running) return status;
84580
+ await sleep(pollMs);
84581
+ }
84582
+ }
84583
+ async stopped(job) {
84584
+ if ((await this.status(job.jobId))?.cancelRequested === true) {
84585
+ await this.finish(job, "cancelled", null);
84586
+ return true;
84587
+ }
84588
+ return false;
84589
+ }
84590
+ async setPhase(job, phase, detail) {
84591
+ job.phase = phase;
84592
+ job.detail = detail;
84593
+ await this.persist(job);
84594
+ }
84595
+ async finish(job, phase, error) {
84596
+ job.phase = phase;
84597
+ job.error = error;
84598
+ job.finishedAt = this.deps.now();
84599
+ job.detail = phase === "done" ? "Cleanup finished" : job.detail;
84600
+ await this.persist(job);
84601
+ }
84602
+ async persist(job) {
84603
+ job.updatedAt = this.deps.now();
84604
+ this.active = job;
84605
+ await this.deps.state.set(job);
84606
+ }
84607
+ };
84197
84608
  var SESSION_VOCABULARY = {
84198
84609
  upload: {
84199
84610
  idLabel: "uploadId",
@@ -84281,7 +84692,7 @@ var require_storage_orchestrator_addon = __commonJS({
84281
84692
  * Reconciles both directions so operator edits survive a reboot:
84282
84693
  * 1. hydrate — DB rows win over any early in-memory seed (restores
84283
84694
  * operator config like `minFreePercent` that the pre-store boot
84284
- * can't see), with the same `isSystem` upgrade as {@link initialize};
84695
+ * can't see);
84285
84696
  * 2. backfill — in-memory locations the DB doesn't have yet (the
84286
84697
  * pre-store seed defaults on a fresh install) are persisted, so the
84287
84698
  * store becomes the durable source of truth from here on.
@@ -84303,15 +84714,7 @@ var require_storage_orchestrator_addon = __commonJS({
84303
84714
  const rows = await store.loadAll();
84304
84715
  this.locationStore = store;
84305
84716
  const dbIds = new Set(rows.map((r) => r.id));
84306
- for (const loc of rows) {
84307
- const upgraded = !loc.isSystem && loc.id === `${loc.type}:default` ? {
84308
- ...loc,
84309
- isSystem: true
84310
- } : loc;
84311
- this.locations.set(upgraded.id, upgraded);
84312
- if (upgraded !== loc) store.upsert(upgraded).catch(() => {
84313
- });
84314
- }
84717
+ for (const loc of rows) this.locations.set(loc.id, loc);
84315
84718
  const backfill = [...this.locations.values()].filter((l) => !dbIds.has(l.id));
84316
84719
  for (const loc of backfill) store.upsert(loc).catch((err) => {
84317
84720
  this.logger.warn("storage-orchestrator: attachStore backfill persist failed", { meta: {
@@ -84403,25 +84806,8 @@ var require_storage_orchestrator_addon = __commonJS({
84403
84806
  async initialize() {
84404
84807
  if (!this.locationStore) return;
84405
84808
  const rows = await this.locationStore.loadAll();
84406
- let upgraded = 0;
84407
- for (const loc of rows) if (!loc.isSystem && loc.id === `${loc.type}:default`) {
84408
- const upgradedLoc = {
84409
- ...loc,
84410
- isSystem: true
84411
- };
84412
- this.locations.set(upgradedLoc.id, upgradedLoc);
84413
- this.locationStore.upsert(upgradedLoc).catch((err) => {
84414
- this.logger.warn("storage-orchestrator: isSystem upgrade persist failed", { meta: {
84415
- id: upgradedLoc.id,
84416
- error: err instanceof Error ? err.message : String(err)
84417
- } });
84418
- });
84419
- upgraded++;
84420
- } else this.locations.set(loc.id, loc);
84421
- this.logger.info("storage-orchestrator: hydrated locations from store", { meta: {
84422
- loaded: this.locations.size,
84423
- isSystemUpgraded: upgraded
84424
- } });
84809
+ for (const loc of rows) this.locations.set(loc.id, loc);
84810
+ this.logger.info("storage-orchestrator: hydrated locations from store", { meta: { loaded: this.locations.size } });
84425
84811
  this.reportDisabledDefaults();
84426
84812
  }
84427
84813
  /**
@@ -84607,10 +84993,6 @@ var require_storage_orchestrator_addon = __commonJS({
84607
84993
  ...input,
84608
84994
  nodeId: "hub"
84609
84995
  };
84610
- if (existing?.isSystem === true) input = {
84611
- ...input,
84612
- isSystem: true
84613
- };
84614
84996
  input = {
84615
84997
  ...input,
84616
84998
  ...resolveEnabled(input, existing, this.hasAnyLocationOfType(input.type))
@@ -84657,21 +85039,22 @@ var require_storage_orchestrator_addon = __commonJS({
84657
85039
  return next;
84658
85040
  }
84659
85041
  /**
84660
- * Remove a location. Refuses to remove the default of a type unless a
84661
- * sibling default exists (defensive `upsertLocation` already ensures
84662
- * at most one default per type, but the logic guards against future
84663
- * bypass paths e.g. SQLite migration that imports two defaults).
84664
- *
84665
- * Persistence (Task 6) mirrors the delete asynchronously, with errors
84666
- * routed to the logger — see `upsertLocation` for the rationale.
85042
+ * Remove a location. Refuses the last location of a type and the last
85043
+ * enabled location of a type. `isSystem` is informational — a frozen
85044
+ * recordings `:default` is deletable once a sibling is live. Deleting
85045
+ * the flagged default promotes an enabled sibling. Occupancy still
85046
+ * refuses unless `force` is set; force never overrides uniqueness /
85047
+ * last-enabled.
84667
85048
  */
84668
85049
  async deleteLocation(id, options) {
84669
85050
  const loc = this.locations.get(id);
84670
85051
  if (!loc) throw new Error(`Storage location "${id}" not found`);
84671
- if (loc.isSystem) throw new Error(`Storage location "${id}" is system-managed and cannot be deleted. Edit its config (path / providerId) instead.`);
84672
- if (loc.isDefault) {
84673
- if (![...this.locations.values()].find((l) => l.type === loc.type && l.id !== id && l.isDefault)) throw new Error(`Cannot delete default location "${id}" for type "${loc.type}" \u2014 promote another location to default first`);
84674
- }
85052
+ const ofType = [...this.locations.values()].filter((l) => l.type === loc.type);
85053
+ if (ofType.length <= 1) throw new Error(`Cannot delete "${id}" \u2014 it is the only location for type "${loc.type}"`);
85054
+ const enabledOfType = ofType.filter((l) => l.enabled !== false);
85055
+ if (loc.enabled !== false && enabledOfType.length <= 1) throw new Error(`Cannot delete "${id}" \u2014 it is the only enabled location for type "${loc.type}"`);
85056
+ const successor = loc.isDefault === true ? ofType.find((l) => l.id !== id && l.enabled !== false) : void 0;
85057
+ if (loc.isDefault === true && successor === void 0) throw new Error(`Cannot delete "${id}" \u2014 it is the only enabled location for type "${loc.type}"`);
84675
85058
  const occupancy = await this.probeOccupancy(loc);
84676
85059
  if (occupancy !== "empty") {
84677
85060
  if (options?.force !== true) throw new Error(occupancy === "occupied" ? `Storage location "${id}" still holds data \u2014 drain it first (storage migration / relocate), or pass force to delete the record anyway and strand what is on it` : `Storage location "${id}" could not be checked for remaining data (unreachable provider, another node, or an unmounted root) \u2014 pass force to delete the record anyway`);
@@ -84681,6 +85064,21 @@ var require_storage_orchestrator_addon = __commonJS({
84681
85064
  occupancy
84682
85065
  } });
84683
85066
  }
85067
+ if (successor !== void 0 && successor.isDefault !== true) {
85068
+ const now = Date.now();
85069
+ const promoted = {
85070
+ ...successor,
85071
+ isDefault: true,
85072
+ updatedAt: now
85073
+ };
85074
+ this.locations.set(successor.id, promoted);
85075
+ if (this.locationStore) this.locationStore.upsert(promoted).catch((err) => {
85076
+ this.logger.error("storage-orchestrator: default promotion persist failed", { meta: {
85077
+ id: successor.id,
85078
+ error: err instanceof Error ? err.message : String(err)
85079
+ } });
85080
+ });
85081
+ }
84684
85082
  this.locations.delete(id);
84685
85083
  if (this.locationStore) this.locationStore.delete(id).catch((err) => {
84686
85084
  this.logger.error("storage-orchestrator: delete persistence failed", { meta: {
@@ -84707,15 +85105,14 @@ var require_storage_orchestrator_addon = __commonJS({
84707
85105
  }
84708
85106
  }
84709
85107
  /**
84710
- * Remove system-seeded locations whose type is no longer declared by any
84711
- * addon (stale defaults from a removed location type). Operator-added
84712
- * (non-system) locations of an undeclared type are KEPT but warned — the
84713
- * operator owns them. Requires the registry to be set first.
85108
+ * Remove seed-shaped locations (`<type>:default`) whose type is no longer
85109
+ * declared by any addon (stale defaults from a removed location type).
85110
+ * Operator-added locations of an undeclared type are KEPT but warned.
84714
85111
  *
84715
85112
  * FAIL-SAFE: if the registry is EMPTY (no addon declared any location), we
84716
85113
  * refuse to prune anything. An empty registry almost always means
84717
85114
  * declarations failed to load (boot ordering, a stale install) — pruning
84718
- * "everything undeclared" in that state would wipe every system location
85115
+ * "everything undeclared" in that state would wipe every seeded location
84719
85116
  * (data/logs/recordings/…). Better to keep stale rows than destroy live ones.
84720
85117
  */
84721
85118
  pruneUndeclaredSystemLocations() {
@@ -84726,7 +85123,7 @@ var require_storage_orchestrator_addon = __commonJS({
84726
85123
  }
84727
85124
  for (const [id, loc] of Array.from(this.locations)) {
84728
85125
  if (this.registry.cardinalityOf(loc.type) !== null) continue;
84729
- if (loc.isSystem) {
85126
+ if (loc.id === `${loc.type}:default`) {
84730
85127
  this.locations.delete(id);
84731
85128
  if (this.locationStore) this.locationStore.delete(id).catch((err) => {
84732
85129
  this.logger.error("storage-orchestrator: prune persistence failed", { meta: {
@@ -84734,7 +85131,7 @@ var require_storage_orchestrator_addon = __commonJS({
84734
85131
  error: err instanceof Error ? err.message : String(err)
84735
85132
  } });
84736
85133
  });
84737
- this.logger.info("storage-orchestrator: pruned stale system location", { meta: {
85134
+ this.logger.info("storage-orchestrator: pruned stale seeded location", { meta: {
84738
85135
  id,
84739
85136
  type: loc.type
84740
85137
  } });
@@ -84894,7 +85291,7 @@ var require_storage_orchestrator_addon = __commonJS({
84894
85291
  providerId: input.providerId,
84895
85292
  config: { basePath: base },
84896
85293
  isDefault: true,
84897
- isSystem: true
85294
+ isSystem: false
84898
85295
  });
84899
85296
  added++;
84900
85297
  }
@@ -85076,6 +85473,7 @@ var require_storage_orchestrator_addon = __commonJS({
85076
85473
  pressureTimer = null;
85077
85474
  pressureSweepInFlight = false;
85078
85475
  migration = null;
85476
+ cleanup = null;
85079
85477
  /**
85080
85478
  * Cached `providerId → nodeLocal` snapshot (SP1). Backs the orchestrator's
85081
85479
  * synchronous `NodeLocalResolver` — `getProviderInfo()` is async, so we
@@ -85133,7 +85531,7 @@ var require_storage_orchestrator_addon = __commonJS({
85133
85531
  const rows = type !== void 0 ? service.listLocations({ type }) : service.listLocations();
85134
85532
  return Promise.all(rows.map(async (loc) => ({
85135
85533
  ...this.redacted(loc),
85136
- capacity: await this.localCapacityOf(loc)
85534
+ capacity: loc.enabled === false ? null : await this.localCapacityOf(loc)
85137
85535
  })));
85138
85536
  },
85139
85537
  getDefaultLocation: async ({ type }) => {
@@ -85301,6 +85699,7 @@ var require_storage_orchestrator_addon = __commonJS({
85301
85699
  const migration = new StorageMigrationCoordinator({
85302
85700
  locations: service,
85303
85701
  state: this.state("storage-migration", require_dist10.StorageMigrationJobSchema.nullable(), null),
85702
+ history: this.state("storage-migration-history", zod.z.array(require_dist10.StorageMigrationJobSchema), []),
85304
85703
  participants: {
85305
85704
  pipeline: {
85306
85705
  pause: async (leaseId) => {
@@ -85351,6 +85750,36 @@ var require_storage_orchestrator_addon = __commonJS({
85351
85750
  deviceKeyOf: (locationId) => this.locationDeviceKey(locationId)
85352
85751
  });
85353
85752
  this.migration = migration;
85753
+ const cleanup = new StorageCleanupCoordinator({
85754
+ locations: service,
85755
+ state: this.state("storage-cleanup", require_dist10.StorageCleanupJobSchema.nullable(), null),
85756
+ participants: {
85757
+ startOrphan: async (restart) => {
85758
+ return parseStarted(await this.ctx.api.addons.custom.mutate({
85759
+ addonId: "pipeline-analytics",
85760
+ action: "retention.orphanAudit",
85761
+ input: {
85762
+ mode: "reclaim",
85763
+ restart,
85764
+ maxRowsPerScope: 2e4
85765
+ }
85766
+ }));
85767
+ },
85768
+ orphanStatus: async () => {
85769
+ return parseOrphanStatus(await this.ctx.api.addons.custom.mutate({
85770
+ addonId: "pipeline-analytics",
85771
+ action: "retention.orphanAuditStatus",
85772
+ input: {}
85773
+ }));
85774
+ },
85775
+ startDebugMedia: () => this.ctx.api.pipelineAnalytics.reclaimDebugMedia.mutate({ mode: "reclaim" }),
85776
+ debugMediaStatus: () => this.ctx.api.pipelineAnalytics.getMediaReclaimStatus.query({}),
85777
+ walkLedger: (input) => this.ctx.api.recording.reconcileLedgerAgainstDisk.mutate(input)
85778
+ },
85779
+ now: () => Date.now(),
85780
+ newId: () => (0, node_crypto.randomUUID)()
85781
+ });
85782
+ this.cleanup = cleanup;
85354
85783
  const migrationProvider = {
85355
85784
  plan: (input) => migration.plan(input),
85356
85785
  start: async (input) => ({ jobId: await migration.start(input) }),
@@ -85358,7 +85787,11 @@ var require_storage_orchestrator_addon = __commonJS({
85358
85787
  cancel: async ({ jobId }) => ({ cancelled: await migration.cancel(jobId) }),
85359
85788
  movers: () => migration.movers(),
85360
85789
  residue: () => migration.residue(),
85361
- drain: async (input) => ({ jobId: await migration.drain(input) })
85790
+ drain: async (input) => ({ jobId: await migration.drain(input) }),
85791
+ cleanupStart: async (input) => ({ jobId: await cleanup.start(input) }),
85792
+ cleanupStatus: ({ jobId }) => cleanup.status(jobId),
85793
+ cleanupCancel: async ({ jobId }) => ({ cancelled: await cleanup.cancel(jobId) }),
85794
+ history: () => migration.history()
85362
85795
  };
85363
85796
  await this.seedFromDeclarations();
85364
85797
  const eventBus = this.ctx.eventBus;
@@ -85603,11 +86036,14 @@ var require_storage_orchestrator_addon = __commonJS({
85603
86036
  }
85604
86037
  }
85605
86038
  /**
85606
- * Does this location still hold anything? A SHALLOW `readdir` of its
85607
- * `basePath` — one syscall, never a walk. `/recordings` holds one entry per
85608
- * camera, so this is cheap on a volume with millions of segments, and a
85609
- * `find`-style walk of a saturated recordings disk is precisely what must
85610
- * not happen here.
86039
+ * Does this location still hold anything of ITS class?
86040
+ *
86041
+ * Shared roots (`/recordings` hosting high + low + events) are scored by
86042
+ * class subtree (`<camera>/high|mid`, `/low`, `/events`), not by a raw
86043
+ * `readdir` of the root — camera dirs and `.camstack-location` would
86044
+ * otherwise make every class look occupied. The walk stays shallow: one
86045
+ * dirent per camera, then first-entry of the named subtree. A `find` of
86046
+ * segments is precisely what must not happen here.
85611
86047
  *
85612
86048
  * **What it cannot see, stated rather than papered over:**
85613
86049
  *
@@ -85616,9 +86052,10 @@ var require_storage_orchestrator_addon = __commonJS({
85616
86052
  * `unknown`, never `empty`.
85617
86053
  * - A location with no `basePath` — same answer.
85618
86054
  * - The difference between "holds live footage" and "holds one stray
85619
- * lock-file". It measures BYTES ON DISK, not durable rows, so it refuses
85620
- * a location holding orphan files that no index names. That direction is
85621
- * the safe one, and `force` is the escape hatch for it.
86055
+ * lock-file". It measures ON-DISK entries of this class, not durable
86056
+ * rows, so it refuses a location holding orphan files that no index
86057
+ * names. That direction is the safe one, and `force` is the escape
86058
+ * hatch for it.
85622
86059
  *
85623
86060
  * A missing root (`ENOENT`) is `empty`, not `unknown`: an unmounted disk and
85624
86061
  * a deleted directory are indistinguishable here, and if the record's own
@@ -85636,16 +86073,16 @@ var require_storage_orchestrator_addon = __commonJS({
85636
86073
  if ((location.nodeId === void 0 || location.nodeId === "" ? HUB_NODE_ID : location.nodeId) !== (this.service?.getLocalNodeId() ?? HUB_NODE_ID)) return "unknown";
85637
86074
  const basePath = this.locationBasePath(location.id);
85638
86075
  if (basePath === null) return "unknown";
85639
- try {
85640
- return (await node_fs_promises.readdir(basePath)).length > 0 ? "occupied" : "empty";
85641
- } catch (err) {
85642
- if (err instanceof Error && "code" in err && err.code === "ENOENT") return "empty";
85643
- this.ctx.logger.warn("storage-orchestrator: could not read a location root to check it", { meta: {
85644
- id: location.id,
85645
- error: err instanceof Error ? err.message : String(err)
85646
- } });
85647
- return "unknown";
85648
- }
86076
+ const occupancy = await probeLocalDirectoryOccupancy({
86077
+ type: location.type,
86078
+ basePath
86079
+ });
86080
+ if (occupancy === "unknown") this.ctx.logger.warn("storage-orchestrator: could not read a location root to check it", { meta: {
86081
+ id: location.id,
86082
+ basePath,
86083
+ type: location.type
86084
+ } });
86085
+ return occupancy;
85649
86086
  }
85650
86087
  /** Free capacity (%) on a location's volume via `statfs`; 100 (guard inert) when unstattable. */
85651
86088
  async locationFreePercent(locationId) {
@@ -85721,6 +86158,38 @@ var require_storage_orchestrator_addon = __commonJS({
85721
86158
  }
85722
86159
  }
85723
86160
  };
86161
+ function parseStarted(raw) {
86162
+ if (raw !== null && typeof raw === "object") {
86163
+ const o = raw;
86164
+ if (typeof o["started"] === "boolean" && typeof o["alreadyRunning"] === "boolean") return {
86165
+ started: o["started"],
86166
+ alreadyRunning: o["alreadyRunning"]
86167
+ };
86168
+ }
86169
+ return {
86170
+ started: false,
86171
+ alreadyRunning: false
86172
+ };
86173
+ }
86174
+ function parseOrphanStatus(raw) {
86175
+ if (raw !== null && typeof raw === "object") {
86176
+ const o = raw;
86177
+ return {
86178
+ running: o["running"] === true,
86179
+ complete: typeof o["complete"] === "boolean" ? o["complete"] : null,
86180
+ totalReclaimed: typeof o["totalReclaimed"] === "number" ? o["totalReclaimed"] : 0,
86181
+ totalBytesReclaimed: typeof o["totalBytesReclaimed"] === "number" ? o["totalBytesReclaimed"] : 0,
86182
+ error: typeof o["error"] === "string" ? o["error"] : null
86183
+ };
86184
+ }
86185
+ return {
86186
+ running: false,
86187
+ complete: true,
86188
+ totalReclaimed: 0,
86189
+ totalBytesReclaimed: 0,
86190
+ error: "orphan audit status unreadable"
86191
+ };
86192
+ }
85724
86193
  exports.SqliteLocationStore = SqliteLocationStore;
85725
86194
  exports.StorageMigrationCoordinator = StorageMigrationCoordinator;
85726
86195
  exports.StorageOrchestratorAddon = StorageOrchestratorAddon;
@@ -85757,7 +86226,7 @@ var require_system_config_addon = __commonJS({
85757
86226
  [Symbol.toStringTag]: { value: "Module" }
85758
86227
  });
85759
86228
  require_chunk_Cek0wNdY();
85760
- var require_dist10 = require_dist_BhE8zNfY();
86229
+ var require_dist10 = require_dist_DCdtLXgx();
85761
86230
  var SECTION_TITLES = {
85762
86231
  server: "Server",
85763
86232
  auth: "Authentication"
@@ -103818,7 +104287,7 @@ var require_winston_logging = __commonJS({
103818
104287
  [Symbol.toStringTag]: { value: "Module" }
103819
104288
  });
103820
104289
  var require_chunk = require_chunk_Cek0wNdY();
103821
- var require_dist10 = require_dist_BhE8zNfY();
104290
+ var require_dist10 = require_dist_DCdtLXgx();
103822
104291
  var require_formatter = require_formatter_DqAKDlvN();
103823
104292
  var node_path = __require("path");
103824
104293
  node_path = require_chunk.__toESM(node_path);
@@ -105761,9 +106230,9 @@ var require_event_category_BaEgqJNv = __commonJS({
105761
106230
  }
105762
106231
  });
105763
106232
 
105764
- // ../types/dist/sleep-CqVyhcl-.js
105765
- var require_sleep_CqVyhcl = __commonJS({
105766
- "../types/dist/sleep-CqVyhcl-.js"(exports) {
106233
+ // ../types/dist/sleep-DkBAyPva.js
106234
+ var require_sleep_DkBAyPva = __commonJS({
106235
+ "../types/dist/sleep-DkBAyPva.js"(exports) {
105767
106236
  "use strict";
105768
106237
  var require_event_category = require_event_category_BaEgqJNv();
105769
106238
  var zod = require_zod();
@@ -108436,8 +108905,6 @@ var require_sleep_CqVyhcl = __commonJS({
108436
108905
  getTrack: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrack", "query", input),
108437
108906
  listTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listTracks", "query", input),
108438
108907
  listRecentTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRecentTracks", "query", input),
108439
- listGroups: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listGroups", "query", input),
108440
- getGroup: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getGroup", "query", input),
108441
108908
  clearTracks: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "clearTracks", "mutation", input),
108442
108909
  getMotionEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getMotionEvents", "query", input),
108443
108910
  getObjectEvents: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getObjectEvents", "query", input),
@@ -109436,7 +109903,7 @@ var require_addon = __commonJS({
109436
109903
  "use strict";
109437
109904
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
109438
109905
  var require_event_category = require_event_category_BaEgqJNv();
109439
- var require_sleep = require_sleep_CqVyhcl();
109906
+ var require_sleep = require_sleep_DkBAyPva();
109440
109907
  var require_err_msg = require_err_msg_COpsHMw2();
109441
109908
  var CAP_INPUT_DEFAULTS = Object.freeze({
109442
109909
  "addons": { "getLogs": { "limit": 100 } },
@@ -109550,7 +110017,6 @@ var require_addon = __commonJS({
109550
110017
  "getMotionEvents": { "limit": 1e3 },
109551
110018
  "getObjectEvents": { "limit": 1e3 },
109552
110019
  "getSensorEvents": { "limit": 1e3 },
109553
- "listGroups": { "limit": 40 },
109554
110020
  "listRecentTracks": { "limit": 200 },
109555
110021
  "reclaimDebugMedia": { "mode": "report" },
109556
110022
  "searchObjectEvents": {
@@ -116320,12 +116786,12 @@ var require_dist2 = __commonJS({
116320
116786
  }
116321
116787
  });
116322
116788
 
116323
- // ../system/dist/manifest-system-deps-uflHvHDB.js
116324
- var require_manifest_system_deps_uflHvHDB = __commonJS({
116325
- "../system/dist/manifest-system-deps-uflHvHDB.js"(exports) {
116789
+ // ../system/dist/manifest-system-deps-Bi7oyXcN.js
116790
+ var require_manifest_system_deps_Bi7oyXcN = __commonJS({
116791
+ "../system/dist/manifest-system-deps-Bi7oyXcN.js"(exports) {
116326
116792
  "use strict";
116327
116793
  var require_chunk = require_chunk_Cek0wNdY();
116328
- require_dist_BhE8zNfY();
116794
+ require_dist_DCdtLXgx();
116329
116795
  var node_crypto = __require("crypto");
116330
116796
  node_crypto = require_chunk.__toESM(node_crypto);
116331
116797
  var _camstack_types_node = require_node();
@@ -128473,7 +128939,7 @@ var require_dist3 = __commonJS({
128473
128939
  "use strict";
128474
128940
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
128475
128941
  var require_chunk = require_chunk_Cek0wNdY();
128476
- var require_dist10 = require_dist_BhE8zNfY();
128942
+ var require_dist10 = require_dist_DCdtLXgx();
128477
128943
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
128478
128944
  require_alerts();
128479
128945
  var require_formatter = require_formatter_DqAKDlvN();
@@ -128499,7 +128965,7 @@ var require_dist3 = __commonJS({
128499
128965
  var require_builtins_winston_logging_index = require_winston_logging();
128500
128966
  var require_file_data_plane = require_file_data_plane_DO8KbxCe();
128501
128967
  var require_tls$1 = require_tls_BxQlomxd();
128502
- var require_manifest_system_deps = require_manifest_system_deps_uflHvHDB();
128968
+ var require_manifest_system_deps = require_manifest_system_deps_Bi7oyXcN();
128503
128969
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
128504
128970
  var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
128505
128971
  var zod = require_zod();
@@ -209341,7 +209807,7 @@ var require_dist4 = __commonJS({
209341
209807
  "use strict";
209342
209808
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
209343
209809
  var require_event_category = require_event_category_BaEgqJNv();
209344
- var require_sleep = require_sleep_CqVyhcl();
209810
+ var require_sleep = require_sleep_DkBAyPva();
209345
209811
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
209346
209812
  var require_enums2 = require_enums();
209347
209813
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -210740,6 +211206,13 @@ var require_dist4 = __commonJS({
210740
211206
  ]);
210741
211207
  var RelocateMediaInputSchema = zod.z.object({
210742
211208
  toLocationId: zod.z.string(),
211209
+ /**
211210
+ * Restrict the pass to rows currently on this location. Omitted / `'*'` =
211211
+ * every row that is not already on `toLocationId` (the historical
211212
+ * behaviour). A named source is what a from→to migration needs: without it
211213
+ * "move events off disk 2" also emptied disk 1.
211214
+ */
211215
+ fromLocationId: zod.z.string().optional(),
210743
211216
  throttleMbps: zod.z.number().min(1).max(1e3).optional(),
210744
211217
  /** Omitted = `move`, the pre-existing behaviour. */
210745
211218
  mode: MediaRelocateModeSchema.optional()
@@ -210771,9 +211244,18 @@ var require_dist4 = __commonJS({
210771
211244
  backups: zod.z.string().min(1).optional(),
210772
211245
  galleryMedia: zod.z.string().min(1).optional()
210773
211246
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
211247
+ var StorageMigrationSourcesSchema = zod.z.object({
211248
+ recordings: zod.z.string().min(1).optional(),
211249
+ recordingsLow: zod.z.string().min(1).optional(),
211250
+ eventMedia: zod.z.string().min(1).optional(),
211251
+ backups: zod.z.string().min(1).optional(),
211252
+ galleryMedia: zod.z.string().min(1).optional()
211253
+ }).optional();
210774
211254
  var StorageMigrationModeSchema = zod.z.enum(["blocking", "nonBlocking"]);
210775
211255
  var StorageMigrationInputSchema = zod.z.object({
210776
211256
  destinations: StorageMigrationDestinationsSchema,
211257
+ /** Omitted = each class's current default. */
211258
+ sources: StorageMigrationSourcesSchema,
210777
211259
  throttleMbps: zod.z.number().min(1).max(1e3).optional(),
210778
211260
  /** Omitted = `blocking`, which stays the default. */
210779
211261
  mode: StorageMigrationModeSchema.optional()
@@ -210819,6 +211301,13 @@ var require_dist4 = __commonJS({
210819
211301
  storageClass: StorageMigrationClassSchema,
210820
211302
  fromLocationId: zod.z.string(),
210821
211303
  toLocationId: zod.z.string(),
211304
+ /**
211305
+ * True when `from` was NOT the class default at plan time. The move still
211306
+ * copies bytes, but the default is left alone and the source is disabled
211307
+ * once the copy verifies. Absent on jobs planned before this field existed
211308
+ * — those jobs always repointed, which is `false`.
211309
+ */
211310
+ freezeSource: zod.z.boolean().optional(),
210822
211311
  moverJobId: zod.z.string().nullable(),
210823
211312
  state: RelocateJobStateSchema.nullable(),
210824
211313
  error: zod.z.string().nullable(),
@@ -210832,6 +211321,7 @@ var require_dist4 = __commonJS({
210832
211321
  * can tell a seconds-long cutover from a thirty-hour one. */
210833
211322
  mode: StorageMigrationModeSchema,
210834
211323
  destinations: StorageMigrationDestinationsSchema,
211324
+ sources: StorageMigrationSourcesSchema,
210835
211325
  throttleMbps: zod.z.number(),
210836
211326
  moves: zod.z.array(StorageMigrationMoveSchema),
210837
211327
  pauseLeaseId: zod.z.string().nullable(),
@@ -210858,6 +211348,7 @@ var require_dist4 = __commonJS({
210858
211348
  });
210859
211349
  var StorageMigrationPlanSchema = zod.z.object({
210860
211350
  destinations: StorageMigrationDestinationsSchema,
211351
+ sources: StorageMigrationSourcesSchema,
210861
211352
  /** The mode this plan was built for. A plan is only valid for its mode: the
210862
211353
  * `eventMedia` seal gate and the single-cardinality refusal both depend on
210863
211354
  * it. */
@@ -210865,7 +211356,8 @@ var require_dist4 = __commonJS({
210865
211356
  moves: zod.z.array(zod.z.object({
210866
211357
  storageClass: StorageMigrationClassSchema,
210867
211358
  fromLocationId: zod.z.string(),
210868
- toLocationId: zod.z.string()
211359
+ toLocationId: zod.z.string(),
211360
+ freezeSource: zod.z.boolean().optional()
210869
211361
  })),
210870
211362
  findings: zod.z.array(StorageMigrationFindingSchema)
210871
211363
  });
@@ -210977,9 +211469,42 @@ var require_dist4 = __commonJS({
210977
211469
  var RelocatableMediaCountSchema = zod.z.object({ rows: zod.z.number().int().nonnegative() }).nullable();
210978
211470
  var RelocatableMediaCountInputSchema = zod.z.object({
210979
211471
  toLocationId: zod.z.string().min(1),
211472
+ /** Restrict the count to one source; omitted / `'*'` = every non-target row. */
211473
+ fromLocationId: zod.z.string().optional(),
210980
211474
  /** Omitted = `move`. */
210981
211475
  mode: MediaRelocateModeSchema.optional()
210982
211476
  });
211477
+ var StorageCleanupPhaseSchema = zod.z.enum([
211478
+ "orphans",
211479
+ "debug-media",
211480
+ "ghost-ledger",
211481
+ "done",
211482
+ "failed",
211483
+ "cancelled"
211484
+ ]);
211485
+ var StorageCleanupInputSchema = zod.z.object({
211486
+ /** Also walk motion stills / track filmstrips. Off by default. */
211487
+ includeDebugMedia: zod.z.boolean().optional()
211488
+ });
211489
+ var StorageCleanupJobSchema = zod.z.object({
211490
+ jobId: zod.z.string(),
211491
+ phase: StorageCleanupPhaseSchema,
211492
+ includeDebugMedia: zod.z.boolean(),
211493
+ orphansReclaimed: zod.z.number().int().nonnegative(),
211494
+ orphanBytesReclaimed: zod.z.number().int().nonnegative(),
211495
+ debugMediaReclaimed: zod.z.number().int().nonnegative(),
211496
+ debugMediaBytesReclaimed: zod.z.number().int().nonnegative(),
211497
+ ghostsForgotten: zod.z.number().int().nonnegative(),
211498
+ ghostBytesForgotten: zod.z.number().int().nonnegative(),
211499
+ /** Short operator-facing line: current collection, pass, or location. */
211500
+ detail: zod.z.string().nullable(),
211501
+ cancelRequested: zod.z.boolean(),
211502
+ startedAt: zod.z.number(),
211503
+ updatedAt: zod.z.number(),
211504
+ finishedAt: zod.z.number().nullable(),
211505
+ error: zod.z.string().nullable()
211506
+ });
211507
+ var StorageCleanupStatusInputSchema = zod.z.object({ jobId: zod.z.string().optional() });
210983
211508
  var SUB_DETECTION_TYPES = ["face", "plate"];
210984
211509
  var RECOGNITION_TYPES = [
210985
211510
  "face",
@@ -216999,6 +217524,27 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
216999
217524
  kind: "mutation",
217000
217525
  auth: "admin"
217001
217526
  }),
217527
+ /** Rename a room: replace the label in the registry IN PLACE and move
217528
+ * every device that carried it, as ONE server operation.
217529
+ *
217530
+ * The admin UI used to do this as `addLocation(to)` → N ×
217531
+ * `setLocation(device, to)` → `removeLocation(from)` from the browser.
217532
+ * Nothing bound those three together, so a closed tab left the fleet
217533
+ * split across two rooms — one of which the operator believed was gone.
217534
+ *
217535
+ * `from` is matched the way the registry matches everywhere else
217536
+ * (trimmed, case-insensitive) and need NOT be registered: the registry is
217537
+ * a suggestion list, and the devices that most need a rename are exactly
217538
+ * the ones carrying a label nobody registered. `to` renames onto an
217539
+ * existing room by MERGING into it, leaving no duplicate label. An empty
217540
+ * `to` throws before anything is written. */
217541
+ renameLocation: require_sleep.method(zod.z.object({
217542
+ from: zod.z.string(),
217543
+ to: zod.z.string()
217544
+ }), zod.z.object({ moved: zod.z.number() }), {
217545
+ kind: "mutation",
217546
+ auth: "admin"
217547
+ }),
217002
217548
  /** Soft-disable / re-enable the device. Drivers consult
217003
217549
  * `BaseDevice.disabled` to gate lifecycle hooks. */
217004
217550
  setDisabled: require_sleep.method(zod.z.object({
@@ -223433,46 +223979,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223433
223979
  /** Cursor for the next page, or null when this page is the last. */
223434
223980
  nextCursor: zod.z.string().nullable()
223435
223981
  });
223436
- var LIST_GROUPS_DEFAULT_LIMIT = 40;
223437
- var LIST_GROUPS_MAX_LIMIT = 100;
223438
- var AnalyticsGroupRecordSchema = zod.z.object({
223439
- id: zod.z.string(),
223440
- deviceId: zod.z.number().int(),
223441
- openedAt: zod.z.number().int(),
223442
- closedAt: zod.z.number().int(),
223443
- timestamp: zod.z.number().int(),
223444
- memberCount: zod.z.number().int(),
223445
- memberTrackIds: zod.z.array(zod.z.string()).readonly(),
223446
- className: zod.z.string(),
223447
- classes: zod.z.array(zod.z.string()).readonly(),
223448
- /** Relative event-media path, or null when the group has no picture yet. */
223449
- mediaUrl: zod.z.string().nullable(),
223450
- singleton: zod.z.boolean()
223451
- });
223452
- var AnalyticsGroupMemberSchema = zod.z.object({
223453
- trackId: zod.z.string(),
223454
- deviceId: zod.z.number().int(),
223455
- className: zod.z.string(),
223456
- firstSeen: zod.z.number().int(),
223457
- lastSeen: zod.z.number().int(),
223458
- mediaUrl: zod.z.string().nullable()
223459
- });
223460
- var AnalyticsGroupDetailSchema = AnalyticsGroupRecordSchema.extend({ members: zod.z.array(AnalyticsGroupMemberSchema).readonly() });
223461
- var ListGroupsQueryInput = zod.z.object({
223462
- /** Devices to merge. An empty array yields `{ groups: [], nextCursor: null }`. */
223463
- deviceIds: zod.z.array(zod.z.number()),
223464
- /** Window lower bound on `closedAt` (inclusive). */
223465
- since: zod.z.number().optional(),
223466
- /** Window upper bound on `openedAt` (inclusive). */
223467
- until: zod.z.number().optional(),
223468
- limit: zod.z.number().int().min(1).max(LIST_GROUPS_MAX_LIMIT).default(LIST_GROUPS_DEFAULT_LIMIT),
223469
- /** Opaque continuation cursor from a previous page's `nextCursor`. */
223470
- cursor: zod.z.string().optional()
223471
- });
223472
- var ListGroupsPageSchema = zod.z.object({
223473
- groups: zod.z.array(AnalyticsGroupRecordSchema).readonly(),
223474
- nextCursor: zod.z.string().nullable()
223475
- });
223476
223982
  var KEY_EVENTS_DEFAULT_LIMIT = 50;
223477
223983
  var KEY_EVENTS_MAX_LIMIT = 200;
223478
223984
  var KeyEventQueryInput = zod.z.object({
@@ -223565,9 +224071,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223565
224071
  /** Unassigned plate reads removed (best-effort; plate leg deferred to plate-intelligence). */
223566
224072
  plates: zod.z.number().int(),
223567
224073
  /** Per-track CLIP search vectors removed (best-effort). */
223568
- embeddings: zod.z.number().int(),
223569
- /** Group membership + group rows removed with their last member (best-effort). */
223570
- groups: zod.z.number().int()
224074
+ embeddings: zod.z.number().int()
223571
224075
  });
223572
224076
  var DiskReconcileCountsSchema = zod.z.object({
223573
224077
  mediaDropped: zod.z.number().int(),
@@ -223780,16 +224284,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223780
224284
  * are not included (same contract as `listTracks`).
223781
224285
  */
223782
224286
  listRecentTracks: require_sleep.method(RecentTracksQueryInput, RecentTracksPageSchema),
223783
- /**
223784
- * Batched co-moving group listing — the Groups feed. Same merge/cursor
223785
- * contract as {@link listRecentTracks}. A group is a sealed partition of
223786
- * one session; `getGroup` is the detail with members.
223787
- */
223788
- listGroups: require_sleep.method(ListGroupsQueryInput, ListGroupsPageSchema),
223789
- getGroup: require_sleep.method(zod.z.object({
223790
- deviceId: zod.z.number(),
223791
- groupId: zod.z.string().min(1)
223792
- }), AnalyticsGroupDetailSchema.nullable()),
223793
224287
  clearTracks: require_sleep.method(zod.z.object({ deviceId: zod.z.number() }), zod.z.void(), {
223794
224288
  kind: "mutation",
223795
224289
  auth: "admin"
@@ -226903,7 +227397,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
226903
227397
  drain: require_sleep.method(StorageMigrationDrainInputSchema, zod.z.object({ jobId: zod.z.string() }), {
226904
227398
  kind: "mutation",
226905
227399
  auth: "admin"
226906
- })
227400
+ }),
227401
+ /**
227402
+ * One operator cleanup of leftover analytics (DB + blobs) and ghost
227403
+ * ledger rows on frozen footage locations. Optional debug-media sweep.
227404
+ * Returns immediately; poll {@link cleanupStatus}.
227405
+ */
227406
+ cleanupStart: require_sleep.method(StorageCleanupInputSchema, zod.z.object({ jobId: zod.z.string() }), {
227407
+ kind: "mutation",
227408
+ auth: "admin"
227409
+ }),
227410
+ cleanupStatus: require_sleep.method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }),
227411
+ cleanupCancel: require_sleep.method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
227412
+ kind: "mutation",
227413
+ auth: "admin"
227414
+ }),
227415
+ /** Recent finished migration jobs, newest first. The live job is `status`. */
227416
+ history: require_sleep.method(zod.z.object({}), zod.z.array(StorageMigrationJobSchema).readonly(), { auth: "admin" })
226907
227417
  }
226908
227418
  };
226909
227419
  var ProviderInfoSchema = zod.z.discriminatedUnion("shouldSaveDiskSpace", [zod.z.object({
@@ -233704,6 +234214,26 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233704
234214
  /** Ignore piles smaller than this (default 1 GB). */
233705
234215
  minMoveGb: zod.z.number().min(0).optional()
233706
234216
  });
234217
+ var RecordingDevicePlacementSchema = zod.z.object({
234218
+ deviceId: zod.z.number().int(),
234219
+ profile: zod.z.string(),
234220
+ locationId: zod.z.string()
234221
+ });
234222
+ var RecordingDevicePinSchema = zod.z.object({
234223
+ deviceId: zod.z.number().int(),
234224
+ /** Recordings-class location this camera is pinned to. */
234225
+ locationId: zod.z.string()
234226
+ });
234227
+ var RecordingPlacementViewSchema = zod.z.object({
234228
+ assignments: zod.z.array(RecordingDevicePlacementSchema),
234229
+ pins: zod.z.array(RecordingDevicePinSchema),
234230
+ defaultLocations: zod.z.record(zod.z.string(), zod.z.string())
234231
+ });
234232
+ var RecordingSetDevicePlacementInputSchema = zod.z.object({
234233
+ deviceId: zod.z.number().int(),
234234
+ /** `null` = Auto. Otherwise a `recordings:*` location id. */
234235
+ locationId: zod.z.string().nullable()
234236
+ });
233707
234237
  var LocateSegmentResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
233708
234238
  kind: zod.z.literal("segment"),
233709
234239
  startMs: zod.z.number(),
@@ -234022,6 +234552,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
234022
234552
  startStorageRebalance: require_sleep.method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
234023
234553
  kind: "mutation",
234024
234554
  auth: "admin"
234555
+ }),
234556
+ /**
234557
+ * The placement plan in force plus operator pins. Drives the Locations
234558
+ * admin page: Auto vs a named recordings location, per camera.
234559
+ */
234560
+ getPlacement: require_sleep.method(zod.z.object({}), RecordingPlacementViewSchema, {
234561
+ kind: "query",
234562
+ auth: "admin"
234563
+ }),
234564
+ /**
234565
+ * Pin a camera to a recordings location, or clear the pin (Auto). High and
234566
+ * mid follow the pin; low stays with the recordingsLow planner.
234567
+ */
234568
+ setDevicePlacement: require_sleep.method(RecordingSetDevicePlacementInputSchema, zod.z.object({ ok: zod.z.literal(true) }), {
234569
+ kind: "mutation",
234570
+ auth: "admin"
234025
234571
  })
234026
234572
  }
234027
234573
  };
@@ -241361,6 +241907,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
241361
241907
  addonId: null,
241362
241908
  access: "delete"
241363
241909
  },
241910
+ "deviceManager.renameLocation": {
241911
+ capName: "device-manager",
241912
+ capScope: "system",
241913
+ addonId: null,
241914
+ access: "create"
241915
+ },
241364
241916
  "deviceManager.runDeviceAction": {
241365
241917
  capName: "device-manager",
241366
241918
  capScope: "system",
@@ -243053,12 +243605,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243053
243605
  addonId: null,
243054
243606
  access: "view"
243055
243607
  },
243056
- "pipelineAnalytics.getGroup": {
243057
- capName: "pipeline-analytics",
243058
- capScope: "device",
243059
- addonId: null,
243060
- access: "view"
243061
- },
243062
243608
  "pipelineAnalytics.getKeyEvents": {
243063
243609
  capName: "pipeline-analytics",
243064
243610
  capScope: "device",
@@ -243161,12 +243707,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243161
243707
  addonId: null,
243162
243708
  access: "view"
243163
243709
  },
243164
- "pipelineAnalytics.listGroups": {
243165
- capName: "pipeline-analytics",
243166
- capScope: "device",
243167
- addonId: null,
243168
- access: "view"
243169
- },
243170
243710
  "pipelineAnalytics.listOpsLog": {
243171
243711
  capName: "pipeline-analytics",
243172
243712
  capScope: "device",
@@ -244181,6 +244721,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
244181
244721
  addonId: null,
244182
244722
  access: "view"
244183
244723
  },
244724
+ "recording.getPlacement": {
244725
+ capName: "recording",
244726
+ capScope: "system",
244727
+ addonId: null,
244728
+ access: "view"
244729
+ },
244184
244730
  "recording.getPlaybackManifest": {
244185
244731
  capName: "recording",
244186
244732
  capScope: "system",
@@ -244307,6 +244853,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
244307
244853
  addonId: null,
244308
244854
  access: "create"
244309
244855
  },
244856
+ "recording.setDevicePlacement": {
244857
+ capName: "recording",
244858
+ capScope: "system",
244859
+ addonId: null,
244860
+ access: "create"
244861
+ },
244310
244862
  "recording.startStorageMigrationMove": {
244311
244863
  capName: "recording",
244312
244864
  capScope: "system",
@@ -244751,12 +245303,36 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
244751
245303
  addonId: null,
244752
245304
  access: "create"
244753
245305
  },
245306
+ "storageMigration.cleanupCancel": {
245307
+ capName: "storage-migration",
245308
+ capScope: "system",
245309
+ addonId: null,
245310
+ access: "create"
245311
+ },
245312
+ "storageMigration.cleanupStart": {
245313
+ capName: "storage-migration",
245314
+ capScope: "system",
245315
+ addonId: null,
245316
+ access: "create"
245317
+ },
245318
+ "storageMigration.cleanupStatus": {
245319
+ capName: "storage-migration",
245320
+ capScope: "system",
245321
+ addonId: null,
245322
+ access: "view"
245323
+ },
244754
245324
  "storageMigration.drain": {
244755
245325
  capName: "storage-migration",
244756
245326
  capScope: "system",
244757
245327
  addonId: null,
244758
245328
  access: "create"
244759
245329
  },
245330
+ "storageMigration.history": {
245331
+ capName: "storage-migration",
245332
+ capScope: "system",
245333
+ addonId: null,
245334
+ access: "view"
245335
+ },
244760
245336
  "storageMigration.movers": {
244761
245337
  capName: "storage-migration",
244762
245338
  capScope: "system",
@@ -247011,11 +247587,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247011
247587
  form: "single",
247012
247588
  optional: true
247013
247589
  }],
247014
- "pipelineAnalytics.getGroup": [{
247015
- name: "deviceId",
247016
- form: "single",
247017
- optional: false
247018
- }],
247019
247590
  "pipelineAnalytics.getKeyEvents": [{
247020
247591
  name: "deviceId",
247021
247592
  form: "single",
@@ -247081,11 +247652,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247081
247652
  form: "single",
247082
247653
  optional: false
247083
247654
  }],
247084
- "pipelineAnalytics.listGroups": [{
247085
- name: "deviceIds",
247086
- form: "array",
247087
- optional: false
247088
- }],
247089
247655
  "pipelineAnalytics.listOpsLog": [{
247090
247656
  name: "deviceId",
247091
247657
  form: "single",
@@ -247501,6 +248067,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247501
248067
  form: "single",
247502
248068
  optional: false
247503
248069
  }],
248070
+ "recording.setDevicePlacement": [{
248071
+ name: "deviceId",
248072
+ form: "single",
248073
+ optional: false
248074
+ }],
247504
248075
  "recording.startStorageMigrationMove": [{
247505
248076
  name: "deviceId",
247506
248077
  form: "single",
@@ -247970,6 +248541,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247970
248541
  "recording.renderGif",
247971
248542
  "recording.rescanStorage",
247972
248543
  "recording.setDeviceConfig",
248544
+ "recording.setDevicePlacement",
247973
248545
  "recording.startStorageMigrationMove",
247974
248546
  "recordingExport.createExport",
247975
248547
  "recordingExport.listExports",
@@ -248560,6 +249132,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
248560
249132
  listLocations: (input) => dispatch("deviceManager", "listLocations", "query", input),
248561
249133
  addLocation: (input) => dispatch("deviceManager", "addLocation", "mutation", input),
248562
249134
  removeLocation: (input) => dispatch("deviceManager", "removeLocation", "mutation", input),
249135
+ renameLocation: (input) => dispatch("deviceManager", "renameLocation", "mutation", input),
248563
249136
  listPersistedByAddon: (input) => dispatch("deviceManager", "listPersistedByAddon", "query", input),
248564
249137
  listAll: (input) => dispatch("deviceManager", "listAll", "query", input),
248565
249138
  getChildren: (input) => dispatch("deviceManager", "getChildren", "query", input),
@@ -248853,7 +249426,9 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
248853
249426
  reconcileLedgerAgainstDisk: (input) => dispatch("recording", "reconcileLedgerAgainstDisk", "mutation", input),
248854
249427
  cancelRelocateJob: (input) => dispatch("recording", "cancelRelocateJob", "mutation", input),
248855
249428
  planStorageRebalance: (input) => dispatch("recording", "planStorageRebalance", "query", input),
248856
- startStorageRebalance: (input) => dispatch("recording", "startStorageRebalance", "mutation", input)
249429
+ startStorageRebalance: (input) => dispatch("recording", "startStorageRebalance", "mutation", input),
249430
+ getPlacement: (input) => dispatch("recording", "getPlacement", "query", input),
249431
+ setDevicePlacement: (input) => dispatch("recording", "setDevicePlacement", "mutation", input)
248857
249432
  },
248858
249433
  recordingExport: {
248859
249434
  createExport: (input) => dispatch("recordingExport", "createExport", "mutation", input),
@@ -248917,7 +249492,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
248917
249492
  cancel: (input) => dispatch("storageMigration", "cancel", "mutation", input),
248918
249493
  movers: (input) => dispatch("storageMigration", "movers", "query", input),
248919
249494
  residue: (input) => dispatch("storageMigration", "residue", "query", input),
248920
- drain: (input) => dispatch("storageMigration", "drain", "mutation", input)
249495
+ drain: (input) => dispatch("storageMigration", "drain", "mutation", input),
249496
+ cleanupStart: (input) => dispatch("storageMigration", "cleanupStart", "mutation", input),
249497
+ cleanupStatus: (input) => dispatch("storageMigration", "cleanupStatus", "query", input),
249498
+ cleanupCancel: (input) => dispatch("storageMigration", "cleanupCancel", "mutation", input),
249499
+ history: (input) => dispatch("storageMigration", "history", "query", input)
248921
249500
  },
248922
249501
  streamBroker: {
248923
249502
  fetchEventMedia: (input) => dispatch("streamBroker", "fetchEventMedia", "mutation", input),
@@ -251703,9 +252282,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
251703
252282
  exports.AlertSourceSchema = AlertSourceSchema;
251704
252283
  exports.AlertStatusSchema = AlertStatusSchema;
251705
252284
  exports.AmbientLightSensorStatusSchema = AmbientLightSensorStatusSchema;
251706
- exports.AnalyticsGroupDetailSchema = AnalyticsGroupDetailSchema;
251707
- exports.AnalyticsGroupMemberSchema = AnalyticsGroupMemberSchema;
251708
- exports.AnalyticsGroupRecordSchema = AnalyticsGroupRecordSchema;
251709
252285
  exports.ApiKeyRecordSchema = ApiKeyRecordSchema;
251710
252286
  exports.ApiKeySummarySchema = ApiKeySummarySchema;
251711
252287
  exports.ArchiveEntrySchema = ArchiveEntrySchema;
@@ -252070,8 +252646,6 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252070
252646
  exports.LedgerWalkSkipReasonSchema = LedgerWalkSkipReasonSchema;
252071
252647
  exports.LinkedDeviceSchema = LinkedDeviceSchema;
252072
252648
  exports.LinkedDevicesModeSchema = LinkedDevicesModeSchema;
252073
- exports.ListGroupsPageSchema = ListGroupsPageSchema;
252074
- exports.ListGroupsQueryInput = ListGroupsQueryInput;
252075
252649
  exports.LlmDefaultSchema = LlmDefaultSchema;
252076
252650
  exports.LlmDefaultSelectorSchema = LlmDefaultSelectorSchema;
252077
252651
  exports.LlmDownloadProgressSchema = LlmDownloadProgressSchema;
@@ -252534,6 +253108,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252534
253108
  exports.StorageBeginDownloadResultSchema = BeginDownloadResultSchema;
252535
253109
  exports.StorageBeginUploadInputSchema = BeginUploadInputSchema;
252536
253110
  exports.StorageBeginUploadResultSchema = BeginUploadResultSchema;
253111
+ exports.StorageCleanupInputSchema = StorageCleanupInputSchema;
253112
+ exports.StorageCleanupJobSchema = StorageCleanupJobSchema;
253113
+ exports.StorageCleanupPhaseSchema = StorageCleanupPhaseSchema;
253114
+ exports.StorageCleanupStatusInputSchema = StorageCleanupStatusInputSchema;
252537
253115
  exports.StorageEndDownloadInputSchema = EndDownloadInputSchema;
252538
253116
  exports.StorageFinalizeUploadInputSchema = FinalizeUploadInputSchema;
252539
253117
  exports.StorageLocationDeclarationSchema = StorageLocationDeclarationSchema;
@@ -252559,6 +253137,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252559
253137
  exports.StorageMigrationPhaseSchema = StorageMigrationPhaseSchema;
252560
253138
  exports.StorageMigrationPlanSchema = StorageMigrationPlanSchema;
252561
253139
  exports.StorageMigrationResidueSchema = StorageMigrationResidueSchema;
253140
+ exports.StorageMigrationSourcesSchema = StorageMigrationSourcesSchema;
252562
253141
  exports.StorageProviderInfoSchema = ProviderInfoSchema;
252563
253142
  exports.StorageReadChunkInputSchema = ReadChunkInputSchema;
252564
253143
  exports.StorageTestLocationResultSchema = TestLocationResultSchema;