camstack 1.2.69 → 1.2.71

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-B29Skpzo.js
23637
- var require_dist_B29Skpzo = __commonJS({
23638
- "../system/dist/dist-B29Skpzo.js"(exports) {
23636
+ // ../system/dist/dist-CFTKQGym.js
23637
+ var require_dist_CFTKQGym = __commonJS({
23638
+ "../system/dist/dist-CFTKQGym.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_B29Skpzo = __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_B29Skpzo = __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_B29Skpzo = __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_B29Skpzo = __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_B29Skpzo = __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_B29Skpzo = __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_B29Skpzo = __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-]+$/),
@@ -37286,6 +37345,39 @@ var require_dist_B29Skpzo = __commonJS({
37286
37345
  /** Present when the pass ended by throwing. */
37287
37346
  error: zod.z.string().nullable()
37288
37347
  });
37348
+ var MediaReclaimStartResultSchema = zod.z.object({
37349
+ started: zod.z.boolean(),
37350
+ /** True when a pass was already running; the new request is ignored. */
37351
+ alreadyRunning: zod.z.boolean()
37352
+ });
37353
+ var MediaReclaimInputSchema = zod.z.object({
37354
+ mode: zod.z.enum(["report", "reclaim"]).default("report"),
37355
+ scopes: zod.z.array(zod.z.enum(["motion-still", "track-filmstrip"])).min(1).optional(),
37356
+ deviceIds: zod.z.array(zod.z.number().int()).min(1).optional(),
37357
+ restart: zod.z.boolean().optional(),
37358
+ pageSize: zod.z.number().int().min(50).max(5e3).optional(),
37359
+ maxRowsPerDevice: zod.z.number().int().min(1).max(5e5).optional(),
37360
+ maxReclaimPerDevice: zod.z.number().int().min(1).max(5e5).optional(),
37361
+ maxBytesPerRun: zod.z.number().int().min(1).optional(),
37362
+ budgetMinutes: zod.z.number().int().min(1).max(720).optional(),
37363
+ throttleBytesPerSec: zod.z.number().int().min(64 * 1024).optional(),
37364
+ graceMinutes: zod.z.number().int().min(1).max(10080).optional()
37365
+ });
37366
+ var MediaReclaimStatusSchema = zod.z.object({
37367
+ running: zod.z.boolean(),
37368
+ mode: zod.z.enum(["report", "reclaim"]).nullable(),
37369
+ totalExamined: zod.z.number(),
37370
+ totalEligible: zod.z.number(),
37371
+ totalReclaimed: zod.z.number(),
37372
+ totalBytesReclaimed: zod.z.number(),
37373
+ totalRefused: zod.z.number(),
37374
+ /** Device+scope windows finished in this pass. */
37375
+ devicesDone: zod.z.number(),
37376
+ complete: zod.z.boolean().nullable(),
37377
+ startedAtMs: zod.z.number().nullable(),
37378
+ finishedAtMs: zod.z.number().nullable(),
37379
+ error: zod.z.string().nullable()
37380
+ });
37289
37381
  var ReplayFrameInputSchema = zod.z.object({
37290
37382
  timestamp: zod.z.number(),
37291
37383
  frame: PipelineRunResultBridge
@@ -37709,6 +37801,20 @@ var require_dist_B29Skpzo = __commonJS({
37709
37801
  auth: "admin"
37710
37802
  }),
37711
37803
  /**
37804
+ * Reclaim the standing debug-media backlog (motion-visit stills + track
37805
+ * filmstrips). DETACHED: returns `{ started }` immediately. The pass is
37806
+ * tens of minutes; awaiting it is a timeout, not a verdict. Dry-run unless
37807
+ * `mode: 'reclaim'`. Poll `getMediaReclaimStatus`.
37808
+ */
37809
+ reclaimDebugMedia: method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
37810
+ kind: "mutation",
37811
+ auth: "admin"
37812
+ }),
37813
+ getMediaReclaimStatus: method(zod.z.object({}), MediaReclaimStatusSchema, {
37814
+ kind: "query",
37815
+ auth: "admin"
37816
+ }),
37817
+ /**
37712
37818
  * The CHEAP QUESTION, asked before any media moves: how big is the dataset
37713
37819
  * the marked (`markForTrain`) tracks would produce?
37714
37820
  *
@@ -40296,7 +40402,23 @@ var require_dist_B29Skpzo = __commonJS({
40296
40402
  drain: method(StorageMigrationDrainInputSchema, zod.z.object({ jobId: zod.z.string() }), {
40297
40403
  kind: "mutation",
40298
40404
  auth: "admin"
40299
- })
40405
+ }),
40406
+ /**
40407
+ * One operator cleanup of leftover analytics (DB + blobs) and ghost
40408
+ * ledger rows on frozen footage locations. Optional debug-media sweep.
40409
+ * Returns immediately; poll {@link cleanupStatus}.
40410
+ */
40411
+ cleanupStart: method(StorageCleanupInputSchema, zod.z.object({ jobId: zod.z.string() }), {
40412
+ kind: "mutation",
40413
+ auth: "admin"
40414
+ }),
40415
+ cleanupStatus: method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }),
40416
+ cleanupCancel: method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
40417
+ kind: "mutation",
40418
+ auth: "admin"
40419
+ }),
40420
+ /** Recent finished migration jobs, newest first. The live job is `status`. */
40421
+ history: method(zod.z.object({}), zod.z.array(StorageMigrationJobSchema).readonly(), { auth: "admin" })
40300
40422
  }
40301
40423
  };
40302
40424
  var ProviderInfoSchema = zod.z.discriminatedUnion("shouldSaveDiskSpace", [zod.z.object({
@@ -47088,6 +47210,26 @@ var require_dist_B29Skpzo = __commonJS({
47088
47210
  /** Ignore piles smaller than this (default 1 GB). */
47089
47211
  minMoveGb: zod.z.number().min(0).optional()
47090
47212
  });
47213
+ var RecordingDevicePlacementSchema = zod.z.object({
47214
+ deviceId: zod.z.number().int(),
47215
+ profile: zod.z.string(),
47216
+ locationId: zod.z.string()
47217
+ });
47218
+ var RecordingDevicePinSchema = zod.z.object({
47219
+ deviceId: zod.z.number().int(),
47220
+ /** Recordings-class location this camera is pinned to. */
47221
+ locationId: zod.z.string()
47222
+ });
47223
+ var RecordingPlacementViewSchema = zod.z.object({
47224
+ assignments: zod.z.array(RecordingDevicePlacementSchema),
47225
+ pins: zod.z.array(RecordingDevicePinSchema),
47226
+ defaultLocations: zod.z.record(zod.z.string(), zod.z.string())
47227
+ });
47228
+ var RecordingSetDevicePlacementInputSchema = zod.z.object({
47229
+ deviceId: zod.z.number().int(),
47230
+ /** `null` = Auto. Otherwise a `recordings:*` location id. */
47231
+ locationId: zod.z.string().nullable()
47232
+ });
47091
47233
  var LocateSegmentResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
47092
47234
  kind: zod.z.literal("segment"),
47093
47235
  startMs: zod.z.number(),
@@ -47406,6 +47548,22 @@ var require_dist_B29Skpzo = __commonJS({
47406
47548
  startStorageRebalance: method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
47407
47549
  kind: "mutation",
47408
47550
  auth: "admin"
47551
+ }),
47552
+ /**
47553
+ * The placement plan in force plus operator pins. Drives the Locations
47554
+ * admin page: Auto vs a named recordings location, per camera.
47555
+ */
47556
+ getPlacement: method(zod.z.object({}), RecordingPlacementViewSchema, {
47557
+ kind: "query",
47558
+ auth: "admin"
47559
+ }),
47560
+ /**
47561
+ * Pin a camera to a recordings location, or clear the pin (Auto). High and
47562
+ * mid follow the pin; low stays with the recordingsLow planner.
47563
+ */
47564
+ setDevicePlacement: method(RecordingSetDevicePlacementInputSchema, zod.z.object({ ok: zod.z.literal(true) }), {
47565
+ kind: "mutation",
47566
+ auth: "admin"
47409
47567
  })
47410
47568
  }
47411
47569
  };
@@ -53519,6 +53677,12 @@ var require_dist_B29Skpzo = __commonJS({
53519
53677
  addonId: null,
53520
53678
  access: "view"
53521
53679
  },
53680
+ "pipelineAnalytics.getMediaReclaimStatus": {
53681
+ capName: "pipeline-analytics",
53682
+ capScope: "device",
53683
+ addonId: null,
53684
+ access: "view"
53685
+ },
53522
53686
  "pipelineAnalytics.getMotionEvents": {
53523
53687
  capName: "pipeline-analytics",
53524
53688
  capScope: "device",
@@ -53693,6 +53857,12 @@ var require_dist_B29Skpzo = __commonJS({
53693
53857
  addonId: null,
53694
53858
  access: "create"
53695
53859
  },
53860
+ "pipelineAnalytics.reclaimDebugMedia": {
53861
+ capName: "pipeline-analytics",
53862
+ capScope: "device",
53863
+ addonId: null,
53864
+ access: "create"
53865
+ },
53696
53866
  "pipelineAnalytics.reconcileFromDisk": {
53697
53867
  capName: "pipeline-analytics",
53698
53868
  capScope: "device",
@@ -54617,6 +54787,12 @@ var require_dist_B29Skpzo = __commonJS({
54617
54787
  addonId: null,
54618
54788
  access: "view"
54619
54789
  },
54790
+ "recording.getPlacement": {
54791
+ capName: "recording",
54792
+ capScope: "system",
54793
+ addonId: null,
54794
+ access: "view"
54795
+ },
54620
54796
  "recording.getPlaybackManifest": {
54621
54797
  capName: "recording",
54622
54798
  capScope: "system",
@@ -54743,6 +54919,12 @@ var require_dist_B29Skpzo = __commonJS({
54743
54919
  addonId: null,
54744
54920
  access: "create"
54745
54921
  },
54922
+ "recording.setDevicePlacement": {
54923
+ capName: "recording",
54924
+ capScope: "system",
54925
+ addonId: null,
54926
+ access: "create"
54927
+ },
54746
54928
  "recording.startStorageMigrationMove": {
54747
54929
  capName: "recording",
54748
54930
  capScope: "system",
@@ -55187,12 +55369,36 @@ var require_dist_B29Skpzo = __commonJS({
55187
55369
  addonId: null,
55188
55370
  access: "create"
55189
55371
  },
55372
+ "storageMigration.cleanupCancel": {
55373
+ capName: "storage-migration",
55374
+ capScope: "system",
55375
+ addonId: null,
55376
+ access: "create"
55377
+ },
55378
+ "storageMigration.cleanupStart": {
55379
+ capName: "storage-migration",
55380
+ capScope: "system",
55381
+ addonId: null,
55382
+ access: "create"
55383
+ },
55384
+ "storageMigration.cleanupStatus": {
55385
+ capName: "storage-migration",
55386
+ capScope: "system",
55387
+ addonId: null,
55388
+ access: "view"
55389
+ },
55190
55390
  "storageMigration.drain": {
55191
55391
  capName: "storage-migration",
55192
55392
  capScope: "system",
55193
55393
  addonId: null,
55194
55394
  access: "create"
55195
55395
  },
55396
+ "storageMigration.history": {
55397
+ capName: "storage-migration",
55398
+ capScope: "system",
55399
+ addonId: null,
55400
+ access: "view"
55401
+ },
55196
55402
  "storageMigration.movers": {
55197
55403
  capName: "storage-migration",
55198
55404
  capScope: "system",
@@ -57309,6 +57515,11 @@ var require_dist_B29Skpzo = __commonJS({
57309
57515
  form: "single",
57310
57516
  optional: true
57311
57517
  }],
57518
+ "pipelineAnalytics.reclaimDebugMedia": [{
57519
+ name: "deviceIds",
57520
+ form: "array",
57521
+ optional: true
57522
+ }],
57312
57523
  "pipelineAnalytics.reconcileFromDisk": [{
57313
57524
  name: "deviceId",
57314
57525
  form: "single",
@@ -57674,6 +57885,11 @@ var require_dist_B29Skpzo = __commonJS({
57674
57885
  form: "single",
57675
57886
  optional: false
57676
57887
  }],
57888
+ "recording.setDevicePlacement": [{
57889
+ name: "deviceId",
57890
+ form: "single",
57891
+ optional: false
57892
+ }],
57677
57893
  "recording.startStorageMigrationMove": [{
57678
57894
  name: "deviceId",
57679
57895
  form: "single",
@@ -58914,6 +59130,12 @@ var require_dist_B29Skpzo = __commonJS({
58914
59130
  return ScopedTokenSchema;
58915
59131
  }
58916
59132
  });
59133
+ Object.defineProperty(exports, "StorageCleanupJobSchema", {
59134
+ enumerable: true,
59135
+ get: function() {
59136
+ return StorageCleanupJobSchema;
59137
+ }
59138
+ });
58917
59139
  Object.defineProperty(exports, "StorageLocationTypeSchema", {
58918
59140
  enumerable: true,
58919
59141
  get: function() {
@@ -59418,7 +59640,7 @@ var require_alerts_addon = __commonJS({
59418
59640
  [Symbol.toStringTag]: { value: "Module" }
59419
59641
  });
59420
59642
  require_chunk_Cek0wNdY();
59421
- var require_dist10 = require_dist_B29Skpzo();
59643
+ var require_dist10 = require_dist_CFTKQGym();
59422
59644
  function selectExpired(alerts, cutoffMs) {
59423
59645
  return alerts.filter((a) => a.createdAt < cutoffMs).sort((a, b) => a.createdAt - b.createdAt).map((a) => a.id);
59424
59646
  }
@@ -60237,7 +60459,7 @@ var require_console_logging = __commonJS({
60237
60459
  [Symbol.toStringTag]: { value: "Module" }
60238
60460
  });
60239
60461
  require_chunk_Cek0wNdY();
60240
- var require_dist10 = require_dist_B29Skpzo();
60462
+ var require_dist10 = require_dist_CFTKQGym();
60241
60463
  var require_formatter = require_formatter_DqAKDlvN();
60242
60464
  var LEVEL_RANK = {
60243
60465
  debug: 0,
@@ -60331,7 +60553,7 @@ var require_core_blocks_addon = __commonJS({
60331
60553
  "use strict";
60332
60554
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
60333
60555
  var require_chunk = require_chunk_Cek0wNdY();
60334
- var require_dist10 = require_dist_B29Skpzo();
60556
+ var require_dist10 = require_dist_CFTKQGym();
60335
60557
  var node_crypto = __require("crypto");
60336
60558
  var node_fs_promises = __require("fs/promises");
60337
60559
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -61228,11 +61450,11 @@ var require_core_blocks = __commonJS({
61228
61450
  }
61229
61451
  });
61230
61452
 
61231
- // ../system/dist/retired-settings-keys-CRL0qOnU.js
61232
- var require_retired_settings_keys_CRL0qOnU = __commonJS({
61233
- "../system/dist/retired-settings-keys-CRL0qOnU.js"(exports) {
61453
+ // ../system/dist/retired-settings-keys-dehyu2N3.js
61454
+ var require_retired_settings_keys_dehyu2N3 = __commonJS({
61455
+ "../system/dist/retired-settings-keys-dehyu2N3.js"(exports) {
61234
61456
  "use strict";
61235
- var require_dist10 = require_dist_B29Skpzo();
61457
+ var require_dist10 = require_dist_CFTKQGym();
61236
61458
  function settingsStoreIsAuthoritativeHere(env) {
61237
61459
  const raw = (env["CAMSTACK_ROLE"] ?? "").trim().toLowerCase();
61238
61460
  return raw === "" || raw === "hub";
@@ -63446,8 +63668,8 @@ var require_device_manager_addon = __commonJS({
63446
63668
  [Symbol.toStringTag]: { value: "Module" }
63447
63669
  });
63448
63670
  require_chunk_Cek0wNdY();
63449
- var require_dist10 = require_dist_B29Skpzo();
63450
- var require_retired_settings_keys = require_retired_settings_keys_CRL0qOnU();
63671
+ var require_dist10 = require_dist_CFTKQGym();
63672
+ var require_retired_settings_keys = require_retired_settings_keys_dehyu2N3();
63451
63673
  var node_crypto = __require("crypto");
63452
63674
  var _camstack_types_node = require_node();
63453
63675
  var JOB_HISTORY = 20;
@@ -64425,6 +64647,9 @@ var require_device_manager_addon = __commonJS({
64425
64647
  for (const f of features) if (DEVICE_FEATURE_VALUES.has(f)) out.push(f);
64426
64648
  return out;
64427
64649
  }
64650
+ function persistedIsCamera(type) {
64651
+ return type === require_dist10.DeviceType.Camera;
64652
+ }
64428
64653
  function buildSourceInfoFromConfig(persistedConfig, stableId, addonId) {
64429
64654
  const id = typeof persistedConfig["entityId"] === "string" ? persistedConfig["entityId"] : void 0;
64430
64655
  const system = typeof persistedConfig["system"] === "string" ? persistedConfig["system"] : void 0;
@@ -64571,7 +64796,7 @@ var require_device_manager_addon = __commonJS({
64571
64796
  online: pctx.host.resolveDeviceOnline(m.id, pctx.registry !== null),
64572
64797
  probed: pctx.host.resolveDeviceProbed(m.id),
64573
64798
  features: persistedFeatures(m.features),
64574
- isCamera: persistedType === require_dist10.DeviceType.Camera,
64799
+ isCamera: persistedIsCamera(persistedType),
64575
64800
  config: persistedConfig ?? {},
64576
64801
  metadata,
64577
64802
  ...m.integrationId !== void 0 ? { integrationId: m.integrationId } : {},
@@ -64616,7 +64841,7 @@ var require_device_manager_addon = __commonJS({
64616
64841
  online: pctx.host.resolveDeviceOnline(deviceId, true),
64617
64842
  probed: pctx.host.resolveDeviceProbed(deviceId),
64618
64843
  features: persistedFeatures(m.features),
64619
- isCamera: false,
64844
+ isCamera: persistedIsCamera(m.type),
64620
64845
  config: persistedConfig ?? {},
64621
64846
  metadata,
64622
64847
  ...m.integrationId !== void 0 ? { integrationId: m.integrationId } : {},
@@ -64664,7 +64889,7 @@ var require_device_manager_addon = __commonJS({
64664
64889
  online: pctx.host.resolveDeviceOnline(m.id, pctx.registry !== null),
64665
64890
  probed: pctx.host.resolveDeviceProbed(m.id),
64666
64891
  features: persistedFeatures(m.features),
64667
- isCamera: false,
64892
+ isCamera: persistedIsCamera(m.type),
64668
64893
  config: persistedConfig,
64669
64894
  metadata,
64670
64895
  ...m.integrationId !== void 0 ? { integrationId: m.integrationId } : {},
@@ -68260,7 +68485,7 @@ var require_hub_forwarder = __commonJS({
68260
68485
  [Symbol.toStringTag]: { value: "Module" }
68261
68486
  });
68262
68487
  require_chunk_Cek0wNdY();
68263
- var require_dist10 = require_dist_B29Skpzo();
68488
+ var require_dist10 = require_dist_CFTKQGym();
68264
68489
  var require_formatter = require_formatter_DqAKDlvN();
68265
68490
  var DEFAULT_OUTBOUND_BUFFER_SIZE = 500;
68266
68491
  var HubForwarderDestination = class {
@@ -68397,7 +68622,7 @@ var require_liveness_monitor_addon = __commonJS({
68397
68622
  "use strict";
68398
68623
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
68399
68624
  require_chunk_Cek0wNdY();
68400
- var require_dist10 = require_dist_B29Skpzo();
68625
+ var require_dist10 = require_dist_CFTKQGym();
68401
68626
  var NO_DEVICES = "liveness:no-devices";
68402
68627
  var ALL_OFFLINE = "liveness:all-devices-offline";
68403
68628
  var NO_FOOTAGE_PREFIX = "liveness:no-footage:";
@@ -68587,7 +68812,7 @@ var require_local_auth_addon = __commonJS({
68587
68812
  [Symbol.toStringTag]: { value: "Module" }
68588
68813
  });
68589
68814
  var require_chunk = require_chunk_Cek0wNdY();
68590
- var require_dist10 = require_dist_B29Skpzo();
68815
+ var require_dist10 = require_dist_CFTKQGym();
68591
68816
  var node_crypto = __require("crypto");
68592
68817
  node_crypto = require_chunk.__toESM(node_crypto);
68593
68818
  var crypto$1 = __require("crypto");
@@ -76400,7 +76625,7 @@ var require_loki_logging = __commonJS({
76400
76625
  [Symbol.toStringTag]: { value: "Module" }
76401
76626
  });
76402
76627
  require_chunk_Cek0wNdY();
76403
- var require_dist10 = require_dist_B29Skpzo();
76628
+ var require_dist10 = require_dist_CFTKQGym();
76404
76629
  function sanitizeLabelName(raw) {
76405
76630
  const replaced = raw.replaceAll(/[^a-zA-Z0-9_]/g, "_");
76406
76631
  return /^[a-zA-Z_]/.test(replaced) ? replaced : `_${replaced}`;
@@ -76965,7 +77190,7 @@ var require_native_metrics_addon = __commonJS({
76965
77190
  [Symbol.toStringTag]: { value: "Module" }
76966
77191
  });
76967
77192
  var require_chunk = require_chunk_Cek0wNdY();
76968
- var require_dist10 = require_dist_B29Skpzo();
77193
+ var require_dist10 = require_dist_CFTKQGym();
76969
77194
  var node_fs_promises = __require("fs/promises");
76970
77195
  var node_child_process = __require("child_process");
76971
77196
  var node_util = __require("util");
@@ -79587,7 +79812,7 @@ var require_filesystem_storage_addon = __commonJS({
79587
79812
  [Symbol.toStringTag]: { value: "Module" }
79588
79813
  });
79589
79814
  var require_chunk = require_chunk_Cek0wNdY();
79590
- var require_dist10 = require_dist_B29Skpzo();
79815
+ var require_dist10 = require_dist_CFTKQGym();
79591
79816
  var node_crypto = __require("crypto");
79592
79817
  var node_fs_promises = __require("fs/promises");
79593
79818
  var node_path = __require("path");
@@ -80703,8 +80928,8 @@ var require_sqlite_settings_addon = __commonJS({
80703
80928
  [Symbol.toStringTag]: { value: "Module" }
80704
80929
  });
80705
80930
  var require_chunk = require_chunk_Cek0wNdY();
80706
- var require_dist10 = require_dist_B29Skpzo();
80707
- var require_retired_settings_keys = require_retired_settings_keys_CRL0qOnU();
80931
+ var require_dist10 = require_dist_CFTKQGym();
80932
+ var require_retired_settings_keys = require_retired_settings_keys_dehyu2N3();
80708
80933
  var node_crypto = __require("crypto");
80709
80934
  var node_fs = __require("fs");
80710
80935
  var node_module = __require("module");
@@ -80740,6 +80965,14 @@ var require_sqlite_settings_addon = __commonJS({
80740
80965
  var SQLITE_PROFILE_REPORT_INTERVAL_MS = 6e4;
80741
80966
  var SQLITE_PROFILE_REPORT_FLOOR_MS = 250;
80742
80967
  var SQLITE_PROFILE_TOP_N = 5;
80968
+ var SQLITE_ENGINE_STORM_WALL_SHARE = 0.5;
80969
+ function sqliteEngineStorm(stats, windowMs) {
80970
+ if (windowMs <= 0) return null;
80971
+ const top = stats[0];
80972
+ if (top === void 0) return null;
80973
+ if (top.totalMs / windowMs < 0.5) return null;
80974
+ return top;
80975
+ }
80743
80976
  function rowsOf(value) {
80744
80977
  if (Array.isArray(value)) return value.length;
80745
80978
  if (value === void 0 || value === null) return 0;
@@ -80874,21 +81107,39 @@ var require_sqlite_settings_addon = __commonJS({
80874
81107
  const stats = this.drain();
80875
81108
  const busyMs = stats.reduce((acc, s) => acc + s.totalMs, 0);
80876
81109
  if (busyMs < this.reportFloorMs) return;
81110
+ const busyPct = Math.round(busyMs / windowMs * 100);
81111
+ const calls = stats.reduce((acc, s) => acc + s.calls, 0);
81112
+ const top = stats.slice(0, this.topN).map((s) => ({
81113
+ op: s.op,
81114
+ collection: s.collection,
81115
+ ...s.owner === void 0 ? {} : { owner: s.owner },
81116
+ ...s.shape === void 0 ? {} : { shape: s.shape },
81117
+ calls: s.calls,
81118
+ totalMs: Math.round(s.totalMs),
81119
+ maxMs: Math.round(s.maxMs),
81120
+ rows: s.rows
81121
+ }));
80877
81122
  this.logger.info("sqlite engine busy", { meta: {
80878
81123
  busyMs: Math.round(busyMs),
80879
81124
  windowMs: Math.round(windowMs),
80880
- busyPct: Math.round(busyMs / windowMs * 100),
80881
- calls: stats.reduce((acc, s) => acc + s.calls, 0),
80882
- top: stats.slice(0, this.topN).map((s) => ({
80883
- op: s.op,
80884
- collection: s.collection,
80885
- ...s.owner === void 0 ? {} : { owner: s.owner },
80886
- ...s.shape === void 0 ? {} : { shape: s.shape },
80887
- calls: s.calls,
80888
- totalMs: Math.round(s.totalMs),
80889
- maxMs: Math.round(s.maxMs),
80890
- rows: s.rows
80891
- }))
81125
+ busyPct,
81126
+ calls,
81127
+ top
81128
+ } });
81129
+ const storm = sqliteEngineStorm(stats, windowMs);
81130
+ if (storm !== null) this.logger.warn("sqlite engine storm", { meta: {
81131
+ busyMs: Math.round(busyMs),
81132
+ windowMs: Math.round(windowMs),
81133
+ busyPct,
81134
+ wallPct: Math.round(storm.totalMs / windowMs * 100),
81135
+ op: storm.op,
81136
+ collection: storm.collection,
81137
+ ...storm.owner === void 0 ? {} : { owner: storm.owner },
81138
+ ...storm.shape === void 0 ? {} : { shape: storm.shape },
81139
+ calls: storm.calls,
81140
+ totalMs: Math.round(storm.totalMs),
81141
+ maxMs: Math.round(storm.maxMs),
81142
+ rows: storm.rows
80892
81143
  } });
80893
81144
  }
80894
81145
  start() {
@@ -82968,6 +83219,7 @@ var require_sqlite_settings_addon = __commonJS({
82968
83219
  return this.vectorIndex;
82969
83220
  }
82970
83221
  };
83222
+ exports.SQLITE_ENGINE_STORM_WALL_SHARE = SQLITE_ENGINE_STORM_WALL_SHARE;
82971
83223
  exports.SQLITE_JOURNAL_SIZE_LIMIT_BYTES = SQLITE_JOURNAL_SIZE_LIMIT_BYTES;
82972
83224
  exports.SQLITE_MMAP_CEILING_BYTES = SQLITE_MMAP_CEILING_BYTES;
82973
83225
  exports.SQLITE_MMAP_FLOOR_BYTES = SQLITE_MMAP_FLOOR_BYTES;
@@ -82987,6 +83239,7 @@ var require_sqlite_settings_addon = __commonJS({
82987
83239
  exports.WAL_MAINTENANCE_INTERVAL_MS = WAL_MAINTENANCE_INTERVAL_MS;
82988
83240
  exports.WalMaintenance = WalMaintenance;
82989
83241
  exports.prefixRange = prefixRange;
83242
+ exports.sqliteEngineStorm = sqliteEngineStorm;
82990
83243
  exports.sqliteMmapSizeFor = sqliteMmapSizeFor;
82991
83244
  exports.sqlitePageCacheKibFor = sqlitePageCacheKibFor;
82992
83245
  }
@@ -83051,6 +83304,7 @@ var require_sqlite_storage = __commonJS({
83051
83304
  exports.DeviceStore$1 = DeviceStore;
83052
83305
  exports.FilesystemStorageAddon = require_builtins_sqlite_storage_filesystem_storage_addon.FilesystemStorageAddon;
83053
83306
  exports.FilesystemStorageProvider = require_builtins_sqlite_storage_filesystem_storage_addon.FilesystemStorageProvider;
83307
+ exports.SQLITE_ENGINE_STORM_WALL_SHARE = require_builtins_sqlite_storage_sqlite_settings_addon.SQLITE_ENGINE_STORM_WALL_SHARE;
83054
83308
  exports.SQLITE_JOURNAL_SIZE_LIMIT_BYTES = require_builtins_sqlite_storage_sqlite_settings_addon.SQLITE_JOURNAL_SIZE_LIMIT_BYTES;
83055
83309
  exports.SQLITE_MMAP_CEILING_BYTES = require_builtins_sqlite_storage_sqlite_settings_addon.SQLITE_MMAP_CEILING_BYTES;
83056
83310
  exports.SQLITE_MMAP_FLOOR_BYTES = require_builtins_sqlite_storage_sqlite_settings_addon.SQLITE_MMAP_FLOOR_BYTES;
@@ -83069,6 +83323,7 @@ var require_sqlite_storage = __commonJS({
83069
83323
  exports.WAL_MAINTENANCE_INTERVAL_MS = require_builtins_sqlite_storage_sqlite_settings_addon.WAL_MAINTENANCE_INTERVAL_MS;
83070
83324
  exports.WalMaintenance = require_builtins_sqlite_storage_sqlite_settings_addon.WalMaintenance;
83071
83325
  exports.default = require_builtins_sqlite_storage_filesystem_storage_addon.FilesystemStorageAddon;
83326
+ exports.sqliteEngineStorm = require_builtins_sqlite_storage_sqlite_settings_addon.sqliteEngineStorm;
83072
83327
  exports.sqliteMmapSizeFor = require_builtins_sqlite_storage_sqlite_settings_addon.sqliteMmapSizeFor;
83073
83328
  exports.sqlitePageCacheKibFor = require_builtins_sqlite_storage_sqlite_settings_addon.sqlitePageCacheKibFor;
83074
83329
  }
@@ -83083,7 +83338,8 @@ var require_storage_orchestrator_addon = __commonJS({
83083
83338
  [Symbol.toStringTag]: { value: "Module" }
83084
83339
  });
83085
83340
  var require_chunk = require_chunk_Cek0wNdY();
83086
- var require_dist10 = require_dist_B29Skpzo();
83341
+ var require_dist10 = require_dist_CFTKQGym();
83342
+ var zod = require_zod();
83087
83343
  var node_crypto = __require("crypto");
83088
83344
  var node_fs_promises = __require("fs/promises");
83089
83345
  node_fs_promises = require_chunk.__toESM(node_fs_promises);
@@ -83267,17 +83523,13 @@ var require_storage_orchestrator_addon = __commonJS({
83267
83523
  "recordings",
83268
83524
  "recordingsLow",
83269
83525
  "eventMedia",
83270
- "backups",
83271
- "galleryMedia"
83526
+ "backups"
83272
83527
  ];
83273
83528
  var MOVER_CLASSES = [
83274
83529
  "recordings",
83275
83530
  "recordingsLow",
83276
- "eventMedia",
83277
- "galleryMedia"
83531
+ "eventMedia"
83278
83532
  ];
83279
- var BLOCKING_ONLY_CLASSES = ["galleryMedia"];
83280
- var GALLERY_FORBIDDEN_NEIGHBOUR_TYPES = ["recordings", "recordingsLow"];
83281
83533
  var MOVE_LANES = ["media", "footage"];
83282
83534
  var PARTICIPANTS = [
83283
83535
  "pipeline",
@@ -83302,35 +83554,34 @@ var require_storage_orchestrator_addon = __commonJS({
83302
83554
  this.deps = deps;
83303
83555
  }
83304
83556
  async plan(input) {
83557
+ refuseRetiredGalleryClass(input.destinations);
83305
83558
  const mode = input.mode ?? "blocking";
83306
83559
  const moves = [];
83307
83560
  const findings = [];
83308
83561
  for (const storageClass of STORAGE_CLASSES) {
83309
83562
  const targetId = input.destinations[storageClass];
83310
83563
  if (targetId === void 0) continue;
83311
- const source = this.deps.locations.getDefaultLocation(storageClass);
83312
- if (!source) throw new Error(`No default storage location for "${storageClass}"`);
83564
+ const namedSourceId = input.sources?.[storageClass];
83565
+ const defaultLoc = this.deps.locations.getDefaultLocation(storageClass);
83566
+ const source = namedSourceId ? this.deps.locations.getLocationById(namedSourceId) : defaultLoc;
83567
+ if (!source) throw new Error(namedSourceId ? `Storage location "${namedSourceId}" not found` : `No default storage location for "${storageClass}"`);
83568
+ if (source.type !== storageClass) throw new Error(`Storage location "${source.id}" is type "${source.type}", expected "${storageClass}"`);
83313
83569
  const target = this.deps.locations.getLocationById(targetId);
83314
83570
  if (!target) throw new Error(`Storage location "${targetId}" not found`);
83315
83571
  if (target.type !== storageClass) throw new Error(`Storage location "${targetId}" is type "${target.type}", expected "${storageClass}"`);
83316
- if (source.id === target.id) throw new Error(`Storage location "${targetId}" is already the "${storageClass}" default`);
83572
+ 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`);
83573
+ const freezeSource = defaultLoc !== void 0 && source.id !== defaultLoc.id;
83317
83574
  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.`);
83318
- if (mode === "nonBlocking" && BLOCKING_ONLY_CLASSES.includes(storageClass)) throw new Error(`"${storageClass}" is a single-cardinality storage class: it can never exist at two locations at once, so it cannot be repointed first and drained behind. Use mode "blocking".`);
83319
- await this.refuseGalleryOntoRecordingsVolume(storageClass, targetId);
83320
83575
  moves.push({
83321
83576
  storageClass,
83322
83577
  fromLocationId: source.id,
83323
83578
  toLocationId: target.id,
83579
+ freezeSource,
83324
83580
  moverJobId: null,
83325
83581
  state: null,
83326
83582
  error: null,
83327
83583
  progress: null
83328
83584
  });
83329
- if (BLOCKING_ONLY_CLASSES.includes(storageClass)) findings.push({
83330
- code: "blockingOnly",
83331
- storageClass,
83332
- message: `"${storageClass}" is single-cardinality: this move is stop-the-world for its whole duration, in every mode. It is a few KB per enrolled sample, which is why that is acceptable.`
83333
- });
83334
83585
  findings.push(...await this.deviceFindings(storageClass, source.id, target.id));
83335
83586
  }
83336
83587
  if (moves.length === 0) throw new Error("select at least one storage class");
@@ -83356,38 +83607,17 @@ var require_storage_orchestrator_addon = __commonJS({
83356
83607
  }
83357
83608
  return {
83358
83609
  destinations: input.destinations,
83610
+ sources: input.sources,
83359
83611
  mode,
83360
- moves: moves.map(({ storageClass, fromLocationId, toLocationId }) => ({
83612
+ moves: moves.map(({ storageClass, fromLocationId, toLocationId, freezeSource }) => ({
83361
83613
  storageClass,
83362
83614
  fromLocationId,
83363
- toLocationId
83615
+ toLocationId,
83616
+ freezeSource
83364
83617
  })),
83365
83618
  findings
83366
83619
  };
83367
83620
  }
83368
- /**
83369
- * HARD refusal: `galleryMedia` onto a volume a recordings class lives on.
83370
- *
83371
- * This is the one same-device case that is a refusal rather than a finding,
83372
- * because its consequence is silent and arrives months later — the gallery is
83373
- * wiped by the next footage wipe, quota eviction or recordings disk swap, and
83374
- * nothing at that moment points back at this migration.
83375
- *
83376
- * It can only refuse what it can SEE. `deviceKeyOf` is a realpath, so it
83377
- * catches a symlink, a bind mount and a literally-shared directory — and
83378
- * misses two distinct directories on one filesystem, and any location pinned
83379
- * to another node. Those come back `null`/distinct and produce a
83380
- * `deviceIdentityUnknown` finding instead.
83381
- */
83382
- async refuseGalleryOntoRecordingsVolume(storageClass, targetId) {
83383
- if (storageClass !== "galleryMedia") return;
83384
- const targetKey = await this.deviceKey(targetId);
83385
- if (targetKey === null) return;
83386
- for (const type of GALLERY_FORBIDDEN_NEIGHBOUR_TYPES) for (const neighbour of this.deps.locations.listLocations({ type })) {
83387
- if (await this.deviceKey(neighbour.id) !== targetKey) continue;
83388
- throw new Error(`Storage location "${targetId}" shares a volume with "${neighbour.id}" (${type}). galleryMedia is deliberately NOT on the recordings volume: enrolment images survive every retention sweep, so they must also survive a footage wipe, a quota eviction and a recordings disk swap. Choose a destination on another volume.`);
83389
- }
83390
- }
83391
83621
  /** Same-device observations that are reported, not refused. */
83392
83622
  async deviceFindings(storageClass, sourceId, targetId) {
83393
83623
  if (this.deps.deviceKeyOf === void 0) return [];
@@ -83413,12 +83643,12 @@ var require_storage_orchestrator_addon = __commonJS({
83413
83643
  this.startReserved = true;
83414
83644
  try {
83415
83645
  const existing = await this.status();
83416
- if (existing && isTerminal(existing) && existing.pausedParticipants.length > 0) {
83646
+ if (existing && isTerminal$1(existing) && existing.pausedParticipants.length > 0) {
83417
83647
  await this.releaseAfterTerminal(existing);
83418
83648
  await this.persist(existing);
83419
83649
  if (existing.pausedParticipants.length > 0) throw new Error(`storage migration ${existing.jobId} still holds maintenance leases`);
83420
83650
  }
83421
- if (existing && !isTerminal(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
83651
+ if (existing && !isTerminal$1(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
83422
83652
  const plan = await this.plan(input);
83423
83653
  const now = this.deps.now();
83424
83654
  const job = {
@@ -83426,6 +83656,7 @@ var require_storage_orchestrator_addon = __commonJS({
83426
83656
  phase: "planning",
83427
83657
  mode: plan.mode,
83428
83658
  destinations: input.destinations,
83659
+ sources: input.sources,
83429
83660
  throttleMbps: input.throttleMbps ?? 40,
83430
83661
  moves: plan.moves.map((move) => ({
83431
83662
  ...move,
@@ -83457,10 +83688,15 @@ var require_storage_orchestrator_addon = __commonJS({
83457
83688
  if (jobId !== void 0 && job?.jobId !== jobId) return null;
83458
83689
  return job;
83459
83690
  }
83691
+ /** Finished jobs, newest first, excluding the one `status` currently shows. */
83692
+ async history() {
83693
+ const current = await this.status();
83694
+ return (await this.deps.history?.get() ?? []).filter((job) => job.jobId !== current?.jobId);
83695
+ }
83460
83696
  async cancel(jobId) {
83461
83697
  const job = await this.status(jobId);
83462
83698
  const cutoverInFlight = job !== null && job.repointed && (job.phase === "refreshing" || job.phase === "resuming");
83463
- if (!job || isTerminal(job) || cutoverInFlight) return false;
83699
+ if (!job || isTerminal$1(job) || cutoverInFlight) return false;
83464
83700
  job.cancelRequested = true;
83465
83701
  await this.persist(job);
83466
83702
  await Promise.all(job.moves.filter((move) => move.moverJobId !== null).map((move) => this.cancelMove(move)));
@@ -83517,18 +83753,22 @@ var require_storage_orchestrator_addon = __commonJS({
83517
83753
  const target = this.deps.locations.getDefaultLocation(storageClass);
83518
83754
  if (!target) continue;
83519
83755
  if (laneOf(storageClass) === "media") {
83520
- const count = await unanswerable(this.deps.participants.analytics.residue({
83521
- toLocationId: target.id,
83522
- mode: storageClass === "galleryMedia" ? "gallery" : "move"
83523
- }));
83524
- if (count !== null && count.rows === 0) continue;
83525
- out.push({
83526
- storageClass,
83527
- fromLocationId: "*",
83528
- toLocationId: target.id,
83529
- items: count?.rows ?? null,
83530
- bytes: null
83531
- });
83756
+ for (const source of this.deps.locations.listLocations({ type: storageClass })) {
83757
+ if (source.id === target.id) continue;
83758
+ const count = await unanswerable(this.deps.participants.analytics.residue({
83759
+ toLocationId: target.id,
83760
+ fromLocationId: source.id,
83761
+ mode: "move"
83762
+ }));
83763
+ if (count !== null && count.rows === 0) continue;
83764
+ out.push({
83765
+ storageClass,
83766
+ fromLocationId: source.id,
83767
+ toLocationId: target.id,
83768
+ items: count?.rows ?? null,
83769
+ bytes: null
83770
+ });
83771
+ }
83532
83772
  continue;
83533
83773
  }
83534
83774
  for (const source of this.deps.locations.listLocations({ type: storageClass })) {
@@ -83582,12 +83822,12 @@ var require_storage_orchestrator_addon = __commonJS({
83582
83822
  this.startReserved = true;
83583
83823
  try {
83584
83824
  const existing = await this.status();
83585
- if (existing && isTerminal(existing) && existing.pausedParticipants.length > 0) {
83825
+ if (existing && isTerminal$1(existing) && existing.pausedParticipants.length > 0) {
83586
83826
  await this.releaseAfterTerminal(existing);
83587
83827
  await this.persist(existing);
83588
83828
  if (existing.pausedParticipants.length > 0) throw new Error(`storage migration ${existing.jobId} still holds maintenance leases`);
83589
83829
  }
83590
- if (existing && !isTerminal(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
83830
+ if (existing && !isTerminal$1(existing)) throw new Error(`storage migration is already active (${existing.jobId})`);
83591
83831
  for (const storageClass of input.classes) {
83592
83832
  if (MOVER_CLASSES.includes(storageClass)) continue;
83593
83833
  throw new Error(`No mover owns "${storageClass}" \u2014 there is nothing that can drain it. Move it by hand.`);
@@ -83652,7 +83892,7 @@ var require_storage_orchestrator_addon = __commonJS({
83652
83892
  await this.persist(job);
83653
83893
  return;
83654
83894
  }
83655
- if (isTerminal(job)) {
83895
+ if (isTerminal$1(job)) {
83656
83896
  if (job.pausedParticipants.length > 0) {
83657
83897
  await this.releaseAfterTerminal(job);
83658
83898
  await this.persist(job);
@@ -83713,11 +83953,12 @@ var require_storage_orchestrator_addon = __commonJS({
83713
83953
  if (job.phase === "verifying" && !nonBlocking) {
83714
83954
  await this.verifyMoves(job);
83715
83955
  if (job.cancelRequested) return this.finishCancelled(job);
83956
+ await this.freezeDrainedSources(job);
83716
83957
  await this.setPhase(job, "repointing");
83717
83958
  }
83718
83959
  if (job.phase === "repointing") {
83719
- const targets = new Map(job.moves.map((move) => [move.storageClass, move.toLocationId]));
83720
- await this.deps.locations.setDefaultLocations(targets);
83960
+ const targets = new Map(job.moves.filter((move) => move.freezeSource !== true).map((move) => [move.storageClass, move.toLocationId]));
83961
+ if (targets.size > 0) await this.deps.locations.setDefaultLocations(targets);
83721
83962
  job.repointed = true;
83722
83963
  await this.setPhase(job, "refreshing");
83723
83964
  }
@@ -83740,6 +83981,7 @@ var require_storage_orchestrator_addon = __commonJS({
83740
83981
  if (job.phase === "verifying" && nonBlocking) {
83741
83982
  await this.verifyMoves(job);
83742
83983
  if (job.cancelRequested) return this.finishCancelled(job);
83984
+ await this.freezeDrainedSources(job);
83743
83985
  await this.setPhase(job, "done");
83744
83986
  }
83745
83987
  } catch (err) {
@@ -83755,7 +83997,7 @@ var require_storage_orchestrator_addon = __commonJS({
83755
83997
  await this.releaseAfterTerminal(job);
83756
83998
  await this.persist(job);
83757
83999
  } finally {
83758
- if (isTerminal(job)) {
84000
+ if (isTerminal$1(job)) {
83759
84001
  await this.releaseAfterTerminal(job);
83760
84002
  this.active = job;
83761
84003
  }
@@ -83799,9 +84041,7 @@ var require_storage_orchestrator_addon = __commonJS({
83799
84041
  *
83800
84042
  * Two lanes — the recorder's footage engine and post-analysis's media engine
83801
84043
  * — run in parallel with each other and strictly FIFO within themselves.
83802
- * `galleryMedia` shares the media lane with `eventMedia` because it shares
83803
- * the same `MediaRelocateEngine`, which refuses a second running job; starting
83804
- * both would fail the second one instantly.
84044
+ * `eventMedia` is the only class on the media lane.
83805
84045
  */
83806
84046
  async startOrResumeMoves(job) {
83807
84047
  if (job.cancelRequested) return;
@@ -83918,6 +84158,24 @@ var require_storage_orchestrator_addon = __commonJS({
83918
84158
  for (const move of job.moves) if (move.state !== "done") throw new Error(`${move.storageClass} move did not complete verification`);
83919
84159
  }
83920
84160
  /**
84161
+ * After a from→to copy whose source was NOT the class default: stop new
84162
+ * writes to `from`. The default is unchanged (that is the whole point of
84163
+ * naming a non-default source). A location that is already disabled is
84164
+ * left alone.
84165
+ */
84166
+ freezeDrainedSources(job) {
84167
+ for (const move of job.moves) {
84168
+ if (move.freezeSource !== true) continue;
84169
+ const loc = this.deps.locations.getLocationById(move.fromLocationId);
84170
+ if (!loc || loc.enabled === false) continue;
84171
+ const { createdAt: _c, updatedAt: _u, capacity: _cap, ...rest } = loc;
84172
+ this.deps.locations.upsertLocation({
84173
+ ...rest,
84174
+ enabled: false
84175
+ });
84176
+ }
84177
+ }
84178
+ /**
83921
84179
  * Arm one class's mover.
83922
84180
  *
83923
84181
  * `draining` runs after every writer has been resumed, so there is no lease
@@ -83930,8 +84188,9 @@ var require_storage_orchestrator_addon = __commonJS({
83930
84188
  if (laneOf(move.storageClass) === "media") {
83931
84189
  const input2 = {
83932
84190
  toLocationId: move.toLocationId,
84191
+ fromLocationId: move.fromLocationId,
83933
84192
  throttleMbps: job.throttleMbps,
83934
- mode: move.storageClass === "galleryMedia" ? "gallery" : "move"
84193
+ mode: "move"
83935
84194
  };
83936
84195
  return (unleased ? await this.deps.participants.analytics.startDrain(input2) : await this.deps.participants.analytics.startMove({
83937
84196
  ...input2,
@@ -83963,7 +84222,7 @@ var require_storage_orchestrator_addon = __commonJS({
83963
84222
  }
83964
84223
  async setPhase(job, phase) {
83965
84224
  job.phase = phase;
83966
- if (isTerminal(job)) job.finishedAt = this.deps.now();
84225
+ if (isTerminal$1(job)) job.finishedAt = this.deps.now();
83967
84226
  await this.persist(job);
83968
84227
  }
83969
84228
  async finishCancelled(job) {
@@ -83975,6 +84234,9 @@ var require_storage_orchestrator_addon = __commonJS({
83975
84234
  async persist(job) {
83976
84235
  job.updatedAt = this.deps.now();
83977
84236
  await this.deps.state.set(job);
84237
+ if (!isTerminal$1(job) || this.deps.history === void 0) return;
84238
+ const prev = [...await this.deps.history.get() ?? []];
84239
+ await this.deps.history.set([job, ...prev.filter((row) => row.jobId !== job.jobId)].slice(0, HISTORY_CAP));
83978
84240
  }
83979
84241
  };
83980
84242
  async function unanswerable(read) {
@@ -83988,11 +84250,16 @@ var require_storage_orchestrator_addon = __commonJS({
83988
84250
  if (job.pauseLeaseId === null) throw new Error("storage migration has no maintenance lease");
83989
84251
  return job.pauseLeaseId;
83990
84252
  }
83991
- function isTerminal(job) {
84253
+ function isTerminal$1(job) {
83992
84254
  return job.phase === "done" || job.phase === "failed" || job.phase === "cancelled";
83993
84255
  }
84256
+ var HISTORY_CAP = 20;
83994
84257
  function laneOf(storageClass) {
83995
- return storageClass === "eventMedia" || storageClass === "galleryMedia" ? "media" : "footage";
84258
+ return storageClass === "eventMedia" ? "media" : "footage";
84259
+ }
84260
+ function refuseRetiredGalleryClass(destinations) {
84261
+ if (destinations.galleryMedia === void 0) return;
84262
+ throw new Error("galleryMedia is no longer a storage class. Enrolled gallery lives in the pipeline-analytics addon data directory, not a location the operator places.");
83996
84263
  }
83997
84264
  function runsUnleased(job) {
83998
84265
  if (job.phase === "sealing" || job.phase === "draining") return true;
@@ -84039,6 +84306,46 @@ var require_storage_orchestrator_addon = __commonJS({
84039
84306
  function canProbeOccupancyLocally(locality) {
84040
84307
  return locality === true;
84041
84308
  }
84309
+ var STORAGE_CLASS_SUBTREES = {
84310
+ eventMedia: ["events"],
84311
+ recordingsLow: ["low"],
84312
+ recordings: ["high", "mid"]
84313
+ };
84314
+ function isDotEntry(name) {
84315
+ return name.startsWith(".");
84316
+ }
84317
+ async function dirHasAnyEntry(dir) {
84318
+ let handle;
84319
+ try {
84320
+ handle = await (0, node_fs_promises.opendir)(dir);
84321
+ } catch (err) {
84322
+ if (err.code === "ENOENT") return false;
84323
+ throw err;
84324
+ }
84325
+ try {
84326
+ return await handle.read() !== null;
84327
+ } finally {
84328
+ await handle.close();
84329
+ }
84330
+ }
84331
+ async function probeLocalDirectoryOccupancy(input) {
84332
+ let names;
84333
+ try {
84334
+ names = await (0, node_fs_promises.readdir)(input.basePath);
84335
+ } catch (err) {
84336
+ if (err.code === "ENOENT") return "empty";
84337
+ return "unknown";
84338
+ }
84339
+ const visible = names.filter((name) => !isDotEntry(name));
84340
+ const subtrees = STORAGE_CLASS_SUBTREES[input.type];
84341
+ if (subtrees === void 0) return visible.length > 0 ? "occupied" : "empty";
84342
+ try {
84343
+ for (const camera of visible) for (const subtree of subtrees) if (await dirHasAnyEntry(node_path.default.join(input.basePath, camera, subtree))) return "occupied";
84344
+ } catch {
84345
+ return "unknown";
84346
+ }
84347
+ return "empty";
84348
+ }
84042
84349
  function resolveEngine(getEngines) {
84043
84350
  const engines = getEngines();
84044
84351
  if (engines.length === 0) throw new Error("settings-store: no data-store-provider engine is registered \u2014 the data door has nothing behind it");
@@ -84128,6 +84435,164 @@ var require_storage_orchestrator_addon = __commonJS({
84128
84435
  }
84129
84436
  return out;
84130
84437
  }
84438
+ var FOOTAGE_TYPES = /* @__PURE__ */ new Set(["recordings", "recordingsLow"]);
84439
+ function isTerminal(job) {
84440
+ return job.phase === "done" || job.phase === "failed" || job.phase === "cancelled";
84441
+ }
84442
+ function footageFrozen(location) {
84443
+ return location.enabled === false || location.config["readOnly"] === true;
84444
+ }
84445
+ var StorageCleanupCoordinator = class {
84446
+ deps;
84447
+ active = null;
84448
+ startReserved = false;
84449
+ constructor(deps) {
84450
+ this.deps = deps;
84451
+ }
84452
+ async start(input) {
84453
+ if (this.startReserved) throw new Error("storage cleanup is already active");
84454
+ this.startReserved = true;
84455
+ try {
84456
+ const existing = await this.status();
84457
+ if (existing && !isTerminal(existing)) throw new Error(`storage cleanup is already active (${existing.jobId})`);
84458
+ const now = this.deps.now();
84459
+ const job = {
84460
+ jobId: this.deps.newId(),
84461
+ phase: "orphans",
84462
+ includeDebugMedia: input.includeDebugMedia === true,
84463
+ orphansReclaimed: 0,
84464
+ orphanBytesReclaimed: 0,
84465
+ debugMediaReclaimed: 0,
84466
+ debugMediaBytesReclaimed: 0,
84467
+ ghostsForgotten: 0,
84468
+ ghostBytesForgotten: 0,
84469
+ detail: "Starting orphan reclaim",
84470
+ cancelRequested: false,
84471
+ startedAt: now,
84472
+ updatedAt: now,
84473
+ finishedAt: null,
84474
+ error: null
84475
+ };
84476
+ await this.persist(job);
84477
+ this.active = job;
84478
+ this.run(job);
84479
+ return job.jobId;
84480
+ } finally {
84481
+ this.startReserved = false;
84482
+ }
84483
+ }
84484
+ async status(jobId) {
84485
+ const job = this.active ?? await this.deps.state.get();
84486
+ if (jobId !== void 0 && job?.jobId !== jobId) return null;
84487
+ return job;
84488
+ }
84489
+ async cancel(jobId) {
84490
+ const job = await this.status(jobId);
84491
+ if (!job || isTerminal(job)) return false;
84492
+ job.cancelRequested = true;
84493
+ job.detail = "Cancel requested \u2014 finishing the current step";
84494
+ await this.persist(job);
84495
+ return true;
84496
+ }
84497
+ async run(job) {
84498
+ try {
84499
+ await this.reclaimOrphans(job);
84500
+ if (await this.stopped(job)) return;
84501
+ if (job.includeDebugMedia) {
84502
+ await this.setPhase(job, "debug-media", "Reclaiming debug media");
84503
+ await this.reclaimDebugMedia(job);
84504
+ if (await this.stopped(job)) return;
84505
+ }
84506
+ await this.setPhase(job, "ghost-ledger", "Forgetting ghost footage rows");
84507
+ await this.forgetGhosts(job);
84508
+ if (await this.stopped(job)) return;
84509
+ await this.finish(job, "done", null);
84510
+ } catch (err) {
84511
+ const message = err instanceof Error ? err.message : String(err);
84512
+ await this.finish(job, "failed", message);
84513
+ }
84514
+ }
84515
+ async reclaimOrphans(job) {
84516
+ let restart = true;
84517
+ for (; ; ) {
84518
+ if (await this.stopped(job)) return;
84519
+ const start = await this.deps.participants.startOrphan(restart);
84520
+ restart = false;
84521
+ if (!start.started && !start.alreadyRunning) throw new Error("orphan reclaim did not start");
84522
+ const status = await this.waitUntilIdle(() => this.deps.participants.orphanStatus(), job, (s) => `Orphan reclaim: ${s.totalReclaimed} rows / ${String(s.totalBytesReclaimed)} bytes`);
84523
+ job.orphansReclaimed += status.totalReclaimed;
84524
+ job.orphanBytesReclaimed += status.totalBytesReclaimed;
84525
+ job.detail = `Orphans: ${job.orphansReclaimed} rows, ${String(job.orphanBytesReclaimed)} bytes`;
84526
+ await this.persist(job);
84527
+ if (status.error) throw new Error(status.error);
84528
+ if (status.complete === true) return;
84529
+ }
84530
+ }
84531
+ async reclaimDebugMedia(job) {
84532
+ const start = await this.deps.participants.startDebugMedia();
84533
+ if (!start.started && !start.alreadyRunning) throw new Error("debug-media reclaim did not start");
84534
+ const status = await this.waitUntilIdle(() => this.deps.participants.debugMediaStatus(), job, (s) => `Debug media: ${s.totalReclaimed} files`);
84535
+ job.debugMediaReclaimed = status.totalReclaimed;
84536
+ job.debugMediaBytesReclaimed = status.totalBytesReclaimed;
84537
+ await this.persist(job);
84538
+ if (status.error) throw new Error(status.error);
84539
+ }
84540
+ async forgetGhosts(job) {
84541
+ const frozen = this.deps.locations.listLocations().filter((l) => FOOTAGE_TYPES.has(l.type) && footageFrozen(l));
84542
+ if (frozen.length === 0) {
84543
+ job.detail = "No frozen footage locations";
84544
+ await this.persist(job);
84545
+ return;
84546
+ }
84547
+ for (const loc of frozen) {
84548
+ if (await this.stopped(job)) return;
84549
+ job.detail = `Ghost ledger: ${loc.id}`;
84550
+ await this.persist(job);
84551
+ const report = await this.deps.participants.walkLedger({
84552
+ locationId: loc.id,
84553
+ apply: true
84554
+ });
84555
+ job.ghostsForgotten += report.forgottenSegments;
84556
+ job.ghostBytesForgotten += report.forgottenBytes;
84557
+ await this.persist(job);
84558
+ }
84559
+ }
84560
+ async waitUntilIdle(read, job, detail) {
84561
+ const pollMs = this.deps.pollMs ?? 2e3;
84562
+ const sleep = this.deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
84563
+ for (; ; ) {
84564
+ const status = await read();
84565
+ job.detail = detail(status);
84566
+ await this.persist(job);
84567
+ if (!status.running) return status;
84568
+ await sleep(pollMs);
84569
+ }
84570
+ }
84571
+ async stopped(job) {
84572
+ if ((await this.status(job.jobId))?.cancelRequested === true) {
84573
+ await this.finish(job, "cancelled", null);
84574
+ return true;
84575
+ }
84576
+ return false;
84577
+ }
84578
+ async setPhase(job, phase, detail) {
84579
+ job.phase = phase;
84580
+ job.detail = detail;
84581
+ await this.persist(job);
84582
+ }
84583
+ async finish(job, phase, error) {
84584
+ job.phase = phase;
84585
+ job.error = error;
84586
+ job.finishedAt = this.deps.now();
84587
+ job.detail = phase === "done" ? "Cleanup finished" : job.detail;
84588
+ await this.persist(job);
84589
+ }
84590
+ async persist(job) {
84591
+ job.updatedAt = this.deps.now();
84592
+ this.active = job;
84593
+ await this.deps.state.set(job);
84594
+ }
84595
+ };
84131
84596
  var SESSION_VOCABULARY = {
84132
84597
  upload: {
84133
84598
  idLabel: "uploadId",
@@ -84215,7 +84680,7 @@ var require_storage_orchestrator_addon = __commonJS({
84215
84680
  * Reconciles both directions so operator edits survive a reboot:
84216
84681
  * 1. hydrate — DB rows win over any early in-memory seed (restores
84217
84682
  * operator config like `minFreePercent` that the pre-store boot
84218
- * can't see), with the same `isSystem` upgrade as {@link initialize};
84683
+ * can't see);
84219
84684
  * 2. backfill — in-memory locations the DB doesn't have yet (the
84220
84685
  * pre-store seed defaults on a fresh install) are persisted, so the
84221
84686
  * store becomes the durable source of truth from here on.
@@ -84237,15 +84702,7 @@ var require_storage_orchestrator_addon = __commonJS({
84237
84702
  const rows = await store.loadAll();
84238
84703
  this.locationStore = store;
84239
84704
  const dbIds = new Set(rows.map((r) => r.id));
84240
- for (const loc of rows) {
84241
- const upgraded = !loc.isSystem && loc.id === `${loc.type}:default` ? {
84242
- ...loc,
84243
- isSystem: true
84244
- } : loc;
84245
- this.locations.set(upgraded.id, upgraded);
84246
- if (upgraded !== loc) store.upsert(upgraded).catch(() => {
84247
- });
84248
- }
84705
+ for (const loc of rows) this.locations.set(loc.id, loc);
84249
84706
  const backfill = [...this.locations.values()].filter((l) => !dbIds.has(l.id));
84250
84707
  for (const loc of backfill) store.upsert(loc).catch((err) => {
84251
84708
  this.logger.warn("storage-orchestrator: attachStore backfill persist failed", { meta: {
@@ -84263,14 +84720,13 @@ var require_storage_orchestrator_addon = __commonJS({
84263
84720
  /**
84264
84721
  * Shout about a persisted row that is BOTH the type default and disabled.
84265
84722
  *
84266
- * `upsertLocation` cannot produce this (`resolveEnabled` force-enables a
84267
- * default) and neither can `setDefaultLocations` but hydrate writes rows
84268
- * straight into the map without either, so a hand-edited store, a partial
84269
- * migration or a row written by another build can. It matters because every
84270
- * bare-type ref resolves through {@link getDefaultLocation}: on such a row
84723
+ * `upsertLocation` can now produce this (enabled is the write set; `isDefault`
84724
+ * is store residue). Hydrate also writes rows straight into the map, so a
84725
+ * hand-edited store or a row written by another build can too. It matters
84726
+ * because every bare-type ref still resolves through {@link getDefaultLocation}
84727
+ * until Stage 5 of the enabled-write-set design: on such a row
84271
84728
  * `storage.write({ location: 'eventMedia' })` and the migration coordinator's
84272
- * drain destination both point at a disk the operator turned off. That is the
84273
- * one thing this model must never do silently.
84729
+ * drain destination both point at a disk the operator turned off.
84274
84730
  *
84275
84731
  * Reported, never repaired: flipping an operator's flag back on at boot would
84276
84732
  * be the system overruling the switch instead of obeying it. The coordinator
@@ -84338,25 +84794,8 @@ var require_storage_orchestrator_addon = __commonJS({
84338
84794
  async initialize() {
84339
84795
  if (!this.locationStore) return;
84340
84796
  const rows = await this.locationStore.loadAll();
84341
- let upgraded = 0;
84342
- for (const loc of rows) if (!loc.isSystem && loc.id === `${loc.type}:default`) {
84343
- const upgradedLoc = {
84344
- ...loc,
84345
- isSystem: true
84346
- };
84347
- this.locations.set(upgradedLoc.id, upgradedLoc);
84348
- this.locationStore.upsert(upgradedLoc).catch((err) => {
84349
- this.logger.warn("storage-orchestrator: isSystem upgrade persist failed", { meta: {
84350
- id: upgradedLoc.id,
84351
- error: err instanceof Error ? err.message : String(err)
84352
- } });
84353
- });
84354
- upgraded++;
84355
- } else this.locations.set(loc.id, loc);
84356
- this.logger.info("storage-orchestrator: hydrated locations from store", { meta: {
84357
- loaded: this.locations.size,
84358
- isSystemUpgraded: upgraded
84359
- } });
84797
+ for (const loc of rows) this.locations.set(loc.id, loc);
84798
+ this.logger.info("storage-orchestrator: hydrated locations from store", { meta: { loaded: this.locations.size } });
84360
84799
  this.reportDisabledDefaults();
84361
84800
  }
84362
84801
  /**
@@ -84542,10 +84981,6 @@ var require_storage_orchestrator_addon = __commonJS({
84542
84981
  ...input,
84543
84982
  nodeId: "hub"
84544
84983
  };
84545
- if (existing?.isSystem === true) input = {
84546
- ...input,
84547
- isSystem: true
84548
- };
84549
84984
  input = {
84550
84985
  ...input,
84551
84986
  ...resolveEnabled(input, existing, this.hasAnyLocationOfType(input.type))
@@ -84555,6 +84990,7 @@ var require_storage_orchestrator_addon = __commonJS({
84555
84990
  createdAt: existing?.createdAt ?? now,
84556
84991
  updatedAt: now
84557
84992
  };
84993
+ refuseLastWriteTargetDisabled(existing, next, this.locations.values());
84558
84994
  const demoted = [];
84559
84995
  if (next.isDefault) {
84560
84996
  for (const [otherId, otherLoc] of this.locations) if (otherLoc.type === next.type && otherLoc.id !== next.id && otherLoc.isDefault) {
@@ -84591,21 +85027,22 @@ var require_storage_orchestrator_addon = __commonJS({
84591
85027
  return next;
84592
85028
  }
84593
85029
  /**
84594
- * Remove a location. Refuses to remove the default of a type unless a
84595
- * sibling default exists (defensive `upsertLocation` already ensures
84596
- * at most one default per type, but the logic guards against future
84597
- * bypass paths e.g. SQLite migration that imports two defaults).
84598
- *
84599
- * Persistence (Task 6) mirrors the delete asynchronously, with errors
84600
- * routed to the logger — see `upsertLocation` for the rationale.
85030
+ * Remove a location. Refuses the last location of a type and the last
85031
+ * enabled location of a type. `isSystem` is informational — a frozen
85032
+ * recordings `:default` is deletable once a sibling is live. Deleting
85033
+ * the flagged default promotes an enabled sibling. Occupancy still
85034
+ * refuses unless `force` is set; force never overrides uniqueness /
85035
+ * last-enabled.
84601
85036
  */
84602
85037
  async deleteLocation(id, options) {
84603
85038
  const loc = this.locations.get(id);
84604
85039
  if (!loc) throw new Error(`Storage location "${id}" not found`);
84605
- if (loc.isSystem) throw new Error(`Storage location "${id}" is system-managed and cannot be deleted. Edit its config (path / providerId) instead.`);
84606
- if (loc.isDefault) {
84607
- 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`);
84608
- }
85040
+ const ofType = [...this.locations.values()].filter((l) => l.type === loc.type);
85041
+ if (ofType.length <= 1) throw new Error(`Cannot delete "${id}" \u2014 it is the only location for type "${loc.type}"`);
85042
+ const enabledOfType = ofType.filter((l) => l.enabled !== false);
85043
+ if (loc.enabled !== false && enabledOfType.length <= 1) throw new Error(`Cannot delete "${id}" \u2014 it is the only enabled location for type "${loc.type}"`);
85044
+ const successor = loc.isDefault === true ? ofType.find((l) => l.id !== id && l.enabled !== false) : void 0;
85045
+ if (loc.isDefault === true && successor === void 0) throw new Error(`Cannot delete "${id}" \u2014 it is the only enabled location for type "${loc.type}"`);
84609
85046
  const occupancy = await this.probeOccupancy(loc);
84610
85047
  if (occupancy !== "empty") {
84611
85048
  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`);
@@ -84615,6 +85052,21 @@ var require_storage_orchestrator_addon = __commonJS({
84615
85052
  occupancy
84616
85053
  } });
84617
85054
  }
85055
+ if (successor !== void 0 && successor.isDefault !== true) {
85056
+ const now = Date.now();
85057
+ const promoted = {
85058
+ ...successor,
85059
+ isDefault: true,
85060
+ updatedAt: now
85061
+ };
85062
+ this.locations.set(successor.id, promoted);
85063
+ if (this.locationStore) this.locationStore.upsert(promoted).catch((err) => {
85064
+ this.logger.error("storage-orchestrator: default promotion persist failed", { meta: {
85065
+ id: successor.id,
85066
+ error: err instanceof Error ? err.message : String(err)
85067
+ } });
85068
+ });
85069
+ }
84618
85070
  this.locations.delete(id);
84619
85071
  if (this.locationStore) this.locationStore.delete(id).catch((err) => {
84620
85072
  this.logger.error("storage-orchestrator: delete persistence failed", { meta: {
@@ -84641,15 +85093,14 @@ var require_storage_orchestrator_addon = __commonJS({
84641
85093
  }
84642
85094
  }
84643
85095
  /**
84644
- * Remove system-seeded locations whose type is no longer declared by any
84645
- * addon (stale defaults from a removed location type). Operator-added
84646
- * (non-system) locations of an undeclared type are KEPT but warned — the
84647
- * operator owns them. Requires the registry to be set first.
85096
+ * Remove seed-shaped locations (`<type>:default`) whose type is no longer
85097
+ * declared by any addon (stale defaults from a removed location type).
85098
+ * Operator-added locations of an undeclared type are KEPT but warned.
84648
85099
  *
84649
85100
  * FAIL-SAFE: if the registry is EMPTY (no addon declared any location), we
84650
85101
  * refuse to prune anything. An empty registry almost always means
84651
85102
  * declarations failed to load (boot ordering, a stale install) — pruning
84652
- * "everything undeclared" in that state would wipe every system location
85103
+ * "everything undeclared" in that state would wipe every seeded location
84653
85104
  * (data/logs/recordings/…). Better to keep stale rows than destroy live ones.
84654
85105
  */
84655
85106
  pruneUndeclaredSystemLocations() {
@@ -84660,7 +85111,7 @@ var require_storage_orchestrator_addon = __commonJS({
84660
85111
  }
84661
85112
  for (const [id, loc] of Array.from(this.locations)) {
84662
85113
  if (this.registry.cardinalityOf(loc.type) !== null) continue;
84663
- if (loc.isSystem) {
85114
+ if (loc.id === `${loc.type}:default`) {
84664
85115
  this.locations.delete(id);
84665
85116
  if (this.locationStore) this.locationStore.delete(id).catch((err) => {
84666
85117
  this.logger.error("storage-orchestrator: prune persistence failed", { meta: {
@@ -84668,7 +85119,7 @@ var require_storage_orchestrator_addon = __commonJS({
84668
85119
  error: err instanceof Error ? err.message : String(err)
84669
85120
  } });
84670
85121
  });
84671
- this.logger.info("storage-orchestrator: pruned stale system location", { meta: {
85122
+ this.logger.info("storage-orchestrator: pruned stale seeded location", { meta: {
84672
85123
  id,
84673
85124
  type: loc.type
84674
85125
  } });
@@ -84828,7 +85279,7 @@ var require_storage_orchestrator_addon = __commonJS({
84828
85279
  providerId: input.providerId,
84829
85280
  config: { basePath: base },
84830
85281
  isDefault: true,
84831
- isSystem: true
85282
+ isSystem: false
84832
85283
  });
84833
85284
  added++;
84834
85285
  }
@@ -84849,9 +85300,20 @@ var require_storage_orchestrator_addon = __commonJS({
84849
85300
  if (existing) return {};
84850
85301
  return { enabled: input.isDefault || !typeAlreadyHasLocation };
84851
85302
  }
84852
- if (input.isDefault && requested === false) return { enabled: true };
84853
85303
  return { enabled: requested };
84854
85304
  }
85305
+ var OPTIONAL_WRITE_TYPES = /* @__PURE__ */ new Set(["backups"]);
85306
+ function refuseLastWriteTargetDisabled(existing, next, locations) {
85307
+ if (OPTIONAL_WRITE_TYPES.has(next.type)) return;
85308
+ if (next.enabled !== false) return;
85309
+ if (existing === void 0) return;
85310
+ if (existing.enabled === false) return;
85311
+ for (const loc of locations) {
85312
+ if (loc.id === next.id || loc.type !== next.type) continue;
85313
+ if (loc.enabled !== false) return;
85314
+ }
85315
+ throw new Error(`Cannot disable "${next.id}": it is the last enabled location of type "${next.type}". Enable another location of this type first.`);
85316
+ }
84855
85317
  var DEFAULT_MAX_ROUNDS = 8;
84856
85318
  var DEFAULT_HYSTERESIS_PERCENT = 1;
84857
85319
  var StoragePressureManager = class {
@@ -84999,6 +85461,7 @@ var require_storage_orchestrator_addon = __commonJS({
84999
85461
  pressureTimer = null;
85000
85462
  pressureSweepInFlight = false;
85001
85463
  migration = null;
85464
+ cleanup = null;
85002
85465
  /**
85003
85466
  * Cached `providerId → nodeLocal` snapshot (SP1). Backs the orchestrator's
85004
85467
  * synchronous `NodeLocalResolver` — `getProviderInfo()` is async, so we
@@ -85056,7 +85519,7 @@ var require_storage_orchestrator_addon = __commonJS({
85056
85519
  const rows = type !== void 0 ? service.listLocations({ type }) : service.listLocations();
85057
85520
  return Promise.all(rows.map(async (loc) => ({
85058
85521
  ...this.redacted(loc),
85059
- capacity: await this.localCapacityOf(loc)
85522
+ capacity: loc.enabled === false ? null : await this.localCapacityOf(loc)
85060
85523
  })));
85061
85524
  },
85062
85525
  getDefaultLocation: async ({ type }) => {
@@ -85224,6 +85687,7 @@ var require_storage_orchestrator_addon = __commonJS({
85224
85687
  const migration = new StorageMigrationCoordinator({
85225
85688
  locations: service,
85226
85689
  state: this.state("storage-migration", require_dist10.StorageMigrationJobSchema.nullable(), null),
85690
+ history: this.state("storage-migration-history", zod.z.array(require_dist10.StorageMigrationJobSchema), []),
85227
85691
  participants: {
85228
85692
  pipeline: {
85229
85693
  pause: async (leaseId) => {
@@ -85274,6 +85738,36 @@ var require_storage_orchestrator_addon = __commonJS({
85274
85738
  deviceKeyOf: (locationId) => this.locationDeviceKey(locationId)
85275
85739
  });
85276
85740
  this.migration = migration;
85741
+ const cleanup = new StorageCleanupCoordinator({
85742
+ locations: service,
85743
+ state: this.state("storage-cleanup", require_dist10.StorageCleanupJobSchema.nullable(), null),
85744
+ participants: {
85745
+ startOrphan: async (restart) => {
85746
+ return parseStarted(await this.ctx.api.addons.custom.mutate({
85747
+ addonId: "pipeline-analytics",
85748
+ action: "retention.orphanAudit",
85749
+ input: {
85750
+ mode: "reclaim",
85751
+ restart,
85752
+ maxRowsPerScope: 2e4
85753
+ }
85754
+ }));
85755
+ },
85756
+ orphanStatus: async () => {
85757
+ return parseOrphanStatus(await this.ctx.api.addons.custom.mutate({
85758
+ addonId: "pipeline-analytics",
85759
+ action: "retention.orphanAuditStatus",
85760
+ input: {}
85761
+ }));
85762
+ },
85763
+ startDebugMedia: () => this.ctx.api.pipelineAnalytics.reclaimDebugMedia.mutate({ mode: "reclaim" }),
85764
+ debugMediaStatus: () => this.ctx.api.pipelineAnalytics.getMediaReclaimStatus.query({}),
85765
+ walkLedger: (input) => this.ctx.api.recording.reconcileLedgerAgainstDisk.mutate(input)
85766
+ },
85767
+ now: () => Date.now(),
85768
+ newId: () => (0, node_crypto.randomUUID)()
85769
+ });
85770
+ this.cleanup = cleanup;
85277
85771
  const migrationProvider = {
85278
85772
  plan: (input) => migration.plan(input),
85279
85773
  start: async (input) => ({ jobId: await migration.start(input) }),
@@ -85281,7 +85775,11 @@ var require_storage_orchestrator_addon = __commonJS({
85281
85775
  cancel: async ({ jobId }) => ({ cancelled: await migration.cancel(jobId) }),
85282
85776
  movers: () => migration.movers(),
85283
85777
  residue: () => migration.residue(),
85284
- drain: async (input) => ({ jobId: await migration.drain(input) })
85778
+ drain: async (input) => ({ jobId: await migration.drain(input) }),
85779
+ cleanupStart: async (input) => ({ jobId: await cleanup.start(input) }),
85780
+ cleanupStatus: ({ jobId }) => cleanup.status(jobId),
85781
+ cleanupCancel: async ({ jobId }) => ({ cancelled: await cleanup.cancel(jobId) }),
85782
+ history: () => migration.history()
85285
85783
  };
85286
85784
  await this.seedFromDeclarations();
85287
85785
  const eventBus = this.ctx.eventBus;
@@ -85526,11 +86024,14 @@ var require_storage_orchestrator_addon = __commonJS({
85526
86024
  }
85527
86025
  }
85528
86026
  /**
85529
- * Does this location still hold anything? A SHALLOW `readdir` of its
85530
- * `basePath` — one syscall, never a walk. `/recordings` holds one entry per
85531
- * camera, so this is cheap on a volume with millions of segments, and a
85532
- * `find`-style walk of a saturated recordings disk is precisely what must
85533
- * not happen here.
86027
+ * Does this location still hold anything of ITS class?
86028
+ *
86029
+ * Shared roots (`/recordings` hosting high + low + events) are scored by
86030
+ * class subtree (`<camera>/high|mid`, `/low`, `/events`), not by a raw
86031
+ * `readdir` of the root — camera dirs and `.camstack-location` would
86032
+ * otherwise make every class look occupied. The walk stays shallow: one
86033
+ * dirent per camera, then first-entry of the named subtree. A `find` of
86034
+ * segments is precisely what must not happen here.
85534
86035
  *
85535
86036
  * **What it cannot see, stated rather than papered over:**
85536
86037
  *
@@ -85539,9 +86040,10 @@ var require_storage_orchestrator_addon = __commonJS({
85539
86040
  * `unknown`, never `empty`.
85540
86041
  * - A location with no `basePath` — same answer.
85541
86042
  * - The difference between "holds live footage" and "holds one stray
85542
- * lock-file". It measures BYTES ON DISK, not durable rows, so it refuses
85543
- * a location holding orphan files that no index names. That direction is
85544
- * the safe one, and `force` is the escape hatch for it.
86043
+ * lock-file". It measures ON-DISK entries of this class, not durable
86044
+ * rows, so it refuses a location holding orphan files that no index
86045
+ * names. That direction is the safe one, and `force` is the escape
86046
+ * hatch for it.
85545
86047
  *
85546
86048
  * A missing root (`ENOENT`) is `empty`, not `unknown`: an unmounted disk and
85547
86049
  * a deleted directory are indistinguishable here, and if the record's own
@@ -85559,16 +86061,16 @@ var require_storage_orchestrator_addon = __commonJS({
85559
86061
  if ((location.nodeId === void 0 || location.nodeId === "" ? HUB_NODE_ID : location.nodeId) !== (this.service?.getLocalNodeId() ?? HUB_NODE_ID)) return "unknown";
85560
86062
  const basePath = this.locationBasePath(location.id);
85561
86063
  if (basePath === null) return "unknown";
85562
- try {
85563
- return (await node_fs_promises.readdir(basePath)).length > 0 ? "occupied" : "empty";
85564
- } catch (err) {
85565
- if (err instanceof Error && "code" in err && err.code === "ENOENT") return "empty";
85566
- this.ctx.logger.warn("storage-orchestrator: could not read a location root to check it", { meta: {
85567
- id: location.id,
85568
- error: err instanceof Error ? err.message : String(err)
85569
- } });
85570
- return "unknown";
85571
- }
86064
+ const occupancy = await probeLocalDirectoryOccupancy({
86065
+ type: location.type,
86066
+ basePath
86067
+ });
86068
+ if (occupancy === "unknown") this.ctx.logger.warn("storage-orchestrator: could not read a location root to check it", { meta: {
86069
+ id: location.id,
86070
+ basePath,
86071
+ type: location.type
86072
+ } });
86073
+ return occupancy;
85572
86074
  }
85573
86075
  /** Free capacity (%) on a location's volume via `statfs`; 100 (guard inert) when unstattable. */
85574
86076
  async locationFreePercent(locationId) {
@@ -85644,6 +86146,38 @@ var require_storage_orchestrator_addon = __commonJS({
85644
86146
  }
85645
86147
  }
85646
86148
  };
86149
+ function parseStarted(raw) {
86150
+ if (raw !== null && typeof raw === "object") {
86151
+ const o = raw;
86152
+ if (typeof o["started"] === "boolean" && typeof o["alreadyRunning"] === "boolean") return {
86153
+ started: o["started"],
86154
+ alreadyRunning: o["alreadyRunning"]
86155
+ };
86156
+ }
86157
+ return {
86158
+ started: false,
86159
+ alreadyRunning: false
86160
+ };
86161
+ }
86162
+ function parseOrphanStatus(raw) {
86163
+ if (raw !== null && typeof raw === "object") {
86164
+ const o = raw;
86165
+ return {
86166
+ running: o["running"] === true,
86167
+ complete: typeof o["complete"] === "boolean" ? o["complete"] : null,
86168
+ totalReclaimed: typeof o["totalReclaimed"] === "number" ? o["totalReclaimed"] : 0,
86169
+ totalBytesReclaimed: typeof o["totalBytesReclaimed"] === "number" ? o["totalBytesReclaimed"] : 0,
86170
+ error: typeof o["error"] === "string" ? o["error"] : null
86171
+ };
86172
+ }
86173
+ return {
86174
+ running: false,
86175
+ complete: true,
86176
+ totalReclaimed: 0,
86177
+ totalBytesReclaimed: 0,
86178
+ error: "orphan audit status unreadable"
86179
+ };
86180
+ }
85647
86181
  exports.SqliteLocationStore = SqliteLocationStore;
85648
86182
  exports.StorageMigrationCoordinator = StorageMigrationCoordinator;
85649
86183
  exports.StorageOrchestratorAddon = StorageOrchestratorAddon;
@@ -85680,7 +86214,7 @@ var require_system_config_addon = __commonJS({
85680
86214
  [Symbol.toStringTag]: { value: "Module" }
85681
86215
  });
85682
86216
  require_chunk_Cek0wNdY();
85683
- var require_dist10 = require_dist_B29Skpzo();
86217
+ var require_dist10 = require_dist_CFTKQGym();
85684
86218
  var SECTION_TITLES = {
85685
86219
  server: "Server",
85686
86220
  auth: "Authentication"
@@ -103741,7 +104275,7 @@ var require_winston_logging = __commonJS({
103741
104275
  [Symbol.toStringTag]: { value: "Module" }
103742
104276
  });
103743
104277
  var require_chunk = require_chunk_Cek0wNdY();
103744
- var require_dist10 = require_dist_B29Skpzo();
104278
+ var require_dist10 = require_dist_CFTKQGym();
103745
104279
  var require_formatter = require_formatter_DqAKDlvN();
103746
104280
  var node_path = __require("path");
103747
104281
  node_path = require_chunk.__toESM(node_path);
@@ -105684,9 +106218,9 @@ var require_event_category_BaEgqJNv = __commonJS({
105684
106218
  }
105685
106219
  });
105686
106220
 
105687
- // ../types/dist/sleep-DIg3xuEw.js
105688
- var require_sleep_DIg3xuEw = __commonJS({
105689
- "../types/dist/sleep-DIg3xuEw.js"(exports) {
106221
+ // ../types/dist/sleep-CqVyhcl-.js
106222
+ var require_sleep_CqVyhcl = __commonJS({
106223
+ "../types/dist/sleep-CqVyhcl-.js"(exports) {
105690
106224
  "use strict";
105691
106225
  var require_event_category = require_event_category_BaEgqJNv();
105692
106226
  var zod = require_zod();
@@ -108393,6 +108927,8 @@ var require_sleep_DIg3xuEw = __commonJS({
108393
108927
  listRelocateMediaJobs: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRelocateMediaJobs", "query", input),
108394
108928
  cancelRelocateMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "cancelRelocateMedia", "mutation", input),
108395
108929
  listOpsLog: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listOpsLog", "query", input),
108930
+ reclaimDebugMedia: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "reclaimDebugMedia", "mutation", input),
108931
+ getMediaReclaimStatus: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getMediaReclaimStatus", "query", input),
108396
108932
  getTrainingExportSummary: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrainingExportSummary", "query", input),
108397
108933
  getTrainingExportUrl: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "getTrainingExportUrl", "query", input),
108398
108934
  listRetrainStaging: (input) => dispatch("pipeline-analytics", "pipelineAnalytics", "listRetrainStaging", "query", input),
@@ -109357,7 +109893,7 @@ var require_addon = __commonJS({
109357
109893
  "use strict";
109358
109894
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
109359
109895
  var require_event_category = require_event_category_BaEgqJNv();
109360
- var require_sleep = require_sleep_DIg3xuEw();
109896
+ var require_sleep = require_sleep_CqVyhcl();
109361
109897
  var require_err_msg = require_err_msg_COpsHMw2();
109362
109898
  var CAP_INPUT_DEFAULTS = Object.freeze({
109363
109899
  "addons": { "getLogs": { "limit": 100 } },
@@ -109473,6 +110009,7 @@ var require_addon = __commonJS({
109473
110009
  "getSensorEvents": { "limit": 1e3 },
109474
110010
  "listGroups": { "limit": 40 },
109475
110011
  "listRecentTracks": { "limit": 200 },
110012
+ "reclaimDebugMedia": { "mode": "report" },
109476
110013
  "searchObjectEvents": {
109477
110014
  "limit": 50,
109478
110015
  "minScore": 0.2
@@ -116240,12 +116777,12 @@ var require_dist2 = __commonJS({
116240
116777
  }
116241
116778
  });
116242
116779
 
116243
- // ../system/dist/manifest-system-deps-DBje540e.js
116244
- var require_manifest_system_deps_DBje540e = __commonJS({
116245
- "../system/dist/manifest-system-deps-DBje540e.js"(exports) {
116780
+ // ../system/dist/manifest-system-deps-BIxY1Y5e.js
116781
+ var require_manifest_system_deps_BIxY1Y5e = __commonJS({
116782
+ "../system/dist/manifest-system-deps-BIxY1Y5e.js"(exports) {
116246
116783
  "use strict";
116247
116784
  var require_chunk = require_chunk_Cek0wNdY();
116248
- require_dist_B29Skpzo();
116785
+ require_dist_CFTKQGym();
116249
116786
  var node_crypto = __require("crypto");
116250
116787
  node_crypto = require_chunk.__toESM(node_crypto);
116251
116788
  var _camstack_types_node = require_node();
@@ -123530,6 +124067,93 @@ var require_manifest_system_deps_DBje540e = __commonJS({
123530
124067
  }
123531
124068
  await deps.installPythonRequirements(reqAbs);
123532
124069
  }
124070
+ var DISTRO_PACKAGE_ALLOWLIST = {
124071
+ apt: ["smbclient"],
124072
+ apk: ["samba-client"],
124073
+ dnf: ["samba-client"]
124074
+ };
124075
+ function packageIsAllowlisted(manager, packageName) {
124076
+ if (manager === "brew") return true;
124077
+ if (packageName.length === 0) return false;
124078
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9+._-]*$/.test(packageName)) return false;
124079
+ return DISTRO_PACKAGE_ALLOWLIST[manager].includes(packageName);
124080
+ }
124081
+ function decidePackageInstall(input) {
124082
+ if (input.manager === "brew") return { kind: "local" };
124083
+ if (!packageIsAllowlisted(input.manager, input.packageName)) return {
124084
+ kind: "refuse",
124085
+ reason: "not-allowlisted"
124086
+ };
124087
+ if (!input.helperAvailable) return {
124088
+ kind: "refuse",
124089
+ reason: "helper-unavailable"
124090
+ };
124091
+ return { kind: "helper" };
124092
+ }
124093
+ function pkgdSocketPath() {
124094
+ return process.env["CAMSTACK_PKGD_SOCKET"] ?? "/run/camstack/pkgd.sock";
124095
+ }
124096
+ function privilegedHelperAvailable(socketPath = pkgdSocketPath()) {
124097
+ return (0, node_fs.existsSync)(socketPath);
124098
+ }
124099
+ function requestPrivilegedInstall(req, socketPath = pkgdSocketPath(), timeoutMs = 360 * 1e3) {
124100
+ return new Promise((resolve) => {
124101
+ const socket = (0, node_net.createConnection)({ path: socketPath });
124102
+ const chunks = [];
124103
+ const finish = (result) => {
124104
+ socket.destroy();
124105
+ resolve(result);
124106
+ };
124107
+ const timer = setTimeout(() => {
124108
+ finish({
124109
+ ok: false,
124110
+ error: `privileged helper timed out after ${timeoutMs}ms`
124111
+ });
124112
+ }, timeoutMs);
124113
+ socket.on("connect", () => {
124114
+ socket.write(`${JSON.stringify({
124115
+ v: 1,
124116
+ ...req
124117
+ })}
124118
+ `);
124119
+ });
124120
+ socket.on("data", (data) => {
124121
+ chunks.push(data);
124122
+ const text = Buffer.concat(chunks).toString("utf8");
124123
+ const nl = text.indexOf("\n");
124124
+ if (nl === -1) return;
124125
+ clearTimeout(timer);
124126
+ try {
124127
+ const parsed = JSON.parse(text.slice(0, nl));
124128
+ finish({
124129
+ ok: parsed.ok === true,
124130
+ ...parsed.error !== void 0 ? { error: parsed.error } : {}
124131
+ });
124132
+ } catch (err) {
124133
+ finish({
124134
+ ok: false,
124135
+ error: err instanceof Error ? err.message : String(err)
124136
+ });
124137
+ }
124138
+ });
124139
+ socket.on("error", (err) => {
124140
+ clearTimeout(timer);
124141
+ finish({
124142
+ ok: false,
124143
+ error: err.message
124144
+ });
124145
+ });
124146
+ socket.on("end", () => {
124147
+ if (chunks.length === 0) {
124148
+ clearTimeout(timer);
124149
+ finish({
124150
+ ok: false,
124151
+ error: "privileged helper closed without a reply"
124152
+ });
124153
+ }
124154
+ });
124155
+ });
124156
+ }
123533
124157
  var execFileAsync = (0, node_util.promisify)(node_child_process.execFile);
123534
124158
  var INSTALL_TIMEOUT_MS = 300 * 1e3;
123535
124159
  var PROBE_TIMEOUT_MS = 1e4;
@@ -123540,12 +124164,16 @@ var require_manifest_system_deps_DBje540e = __commonJS({
123540
124164
  const detectManager = options.detectManager ?? defaultDetectManager;
123541
124165
  const run = options.run ?? defaultRun;
123542
124166
  const platform = options.platform ?? process.platform;
124167
+ const helperAvailable = options.helperAvailable ?? (async () => privilegedHelperAvailable());
124168
+ const requestInstall = options.requestInstall ?? ((req) => requestPrivilegedInstall(req));
123543
124169
  for (const dep of deps) try {
123544
124170
  await satisfyOne(dep, declaration.id, {
123545
124171
  hasBinary,
123546
124172
  detectManager,
123547
124173
  run,
123548
- platform
124174
+ platform,
124175
+ helperAvailable,
124176
+ requestInstall
123549
124177
  }, logger);
123550
124178
  } catch (err) {
123551
124179
  logger.warn("system dependency step failed", { meta: {
@@ -123583,15 +124211,43 @@ var require_manifest_system_deps_DBje540e = __commonJS({
123583
124211
  } });
123584
124212
  return;
123585
124213
  }
123586
- const plan = planFor(manager, packageName);
124214
+ const decision = decidePackageInstall({
124215
+ manager,
124216
+ packageName,
124217
+ helperAvailable: await rt.helperAvailable()
124218
+ });
124219
+ if (decision.kind === "refuse") {
124220
+ const message = decision.reason === "helper-unavailable" ? "system dependency missing and the privileged helper is not running \u2014 apt cannot run as uid 1001" : "system dependency missing and the package is not on the helper allowlist";
124221
+ logger.warn(message, { meta: {
124222
+ addonId,
124223
+ binary: dep.binary,
124224
+ manager,
124225
+ package: packageName,
124226
+ reason: decision.reason,
124227
+ ...dep.reason !== void 0 ? { reason: dep.reason } : {}
124228
+ } });
124229
+ return;
124230
+ }
123587
124231
  logger.info("installing system dependency", { meta: {
123588
124232
  addonId,
123589
124233
  binary: dep.binary,
123590
124234
  manager,
123591
- package: packageName
124235
+ package: packageName,
124236
+ via: decision.kind
123592
124237
  } });
123593
124238
  try {
123594
- for (const [command, args] of plan.steps) await rt.run(command, args);
124239
+ if (decision.kind === "helper") {
124240
+ const result = await rt.requestInstall({
124241
+ addonId,
124242
+ binary: dep.binary,
124243
+ manager,
124244
+ packageName
124245
+ });
124246
+ if (!result.ok) throw new Error(result.error ?? "privileged helper refused");
124247
+ } else {
124248
+ const plan = planFor(manager, packageName);
124249
+ for (const [command, args] of plan.steps) await rt.run(command, args);
124250
+ }
123595
124251
  } catch (err) {
123596
124252
  logger.warn("system dependency install failed \u2014 the addon loads, its capability must refuse", { meta: {
123597
124253
  addonId,
@@ -128274,7 +128930,7 @@ var require_dist3 = __commonJS({
128274
128930
  "use strict";
128275
128931
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
128276
128932
  var require_chunk = require_chunk_Cek0wNdY();
128277
- var require_dist10 = require_dist_B29Skpzo();
128933
+ var require_dist10 = require_dist_CFTKQGym();
128278
128934
  var require_builtins_alerts_alerts_addon = require_alerts_addon();
128279
128935
  require_alerts();
128280
128936
  var require_formatter = require_formatter_DqAKDlvN();
@@ -128300,7 +128956,7 @@ var require_dist3 = __commonJS({
128300
128956
  var require_builtins_winston_logging_index = require_winston_logging();
128301
128957
  var require_file_data_plane = require_file_data_plane_DO8KbxCe();
128302
128958
  var require_tls$1 = require_tls_BxQlomxd();
128303
- var require_manifest_system_deps = require_manifest_system_deps_DBje540e();
128959
+ var require_manifest_system_deps = require_manifest_system_deps_BIxY1Y5e();
128304
128960
  var require_resource_monitor = require_resource_monitor_CdnzxBLP();
128305
128961
  var require_custom_action_registry = require_custom_action_registry_jY0NOZK8();
128306
128962
  var zod = require_zod();
@@ -209142,7 +209798,7 @@ var require_dist4 = __commonJS({
209142
209798
  "use strict";
209143
209799
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
209144
209800
  var require_event_category = require_event_category_BaEgqJNv();
209145
- var require_sleep = require_sleep_DIg3xuEw();
209801
+ var require_sleep = require_sleep_CqVyhcl();
209146
209802
  var require_canonical_hash = require_canonical_hash_DNV8S5ET();
209147
209803
  var require_enums2 = require_enums();
209148
209804
  var require_err_msg = require_err_msg_COpsHMw2();
@@ -210541,6 +211197,13 @@ var require_dist4 = __commonJS({
210541
211197
  ]);
210542
211198
  var RelocateMediaInputSchema = zod.z.object({
210543
211199
  toLocationId: zod.z.string(),
211200
+ /**
211201
+ * Restrict the pass to rows currently on this location. Omitted / `'*'` =
211202
+ * every row that is not already on `toLocationId` (the historical
211203
+ * behaviour). A named source is what a from→to migration needs: without it
211204
+ * "move events off disk 2" also emptied disk 1.
211205
+ */
211206
+ fromLocationId: zod.z.string().optional(),
210544
211207
  throttleMbps: zod.z.number().min(1).max(1e3).optional(),
210545
211208
  /** Omitted = `move`, the pre-existing behaviour. */
210546
211209
  mode: MediaRelocateModeSchema.optional()
@@ -210572,9 +211235,18 @@ var require_dist4 = __commonJS({
210572
211235
  backups: zod.z.string().min(1).optional(),
210573
211236
  galleryMedia: zod.z.string().min(1).optional()
210574
211237
  }).refine((value) => Object.keys(value).length > 0, { message: "select at least one storage class" });
211238
+ var StorageMigrationSourcesSchema = zod.z.object({
211239
+ recordings: zod.z.string().min(1).optional(),
211240
+ recordingsLow: zod.z.string().min(1).optional(),
211241
+ eventMedia: zod.z.string().min(1).optional(),
211242
+ backups: zod.z.string().min(1).optional(),
211243
+ galleryMedia: zod.z.string().min(1).optional()
211244
+ }).optional();
210575
211245
  var StorageMigrationModeSchema = zod.z.enum(["blocking", "nonBlocking"]);
210576
211246
  var StorageMigrationInputSchema = zod.z.object({
210577
211247
  destinations: StorageMigrationDestinationsSchema,
211248
+ /** Omitted = each class's current default. */
211249
+ sources: StorageMigrationSourcesSchema,
210578
211250
  throttleMbps: zod.z.number().min(1).max(1e3).optional(),
210579
211251
  /** Omitted = `blocking`, which stays the default. */
210580
211252
  mode: StorageMigrationModeSchema.optional()
@@ -210620,6 +211292,13 @@ var require_dist4 = __commonJS({
210620
211292
  storageClass: StorageMigrationClassSchema,
210621
211293
  fromLocationId: zod.z.string(),
210622
211294
  toLocationId: zod.z.string(),
211295
+ /**
211296
+ * True when `from` was NOT the class default at plan time. The move still
211297
+ * copies bytes, but the default is left alone and the source is disabled
211298
+ * once the copy verifies. Absent on jobs planned before this field existed
211299
+ * — those jobs always repointed, which is `false`.
211300
+ */
211301
+ freezeSource: zod.z.boolean().optional(),
210623
211302
  moverJobId: zod.z.string().nullable(),
210624
211303
  state: RelocateJobStateSchema.nullable(),
210625
211304
  error: zod.z.string().nullable(),
@@ -210633,6 +211312,7 @@ var require_dist4 = __commonJS({
210633
211312
  * can tell a seconds-long cutover from a thirty-hour one. */
210634
211313
  mode: StorageMigrationModeSchema,
210635
211314
  destinations: StorageMigrationDestinationsSchema,
211315
+ sources: StorageMigrationSourcesSchema,
210636
211316
  throttleMbps: zod.z.number(),
210637
211317
  moves: zod.z.array(StorageMigrationMoveSchema),
210638
211318
  pauseLeaseId: zod.z.string().nullable(),
@@ -210659,6 +211339,7 @@ var require_dist4 = __commonJS({
210659
211339
  });
210660
211340
  var StorageMigrationPlanSchema = zod.z.object({
210661
211341
  destinations: StorageMigrationDestinationsSchema,
211342
+ sources: StorageMigrationSourcesSchema,
210662
211343
  /** The mode this plan was built for. A plan is only valid for its mode: the
210663
211344
  * `eventMedia` seal gate and the single-cardinality refusal both depend on
210664
211345
  * it. */
@@ -210666,7 +211347,8 @@ var require_dist4 = __commonJS({
210666
211347
  moves: zod.z.array(zod.z.object({
210667
211348
  storageClass: StorageMigrationClassSchema,
210668
211349
  fromLocationId: zod.z.string(),
210669
- toLocationId: zod.z.string()
211350
+ toLocationId: zod.z.string(),
211351
+ freezeSource: zod.z.boolean().optional()
210670
211352
  })),
210671
211353
  findings: zod.z.array(StorageMigrationFindingSchema)
210672
211354
  });
@@ -210778,9 +211460,42 @@ var require_dist4 = __commonJS({
210778
211460
  var RelocatableMediaCountSchema = zod.z.object({ rows: zod.z.number().int().nonnegative() }).nullable();
210779
211461
  var RelocatableMediaCountInputSchema = zod.z.object({
210780
211462
  toLocationId: zod.z.string().min(1),
211463
+ /** Restrict the count to one source; omitted / `'*'` = every non-target row. */
211464
+ fromLocationId: zod.z.string().optional(),
210781
211465
  /** Omitted = `move`. */
210782
211466
  mode: MediaRelocateModeSchema.optional()
210783
211467
  });
211468
+ var StorageCleanupPhaseSchema = zod.z.enum([
211469
+ "orphans",
211470
+ "debug-media",
211471
+ "ghost-ledger",
211472
+ "done",
211473
+ "failed",
211474
+ "cancelled"
211475
+ ]);
211476
+ var StorageCleanupInputSchema = zod.z.object({
211477
+ /** Also walk motion stills / track filmstrips. Off by default. */
211478
+ includeDebugMedia: zod.z.boolean().optional()
211479
+ });
211480
+ var StorageCleanupJobSchema = zod.z.object({
211481
+ jobId: zod.z.string(),
211482
+ phase: StorageCleanupPhaseSchema,
211483
+ includeDebugMedia: zod.z.boolean(),
211484
+ orphansReclaimed: zod.z.number().int().nonnegative(),
211485
+ orphanBytesReclaimed: zod.z.number().int().nonnegative(),
211486
+ debugMediaReclaimed: zod.z.number().int().nonnegative(),
211487
+ debugMediaBytesReclaimed: zod.z.number().int().nonnegative(),
211488
+ ghostsForgotten: zod.z.number().int().nonnegative(),
211489
+ ghostBytesForgotten: zod.z.number().int().nonnegative(),
211490
+ /** Short operator-facing line: current collection, pass, or location. */
211491
+ detail: zod.z.string().nullable(),
211492
+ cancelRequested: zod.z.boolean(),
211493
+ startedAt: zod.z.number(),
211494
+ updatedAt: zod.z.number(),
211495
+ finishedAt: zod.z.number().nullable(),
211496
+ error: zod.z.string().nullable()
211497
+ });
211498
+ var StorageCleanupStatusInputSchema = zod.z.object({ jobId: zod.z.string().optional() });
210784
211499
  var SUB_DETECTION_TYPES = ["face", "plate"];
210785
211500
  var RECOGNITION_TYPES = [
210786
211501
  "face",
@@ -223483,6 +224198,39 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223483
224198
  /** Present when the pass ended by throwing. */
223484
224199
  error: zod.z.string().nullable()
223485
224200
  });
224201
+ var MediaReclaimStartResultSchema = zod.z.object({
224202
+ started: zod.z.boolean(),
224203
+ /** True when a pass was already running; the new request is ignored. */
224204
+ alreadyRunning: zod.z.boolean()
224205
+ });
224206
+ var MediaReclaimInputSchema = zod.z.object({
224207
+ mode: zod.z.enum(["report", "reclaim"]).default("report"),
224208
+ scopes: zod.z.array(zod.z.enum(["motion-still", "track-filmstrip"])).min(1).optional(),
224209
+ deviceIds: zod.z.array(zod.z.number().int()).min(1).optional(),
224210
+ restart: zod.z.boolean().optional(),
224211
+ pageSize: zod.z.number().int().min(50).max(5e3).optional(),
224212
+ maxRowsPerDevice: zod.z.number().int().min(1).max(5e5).optional(),
224213
+ maxReclaimPerDevice: zod.z.number().int().min(1).max(5e5).optional(),
224214
+ maxBytesPerRun: zod.z.number().int().min(1).optional(),
224215
+ budgetMinutes: zod.z.number().int().min(1).max(720).optional(),
224216
+ throttleBytesPerSec: zod.z.number().int().min(64 * 1024).optional(),
224217
+ graceMinutes: zod.z.number().int().min(1).max(10080).optional()
224218
+ });
224219
+ var MediaReclaimStatusSchema = zod.z.object({
224220
+ running: zod.z.boolean(),
224221
+ mode: zod.z.enum(["report", "reclaim"]).nullable(),
224222
+ totalExamined: zod.z.number(),
224223
+ totalEligible: zod.z.number(),
224224
+ totalReclaimed: zod.z.number(),
224225
+ totalBytesReclaimed: zod.z.number(),
224226
+ totalRefused: zod.z.number(),
224227
+ /** Device+scope windows finished in this pass. */
224228
+ devicesDone: zod.z.number(),
224229
+ complete: zod.z.boolean().nullable(),
224230
+ startedAtMs: zod.z.number().nullable(),
224231
+ finishedAtMs: zod.z.number().nullable(),
224232
+ error: zod.z.string().nullable()
224233
+ });
223486
224234
  var ReplayFrameInputSchema = zod.z.object({
223487
224235
  timestamp: zod.z.number(),
223488
224236
  frame: PipelineRunResultBridge
@@ -223906,6 +224654,20 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
223906
224654
  auth: "admin"
223907
224655
  }),
223908
224656
  /**
224657
+ * Reclaim the standing debug-media backlog (motion-visit stills + track
224658
+ * filmstrips). DETACHED: returns `{ started }` immediately. The pass is
224659
+ * tens of minutes; awaiting it is a timeout, not a verdict. Dry-run unless
224660
+ * `mode: 'reclaim'`. Poll `getMediaReclaimStatus`.
224661
+ */
224662
+ reclaimDebugMedia: require_sleep.method(MediaReclaimInputSchema, MediaReclaimStartResultSchema, {
224663
+ kind: "mutation",
224664
+ auth: "admin"
224665
+ }),
224666
+ getMediaReclaimStatus: require_sleep.method(zod.z.object({}), MediaReclaimStatusSchema, {
224667
+ kind: "query",
224668
+ auth: "admin"
224669
+ }),
224670
+ /**
223909
224671
  * The CHEAP QUESTION, asked before any media moves: how big is the dataset
223910
224672
  * the marked (`markForTrain`) tracks would produce?
223911
224673
  *
@@ -226657,7 +227419,23 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
226657
227419
  drain: require_sleep.method(StorageMigrationDrainInputSchema, zod.z.object({ jobId: zod.z.string() }), {
226658
227420
  kind: "mutation",
226659
227421
  auth: "admin"
226660
- })
227422
+ }),
227423
+ /**
227424
+ * One operator cleanup of leftover analytics (DB + blobs) and ghost
227425
+ * ledger rows on frozen footage locations. Optional debug-media sweep.
227426
+ * Returns immediately; poll {@link cleanupStatus}.
227427
+ */
227428
+ cleanupStart: require_sleep.method(StorageCleanupInputSchema, zod.z.object({ jobId: zod.z.string() }), {
227429
+ kind: "mutation",
227430
+ auth: "admin"
227431
+ }),
227432
+ cleanupStatus: require_sleep.method(StorageCleanupStatusInputSchema, StorageCleanupJobSchema.nullable(), { auth: "admin" }),
227433
+ cleanupCancel: require_sleep.method(zod.z.object({ jobId: zod.z.string() }), zod.z.object({ cancelled: zod.z.boolean() }), {
227434
+ kind: "mutation",
227435
+ auth: "admin"
227436
+ }),
227437
+ /** Recent finished migration jobs, newest first. The live job is `status`. */
227438
+ history: require_sleep.method(zod.z.object({}), zod.z.array(StorageMigrationJobSchema).readonly(), { auth: "admin" })
226661
227439
  }
226662
227440
  };
226663
227441
  var ProviderInfoSchema = zod.z.discriminatedUnion("shouldSaveDiskSpace", [zod.z.object({
@@ -233458,6 +234236,26 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233458
234236
  /** Ignore piles smaller than this (default 1 GB). */
233459
234237
  minMoveGb: zod.z.number().min(0).optional()
233460
234238
  });
234239
+ var RecordingDevicePlacementSchema = zod.z.object({
234240
+ deviceId: zod.z.number().int(),
234241
+ profile: zod.z.string(),
234242
+ locationId: zod.z.string()
234243
+ });
234244
+ var RecordingDevicePinSchema = zod.z.object({
234245
+ deviceId: zod.z.number().int(),
234246
+ /** Recordings-class location this camera is pinned to. */
234247
+ locationId: zod.z.string()
234248
+ });
234249
+ var RecordingPlacementViewSchema = zod.z.object({
234250
+ assignments: zod.z.array(RecordingDevicePlacementSchema),
234251
+ pins: zod.z.array(RecordingDevicePinSchema),
234252
+ defaultLocations: zod.z.record(zod.z.string(), zod.z.string())
234253
+ });
234254
+ var RecordingSetDevicePlacementInputSchema = zod.z.object({
234255
+ deviceId: zod.z.number().int(),
234256
+ /** `null` = Auto. Otherwise a `recordings:*` location id. */
234257
+ locationId: zod.z.string().nullable()
234258
+ });
233461
234259
  var LocateSegmentResultSchema = zod.z.discriminatedUnion("kind", [zod.z.object({
233462
234260
  kind: zod.z.literal("segment"),
233463
234261
  startMs: zod.z.number(),
@@ -233776,6 +234574,22 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
233776
234574
  startStorageRebalance: require_sleep.method(RecordingRebalanceInputSchema, RecordingRebalancePlanSchema, {
233777
234575
  kind: "mutation",
233778
234576
  auth: "admin"
234577
+ }),
234578
+ /**
234579
+ * The placement plan in force plus operator pins. Drives the Locations
234580
+ * admin page: Auto vs a named recordings location, per camera.
234581
+ */
234582
+ getPlacement: require_sleep.method(zod.z.object({}), RecordingPlacementViewSchema, {
234583
+ kind: "query",
234584
+ auth: "admin"
234585
+ }),
234586
+ /**
234587
+ * Pin a camera to a recordings location, or clear the pin (Auto). High and
234588
+ * mid follow the pin; low stays with the recordingsLow planner.
234589
+ */
234590
+ setDevicePlacement: require_sleep.method(RecordingSetDevicePlacementInputSchema, zod.z.object({ ok: zod.z.literal(true) }), {
234591
+ kind: "mutation",
234592
+ auth: "admin"
233779
234593
  })
233780
234594
  }
233781
234595
  };
@@ -242825,6 +243639,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242825
243639
  addonId: null,
242826
243640
  access: "view"
242827
243641
  },
243642
+ "pipelineAnalytics.getMediaReclaimStatus": {
243643
+ capName: "pipeline-analytics",
243644
+ capScope: "device",
243645
+ addonId: null,
243646
+ access: "view"
243647
+ },
242828
243648
  "pipelineAnalytics.getMotionEvents": {
242829
243649
  capName: "pipeline-analytics",
242830
243650
  capScope: "device",
@@ -242999,6 +243819,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
242999
243819
  addonId: null,
243000
243820
  access: "create"
243001
243821
  },
243822
+ "pipelineAnalytics.reclaimDebugMedia": {
243823
+ capName: "pipeline-analytics",
243824
+ capScope: "device",
243825
+ addonId: null,
243826
+ access: "create"
243827
+ },
243002
243828
  "pipelineAnalytics.reconcileFromDisk": {
243003
243829
  capName: "pipeline-analytics",
243004
243830
  capScope: "device",
@@ -243923,6 +244749,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
243923
244749
  addonId: null,
243924
244750
  access: "view"
243925
244751
  },
244752
+ "recording.getPlacement": {
244753
+ capName: "recording",
244754
+ capScope: "system",
244755
+ addonId: null,
244756
+ access: "view"
244757
+ },
243926
244758
  "recording.getPlaybackManifest": {
243927
244759
  capName: "recording",
243928
244760
  capScope: "system",
@@ -244049,6 +244881,12 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
244049
244881
  addonId: null,
244050
244882
  access: "create"
244051
244883
  },
244884
+ "recording.setDevicePlacement": {
244885
+ capName: "recording",
244886
+ capScope: "system",
244887
+ addonId: null,
244888
+ access: "create"
244889
+ },
244052
244890
  "recording.startStorageMigrationMove": {
244053
244891
  capName: "recording",
244054
244892
  capScope: "system",
@@ -244493,12 +245331,36 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
244493
245331
  addonId: null,
244494
245332
  access: "create"
244495
245333
  },
245334
+ "storageMigration.cleanupCancel": {
245335
+ capName: "storage-migration",
245336
+ capScope: "system",
245337
+ addonId: null,
245338
+ access: "create"
245339
+ },
245340
+ "storageMigration.cleanupStart": {
245341
+ capName: "storage-migration",
245342
+ capScope: "system",
245343
+ addonId: null,
245344
+ access: "create"
245345
+ },
245346
+ "storageMigration.cleanupStatus": {
245347
+ capName: "storage-migration",
245348
+ capScope: "system",
245349
+ addonId: null,
245350
+ access: "view"
245351
+ },
244496
245352
  "storageMigration.drain": {
244497
245353
  capName: "storage-migration",
244498
245354
  capScope: "system",
244499
245355
  addonId: null,
244500
245356
  access: "create"
244501
245357
  },
245358
+ "storageMigration.history": {
245359
+ capName: "storage-migration",
245360
+ capScope: "system",
245361
+ addonId: null,
245362
+ access: "view"
245363
+ },
244502
245364
  "storageMigration.movers": {
244503
245365
  capName: "storage-migration",
244504
245366
  capScope: "system",
@@ -246873,6 +247735,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
246873
247735
  form: "single",
246874
247736
  optional: true
246875
247737
  }],
247738
+ "pipelineAnalytics.reclaimDebugMedia": [{
247739
+ name: "deviceIds",
247740
+ form: "array",
247741
+ optional: true
247742
+ }],
246876
247743
  "pipelineAnalytics.reconcileFromDisk": [{
246877
247744
  name: "deviceId",
246878
247745
  form: "single",
@@ -247238,6 +248105,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247238
248105
  form: "single",
247239
248106
  optional: false
247240
248107
  }],
248108
+ "recording.setDevicePlacement": [{
248109
+ name: "deviceId",
248110
+ form: "single",
248111
+ optional: false
248112
+ }],
247241
248113
  "recording.startStorageMigrationMove": [{
247242
248114
  name: "deviceId",
247243
248115
  form: "single",
@@ -247707,6 +248579,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
247707
248579
  "recording.renderGif",
247708
248580
  "recording.rescanStorage",
247709
248581
  "recording.setDeviceConfig",
248582
+ "recording.setDevicePlacement",
247710
248583
  "recording.startStorageMigrationMove",
247711
248584
  "recordingExport.createExport",
247712
248585
  "recordingExport.listExports",
@@ -248590,7 +249463,9 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
248590
249463
  reconcileLedgerAgainstDisk: (input) => dispatch("recording", "reconcileLedgerAgainstDisk", "mutation", input),
248591
249464
  cancelRelocateJob: (input) => dispatch("recording", "cancelRelocateJob", "mutation", input),
248592
249465
  planStorageRebalance: (input) => dispatch("recording", "planStorageRebalance", "query", input),
248593
- startStorageRebalance: (input) => dispatch("recording", "startStorageRebalance", "mutation", input)
249466
+ startStorageRebalance: (input) => dispatch("recording", "startStorageRebalance", "mutation", input),
249467
+ getPlacement: (input) => dispatch("recording", "getPlacement", "query", input),
249468
+ setDevicePlacement: (input) => dispatch("recording", "setDevicePlacement", "mutation", input)
248594
249469
  },
248595
249470
  recordingExport: {
248596
249471
  createExport: (input) => dispatch("recordingExport", "createExport", "mutation", input),
@@ -248654,7 +249529,11 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
248654
249529
  cancel: (input) => dispatch("storageMigration", "cancel", "mutation", input),
248655
249530
  movers: (input) => dispatch("storageMigration", "movers", "query", input),
248656
249531
  residue: (input) => dispatch("storageMigration", "residue", "query", input),
248657
- drain: (input) => dispatch("storageMigration", "drain", "mutation", input)
249532
+ drain: (input) => dispatch("storageMigration", "drain", "mutation", input),
249533
+ cleanupStart: (input) => dispatch("storageMigration", "cleanupStart", "mutation", input),
249534
+ cleanupStatus: (input) => dispatch("storageMigration", "cleanupStatus", "query", input),
249535
+ cleanupCancel: (input) => dispatch("storageMigration", "cleanupCancel", "mutation", input),
249536
+ history: (input) => dispatch("storageMigration", "history", "query", input)
248658
249537
  },
248659
249538
  streamBroker: {
248660
249539
  fetchEventMedia: (input) => dispatch("streamBroker", "fetchEventMedia", "mutation", input),
@@ -252271,6 +253150,10 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252271
253150
  exports.StorageBeginDownloadResultSchema = BeginDownloadResultSchema;
252272
253151
  exports.StorageBeginUploadInputSchema = BeginUploadInputSchema;
252273
253152
  exports.StorageBeginUploadResultSchema = BeginUploadResultSchema;
253153
+ exports.StorageCleanupInputSchema = StorageCleanupInputSchema;
253154
+ exports.StorageCleanupJobSchema = StorageCleanupJobSchema;
253155
+ exports.StorageCleanupPhaseSchema = StorageCleanupPhaseSchema;
253156
+ exports.StorageCleanupStatusInputSchema = StorageCleanupStatusInputSchema;
252274
253157
  exports.StorageEndDownloadInputSchema = EndDownloadInputSchema;
252275
253158
  exports.StorageFinalizeUploadInputSchema = FinalizeUploadInputSchema;
252276
253159
  exports.StorageLocationDeclarationSchema = StorageLocationDeclarationSchema;
@@ -252296,6 +253179,7 @@ ${recipe.triggers.map((t, i) => emitTrigger(t, i)).join("\n\n")}
252296
253179
  exports.StorageMigrationPhaseSchema = StorageMigrationPhaseSchema;
252297
253180
  exports.StorageMigrationPlanSchema = StorageMigrationPlanSchema;
252298
253181
  exports.StorageMigrationResidueSchema = StorageMigrationResidueSchema;
253182
+ exports.StorageMigrationSourcesSchema = StorageMigrationSourcesSchema;
252299
253183
  exports.StorageProviderInfoSchema = ProviderInfoSchema;
252300
253184
  exports.StorageReadChunkInputSchema = ReadChunkInputSchema;
252301
253185
  exports.StorageTestLocationResultSchema = TestLocationResultSchema;